package database import ( "context" "database/sql" "database/sql/driver" "errors" "fmt" "io" "log/slog" "sync" "time" "github.com/ansg191/ibd-trader-backend/db" "github.com/ansg191/ibd-trader-backend/internal/keys" "github.com/golang-migrate/migrate/v4" _ "github.com/golang-migrate/migrate/v4/database/postgres" "github.com/golang-migrate/migrate/v4/source/iofs" _ "github.com/lib/pq" ) type Database interface { io.Closer UserStore CookieStore KeyStore SessionStore StockStore driver.Pinger Migrate(ctx context.Context) error Maintenance(ctx context.Context) } type database struct { logger *slog.Logger db *sql.DB url string kms keys.KeyManagementService keyName string } func New(ctx context.Context, logger *slog.Logger, url string, kms keys.KeyManagementService, keyName string) (Database, error) { sqlDB, err := sql.Open("postgres", url) if err != nil { return nil, err } err = sqlDB.PingContext(ctx) if err != nil { // Ping failed. Don't error, but give a warning. logger.WarnContext(ctx, "Unable to ping database", "error", err) } return &database{ logger: logger, db: sqlDB, url: url, kms: kms, keyName: keyName, }, nil } func (d *database) Close() error { return d.db.Close() } func (d *database) Migrate(ctx context.Context) error { fs, err := iofs.New(db.Migrations, "migrations") if err != nil { return err } m, err := migrate.NewWithSourceInstance("iofs", fs, d.url) if err != nil { return err } d.logger.InfoContext(ctx, "Running DB migration") err = m.Up() if err != nil && !errors.Is(err, migrate.ErrNoChange) { d.logger.ErrorContext(ctx, "DB migration failed", "error", err) return err } return nil } func (d *database) Maintenance(ctx context.Context) { ticker := time.NewTicker(15 * time.Minute) defer ticker.Stop() for { select { case <-ticker.C: func() { var wg sync.WaitGroup wg.Add(1) ctx, cancel := context.WithTimeout(context.Background(), 10*time.Minute) defer cancel() go d.cleanupSessions(ctx, &wg) wg.Wait() }() case <-ctx.Done(): return } } } func (d *database) Ping(ctx context.Context) error { return d.db.PingContext(ctx) } func (d *database) execInternal(ctx context.Context, queryName string, fn func(string) (any, error)) (any, error) { query, err := db.GetQuery(queryName) if err != nil { return nil, fmt.Errorf("unable to get query: %w", err) } d.logger.DebugContext(ctx, "Executing query", "name", queryName, "query", query) now := time.Now() // Execute the query result, err := fn(query) if err != nil { return nil, fmt.Errorf("unable to execute query: %w", err) } d.logger.DebugContext(ctx, "Query executed successfully", "name", queryName, "duration", time.Since(now)) return result, nil } func (d *database) exec(ctx context.Context, exec executor, queryName string, args ...any) (sql.Result, error) { ret, err := d.execInternal(ctx, queryName, func(query string) (any, error) { return exec.ExecContext(ctx, query, args...) }) if err != nil { return nil, err } else { return ret.(sql.Result), nil } } func (d *database) query(ctx context.Context, exec executor, queryName string, args ...any) (*sql.Rows, error) { ret, err := d.execInternal(ctx, queryName, func(query string) (any, error) { return exec.QueryContext(ctx, query, args...) }) if err != nil { return nil, err } else { return ret.(*sql.Rows), nil } } func (d *database) queryRow(ctx context.Context, exec executor, queryName string, args ...any) (*sql.Row, error) { ret, err := d.execInternal(ctx, queryName, func(query string) (any, error) { return exec.QueryRowContext(ctx, query, args...), nil }) if err != nil { return nil, err } else { return ret.(*sql.Row), nil } } type executor interface { ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) QueryContext(ctx context.Context, query string, args ...any) (*sql.Rows, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/.github/workflows/nightly.yml (unfollow)
AgeCommit message (Expand)AuthorFilesLines
2024-02-26fix: better assetsInlineLimit runtime type checking (#10154)Gravatar James Ross 2-9/+13
2024-02-26fix: correct remote url (#10223)Gravatar Ben Holmes 2-2/+8
2024-02-26fix(toolbar): Make it so every built-in app can be closed by outside clicks (...Gravatar Erika 8-51/+63
2024-02-26Fix an issue where Vercel adapter may create functions for prerendered routes...Gravatar Ming-jun Lu 4-2/+26
2024-02-26[ci] formatGravatar Matthew Phillips 1-1/+1
2024-02-26Fix hydration scripts missing from dynamic slot usage (#10219)Gravatar Matthew Phillips 6-1/+69
2024-02-26[ci] formatGravatar Matthew Phillips 1-5/+5
2024-02-26Prevent errors in rendering from crashing server (#10221)Gravatar Matthew Phillips 7-14/+78
2024-02-26fix: svelte 5 mount/hydrate api change. (#10224)Gravatar 前端子鱼 3-5/+12
2024-02-24[ci] formatGravatar Arsh 1-9/+30
2024-02-24prevent warning: `Astro.request.headers` is not available in "static" output ...Gravatar Arsh 2-27/+30
2024-02-23Improved error logging from config (#10207)Gravatar Ben Holmes 4-36/+67
2024-02-23[ci] formatGravatar Arsh 3-3/+3
2024-02-23fix(dev): remove params for prerendered pages (#10199)Gravatar Arsh 9-13/+78
2024-02-23[ci] release (#10213)astro@4.4.4@astrojs/vercel@7.3.3@astrojs/node@8.2.1@astrojs/db@0.4.0Gravatar Houston (Bot) 41-177/+95
2024-02-23Fixes edge middleware calling nested routes (#10215)Gravatar Matthew Phillips 2-1/+6
2024-02-23Adds an error message for non-string transition:name values (#10205)Gravatar Martin Trapp 2-0/+8
2024-02-23[ci] formatGravatar Furkan Erdem 1-1/+1
2024-02-23Fix(node): Custom headers are not present in responses from standalone Node s...Gravatar Furkan Erdem 8-0/+163