diff --git a/Makefile b/Makefile index 25af4c1..f35c130 100644 --- a/Makefile +++ b/Makefile @@ -8,7 +8,7 @@ seeddb: .PHONY: backend backend: - CGO_ENABLED=0 go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD`" ./backend + CGO_ENABLED=0 go build -v -o pronouns -ldflags="-buildid= -X codeberg.org/u1f320/pronouns.cc/backend/server.Revision=`git rev-parse --short HEAD` -X codeberg.org/u1f320/pronouns.cc/backend/server.Tag=`git describe --tags --long`" . .PHONY: generate generate: diff --git a/README.md b/README.md index d7a967a..b39015a 100644 --- a/README.md +++ b/README.md @@ -63,6 +63,14 @@ PORT=8080 # Port the API will listen on. Default is 8080, this is also default f MINIO_ENDPOINT=localhost:9000 # This always needs to be set, it *does not* need to point to a running MinIO server. ``` +## Updating in production + +1. Build the backend with `make backend` +2. Build the frontend with `cd frontend && pnpm install && pnpm build` +3. Stop the servers: `systemctl stop pronouns-api pronouns-fe` +4. Run migrations: `./pronouns database migrate` +5. Start the servers: `systemctl start pronouns-api pronouns-fe` + ## License Copyright (C) 2022 Sam diff --git a/backend/main.go b/backend/main.go index 152d706..947f76b 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,4 +1,4 @@ -package main +package backend import ( "context" @@ -12,14 +12,22 @@ import ( "github.com/go-chi/render" _ "github.com/joho/godotenv/autoload" + "github.com/urfave/cli/v2" ) -func main() { +var Command = &cli.Command{ + Name: "web", + Usage: "Run the API server", + Action: run, +} + +func run(c *cli.Context) error { port := ":" + os.Getenv("PORT") s, err := server.New() if err != nil { log.Fatalf("Error creating server: %v", err) + return nil } // set render.Decode to a custom one that checks content length @@ -44,10 +52,12 @@ func main() { case <-ctx.Done(): log.Info("Interrupt signal received, shutting down...") s.DB.Close() - return + return nil case err := <-e: log.Fatalf("Error running server: %v", err) } + + return nil } const MaxContentLength = 2 * 1024 * 1024 diff --git a/backend/routes.go b/backend/routes.go index de87d97..f3f44e7 100644 --- a/backend/routes.go +++ b/backend/routes.go @@ -1,4 +1,4 @@ -package main +package backend import ( "codeberg.org/u1f320/pronouns.cc/backend/routes/auth" diff --git a/backend/server/server.go b/backend/server/server.go index 1d419e3..70e9725 100644 --- a/backend/server/server.go +++ b/backend/server/server.go @@ -16,7 +16,10 @@ import ( ) // Revision is the git commit, filled at build time -var Revision = "[unknown]" +var ( + Revision = "[unknown]" + Tag = "[unknown]" +) // Repository is the URL of the git repository const Repository = "https://codeberg.org/u1f320/pronouns.cc" diff --git a/go.mod b/go.mod index 4f665b1..0723ecc 100644 --- a/go.mod +++ b/go.mod @@ -19,12 +19,14 @@ require ( github.com/minio/minio-go/v7 v7.0.37 github.com/rs/xid v1.4.0 github.com/rubenv/sql-migrate v1.1.1 + github.com/urfave/cli/v2 v2.25.0 go.uber.org/zap v1.21.0 golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602 ) require ( github.com/cespare/xxhash/v2 v2.1.2 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/dustin/go-humanize v1.0.0 // indirect github.com/go-gorp/gorp/v3 v3.0.2 // indirect github.com/golang/protobuf v1.5.2 // indirect @@ -47,8 +49,10 @@ require ( github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/pkg/errors v0.9.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sirupsen/logrus v1.9.0 // indirect github.com/tilinna/clock v1.0.2 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect go.uber.org/atomic v1.7.0 // indirect go.uber.org/multierr v1.6.0 // indirect golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect diff --git a/go.sum b/go.sum index 42793d4..4b47185 100644 --- a/go.sum +++ b/go.sum @@ -78,6 +78,8 @@ github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7 github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -407,6 +409,8 @@ github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThC github.com/rubenv/sql-migrate v1.1.1 h1:haR5Hn8hbW9/SpAICrXoZqXnywS7Q5WijwkQENPeNWY= github.com/rubenv/sql-migrate v1.1.1/go.mod h1:/7TZymwxN8VWumcIxw1jjHEcR1djpdkMHQPT4FWdnbQ= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= @@ -442,6 +446,10 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= github.com/tilinna/clock v1.0.2 h1:6BO2tyAC9JbPExKH/z9zl44FLu1lImh3nDNKA0kgrkI= github.com/tilinna/clock v1.0.2/go.mod h1:ZsP7BcY7sEEz7ktc0IVy8Us6boDrK8VradlKRUGfOao= +github.com/urfave/cli/v2 v2.25.0 h1:ykdZKuQey2zq0yin/l7JOm9Mh+pg72ngYMeB0ABn6q8= +github.com/urfave/cli/v2 v2.25.0/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= @@ -859,8 +867,8 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gorm.io/driver/postgres v1.0.8/go.mod h1:4eOzrI1MUfm6ObJU/UcmbXyiHSs8jSwH95G5P5dxcAg= gorm.io/gorm v1.20.12/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= gorm.io/gorm v1.21.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= diff --git a/main.go b/main.go new file mode 100644 index 0000000..eb78f8a --- /dev/null +++ b/main.go @@ -0,0 +1,40 @@ +package main + +import ( + "fmt" + "os" + + "codeberg.org/u1f320/pronouns.cc/backend" + "codeberg.org/u1f320/pronouns.cc/backend/server" + "codeberg.org/u1f320/pronouns.cc/scripts/cleandb" + "codeberg.org/u1f320/pronouns.cc/scripts/migrate" + "codeberg.org/u1f320/pronouns.cc/scripts/seeddb" + "github.com/urfave/cli/v2" +) + +var app = &cli.App{ + HelpName: "pronouns.cc", + Usage: "Pronoun card website and API", + Version: server.Tag, + Commands: []*cli.Command{ + backend.Command, + { + Name: "database", + Aliases: []string{"db"}, + Usage: "Manage the database", + Subcommands: []*cli.Command{ + migrate.Command, + seeddb.Command, + cleandb.Command, + }, + }, + }, +} + +func main() { + err := app.Run(os.Args) + if err != nil { + fmt.Println(err) + os.Exit(1) + } +} diff --git a/scripts/cleandb/main.go b/scripts/cleandb/main.go index 8fa2089..dc1c001 100644 --- a/scripts/cleandb/main.go +++ b/scripts/cleandb/main.go @@ -1,22 +1,28 @@ -package main +package cleandb import ( "context" "fmt" - "os" "time" dbpkg "codeberg.org/u1f320/pronouns.cc/backend/db" "github.com/georgysavva/scany/pgxscan" "github.com/joho/godotenv" "github.com/rs/xid" + "github.com/urfave/cli/v2" ) -func main() { +var Command = &cli.Command{ + Name: "clean", + Usage: "Clean deleted tokens + users daily", + Action: run, +} + +func run(c *cli.Context) error { err := godotenv.Load() if err != nil { fmt.Println("error loading .env file:", err) - os.Exit(1) + return err } ctx := context.Background() @@ -24,7 +30,7 @@ func main() { db, err := dbpkg.New() if err != nil { fmt.Println("error opening database:", err) - os.Exit(1) + return err } defer db.Close() @@ -35,7 +41,7 @@ func main() { ct, err := db.Exec(ctx, "DELETE FROM tokens WHERE invalidated = true OR expires < $1", time.Now()) if err != nil { fmt.Println("executing query:", err) - os.Exit(1) + return err } fmt.Printf("deleted %v invalidated or expired tokens\n", ct.RowsAffected()) @@ -48,12 +54,12 @@ func main() { ORDER BY id`, time.Now().Add(-dbpkg.SelfDeleteAfter), time.Now().Add(-dbpkg.ModDeleteAfter)) if err != nil { fmt.Println("error getting to-be-deleted users:", err) - os.Exit(1) + return err } if len(users) == 0 { fmt.Println("there are no users pending deletion") - os.Exit(0) + return nil } for _, u := range users { @@ -102,8 +108,9 @@ func main() { ct, err = db.Exec(ctx, "DELETE FROM users WHERE id = ANY($1)", ids) if err != nil { fmt.Printf("error deleting users: %v\n", err) - os.Exit(1) + return err } fmt.Printf("deleted %v users!\n", ct.RowsAffected()) + return nil } diff --git a/scripts/migrate/008_data_exports.sql b/scripts/migrate/008_data_exports.sql new file mode 100644 index 0000000..af11a5e --- /dev/null +++ b/scripts/migrate/008_data_exports.sql @@ -0,0 +1,10 @@ +-- +migrate Up + +-- 2023-03-15: Add data export + +create table data_exports ( + id serial primary key, + user_id text not null references users (id) on delete cascade, + hash text not null, + created_at timestamptz not null default now() +); diff --git a/scripts/migrate/main.go b/scripts/migrate/main.go index 52362b7..27be9b8 100644 --- a/scripts/migrate/main.go +++ b/scripts/migrate/main.go @@ -1,5 +1,5 @@ // migrate runs (forward) migrations -package main +package migrate import ( "database/sql" @@ -9,6 +9,7 @@ import ( "github.com/joho/godotenv" migrate "github.com/rubenv/sql-migrate" + "github.com/urfave/cli/v2" // SQL driver _ "github.com/jackc/pgx/v4/stdlib" @@ -17,7 +18,13 @@ import ( //go:embed *.sql var migrations embed.FS -func main() { +var Command = &cli.Command{ + Name: "migrate", + Usage: "Migrate the database", + Action: run, +} + +func run(c *cli.Context) error { err := godotenv.Load() if err != nil { fmt.Println("error loading .env file:", err) @@ -42,7 +49,7 @@ func main() { if err := db.Ping(); err != nil { fmt.Println("error pinging database:", err) - return + return nil } src := &migrate.EmbedFileSystemMigrationSource{ @@ -54,7 +61,7 @@ func main() { n, err := migrate.Exec(db, "postgres", src, migrate.Up) if err != nil { fmt.Println("error executing migrations:", err) - return + return nil } if n == 0 { @@ -62,4 +69,5 @@ func main() { } else { fmt.Printf("executed %v migrations!\n", n) } + return nil } diff --git a/scripts/seeddb/main.go b/scripts/seeddb/main.go index 9bd0efc..4cf31c2 100644 --- a/scripts/seeddb/main.go +++ b/scripts/seeddb/main.go @@ -1,28 +1,34 @@ -package main +package seeddb import ( - "context" "fmt" "os" "codeberg.org/u1f320/pronouns.cc/backend/db" "github.com/jackc/pgx/v4/pgxpool" "github.com/joho/godotenv" + "github.com/urfave/cli/v2" ) -func main() { +var Command = &cli.Command{ + Name: "seed", + Usage: "Seed the database with test data", + Action: run, +} + +func run(c *cli.Context) error { err := godotenv.Load() if err != nil { fmt.Println("error loading .env file:", err) - os.Exit(1) + return err } - ctx := context.Background() + ctx := c.Context pool, err := pgxpool.Connect(ctx, os.Getenv("DATABASE_URL")) if err != nil { fmt.Println("error opening database:", err) - os.Exit(1) + return err } defer pool.Close() @@ -33,19 +39,19 @@ func main() { tx, err := pg.Begin(ctx) if err != nil { fmt.Println("error beginning transaction:", err) - os.Exit(1) + return err } u, err := pg.CreateUser(ctx, tx, "test") if err != nil { fmt.Println("error creating user:", err) - os.Exit(1) + return err } _, err = pg.UpdateUser(ctx, tx, u.ID, ptr("testing"), ptr("This is a bio!"), &[]string{"https://pronouns.cc"}, nil) if err != nil { fmt.Println("error setting user info:", err) - os.Exit(1) + return err } err = pg.SetUserNamesPronouns(ctx, tx, u.ID, []db.FieldEntry{ @@ -57,7 +63,7 @@ func main() { }) if err != nil { fmt.Println("error setting pronouns:", err) - os.Exit(1) + return err } err = pg.SetUserFields(ctx, tx, u.ID, []db.Field{ @@ -114,16 +120,17 @@ func main() { }) if err != nil { fmt.Println("error setting fields:", err) - os.Exit(1) + return err } err = tx.Commit(ctx) if err != nil { fmt.Println("error committing transaction:", err) - os.Exit(1) + return err } fmt.Println("Created testing user with ID", u.ID, "and name", u.Username) + return nil } func ptr[T any](v T) *T {