add database seed from file
This commit is contained in:
parent
bad1df395a
commit
153812d79f
|
@ -13,3 +13,4 @@ vite.config.js.timestamp-*
|
||||||
vite.config.ts.timestamp-*
|
vite.config.ts.timestamp-*
|
||||||
target
|
target
|
||||||
tmp
|
tmp
|
||||||
|
seed.yaml
|
||||||
|
|
13
README.md
13
README.md
|
@ -26,19 +26,24 @@ Requirements:
|
||||||
- Redis 6.0 or later
|
- Redis 6.0 or later
|
||||||
- Node.js (latest version)
|
- Node.js (latest version)
|
||||||
- MinIO **if using avatars, flags, or data exports** (_not_ required otherwise)
|
- MinIO **if using avatars, flags, or data exports** (_not_ required otherwise)
|
||||||
|
- [Air](https://github.com/cosmtrek/air) for live reloading the backend
|
||||||
|
|
||||||
### Setup
|
### Setup
|
||||||
|
|
||||||
1. Create a PostgreSQL user and database (the user should own the database).
|
1. Create a PostgreSQL user and database (the user should own the database).
|
||||||
For example: `create user pronouns with password 'password'; create database pronouns with owner pronouns;`
|
For example: `create user pronouns with password 'password'; create database pronouns with owner pronouns;`
|
||||||
2. Copy `.env.example` in the repository root to a new file named `.env` and fill out the required options.
|
2. Copy `.env.example` in the repository root to a new file named `.env` and fill out the required options.
|
||||||
3. Run `go run -v . database migrate` to initialize the database, then optionally `go run -v . database seed` to insert a test user.
|
3. Run `go run -v . database migrate` to initialize the database, then optionally `go run -v . database seed` to insert a test user.
|
||||||
4. Run `go run -v . web` to run the backend.
|
4. Run `pnpm dev`. Alternatively, if you don't want the backend to live reload, run `go run -v . web`,
|
||||||
5. Copy `frontend/.env.example` into `frontend/.env` and tweak as necessary.
|
then change to the `frontend/` directory and run `pnpm dev`.
|
||||||
6. cd into the `frontend` directory and run `pnpm dev` to run the frontend.
|
|
||||||
|
|
||||||
See [`docs/production.md`](/docs/production.md#configuration) for more information about keys in the backend and frontend `.env` files.
|
See [`docs/production.md`](/docs/production.md#configuration) for more information about keys in the backend and frontend `.env` files.
|
||||||
|
|
||||||
|
### Seeding
|
||||||
|
|
||||||
|
To seed the database with some data, create a `seed.yaml` file, then use `go run -v . database seed`.
|
||||||
|
For the file format, refer to the `Seed` struct in `scripts/seeddb`.
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
Copyright (C) 2022 Sam <u1f320>
|
Copyright (C) 2022 Sam <u1f320>
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -28,6 +28,7 @@ require (
|
||||||
go.uber.org/zap v1.24.0
|
go.uber.org/zap v1.24.0
|
||||||
golang.org/x/oauth2 v0.7.0
|
golang.org/x/oauth2 v0.7.0
|
||||||
google.golang.org/api v0.118.0
|
google.golang.org/api v0.118.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
|
|
|
@ -1,15 +1,42 @@
|
||||||
package seeddb
|
package seeddb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
"codeberg.org/pronounscc/pronouns.cc/backend/db"
|
||||||
"github.com/jackc/pgx/v5/pgxpool"
|
"github.com/jackc/pgx/v5/pgxpool"
|
||||||
"github.com/joho/godotenv"
|
"github.com/joho/godotenv"
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/urfave/cli/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type Seed struct {
|
||||||
|
Users []SeedUser `yaml:"users"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeedUser struct {
|
||||||
|
Username string `yaml:"username"`
|
||||||
|
DisplayName *string `yaml:"displayName"`
|
||||||
|
Bio *string `yaml:"bio"`
|
||||||
|
Links []string `yaml:"links"`
|
||||||
|
Names []db.FieldEntry `yaml:"names"`
|
||||||
|
Pronouns []db.PronounEntry `yaml:"pronouns"`
|
||||||
|
Fields []db.Field `yaml:"fields"`
|
||||||
|
Members []SeedMember `yaml:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SeedMember struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
DisplayName *string `yaml:"displayName"`
|
||||||
|
Bio string `yaml:"bio"`
|
||||||
|
Links []string `yaml:"links"`
|
||||||
|
Names []db.FieldEntry `yaml:"names"`
|
||||||
|
Pronouns []db.PronounEntry `yaml:"pronouns"`
|
||||||
|
Fields []db.Field `yaml:"fields"`
|
||||||
|
Members []SeedMember `yaml:"members"`
|
||||||
|
}
|
||||||
|
|
||||||
var Command = &cli.Command{
|
var Command = &cli.Command{
|
||||||
Name: "seed",
|
Name: "seed",
|
||||||
Usage: "Seed the database with test data",
|
Usage: "Seed the database with test data",
|
||||||
|
@ -19,7 +46,7 @@ var Command = &cli.Command{
|
||||||
func run(c *cli.Context) error {
|
func run(c *cli.Context) error {
|
||||||
err := godotenv.Load()
|
err := godotenv.Load()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error loading .env file:", err)
|
log.Println("error loading .env file:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,112 +54,94 @@ func run(c *cli.Context) error {
|
||||||
|
|
||||||
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
|
pool, err := pgxpool.New(ctx, os.Getenv("DATABASE_URL"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error opening database:", err)
|
log.Println("error opening database:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer pool.Close()
|
defer pool.Close()
|
||||||
|
|
||||||
fmt.Println("opened database")
|
log.Println("opened database")
|
||||||
|
|
||||||
pg := &db.DB{Pool: pool}
|
pg := &db.DB{Pool: pool}
|
||||||
|
|
||||||
|
// read seed file
|
||||||
|
seedFile, err := os.ReadFile("seed.yaml")
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error opening seed.yaml:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var seed Seed
|
||||||
|
err = yaml.Unmarshal(seedFile, &seed)
|
||||||
|
if err != nil {
|
||||||
|
log.Println("error reading seed.yaml:", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
tx, err := pg.Begin(ctx)
|
tx, err := pg.Begin(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error beginning transaction:", err)
|
log.Println("error beginning transaction:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
defer tx.Rollback(ctx)
|
||||||
|
|
||||||
u, err := pg.CreateUser(ctx, tx, "test")
|
for i, su := range seed.Users {
|
||||||
if err != nil {
|
u, err := pg.CreateUser(ctx, tx, su.Username)
|
||||||
fmt.Println("error creating user:", err)
|
if err != nil {
|
||||||
return err
|
log.Printf("error creating user #%v/%s: %v", i+1, su.Username, err)
|
||||||
}
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
_, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), nil, ptr(false), &[]string{"https://pronouns.cc"}, nil, nil, nil)
|
_, err = pg.UpdateUser(ctx, tx, u.ID, su.DisplayName, su.Bio, nil, nil, &su.Links, nil, nil, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error setting user info:", err)
|
log.Printf("updating user %s: %v", su.Username, err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
err = pg.SetUserNamesPronouns(ctx, tx, u.ID, []db.FieldEntry{
|
err = pg.SetUserNamesPronouns(ctx, tx, u.ID, db.NotNull(su.Names), db.NotNull(su.Pronouns))
|
||||||
{Value: "testing 1", Status: "favourite"},
|
if err != nil {
|
||||||
{Value: "testing 2", Status: "okay"},
|
log.Printf("setting names/pronouns for user %s: %v", su.Username, err)
|
||||||
}, []db.PronounEntry{
|
return err
|
||||||
{Pronouns: "it/it/its/its/itself", DisplayText: ptr("it/its"), Status: "favourite"},
|
}
|
||||||
{Pronouns: "they/them/their/theirs/themself", Status: "okay"},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error setting pronouns:", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = pg.SetUserFields(ctx, tx, u.ID, []db.Field{
|
err = pg.SetUserFields(ctx, tx, u.ID, db.NotNull(su.Fields))
|
||||||
{
|
if err != nil {
|
||||||
Name: "Field 1",
|
log.Printf("setting fields for user %s: %v", su.Username, err)
|
||||||
Entries: []db.FieldEntry{
|
return err
|
||||||
{Value: "Favourite 1", Status: "favourite"},
|
}
|
||||||
{Value: "Okay 1", Status: "okay"},
|
|
||||||
{Value: "Jokingly 1", Status: "jokingly"},
|
log.Printf("creating members for user %s", su.Username)
|
||||||
{Value: "Friends only 1", Status: "friends_only"},
|
|
||||||
{Value: "Avoid 1", Status: "avoid"},
|
for _, sm := range su.Members {
|
||||||
},
|
m, err := pg.CreateMember(ctx, tx, u.ID, sm.Name, sm.DisplayName, sm.Bio, db.NotNull(sm.Links))
|
||||||
},
|
if err != nil {
|
||||||
{
|
log.Printf("creating member %s: %v", sm.Name, err)
|
||||||
Name: "Field 2",
|
return err
|
||||||
Entries: []db.FieldEntry{
|
}
|
||||||
{Value: "Favourite 2", Status: "favourite"},
|
|
||||||
{Value: "Okay 2", Status: "okay"},
|
err = pg.SetMemberNamesPronouns(ctx, tx, m.ID, db.NotNull(sm.Names), db.NotNull(sm.Pronouns))
|
||||||
{Value: "Jokingly 2", Status: "jokingly"},
|
if err != nil {
|
||||||
{Value: "Friends only 2", Status: "friends_only"},
|
log.Printf("setting names/pronouns for member %s: %v", sm.Name, err)
|
||||||
{Value: "Avoid 2", Status: "avoid"},
|
return err
|
||||||
},
|
}
|
||||||
},
|
|
||||||
{
|
err = pg.SetMemberFields(ctx, tx, m.ID, db.NotNull(sm.Fields))
|
||||||
Name: "Field 3",
|
if err != nil {
|
||||||
Entries: []db.FieldEntry{
|
log.Printf("setting fields for member %s: %v", sm.Name, err)
|
||||||
{Value: "Favourite 3", Status: "favourite"},
|
return err
|
||||||
{Value: "Okay 3", Status: "okay"},
|
}
|
||||||
{Value: "Jokingly 3", Status: "jokingly"},
|
|
||||||
{Value: "Friends only 3", Status: "friends_only"},
|
log.Printf("created member %s", sm.Name)
|
||||||
{Value: "Avoid 3", Status: "avoid"},
|
}
|
||||||
},
|
|
||||||
},
|
log.Printf("created user %s", su.Username)
|
||||||
{
|
|
||||||
Name: "Field 4",
|
|
||||||
Entries: []db.FieldEntry{
|
|
||||||
{Value: "Favourite 4", Status: "favourite"},
|
|
||||||
{Value: "Okay 4", Status: "okay"},
|
|
||||||
{Value: "Jokingly 4", Status: "jokingly"},
|
|
||||||
{Value: "Friends only 4", Status: "friends_only"},
|
|
||||||
{Value: "Avoid 4", Status: "avoid"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Name: "Field 5",
|
|
||||||
Entries: []db.FieldEntry{
|
|
||||||
{Value: "Favourite 5", Status: "favourite"},
|
|
||||||
{Value: "Okay 5", Status: "okay"},
|
|
||||||
{Value: "Jokingly 5", Status: "jokingly"},
|
|
||||||
{Value: "Friends only 5", Status: "friends_only"},
|
|
||||||
{Value: "Avoid 5", Status: "avoid"},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println("error setting fields:", err)
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = tx.Commit(ctx)
|
err = tx.Commit(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println("error committing transaction:", err)
|
log.Println("error committing transaction:", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println("Created testing user with ID", u.ID, "and name", u.Username)
|
log.Printf("seeded database with %d users", len(seed.Users))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func ptr[T any](v T) *T {
|
|
||||||
return &v
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
users:
|
||||||
|
- username: test1
|
||||||
|
displayName: "Test 1 :3"
|
||||||
|
bio: |
|
||||||
|
*Hiiiiii*
|
||||||
|
I'm a user!
|
||||||
|
links: [https://pronouns.cc, https://codeberg.org/pronounscc/pronouns.cc]
|
||||||
|
names:
|
||||||
|
- value: Test
|
||||||
|
status: favourite
|
||||||
|
- value: Tester
|
||||||
|
status: okay
|
||||||
|
- value: Testington
|
||||||
|
status: avoid
|
||||||
|
pronouns:
|
||||||
|
- pronouns: they/them/their/theirs/themself
|
||||||
|
status: favourite
|
||||||
|
- pronouns: it/it/its/its/itself
|
||||||
|
displayText: it/its
|
||||||
|
status: okay
|
||||||
|
fields:
|
||||||
|
- name: Field 1
|
||||||
|
entries:
|
||||||
|
- value: Entry 1
|
||||||
|
status: favourite
|
||||||
|
- value: Entry 2
|
||||||
|
status: favourite
|
||||||
|
- value: Entry 3
|
||||||
|
status: friends_only
|
||||||
|
- name: Field 2
|
||||||
|
entries:
|
||||||
|
- value: Entry 4
|
||||||
|
status: avoid
|
||||||
|
- value: Entry 4
|
||||||
|
status: okay
|
||||||
|
members:
|
||||||
|
- name: member 1
|
||||||
|
- name: member 2
|
||||||
|
- name: member 3
|
||||||
|
- name: member 4
|
||||||
|
- name: member 5
|
||||||
|
- name: member 6
|
||||||
|
- name: member 7
|
Loading…
Reference in New Issue