updated to use bun and updated hws modules.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"context"
|
||||
"projectreshoot/internal/config"
|
||||
"projectreshoot/internal/handler"
|
||||
"projectreshoot/internal/models"
|
||||
@@ -11,19 +11,25 @@ import (
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func setupAuth(
|
||||
config *config.Config,
|
||||
logger *hlog.Logger,
|
||||
conn *sql.DB,
|
||||
db *bun.DB,
|
||||
server *hws.Server,
|
||||
ignoredPaths []string,
|
||||
) (*hwsauth.Authenticator[*models.User], error) {
|
||||
) (*hwsauth.Authenticator[*models.UserBun, bun.Tx], error) {
|
||||
beginTx := func(ctx context.Context) (hwsauth.DBTransaction, error) {
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
return tx, err
|
||||
}
|
||||
auth, err := hwsauth.NewAuthenticator(
|
||||
models.GetUserFromID,
|
||||
config.HWSAuth,
|
||||
models.GetUserByID,
|
||||
server,
|
||||
conn,
|
||||
beginTx,
|
||||
logger,
|
||||
handler.ErrorPage,
|
||||
)
|
||||
@@ -31,21 +37,8 @@ func setupAuth(
|
||||
return nil, errors.Wrap(err, "hwsauth.NewAuthenticator")
|
||||
}
|
||||
|
||||
auth.SSL = config.SSL
|
||||
auth.AccessTokenExpiry = config.AccessTokenExpiry
|
||||
auth.RefreshTokenExpiry = config.RefreshTokenExpiry
|
||||
auth.TokenFreshTime = config.TokenFreshTime
|
||||
auth.TrustedHost = config.TrustedHost
|
||||
auth.SecretKey = config.SecretKey
|
||||
auth.LandingPage = "/profile"
|
||||
|
||||
auth.IgnorePaths(ignoredPaths...)
|
||||
|
||||
err = auth.Initialise()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "auth.Initialise")
|
||||
}
|
||||
|
||||
contexts.CurrentUser = auth.CurrentModel
|
||||
|
||||
return auth, nil
|
||||
|
||||
52
cmd/projectreshoot/db.go
Normal file
52
cmd/projectreshoot/db.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"projectreshoot/internal/config"
|
||||
"projectreshoot/internal/models"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
"github.com/uptrace/bun/dialect/pgdialect"
|
||||
"github.com/uptrace/bun/driver/pgdriver"
|
||||
)
|
||||
|
||||
func setupBun(ctx context.Context, cfg *config.DBConfig, resetDB bool) (db *bun.DB, close func() error, err error) {
|
||||
dsn := fmt.Sprintf("postgres://%s:%s@%s:%v/%s?sslmode=%s", cfg.User, cfg.Password, cfg.Host, cfg.Port, cfg.DB, cfg.SSL)
|
||||
sqldb := sql.OpenDB(pgdriver.NewConnector(pgdriver.WithDSN(dsn)))
|
||||
db = bun.NewDB(sqldb, pgdialect.New())
|
||||
close = sqldb.Close
|
||||
|
||||
err = loadModels(ctx, db, resetDB)
|
||||
if err != nil {
|
||||
return nil, nil, errors.Wrap(err, "loadModels")
|
||||
}
|
||||
|
||||
return db, close, nil
|
||||
}
|
||||
|
||||
func loadModels(ctx context.Context, db *bun.DB, resetDB bool) error {
|
||||
models := []any{
|
||||
(*models.UserBun)(nil),
|
||||
}
|
||||
|
||||
for _, model := range models {
|
||||
_, err := db.NewCreateTable().
|
||||
Model(model).
|
||||
IfNotExists().
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.NewCreateTable")
|
||||
}
|
||||
if resetDB {
|
||||
err = db.ResetModel(ctx, model)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.ResetModel")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
func setupDBConn(dbName string) (*sql.DB, error) {
|
||||
opts := "_journal_mode=WAL&_synchronous=NORMAL&_txlock=IMMEDIATE"
|
||||
file := fmt.Sprintf("file:%s.db?%s", dbName, opts)
|
||||
conn, err := sql.Open("sqlite3", file)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "sql.Open")
|
||||
}
|
||||
err = checkDBVersion(conn, dbName)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "checkDBVersion")
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
// Check the database version
|
||||
func checkDBVersion(db *sql.DB, dbName string) error {
|
||||
expectVer, err := strconv.Atoi(dbName)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "strconv.Atoi")
|
||||
}
|
||||
query := `SELECT version_id FROM goose_db_version WHERE is_applied = 1
|
||||
ORDER BY version_id DESC LIMIT 1`
|
||||
rows, err := db.Query(query)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "db.Query")
|
||||
}
|
||||
defer rows.Close()
|
||||
if rows.Next() {
|
||||
var version int
|
||||
err = rows.Scan(&version)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "rows.Scan")
|
||||
}
|
||||
if version != expectVer {
|
||||
return errors.New("Version mismatch")
|
||||
}
|
||||
} else {
|
||||
return errors.New("No version found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -7,22 +7,18 @@ import (
|
||||
|
||||
func setupFlags() map[string]string {
|
||||
// Parse commandline args
|
||||
host := flag.String("host", "", "Override host to listen on")
|
||||
port := flag.String("port", "", "Override port to listen on")
|
||||
test := flag.Bool("test", false, "Run server in test mode")
|
||||
dbver := flag.Bool("dbver", false, "Get the version of the database required")
|
||||
loglevel := flag.String("loglevel", "", "Set log level")
|
||||
logoutput := flag.String("logoutput", "", "Set log destination (file, console or both)")
|
||||
resetDB := flag.Bool("resetdb", false, "Reset all the database tables with the updated models")
|
||||
printEnv := flag.Bool("printenv", false, "Print all environment variables and their documentation")
|
||||
genEnv := flag.String("genenv", "", "Generate a .env file with all environment variables (specify filename)")
|
||||
envfile := flag.String("envfile", ".env", "Specify a .env file to use for the configuration")
|
||||
flag.Parse()
|
||||
|
||||
// Map the args for easy access
|
||||
args := map[string]string{
|
||||
"host": *host,
|
||||
"port": *port,
|
||||
"test": strconv.FormatBool(*test),
|
||||
"dbver": strconv.FormatBool(*dbver),
|
||||
"loglevel": *loglevel,
|
||||
"logoutput": *logoutput,
|
||||
"resetdb": strconv.FormatBool(*resetDB),
|
||||
"printenv": strconv.FormatBool(*printEnv),
|
||||
"genenv": *genEnv,
|
||||
"envfile": *envfile,
|
||||
}
|
||||
return args
|
||||
}
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"projectreshoot/internal/config"
|
||||
@@ -10,39 +9,31 @@ import (
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/jwt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func setupHttpServer(
|
||||
staticFS *fs.FS,
|
||||
config *config.Config,
|
||||
logger *hlog.Logger,
|
||||
conn *sql.DB,
|
||||
tokenGen *jwt.TokenGenerator,
|
||||
bun *bun.DB,
|
||||
) (server *hws.Server, err error) {
|
||||
if staticFS == nil {
|
||||
return nil, errors.New("No filesystem provided")
|
||||
}
|
||||
fs := http.FS(*staticFS)
|
||||
httpServer, err := hws.NewServer(
|
||||
config.Host,
|
||||
config.Port,
|
||||
)
|
||||
httpServer, err := hws.NewServer(config.HWS)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hws.NewServer")
|
||||
}
|
||||
httpServer.ReadHeaderTimeout(config.ReadHeaderTimeout)
|
||||
httpServer.WriteTimeout(config.WriteTimeout)
|
||||
httpServer.IdleTimeout(config.IdleTimeout)
|
||||
httpServer.GZIP = config.GZIP
|
||||
|
||||
ignoredPaths := []string{
|
||||
"/static/css/output.css",
|
||||
"/static/favicon.ico",
|
||||
}
|
||||
|
||||
auth, err := setupAuth(config, logger, conn, httpServer, ignoredPaths)
|
||||
auth, err := setupAuth(config, logger, bun, httpServer, ignoredPaths)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "setupAuth")
|
||||
}
|
||||
@@ -62,7 +53,7 @@ func setupHttpServer(
|
||||
return nil, errors.Wrap(err, "httpServer.LoggerIgnorePaths")
|
||||
}
|
||||
|
||||
err = addRoutes(httpServer, &fs, config, logger, conn, tokenGen, auth)
|
||||
err = addRoutes(httpServer, &fs, config, logger, bun, auth)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "addRoutes")
|
||||
}
|
||||
|
||||
@@ -3,17 +3,18 @@ package main
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"projectreshoot/internal/config"
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
// Take in the desired logOutput and a console writer to use
|
||||
func setupLogger(logLevel hlog.Level, logOutput string, w *io.Writer, logDirectory string) (*hlog.Logger, error) {
|
||||
func setupLogger(cfg *config.HLOGConfig, w *io.Writer) (*hlog.Logger, error) {
|
||||
// Setup the logfile
|
||||
var logfile *os.File = nil
|
||||
if logOutput == "both" || logOutput == "file" {
|
||||
logfile, err := hlog.NewLogFile(logDirectory)
|
||||
if cfg.LogOutput == "both" || cfg.LogOutput == "file" {
|
||||
logfile, err := hlog.NewLogFile(cfg.LogDir)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hlog")
|
||||
}
|
||||
@@ -22,11 +23,11 @@ func setupLogger(logLevel hlog.Level, logOutput string, w *io.Writer, logDirecto
|
||||
|
||||
// Setup the console writer
|
||||
var consoleWriter io.Writer
|
||||
if logOutput == "both" || logOutput == "console" {
|
||||
if cfg.LogOutput == "both" || cfg.LogOutput == "console" {
|
||||
if w != nil {
|
||||
consoleWriter = *w
|
||||
} else {
|
||||
if logOutput == "console" {
|
||||
if cfg.LogOutput == "console" {
|
||||
return nil, errors.New("Console logging specified as sole method but no writer provided")
|
||||
}
|
||||
}
|
||||
@@ -34,10 +35,10 @@ func setupLogger(logLevel hlog.Level, logOutput string, w *io.Writer, logDirecto
|
||||
|
||||
// Setup the logger
|
||||
logger, err := hlog.NewLogger(
|
||||
logLevel,
|
||||
cfg.LogLevel,
|
||||
consoleWriter,
|
||||
logfile,
|
||||
logDirectory,
|
||||
cfg.LogDir,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hlog")
|
||||
|
||||
@@ -13,13 +13,32 @@ func main() {
|
||||
args := setupFlags()
|
||||
ctx := context.Background()
|
||||
|
||||
config, err := config.GetConfig(args)
|
||||
// Handle printenv flag
|
||||
if args["printenv"] == "true" {
|
||||
if err := config.PrintEnvVars(os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to print environment variables: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Handle genenv flag
|
||||
if args["genenv"] != "" {
|
||||
if err := config.GenerateDotEnv(args["genenv"]); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "Failed to generate .env file: %s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
fmt.Printf("Successfully generated .env file: %s\n", args["genenv"])
|
||||
return
|
||||
}
|
||||
|
||||
cfg, err := config.GetConfig(args["envfile"])
|
||||
if err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", errors.Wrap(err, "Failed to load config"))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
if err := run(ctx, os.Stdout, args, config); err != nil {
|
||||
if err := run(ctx, os.Stdout, args, cfg); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -1,16 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"projectreshoot/internal/models"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func addMiddleware(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
) error {
|
||||
|
||||
err := server.AddMiddleware(
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"net/http"
|
||||
"projectreshoot/internal/config"
|
||||
"projectreshoot/internal/handler"
|
||||
"projectreshoot/internal/models"
|
||||
"projectreshoot/internal/view/page"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/jwt"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func addRoutes(
|
||||
@@ -20,9 +20,8 @@ func addRoutes(
|
||||
staticFS *http.FileSystem,
|
||||
config *config.Config,
|
||||
logger *hlog.Logger,
|
||||
conn *sql.DB,
|
||||
tokenGen *jwt.TokenGenerator,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
db *bun.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
) error {
|
||||
// Create the routes
|
||||
routes := []hws.Route{
|
||||
@@ -44,32 +43,32 @@ func addRoutes(
|
||||
{
|
||||
Path: "/login",
|
||||
Method: hws.MethodGET,
|
||||
Handler: auth.LogoutReq(handler.LoginPage(config.TrustedHost)),
|
||||
Handler: auth.LogoutReq(handler.LoginPage(config.HWSAuth.TrustedHost)),
|
||||
},
|
||||
{
|
||||
Path: "/login",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LogoutReq(handler.LoginRequest(server, auth, conn)),
|
||||
Handler: auth.LogoutReq(handler.LoginRequest(server, auth, db)),
|
||||
},
|
||||
{
|
||||
Path: "/register",
|
||||
Method: hws.MethodGET,
|
||||
Handler: auth.LogoutReq(handler.RegisterPage(config.TrustedHost)),
|
||||
Handler: auth.LogoutReq(handler.RegisterPage(config.HWSAuth.TrustedHost)),
|
||||
},
|
||||
{
|
||||
Path: "/register",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LogoutReq(handler.RegisterRequest(config, logger, conn, tokenGen)),
|
||||
Handler: auth.LogoutReq(handler.RegisterRequest(server, auth, db)),
|
||||
},
|
||||
{
|
||||
Path: "/logout",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handler.Logout(server, auth, conn),
|
||||
Handler: handler.Logout(server, auth, db),
|
||||
},
|
||||
{
|
||||
Path: "/reauthenticate",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LoginReq(handler.Reauthenticate(server, auth, conn)),
|
||||
Handler: auth.LoginReq(handler.Reauthenticate(server, auth, db)),
|
||||
},
|
||||
{
|
||||
Path: "/profile",
|
||||
@@ -89,17 +88,17 @@ func addRoutes(
|
||||
{
|
||||
Path: "/change-username",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LoginReq(auth.FreshReq(handler.ChangeUsername(server, auth, conn))),
|
||||
Handler: auth.LoginReq(auth.FreshReq(handler.ChangeUsername(server, auth, db))),
|
||||
},
|
||||
{
|
||||
Path: "/change-password",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LoginReq(auth.FreshReq(handler.ChangePassword(server, auth, conn))),
|
||||
Handler: auth.LoginReq(auth.FreshReq(handler.ChangePassword(server, auth, db))),
|
||||
},
|
||||
{
|
||||
Path: "/change-bio",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: auth.LoginReq(handler.ChangeBio(server, auth, conn)),
|
||||
Handler: auth.LoginReq(handler.ChangeBio(server, auth, db)),
|
||||
},
|
||||
{
|
||||
Path: "/movies",
|
||||
@@ -109,12 +108,12 @@ func addRoutes(
|
||||
{
|
||||
Path: "/search-movies",
|
||||
Method: hws.MethodPOST,
|
||||
Handler: handler.SearchMovies(config, logger),
|
||||
Handler: handler.SearchMovies(config.TMDB, logger),
|
||||
},
|
||||
{
|
||||
Path: "/movie/{movie_id}",
|
||||
Method: hws.MethodGET,
|
||||
Handler: handler.Movie(server, config),
|
||||
Handler: handler.Movie(server, config.TMDB),
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,17 +2,15 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/signal"
|
||||
"projectreshoot/internal/config"
|
||||
"projectreshoot/pkg/embedfs"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/jwt"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -21,13 +19,7 @@ func run(ctx context.Context, w io.Writer, args map[string]string, config *confi
|
||||
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||
defer cancel()
|
||||
|
||||
// Return the version of the database required
|
||||
if args["dbver"] == "true" {
|
||||
fmt.Fprintf(w, "Database version: %s\n", config.DBName)
|
||||
return nil
|
||||
}
|
||||
|
||||
logger, err := setupLogger(config.LogLevel, config.LogOutput, &w, config.LogDir)
|
||||
logger, err := setupLogger(config.HLOG, &w)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setupLogger")
|
||||
}
|
||||
@@ -35,11 +27,15 @@ func run(ctx context.Context, w io.Writer, args map[string]string, config *confi
|
||||
// Setup the database connection
|
||||
logger.Debug().Msg("Config loaded and logger started")
|
||||
logger.Debug().Msg("Connecting to database")
|
||||
conn, err := setupDBConn(config.DBName)
|
||||
resetdb, err := strconv.ParseBool(args["resetdb"])
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "strconv.ParseBool")
|
||||
}
|
||||
bun, closedb, err := setupBun(ctx, config.DB, resetdb)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setupDBConn")
|
||||
}
|
||||
defer conn.Close()
|
||||
defer closedb()
|
||||
|
||||
// Setup embedded files
|
||||
logger.Debug().Msg("Getting embedded files")
|
||||
@@ -48,19 +44,8 @@ func run(ctx context.Context, w io.Writer, args map[string]string, config *confi
|
||||
return errors.Wrap(err, "getStaticFiles")
|
||||
}
|
||||
|
||||
// Setup TokenGenerator
|
||||
logger.Debug().Msg("Creating TokenGenerator")
|
||||
tokenGen, err := jwt.CreateGenerator(
|
||||
config.AccessTokenExpiry,
|
||||
config.RefreshTokenExpiry,
|
||||
config.TokenFreshTime,
|
||||
config.TrustedHost,
|
||||
config.SecretKey,
|
||||
conn,
|
||||
)
|
||||
|
||||
logger.Debug().Msg("Setting up HTTP server")
|
||||
httpServer, err := setupHttpServer(&staticFS, config, logger, conn, tokenGen)
|
||||
httpServer, err := setupHttpServer(&staticFS, config, logger, bun)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "setupHttpServer")
|
||||
}
|
||||
|
||||
32
go.mod
32
go.mod
@@ -4,44 +4,50 @@ go 1.25.5
|
||||
|
||||
require (
|
||||
git.haelnorr.com/h/golib/cookies v0.9.0
|
||||
git.haelnorr.com/h/golib/env v0.9.0
|
||||
git.haelnorr.com/h/golib/hlog v0.9.0
|
||||
git.haelnorr.com/h/golib/hws v0.1.0
|
||||
git.haelnorr.com/h/golib/hwsauth v0.2.0
|
||||
git.haelnorr.com/h/golib/jwt v0.9.2
|
||||
git.haelnorr.com/h/golib/env v0.9.1
|
||||
git.haelnorr.com/h/golib/hlog v0.9.1
|
||||
git.haelnorr.com/h/golib/hws v0.2.0
|
||||
git.haelnorr.com/h/golib/hwsauth v0.3.1
|
||||
git.haelnorr.com/h/golib/tmdb v0.8.0
|
||||
github.com/a-h/templ v0.3.977
|
||||
github.com/joho/godotenv v1.5.1
|
||||
github.com/mattn/go-sqlite3 v1.14.24
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pressly/goose/v3 v3.24.1
|
||||
golang.org/x/crypto v0.33.0
|
||||
github.com/uptrace/bun v1.2.16
|
||||
golang.org/x/crypto v0.45.0
|
||||
modernc.org/sqlite v1.35.0
|
||||
)
|
||||
|
||||
replace git.haelnorr.com/h/golib/hwsauth => /home/haelnorr/projects/golib/hwsauth
|
||||
|
||||
replace git.haelnorr.com/h/golib/hws => /home/haelnorr/projects/golib/hws
|
||||
|
||||
require (
|
||||
git.haelnorr.com/h/golib/jwt v0.10.0 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/golang-jwt/jwt v3.2.2+incompatible // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/jinzhu/inflection v1.0.0 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mfridman/interpolate v0.0.2 // indirect
|
||||
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 // indirect
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||
github.com/rs/zerolog v1.34.0 // indirect
|
||||
github.com/sethvargo/go-retry v0.3.0 // indirect
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 // indirect
|
||||
golang.org/x/sync v0.16.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/sys v0.40.0 // indirect
|
||||
k8s.io/apimachinery v0.35.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 // indirect
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570 // indirect
|
||||
mellium.im/sasl v0.3.2 // indirect
|
||||
modernc.org/libc v1.61.13 // indirect
|
||||
modernc.org/mathutil v1.7.1 // indirect
|
||||
modernc.org/memory v1.8.2 // indirect
|
||||
|
||||
52
go.sum
52
go.sum
@@ -1,11 +1,15 @@
|
||||
git.haelnorr.com/h/golib/cookies v0.9.0 h1:Vf+eX1prHkKuGrQon1BHY87yaPc1H+HJFRXDOV/AuWs=
|
||||
git.haelnorr.com/h/golib/cookies v0.9.0/go.mod h1:y1385YExI9gLwckCVDCYVcsFXr6N7T3brJjnJD2QIuo=
|
||||
git.haelnorr.com/h/golib/env v0.9.0 h1:Ahqr3PbHy7HdWEHUhylzIZy6Gg8mST5UdgKlU2RAhls=
|
||||
git.haelnorr.com/h/golib/env v0.9.0/go.mod h1:glUQVdA1HMKX1avTDyTyuhcr36SSxZtlJxKDT5KTztg=
|
||||
git.haelnorr.com/h/golib/hlog v0.9.0 h1:ib8n2MdmiRK2TF067p220kXmhDe9aAnlcsgpuv+QpvE=
|
||||
git.haelnorr.com/h/golib/hlog v0.9.0/go.mod h1:oOlzb8UVHUYP1k7dN5PSJXVskAB2z8EYgRN85jAi0Zk=
|
||||
git.haelnorr.com/h/golib/jwt v0.9.2 h1:l1Ow7DPGACAU54CnMP/NlZjdc4nRD1wr3xZ8a7taRvU=
|
||||
git.haelnorr.com/h/golib/jwt v0.9.2/go.mod h1:fbuPrfucT9lL0faV5+Q5Gk9WFJxPlwzRPpbMQKYZok4=
|
||||
git.haelnorr.com/h/golib/env v0.9.1 h1:2Vsj+mJKnO5f1Md1GO5v9ggLN5zWa0baCewcSHTjoNY=
|
||||
git.haelnorr.com/h/golib/env v0.9.1/go.mod h1:glUQVdA1HMKX1avTDyTyuhcr36SSxZtlJxKDT5KTztg=
|
||||
git.haelnorr.com/h/golib/hlog v0.9.1 h1:9VmE/IQTfD8LAEyTbUCZLy/+8PbcHA1Kob/WQHRHKzc=
|
||||
git.haelnorr.com/h/golib/hlog v0.9.1/go.mod h1:oOlzb8UVHUYP1k7dN5PSJXVskAB2z8EYgRN85jAi0Zk=
|
||||
git.haelnorr.com/h/golib/hws v0.2.0 h1:MR2Tu2qPaW+/oK8aXFJLRFaYZIHgKiex3t3zE41cu1U=
|
||||
git.haelnorr.com/h/golib/hws v0.2.0/go.mod h1:6ZlRKnt8YMpv5XcMXmyBGmD1/euvBo3d1azEvHJjOLo=
|
||||
git.haelnorr.com/h/golib/hwsauth v0.3.1 h1:+vVkVj/5DTPXSp7em2DqF3QuovhHKSCRTRFbwRQ7g8E=
|
||||
git.haelnorr.com/h/golib/hwsauth v0.3.1/go.mod h1:WHHMy1EVQWrHtyJx+gQQkB+5otJ4E6ZyEtKBjqZqKhQ=
|
||||
git.haelnorr.com/h/golib/jwt v0.10.0 h1:8cI8mSnb8X+EmJtrBO/5UZwuBMtib0IE9dv85gkm94E=
|
||||
git.haelnorr.com/h/golib/jwt v0.10.0/go.mod h1:fbuPrfucT9lL0faV5+Q5Gk9WFJxPlwzRPpbMQKYZok4=
|
||||
git.haelnorr.com/h/golib/tmdb v0.8.0 h1:OQ6M2TB8FHm8fJD7/ebfWm63Duzfp0kmFX9genEig34=
|
||||
git.haelnorr.com/h/golib/tmdb v0.8.0/go.mod h1:mGKYa3o3z0IsQ5EO3MPmnL2Bwl2sSMsUHXVgaIGR7Z0=
|
||||
github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU=
|
||||
@@ -28,6 +32,8 @@ github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlG
|
||||
github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw=
|
||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
||||
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||
@@ -37,8 +43,6 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
|
||||
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
|
||||
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
||||
github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY=
|
||||
github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg=
|
||||
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||
@@ -49,6 +53,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pressly/goose/v3 v3.24.1 h1:bZmxRco2uy5uu5Ng1MMVEfYsFlrMJI+e/VMXHQ3C4LY=
|
||||
github.com/pressly/goose/v3 v3.24.1/go.mod h1:rEWreU9uVtt0DHCyLzF9gRcWiiTF/V+528DV+4DORug=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1 h1:GJYJZwO6IdxN/IKbneznS6yPkVC+c3zyY/j19c++5Fg=
|
||||
github.com/puzpuzpuz/xsync/v3 v3.5.1/go.mod h1:VjzYrABPabuM4KyBh1Ftq6u8nhwY5tBPKP9jpmh0nnA=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
|
||||
@@ -58,10 +64,26 @@ github.com/sethvargo/go-retry v0.3.0 h1:EEt31A35QhrcRZtrYFDTBg91cqZVnFL2navjDrah
|
||||
github.com/sethvargo/go-retry v0.3.0/go.mod h1:mNX17F0C/HguQMyMyJxcnU471gOZGxCLyYaFyAZraas=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc h1:9lRDQMhESg+zvGYmW5DyG0UqvY96Bu5QYsTLvCHdrgo=
|
||||
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc/go.mod h1:bciPuU6GHm1iF1pBvUfxfsH0Wmnc2VbpgvbI9ZWuIRs=
|
||||
github.com/uptrace/bun v1.2.16 h1:QlObi6ZIK5Ao7kAALnh91HWYNZUBbVwye52fmlQM9kc=
|
||||
github.com/uptrace/bun v1.2.16/go.mod h1:jMoNg2n56ckaawi/O/J92BHaECmrz6IRjuMWqlMaMTM=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16 h1:KFNZ0LxAyczKNfK/IJWMyaleO6eI9/Z5tUv3DE1NVL4=
|
||||
github.com/uptrace/bun/dialect/pgdialect v1.2.16/go.mod h1:IJdMeV4sLfh0LDUZl7TIxLI0LipF1vwTK3hBC7p5qLo=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16 h1:b1kpXKUxtTSGYow5Vlsb+dKV3z0R7aSAJNfMfKp61ZU=
|
||||
github.com/uptrace/bun/driver/pgdriver v1.2.16/go.mod h1:H6lUZ9CBfp1X5Vq62YGSV7q96/v94ja9AYFjKvdoTk0=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
|
||||
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
|
||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
golang.org/x/crypto v0.33.0 h1:IOBPskki6Lysi0lo9qQvbxiQ+FvsCC/YWOecCHAixus=
|
||||
golang.org/x/crypto v0.33.0/go.mod h1:bVdXmD7IV/4GdElGPozy6U7lWdRXA4qyRVGJV57uQ5M=
|
||||
golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q=
|
||||
golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8 h1:aAcj0Da7eBAtrTp03QXWvm88pSyOt+UgdZw2BFZ+lEw=
|
||||
golang.org/x/exp v0.0.0-20240325151524-a685a6edb6d8/go.mod h1:CQ1k9gNrJ50XIzaKCRR2hssIjF07kZFEiieALBM/ARQ=
|
||||
golang.org/x/mod v0.26.0 h1:EGMPT//Ezu+ylkCijjPc+f4Aih7sZvaAr+O3EHBxvZg=
|
||||
@@ -71,8 +93,8 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
|
||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
|
||||
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0=
|
||||
golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
@@ -81,8 +103,10 @@ k8s.io/apimachinery v0.35.0 h1:Z2L3IHvPVv/MJ7xRxHEtk6GoJElaAqDCCU0S6ncYok8=
|
||||
k8s.io/apimachinery v0.35.0/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns=
|
||||
k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk=
|
||||
k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 h1:SjGebBtkBqHFOli+05xYbK8YF1Dzkbzn+gDM4X9T4Ck=
|
||||
k8s.io/utils v0.0.0-20251002143259-bc988d571ff4/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570 h1:JT4W8lsdrGENg9W+YwwdLJxklIuKWdRm+BC+xt33FOY=
|
||||
k8s.io/utils v0.0.0-20260108192941-914a6e750570/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk=
|
||||
mellium.im/sasl v0.3.2 h1:PT6Xp7ccn9XaXAnJ03FcEjmAn7kK1x7aoXV6F+Vmrl0=
|
||||
mellium.im/sasl v0.3.2/go.mod h1:NKXDi1zkr+BlMHLQjY3ofYuU4KSPFxknb8mfEu6SveY=
|
||||
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0=
|
||||
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo=
|
||||
|
||||
37
internal/config/auth.go
Normal file
37
internal/config/auth.go
Normal file
@@ -0,0 +1,37 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HWSAUTHConfig struct {
|
||||
SSL bool // ENV HWSAUTH_SSL: Flag for SSL Mode (default: false)
|
||||
TrustedHost string // ENV HWSAUTH_TRUSTED_HOST: Full server address to accept as trusted SSL host (required if SSL is true)
|
||||
SecretKey string // ENV HWSAUTH_SECRET_KEY: Secret key for signing tokens (required)
|
||||
AccessTokenExpiry int64 // ENV HWSAUTH_ACCESS_TOKEN_EXPIRY: Access token expiry in minutes (default: 5)
|
||||
RefreshTokenExpiry int64 // ENV HWSAUTH_REFRESH_TOKEN_EXPIRY: Refresh token expiry in minutes (default: 1440)
|
||||
TokenFreshTime int64 // ENV HWSAUTH_TOKEN_FRESH_TIME: Time for tokens to stay fresh in minutes (default: 5)
|
||||
}
|
||||
|
||||
func setupHWSAuth() (*HWSAUTHConfig, error) {
|
||||
ssl := env.Bool("HWSAUTH_SSL", false)
|
||||
trustedHost := env.String("HWS_TRUSTED_HOST", "")
|
||||
if ssl && trustedHost == "" {
|
||||
return nil, errors.New("SSL is enabled and no HWS_TRUSTED_HOST set")
|
||||
}
|
||||
cfg := &HWSAUTHConfig{
|
||||
SSL: ssl,
|
||||
TrustedHost: trustedHost,
|
||||
SecretKey: env.String("HWSAUTH_SECRET_KEY", ""),
|
||||
AccessTokenExpiry: env.Int64("HWSAUTH_ACCESS_TOKEN_EXPIRY", 5),
|
||||
RefreshTokenExpiry: env.Int64("HWSAUTH_REFRESH_TOKEN_EXPIRY", 1440),
|
||||
TokenFreshTime: env.Int64("HWSAUTH_TOKEN_FRESH_TIME", 5),
|
||||
}
|
||||
|
||||
if cfg.SecretKey == "" {
|
||||
return nil, errors.New("Envar not set: HWSAUTH_SECRET_KEY")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -1,116 +1,55 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/tmdb"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"github.com/joho/godotenv"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Host string // Host to listen on
|
||||
Port string // Port to listen on
|
||||
TrustedHost string // Domain/Hostname to accept as trusted
|
||||
SSL bool // Flag for SSL Mode
|
||||
GZIP bool // Flag for GZIP compression on requests
|
||||
ReadHeaderTimeout time.Duration // Timeout for reading request headers in seconds
|
||||
WriteTimeout time.Duration // Timeout for writing requests in seconds
|
||||
IdleTimeout time.Duration // Timeout for idle connections in seconds
|
||||
DBName string // Filename of the db - hardcoded and doubles as DB version
|
||||
DBLockTimeout time.Duration // Timeout for acquiring database lock
|
||||
SecretKey string // Secret key for signing tokens
|
||||
AccessTokenExpiry int64 // Access token expiry in minutes
|
||||
RefreshTokenExpiry int64 // Refresh token expiry in minutes
|
||||
TokenFreshTime int64 // Time for tokens to stay fresh in minutes
|
||||
LogLevel hlog.Level // Log level for global logging. Defaults to info
|
||||
LogOutput string // "file", "console", or "both". Defaults to console
|
||||
LogDir string // Path to create log files
|
||||
TMDBToken string // Read access token for TMDB API
|
||||
TMDBConfig *tmdb.Config // Config data for interfacing with TMDB
|
||||
DB *DBConfig
|
||||
HWS *hws.Config
|
||||
HWSAuth *hwsauth.Config
|
||||
TMDB *TMDBConfig
|
||||
HLOG *HLOGConfig
|
||||
}
|
||||
|
||||
// Load the application configuration and get a pointer to the Config object
|
||||
func GetConfig(args map[string]string) (*Config, error) {
|
||||
godotenv.Load(".env")
|
||||
var (
|
||||
host string
|
||||
port string
|
||||
logLevel hlog.Level
|
||||
logOutput string
|
||||
valid bool
|
||||
)
|
||||
func GetConfig(envfile string) (*Config, error) {
|
||||
godotenv.Load(envfile)
|
||||
|
||||
if args["host"] != "" {
|
||||
host = args["host"]
|
||||
} else {
|
||||
host = env.String("HOST", "127.0.0.1")
|
||||
}
|
||||
if args["port"] != "" {
|
||||
port = args["port"]
|
||||
} else {
|
||||
port = env.String("PORT", "3010")
|
||||
}
|
||||
if args["loglevel"] != "" {
|
||||
logLevel = hlog.LogLevel(args["loglevel"])
|
||||
} else {
|
||||
logLevel = hlog.LogLevel(env.String("LOG_LEVEL", "info"))
|
||||
}
|
||||
if args["logoutput"] != "" {
|
||||
opts := map[string]string{
|
||||
"both": "both",
|
||||
"file": "file",
|
||||
"console": "console",
|
||||
}
|
||||
logOutput, valid = opts[args["logoutput"]]
|
||||
if !valid {
|
||||
logOutput = "console"
|
||||
fmt.Println(
|
||||
"Log output type was not parsed correctly. Defaulting to console only",
|
||||
)
|
||||
}
|
||||
} else {
|
||||
logOutput = env.String("LOG_OUTPUT", "console")
|
||||
}
|
||||
if logOutput != "both" && logOutput != "console" && logOutput != "file" {
|
||||
logOutput = "console"
|
||||
}
|
||||
tmdbcfg, err := tmdb.GetConfig(os.Getenv("TMDB_API_TOKEN"))
|
||||
db, err := setupDB()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tmdb.GetConfig")
|
||||
return nil, errors.Wrap(err, "setupDB")
|
||||
}
|
||||
|
||||
hws, err := hws.ConfigFromEnv()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hws.ConfigFromEnv")
|
||||
}
|
||||
|
||||
hwsAuth, err := hwsauth.ConfigFromEnv()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hwsauth.ConfigFromEnv")
|
||||
}
|
||||
|
||||
tmdb, err := setupTMDB()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "setupTMDB")
|
||||
}
|
||||
|
||||
hlog, err := setupHLOG()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "setupHLOG")
|
||||
}
|
||||
|
||||
config := &Config{
|
||||
Host: host,
|
||||
Port: port,
|
||||
TrustedHost: env.String("TRUSTED_HOST", "127.0.0.1"),
|
||||
SSL: env.Bool("SSL_MODE", false),
|
||||
GZIP: env.Bool("GZIP", false),
|
||||
ReadHeaderTimeout: env.Duration("READ_HEADER_TIMEOUT", 2) * time.Second,
|
||||
WriteTimeout: env.Duration("WRITE_TIMEOUT", 10) * time.Second,
|
||||
IdleTimeout: env.Duration("IDLE_TIMEOUT", 120) * time.Second,
|
||||
DBName: "00001",
|
||||
DBLockTimeout: env.Duration("DB_LOCK_TIMEOUT", 60),
|
||||
SecretKey: env.String("SECRET_KEY", ""),
|
||||
AccessTokenExpiry: env.Int64("ACCESS_TOKEN_EXPIRY", 5),
|
||||
RefreshTokenExpiry: env.Int64("REFRESH_TOKEN_EXPIRY", 1440), // defaults to 1 day
|
||||
TokenFreshTime: env.Int64("TOKEN_FRESH_TIME", 5),
|
||||
LogLevel: logLevel,
|
||||
LogOutput: logOutput,
|
||||
LogDir: env.String("LOG_DIR", ""),
|
||||
TMDBToken: env.String("TMDB_API_TOKEN", ""),
|
||||
TMDBConfig: tmdbcfg,
|
||||
}
|
||||
|
||||
if config.SecretKey == "" && args["dbver"] != "true" {
|
||||
return nil, errors.New("Envar not set: SECRET_KEY")
|
||||
}
|
||||
if config.TMDBToken == "" && args["dbver"] != "true" {
|
||||
return nil, errors.New("Envar not set: TMDB_API_TOKEN")
|
||||
DB: db,
|
||||
HWS: hws,
|
||||
HWSAuth: hwsAuth,
|
||||
TMDB: tmdb,
|
||||
HLOG: hlog,
|
||||
}
|
||||
|
||||
return config, nil
|
||||
|
||||
55
internal/config/db.go
Normal file
55
internal/config/db.go
Normal file
@@ -0,0 +1,55 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type DBConfig struct {
|
||||
User string // ENV DB_USER: Database user for authentication (required)
|
||||
Password string // ENV DB_PASSWORD: Database password for authentication (required)
|
||||
Host string // ENV DB_HOST: Database host address (required)
|
||||
Port uint16 // ENV DB_PORT: Database port (default: 5432)
|
||||
DB string // ENV DB_NAME: Database name to connect to (required)
|
||||
SSL string // ENV DB_SSL: SSL mode for connection (default: disable)
|
||||
}
|
||||
|
||||
func setupDB() (*DBConfig, error) {
|
||||
cfg := &DBConfig{
|
||||
User: env.String("DB_USER", ""),
|
||||
Password: env.String("DB_PASSWORD", ""),
|
||||
Host: env.String("DB_HOST", ""),
|
||||
Port: env.UInt16("DB_PORT", 5432),
|
||||
DB: env.String("DB_NAME", ""),
|
||||
SSL: env.String("DB_SSL", "disable"),
|
||||
}
|
||||
|
||||
// Validate SSL mode
|
||||
validSSLModes := map[string]bool{
|
||||
"disable": true,
|
||||
"require": true,
|
||||
"verify-ca": true,
|
||||
"verify-full": true,
|
||||
"allow": true,
|
||||
"prefer": true,
|
||||
}
|
||||
if !validSSLModes[cfg.SSL] {
|
||||
return nil, errors.Errorf("Invalid DB_SSL value: %s. Must be one of: disable, allow, prefer, require, verify-ca, verify-full", cfg.SSL)
|
||||
}
|
||||
|
||||
// Check required fields
|
||||
if cfg.User == "" {
|
||||
return nil, errors.New("Envar not set: DB_USER")
|
||||
}
|
||||
if cfg.Password == "" {
|
||||
return nil, errors.New("Envar not set: DB_PASSWORD")
|
||||
}
|
||||
if cfg.Host == "" {
|
||||
return nil, errors.New("Envar not set: DB_HOST")
|
||||
}
|
||||
if cfg.DB == "" {
|
||||
return nil, errors.New("Envar not set: DB_NAME")
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
114
internal/config/envdoc.go
Normal file
114
internal/config/envdoc.go
Normal file
@@ -0,0 +1,114 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnvVar represents an environment variable with its documentation
|
||||
type EnvVar struct {
|
||||
Name string
|
||||
Description string
|
||||
Default string
|
||||
HasDefault bool
|
||||
Required bool
|
||||
}
|
||||
|
||||
// extractEnvVars parses a struct's field comments to extract environment variable documentation
|
||||
func extractEnvVars(structType reflect.Type, fieldIndex int) *EnvVar {
|
||||
field := structType.Field(fieldIndex)
|
||||
tag := field.Tag.Get("comment")
|
||||
if tag == "" {
|
||||
// Try to get the comment from the struct field's tag or use reflection
|
||||
// For now, we'll parse it manually from the comment string
|
||||
return nil
|
||||
}
|
||||
|
||||
comment := tag
|
||||
if !strings.HasPrefix(comment, "ENV ") {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Remove "ENV " prefix
|
||||
comment = strings.TrimPrefix(comment, "ENV ")
|
||||
|
||||
// Extract name and description
|
||||
parts := strings.SplitN(comment, ":", 2)
|
||||
if len(parts) != 2 {
|
||||
return nil
|
||||
}
|
||||
|
||||
name := strings.TrimSpace(parts[0])
|
||||
desc := strings.TrimSpace(parts[1])
|
||||
|
||||
// Check for default value in description
|
||||
defaultRegex := regexp.MustCompile(`\(default:\s*([^)]+)\)`)
|
||||
matches := defaultRegex.FindStringSubmatch(desc)
|
||||
|
||||
envVar := &EnvVar{
|
||||
Name: name,
|
||||
Description: desc,
|
||||
}
|
||||
|
||||
if len(matches) > 1 {
|
||||
envVar.Default = matches[1]
|
||||
envVar.HasDefault = true
|
||||
// Remove the default notation from description
|
||||
envVar.Description = strings.TrimSpace(defaultRegex.ReplaceAllString(desc, ""))
|
||||
}
|
||||
|
||||
return envVar
|
||||
}
|
||||
|
||||
// GetAllEnvVars returns a list of all environment variables used in the config
|
||||
func GetAllEnvVars() []EnvVar {
|
||||
var envVars []EnvVar
|
||||
|
||||
// Manually define all env vars based on the config structs
|
||||
// This is more reliable than reflection for extracting comments
|
||||
|
||||
// DBConfig
|
||||
envVars = append(envVars, []EnvVar{
|
||||
{Name: "DB_USER", Description: "Database user for authentication", HasDefault: false, Required: true},
|
||||
{Name: "DB_PASSWORD", Description: "Database password for authentication", HasDefault: false, Required: true},
|
||||
{Name: "DB_HOST", Description: "Database host address", HasDefault: false, Required: true},
|
||||
{Name: "DB_PORT", Description: "Database port", Default: "5432", HasDefault: true, Required: false},
|
||||
{Name: "DB_NAME", Description: "Database name to connect to", HasDefault: false, Required: true},
|
||||
{Name: "DB_SSL", Description: "SSL mode for connection", Default: "disable", HasDefault: true, Required: false},
|
||||
}...)
|
||||
|
||||
// HWSConfig
|
||||
envVars = append(envVars, []EnvVar{
|
||||
{Name: "HWS_HOST", Description: "Host to listen on", Default: "127.0.0.1", HasDefault: true, Required: false},
|
||||
{Name: "HWS_PORT", Description: "Port to listen on", Default: "3000", HasDefault: true, Required: false},
|
||||
{Name: "HWS_TRUSTED_HOST", Description: "Domain/Hostname to accept as trusted", Default: "same as Host", HasDefault: true, Required: false},
|
||||
{Name: "HWS_SSL", Description: "Flag for SSL Mode", Default: "false", HasDefault: true, Required: false},
|
||||
{Name: "HWS_GZIP", Description: "Flag for GZIP compression on requests", Default: "false", HasDefault: true, Required: false},
|
||||
{Name: "HWS_READ_HEADER_TIMEOUT", Description: "Timeout for reading request headers in seconds", Default: "2", HasDefault: true, Required: false},
|
||||
{Name: "HWS_WRITE_TIMEOUT", Description: "Timeout for writing requests in seconds", Default: "10", HasDefault: true, Required: false},
|
||||
{Name: "HWS_IDLE_TIMEOUT", Description: "Timeout for idle connections in seconds", Default: "120", HasDefault: true, Required: false},
|
||||
}...)
|
||||
|
||||
// HWSAUTHConfig
|
||||
envVars = append(envVars, []EnvVar{
|
||||
{Name: "HWSAUTH_SECRET_KEY", Description: "Secret key for signing tokens", HasDefault: false, Required: true},
|
||||
{Name: "HWSAUTH_ACCESS_TOKEN_EXPIRY", Description: "Access token expiry in minutes", Default: "5", HasDefault: true, Required: false},
|
||||
{Name: "HWSAUTH_REFRESH_TOKEN_EXPIRY", Description: "Refresh token expiry in minutes", Default: "1440", HasDefault: true, Required: false},
|
||||
{Name: "HWSAUTH_TOKEN_FRESH_TIME", Description: "Time for tokens to stay fresh in minutes", Default: "5", HasDefault: true, Required: false},
|
||||
}...)
|
||||
|
||||
// TMDBConfig
|
||||
envVars = append(envVars, []EnvVar{
|
||||
{Name: "TMDB_TOKEN", Description: "API token for TMDB", HasDefault: false, Required: true},
|
||||
}...)
|
||||
|
||||
// HLOGConfig
|
||||
envVars = append(envVars, []EnvVar{
|
||||
{Name: "LOG_LEVEL", Description: "Log level for global logging", Default: "info", HasDefault: true, Required: false},
|
||||
{Name: "LOG_OUTPUT", Description: "Output method for the logger (file, console, or both)", Default: "console", HasDefault: true, Required: false},
|
||||
{Name: "LOG_DIR", Description: "Path to create log files", HasDefault: false, Required: false},
|
||||
}...)
|
||||
|
||||
return envVars
|
||||
}
|
||||
95
internal/config/envgen.go
Normal file
95
internal/config/envgen.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GenerateDotEnv creates a new .env file with all environment variables and their defaults
|
||||
func GenerateDotEnv(filename string) error {
|
||||
envVars := GetAllEnvVars()
|
||||
|
||||
file, err := os.Create(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to create file: %w", err)
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
// Write header
|
||||
fmt.Fprintln(file, "# Environment Configuration")
|
||||
fmt.Fprintln(file, "# Generated by Project Reshoot")
|
||||
fmt.Fprintln(file, "#")
|
||||
fmt.Fprintln(file, "# Variables marked as (required) must be set")
|
||||
fmt.Fprintln(file, "# Variables with defaults can be left commented out to use the default value")
|
||||
fmt.Fprintln(file)
|
||||
|
||||
// Group by prefix
|
||||
groups := map[string][]EnvVar{
|
||||
"DB_": {},
|
||||
"HWS_": {},
|
||||
"HWSAUTH_": {},
|
||||
"TMDB_": {},
|
||||
"LOG_": {},
|
||||
}
|
||||
|
||||
for _, ev := range envVars {
|
||||
assigned := false
|
||||
for prefix := range groups {
|
||||
if strings.HasPrefix(ev.Name, prefix) {
|
||||
groups[prefix] = append(groups[prefix], ev)
|
||||
assigned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !assigned {
|
||||
// Handle ungrouped vars
|
||||
if _, ok := groups["OTHER"]; !ok {
|
||||
groups["OTHER"] = []EnvVar{}
|
||||
}
|
||||
groups["OTHER"] = append(groups["OTHER"], ev)
|
||||
}
|
||||
}
|
||||
|
||||
// Print each group
|
||||
groupOrder := []string{"DB_", "HWS_", "HWSAUTH_", "TMDB_", "LOG_", "OTHER"}
|
||||
groupTitles := map[string]string{
|
||||
"DB_": "Database Configuration",
|
||||
"HWS_": "HTTP Web Server Configuration",
|
||||
"HWSAUTH_": "Authentication Configuration",
|
||||
"TMDB_": "TMDB API Configuration",
|
||||
"LOG_": "Logging Configuration",
|
||||
"OTHER": "Other Configuration",
|
||||
}
|
||||
|
||||
for _, prefix := range groupOrder {
|
||||
vars, ok := groups[prefix]
|
||||
if !ok || len(vars) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(file, "# %s\n", groupTitles[prefix])
|
||||
fmt.Fprintln(file, strings.Repeat("#", len(groupTitles[prefix])+2))
|
||||
|
||||
for _, ev := range vars {
|
||||
// Write description as comment
|
||||
if ev.Required {
|
||||
fmt.Fprintf(file, "# %s (required)\n", ev.Description)
|
||||
// Leave required variables uncommented but empty
|
||||
fmt.Fprintf(file, "%s=\n", ev.Name)
|
||||
} else if ev.HasDefault {
|
||||
fmt.Fprintf(file, "# %s\n", ev.Description)
|
||||
// Comment out variables with defaults
|
||||
fmt.Fprintf(file, "# %s=%s\n", ev.Name, ev.Default)
|
||||
} else {
|
||||
fmt.Fprintf(file, "# %s\n", ev.Description)
|
||||
// Optional variables without defaults are commented out
|
||||
fmt.Fprintf(file, "# %s=\n", ev.Name)
|
||||
}
|
||||
fmt.Fprintln(file)
|
||||
}
|
||||
fmt.Fprintln(file)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
87
internal/config/envprint.go
Normal file
87
internal/config/envprint.go
Normal file
@@ -0,0 +1,87 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// PrintEnvVars writes all environment variables and their documentation to the provided writer
|
||||
func PrintEnvVars(w io.Writer) error {
|
||||
envVars := GetAllEnvVars()
|
||||
|
||||
// Find the longest name for alignment
|
||||
maxNameLen := 0
|
||||
for _, ev := range envVars {
|
||||
if len(ev.Name) > maxNameLen {
|
||||
maxNameLen = len(ev.Name)
|
||||
}
|
||||
}
|
||||
|
||||
// Print header
|
||||
fmt.Fprintln(w, "Environment Variables")
|
||||
fmt.Fprintln(w, strings.Repeat("=", 80))
|
||||
fmt.Fprintln(w)
|
||||
|
||||
// Group by prefix
|
||||
groups := map[string][]EnvVar{
|
||||
"DB_": {},
|
||||
"HWS_": {},
|
||||
"HWSAUTH_": {},
|
||||
"TMDB_": {},
|
||||
"LOG_": {},
|
||||
}
|
||||
|
||||
for _, ev := range envVars {
|
||||
assigned := false
|
||||
for prefix := range groups {
|
||||
if strings.HasPrefix(ev.Name, prefix) {
|
||||
groups[prefix] = append(groups[prefix], ev)
|
||||
assigned = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !assigned {
|
||||
// Handle ungrouped vars
|
||||
if _, ok := groups["OTHER"]; !ok {
|
||||
groups["OTHER"] = []EnvVar{}
|
||||
}
|
||||
groups["OTHER"] = append(groups["OTHER"], ev)
|
||||
}
|
||||
}
|
||||
|
||||
// Print each group
|
||||
groupOrder := []string{"DB_", "HWS_", "HWSAUTH_", "TMDB_", "LOG_", "OTHER"}
|
||||
groupTitles := map[string]string{
|
||||
"DB_": "Database Configuration",
|
||||
"HWS_": "HTTP Web Server Configuration",
|
||||
"HWSAUTH_": "Authentication Configuration",
|
||||
"TMDB_": "TMDB API Configuration",
|
||||
"LOG_": "Logging Configuration",
|
||||
"OTHER": "Other Configuration",
|
||||
}
|
||||
|
||||
for _, prefix := range groupOrder {
|
||||
vars, ok := groups[prefix]
|
||||
if !ok || len(vars) == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
fmt.Fprintf(w, "%s\n", groupTitles[prefix])
|
||||
fmt.Fprintln(w, strings.Repeat("-", len(groupTitles[prefix])))
|
||||
|
||||
for _, ev := range vars {
|
||||
padding := strings.Repeat(" ", maxNameLen-len(ev.Name))
|
||||
if ev.Required {
|
||||
fmt.Fprintf(w, " %s%s : %s (required)\n", ev.Name, padding, ev.Description)
|
||||
} else if ev.HasDefault {
|
||||
fmt.Fprintf(w, " %s%s : %s (default: %s)\n", ev.Name, padding, ev.Description, ev.Default)
|
||||
} else {
|
||||
fmt.Fprintf(w, " %s%s : %s\n", ev.Name, padding, ev.Description)
|
||||
}
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
29
internal/config/httpserver.go
Normal file
29
internal/config/httpserver.go
Normal file
@@ -0,0 +1,29 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
)
|
||||
|
||||
type HWSConfig struct {
|
||||
Host string // ENV HWS_HOST: Host to listen on (default: 127.0.0.1)
|
||||
Port uint64 // ENV HWS_PORT: Port to listen on (default: 3000)
|
||||
GZIP bool // ENV HWS_GZIP: Flag for GZIP compression on requests (default: false)
|
||||
ReadHeaderTimeout time.Duration // ENV HWS_READ_HEADER_TIMEOUT: Timeout for reading request headers in seconds (default: 2)
|
||||
WriteTimeout time.Duration // ENV HWS_WRITE_TIMEOUT: Timeout for writing requests in seconds (default: 10)
|
||||
IdleTimeout time.Duration // ENV HWS_IDLE_TIMEOUT: Timeout for idle connections in seconds (default: 120)
|
||||
}
|
||||
|
||||
func setupHWS() (*HWSConfig, error) {
|
||||
cfg := &HWSConfig{
|
||||
Host: env.String("HWS_HOST", "127.0.0.1"),
|
||||
Port: env.UInt64("HWS_PORT", 3000),
|
||||
GZIP: env.Bool("HWS_GZIP", false),
|
||||
ReadHeaderTimeout: time.Duration(env.Int("HWS_READ_HEADER_TIMEOUT", 2)) * time.Second,
|
||||
WriteTimeout: time.Duration(env.Int("HWS_WRITE_TIMEOUT", 10)) * time.Second,
|
||||
IdleTimeout: time.Duration(env.Int("HWS_IDLE_TIMEOUT", 120)) * time.Second,
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
36
internal/config/logger.go
Normal file
36
internal/config/logger.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type HLOGConfig struct {
|
||||
// ENV LOG_LEVEL: Log level for global logging. (default: info)
|
||||
LogLevel hlog.Level
|
||||
|
||||
// ENV LOG_OUTPUT: Output method for the logger. (default: console)
|
||||
// Valid options: "file", "console", "both"
|
||||
LogOutput string
|
||||
|
||||
// ENV LOG_DIR: Path to create log files
|
||||
LogDir string
|
||||
}
|
||||
|
||||
func setupHLOG() (*HLOGConfig, error) {
|
||||
logLevel, err := hlog.LogLevel(env.String("LOG_LEVEL", "info"))
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "hlog.LogLevel")
|
||||
}
|
||||
logOutput := env.String("LOG_OUTPUT", "console")
|
||||
if logOutput != "both" && logOutput != "console" && logOutput != "file" {
|
||||
return nil, errors.Errorf("Invalid LOG_OUTPUT: %s", logOutput)
|
||||
}
|
||||
cfg := &HLOGConfig{
|
||||
LogLevel: logLevel,
|
||||
LogOutput: logOutput,
|
||||
LogDir: env.String("LOG_DIR", ""),
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
28
internal/config/tmdb.go
Normal file
28
internal/config/tmdb.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.haelnorr.com/h/golib/env"
|
||||
"git.haelnorr.com/h/golib/tmdb"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type TMDBConfig struct {
|
||||
Token string // ENV TMDB_TOKEN: API token for TMDB (required)
|
||||
Config *tmdb.Config // Config data for interfacing with TMDB
|
||||
}
|
||||
|
||||
func setupTMDB() (*TMDBConfig, error) {
|
||||
token := env.String("TMDB_TOKEN", "")
|
||||
if token == "" {
|
||||
return nil, errors.New("No TMDB API Token provided")
|
||||
}
|
||||
tmdbcfg, err := tmdb.GetConfig(token)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tmdb.GetConfig")
|
||||
}
|
||||
cfg := &TMDBConfig{
|
||||
Token: token,
|
||||
Config: tmdbcfg,
|
||||
}
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -2,19 +2,20 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"projectreshoot/internal/models"
|
||||
"projectreshoot/internal/view/component/account"
|
||||
"projectreshoot/internal/view/page"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"git.haelnorr.com/h/golib/cookies"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Renders the account page on the 'General' subpage
|
||||
@@ -46,8 +47,8 @@ func AccountSubpage() http.Handler {
|
||||
// Handles a request to change the users username
|
||||
func ChangeUsername(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -55,7 +56,7 @@ func ChangeUsername(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
@@ -69,7 +70,7 @@ func ChangeUsername(
|
||||
}
|
||||
r.ParseForm()
|
||||
newUsername := r.FormValue("username")
|
||||
unique, err := models.CheckUsernameUnique(tx, newUsername)
|
||||
unique, err := models.IsUsernameUnique(ctx, tx, newUsername)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
@@ -89,7 +90,7 @@ func ChangeUsername(
|
||||
return
|
||||
}
|
||||
user := auth.CurrentModel(r.Context())
|
||||
err = user.ChangeUsername(tx, newUsername)
|
||||
err = user.ChangeUsername(ctx, tx, newUsername)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
@@ -111,8 +112,8 @@ func ChangeUsername(
|
||||
// Handles a request to change the users bio
|
||||
func ChangeBio(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -120,7 +121,7 @@ func ChangeBio(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
@@ -142,7 +143,7 @@ func ChangeBio(
|
||||
return
|
||||
}
|
||||
user := auth.CurrentModel(r.Context())
|
||||
err = user.ChangeBio(tx, newBio)
|
||||
err = user.ChangeBio(ctx, tx, newBio)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
@@ -178,8 +179,8 @@ func validateChangePassword(
|
||||
// Handles a request to change the users password
|
||||
func ChangePassword(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -187,7 +188,7 @@ func ChangePassword(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
@@ -206,7 +207,7 @@ func ChangePassword(
|
||||
return
|
||||
}
|
||||
user := auth.CurrentModel(r.Context())
|
||||
err = user.SetPassword(tx, newPass)
|
||||
err = user.SetPassword(ctx, tx, newPass)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
|
||||
@@ -2,35 +2,41 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"projectreshoot/internal/models"
|
||||
"projectreshoot/internal/view/component/form"
|
||||
"projectreshoot/internal/view/page"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"git.haelnorr.com/h/golib/cookies"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Validates the username matches a user in the database and the password
|
||||
// is correct. Returns the corresponding user
|
||||
func validateLogin(
|
||||
tx *sql.Tx,
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
r *http.Request,
|
||||
) (*models.User, error) {
|
||||
) (*models.UserBun, error) {
|
||||
formUsername := r.FormValue("username")
|
||||
formPassword := r.FormValue("password")
|
||||
user, err := models.GetUserFromUsername(tx, formUsername)
|
||||
user, err := models.GetUserByUsername(ctx, tx, formUsername)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "db.GetUserFromUsername")
|
||||
}
|
||||
|
||||
err = user.CheckPassword(tx, formPassword)
|
||||
if user == nil {
|
||||
return nil, errors.New("Username or password incorrect")
|
||||
}
|
||||
|
||||
err = user.CheckPassword(ctx, tx, formPassword)
|
||||
if err != nil {
|
||||
if !strings.Contains(err.Error(), "Username or password incorrect") {
|
||||
return nil, errors.Wrap(err, "user.CheckPassword")
|
||||
@@ -55,8 +61,8 @@ func checkRememberMe(r *http.Request) bool {
|
||||
// template for user feedback
|
||||
func LoginRequest(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -64,7 +70,7 @@ func LoginRequest(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
@@ -77,7 +83,7 @@ func LoginRequest(
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
user, err := validateLogin(tx, r)
|
||||
user, err := validateLogin(ctx, tx, r)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
if err.Error() != "Username or password incorrect" {
|
||||
|
||||
@@ -2,26 +2,27 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"net/http"
|
||||
"projectreshoot/internal/models"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Handle a logout request
|
||||
func Logout(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second)
|
||||
defer cancel()
|
||||
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
func Movie(
|
||||
server *hws.Server,
|
||||
config *config.Config,
|
||||
cfg *config.TMDBConfig,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -31,7 +31,7 @@ func Movie(
|
||||
}
|
||||
return
|
||||
}
|
||||
movie, err := tmdb.GetMovie(int32(movie_id), config.TMDBToken)
|
||||
movie, err := tmdb.GetMovie(int32(movie_id), cfg.Token)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
@@ -43,7 +43,7 @@ func Movie(
|
||||
}
|
||||
return
|
||||
}
|
||||
credits, err := tmdb.GetCredits(int32(movie_id), config.TMDBToken)
|
||||
credits, err := tmdb.GetCredits(int32(movie_id), cfg.Token)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
@@ -55,7 +55,7 @@ func Movie(
|
||||
}
|
||||
return
|
||||
}
|
||||
page.Movie(movie, credits, &config.TMDBConfig.Image).Render(r.Context(), w)
|
||||
page.Movie(movie, credits, &cfg.Config.Image).Render(r.Context(), w)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
)
|
||||
|
||||
func SearchMovies(
|
||||
config *config.Config,
|
||||
cfg *config.TMDBConfig,
|
||||
logger *hlog.Logger,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
@@ -22,12 +22,12 @@ func SearchMovies(
|
||||
w.WriteHeader(http.StatusOK)
|
||||
return
|
||||
}
|
||||
movies, err := tmdb.SearchMovies(config.TMDBToken, query, false, 1)
|
||||
movies, err := tmdb.SearchMovies(cfg.Token, query, false, 1)
|
||||
if err != nil {
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
search.MovieResults(movies, &config.TMDBConfig.Image).Render(r.Context(), w)
|
||||
search.MovieResults(movies, &cfg.Config.Image).Render(r.Context(), w)
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@@ -2,28 +2,30 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"projectreshoot/internal/models"
|
||||
"projectreshoot/internal/view/component/form"
|
||||
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
// Validate the provided password
|
||||
func validatePassword(
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
tx *sql.Tx,
|
||||
ctx context.Context,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
tx bun.Tx,
|
||||
r *http.Request,
|
||||
) error {
|
||||
r.ParseForm()
|
||||
password := r.FormValue("password")
|
||||
user := auth.CurrentModel(r.Context())
|
||||
err := user.CheckPassword(tx, password)
|
||||
err := user.CheckPassword(ctx, tx, password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "user.CheckPassword")
|
||||
}
|
||||
@@ -33,8 +35,8 @@ func validatePassword(
|
||||
// Handle request to reauthenticate (i.e. make token fresh again)
|
||||
func Reauthenticate(
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.User],
|
||||
conn *sql.DB,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -42,7 +44,7 @@ func Reauthenticate(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
@@ -55,7 +57,7 @@ func Reauthenticate(
|
||||
return
|
||||
}
|
||||
defer tx.Rollback()
|
||||
err = validatePassword(auth, tx, r)
|
||||
err = validatePassword(ctx, auth, tx, r)
|
||||
if err != nil {
|
||||
w.WriteHeader(445)
|
||||
form.ConfirmPassword("Incorrect password").Render(r.Context(), w)
|
||||
|
||||
@@ -2,30 +2,30 @@ package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"projectreshoot/internal/config"
|
||||
"projectreshoot/internal/models"
|
||||
"projectreshoot/internal/view/component/form"
|
||||
"projectreshoot/internal/view/page"
|
||||
|
||||
"git.haelnorr.com/h/golib/cookies"
|
||||
"git.haelnorr.com/h/golib/hlog"
|
||||
"git.haelnorr.com/h/golib/jwt"
|
||||
"git.haelnorr.com/h/golib/hws"
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func validateRegistration(
|
||||
tx *sql.Tx,
|
||||
ctx context.Context,
|
||||
tx bun.Tx,
|
||||
r *http.Request,
|
||||
) (*models.User, error) {
|
||||
) (*models.UserBun, error) {
|
||||
formUsername := r.FormValue("username")
|
||||
formPassword := r.FormValue("password")
|
||||
formConfirmPassword := r.FormValue("confirm-password")
|
||||
unique, err := models.CheckUsernameUnique(tx, formUsername)
|
||||
unique, err := models.IsUsernameUnique(ctx, tx, formUsername)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "models.CheckUsernameUnique")
|
||||
}
|
||||
@@ -38,7 +38,7 @@ func validateRegistration(
|
||||
if len(formPassword) > 72 {
|
||||
return nil, errors.New("Password exceeds maximum length of 72 bytes")
|
||||
}
|
||||
user, err := models.CreateNewUser(tx, formUsername, formPassword)
|
||||
user, err := models.CreateUser(ctx, tx, formUsername, formPassword)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "models.CreateNewUser")
|
||||
}
|
||||
@@ -47,10 +47,9 @@ func validateRegistration(
|
||||
}
|
||||
|
||||
func RegisterRequest(
|
||||
config *config.Config,
|
||||
logger *hlog.Logger,
|
||||
conn *sql.DB,
|
||||
tokenGen *jwt.TokenGenerator,
|
||||
server *hws.Server,
|
||||
auth *hwsauth.Authenticator[*models.UserBun, bun.Tx],
|
||||
db *bun.DB,
|
||||
) http.Handler {
|
||||
return http.HandlerFunc(
|
||||
func(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -58,21 +57,33 @@ func RegisterRequest(
|
||||
defer cancel()
|
||||
|
||||
// Start the transaction
|
||||
tx, err := conn.BeginTx(ctx, nil)
|
||||
tx, err := db.BeginTx(ctx, nil)
|
||||
if err != nil {
|
||||
logger.Warn().Err(err).Msg("Failed to set token cookies")
|
||||
w.WriteHeader(http.StatusServiceUnavailable)
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusServiceUnavailable,
|
||||
Message: "Failed to start transaction",
|
||||
Error: err,
|
||||
})
|
||||
if err != nil {
|
||||
server.ThrowFatal(w, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
r.ParseForm()
|
||||
user, err := validateRegistration(tx, r)
|
||||
user, err := validateRegistration(ctx, tx, r)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
if err.Error() != "Username is taken" &&
|
||||
err.Error() != "Passwords do not match" &&
|
||||
err.Error() != "Password exceeds maximum length of 72 bytes" {
|
||||
logger.Warn().Caller().Err(err).Msg("Registration request failed")
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Registration failed",
|
||||
Error: err,
|
||||
})
|
||||
if err != nil {
|
||||
server.ThrowFatal(w, err)
|
||||
}
|
||||
} else {
|
||||
form.RegisterForm(err.Error()).Render(r.Context(), w)
|
||||
}
|
||||
@@ -80,11 +91,17 @@ func RegisterRequest(
|
||||
}
|
||||
|
||||
rememberMe := checkRememberMe(r)
|
||||
err = jwt.SetTokenCookies(w, r, tokenGen, user.ID(), true, rememberMe, config.SSL)
|
||||
err = auth.Login(w, r, user, rememberMe)
|
||||
if err != nil {
|
||||
tx.Rollback()
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
logger.Warn().Caller().Err(err).Msg("Failed to set token cookies")
|
||||
err := server.ThrowError(w, r, hws.HWSError{
|
||||
StatusCode: http.StatusInternalServerError,
|
||||
Message: "Login failed",
|
||||
Error: err,
|
||||
})
|
||||
if err != nil {
|
||||
server.ThrowFatal(w, err)
|
||||
}
|
||||
return
|
||||
}
|
||||
tx.Commit()
|
||||
|
||||
@@ -47,7 +47,7 @@ func (user *User) CheckPassword(tx *sql.Tx, password string) error {
|
||||
}
|
||||
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "bcrypt.CompareHashAndPassword")
|
||||
return errors.Wrap(err, "Username or password incorrect")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
163
internal/models/user_bun.go
Normal file
163
internal/models/user_bun.go
Normal file
@@ -0,0 +1,163 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
"github.com/uptrace/bun"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type UserBun struct {
|
||||
bun.BaseModel `bun:"table:users,alias:u"`
|
||||
|
||||
ID int `bun:"id,pk,autoincrement"` // Integer ID (index primary key)
|
||||
Username string `bun:"username,unique"` // Username (unique)
|
||||
PasswordHash string `bun:"password_hash,nullzero"` // Bcrypt hashed password (not exported in JSON)
|
||||
CreatedAt int64 `bun:"created_at"` // Epoch timestamp when the user was added to the database
|
||||
Bio string `bun:"bio"` // Short byline set by the user
|
||||
}
|
||||
|
||||
func (user *UserBun) GetID() int {
|
||||
return user.ID
|
||||
}
|
||||
|
||||
// Uses bcrypt to set the users password_hash from the given password
|
||||
func (user *UserBun) SetPassword(ctx context.Context, tx bun.Tx, password string) error {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "bcrypt.GenerateFromPassword")
|
||||
}
|
||||
newPassword := string(hashedPassword)
|
||||
|
||||
_, err = tx.NewUpdate().
|
||||
Model(user).
|
||||
Set("password_hash = ?", newPassword).
|
||||
Where("id = ?", user.ID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "tx.Update")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Uses bcrypt to check if the given password matches the users password_hash
|
||||
func (user *UserBun) CheckPassword(ctx context.Context, tx bun.Tx, password string) error {
|
||||
var hashedPassword string
|
||||
err := tx.NewSelect().
|
||||
Table("users").
|
||||
Column("password_hash").
|
||||
Where("id = ?", user.ID).
|
||||
Limit(1).
|
||||
Scan(ctx, &hashedPassword)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "tx.Select")
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Username or password incorrect")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Change the user's username
|
||||
func (user *UserBun) ChangeUsername(ctx context.Context, tx bun.Tx, newUsername string) error {
|
||||
_, err := tx.NewUpdate().
|
||||
Model(user).
|
||||
Set("username = ?", newUsername).
|
||||
Where("id = ?", user.ID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "tx.Update")
|
||||
}
|
||||
user.Username = newUsername
|
||||
return nil
|
||||
}
|
||||
|
||||
// Change the user's bio
|
||||
func (user *UserBun) ChangeBio(ctx context.Context, tx bun.Tx, newBio string) error {
|
||||
_, err := tx.NewUpdate().
|
||||
Model(user).
|
||||
Set("bio = ?", newBio).
|
||||
Where("id = ?", user.ID).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "tx.Update")
|
||||
}
|
||||
user.Bio = newBio
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateUser creates a new user with the given username and password
|
||||
func CreateUser(ctx context.Context, tx bun.Tx, username, password string) (*UserBun, error) {
|
||||
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "bcrypt.GenerateFromPassword")
|
||||
}
|
||||
|
||||
user := &UserBun{
|
||||
Username: username,
|
||||
PasswordHash: string(hashedPassword),
|
||||
CreatedAt: 0, // You may want to set this to time.Now().Unix()
|
||||
Bio: "",
|
||||
}
|
||||
|
||||
_, err = tx.NewInsert().
|
||||
Model(user).
|
||||
Exec(ctx)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "tx.Insert")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUserByID queries the database for a user matching the given ID
|
||||
// Returns nil, nil if no user is found
|
||||
func GetUserByID(ctx context.Context, tx bun.Tx, id int) (*UserBun, error) {
|
||||
user := new(UserBun)
|
||||
err := tx.NewSelect().
|
||||
Model(user).
|
||||
Where("id = ?", id).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "tx.Select")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetUserByUsername queries the database for a user matching the given username
|
||||
// Returns nil, nil if no user is found
|
||||
func GetUserByUsername(ctx context.Context, tx bun.Tx, username string) (*UserBun, error) {
|
||||
user := new(UserBun)
|
||||
err := tx.NewSelect().
|
||||
Model(user).
|
||||
Where("username = ?", username).
|
||||
Limit(1).
|
||||
Scan(ctx)
|
||||
if err != nil {
|
||||
if err.Error() == "sql: no rows in result set" {
|
||||
return nil, nil
|
||||
}
|
||||
return nil, errors.Wrap(err, "tx.Select")
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// IsUsernameUnique checks if the given username is unique (not already taken)
|
||||
// Returns true if the username is available, false if it's taken
|
||||
func IsUsernameUnique(ctx context.Context, tx bun.Tx, username string) (bool, error) {
|
||||
count, err := tx.NewSelect().
|
||||
Model((*UserBun)(nil)).
|
||||
Where("username = ?", username).
|
||||
Count(ctx)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "tx.Count")
|
||||
}
|
||||
return count == 0, nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -31,7 +32,9 @@ func CreateNewUser(
|
||||
|
||||
// Fetches data from the users table using "WHERE column = 'value'"
|
||||
func fetchUserData(
|
||||
tx *sql.Tx,
|
||||
tx interface {
|
||||
Query(query string, args ...any) (*sql.Rows, error)
|
||||
},
|
||||
column string,
|
||||
value any,
|
||||
) (*sql.Rows, error) {
|
||||
@@ -87,7 +90,7 @@ func GetUserFromUsername(tx *sql.Tx, username string) (*User, error) {
|
||||
}
|
||||
|
||||
// Queries the database for a user matching the given ID.
|
||||
func GetUserFromID(tx *sql.Tx, id int) (*User, error) {
|
||||
func GetUserFromID(tx hwsauth.DBTransaction, id int) (*User, error) {
|
||||
rows, err := fetchUserData(tx, "id", id)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "fetchUserData")
|
||||
|
||||
@@ -6,4 +6,4 @@ import (
|
||||
"git.haelnorr.com/h/golib/hwsauth"
|
||||
)
|
||||
|
||||
var CurrentUser hwsauth.ContextLoader[*models.User]
|
||||
var CurrentUser hwsauth.ContextLoader[*models.UserBun]
|
||||
|
||||
@@ -273,9 +273,6 @@
|
||||
.ms-3 {
|
||||
margin-inline-start: calc(var(--spacing) * 3);
|
||||
}
|
||||
.mt-0 {
|
||||
margin-top: calc(var(--spacing) * 0);
|
||||
}
|
||||
.mt-0\.5 {
|
||||
margin-top: calc(var(--spacing) * 0.5);
|
||||
}
|
||||
@@ -449,21 +446,12 @@
|
||||
.flex-1 {
|
||||
flex: 1;
|
||||
}
|
||||
.flex-shrink {
|
||||
flex-shrink: 1;
|
||||
}
|
||||
.shrink-0 {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.flex-grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.grow {
|
||||
flex-grow: 1;
|
||||
}
|
||||
.border-collapse {
|
||||
border-collapse: collapse;
|
||||
}
|
||||
.translate-x-0 {
|
||||
--tw-translate-x: calc(var(--spacing) * 0);
|
||||
translate: var(--tw-translate-x) var(--tw-translate-y);
|
||||
@@ -478,9 +466,6 @@
|
||||
.cursor-pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
.resize {
|
||||
resize: both;
|
||||
}
|
||||
.flex-col {
|
||||
flex-direction: column;
|
||||
}
|
||||
@@ -852,10 +837,6 @@
|
||||
--tw-shadow-color: color-mix(in oklab, var(--color-black) var(--tw-shadow-alpha), transparent);
|
||||
}
|
||||
}
|
||||
.outline {
|
||||
outline-style: var(--tw-outline-style);
|
||||
outline-width: 1px;
|
||||
}
|
||||
.transition {
|
||||
transition-property: color, background-color, border-color, outline-color, text-decoration-color, fill, stroke, --tw-gradient-from, --tw-gradient-via, --tw-gradient-to, opacity, box-shadow, transform, translate, scale, rotate, filter, -webkit-backdrop-filter, backdrop-filter, display, content-visibility, overlay, pointer-events;
|
||||
transition-timing-function: var(--tw-ease, var(--default-transition-timing-function));
|
||||
@@ -1630,11 +1611,6 @@
|
||||
inherits: false;
|
||||
initial-value: 0 0 #0000;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@property --tw-duration {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
@@ -1663,6 +1639,11 @@
|
||||
inherits: false;
|
||||
initial-value: 1;
|
||||
}
|
||||
@property --tw-outline-style {
|
||||
syntax: "*";
|
||||
inherits: false;
|
||||
initial-value: solid;
|
||||
}
|
||||
@layer properties {
|
||||
@supports ((-webkit-hyphens: none) and (not (margin-trim: inline))) or ((-moz-orient: inline) and (not (color:rgb(from red r g b)))) {
|
||||
*, ::before, ::after, ::backdrop {
|
||||
@@ -1695,13 +1676,13 @@
|
||||
--tw-ring-offset-width: 0px;
|
||||
--tw-ring-offset-color: #fff;
|
||||
--tw-ring-offset-shadow: 0 0 #0000;
|
||||
--tw-outline-style: solid;
|
||||
--tw-duration: initial;
|
||||
--tw-ease: initial;
|
||||
--tw-content: "";
|
||||
--tw-scale-x: 1;
|
||||
--tw-scale-y: 1;
|
||||
--tw-scale-z: 1;
|
||||
--tw-outline-style: solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user