surtur
6b45213649
All checks were successful
continuous-integration/drone/push Build is passing
* add user onboarding workflow * fix user editing (no edits of passwords of regular users after onboarding) * refresh HIBP breach cache in DB on app start-up * display HIBP breach details * fix request scheduling to prevent panics (this still needs some love..) * fix middleware auth * add TODOs * update head.tmpl * reword some error messages
184 lines
4.4 KiB
Go
184 lines
4.4 KiB
Go
// Copyright 2023 wanderer <a_mirre at utb dot cz>
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
package db
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"os"
|
|
|
|
"git.dotya.ml/mirre-mt/pcmt/ent"
|
|
"git.dotya.ml/mirre-mt/pcmt/ent/migrate"
|
|
moduser "git.dotya.ml/mirre-mt/pcmt/modules/user"
|
|
"git.dotya.ml/mirre-mt/pcmt/slogging"
|
|
"golang.org/x/exp/slog"
|
|
)
|
|
|
|
// CtxKey serves as a key to context values for this package.
|
|
type CtxKey struct{}
|
|
|
|
// migrateRecreateColIdx deletes and re-creates db schema.
|
|
func migrateRecreateColIdx(ctx context.Context, client *ent.Client) error { //nolint: unused
|
|
slogger := ctx.Value(CtxKey{}).(*slogging.Slogger)
|
|
log := *slogger
|
|
|
|
log.Logger = log.Logger.With(
|
|
slog.Group("pcmt extra", slog.String("module", "modules/db")),
|
|
)
|
|
|
|
// deleting data is very drastic and should be done by the user.
|
|
// if setup is already found, simply bail.
|
|
// _, err := client.Setup.Delete().Exec(ctx)
|
|
// if err != nil {
|
|
// log.Errorf("failed to delete setup table: %v", err)
|
|
// return err
|
|
// }
|
|
//
|
|
// _, err = client.User.Delete().Exec(ctx)
|
|
// if err != nil {
|
|
// log.Errorf("failed to delete user table: %v", err)
|
|
// return err
|
|
// }
|
|
|
|
err := client.Schema.
|
|
Create(
|
|
ctx,
|
|
migrate.WithDropIndex(true),
|
|
migrate.WithDropColumn(true),
|
|
)
|
|
if err != nil {
|
|
log.Errorf("failed to recreate schema resources: %v", err)
|
|
return err
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// IsSetUp checks if the database has previously been set up.
|
|
func IsSetUp(ctx context.Context, client *ent.Client) (bool, error) {
|
|
slogger := ctx.Value(CtxKey{}).(*slogging.Slogger)
|
|
log := *slogger
|
|
|
|
log.Logger = log.Logger.With(
|
|
slog.Group("pcmt extra", slog.String("module", "modules/db")),
|
|
)
|
|
|
|
is, err := client.Setup.
|
|
Query().
|
|
Only(ctx)
|
|
|
|
if is != nil {
|
|
log.Debug("apparently the db has already been set up")
|
|
} else {
|
|
log.Debug("apparently the db was not yet set up")
|
|
}
|
|
|
|
switch {
|
|
case ent.IsNotFound(err) && is == nil:
|
|
return false, nil
|
|
|
|
case ent.IsNotSingular(err):
|
|
return true, err
|
|
|
|
case err != nil:
|
|
return false, err
|
|
|
|
case err == nil && is != nil:
|
|
return true, nil
|
|
}
|
|
|
|
return false, nil
|
|
}
|
|
|
|
// PrintMigration prints the upcoming migration to stdout.
|
|
func PrintMigration(ctx context.Context, client *ent.Client) error {
|
|
slogger := ctx.Value(CtxKey{}).(*slogging.Slogger)
|
|
log := *slogger
|
|
|
|
log.Logger = log.With(
|
|
slog.Group("pcmt extra", slog.String("module", "modules/db")),
|
|
)
|
|
|
|
log.Info("printing the upcoming migration to stdout")
|
|
|
|
if err := client.Schema.WriteTo(ctx, os.Stdout); err != nil {
|
|
log.Errorf("failed to print schema changes: %v", err)
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// SetUp attempts to automatically migrate DB schema and creates the set-up
|
|
// record indicating that the DB has been set up. Optionally and only if the DB
|
|
// has not been set up prior, it creates and admin user with the initPasswd and
|
|
// the default username and email (see ../user/const.go).
|
|
func SetUp(ctx context.Context, client *ent.Client, createAdmin bool, initPasswd string) error {
|
|
// TODO: https://entgo.io/blog/2022/05/09/versioned-migrations-sum-file/
|
|
if err := client.Schema.Create(ctx); err != nil {
|
|
return fmt.Errorf("failed to create schema resources: %v", err)
|
|
}
|
|
|
|
isSetup, err := IsSetUp(ctx, client)
|
|
|
|
switch {
|
|
case err == nil && isSetup:
|
|
return nil
|
|
|
|
case err != nil && isSetup:
|
|
return err
|
|
|
|
case err == nil && !isSetup:
|
|
// run the setup in a transaction.
|
|
tx, err := client.Tx(ctx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
txClient := tx.Client()
|
|
|
|
usrCtx := context.WithValue(context.Background(),
|
|
moduser.CtxKey{}, ctx.Value(CtxKey{}).(*slogging.Slogger),
|
|
)
|
|
|
|
// use the "doSetUp" below, but give it the transactional client; no
|
|
// code changes to "doSetUp".
|
|
if err := doSetUp(usrCtx, txClient, createAdmin, initPasswd); err != nil {
|
|
return rollback(tx, err)
|
|
}
|
|
|
|
return tx.Commit()
|
|
}
|
|
|
|
// the remaining scenario (not set up and err) simply returns the err.
|
|
|
|
return err
|
|
}
|
|
|
|
func doSetUp(ctx context.Context, client *ent.Client, createAdmin bool, initPasswd string) error {
|
|
if _, err := client.Setup.
|
|
Create().
|
|
Save(ctx); err != nil {
|
|
return err
|
|
}
|
|
|
|
if createAdmin {
|
|
if err := moduser.CreateFirst(ctx, client, moduser.AdminUname, moduser.AdminEmail, initPasswd); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// rollback calls to tx.Rollback and wraps the given error with the rollback
|
|
// error if occurred.
|
|
func rollback(tx *ent.Tx, err error) error {
|
|
if rerr := tx.Rollback(); rerr != nil {
|
|
err = fmt.Errorf("%w: %v", err, rerr)
|
|
}
|
|
|
|
return err
|
|
}
|