diff --git a/internal/database/confirmation.xo.go b/internal/database/confirmation.xo.go deleted file mode 100644 index 222bf8a..0000000 --- a/internal/database/confirmation.xo.go +++ /dev/null @@ -1,233 +0,0 @@ -// Package database contains the types for schema 'public'. -package database - -// Code generated by xo. DO NOT EDIT. - -import ( - "errors" - "time" -) - -// Confirmation represents a row from '"public"."confirmation"'. -type Confirmation struct { - EmailAddress string `db:"email_address"` // email_address - UserID int64 `db:"user_id"` // user_id - Selector string `db:"selector"` // selector - Verifier []byte `db:"verifier"` // verifier - ExpiresAt time.Time `db:"expires_at"` // expires_at - - // xo fields - _exists, _deleted bool -} - -// Exists determines if the Confirmation exists in the database. -func (c *Confirmation) Exists() bool { - return c._exists -} - -// Deleted provides information if the Confirmation has been deleted from the database. -func (c *Confirmation) Deleted() bool { - return c._deleted -} - -// Insert inserts the Confirmation to the database. -func (c *Confirmation) Insert(db XODB) error { - var err error - - // if already exist, bail - if c._exists { - return errors.New("insert failed: already exists") - } - - // sql insert query, primary key must be provided - const sqlstr = `INSERT INTO "public"."confirmation" (` + - `"email_address", "user_id", "selector", "verifier", "expires_at"` + - `) VALUES (` + - `$1, $2, $3, $4, $5` + - `)` - - // run query - XOLog(sqlstr, c.EmailAddress, c.UserID, c.Selector, c.Verifier, c.ExpiresAt) - _, err = db.Exec(sqlstr, c.EmailAddress, c.UserID, c.Selector, c.Verifier, c.ExpiresAt) - if err != nil { - return err - } - - // set existence - c._exists = true - - return nil -} - -// Update updates the Confirmation in the database. -func (c *Confirmation) Update(db XODB) error { - var err error - - // if doesn't exist, bail - if !c._exists { - return errors.New("update failed: does not exist") - } - - // if deleted, bail - if c._deleted { - return errors.New("update failed: marked for deletion") - } - - // sql query - const sqlstr = `UPDATE "public"."confirmation" SET (` + - `"email_address", "user_id", "verifier", "expires_at"` + - `) = ( ` + - `$1, $2, $3, $4` + - `) WHERE "selector" = $5` - - // run query - XOLog(sqlstr, c.EmailAddress, c.UserID, c.Verifier, c.ExpiresAt, c.Selector) - _, err = db.Exec(sqlstr, c.EmailAddress, c.UserID, c.Verifier, c.ExpiresAt, c.Selector) - return err -} - -// Save saves the Confirmation to the database. -func (c *Confirmation) Save(db XODB) error { - if c.Exists() { - return c.Update(db) - } - - return c.Insert(db) -} - -// Upsert performs an upsert for Confirmation. -// -// NOTE: PostgreSQL 9.5+ only -func (c *Confirmation) Upsert(db XODB) error { - var err error - - // if already exist, bail - if c._exists { - return errors.New("insert failed: already exists") - } - - // sql query - const sqlstr = `INSERT INTO "public"."confirmation" (` + - `"email_address", "user_id", "selector", "verifier", "expires_at"` + - `) VALUES (` + - `$1, $2, $3, $4, $5` + - `) ON CONFLICT ("selector") DO UPDATE SET (` + - `"email_address", "user_id", "selector", "verifier", "expires_at"` + - `) = (` + - `EXCLUDED."email_address", EXCLUDED."user_id", EXCLUDED."selector", EXCLUDED."verifier", EXCLUDED."expires_at"` + - `)` - - // run query - XOLog(sqlstr, c.EmailAddress, c.UserID, c.Selector, c.Verifier, c.ExpiresAt) - _, err = db.Exec(sqlstr, c.EmailAddress, c.UserID, c.Selector, c.Verifier, c.ExpiresAt) - if err != nil { - return err - } - - // set existence - c._exists = true - - return nil -} - -// Delete deletes the Confirmation from the database. -func (c *Confirmation) Delete(db XODB) error { - var err error - - // if doesn't exist, bail - if !c._exists { - return nil - } - - // if deleted, bail - if c._deleted { - return nil - } - - // sql query - const sqlstr = `DELETE FROM "public"."confirmation" WHERE "selector" = $1` - - // run query - XOLog(sqlstr, c.Selector) - _, err = db.Exec(sqlstr, c.Selector) - if err != nil { - return err - } - - // set deleted - c._deleted = true - - return nil -} - -// User returns the User associated with the Confirmation's UserID (user_id). -// -// Generated from foreign key 'confirmation_user_id_fkey'. -func (c *Confirmation) User(db XODB) (*User, error) { - return UserByID(db, c.UserID) -} - -// ConfirmationBySelector retrieves a row from '"public"."confirmation"' as a Confirmation. -// -// Generated from index 'confirmation_pkey'. -func ConfirmationBySelector(db XODB, selector string) (*Confirmation, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"email_address", "user_id", "selector", "verifier", "expires_at" ` + - `FROM "public"."confirmation" ` + - `WHERE "selector" = $1` - - // run query - XOLog(sqlstr, selector) - c := Confirmation{ - _exists: true, - } - - err = db.QueryRow(sqlstr, selector).Scan(&c.EmailAddress, &c.UserID, &c.Selector, &c.Verifier, &c.ExpiresAt) - if err != nil { - return nil, err - } - - return &c, nil -} - -// ConfirmationsByUserID retrieves a row from '"public"."confirmation"' as a Confirmation. -// -// Generated from index 'confirmation_user_id_idx'. -func ConfirmationsByUserID(db XODB, userID int64) ([]*Confirmation, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"email_address", "user_id", "selector", "verifier", "expires_at" ` + - `FROM "public"."confirmation" ` + - `WHERE "user_id" = $1` - - // run query - XOLog(sqlstr, userID) - q, err := db.Query(sqlstr, userID) - if err != nil { - return nil, err - } - defer q.Close() - - // load results - res := []*Confirmation{} - for q.Next() { - c := Confirmation{ - _exists: true, - } - - // scan - err = q.Scan(&c.EmailAddress, &c.UserID, &c.Selector, &c.Verifier, &c.ExpiresAt) - if err != nil { - return nil, err - } - - res = append(res, &c) - } - - return res, nil -} diff --git a/internal/database/db.go b/internal/database/doc.go similarity index 80% rename from internal/database/db.go rename to internal/database/doc.go index 789d2af..7becb58 100644 --- a/internal/database/db.go +++ b/internal/database/doc.go @@ -1,9 +1,13 @@ +// Package database contains the database queries. package database -//go:generate sh -c "rm -f *.xo.go" -//go:generate xo $DATABASE_DSN --escape-all --template-path templates/ --package database -o . -//go:generate sh -c "rm -f schemamigration.xo.go" -//go:generate +// this package uses github.com/kyleconroy/sqlc to generate +// code from SQL queries. You only need to put sqlc in PATH, if +// the models have to be regenerated. + +//go:generate sqlc compile +//go:generate bash -c "rm -f *.sql.go db.go models.go querier.go" +//go:generate sqlc generate import ( "database/sql" diff --git a/internal/database/email.xo.go b/internal/database/email.xo.go deleted file mode 100644 index 17aaf6e..0000000 --- a/internal/database/email.xo.go +++ /dev/null @@ -1,231 +0,0 @@ -// Package database contains the types for schema 'public'. -package database - -// Code generated by xo. DO NOT EDIT. - -import ( - "errors" - "time" -) - -// Email represents a row from '"public"."email"'. -type Email struct { - Address string `db:"address"` // address - UserID int64 `db:"user_id"` // user_id - CreatedAt time.Time `db:"created_at"` // created_at - - // xo fields - _exists, _deleted bool -} - -// Exists determines if the Email exists in the database. -func (e *Email) Exists() bool { - return e._exists -} - -// Deleted provides information if the Email has been deleted from the database. -func (e *Email) Deleted() bool { - return e._deleted -} - -// Insert inserts the Email to the database. -func (e *Email) Insert(db XODB) error { - var err error - - // if already exist, bail - if e._exists { - return errors.New("insert failed: already exists") - } - - // sql insert query, primary key must be provided - const sqlstr = `INSERT INTO "public"."email" (` + - `"address", "user_id", "created_at"` + - `) VALUES (` + - `$1, $2, $3` + - `)` - - // run query - XOLog(sqlstr, e.Address, e.UserID, e.CreatedAt) - _, err = db.Exec(sqlstr, e.Address, e.UserID, e.CreatedAt) - if err != nil { - return err - } - - // set existence - e._exists = true - - return nil -} - -// Update updates the Email in the database. -func (e *Email) Update(db XODB) error { - var err error - - // if doesn't exist, bail - if !e._exists { - return errors.New("update failed: does not exist") - } - - // if deleted, bail - if e._deleted { - return errors.New("update failed: marked for deletion") - } - - // sql query - const sqlstr = `UPDATE "public"."email" SET (` + - `"user_id", "created_at"` + - `) = ( ` + - `$1, $2` + - `) WHERE "address" = $3` - - // run query - XOLog(sqlstr, e.UserID, e.CreatedAt, e.Address) - _, err = db.Exec(sqlstr, e.UserID, e.CreatedAt, e.Address) - return err -} - -// Save saves the Email to the database. -func (e *Email) Save(db XODB) error { - if e.Exists() { - return e.Update(db) - } - - return e.Insert(db) -} - -// Upsert performs an upsert for Email. -// -// NOTE: PostgreSQL 9.5+ only -func (e *Email) Upsert(db XODB) error { - var err error - - // if already exist, bail - if e._exists { - return errors.New("insert failed: already exists") - } - - // sql query - const sqlstr = `INSERT INTO "public"."email" (` + - `"address", "user_id", "created_at"` + - `) VALUES (` + - `$1, $2, $3` + - `) ON CONFLICT ("address") DO UPDATE SET (` + - `"address", "user_id", "created_at"` + - `) = (` + - `EXCLUDED."address", EXCLUDED."user_id", EXCLUDED."created_at"` + - `)` - - // run query - XOLog(sqlstr, e.Address, e.UserID, e.CreatedAt) - _, err = db.Exec(sqlstr, e.Address, e.UserID, e.CreatedAt) - if err != nil { - return err - } - - // set existence - e._exists = true - - return nil -} - -// Delete deletes the Email from the database. -func (e *Email) Delete(db XODB) error { - var err error - - // if doesn't exist, bail - if !e._exists { - return nil - } - - // if deleted, bail - if e._deleted { - return nil - } - - // sql query - const sqlstr = `DELETE FROM "public"."email" WHERE "address" = $1` - - // run query - XOLog(sqlstr, e.Address) - _, err = db.Exec(sqlstr, e.Address) - if err != nil { - return err - } - - // set deleted - e._deleted = true - - return nil -} - -// User returns the User associated with the Email's UserID (user_id). -// -// Generated from foreign key 'email_user_id_fkey'. -func (e *Email) User(db XODB) (*User, error) { - return UserByID(db, e.UserID) -} - -// EmailByAddress retrieves a row from '"public"."email"' as a Email. -// -// Generated from index 'email_pkey'. -func EmailByAddress(db XODB, address string) (*Email, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"address", "user_id", "created_at" ` + - `FROM "public"."email" ` + - `WHERE "address" = $1` - - // run query - XOLog(sqlstr, address) - e := Email{ - _exists: true, - } - - err = db.QueryRow(sqlstr, address).Scan(&e.Address, &e.UserID, &e.CreatedAt) - if err != nil { - return nil, err - } - - return &e, nil -} - -// EmailsByUserID retrieves a row from '"public"."email"' as a Email. -// -// Generated from index 'email_user_id_idx'. -func EmailsByUserID(db XODB, userID int64) ([]*Email, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"address", "user_id", "created_at" ` + - `FROM "public"."email" ` + - `WHERE "user_id" = $1` - - // run query - XOLog(sqlstr, userID) - q, err := db.Query(sqlstr, userID) - if err != nil { - return nil, err - } - defer q.Close() - - // load results - res := []*Email{} - for q.Next() { - e := Email{ - _exists: true, - } - - // scan - err = q.Scan(&e.Address, &e.UserID, &e.CreatedAt) - if err != nil { - return nil, err - } - - res = append(res, &e) - } - - return res, nil -} diff --git a/internal/database/migrate.go b/internal/database/migrate.go index 3d57dad..e976b38 100644 --- a/internal/database/migrate.go +++ b/internal/database/migrate.go @@ -6,29 +6,29 @@ import ( "net/http" "os" - vfs "github.com/ailox/migrate-vfs" - "github.com/golang-migrate/migrate" - "github.com/golang-migrate/migrate/database/postgres" + "github.com/golang-migrate/migrate/v4" + "github.com/golang-migrate/migrate/v4/database/postgres" + "github.com/golang-migrate/migrate/v4/source/httpfs" "github.com/jmoiron/sqlx" ) -// GetMigrator returns a Database Migrator for PostgreSQL. +// GetMigrator returns a Database Migrator for PostgreSQL for the supplied +// http.FileSystem and path. func getMigrator(db *sql.DB, fs http.FileSystem, path string) (*migrate.Migrate, error) { - vfsSource, err := vfs.WithInstance(fs, path) + driver, err := postgres.WithInstance(db, &postgres.Config{}) if err != nil { return nil, err } - driver, err := postgres.WithInstance(db, &postgres.Config{}) + + migrations, err := httpfs.New(fs, path) if err != nil { return nil, err } // the strings are only for logging purpose migrator, err := migrate.NewWithInstance( - // if the linter throws an error here because "source" doesnt - // match the correct type, this error can be safely ignored. - "vfs-dir", vfsSource, - "paul", driver, + "assets", migrations, + "database", driver, ) if err != nil { return nil, err @@ -61,9 +61,7 @@ const ( // * `(integer)` – Migrate to specific version func Migrate(db *sqlx.DB, fs http.FileSystem, revision int) error { // migrate database - var migrationPathInFs string - - migrationPathInFs = "/migrations" + const migrationPathInFs = "/migrations" migrator, err := getMigrator(db.DB, fs, migrationPathInFs) if err != nil { diff --git a/internal/database/reset.xo.go b/internal/database/reset.xo.go deleted file mode 100644 index 2f2485d..0000000 --- a/internal/database/reset.xo.go +++ /dev/null @@ -1,219 +0,0 @@ -// Package database contains the types for schema 'public'. -package database - -// Code generated by xo. DO NOT EDIT. - -import ( - "errors" - "time" -) - -// Reset represents a row from '"public"."reset"'. -type Reset struct { - UserID int64 `db:"user_id"` // user_id - Selector string `db:"selector"` // selector - Verifier []byte `db:"verifier"` // verifier - ExpiresAt time.Time `db:"expires_at"` // expires_at - - // xo fields - _exists, _deleted bool -} - -// Exists determines if the Reset exists in the database. -func (r *Reset) Exists() bool { - return r._exists -} - -// Deleted provides information if the Reset has been deleted from the database. -func (r *Reset) Deleted() bool { - return r._deleted -} - -// Insert inserts the Reset to the database. -func (r *Reset) Insert(db XODB) error { - var err error - - // if already exist, bail - if r._exists { - return errors.New("insert failed: already exists") - } - - // sql insert query, primary key must be provided - const sqlstr = `INSERT INTO "public"."reset" (` + - `"user_id", "selector", "verifier", "expires_at"` + - `) VALUES (` + - `$1, $2, $3, $4` + - `)` - - // run query - XOLog(sqlstr, r.UserID, r.Selector, r.Verifier, r.ExpiresAt) - _, err = db.Exec(sqlstr, r.UserID, r.Selector, r.Verifier, r.ExpiresAt) - if err != nil { - return err - } - - // set existence - r._exists = true - - return nil -} - -// Update updates the Reset in the database. -func (r *Reset) Update(db XODB) error { - var err error - - // if doesn't exist, bail - if !r._exists { - return errors.New("update failed: does not exist") - } - - // if deleted, bail - if r._deleted { - return errors.New("update failed: marked for deletion") - } - - // sql query - const sqlstr = `UPDATE "public"."reset" SET (` + - `"user_id", "verifier", "expires_at"` + - `) = ( ` + - `$1, $2, $3` + - `) WHERE "selector" = $4` - - // run query - XOLog(sqlstr, r.UserID, r.Verifier, r.ExpiresAt, r.Selector) - _, err = db.Exec(sqlstr, r.UserID, r.Verifier, r.ExpiresAt, r.Selector) - return err -} - -// Save saves the Reset to the database. -func (r *Reset) Save(db XODB) error { - if r.Exists() { - return r.Update(db) - } - - return r.Insert(db) -} - -// Upsert performs an upsert for Reset. -// -// NOTE: PostgreSQL 9.5+ only -func (r *Reset) Upsert(db XODB) error { - var err error - - // if already exist, bail - if r._exists { - return errors.New("insert failed: already exists") - } - - // sql query - const sqlstr = `INSERT INTO "public"."reset" (` + - `"user_id", "selector", "verifier", "expires_at"` + - `) VALUES (` + - `$1, $2, $3, $4` + - `) ON CONFLICT ("selector") DO UPDATE SET (` + - `"user_id", "selector", "verifier", "expires_at"` + - `) = (` + - `EXCLUDED."user_id", EXCLUDED."selector", EXCLUDED."verifier", EXCLUDED."expires_at"` + - `)` - - // run query - XOLog(sqlstr, r.UserID, r.Selector, r.Verifier, r.ExpiresAt) - _, err = db.Exec(sqlstr, r.UserID, r.Selector, r.Verifier, r.ExpiresAt) - if err != nil { - return err - } - - // set existence - r._exists = true - - return nil -} - -// Delete deletes the Reset from the database. -func (r *Reset) Delete(db XODB) error { - var err error - - // if doesn't exist, bail - if !r._exists { - return nil - } - - // if deleted, bail - if r._deleted { - return nil - } - - // sql query - const sqlstr = `DELETE FROM "public"."reset" WHERE "selector" = $1` - - // run query - XOLog(sqlstr, r.Selector) - _, err = db.Exec(sqlstr, r.Selector) - if err != nil { - return err - } - - // set deleted - r._deleted = true - - return nil -} - -// User returns the User associated with the Reset's UserID (user_id). -// -// Generated from foreign key 'reset_user_id_fkey'. -func (r *Reset) User(db XODB) (*User, error) { - return UserByID(db, r.UserID) -} - -// ResetBySelector retrieves a row from '"public"."reset"' as a Reset. -// -// Generated from index 'reset_pkey'. -func ResetBySelector(db XODB, selector string) (*Reset, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"user_id", "selector", "verifier", "expires_at" ` + - `FROM "public"."reset" ` + - `WHERE "selector" = $1` - - // run query - XOLog(sqlstr, selector) - r := Reset{ - _exists: true, - } - - err = db.QueryRow(sqlstr, selector).Scan(&r.UserID, &r.Selector, &r.Verifier, &r.ExpiresAt) - if err != nil { - return nil, err - } - - return &r, nil -} - -// ResetByUserID retrieves a row from '"public"."reset"' as a Reset. -// -// Generated from index 'reset_user_id_idx'. -func ResetByUserID(db XODB, userID int64) (*Reset, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"user_id", "selector", "verifier", "expires_at" ` + - `FROM "public"."reset" ` + - `WHERE "user_id" = $1` - - // run query - XOLog(sqlstr, userID) - r := Reset{ - _exists: true, - } - - err = db.QueryRow(sqlstr, userID).Scan(&r.UserID, &r.Selector, &r.Verifier, &r.ExpiresAt) - if err != nil { - return nil, err - } - - return &r, nil -} diff --git a/internal/database/sql/queries/confirmation.sql b/internal/database/sql/queries/confirmation.sql new file mode 100644 index 0000000..bf61aa7 --- /dev/null +++ b/internal/database/sql/queries/confirmation.sql @@ -0,0 +1,21 @@ +-- name: CreateConfirmation :one +INSERT INTO "public"."confirmation" ( +"email_address", "user_id", "selector", "verifier", "expires_at" +) VALUES ( +$1, $2, $3, $4, $5 +) RETURNING *; + +-- name: DestroyConfirmation :exec +DELETE FROM "public"."confirmation" WHERE "selector" = $1; + +-- name: GetConfirmationBySelector :one +SELECT +"email_address", "user_id", "selector", "verifier", "expires_at" +FROM "public"."confirmation" +WHERE "selector" = $1; + +-- name: GetConfirmationByUserID :one +SELECT +"email_address", "user_id", "selector", "verifier", "expires_at" +FROM "public"."confirmation" +WHERE "user_id" = $1; \ No newline at end of file diff --git a/internal/database/sql/queries/email.sql b/internal/database/sql/queries/email.sql new file mode 100644 index 0000000..acbe283 --- /dev/null +++ b/internal/database/sql/queries/email.sql @@ -0,0 +1,28 @@ +-- name: CreateEmail :one +INSERT INTO "email" ( +"address", "user_id", "created_at" +) VALUES ( +$1, $2, $3 +) RETURNING *; + +-- name: UpdateEmail :exec +UPDATE "email" SET ( +"user_id", "created_at" +) = ( +$1, $2 +) WHERE "address" = $3; + +-- name: DestroyEmail :exec +DELETE FROM "email" WHERE "address" = $1; + +-- name: GetEmailByAddress :one +SELECT +"address", "user_id", "created_at" +FROM "public"."email" +WHERE "address" = $1; + +-- name: GetEmailByUserID :one +SELECT +"address", "user_id", "created_at" +FROM "public"."email" +WHERE "user_id" = $1; \ No newline at end of file diff --git a/internal/database/sql/queries/reset.sql b/internal/database/sql/queries/reset.sql new file mode 100644 index 0000000..c71e3a0 --- /dev/null +++ b/internal/database/sql/queries/reset.sql @@ -0,0 +1,21 @@ +-- name: CreateReset :one +INSERT INTO "reset" ( +"user_id", "selector", "verifier", "expires_at" +) VALUES ( +$1, $2, $3, $4 +) RETURNING *; + +-- name: DestroyReset :exec +DELETE FROM "reset" WHERE "selector" = $1; + +-- name: GetResetBySelector :one +SELECT +"user_id", "selector", "verifier", "expires_at" +FROM "reset" +WHERE "selector" = $1; + +-- name: GetResetByUserID :one +SELECT +"user_id", "selector", "verifier", "expires_at" +FROM "reset" +WHERE "user_id" = $1; \ No newline at end of file diff --git a/internal/database/sql/queries/user.sql b/internal/database/sql/queries/user.sql new file mode 100644 index 0000000..8b48299 --- /dev/null +++ b/internal/database/sql/queries/user.sql @@ -0,0 +1,19 @@ +-- name: GetUserByID :one +SELECT +"id", "is_admin", "password", "created_at" +FROM "user" +WHERE "id" = $1; + +-- name: CreateUser :one +INSERT INTO "user" ( +"is_admin", "password", "created_at" +) VALUES ( +$1, $2, $3 +) RETURNING *; + +-- name: UpdateUser :exec +UPDATE "user" SET ( +"is_admin", "password", "created_at" +) = ( +$1, $2, $3 +) WHERE "id" = $4; \ No newline at end of file diff --git a/internal/database/sql/schema/schema.sql b/internal/database/sql/schema/schema.sql new file mode 100644 index 0000000..53d832c --- /dev/null +++ b/internal/database/sql/schema/schema.sql @@ -0,0 +1,58 @@ +-- This file is used for generating queries; If you change anything please +-- also add migrations in `assets/migrations`. + +CREATE TABLE "user" ( + "id" bigserial NOT NULL, + "is_admin" boolean NOT NULL DEFAULT false, + "password" bytea NULL, + "created_at" timestamptz NOT NULL DEFAULT NOW(), + PRIMARY KEY ("id") +); + +CREATE TABLE "email" ( + "address" text NOT NULL, + "user_id" bigint NOT NULL, + "created_at" timestamptz NOT NULL DEFAULT NOW(), + FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE, + PRIMARY KEY ("address") +); +CREATE INDEX ON "email" ("user_id"); + +CREATE TABLE "confirmation" ( + "email_address" text NOT NULL, + "user_id" bigint NOT NULL, + "selector" text NOT NULL, + "verifier" bytea NOT NULL, -- hashed + "expires_at" timestamptz NOT NULL DEFAULT NOW(), + FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE, + PRIMARY KEY ("selector") +); +CREATE INDEX ON "confirmation" ("user_id"); + +CREATE TABLE "reset" ( + "user_id" bigint NOT NULL, + "selector" text NOT NULL, + "verifier" bytea NOT NULL, -- hashed + "expires_at" timestamptz NOT NULL DEFAULT NOW(), + FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE, + PRIMARY KEY ("selector") +); +CREATE UNIQUE INDEX ON "reset" ("user_id"); + +CREATE TABLE "external_auth" ( + "id" bigserial NOT NULL, + "name" text NOT NULL, + "type" text NOT NULL, + "config" jsonb NOT NULL, + PRIMARY KEY ("id") +); +CREATE INDEX ON "external_auth" ("type"); + +CREATE TABLE "external_user" ( + "external_auth_id" bigint NOT NULL, + "foreign_id" text NOT NULL, + "user_id" bigint NOT NULL, + FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE, + FOREIGN KEY ("external_auth_id") REFERENCES "external_auth" ("id") ON DELETE CASCADE, + PRIMARY KEY ("external_auth_id", "foreign_id") +); \ No newline at end of file diff --git a/internal/database/sqlc.yaml b/internal/database/sqlc.yaml new file mode 100644 index 0000000..d34756d --- /dev/null +++ b/internal/database/sqlc.yaml @@ -0,0 +1,16 @@ +version: "1" +packages: + - name: "database" + path: "./" + schema: "./sql/schema" + queries: "./sql/queries" + emit_json_tags: true + emit_interface: true + +overrides: + - db_type: "citext" + "null": false + go_type: "string" + - db_type: "citext" + "null": true + go_type: "database/sql.NullString" \ No newline at end of file diff --git a/internal/database/user.xo.go b/internal/database/user.xo.go deleted file mode 100644 index b3eb9c5..0000000 --- a/internal/database/user.xo.go +++ /dev/null @@ -1,186 +0,0 @@ -// Package database contains the types for schema 'public'. -package database - -// Code generated by xo. DO NOT EDIT. - -import ( - "errors" - "time" -) - -// User represents a row from '"public"."user"'. -type User struct { - ID int64 `db:"id"` // id - IsAdmin bool `db:"is_admin"` // is_admin - Password []byte `db:"password"` // password - CreatedAt time.Time `db:"created_at"` // created_at - - // xo fields - _exists, _deleted bool -} - -// Exists determines if the User exists in the database. -func (u *User) Exists() bool { - return u._exists -} - -// Deleted provides information if the User has been deleted from the database. -func (u *User) Deleted() bool { - return u._deleted -} - -// Insert inserts the User to the database. -func (u *User) Insert(db XODB) error { - var err error - - // if already exist, bail - if u._exists { - return errors.New("insert failed: already exists") - } - - // sql insert query, primary key provided by sequence - const sqlstr = `INSERT INTO "public"."user" (` + - `"is_admin", "password", "created_at"` + - `) VALUES (` + - `$1, $2, $3` + - `) RETURNING "id"` - - // run query - XOLog(sqlstr, u.IsAdmin, u.Password, u.CreatedAt) - err = db.QueryRow(sqlstr, u.IsAdmin, u.Password, u.CreatedAt).Scan(&u.ID) - if err != nil { - return err - } - - // set existence - u._exists = true - - return nil -} - -// Update updates the User in the database. -func (u *User) Update(db XODB) error { - var err error - - // if doesn't exist, bail - if !u._exists { - return errors.New("update failed: does not exist") - } - - // if deleted, bail - if u._deleted { - return errors.New("update failed: marked for deletion") - } - - // sql query - const sqlstr = `UPDATE "public"."user" SET (` + - `"is_admin", "password", "created_at"` + - `) = ( ` + - `$1, $2, $3` + - `) WHERE "id" = $4` - - // run query - XOLog(sqlstr, u.IsAdmin, u.Password, u.CreatedAt, u.ID) - _, err = db.Exec(sqlstr, u.IsAdmin, u.Password, u.CreatedAt, u.ID) - return err -} - -// Save saves the User to the database. -func (u *User) Save(db XODB) error { - if u.Exists() { - return u.Update(db) - } - - return u.Insert(db) -} - -// Upsert performs an upsert for User. -// -// NOTE: PostgreSQL 9.5+ only -func (u *User) Upsert(db XODB) error { - var err error - - // if already exist, bail - if u._exists { - return errors.New("insert failed: already exists") - } - - // sql query - const sqlstr = `INSERT INTO "public"."user" (` + - `"id", "is_admin", "password", "created_at"` + - `) VALUES (` + - `$1, $2, $3, $4` + - `) ON CONFLICT ("id") DO UPDATE SET (` + - `"id", "is_admin", "password", "created_at"` + - `) = (` + - `EXCLUDED."id", EXCLUDED."is_admin", EXCLUDED."password", EXCLUDED."created_at"` + - `)` - - // run query - XOLog(sqlstr, u.ID, u.IsAdmin, u.Password, u.CreatedAt) - _, err = db.Exec(sqlstr, u.ID, u.IsAdmin, u.Password, u.CreatedAt) - if err != nil { - return err - } - - // set existence - u._exists = true - - return nil -} - -// Delete deletes the User from the database. -func (u *User) Delete(db XODB) error { - var err error - - // if doesn't exist, bail - if !u._exists { - return nil - } - - // if deleted, bail - if u._deleted { - return nil - } - - // sql query - const sqlstr = `DELETE FROM "public"."user" WHERE "id" = $1` - - // run query - XOLog(sqlstr, u.ID) - _, err = db.Exec(sqlstr, u.ID) - if err != nil { - return err - } - - // set deleted - u._deleted = true - - return nil -} - -// UserByID retrieves a row from '"public"."user"' as a User. -// -// Generated from index 'user_pkey'. -func UserByID(db XODB, id int64) (*User, error) { - var err error - - // sql query - const sqlstr = `SELECT ` + - `"id", "is_admin", "password", "created_at" ` + - `FROM "public"."user" ` + - `WHERE "id" = $1` - - // run query - XOLog(sqlstr, id) - u := User{ - _exists: true, - } - - err = db.QueryRow(sqlstr, id).Scan(&u.ID, &u.IsAdmin, &u.Password, &u.CreatedAt) - if err != nil { - return nil, err - } - - return &u, nil -} diff --git a/internal/database/xo_db.xo.go b/internal/database/xo_db.xo.go deleted file mode 100644 index 8547b69..0000000 --- a/internal/database/xo_db.xo.go +++ /dev/null @@ -1,85 +0,0 @@ -// Package database contains the types for schema 'public'. -package database - -// Code generated by xo. DO NOT EDIT. - -import ( - "database/sql" - "database/sql/driver" - "encoding/csv" - "errors" - "fmt" - "regexp" - "strings" -) - -// XODB is the common interface for database operations that can be used with -// types from schema 'public'. -// -// This should work with database/sql.DB and database/sql.Tx. -type XODB interface { - Exec(string, ...interface{}) (sql.Result, error) - Query(string, ...interface{}) (*sql.Rows, error) - QueryRow(string, ...interface{}) *sql.Row -} - -// XOLog provides the log func used by generated queries. -var XOLog = func(string, ...interface{}) {} - -// ScannerValuer is the common interface for types that implement both the -// database/sql.Scanner and sql/driver.Valuer interfaces. -type ScannerValuer interface { - sql.Scanner - driver.Valuer -} - -// StringSlice is a slice of strings. -type StringSlice []string - -// quoteEscapeRegex is the regex to match escaped characters in a string. -var quoteEscapeRegex = regexp.MustCompile(`([^\\]([\\]{2})*)\\"`) - -// Scan satisfies the sql.Scanner interface for StringSlice. -func (ss *StringSlice) Scan(src interface{}) error { - buf, ok := src.([]byte) - if !ok { - return errors.New("invalid StringSlice") - } - - // change quote escapes for csv parser - str := quoteEscapeRegex.ReplaceAllString(string(buf), `$1""`) - str = strings.Replace(str, `\\`, `\`, -1) - - // remove braces - str = str[1 : len(str)-1] - - // bail if only one - if len(str) == 0 { - *ss = StringSlice([]string{}) - return nil - } - - // parse with csv reader - cr := csv.NewReader(strings.NewReader(str)) - slice, err := cr.Read() - if err != nil { - fmt.Printf("exiting!: %v\n", err) - return err - } - - *ss = StringSlice(slice) - - return nil -} - -// Value satisfies the driver.Valuer interface for StringSlice. -func (ss StringSlice) Value() (driver.Value, error) { - v := make([]string, len(ss)) - for i, s := range ss { - v[i] = `"` + strings.Replace(strings.Replace(s, `\`, `\\\`, -1), `"`, `\"`, -1) + `"` - } - return "{" + strings.Join(v, ",") + "}", nil -} - -// Slice is a slice of ScannerValuers. -type Slice []ScannerValuer