diff --git a/.air.toml b/.air.toml index d1066a7..921489b 100644 --- a/.air.toml +++ b/.air.toml @@ -5,7 +5,7 @@ tmp_dir = "tmp" [build] args_bin = [] bin = "./tmp/main" - cmd = "go build -o ./tmp/main ." + cmd = "go build -o ./tmp/main ./cmd/reshoot" delay = 1000 exclude_dir = [] exclude_file = [] diff --git a/.github/workflows/deploy_production.yaml b/.github/workflows/deploy_production.yaml index 058d43d..9166609 100644 --- a/.github/workflows/deploy_production.yaml +++ b/.github/workflows/deploy_production.yaml @@ -53,10 +53,10 @@ jobs: echo " UserKnownHostsFile /dev/null" >> ~/.ssh/config ssh -i ~/.ssh/id_ed25519 $USER@$HOST mkdir -p $DIR - scp -i ~/.ssh/id_ed25519 projectreshoot-production-${GITHUB_SHA} $USER@$HOST:$DIR + scp -i ~/.ssh/id_ed25519 ./bin/projectreshoot-production-${GITHUB_SHA} $USER@$HOST:$DIR ssh -i ~/.ssh/id_ed25519 $USER@$HOST mkdir -p $MIG_DIR - scp -i ~/.ssh/id_ed25519 prmigrate-production-${GITHUB_SHA} $USER@$HOST:$MIG_DIR + scp -i ~/.ssh/id_ed25519 .bin/migrate-production-${GITHUB_SHA} $USER@$HOST:$MIG_DIR scp -i ~/.ssh/id_ed25519 ./deploy/db/backup.sh $USER@$HOST:$MIG_DIR scp -i ~/.ssh/id_ed25519 ./deploy/db/migrate.sh $USER@$HOST:$MIG_DIR diff --git a/.github/workflows/deploy_staging.yaml b/.github/workflows/deploy_staging.yaml index 539097e..07230d6 100644 --- a/.github/workflows/deploy_staging.yaml +++ b/.github/workflows/deploy_staging.yaml @@ -53,10 +53,10 @@ jobs: echo " UserKnownHostsFile /dev/null" >> ~/.ssh/config ssh -i ~/.ssh/id_ed25519 $USER@$HOST mkdir -p $DIR - scp -i ~/.ssh/id_ed25519 projectreshoot-staging-${GITHUB_SHA} $USER@$HOST:$DIR + scp -i ~/.ssh/id_ed25519 ./bin/projectreshoot-staging-${GITHUB_SHA} $USER@$HOST:$DIR ssh -i ~/.ssh/id_ed25519 $USER@$HOST mkdir -p $MIG_DIR - scp -i ~/.ssh/id_ed25519 prmigrate-staging-${GITHUB_SHA} $USER@$HOST:$MIG_DIR + scp -i ~/.ssh/id_ed25519 ./bin/migrate-staging-${GITHUB_SHA} $USER@$HOST:$MIG_DIR scp -i ~/.ssh/id_ed25519 ./deploy/db/backup.sh $USER@$HOST:$MIG_DIR scp -i ~/.ssh/id_ed25519 ./deploy/db/migrate.sh $USER@$HOST:$MIG_DIR diff --git a/.gitignore b/.gitignore index 9e3f92b..9d8ebf9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ .env -query.sql -*.db +*.db* .logs/ server.log +bin/ tmp/ -prmigrate -projectreshoot static/css/output.css view/**/*_templ.go view/**/*_templ.txt diff --git a/Makefile b/Makefile index acabebf..78aa20a 100644 --- a/Makefile +++ b/Makefile @@ -5,16 +5,16 @@ BINARY_NAME=projectreshoot build: - tailwindcss -i ./static/css/input.css -o ./static/css/output.css && \ + tailwindcss -i ./pkg/embedfs/files/css/input.css -o ./pkg/embedfs/files/css/output.css && \ go mod tidy && \ templ generate && \ - go generate && \ - go build -ldflags="-w -s" -o ${BINARY_NAME}${SUFFIX} + go generate ./cmd/projectreshoot && \ + go build -ldflags="-w -s" -o ./bin/${BINARY_NAME}${SUFFIX} ./cmd/projectreshoot dev: templ generate --watch &\ air &\ - tailwindcss -i ./static/css/input.css -o ./static/css/output.css --watch + tailwindcss -i ./pkg/embedfs/files/css/input.css -o ./pkg/embedfs/files/css/output.css --watch tester: go mod tidy && \ @@ -23,15 +23,15 @@ tester: test: go mod tidy && \ templ generate && \ - go generate && \ - go test . - go test ./db - go test ./middleware + go generate ./cmd/projectreshoot && \ + go test ./cmd/projectreshoot + go test ./pkg/db + go test ./internal/middleware clean: go clean migrate: go mod tidy && \ - go generate && \ - go build -ldflags="-w -s" -o prmigrate${SUFFIX} ./migrate + go generate ./cmd/migrate && \ + go build -ldflags="-w -s" -o ./bin/migrate${SUFFIX} ./cmd/migrate diff --git a/migrate/migrate.go b/cmd/migrate/migrate.go similarity index 95% rename from migrate/migrate.go rename to cmd/migrate/migrate.go index dd8a99e..da64943 100644 --- a/migrate/migrate.go +++ b/cmd/migrate/migrate.go @@ -20,7 +20,7 @@ var migrationsFS embed.FS func main() { if len(os.Args) != 4 { - fmt.Println("Usage: prmigrate up-to|down-to ") + fmt.Println("Usage: migrate up-to|down-to ") os.Exit(1) } diff --git a/migrate/migrations/00001_init.sql b/cmd/migrate/migrations/00001_init.sql similarity index 100% rename from migrate/migrations/00001_init.sql rename to cmd/migrate/migrations/00001_init.sql diff --git a/cmd/projectreshoot/dbconn.go b/cmd/projectreshoot/dbconn.go new file mode 100644 index 0000000..650f8c1 --- /dev/null +++ b/cmd/projectreshoot/dbconn.go @@ -0,0 +1,37 @@ +package main + +import ( + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" + "projectreshoot/pkg/tests" + "strconv" + + "github.com/pkg/errors" + "github.com/rs/zerolog" +) + +func setupDBConn( + args map[string]string, + logger *zerolog.Logger, + config *config.Config, +) (*db.SafeConn, error) { + if args["test"] == "true" { + logger.Debug().Msg("Server in test mode, using test database") + ver, err := strconv.ParseInt(config.DBName, 10, 0) + if err != nil { + return nil, errors.Wrap(err, "strconv.ParseInt") + } + wconn, rconn, err := tests.SetupTestDB(ver) + if err != nil { + return nil, errors.Wrap(err, "tests.SetupTestDB") + } + conn := db.MakeSafe(wconn, rconn, logger) + return conn, nil + } else { + conn, err := db.ConnectToDatabase(config.DBName, logger) + if err != nil { + return nil, errors.Wrap(err, "db.ConnectToDatabase") + } + return conn, nil + } +} diff --git a/cmd/projectreshoot/flags.go b/cmd/projectreshoot/flags.go new file mode 100644 index 0000000..3501397 --- /dev/null +++ b/cmd/projectreshoot/flags.go @@ -0,0 +1,30 @@ +package main + +import ( + "flag" + "strconv" +) + +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") + tester := flag.Bool("tester", false, "Run tester function instead of main program") + 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)") + flag.Parse() + + // Map the args for easy access + args := map[string]string{ + "host": *host, + "port": *port, + "test": strconv.FormatBool(*test), + "tester": strconv.FormatBool(*tester), + "dbver": strconv.FormatBool(*dbver), + "loglevel": *loglevel, + "logoutput": *logoutput, + } + return args +} diff --git a/cmd/projectreshoot/main.go b/cmd/projectreshoot/main.go new file mode 100644 index 0000000..65f289b --- /dev/null +++ b/cmd/projectreshoot/main.go @@ -0,0 +1,16 @@ +package main + +import ( + "context" + "fmt" + "os" +) + +func main() { + args := setupFlags() + ctx := context.Background() + if err := run(ctx, os.Stdout, args); err != nil { + fmt.Fprintf(os.Stderr, "%s\n", err) + os.Exit(1) + } +} diff --git a/main_test.go b/cmd/projectreshoot/main_test.go similarity index 100% rename from main_test.go rename to cmd/projectreshoot/main_test.go diff --git a/cmd/projectreshoot/run.go b/cmd/projectreshoot/run.go new file mode 100644 index 0000000..c151eaf --- /dev/null +++ b/cmd/projectreshoot/run.go @@ -0,0 +1,119 @@ +package main + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "os/signal" + "projectreshoot/internal/httpserver" + "projectreshoot/pkg/config" + "projectreshoot/pkg/embedfs" + "projectreshoot/pkg/logging" + "sync" + "time" + + "github.com/pkg/errors" +) + +var maint uint32 // atomic: 1 if in maintenance mode + +// Initializes and runs the server +func run(ctx context.Context, w io.Writer, args map[string]string) error { + ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) + defer cancel() + + config, err := config.GetConfig(args) + if err != nil { + return errors.Wrap(err, "server.GetConfig") + } + + // Return the version of the database required + if args["dbver"] == "true" { + fmt.Fprintf(w, "Database version: %s\n", config.DBName) + return nil + } + + // Setup the logfile + var logfile *os.File = nil + if config.LogOutput == "both" || config.LogOutput == "file" { + logfile, err = logging.GetLogFile(config.LogDir) + if err != nil { + return errors.Wrap(err, "logging.GetLogFile") + } + defer logfile.Close() + } + + // Setup the console writer + var consoleWriter io.Writer + if config.LogOutput == "both" || config.LogOutput == "console" { + consoleWriter = w + } + + // Setup the logger + logger, err := logging.GetLogger( + config.LogLevel, + consoleWriter, + logfile, + config.LogDir, + ) + if err != nil { + return errors.Wrap(err, "logging.GetLogger") + } + + // Setup the database connection + logger.Debug().Msg("Config loaded and logger started") + logger.Debug().Msg("Connecting to database") + conn, err := setupDBConn(args, logger, config) + if err != nil { + return errors.Wrap(err, "setupDBConn") + } + defer conn.Close() + + // Setup embedded files + logger.Debug().Msg("Getting embedded files") + staticFS, err := embedfs.GetEmbeddedFS() + if err != nil { + return errors.Wrap(err, "getStaticFiles") + } + + logger.Debug().Msg("Setting up HTTP server") + httpServer := httpserver.NewServer(config, logger, conn, &staticFS, &maint) + + // Runs function for testing in dev if --tester flag true + if args["tester"] == "true" { + logger.Debug().Msg("Running tester function") + test(config, logger, conn, httpServer) + return nil + } + + // Setups a channel to listen for os.Signal + handleMaintSignals(conn, config, httpServer, logger) + + // Runs the http server + logger.Debug().Msg("Starting up the HTTP server") + go func() { + logger.Info().Str("address", httpServer.Addr).Msg("Listening for requests") + if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Error().Err(err).Msg("Error listening and serving") + } + }() + + // Handles graceful shutdown + var wg sync.WaitGroup + wg.Add(1) + go func() { + defer wg.Done() + <-ctx.Done() + shutdownCtx := context.Background() + shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) + defer cancel() + if err := httpServer.Shutdown(shutdownCtx); err != nil { + logger.Error().Err(err).Msg("Error shutting down server") + } + }() + wg.Wait() + logger.Info().Msg("Shutting down") + return nil +} diff --git a/cmd/projectreshoot/signals.go b/cmd/projectreshoot/signals.go new file mode 100644 index 0000000..e608cc3 --- /dev/null +++ b/cmd/projectreshoot/signals.go @@ -0,0 +1,50 @@ +package main + +import ( + "net/http" + "os" + "os/signal" + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" + "sync/atomic" + "syscall" + "time" + + "github.com/rs/zerolog" +) + +// Handle SIGUSR1 and SIGUSR2 syscalls to toggle maintenance mode +func handleMaintSignals( + conn *db.SafeConn, + config *config.Config, + srv *http.Server, + logger *zerolog.Logger, +) { + logger.Debug().Msg("Starting signal listener") + ch := make(chan os.Signal, 1) + srv.RegisterOnShutdown(func() { + logger.Debug().Msg("Shutting down signal listener") + close(ch) + }) + go func() { + for sig := range ch { + switch sig { + case syscall.SIGUSR1: + if atomic.LoadUint32(&maint) != 1 { + atomic.StoreUint32(&maint, 1) + logger.Info().Msg("Signal received: Starting maintenance") + logger.Info().Msg("Attempting to acquire database lock") + conn.Pause(config.DBLockTimeout * time.Second) + } + case syscall.SIGUSR2: + if atomic.LoadUint32(&maint) != 0 { + logger.Info().Msg("Signal received: Maintenance over") + logger.Info().Msg("Releasing database lock") + conn.Resume() + atomic.StoreUint32(&maint, 0) + } + } + } + }() + signal.Notify(ch, syscall.SIGUSR1, syscall.SIGUSR2) +} diff --git a/cmd/projectreshoot/tester.go b/cmd/projectreshoot/tester.go new file mode 100644 index 0000000..3932bf7 --- /dev/null +++ b/cmd/projectreshoot/tester.go @@ -0,0 +1,32 @@ +package main + +import ( + "net/http" + + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" + "projectreshoot/pkg/tmdb" + + "github.com/rs/zerolog" +) + +// This function will only be called if the --test commandline flag is set. +// After the function finishes the application will close. +// Running command `make tester` will run the test using port 3232 to avoid +// conflicts on the default 3333. Useful for testing things out during dev. +// If you add code here, remember to run: +// `git update-index --assume-unchanged tester.go` to avoid tracking changes +func test( + config *config.Config, + logger *zerolog.Logger, + conn *db.SafeConn, + srv *http.Server, +) { + query := "a few good men" + search, err := tmdb.SearchMovies(config.TMDBToken, query, false, 1) + if err != nil { + logger.Error().Err(err).Msg("error occured") + return + } + logger.Info().Interface("results", search).Msg("search results received") +} diff --git a/db/safetx.go b/db/safetx.go deleted file mode 100644 index 5a3f06e..0000000 --- a/db/safetx.go +++ /dev/null @@ -1,61 +0,0 @@ -package db - -import ( - "context" - "database/sql" - - "github.com/pkg/errors" -) - -// Extends sql.Tx for use with SafeConn -type SafeTX struct { - tx *sql.Tx - sc *SafeConn -} - -// Query the database inside the transaction -func (stx *SafeTX) Query( - ctx context.Context, - query string, - args ...interface{}, -) (*sql.Rows, error) { - if stx.tx == nil { - return nil, errors.New("Cannot query without a transaction") - } - return stx.tx.QueryContext(ctx, query, args...) -} - -// Exec a statement on the database inside the transaction -func (stx *SafeTX) Exec( - ctx context.Context, - query string, - args ...interface{}, -) (sql.Result, error) { - if stx.tx == nil { - return nil, errors.New("Cannot exec without a transaction") - } - return stx.tx.ExecContext(ctx, query, args...) -} - -// Commit the current transaction and release the read lock -func (stx *SafeTX) Commit() error { - if stx.tx == nil { - return errors.New("Cannot commit without a transaction") - } - err := stx.tx.Commit() - stx.tx = nil - - stx.sc.releaseReadLock() - return err -} - -// Abort the current transaction, releasing the read lock -func (stx *SafeTX) Rollback() error { - if stx.tx == nil { - return errors.New("Cannot rollback without a transaction") - } - err := stx.tx.Rollback() - stx.tx = nil - stx.sc.releaseReadLock() - return err -} diff --git a/deploy/db/migrate.sh b/deploy/db/migrate.sh index dd2f03b..904bcfc 100755 --- a/deploy/db/migrate.sh +++ b/deploy/db/migrate.sh @@ -64,7 +64,7 @@ failed_cleanup() { trap 'if [ $? -ne 0 ]; then failed_cleanup; fi' EXIT echo "Migration in progress from $CUR_VER to $TGT_VER" -${MIGRATION_BIN}/prmigrate-${ENVR}-${COMMIT_HASH} $UPDATED_BACKUP $CMD $TGT_VER +${MIGRATION_BIN}/migrate-${ENVR}-${COMMIT_HASH} $UPDATED_BACKUP $CMD $TGT_VER if [ $? -ne 0 ]; then echo "Migration failed" exit 1 diff --git a/go.mod b/go.mod index f15482c..6e65741 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/golang-jwt/jwt v3.2.2+incompatible github.com/google/uuid v1.6.0 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 github.com/rs/zerolog v1.33.0 diff --git a/go.sum b/go.sum index b4bb05c..4e74dcf 100644 --- a/go.sum +++ b/go.sum @@ -28,6 +28,8 @@ 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= diff --git a/handler/account.go b/internal/handler/account.go similarity index 93% rename from handler/account.go rename to internal/handler/account.go index 5236e1e..fcb97e8 100644 --- a/handler/account.go +++ b/internal/handler/account.go @@ -5,11 +5,12 @@ import ( "net/http" "time" - "projectreshoot/contexts" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/view/component/account" - "projectreshoot/view/page" + "projectreshoot/internal/models" + "projectreshoot/internal/view/component/account" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/contexts" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -60,7 +61,7 @@ func ChangeUsername( } r.ParseForm() newUsername := r.FormValue("username") - unique, err := db.CheckUsernameUnique(ctx, tx, newUsername) + unique, err := models.CheckUsernameUnique(ctx, tx, newUsername) if err != nil { tx.Rollback() logger.Error().Err(err).Msg("Error updating username") @@ -127,8 +128,6 @@ func ChangeBio( ) } func validateChangePassword( - ctx context.Context, - tx *db.SafeTX, r *http.Request, ) (string, error) { r.ParseForm() @@ -160,7 +159,7 @@ func ChangePassword( w.WriteHeader(http.StatusServiceUnavailable) return } - newPass, err := validateChangePassword(ctx, tx, r) + newPass, err := validateChangePassword(r) if err != nil { tx.Rollback() account.ChangePassword(err.Error()).Render(r.Context(), w) diff --git a/handler/errorpage.go b/internal/handler/errorpage.go similarity index 94% rename from handler/errorpage.go rename to internal/handler/errorpage.go index 67fe951..4632cf9 100644 --- a/handler/errorpage.go +++ b/internal/handler/errorpage.go @@ -2,7 +2,7 @@ package handler import ( "net/http" - "projectreshoot/view/page" + "projectreshoot/internal/view/page" ) func ErrorPage( diff --git a/handler/index.go b/internal/handler/index.go similarity index 91% rename from handler/index.go rename to internal/handler/index.go index f7e09bf..a378b3c 100644 --- a/handler/index.go +++ b/internal/handler/index.go @@ -3,7 +3,7 @@ package handler import ( "net/http" - "projectreshoot/view/page" + "projectreshoot/internal/view/page" ) // Handles responses to the / path. Also serves a 404 Page for paths that diff --git a/handler/login.go b/internal/handler/login.go similarity index 89% rename from handler/login.go rename to internal/handler/login.go index f366447..8157db3 100644 --- a/handler/login.go +++ b/internal/handler/login.go @@ -5,11 +5,12 @@ import ( "net/http" "time" - "projectreshoot/config" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/view/component/form" - "projectreshoot/view/page" + "projectreshoot/internal/models" + "projectreshoot/internal/view/component/form" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/config" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -19,12 +20,12 @@ import ( // is correct. Returns the corresponding user func validateLogin( ctx context.Context, - tx *db.SafeTX, + tx db.SafeTX, r *http.Request, -) (*db.User, error) { +) (*models.User, error) { formUsername := r.FormValue("username") formPassword := r.FormValue("password") - user, err := db.GetUserFromUsername(ctx, tx, formUsername) + user, err := models.GetUserFromUsername(ctx, tx, formUsername) if err != nil { return nil, errors.Wrap(err, "db.GetUserFromUsername") } diff --git a/handler/logout.go b/internal/handler/logout.go similarity index 94% rename from handler/logout.go rename to internal/handler/logout.go index 7a14572..715c81f 100644 --- a/handler/logout.go +++ b/internal/handler/logout.go @@ -6,10 +6,10 @@ import ( "strings" "time" - "projectreshoot/config" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/jwt" + "projectreshoot/pkg/config" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" + "projectreshoot/pkg/jwt" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -18,7 +18,7 @@ import ( func revokeAccess( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, atStr string, ) error { aT, err := jwt.ParseAccessToken(config, ctx, tx, atStr) @@ -39,7 +39,7 @@ func revokeAccess( func revokeRefresh( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, rtStr string, ) error { rT, err := jwt.ParseRefreshToken(config, ctx, tx, rtStr) @@ -61,7 +61,7 @@ func revokeRefresh( func revokeTokens( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, r *http.Request, ) error { // get the tokens from the cookies diff --git a/handler/movie.go b/internal/handler/movie.go similarity index 92% rename from handler/movie.go rename to internal/handler/movie.go index b312d39..c4b286e 100644 --- a/handler/movie.go +++ b/internal/handler/movie.go @@ -2,9 +2,9 @@ package handler import ( "net/http" - "projectreshoot/config" - "projectreshoot/tmdb" - "projectreshoot/view/page" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/config" + "projectreshoot/pkg/tmdb" "strconv" "github.com/rs/zerolog" diff --git a/handler/movie_search.go b/internal/handler/movie_search.go similarity index 84% rename from handler/movie_search.go rename to internal/handler/movie_search.go index bb19b11..aaa425a 100644 --- a/handler/movie_search.go +++ b/internal/handler/movie_search.go @@ -2,10 +2,10 @@ package handler import ( "net/http" - "projectreshoot/config" - "projectreshoot/tmdb" - "projectreshoot/view/component/search" - "projectreshoot/view/page" + "projectreshoot/internal/view/component/search" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/config" + "projectreshoot/pkg/tmdb" "github.com/rs/zerolog" ) diff --git a/handler/page.go b/internal/handler/page.go similarity index 100% rename from handler/page.go rename to internal/handler/page.go diff --git a/handler/profile.go b/internal/handler/profile.go similarity index 84% rename from handler/profile.go rename to internal/handler/profile.go index 51763ea..ad85283 100644 --- a/handler/profile.go +++ b/internal/handler/profile.go @@ -2,7 +2,7 @@ package handler import ( "net/http" - "projectreshoot/view/page" + "projectreshoot/internal/view/page" ) func ProfilePage() http.Handler { diff --git a/handler/reauthenticatate.go b/internal/handler/reauthenticatate.go similarity index 92% rename from handler/reauthenticatate.go rename to internal/handler/reauthenticatate.go index 6bf7317..b3e8189 100644 --- a/handler/reauthenticatate.go +++ b/internal/handler/reauthenticatate.go @@ -5,12 +5,12 @@ import ( "net/http" "time" - "projectreshoot/config" - "projectreshoot/contexts" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/jwt" - "projectreshoot/view/component/form" + "projectreshoot/internal/view/component/form" + "projectreshoot/pkg/config" + "projectreshoot/pkg/contexts" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" + "projectreshoot/pkg/jwt" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -20,7 +20,7 @@ import ( func getTokens( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx db.SafeTX, r *http.Request, ) (*jwt.AccessToken, *jwt.RefreshToken, error) { // get the existing tokens from the cookies @@ -39,7 +39,7 @@ func getTokens( // Revoke the given token pair func revokeTokenPair( ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, aT *jwt.AccessToken, rT *jwt.RefreshToken, ) error { @@ -58,7 +58,7 @@ func revokeTokenPair( func refreshTokens( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, w http.ResponseWriter, r *http.Request, ) error { diff --git a/handler/register.go b/internal/handler/register.go similarity index 86% rename from handler/register.go rename to internal/handler/register.go index 2599ef5..226b3b7 100644 --- a/handler/register.go +++ b/internal/handler/register.go @@ -5,11 +5,12 @@ import ( "net/http" "time" - "projectreshoot/config" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/view/component/form" - "projectreshoot/view/page" + "projectreshoot/internal/models" + "projectreshoot/internal/view/component/form" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/config" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -17,13 +18,13 @@ import ( func validateRegistration( ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, r *http.Request, -) (*db.User, error) { +) (*models.User, error) { formUsername := r.FormValue("username") formPassword := r.FormValue("password") formConfirmPassword := r.FormValue("confirm-password") - unique, err := db.CheckUsernameUnique(ctx, tx, formUsername) + unique, err := models.CheckUsernameUnique(ctx, tx, formUsername) if err != nil { return nil, errors.Wrap(err, "db.CheckUsernameUnique") } @@ -36,7 +37,7 @@ func validateRegistration( if len(formPassword) > 72 { return nil, errors.New("Password exceeds maximum length of 72 bytes") } - user, err := db.CreateNewUser(ctx, tx, formUsername, formPassword) + user, err := models.CreateNewUser(ctx, tx, formUsername, formPassword) if err != nil { return nil, errors.Wrap(err, "db.CreateNewUser") } diff --git a/handler/static.go b/internal/handler/static.go similarity index 100% rename from handler/static.go rename to internal/handler/static.go diff --git a/server/routes.go b/internal/httpserver/routes.go similarity index 91% rename from server/routes.go rename to internal/httpserver/routes.go index e99fd46..e31bc7a 100644 --- a/server/routes.go +++ b/internal/httpserver/routes.go @@ -1,13 +1,13 @@ -package server +package httpserver import ( "net/http" - "projectreshoot/config" - "projectreshoot/db" - "projectreshoot/handler" - "projectreshoot/middleware" - "projectreshoot/view/page" + "projectreshoot/internal/handler" + "projectreshoot/internal/middleware" + "projectreshoot/internal/view/page" + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" "github.com/rs/zerolog" ) diff --git a/server/server.go b/internal/httpserver/server.go similarity index 55% rename from server/server.go rename to internal/httpserver/server.go index 8f189ad..f634505 100644 --- a/server/server.go +++ b/internal/httpserver/server.go @@ -1,17 +1,39 @@ -package server +package httpserver import ( + "io/fs" + "net" "net/http" + "time" - "projectreshoot/config" - "projectreshoot/db" - "projectreshoot/middleware" + "projectreshoot/internal/middleware" + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" "github.com/rs/zerolog" ) -// Returns a new http.Handler with all the routes and middleware added func NewServer( + config *config.Config, + logger *zerolog.Logger, + conn *db.SafeConn, + staticFS *fs.FS, + maint *uint32, +) *http.Server { + fs := http.FS(*staticFS) + srv := createServer(config, logger, conn, &fs, maint) + httpServer := &http.Server{ + Addr: net.JoinHostPort(config.Host, config.Port), + Handler: srv, + ReadHeaderTimeout: config.ReadHeaderTimeout * time.Second, + WriteTimeout: config.WriteTimeout * time.Second, + IdleTimeout: config.IdleTimeout * time.Second, + } + return httpServer +} + +// Returns a new http.Handler with all the routes and middleware added +func createServer( config *config.Config, logger *zerolog.Logger, conn *db.SafeConn, diff --git a/middleware/authentication.go b/internal/middleware/authentication.go similarity index 92% rename from middleware/authentication.go rename to internal/middleware/authentication.go index 6c192ea..4393eff 100644 --- a/middleware/authentication.go +++ b/internal/middleware/authentication.go @@ -6,12 +6,13 @@ import ( "sync/atomic" "time" - "projectreshoot/config" - "projectreshoot/contexts" - "projectreshoot/cookies" - "projectreshoot/db" - "projectreshoot/handler" - "projectreshoot/jwt" + "projectreshoot/internal/handler" + "projectreshoot/internal/models" + "projectreshoot/pkg/config" + "projectreshoot/pkg/contexts" + "projectreshoot/pkg/cookies" + "projectreshoot/pkg/db" + "projectreshoot/pkg/jwt" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -21,11 +22,11 @@ import ( func refreshAuthTokens( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, w http.ResponseWriter, req *http.Request, ref *jwt.RefreshToken, -) (*db.User, error) { +) (*models.User, error) { user, err := ref.GetUser(ctx, tx) if err != nil { return nil, errors.Wrap(err, "ref.GetUser") @@ -54,7 +55,7 @@ func refreshAuthTokens( func getAuthenticatedUser( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx *db.SafeWTX, w http.ResponseWriter, r *http.Request, ) (*contexts.AuthenticatedUser, error) { diff --git a/middleware/authentication_test.go b/internal/middleware/authentication_test.go similarity index 97% rename from middleware/authentication_test.go rename to internal/middleware/authentication_test.go index bb3dceb..df68ad0 100644 --- a/middleware/authentication_test.go +++ b/internal/middleware/authentication_test.go @@ -8,9 +8,9 @@ import ( "sync/atomic" "testing" - "projectreshoot/contexts" - "projectreshoot/db" - "projectreshoot/tests" + "projectreshoot/pkg/contexts" + "projectreshoot/pkg/db" + "projectreshoot/pkg/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -22,9 +22,9 @@ func TestAuthenticationMiddleware(t *testing.T) { logger := tests.NilLogger() ver, err := strconv.ParseInt(cfg.DBName, 10, 0) require.NoError(t, err) - conn, err := tests.SetupTestDB(ver) + wconn, rconn, err := tests.SetupTestDB(ver) require.NoError(t, err) - sconn := db.MakeSafe(conn, logger) + sconn := db.MakeSafe(wconn, rconn, logger) defer sconn.Close() // Handler to check outcome of Authentication middleware diff --git a/middleware/gzip.go b/internal/middleware/gzip.go similarity index 100% rename from middleware/gzip.go rename to internal/middleware/gzip.go diff --git a/middleware/logging.go b/internal/middleware/logging.go similarity index 94% rename from middleware/logging.go rename to internal/middleware/logging.go index ae6616b..e890152 100644 --- a/middleware/logging.go +++ b/internal/middleware/logging.go @@ -2,8 +2,8 @@ package middleware import ( "net/http" - "projectreshoot/contexts" - "projectreshoot/handler" + "projectreshoot/internal/handler" + "projectreshoot/pkg/contexts" "time" "github.com/rs/zerolog" diff --git a/middleware/pageprotection.go b/internal/middleware/pageprotection.go similarity index 92% rename from middleware/pageprotection.go rename to internal/middleware/pageprotection.go index 7f104c0..c2442e5 100644 --- a/middleware/pageprotection.go +++ b/internal/middleware/pageprotection.go @@ -2,8 +2,8 @@ package middleware import ( "net/http" - "projectreshoot/contexts" - "projectreshoot/handler" + "projectreshoot/internal/handler" + "projectreshoot/pkg/contexts" ) // Checks if the user is set in the context and shows 401 page if not logged in diff --git a/middleware/pageprotection_test.go b/internal/middleware/pageprotection_test.go similarity index 93% rename from middleware/pageprotection_test.go rename to internal/middleware/pageprotection_test.go index 93414f8..8cbc81b 100644 --- a/middleware/pageprotection_test.go +++ b/internal/middleware/pageprotection_test.go @@ -7,8 +7,8 @@ import ( "sync/atomic" "testing" - "projectreshoot/db" - "projectreshoot/tests" + "projectreshoot/pkg/db" + "projectreshoot/pkg/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,9 +20,9 @@ func TestPageLoginRequired(t *testing.T) { logger := tests.NilLogger() ver, err := strconv.ParseInt(cfg.DBName, 10, 0) require.NoError(t, err) - conn, err := tests.SetupTestDB(ver) + wconn, rconn, err := tests.SetupTestDB(ver) require.NoError(t, err) - sconn := db.MakeSafe(conn, logger) + sconn := db.MakeSafe(wconn, rconn, logger) defer sconn.Close() // Handler to check outcome of Authentication middleware diff --git a/middleware/reauthentication.go b/internal/middleware/reauthentication.go similarity index 91% rename from middleware/reauthentication.go rename to internal/middleware/reauthentication.go index b1fdb93..a5dcf00 100644 --- a/middleware/reauthentication.go +++ b/internal/middleware/reauthentication.go @@ -2,7 +2,7 @@ package middleware import ( "net/http" - "projectreshoot/contexts" + "projectreshoot/pkg/contexts" "time" ) diff --git a/middleware/reauthentication_test.go b/internal/middleware/reauthentication_test.go similarity index 93% rename from middleware/reauthentication_test.go rename to internal/middleware/reauthentication_test.go index 646a557..de407e0 100644 --- a/middleware/reauthentication_test.go +++ b/internal/middleware/reauthentication_test.go @@ -7,8 +7,8 @@ import ( "sync/atomic" "testing" - "projectreshoot/db" - "projectreshoot/tests" + "projectreshoot/pkg/db" + "projectreshoot/pkg/tests" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,9 +20,9 @@ func TestReauthRequired(t *testing.T) { logger := tests.NilLogger() ver, err := strconv.ParseInt(cfg.DBName, 10, 0) require.NoError(t, err) - conn, err := tests.SetupTestDB(ver) + wconn, rconn, err := tests.SetupTestDB(ver) require.NoError(t, err) - sconn := db.MakeSafe(conn, logger) + sconn := db.MakeSafe(wconn, rconn, logger) defer sconn.Close() // Handler to check outcome of Authentication middleware diff --git a/middleware/start.go b/internal/middleware/start.go similarity index 91% rename from middleware/start.go rename to internal/middleware/start.go index fb9a66c..f0235b1 100644 --- a/middleware/start.go +++ b/internal/middleware/start.go @@ -2,7 +2,7 @@ package middleware import ( "net/http" - "projectreshoot/contexts" + "projectreshoot/pkg/contexts" "time" ) diff --git a/db/user.go b/internal/models/user.go similarity index 83% rename from db/user.go rename to internal/models/user.go index a2daa26..47a2341 100644 --- a/db/user.go +++ b/internal/models/user.go @@ -1,7 +1,8 @@ -package db +package models import ( "context" + "projectreshoot/pkg/db" "github.com/pkg/errors" "golang.org/x/crypto/bcrypt" @@ -16,7 +17,7 @@ type User struct { } // Uses bcrypt to set the users Password_hash from the given password -func (user *User) SetPassword(ctx context.Context, tx *SafeTX, password string) error { +func (user *User) SetPassword(ctx context.Context, tx *db.SafeWTX, password string) error { hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) if err != nil { return errors.Wrap(err, "bcrypt.GenerateFromPassword") @@ -40,7 +41,7 @@ func (user *User) CheckPassword(password string) error { } // Change the user's username -func (user *User) ChangeUsername(ctx context.Context, tx *SafeTX, newUsername string) error { +func (user *User) ChangeUsername(ctx context.Context, tx *db.SafeWTX, newUsername string) error { query := `UPDATE users SET username = ? WHERE id = ?` _, err := tx.Exec(ctx, query, newUsername, user.ID) if err != nil { @@ -50,7 +51,7 @@ func (user *User) ChangeUsername(ctx context.Context, tx *SafeTX, newUsername st } // Change the user's bio -func (user *User) ChangeBio(ctx context.Context, tx *SafeTX, newBio string) error { +func (user *User) ChangeBio(ctx context.Context, tx *db.SafeWTX, newBio string) error { query := `UPDATE users SET bio = ? WHERE id = ?` _, err := tx.Exec(ctx, query, newBio, user.ID) if err != nil { diff --git a/db/user_functions.go b/internal/models/user_functions.go similarity index 88% rename from db/user_functions.go rename to internal/models/user_functions.go index 6dd76d4..2bfea8a 100644 --- a/db/user_functions.go +++ b/internal/models/user_functions.go @@ -1,9 +1,10 @@ -package db +package models import ( "context" "database/sql" "fmt" + "projectreshoot/pkg/db" "github.com/pkg/errors" ) @@ -11,7 +12,7 @@ import ( // Creates a new user in the database and returns a pointer func CreateNewUser( ctx context.Context, - tx *SafeTX, + tx *db.SafeWTX, username string, password string, ) (*User, error) { @@ -34,7 +35,7 @@ func CreateNewUser( // Fetches data from the users table using "WHERE column = 'value'" func fetchUserData( ctx context.Context, - tx *SafeTX, + tx db.SafeTX, column string, value interface{}, ) (*sql.Rows, error) { @@ -77,7 +78,7 @@ func scanUserRow(user *User, rows *sql.Rows) error { // Queries the database for a user matching the given username. // Query is case insensitive -func GetUserFromUsername(ctx context.Context, tx *SafeTX, username string) (*User, error) { +func GetUserFromUsername(ctx context.Context, tx db.SafeTX, username string) (*User, error) { rows, err := fetchUserData(ctx, tx, "username", username) if err != nil { return nil, errors.Wrap(err, "fetchUserData") @@ -92,7 +93,7 @@ func GetUserFromUsername(ctx context.Context, tx *SafeTX, username string) (*Use } // Queries the database for a user matching the given ID. -func GetUserFromID(ctx context.Context, tx *SafeTX, id int) (*User, error) { +func GetUserFromID(ctx context.Context, tx db.SafeTX, id int) (*User, error) { rows, err := fetchUserData(ctx, tx, "id", id) if err != nil { return nil, errors.Wrap(err, "fetchUserData") @@ -107,7 +108,7 @@ func GetUserFromID(ctx context.Context, tx *SafeTX, id int) (*User, error) { } // Checks if the given username is unique. Returns true if not taken -func CheckUsernameUnique(ctx context.Context, tx *SafeTX, username string) (bool, error) { +func CheckUsernameUnique(ctx context.Context, tx db.SafeTX, username string) (bool, error) { query := `SELECT 1 FROM users WHERE username = ? COLLATE NOCASE LIMIT 1` rows, err := tx.Query(ctx, query, username) if err != nil { diff --git a/view/component/account/changebio.templ b/internal/view/component/account/changebio.templ similarity index 98% rename from view/component/account/changebio.templ rename to internal/view/component/account/changebio.templ index cb85013..68e42d8 100644 --- a/view/component/account/changebio.templ +++ b/internal/view/component/account/changebio.templ @@ -1,6 +1,6 @@ package account -import "projectreshoot/contexts" +import "projectreshoot/pkg/contexts" templ ChangeBio(err string, bio string) { {{ diff --git a/internal/view/component/account/changebio_templ.go b/internal/view/component/account/changebio_templ.go new file mode 100644 index 0000000..935782e --- /dev/null +++ b/internal/view/component/account/changebio_templ.go @@ -0,0 +1,60 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/contexts" + +func ChangeBio(err string, bio string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + user := contexts.GetUser(ctx) + if bio == "" { + bio = user.Bio + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/changepassword.templ b/internal/view/component/account/changepassword.templ similarity index 100% rename from view/component/account/changepassword.templ rename to internal/view/component/account/changepassword.templ diff --git a/internal/view/component/account/changepassword_templ.go b/internal/view/component/account/changepassword_templ.go new file mode 100644 index 0000000..4808626 --- /dev/null +++ b/internal/view/component/account/changepassword_templ.go @@ -0,0 +1,55 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func ChangePassword(err string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/changeusername.templ b/internal/view/component/account/changeusername.templ similarity index 98% rename from view/component/account/changeusername.templ rename to internal/view/component/account/changeusername.templ index 25cc151..6c2895e 100644 --- a/view/component/account/changeusername.templ +++ b/internal/view/component/account/changeusername.templ @@ -1,6 +1,6 @@ package account -import "projectreshoot/contexts" +import "projectreshoot/pkg/contexts" templ ChangeUsername(err string, username string) { {{ diff --git a/internal/view/component/account/changeusername_templ.go b/internal/view/component/account/changeusername_templ.go new file mode 100644 index 0000000..22f647b --- /dev/null +++ b/internal/view/component/account/changeusername_templ.go @@ -0,0 +1,62 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/contexts" + +func ChangeUsername(err string, username string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + user := contexts.GetUser(ctx) + if username == "" { + username = user.Username + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/container.templ b/internal/view/component/account/container.templ similarity index 100% rename from view/component/account/container.templ rename to internal/view/component/account/container.templ diff --git a/internal/view/component/account/container_templ.go b/internal/view/component/account/container_templ.go new file mode 100644 index 0000000..5cb89da --- /dev/null +++ b/internal/view/component/account/container_templ.go @@ -0,0 +1,77 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func AccountContainer(subpage string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = SelectMenu(subpage).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(subpage) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/account/container.templ`, Line: 16, Col: 13} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + switch subpage { + case "General": + templ_7745c5c3_Err = AccountGeneral().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + case "Security": + templ_7745c5c3_Err = AccountSecurity().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/general.templ b/internal/view/component/account/general.templ similarity index 100% rename from view/component/account/general.templ rename to internal/view/component/account/general.templ diff --git a/internal/view/component/account/general_templ.go b/internal/view/component/account/general_templ.go new file mode 100644 index 0000000..39d9a7a --- /dev/null +++ b/internal/view/component/account/general_templ.go @@ -0,0 +1,52 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func AccountGeneral() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ChangeUsername("", "").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ChangeBio("", "").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/security.templ b/internal/view/component/account/security.templ similarity index 100% rename from view/component/account/security.templ rename to internal/view/component/account/security.templ diff --git a/internal/view/component/account/security_templ.go b/internal/view/component/account/security_templ.go new file mode 100644 index 0000000..a0b15e5 --- /dev/null +++ b/internal/view/component/account/security_templ.go @@ -0,0 +1,48 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func AccountSecurity() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = ChangePassword("").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/account/selectmenu.templ b/internal/view/component/account/selectmenu.templ similarity index 100% rename from view/component/account/selectmenu.templ rename to internal/view/component/account/selectmenu.templ diff --git a/internal/view/component/account/selectmenu_templ.go b/internal/view/component/account/selectmenu_templ.go new file mode 100644 index 0000000..86021b5 --- /dev/null +++ b/internal/view/component/account/selectmenu_templ.go @@ -0,0 +1,131 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package account + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "fmt" + +type MenuItem struct { + name string + href string +} + +func getMenuItems() []MenuItem { + return []MenuItem{ + { + name: "General", + href: "general", + }, + { + name: "Security", + href: "security", + }, + { + name: "Preferences", + href: "preferences", + }, + } +} + +func SelectMenu(activePage string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + menuItems := getMenuItems() + page := fmt.Sprintf("{page:'%s'}", activePage) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
    ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, item := range menuItems { + + activebind := fmt.Sprintf("page === '%s' && 'bg-mantle'", item.name) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
  • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/footer/footer.templ b/internal/view/component/footer/footer.templ similarity index 100% rename from view/component/footer/footer.templ rename to internal/view/component/footer/footer.templ diff --git a/internal/view/component/footer/footer_templ.go b/internal/view/component/footer/footer_templ.go new file mode 100644 index 0000000..359e1ff --- /dev/null +++ b/internal/view/component/footer/footer_templ.go @@ -0,0 +1,92 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package footer + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type FooterItem struct { + name string + href string +} + +// Specify the links to show in the footer +func getFooterItems() []FooterItem { + return []FooterItem{ + { + name: "About", + href: "/about", + }, + { + name: "Github", + href: "https://github.com/haelnorr/projectreshoot", + }, + } +} + +// Returns the template fragment for the Footer +func Footer() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/form/confirmpass.templ b/internal/view/component/form/confirmpass.templ similarity index 100% rename from view/component/form/confirmpass.templ rename to internal/view/component/form/confirmpass.templ diff --git a/internal/view/component/form/confirmpass_templ.go b/internal/view/component/form/confirmpass_templ.go new file mode 100644 index 0000000..ceed0b2 --- /dev/null +++ b/internal/view/component/form/confirmpass_templ.go @@ -0,0 +1,55 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package form + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func ConfirmPassword(err string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/form/loginform.templ b/internal/view/component/form/loginform.templ similarity index 100% rename from view/component/form/loginform.templ rename to internal/view/component/form/loginform.templ diff --git a/internal/view/component/form/loginform_templ.go b/internal/view/component/form/loginform_templ.go new file mode 100644 index 0000000..e8bc085 --- /dev/null +++ b/internal/view/component/form/loginform_templ.go @@ -0,0 +1,60 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package form + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// Login Form. If loginError is not an empty string, it will display the +// contents of loginError to the user. +// If loginError is "Username or password incorrect" it will also show +// error icons on the username and password field +func LoginForm(loginError string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + credErr := "Username or password incorrect" + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/form/registerform.templ b/internal/view/component/form/registerform.templ similarity index 100% rename from view/component/form/registerform.templ rename to internal/view/component/form/registerform.templ diff --git a/internal/view/component/form/registerform_templ.go b/internal/view/component/form/registerform_templ.go new file mode 100644 index 0000000..d332b3f --- /dev/null +++ b/internal/view/component/form/registerform_templ.go @@ -0,0 +1,63 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package form + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// Login Form. If loginError is not an empty string, it will display the +// contents of loginError to the user. +func RegisterForm(registerError string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + + usernameErr := "Username is taken" + passErrs := []string{ + "Password exceeds maximum length of 72 bytes", + "Passwords do not match", + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/nav/navbar.templ b/internal/view/component/nav/navbar.templ similarity index 100% rename from view/component/nav/navbar.templ rename to internal/view/component/nav/navbar.templ diff --git a/internal/view/component/nav/navbar_templ.go b/internal/view/component/nav/navbar_templ.go new file mode 100644 index 0000000..7e7b2f8 --- /dev/null +++ b/internal/view/component/nav/navbar_templ.go @@ -0,0 +1,77 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package nav + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +type NavItem struct { + name string // Label to display + href string // Link reference +} + +// Return the list of navbar links +func getNavItems() []NavItem { + return []NavItem{ + { + name: "Movies", + href: "/movies", + }, + } +} + +// Returns the navbar template fragment +func Navbar() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + navItems := getNavItems() + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Project Reshoot
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = navLeft(navItems).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = navRight().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = sideNav(navItems).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/nav/navbarleft.templ b/internal/view/component/nav/navbarleft.templ similarity index 100% rename from view/component/nav/navbarleft.templ rename to internal/view/component/nav/navbarleft.templ diff --git a/internal/view/component/nav/navbarleft_templ.go b/internal/view/component/nav/navbarleft_templ.go new file mode 100644 index 0000000..119bb65 --- /dev/null +++ b/internal/view/component/nav/navbarleft_templ.go @@ -0,0 +1,73 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package nav + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +// Returns the left portion of the navbar +func navLeft(navItems []NavItem) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/nav/navbarright.templ b/internal/view/component/nav/navbarright.templ similarity index 98% rename from view/component/nav/navbarright.templ rename to internal/view/component/nav/navbarright.templ index 4d3a114..2f7f767 100644 --- a/view/component/nav/navbarright.templ +++ b/internal/view/component/nav/navbarright.templ @@ -1,6 +1,6 @@ package nav -import "projectreshoot/contexts" +import "projectreshoot/pkg/contexts" type ProfileItem struct { name string // Label to display diff --git a/internal/view/component/nav/navbarright_templ.go b/internal/view/component/nav/navbarright_templ.go new file mode 100644 index 0000000..98515bc --- /dev/null +++ b/internal/view/component/nav/navbarright_templ.go @@ -0,0 +1,124 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package nav + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/contexts" + +type ProfileItem struct { + name string // Label to display + href string // Link reference +} + +// Return the list of profile links +func getProfileItems() []ProfileItem { + return []ProfileItem{ + { + name: "Profile", + href: "/profile", + }, + { + name: "Account", + href: "/account", + }, + } +} + +// Returns the right portion of the navbar +func navRight() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + user := contexts.GetUser(ctx) + items := getProfileItems() + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if user != nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } else { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "Login Register") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/nav/sidenav.templ b/internal/view/component/nav/sidenav.templ similarity index 97% rename from view/component/nav/sidenav.templ rename to internal/view/component/nav/sidenav.templ index b4bfd62..f8b6243 100644 --- a/view/component/nav/sidenav.templ +++ b/internal/view/component/nav/sidenav.templ @@ -1,6 +1,6 @@ package nav -import "projectreshoot/contexts" +import "projectreshoot/pkg/contexts" // Returns the mobile version of the navbar thats only visible when activated templ sideNav(navItems []NavItem) { diff --git a/internal/view/component/nav/sidenav_templ.go b/internal/view/component/nav/sidenav_templ.go new file mode 100644 index 0000000..3fb57d6 --- /dev/null +++ b/internal/view/component/nav/sidenav_templ.go @@ -0,0 +1,86 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package nav + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/contexts" + +// Returns the mobile version of the navbar thats only visible when activated +func sideNav(navItems []NavItem) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + user := contexts.GetUser(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + if user == nil { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/popup/confirmPasswordModal.templ b/internal/view/component/popup/confirmPasswordModal.templ similarity index 88% rename from view/component/popup/confirmPasswordModal.templ rename to internal/view/component/popup/confirmPasswordModal.templ index 7c2daef..bbe58a4 100644 --- a/view/component/popup/confirmPasswordModal.templ +++ b/internal/view/component/popup/confirmPasswordModal.templ @@ -1,7 +1,7 @@ package popup -import "projectreshoot/view/component/form" +import "projectreshoot/internal/view/component/form" templ ConfirmPasswordModal() {
To complete this action you need to confirm your password
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = form.ConfirmPassword("").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/popup/error500Popup.templ b/internal/view/component/popup/error500Popup.templ similarity index 100% rename from view/component/popup/error500Popup.templ rename to internal/view/component/popup/error500Popup.templ diff --git a/internal/view/component/popup/error500Popup_templ.go b/internal/view/component/popup/error500Popup_templ.go new file mode 100644 index 0000000..cd21eef --- /dev/null +++ b/internal/view/component/popup/error500Popup_templ.go @@ -0,0 +1,40 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package popup + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Error500Popup() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Something went wrong

An error occured on the server. Please try again later, or contact an administrator

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/popup/error503Popup.templ b/internal/view/component/popup/error503Popup.templ similarity index 100% rename from view/component/popup/error503Popup.templ rename to internal/view/component/popup/error503Popup.templ diff --git a/internal/view/component/popup/error503Popup_templ.go b/internal/view/component/popup/error503Popup_templ.go new file mode 100644 index 0000000..e542023 --- /dev/null +++ b/internal/view/component/popup/error503Popup_templ.go @@ -0,0 +1,40 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package popup + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +func Error503Popup() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Service Unavailable

The service is currently available. It could be down for maintenance. Please try again later.

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/component/search/movies_results.templ b/internal/view/component/search/movies_results.templ similarity index 97% rename from view/component/search/movies_results.templ rename to internal/view/component/search/movies_results.templ index 396a6fa..d8d8e7a 100644 --- a/view/component/search/movies_results.templ +++ b/internal/view/component/search/movies_results.templ @@ -1,6 +1,6 @@ package search -import "projectreshoot/tmdb" +import "projectreshoot/pkg/tmdb" import "fmt" templ MovieResults(movies *tmdb.ResultMovies, image *tmdb.Image) { diff --git a/internal/view/component/search/movies_results_templ.go b/internal/view/component/search/movies_results_templ.go new file mode 100644 index 0000000..70ca412 --- /dev/null +++ b/internal/view/component/search/movies_results_templ.go @@ -0,0 +1,132 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package search + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/tmdb" +import "fmt" + +func MovieResults(movies *tmdb.ResultMovies, image *tmdb.Image) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + for _, movie := range movies.Results { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
\"Movie
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(movie.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/search/movies_results.templ`, Line: 31, Col: 18} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(movie.ReleaseYear()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/search/movies_results.templ`, Line: 31, Col: 42} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "

Released: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(movie.ReleaseDate) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/search/movies_results.templ`, Line: 34, Col: 50} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "

Original Title: ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(movie.OriginalTitle) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/search/movies_results.templ`, Line: 38, Col: 52} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(movie.Overview) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/component/search/movies_results.templ`, Line: 40, Col: 45} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/layout/global.templ b/internal/view/layout/global.templ similarity index 96% rename from view/layout/global.templ rename to internal/view/layout/global.templ index 95536f2..b099138 100644 --- a/view/layout/global.templ +++ b/internal/view/layout/global.templ @@ -1,8 +1,8 @@ package layout -import "projectreshoot/view/component/nav" -import "projectreshoot/view/component/footer" -import "projectreshoot/view/component/popup" +import "projectreshoot/internal/view/component/nav" +import "projectreshoot/internal/view/component/footer" +import "projectreshoot/internal/view/component/popup" // Global page layout. Includes HTML document settings, header tags // navbar and footer diff --git a/internal/view/layout/global_templ.go b/internal/view/layout/global_templ.go new file mode 100644 index 0000000..cfca892 --- /dev/null +++ b/internal/view/layout/global_templ.go @@ -0,0 +1,99 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package layout + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/component/nav" +import "projectreshoot/internal/view/component/footer" +import "projectreshoot/internal/view/component/popup" + +// Global page layout. Includes HTML document settings, header tags +// navbar and footer +func Global(title string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var2 string + templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/layout/global.templ`, Line: 36, Col: 17} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = popup.Error500Popup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = popup.Error503Popup().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = popup.ConfirmPasswordModal().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = nav.Navbar().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templ_7745c5c3_Var1.Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = footer.Footer().Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 6, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/about.templ b/internal/view/page/about.templ similarity index 97% rename from view/page/about.templ rename to internal/view/page/about.templ index 66b28e8..9c05aaa 100644 --- a/view/page/about.templ +++ b/internal/view/page/about.templ @@ -1,6 +1,6 @@ package page -import "projectreshoot/view/layout" +import "projectreshoot/internal/view/layout" // Returns the about page content templ About() { diff --git a/internal/view/page/about_templ.go b/internal/view/page/about_templ.go new file mode 100644 index 0000000..f449951 --- /dev/null +++ b/internal/view/page/about_templ.go @@ -0,0 +1,61 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" + +// Returns the about page content +func About() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
About
What is Project Reshoot?
Project Reshoot is a movie review site that aims to provide a better experience for the users. Instead of a single number that shows the average, or a spread of star ratings, Project Reshoot asks you to rate movies with a vibe. These ratings are shown as an easy to see pie chart showing how everyone felt.
The other major feature is the ability for you to customize what details you see about movies, hiding details you don't want to see until after you've watched it. This gives you peace of mind when searching for new movies to watch.
Why the name?
The name came partially from the premise of wanting to deliver a new take on movie reviews, and partially from it being rewritten from scratch in a new technology stack (Goodbye NextJS, Hello GOTH).
Who's behind it?
Currently Project Reshoot is being built by a team of 1. It is somewhat of a passion project and a way to practice my development skills. For the time being, it will likely stay that way, but if you want to contribute, you can check out the Github repo here.
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("About").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/account.templ b/internal/view/page/account.templ similarity index 56% rename from view/page/account.templ rename to internal/view/page/account.templ index f4aa29a..ed9356d 100644 --- a/view/page/account.templ +++ b/internal/view/page/account.templ @@ -1,7 +1,7 @@ package page -import "projectreshoot/view/layout" -import "projectreshoot/view/component/account" +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/account" templ Account(subpage string) { @layout.Global("Account - " + subpage) { diff --git a/internal/view/page/account_templ.go b/internal/view/page/account_templ.go new file mode 100644 index 0000000..3f8c7fa --- /dev/null +++ b/internal/view/page/account_templ.go @@ -0,0 +1,61 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/account" + +func Account(subpage string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = account.AccountContainer(subpage).Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Account - "+subpage).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/error.templ b/internal/view/page/error.templ similarity index 95% rename from view/page/error.templ rename to internal/view/page/error.templ index 5da21ec..b40aa33 100644 --- a/view/page/error.templ +++ b/internal/view/page/error.templ @@ -1,6 +1,6 @@ package page -import "projectreshoot/view/layout" +import "projectreshoot/internal/view/layout" import "strconv" // Page template for Error pages. Error code should be a HTTP status code as diff --git a/internal/view/page/error_templ.go b/internal/view/page/error_templ.go new file mode 100644 index 0000000..9a3f240 --- /dev/null +++ b/internal/view/page/error_templ.go @@ -0,0 +1,103 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" +import "strconv" + +// Page template for Error pages. Error code should be a HTTP status code as +// a string, and err should be the corresponding response title. +// Message is a custom error message displayed below the code and error. +func Error(code int, err string, message string) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(strconv.Itoa(code)) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/error.templ`, Line: 18, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(err) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/error.templ`, Line: 22, Col: 10} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, "

") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var5 string + templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(message) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/error.templ`, Line: 25, Col: 14} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "

Go to homepage
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global(err).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/index.templ b/internal/view/page/index.templ similarity index 85% rename from view/page/index.templ rename to internal/view/page/index.templ index 350db4f..bdf891d 100644 --- a/view/page/index.templ +++ b/internal/view/page/index.templ @@ -1,6 +1,6 @@ package page -import "projectreshoot/view/layout" +import "projectreshoot/internal/view/layout" // Page content for the index page templ Index() { diff --git a/internal/view/page/index_templ.go b/internal/view/page/index_templ.go new file mode 100644 index 0000000..1e53b57 --- /dev/null +++ b/internal/view/page/index_templ.go @@ -0,0 +1,61 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" + +// Page content for the index page +func Index() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Project Reshoot
A better way to discover and rate films
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Project Reshoot").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/login.templ b/internal/view/page/login.templ similarity index 91% rename from view/page/login.templ rename to internal/view/page/login.templ index 1335601..0cd284d 100644 --- a/view/page/login.templ +++ b/internal/view/page/login.templ @@ -1,7 +1,7 @@ package page -import "projectreshoot/view/layout" -import "projectreshoot/view/component/form" +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/form" // Returns the login page templ Login() { diff --git a/internal/view/page/login_templ.go b/internal/view/page/login_templ.go new file mode 100644 index 0000000..923ea97 --- /dev/null +++ b/internal/view/page/login_templ.go @@ -0,0 +1,70 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/form" + +// Returns the login page +func Login() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Login

Don't have an account yet? Sign up here

Or
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = form.LoginForm("").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Login").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/movie.templ b/internal/view/page/movie.templ similarity index 97% rename from view/page/movie.templ rename to internal/view/page/movie.templ index ef7700b..3bc38ae 100644 --- a/view/page/movie.templ +++ b/internal/view/page/movie.templ @@ -1,7 +1,7 @@ package page -import "projectreshoot/tmdb" -import "projectreshoot/view/layout" +import "projectreshoot/pkg/tmdb" +import "projectreshoot/internal/view/layout" templ Movie(movie *tmdb.Movie, credits *tmdb.Credits, image *tmdb.Image) { @layout.Global(movie.Title) { diff --git a/view/page/movie_search.templ b/internal/view/page/movie_search.templ similarity index 94% rename from view/page/movie_search.templ rename to internal/view/page/movie_search.templ index c9bf9ff..65c23bc 100644 --- a/view/page/movie_search.templ +++ b/internal/view/page/movie_search.templ @@ -1,6 +1,6 @@ package page -import "projectreshoot/view/layout" +import "projectreshoot/internal/view/layout" templ Movies() { @layout.Global("Search movies") { diff --git a/internal/view/page/movie_search_templ.go b/internal/view/page/movie_search_templ.go new file mode 100644 index 0000000..5d82b0e --- /dev/null +++ b/internal/view/page/movie_search_templ.go @@ -0,0 +1,60 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" + +func Movies() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Search movies").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/internal/view/page/movie_templ.go b/internal/view/page/movie_templ.go new file mode 100644 index 0000000..f3adedd --- /dev/null +++ b/internal/view/page/movie_templ.go @@ -0,0 +1,188 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/pkg/tmdb" +import "projectreshoot/internal/view/layout" + +func Movie(movie *tmdb.Movie, credits *tmdb.Credits, image *tmdb.Image) templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + for _, billedcrew := range credits.BilledCrew() { + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(billedcrew.Name) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 15, Col: 47} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 3, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var4 string + templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(billedcrew.FRoles()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 16, Col: 55} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 4, "") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 5, "
\"Poster\"
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var6 string + templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(movie.Title) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 58, Col: 19} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 7, " ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var7 string + templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(movie.FGenres()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 61, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 8, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(movie.FRuntime()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 62, Col: 33} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 9, " • ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(movie.ReleaseYear()) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 63, Col: 36} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 10, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(movie.Tagline) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 77, Col: 22} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 11, "
Overview ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(movie.Overview) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/movie.templ`, Line: 86, Col: 23} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 12, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global(movie.Title).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/profile.templ b/internal/view/page/profile.templ similarity index 68% rename from view/page/profile.templ rename to internal/view/page/profile.templ index e85f6bc..7acffc0 100644 --- a/view/page/profile.templ +++ b/internal/view/page/profile.templ @@ -1,7 +1,7 @@ package page -import "projectreshoot/view/layout" -import "projectreshoot/contexts" +import "projectreshoot/internal/view/layout" +import "projectreshoot/pkg/contexts" templ Profile() { {{ user := contexts.GetUser(ctx) }} diff --git a/internal/view/page/profile_templ.go b/internal/view/page/profile_templ.go new file mode 100644 index 0000000..1a3b4ed --- /dev/null +++ b/internal/view/page/profile_templ.go @@ -0,0 +1,75 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" +import "projectreshoot/pkg/contexts" + +func Profile() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + user := contexts.GetUser(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "
Hello, ") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var3 string + templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(user.Username) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `internal/view/page/profile.templ`, Line: 10, Col: 25} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Profile - "+user.Username).Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/view/page/register.templ b/internal/view/page/register.templ similarity index 91% rename from view/page/register.templ rename to internal/view/page/register.templ index a8e3bb1..cf7e222 100644 --- a/view/page/register.templ +++ b/internal/view/page/register.templ @@ -1,7 +1,7 @@ package page -import "projectreshoot/view/layout" -import "projectreshoot/view/component/form" +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/form" // Returns the login page templ Register() { diff --git a/internal/view/page/register_templ.go b/internal/view/page/register_templ.go new file mode 100644 index 0000000..d43a1e6 --- /dev/null +++ b/internal/view/page/register_templ.go @@ -0,0 +1,70 @@ +// Code generated by templ - DO NOT EDIT. + +// templ: version: v0.3.833 +package page + +//lint:file-ignore SA4006 This context is only used if a nested component is present. + +import "github.com/a-h/templ" +import templruntime "github.com/a-h/templ/runtime" + +import "projectreshoot/internal/view/layout" +import "projectreshoot/internal/view/component/form" + +// Returns the login page +func Register() templ.Component { + return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + if templ_7745c5c3_CtxErr := ctx.Err(); templ_7745c5c3_CtxErr != nil { + return templ_7745c5c3_CtxErr + } + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Var1 := templ.GetChildren(ctx) + if templ_7745c5c3_Var1 == nil { + templ_7745c5c3_Var1 = templ.NopComponent + } + ctx = templ.ClearChildren(ctx) + templ_7745c5c3_Var2 := templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { + templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context + templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) + if !templ_7745c5c3_IsBuffer { + defer func() { + templ_7745c5c3_BufErr := templruntime.ReleaseBuffer(templ_7745c5c3_Buffer) + if templ_7745c5c3_Err == nil { + templ_7745c5c3_Err = templ_7745c5c3_BufErr + } + }() + } + ctx = templ.InitializeContext(ctx) + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 1, "

Register

Already have an account? Login here

Or
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = form.RegisterForm("").Render(ctx, templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 2, "
") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) + templ_7745c5c3_Err = layout.Global("Register").Render(templ.WithChildren(ctx, templ_7745c5c3_Var2), templ_7745c5c3_Buffer) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + return nil + }) +} + +var _ = templruntime.GeneratedTemplate diff --git a/main.go b/main.go deleted file mode 100644 index 257de55..0000000 --- a/main.go +++ /dev/null @@ -1,233 +0,0 @@ -package main - -import ( - "context" - "embed" - "flag" - "fmt" - "io" - "io/fs" - "net" - "net/http" - "os" - "os/signal" - "strconv" - "sync" - "sync/atomic" - "syscall" - "time" - - "projectreshoot/config" - "projectreshoot/db" - "projectreshoot/logging" - "projectreshoot/server" - "projectreshoot/tests" - - "github.com/pkg/errors" - "github.com/rs/zerolog" -) - -//go:embed static/* -var embeddedStatic embed.FS - -// Gets the static files -func getStaticFiles(logger *zerolog.Logger) (http.FileSystem, error) { - if _, err := os.Stat("static"); err == nil { - // Use actual filesystem in development - logger.Debug().Msg("Using filesystem for static files") - return http.Dir("static"), nil - } else { - // Use embedded filesystem in production - logger.Debug().Msg("Using embedded static files") - subFS, err := fs.Sub(embeddedStatic, "static") - if err != nil { - return nil, errors.Wrap(err, "fs.Sub") - } - return http.FS(subFS), nil - } -} - -var maint uint32 // atomic: 1 if in maintenance mode - -// Handle SIGUSR1 and SIGUSR2 syscalls to toggle maintenance mode -func handleMaintSignals( - conn *db.SafeConn, - srv *http.Server, - logger *zerolog.Logger, - config *config.Config, -) { - logger.Debug().Msg("Starting signal listener") - ch := make(chan os.Signal, 1) - srv.RegisterOnShutdown(func() { - logger.Debug().Msg("Shutting down signal listener") - close(ch) - }) - go func() { - for sig := range ch { - switch sig { - case syscall.SIGUSR1: - if atomic.LoadUint32(&maint) != 1 { - atomic.StoreUint32(&maint, 1) - logger.Info().Msg("Signal received: Starting maintenance") - logger.Info().Msg("Attempting to acquire database lock") - conn.Pause(config.DBLockTimeout * time.Second) - } - case syscall.SIGUSR2: - if atomic.LoadUint32(&maint) != 0 { - logger.Info().Msg("Signal received: Maintenance over") - logger.Info().Msg("Releasing database lock") - conn.Resume() - atomic.StoreUint32(&maint, 0) - } - } - } - }() - signal.Notify(ch, syscall.SIGUSR1, syscall.SIGUSR2) -} - -// Initializes and runs the server -func run(ctx context.Context, w io.Writer, args map[string]string) error { - ctx, cancel := signal.NotifyContext(ctx, os.Interrupt) - defer cancel() - - config, err := config.GetConfig(args) - if err != nil { - return errors.Wrap(err, "server.GetConfig") - } - - // Return the version of the database required - if args["dbver"] == "true" { - fmt.Fprintf(w, "Database version: %s\n", config.DBName) - return nil - } - - var logfile *os.File = nil - if config.LogOutput == "both" || config.LogOutput == "file" { - logfile, err = logging.GetLogFile(config.LogDir) - if err != nil { - return errors.Wrap(err, "logging.GetLogFile") - } - defer logfile.Close() - } - - var consoleWriter io.Writer - if config.LogOutput == "both" || config.LogOutput == "console" { - consoleWriter = w - } - - logger, err := logging.GetLogger( - config.LogLevel, - consoleWriter, - logfile, - config.LogDir, - ) - if err != nil { - return errors.Wrap(err, "logging.GetLogger") - } - - logger.Debug().Msg("Config loaded and logger started") - logger.Debug().Msg("Connecting to database") - var conn *db.SafeConn - if args["test"] == "true" { - logger.Debug().Msg("Server in test mode, using test database") - ver, err := strconv.ParseInt(config.DBName, 10, 0) - if err != nil { - return errors.Wrap(err, "strconv.ParseInt") - } - testconn, err := tests.SetupTestDB(ver) - if err != nil { - return errors.Wrap(err, "tests.SetupTestDB") - } - conn = db.MakeSafe(testconn, logger) - } else { - conn, err = db.ConnectToDatabase(config.DBName, logger) - if err != nil { - return errors.Wrap(err, "db.ConnectToDatabase") - } - } - defer conn.Close() - - logger.Debug().Msg("Getting static files") - staticFS, err := getStaticFiles(logger) - if err != nil { - return errors.Wrap(err, "getStaticFiles") - } - - logger.Debug().Msg("Setting up HTTP server") - srv := server.NewServer(config, logger, conn, &staticFS, &maint) - httpServer := &http.Server{ - Addr: net.JoinHostPort(config.Host, config.Port), - Handler: srv, - ReadHeaderTimeout: config.ReadHeaderTimeout * time.Second, - WriteTimeout: config.WriteTimeout * time.Second, - IdleTimeout: config.IdleTimeout * time.Second, - } - - // Runs function for testing in dev if --test flag true - if args["tester"] == "true" { - logger.Debug().Msg("Running tester function") - test(config, logger, conn, httpServer) - return nil - } - - // Setups a channel to listen for os.Signal - handleMaintSignals(conn, httpServer, logger, config) - - // Runs the http server - logger.Debug().Msg("Starting up the HTTP server") - go func() { - logger.Info().Str("address", httpServer.Addr).Msg("Listening for requests") - if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - logger.Error().Err(err).Msg("Error listening and serving") - } - }() - - // Handles graceful shutdown - var wg sync.WaitGroup - wg.Add(1) - go func() { - defer wg.Done() - <-ctx.Done() - shutdownCtx := context.Background() - shutdownCtx, cancel := context.WithTimeout(shutdownCtx, 10*time.Second) - defer cancel() - if err := httpServer.Shutdown(shutdownCtx); err != nil { - logger.Error().Err(err).Msg("Error shutting down server") - } - }() - wg.Wait() - logger.Info().Msg("Shutting down") - return nil -} - -// Start of runtime. Parse commandline arguments & flags, Initializes context -// and starts the server -func main() { - // 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") - tester := flag.Bool("tester", false, "Run tester function instead of main program") - 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)") - flag.Parse() - - // Map the args for easy access - args := map[string]string{ - "host": *host, - "port": *port, - "test": strconv.FormatBool(*test), - "tester": strconv.FormatBool(*tester), - "dbver": strconv.FormatBool(*dbver), - "loglevel": *loglevel, - "logoutput": *logoutput, - } - - // Start the server - ctx := context.Background() - if err := run(ctx, os.Stdout, args); err != nil { - fmt.Fprintf(os.Stderr, "%s\n", err) - os.Exit(1) - } -} diff --git a/config/config.go b/pkg/config/config.go similarity index 98% rename from config/config.go rename to pkg/config/config.go index f3e1012..7110e9d 100644 --- a/config/config.go +++ b/pkg/config/config.go @@ -5,8 +5,8 @@ import ( "os" "time" - "projectreshoot/logging" - "projectreshoot/tmdb" + "projectreshoot/pkg/logging" + "projectreshoot/pkg/tmdb" "github.com/joho/godotenv" "github.com/pkg/errors" diff --git a/config/environment.go b/pkg/config/environment.go similarity index 100% rename from config/environment.go rename to pkg/config/environment.go diff --git a/contexts/keys.go b/pkg/contexts/keys.go similarity index 100% rename from contexts/keys.go rename to pkg/contexts/keys.go diff --git a/contexts/request_timer.go b/pkg/contexts/request_timer.go similarity index 100% rename from contexts/request_timer.go rename to pkg/contexts/request_timer.go diff --git a/contexts/user.go b/pkg/contexts/user.go similarity index 91% rename from contexts/user.go rename to pkg/contexts/user.go index 0ef041f..c24e6bb 100644 --- a/contexts/user.go +++ b/pkg/contexts/user.go @@ -2,11 +2,11 @@ package contexts import ( "context" - "projectreshoot/db" + "projectreshoot/internal/models" ) type AuthenticatedUser struct { - *db.User + *models.User Fresh int64 } diff --git a/cookies/functions.go b/pkg/cookies/functions.go similarity index 100% rename from cookies/functions.go rename to pkg/cookies/functions.go diff --git a/cookies/pagefrom.go b/pkg/cookies/pagefrom.go similarity index 100% rename from cookies/pagefrom.go rename to pkg/cookies/pagefrom.go diff --git a/cookies/tokens.go b/pkg/cookies/tokens.go similarity index 93% rename from cookies/tokens.go rename to pkg/cookies/tokens.go index a5c20a0..696758c 100644 --- a/cookies/tokens.go +++ b/pkg/cookies/tokens.go @@ -4,9 +4,9 @@ import ( "net/http" "time" - "projectreshoot/config" - "projectreshoot/db" - "projectreshoot/jwt" + "projectreshoot/internal/models" + "projectreshoot/pkg/config" + "projectreshoot/pkg/jwt" "github.com/pkg/errors" ) @@ -58,7 +58,7 @@ func SetTokenCookies( w http.ResponseWriter, r *http.Request, config *config.Config, - user *db.User, + user *models.User, fresh bool, rememberMe bool, ) error { diff --git a/db/connection.go b/pkg/db/connection.go similarity index 65% rename from db/connection.go rename to pkg/db/connection.go index 67cdf22..a5eb718 100644 --- a/db/connection.go +++ b/pkg/db/connection.go @@ -8,7 +8,7 @@ import ( "github.com/pkg/errors" "github.com/rs/zerolog" - _ "modernc.org/sqlite" + _ "github.com/mattn/go-sqlite3" ) // Returns a database connection handle for the DB @@ -16,20 +16,30 @@ func ConnectToDatabase( dbName string, logger *zerolog.Logger, ) (*SafeConn, error) { - file := fmt.Sprintf("file:%s.db", dbName) - db, err := sql.Open("sqlite", file) + opts := "_journal_mode=WAL&_synchronous=NORMAL&_txlock=IMMEDIATE" + file := fmt.Sprintf("file:%s.db?%s", dbName, opts) + wconn, err := sql.Open("sqlite3", file) if err != nil { - return nil, errors.Wrap(err, "sql.Open") + return nil, errors.Wrap(err, "sql.Open (rw)") } + wconn.SetMaxOpenConns(1) + opts = "_synchronous=NORMAL&mode=ro" + file = fmt.Sprintf("file:%s.db?%s", dbName, opts) + + rconn, err := sql.Open("sqlite3", file) + if err != nil { + return nil, errors.Wrap(err, "sql.Open (ro)") + } + version, err := strconv.Atoi(dbName) if err != nil { return nil, errors.Wrap(err, "strconv.Atoi") } - err = checkDBVersion(db, version) + err = checkDBVersion(rconn, version) if err != nil { return nil, errors.Wrap(err, "checkDBVersion") } - conn := MakeSafe(db, logger) + conn := MakeSafe(wconn, rconn, logger) return conn, nil } diff --git a/db/safeconn.go b/pkg/db/safeconn.go similarity index 73% rename from db/safeconn.go rename to pkg/db/safeconn.go index 4d66f70..0fa1560 100644 --- a/db/safeconn.go +++ b/pkg/db/safeconn.go @@ -10,7 +10,8 @@ import ( ) type SafeConn struct { - db *sql.DB + wconn *sql.DB + rconn *sql.DB readLockCount uint32 globalLockStatus uint32 globalLockRequested uint32 @@ -18,8 +19,8 @@ type SafeConn struct { } // Make the provided db handle safe and attach a logger to it -func MakeSafe(db *sql.DB, logger *zerolog.Logger) *SafeConn { - return &SafeConn{db: db, logger: logger} +func MakeSafe(wconn *sql.DB, rconn *sql.DB, logger *zerolog.Logger) *SafeConn { + return &SafeConn{wconn: wconn, rconn: rconn, logger: logger} } // Attempts to acquire a global lock on the database connection @@ -61,7 +62,7 @@ func (conn *SafeConn) releaseReadLock() { // Starts a new transaction based on the current context. Will cancel if // the context is closed/cancelled/done -func (conn *SafeConn) Begin(ctx context.Context) (*SafeTX, error) { +func (conn *SafeConn) Begin(ctx context.Context) (*SafeWTX, error) { lockAcquired := make(chan struct{}) lockCtx, cancel := context.WithCancel(ctx) defer cancel() @@ -79,12 +80,44 @@ func (conn *SafeConn) Begin(ctx context.Context) (*SafeTX, error) { select { case <-lockAcquired: - tx, err := conn.db.BeginTx(ctx, nil) + tx, err := conn.wconn.BeginTx(ctx, nil) if err != nil { conn.releaseReadLock() return nil, err } - return &SafeTX{tx: tx, sc: conn}, nil + return &SafeWTX{tx: tx, sc: conn}, nil + case <-ctx.Done(): + cancel() + return nil, errors.New("Transaction time out due to database lock") + } +} + +// Starts a new READONLY transaction based on the current context. Will cancel if +// the context is closed/cancelled/done +func (conn *SafeConn) RBegin(ctx context.Context) (*SafeRTX, error) { + lockAcquired := make(chan struct{}) + lockCtx, cancel := context.WithCancel(ctx) + defer cancel() + + go func() { + select { + case <-lockCtx.Done(): + return + default: + if conn.acquireReadLock() { + close(lockAcquired) + } + } + }() + + select { + case <-lockAcquired: + tx, err := conn.rconn.BeginTx(ctx, nil) + if err != nil { + conn.releaseReadLock() + return nil, err + } + return &SafeRTX{tx: tx, sc: conn}, nil case <-ctx.Done(): cancel() return nil, errors.New("Transaction time out due to database lock") @@ -125,5 +158,5 @@ func (conn *SafeConn) Close() error { conn.acquireGlobalLock() defer conn.releaseGlobalLock() conn.logger.Debug().Msg("Closing database connection") - return conn.db.Close() + return conn.wconn.Close() } diff --git a/db/safeconntx_test.go b/pkg/db/safeconntx_test.go similarity index 90% rename from db/safeconntx_test.go rename to pkg/db/safeconntx_test.go index b1816d7..35d483a 100644 --- a/db/safeconntx_test.go +++ b/pkg/db/safeconntx_test.go @@ -2,7 +2,7 @@ package db import ( "context" - "projectreshoot/tests" + "projectreshoot/pkg/tests" "strconv" "sync" "testing" @@ -18,9 +18,9 @@ func TestSafeConn(t *testing.T) { logger := tests.NilLogger() ver, err := strconv.ParseInt(cfg.DBName, 10, 0) require.NoError(t, err) - conn, err := tests.SetupTestDB(ver) + wconn, rconn, err := tests.SetupTestDB(ver) require.NoError(t, err) - sconn := MakeSafe(conn, logger) + sconn := MakeSafe(wconn, rconn, logger) defer sconn.Close() t.Run("Global lock waits for read locks to finish", func(t *testing.T) { @@ -87,9 +87,9 @@ func TestSafeTX(t *testing.T) { logger := tests.NilLogger() ver, err := strconv.ParseInt(cfg.DBName, 10, 0) require.NoError(t, err) - conn, err := tests.SetupTestDB(ver) + wconn, rconn, err := tests.SetupTestDB(ver) require.NoError(t, err) - sconn := MakeSafe(conn, logger) + sconn := MakeSafe(wconn, rconn, logger) defer sconn.Close() t.Run("Commit releases lock", func(t *testing.T) { @@ -106,12 +106,12 @@ func TestSafeTX(t *testing.T) { tx.Rollback() assert.Equal(t, uint32(0), sconn.readLockCount) }) - t.Run("Multiple TX can gain read lock", func(t *testing.T) { - tx1, err := sconn.Begin(t.Context()) + t.Run("Multiple RTX can gain read lock", func(t *testing.T) { + tx1, err := sconn.RBegin(t.Context()) require.NoError(t, err) - tx2, err := sconn.Begin(t.Context()) + tx2, err := sconn.RBegin(t.Context()) require.NoError(t, err) - tx3, err := sconn.Begin(t.Context()) + tx3, err := sconn.RBegin(t.Context()) require.NoError(t, err) tx1.Commit() tx2.Commit() diff --git a/pkg/db/safetx.go b/pkg/db/safetx.go new file mode 100644 index 0000000..070b23f --- /dev/null +++ b/pkg/db/safetx.go @@ -0,0 +1,163 @@ +package db + +import ( + "context" + "database/sql" + "regexp" + "strings" + + "github.com/pkg/errors" +) + +type SafeTX interface { + Query(ctx context.Context, query string, args ...interface{}) (*sql.Rows, error) + QueryRow(ctx context.Context, query string, args ...interface{}) (*sql.Row, error) + Commit() error + Rollback() error +} + +// Extends sql.Tx for use with SafeConn +type SafeWTX struct { + tx *sql.Tx + sc *SafeConn +} +type SafeRTX struct { + tx *sql.Tx + sc *SafeConn +} + +func isWriteOperation(query string) bool { + query = strings.TrimSpace(query) + query = strings.ToUpper(query) + writeOpsRegex := `^(INSERT|UPDATE|DELETE|REPLACE|MERGE|CREATE|DROP|ALTER|TRUNCATE)\s+` + re := regexp.MustCompile(writeOpsRegex) + return re.MatchString(query) +} + +// Query the database inside the transaction +func (stx *SafeRTX) Query( + ctx context.Context, + query string, + args ...interface{}, +) (*sql.Rows, error) { + if stx.tx == nil { + return nil, errors.New("Cannot query without a transaction") + } + if isWriteOperation(query) { + return nil, errors.New("Cannot query with a write operation") + } + rows, err := stx.tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, errors.Wrap(err, "tx.QueryContext") + } + return rows, nil +} + +// Query the database inside the transaction +func (stx *SafeWTX) Query( + ctx context.Context, + query string, + args ...interface{}, +) (*sql.Rows, error) { + if stx.tx == nil { + return nil, errors.New("Cannot query without a transaction") + } + if isWriteOperation(query) { + return nil, errors.New("Cannot query with a write operation") + } + rows, err := stx.tx.QueryContext(ctx, query, args...) + if err != nil { + return nil, errors.Wrap(err, "tx.QueryContext") + } + return rows, nil +} + +// Query a row from the database inside the transaction +func (stx *SafeRTX) QueryRow( + ctx context.Context, + query string, + args ...interface{}, +) (*sql.Row, error) { + if stx.tx == nil { + return nil, errors.New("Cannot query without a transaction") + } + if isWriteOperation(query) { + return nil, errors.New("Cannot query with a write operation") + } + return stx.tx.QueryRowContext(ctx, query, args...), nil +} + +// Query a row from the database inside the transaction +func (stx *SafeWTX) QueryRow( + ctx context.Context, + query string, + args ...interface{}, +) (*sql.Row, error) { + if stx.tx == nil { + return nil, errors.New("Cannot query without a transaction") + } + if isWriteOperation(query) { + return nil, errors.New("Cannot query with a write operation") + } + return stx.tx.QueryRowContext(ctx, query, args...), nil +} + +// Exec a statement on the database inside the transaction +func (stx *SafeWTX) Exec( + ctx context.Context, + query string, + args ...interface{}, +) (sql.Result, error) { + if stx.tx == nil { + return nil, errors.New("Cannot exec without a transaction") + } + res, err := stx.tx.ExecContext(ctx, query, args...) + if err != nil { + return nil, errors.Wrap(err, "tx.ExecContext") + } + return res, nil +} + +// Commit the current transaction and release the read lock +func (stx *SafeRTX) Commit() error { + if stx.tx == nil { + return errors.New("Cannot commit without a transaction") + } + err := stx.tx.Commit() + stx.tx = nil + stx.sc.releaseReadLock() + return err +} + +// Commit the current transaction and release the read lock +func (stx *SafeWTX) Commit() error { + if stx.tx == nil { + return errors.New("Cannot commit without a transaction") + } + err := stx.tx.Commit() + stx.tx = nil + stx.sc.releaseReadLock() + return err +} + +// Abort the current transaction, releasing the read lock +func (stx *SafeRTX) Rollback() error { + if stx.tx == nil { + return errors.New("Cannot rollback without a transaction") + } + err := stx.tx.Rollback() + stx.tx = nil + stx.sc.releaseReadLock() + return err +} + +// Abort the current transaction, releasing the read lock +func (stx *SafeWTX) Rollback() error { + if stx.tx == nil { + return errors.New("Cannot rollback without a transaction") + } + err := stx.tx.Rollback() + stx.tx = nil + stx.sc.releaseReadLock() + return err +} diff --git a/pkg/embedfs/embedfs.go b/pkg/embedfs/embedfs.go new file mode 100644 index 0000000..e730359 --- /dev/null +++ b/pkg/embedfs/embedfs.go @@ -0,0 +1,20 @@ +package embedfs + +import ( + "embed" + "io/fs" + + "github.com/pkg/errors" +) + +//go:embed files/* +var embeddedFiles embed.FS + +// Gets the embedded files +func GetEmbeddedFS() (fs.FS, error) { + subFS, err := fs.Sub(embeddedFiles, "files") + if err != nil { + return nil, errors.Wrap(err, "fs.Sub") + } + return subFS, nil +} diff --git a/pkg/embedfs/files/assets/error.png b/pkg/embedfs/files/assets/error.png new file mode 100644 index 0000000..d1ffa7d Binary files /dev/null and b/pkg/embedfs/files/assets/error.png differ diff --git a/static/css/input.css b/pkg/embedfs/files/css/input.css similarity index 100% rename from static/css/input.css rename to pkg/embedfs/files/css/input.css diff --git a/pkg/embedfs/files/css/output.css b/pkg/embedfs/files/css/output.css new file mode 100644 index 0000000..6e4e488 --- /dev/null +++ b/pkg/embedfs/files/css/output.css @@ -0,0 +1,1913 @@ +/*! tailwindcss v4.0.3 | MIT License | https://tailwindcss.com */ +@import url("https://fonts.googleapis.com/css2?family=Ubuntu+Mono:ital,wght@0,400;0,700;1,400;1,700&display=swap"); +@layer theme, base, components, utilities; +@layer theme { + :root, :host { + --font-sans: ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', + 'Segoe UI Symbol', 'Noto Color Emoji'; + --font-serif: ui-serif, Georgia, Cambria, 'Times New Roman', Times, serif; + --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', + 'Courier New', monospace; + --color-red-50: oklch(0.971 0.013 17.38); + --color-red-100: oklch(0.936 0.032 17.717); + --color-red-200: oklch(0.885 0.062 18.334); + --color-red-300: oklch(0.808 0.114 19.571); + --color-red-400: oklch(0.704 0.191 22.216); + --color-red-500: oklch(0.637 0.237 25.331); + --color-red-600: oklch(0.577 0.245 27.325); + --color-red-700: oklch(0.505 0.213 27.518); + --color-red-800: oklch(0.444 0.177 26.899); + --color-red-900: oklch(0.396 0.141 25.723); + --color-red-950: oklch(0.258 0.092 26.042); + --color-orange-50: oklch(0.98 0.016 73.684); + --color-orange-100: oklch(0.954 0.038 75.164); + --color-orange-200: oklch(0.901 0.076 70.697); + --color-orange-300: oklch(0.837 0.128 66.29); + --color-orange-400: oklch(0.75 0.183 55.934); + --color-orange-500: oklch(0.705 0.213 47.604); + --color-orange-600: oklch(0.646 0.222 41.116); + --color-orange-700: oklch(0.553 0.195 38.402); + --color-orange-800: oklch(0.47 0.157 37.304); + --color-orange-900: oklch(0.408 0.123 38.172); + --color-orange-950: oklch(0.266 0.079 36.259); + --color-amber-50: oklch(0.987 0.022 95.277); + --color-amber-100: oklch(0.962 0.059 95.617); + --color-amber-200: oklch(0.924 0.12 95.746); + --color-amber-300: oklch(0.879 0.169 91.605); + --color-amber-400: oklch(0.828 0.189 84.429); + --color-amber-500: oklch(0.769 0.188 70.08); + --color-amber-600: oklch(0.666 0.179 58.318); + --color-amber-700: oklch(0.555 0.163 48.998); + --color-amber-800: oklch(0.473 0.137 46.201); + --color-amber-900: oklch(0.414 0.112 45.904); + --color-amber-950: oklch(0.279 0.077 45.635); + --color-yellow-50: oklch(0.987 0.026 102.212); + --color-yellow-100: oklch(0.973 0.071 103.193); + --color-yellow-200: oklch(0.945 0.129 101.54); + --color-yellow-300: oklch(0.905 0.182 98.111); + --color-yellow-400: oklch(0.852 0.199 91.936); + --color-yellow-500: oklch(0.795 0.184 86.047); + --color-yellow-600: oklch(0.681 0.162 75.834); + --color-yellow-700: oklch(0.554 0.135 66.442); + --color-yellow-800: oklch(0.476 0.114 61.907); + --color-yellow-900: oklch(0.421 0.095 57.708); + --color-yellow-950: oklch(0.286 0.066 53.813); + --color-lime-50: oklch(0.986 0.031 120.757); + --color-lime-100: oklch(0.967 0.067 122.328); + --color-lime-200: oklch(0.938 0.127 124.321); + --color-lime-300: oklch(0.897 0.196 126.665); + --color-lime-400: oklch(0.841 0.238 128.85); + --color-lime-500: oklch(0.768 0.233 130.85); + --color-lime-600: oklch(0.648 0.2 131.684); + --color-lime-700: oklch(0.532 0.157 131.589); + --color-lime-800: oklch(0.453 0.124 130.933); + --color-lime-900: oklch(0.405 0.101 131.063); + --color-lime-950: oklch(0.274 0.072 132.109); + --color-green-50: oklch(0.982 0.018 155.826); + --color-green-100: oklch(0.962 0.044 156.743); + --color-green-200: oklch(0.925 0.084 155.995); + --color-green-300: oklch(0.871 0.15 154.449); + --color-green-400: oklch(0.792 0.209 151.711); + --color-green-500: oklch(0.723 0.219 149.579); + --color-green-600: oklch(0.627 0.194 149.214); + --color-green-700: oklch(0.527 0.154 150.069); + --color-green-800: oklch(0.448 0.119 151.328); + --color-green-900: oklch(0.393 0.095 152.535); + --color-green-950: oklch(0.266 0.065 152.934); + --color-emerald-50: oklch(0.979 0.021 166.113); + --color-emerald-100: oklch(0.95 0.052 163.051); + --color-emerald-200: oklch(0.905 0.093 164.15); + --color-emerald-300: oklch(0.845 0.143 164.978); + --color-emerald-400: oklch(0.765 0.177 163.223); + --color-emerald-500: oklch(0.696 0.17 162.48); + --color-emerald-600: oklch(0.596 0.145 163.225); + --color-emerald-700: oklch(0.508 0.118 165.612); + --color-emerald-800: oklch(0.432 0.095 166.913); + --color-emerald-900: oklch(0.378 0.077 168.94); + --color-emerald-950: oklch(0.262 0.051 172.552); + --color-teal-50: oklch(0.984 0.014 180.72); + --color-teal-100: oklch(0.953 0.051 180.801); + --color-teal-200: oklch(0.91 0.096 180.426); + --color-teal-300: oklch(0.855 0.138 181.071); + --color-teal-400: oklch(0.777 0.152 181.912); + --color-teal-500: oklch(0.704 0.14 182.503); + --color-teal-600: oklch(0.6 0.118 184.704); + --color-teal-700: oklch(0.511 0.096 186.391); + --color-teal-800: oklch(0.437 0.078 188.216); + --color-teal-900: oklch(0.386 0.063 188.416); + --color-teal-950: oklch(0.277 0.046 192.524); + --color-cyan-50: oklch(0.984 0.019 200.873); + --color-cyan-100: oklch(0.956 0.045 203.388); + --color-cyan-200: oklch(0.917 0.08 205.041); + --color-cyan-300: oklch(0.865 0.127 207.078); + --color-cyan-400: oklch(0.789 0.154 211.53); + --color-cyan-500: oklch(0.715 0.143 215.221); + --color-cyan-600: oklch(0.609 0.126 221.723); + --color-cyan-700: oklch(0.52 0.105 223.128); + --color-cyan-800: oklch(0.45 0.085 224.283); + --color-cyan-900: oklch(0.398 0.07 227.392); + --color-cyan-950: oklch(0.302 0.056 229.695); + --color-sky-50: oklch(0.977 0.013 236.62); + --color-sky-100: oklch(0.951 0.026 236.824); + --color-sky-200: oklch(0.901 0.058 230.902); + --color-sky-300: oklch(0.828 0.111 230.318); + --color-sky-400: oklch(0.746 0.16 232.661); + --color-sky-500: oklch(0.685 0.169 237.323); + --color-sky-600: oklch(0.588 0.158 241.966); + --color-sky-700: oklch(0.5 0.134 242.749); + --color-sky-800: oklch(0.443 0.11 240.79); + --color-sky-900: oklch(0.391 0.09 240.876); + --color-sky-950: oklch(0.293 0.066 243.157); + --color-blue-50: oklch(0.97 0.014 254.604); + --color-blue-100: oklch(0.932 0.032 255.585); + --color-blue-200: oklch(0.882 0.059 254.128); + --color-blue-300: oklch(0.809 0.105 251.813); + --color-blue-400: oklch(0.707 0.165 254.624); + --color-blue-500: oklch(0.623 0.214 259.815); + --color-blue-600: oklch(0.546 0.245 262.881); + --color-blue-700: oklch(0.488 0.243 264.376); + --color-blue-800: oklch(0.424 0.199 265.638); + --color-blue-900: oklch(0.379 0.146 265.522); + --color-blue-950: oklch(0.282 0.091 267.935); + --color-indigo-50: oklch(0.962 0.018 272.314); + --color-indigo-100: oklch(0.93 0.034 272.788); + --color-indigo-200: oklch(0.87 0.065 274.039); + --color-indigo-300: oklch(0.785 0.115 274.713); + --color-indigo-400: oklch(0.673 0.182 276.935); + --color-indigo-500: oklch(0.585 0.233 277.117); + --color-indigo-600: oklch(0.511 0.262 276.966); + --color-indigo-700: oklch(0.457 0.24 277.023); + --color-indigo-800: oklch(0.398 0.195 277.366); + --color-indigo-900: oklch(0.359 0.144 278.697); + --color-indigo-950: oklch(0.257 0.09 281.288); + --color-violet-50: oklch(0.969 0.016 293.756); + --color-violet-100: oklch(0.943 0.029 294.588); + --color-violet-200: oklch(0.894 0.057 293.283); + --color-violet-300: oklch(0.811 0.111 293.571); + --color-violet-400: oklch(0.702 0.183 293.541); + --color-violet-500: oklch(0.606 0.25 292.717); + --color-violet-600: oklch(0.541 0.281 293.009); + --color-violet-700: oklch(0.491 0.27 292.581); + --color-violet-800: oklch(0.432 0.232 292.759); + --color-violet-900: oklch(0.38 0.189 293.745); + --color-violet-950: oklch(0.283 0.141 291.089); + --color-purple-50: oklch(0.977 0.014 308.299); + --color-purple-100: oklch(0.946 0.033 307.174); + --color-purple-200: oklch(0.902 0.063 306.703); + --color-purple-300: oklch(0.827 0.119 306.383); + --color-purple-400: oklch(0.714 0.203 305.504); + --color-purple-500: oklch(0.627 0.265 303.9); + --color-purple-600: oklch(0.558 0.288 302.321); + --color-purple-700: oklch(0.496 0.265 301.924); + --color-purple-800: oklch(0.438 0.218 303.724); + --color-purple-900: oklch(0.381 0.176 304.987); + --color-purple-950: oklch(0.291 0.149 302.717); + --color-fuchsia-50: oklch(0.977 0.017 320.058); + --color-fuchsia-100: oklch(0.952 0.037 318.852); + --color-fuchsia-200: oklch(0.903 0.076 319.62); + --color-fuchsia-300: oklch(0.833 0.145 321.434); + --color-fuchsia-400: oklch(0.74 0.238 322.16); + --color-fuchsia-500: oklch(0.667 0.295 322.15); + --color-fuchsia-600: oklch(0.591 0.293 322.896); + --color-fuchsia-700: oklch(0.518 0.253 323.949); + --color-fuchsia-800: oklch(0.452 0.211 324.591); + --color-fuchsia-900: oklch(0.401 0.17 325.612); + --color-fuchsia-950: oklch(0.293 0.136 325.661); + --color-pink-50: oklch(0.971 0.014 343.198); + --color-pink-100: oklch(0.948 0.028 342.258); + --color-pink-200: oklch(0.899 0.061 343.231); + --color-pink-300: oklch(0.823 0.12 346.018); + --color-pink-400: oklch(0.718 0.202 349.761); + --color-pink-500: oklch(0.656 0.241 354.308); + --color-pink-600: oklch(0.592 0.249 0.584); + --color-pink-700: oklch(0.525 0.223 3.958); + --color-pink-800: oklch(0.459 0.187 3.815); + --color-pink-900: oklch(0.408 0.153 2.432); + --color-pink-950: oklch(0.284 0.109 3.907); + --color-rose-50: oklch(0.969 0.015 12.422); + --color-rose-100: oklch(0.941 0.03 12.58); + --color-rose-200: oklch(0.892 0.058 10.001); + --color-rose-300: oklch(0.81 0.117 11.638); + --color-rose-400: oklch(0.712 0.194 13.428); + --color-rose-500: oklch(0.645 0.246 16.439); + --color-rose-600: oklch(0.586 0.253 17.585); + --color-rose-700: oklch(0.514 0.222 16.935); + --color-rose-800: oklch(0.455 0.188 13.697); + --color-rose-900: oklch(0.41 0.159 10.272); + --color-rose-950: oklch(0.271 0.105 12.094); + --color-slate-50: oklch(0.984 0.003 247.858); + --color-slate-100: oklch(0.968 0.007 247.896); + --color-slate-200: oklch(0.929 0.013 255.508); + --color-slate-300: oklch(0.869 0.022 252.894); + --color-slate-400: oklch(0.704 0.04 256.788); + --color-slate-500: oklch(0.554 0.046 257.417); + --color-slate-600: oklch(0.446 0.043 257.281); + --color-slate-700: oklch(0.372 0.044 257.287); + --color-slate-800: oklch(0.279 0.041 260.031); + --color-slate-900: oklch(0.208 0.042 265.755); + --color-slate-950: oklch(0.129 0.042 264.695); + --color-gray-50: oklch(0.985 0.002 247.839); + --color-gray-100: oklch(0.967 0.003 264.542); + --color-gray-200: oklch(0.928 0.006 264.531); + --color-gray-300: oklch(0.872 0.01 258.338); + --color-gray-400: oklch(0.707 0.022 261.325); + --color-gray-500: oklch(0.551 0.027 264.364); + --color-gray-600: oklch(0.446 0.03 256.802); + --color-gray-700: oklch(0.373 0.034 259.733); + --color-gray-800: oklch(0.278 0.033 256.848); + --color-gray-900: oklch(0.21 0.034 264.665); + --color-gray-950: oklch(0.13 0.028 261.692); + --color-zinc-50: oklch(0.985 0 0); + --color-zinc-100: oklch(0.967 0.001 286.375); + --color-zinc-200: oklch(0.92 0.004 286.32); + --color-zinc-300: oklch(0.871 0.006 286.286); + --color-zinc-400: oklch(0.705 0.015 286.067); + --color-zinc-500: oklch(0.552 0.016 285.938); + --color-zinc-600: oklch(0.442 0.017 285.786); + --color-zinc-700: oklch(0.37 0.013 285.805); + --color-zinc-800: oklch(0.274 0.006 286.033); + --color-zinc-900: oklch(0.21 0.006 285.885); + --color-zinc-950: oklch(0.141 0.005 285.823); + --color-neutral-50: oklch(0.985 0 0); + --color-neutral-100: oklch(0.97 0 0); + --color-neutral-200: oklch(0.922 0 0); + --color-neutral-300: oklch(0.87 0 0); + --color-neutral-400: oklch(0.708 0 0); + --color-neutral-500: oklch(0.556 0 0); + --color-neutral-600: oklch(0.439 0 0); + --color-neutral-700: oklch(0.371 0 0); + --color-neutral-800: oklch(0.269 0 0); + --color-neutral-900: oklch(0.205 0 0); + --color-neutral-950: oklch(0.145 0 0); + --color-stone-50: oklch(0.985 0.001 106.423); + --color-stone-100: oklch(0.97 0.001 106.424); + --color-stone-200: oklch(0.923 0.003 48.717); + --color-stone-300: oklch(0.869 0.005 56.366); + --color-stone-400: oklch(0.709 0.01 56.259); + --color-stone-500: oklch(0.553 0.013 58.071); + --color-stone-600: oklch(0.444 0.011 73.639); + --color-stone-700: oklch(0.374 0.01 67.558); + --color-stone-800: oklch(0.268 0.007 34.298); + --color-stone-900: oklch(0.216 0.006 56.043); + --color-stone-950: oklch(0.147 0.004 49.25); + --color-black: #000; + --color-white: #fff; + --spacing: 0.25rem; + --breakpoint-sm: 40rem; + --breakpoint-md: 48rem; + --breakpoint-lg: 64rem; + --breakpoint-xl: 80rem; + --breakpoint-2xl: 96rem; + --container-3xs: 16rem; + --container-2xs: 18rem; + --container-xs: 20rem; + --container-sm: 24rem; + --container-md: 28rem; + --container-lg: 32rem; + --container-xl: 36rem; + --container-2xl: 42rem; + --container-3xl: 48rem; + --container-4xl: 56rem; + --container-5xl: 64rem; + --container-6xl: 72rem; + --container-7xl: 80rem; + --text-xs: 0.75rem; + --text-xs--line-height: calc(1 / 0.75); + --text-sm: 0.875rem; + --text-sm--line-height: calc(1.25 / 0.875); + --text-base: 1rem; + --text-base--line-height: calc(1.5 / 1); + --text-lg: 1.125rem; + --text-lg--line-height: calc(1.75 / 1.125); + --text-xl: 1.25rem; + --text-xl--line-height: calc(1.75 / 1.25); + --text-2xl: 1.5rem; + --text-2xl--line-height: calc(2 / 1.5); + --text-3xl: 1.875rem; + --text-3xl--line-height: calc(2.25 / 1.875); + --text-4xl: 2.25rem; + --text-4xl--line-height: calc(2.5 / 2.25); + --text-5xl: 3rem; + --text-5xl--line-height: 1; + --text-6xl: 3.75rem; + --text-6xl--line-height: 1; + --text-7xl: 4.5rem; + --text-7xl--line-height: 1; + --text-8xl: 6rem; + --text-8xl--line-height: 1; + --text-9xl: 8rem; + --text-9xl--line-height: 1; + --font-weight-thin: 100; + --font-weight-extralight: 200; + --font-weight-light: 300; + --font-weight-normal: 400; + --font-weight-medium: 500; + --font-weight-semibold: 600; + --font-weight-bold: 700; + --font-weight-extrabold: 800; + --font-weight-black: 900; + --tracking-tighter: -0.05em; + --tracking-tight: -0.025em; + --tracking-normal: 0em; + --tracking-wide: 0.025em; + --tracking-wider: 0.05em; + --tracking-widest: 0.1em; + --leading-tight: 1.25; + --leading-snug: 1.375; + --leading-normal: 1.5; + --leading-relaxed: 1.625; + --leading-loose: 2; + --radius-xs: 0.125rem; + --radius-sm: 0.25rem; + --radius-md: 0.375rem; + --radius-lg: 0.5rem; + --radius-xl: 0.75rem; + --radius-2xl: 1rem; + --radius-3xl: 1.5rem; + --radius-4xl: 2rem; + --shadow-2xs: 0 1px rgb(0 0 0 / 0.05); + --shadow-xs: 0 1px 2px 0 rgb(0 0 0 / 0.05); + --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1); + --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); + --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); + --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); + --shadow-2xl: 0 25px 50px -12px rgb(0 0 0 / 0.25); + --inset-shadow-2xs: inset 0 1px rgb(0 0 0 / 0.05); + --inset-shadow-xs: inset 0 1px 1px rgb(0 0 0 / 0.05); + --inset-shadow-sm: inset 0 2px 4px rgb(0 0 0 / 0.05); + --drop-shadow-xs: 0 1px 1px rgb(0 0 0 / 0.05); + --drop-shadow-sm: 0 1px 2px rgb(0 0 0 / 0.15); + --drop-shadow-md: 0 3px 3px rgb(0 0 0 / 0.12); + --drop-shadow-lg: 0 4px 4px rgb(0 0 0 / 0.15); + --drop-shadow-xl: 0 9px 7px rgb(0 0 0 / 0.1); + --drop-shadow-2xl: 0 25px 25px rgb(0 0 0 / 0.15); + --ease-in: cubic-bezier(0.4, 0, 1, 1); + --ease-out: cubic-bezier(0, 0, 0.2, 1); + --ease-in-out: cubic-bezier(0.4, 0, 0.2, 1); + --animate-spin: spin 1s linear infinite; + --animate-ping: ping 1s cubic-bezier(0, 0, 0.2, 1) infinite; + --animate-pulse: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite; + --animate-bounce: bounce 1s infinite; + --blur-xs: 4px; + --blur-sm: 8px; + --blur-md: 12px; + --blur-lg: 16px; + --blur-xl: 24px; + --blur-2xl: 40px; + --blur-3xl: 64px; + --perspective-dramatic: 100px; + --perspective-near: 300px; + --perspective-normal: 500px; + --perspective-midrange: 800px; + --perspective-distant: 1200px; + --aspect-video: 16 / 9; + --default-transition-duration: 150ms; + --default-transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1); + --default-font-family: var(--font-sans); + --default-font-feature-settings: var(--font-sans--font-feature-settings); + --default-font-variation-settings: var(--font-sans--font-variation-settings); + --default-mono-font-family: var(--font-mono); + --default-mono-font-feature-settings: var(--font-mono--font-feature-settings); + --default-mono-font-variation-settings: var(--font-mono--font-variation-settings); + --color-rosewater: var(--rosewater); + --color-flamingo: var(--flamingo); + --color-pink: var(--pink); + --color-mauve: var(--mauve); + --color-red: var(--red); + --color-dark-red: var(--dark-red); + --color-maroon: var(--maroon); + --color-peach: var(--peach); + --color-yellow: var(--yellow); + --color-green: var(--green); + --color-teal: var(--teal); + --color-sky: var(--sky); + --color-sapphire: var(--sapphire); + --color-blue: var(--blue); + --color-lavender: var(--lavender); + --color-text: var(--text); + --color-subtext1: var(--subtext1); + --color-subtext0: var(--subtext0); + --color-overlay2: var(--overlay2); + --color-overlay1: var(--overlay1); + --color-overlay0: var(--overlay0); + --color-surface2: var(--surface2); + --color-surface1: var(--surface1); + --color-surface0: var(--surface0); + --color-base: var(--base); + --color-mantle: var(--mantle); + --color-crust: var(--crust); + } +} +@layer base { + *, ::after, ::before, ::backdrop, ::file-selector-button { + box-sizing: border-box; + margin: 0; + padding: 0; + border: 0 solid; + } + html, :host { + line-height: 1.5; + -webkit-text-size-adjust: 100%; + tab-size: 4; + font-family: var( --default-font-family, ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji' ); + font-feature-settings: var(--default-font-feature-settings, normal); + font-variation-settings: var(--default-font-variation-settings, normal); + -webkit-tap-highlight-color: transparent; + } + body { + line-height: inherit; + } + hr { + height: 0; + color: inherit; + border-top-width: 1px; + } + abbr:where([title]) { + -webkit-text-decoration: underline dotted; + text-decoration: underline dotted; + } + h1, h2, h3, h4, h5, h6 { + font-size: inherit; + font-weight: inherit; + } + a { + color: inherit; + -webkit-text-decoration: inherit; + text-decoration: inherit; + } + b, strong { + font-weight: bolder; + } + code, kbd, samp, pre { + font-family: var( --default-mono-font-family, ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace ); + font-feature-settings: var(--default-mono-font-feature-settings, normal); + font-variation-settings: var(--default-mono-font-variation-settings, normal); + font-size: 1em; + } + small { + font-size: 80%; + } + sub, sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + sub { + bottom: -0.25em; + } + sup { + top: -0.5em; + } + table { + text-indent: 0; + border-color: inherit; + border-collapse: collapse; + } + :-moz-focusring { + outline: auto; + } + progress { + vertical-align: baseline; + } + summary { + display: list-item; + } + ol, ul, menu { + list-style: none; + } + img, svg, video, canvas, audio, iframe, embed, object { + display: block; + vertical-align: middle; + } + img, video { + max-width: 100%; + height: auto; + } + button, input, select, optgroup, textarea, ::file-selector-button { + font: inherit; + font-feature-settings: inherit; + font-variation-settings: inherit; + letter-spacing: inherit; + color: inherit; + border-radius: 0; + background-color: transparent; + opacity: 1; + } + :where(select:is([multiple], [size])) optgroup { + font-weight: bolder; + } + :where(select:is([multiple], [size])) optgroup option { + padding-inline-start: 20px; + } + ::file-selector-button { + margin-inline-end: 4px; + } + ::placeholder { + opacity: 1; + color: color-mix(in oklab, currentColor 50%, transparent); + } + textarea { + resize: vertical; + } + ::-webkit-search-decoration { + -webkit-appearance: none; + } + ::-webkit-date-and-time-value { + min-height: 1lh; + text-align: inherit; + } + ::-webkit-datetime-edit { + display: inline-flex; + } + ::-webkit-datetime-edit-fields-wrapper { + padding: 0; + } + ::-webkit-datetime-edit, ::-webkit-datetime-edit-year-field, ::-webkit-datetime-edit-month-field, ::-webkit-datetime-edit-day-field, ::-webkit-datetime-edit-hour-field, ::-webkit-datetime-edit-minute-field, ::-webkit-datetime-edit-second-field, ::-webkit-datetime-edit-millisecond-field, ::-webkit-datetime-edit-meridiem-field { + padding-block: 0; + } + :-moz-ui-invalid { + box-shadow: none; + } + button, input:where([type='button'], [type='reset'], [type='submit']), ::file-selector-button { + appearance: button; + } + ::-webkit-inner-spin-button, ::-webkit-outer-spin-button { + height: auto; + } + [hidden]:where(:not([hidden='until-found'])) { + display: none !important; + } +} +@layer utilities { + .pointer-events-none { + pointer-events: none; + } + .visible { + visibility: visible; + } + .sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip: rect(0, 0, 0, 0); + white-space: nowrap; + border-width: 0; + } + .absolute { + position: absolute; + } + .relative { + position: relative; + } + .static { + position: static; + } + .inset-y-0 { + inset-block: calc(var(--spacing) * 0); + } + .start-43 { + inset-inline-start: calc(var(--spacing) * 43); + } + .end-0 { + inset-inline-end: calc(var(--spacing) * 0); + } + .end-4 { + inset-inline-end: calc(var(--spacing) * 4); + } + .top-0 { + top: calc(var(--spacing) * 0); + } + .top-4 { + top: calc(var(--spacing) * 4); + } + .right-0 { + right: calc(var(--spacing) * 0); + } + .bottom-0 { + bottom: calc(var(--spacing) * 0); + } + .left-0 { + left: calc(var(--spacing) * 0); + } + .z-1 { + z-index: 1; + } + .z-10 { + z-index: 10; + } + .z-50 { + z-index: 50; + } + .m-auto { + margin: auto; + } + .mx-5 { + margin-inline: calc(var(--spacing) * 5); + } + .mx-auto { + margin-inline: auto; + } + .ms-3 { + margin-inline-start: calc(var(--spacing) * 3); + } + .mt-0\.5 { + margin-top: calc(var(--spacing) * 0.5); + } + .mt-1 { + margin-top: calc(var(--spacing) * 1); + } + .mt-1\.5 { + margin-top: calc(var(--spacing) * 1.5); + } + .mt-2 { + margin-top: calc(var(--spacing) * 2); + } + .mt-4 { + margin-top: calc(var(--spacing) * 4); + } + .mt-5 { + margin-top: calc(var(--spacing) * 5); + } + .mt-6 { + margin-top: calc(var(--spacing) * 6); + } + .mt-7 { + margin-top: calc(var(--spacing) * 7); + } + .mt-8 { + margin-top: calc(var(--spacing) * 8); + } + .mt-10 { + margin-top: calc(var(--spacing) * 10); + } + .mt-12 { + margin-top: calc(var(--spacing) * 12); + } + .mt-20 { + margin-top: calc(var(--spacing) * 20); + } + .mt-24 { + margin-top: calc(var(--spacing) * 24); + } + .mt-25 { + margin-top: calc(var(--spacing) * 25); + } + .mr-5 { + margin-right: calc(var(--spacing) * 5); + } + .mb-2 { + margin-bottom: calc(var(--spacing) * 2); + } + .mb-auto { + margin-bottom: auto; + } + .ml-0 { + margin-left: calc(var(--spacing) * 0); + } + .ml-\[40px\] { + margin-left: 40px; + } + .ml-auto { + margin-left: auto; + } + .block { + display: block; + } + .contents { + display: contents; + } + .flex { + display: flex; + } + .grid { + display: grid; + } + .hidden { + display: none; + } + .inline { + display: inline; + } + .inline-block { + display: inline-block; + } + .inline-flex { + display: inline-flex; + } + .table { + display: table; + } + .aspect-\[2\/3\] { + aspect-ratio: 2/3; + } + .size-5 { + width: calc(var(--spacing) * 5); + height: calc(var(--spacing) * 5); + } + .size-6 { + width: calc(var(--spacing) * 6); + height: calc(var(--spacing) * 6); + } + .h-16 { + height: calc(var(--spacing) * 16); + } + .h-20 { + height: calc(var(--spacing) * 20); + } + .h-full { + height: 100%; + } + .h-screen { + height: 100vh; + } + .min-h-100 { + min-height: calc(var(--spacing) * 100); + } + .w-20 { + width: calc(var(--spacing) * 20); + } + .w-26 { + width: calc(var(--spacing) * 26); + } + .w-36 { + width: calc(var(--spacing) * 36); + } + .w-40 { + width: calc(var(--spacing) * 40); + } + .w-50 { + width: calc(var(--spacing) * 50); + } + .w-60 { + width: calc(var(--spacing) * 60); + } + .w-82 { + width: calc(var(--spacing) * 82); + } + .w-\[90\%\] { + width: 90%; + } + .w-\[130px\] { + width: 130px; + } + .w-\[154px\] { + width: 154px; + } + .w-fit { + width: fit-content; + } + .w-full { + width: 100%; + } + .max-w-4xl { + max-width: var(--container-4xl); + } + .max-w-100 { + max-width: calc(var(--spacing) * 100); + } + .max-w-150 { + max-width: calc(var(--spacing) * 150); + } + .max-w-200 { + max-width: calc(var(--spacing) * 200); + } + .max-w-md { + max-width: var(--container-md); + } + .max-w-screen-xl { + max-width: var(--breakpoint-xl); + } + .flex-1 { + flex: 1; + } + .shrink-0 { + flex-shrink: 0; + } + .flex-grow { + flex-grow: 1; + } + .translate-x-0 { + --tw-translate-x: calc(var(--spacing) * 0); + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .translate-x-\[100\%\] { + --tw-translate-x: 100%; + translate: var(--tw-translate-x) var(--tw-translate-y); + } + .transform { + transform: var(--tw-rotate-x) var(--tw-rotate-y) var(--tw-rotate-z) var(--tw-skew-x) var(--tw-skew-y); + } + .flex-col { + flex-direction: column; + } + .flex-wrap { + flex-wrap: wrap; + } + .place-content-center { + place-content: center; + } + .items-center { + align-items: center; + } + .items-start { + align-items: flex-start; + } + .justify-around { + justify-content: space-around; + } + .justify-between { + justify-content: space-between; + } + .justify-center { + justify-content: center; + } + .justify-end { + justify-content: flex-end; + } + .gap-2 { + gap: calc(var(--spacing) * 2); + } + .gap-6 { + gap: calc(var(--spacing) * 6); + } + .gap-8 { + gap: calc(var(--spacing) * 8); + } + .gap-x-1 { + column-gap: calc(var(--spacing) * 1); + } + .gap-x-2 { + column-gap: calc(var(--spacing) * 2); + } + .space-y-1 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse))); + } + } + .space-y-4 { + :where(& > :not(:last-child)) { + --tw-space-y-reverse: 0; + margin-block-start: calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse)); + margin-block-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse))); + } + } + .gap-y-4 { + row-gap: calc(var(--spacing) * 4); + } + .space-x-2 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 2) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-x-reverse))); + } + } + .space-x-4 { + :where(& > :not(:last-child)) { + --tw-space-x-reverse: 0; + margin-inline-start: calc(calc(var(--spacing) * 4) * var(--tw-space-x-reverse)); + margin-inline-end: calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-x-reverse))); + } + } + .divide-y { + :where(& > :not(:last-child)) { + --tw-divide-y-reverse: 0; + border-bottom-style: var(--tw-border-style); + border-top-style: var(--tw-border-style); + border-top-width: calc(1px * var(--tw-divide-y-reverse)); + border-bottom-width: calc(1px * calc(1 - var(--tw-divide-y-reverse))); + } + } + .divide-surface2 { + :where(& > :not(:last-child)) { + border-color: var(--surface2); + } + } + .overflow-hidden { + overflow: hidden; + } + .overflow-x-hidden { + overflow-x: hidden; + } + .rounded { + border-radius: 0.25rem; + } + .rounded-full { + border-radius: calc(infinity * 1px); + } + .rounded-lg { + border-radius: var(--radius-lg); + } + .rounded-sm { + border-radius: var(--radius-sm); + } + .rounded-xl { + border-radius: var(--radius-xl); + } + .rounded-l-xl { + border-top-left-radius: var(--radius-xl); + border-bottom-left-radius: var(--radius-xl); + } + .border { + border-style: var(--tw-border-style); + border-width: 1px; + } + .border-e { + border-inline-end-style: var(--tw-border-style); + border-inline-end-width: 1px; + } + .border-b { + border-bottom-style: var(--tw-border-style); + border-bottom-width: 1px; + } + .border-gray-200 { + border-color: var(--color-gray-200); + } + .border-overlay0 { + border-color: var(--overlay0); + } + .border-surface1 { + border-color: var(--surface1); + } + .border-surface2 { + border-color: var(--surface2); + } + .border-transparent { + border-color: transparent; + } + .bg-base { + background-color: var(--base); + } + .bg-blue { + background-color: var(--blue); + } + .bg-crust { + background-color: var(--crust); + } + .bg-dark-red { + background-color: var(--dark-red); + } + .bg-green { + background-color: var(--green); + } + .bg-mantle { + background-color: var(--mantle); + } + .bg-mauve { + background-color: var(--mauve); + } + .bg-overlay0 { + background-color: var(--overlay0); + } + .bg-overlay0\/55 { + background-color: color-mix(in oklab, var(--overlay0) 55%, transparent); + } + .bg-overlay2 { + background-color: var(--overlay2); + } + .bg-sapphire { + background-color: var(--sapphire); + } + .bg-surface0 { + background-color: var(--surface0); + } + .bg-surface2 { + background-color: var(--surface2); + } + .bg-teal { + background-color: var(--teal); + } + .object-cover { + object-fit: cover; + } + .p-2 { + padding: calc(var(--spacing) * 2); + } + .p-2\.5 { + padding: calc(var(--spacing) * 2.5); + } + .p-4 { + padding: calc(var(--spacing) * 4); + } + .p-5 { + padding: calc(var(--spacing) * 5); + } + .px-2 { + padding-inline: calc(var(--spacing) * 2); + } + .px-4 { + padding-inline: calc(var(--spacing) * 4); + } + .px-5 { + padding-inline: calc(var(--spacing) * 5); + } + .py-1 { + padding-block: calc(var(--spacing) * 1); + } + .py-2 { + padding-block: calc(var(--spacing) * 2); + } + .py-3 { + padding-block: calc(var(--spacing) * 3); + } + .py-6 { + padding-block: calc(var(--spacing) * 6); + } + .py-8 { + padding-block: calc(var(--spacing) * 8); + } + .pe-2 { + padding-inline-end: calc(var(--spacing) * 2); + } + .pe-3 { + padding-inline-end: calc(var(--spacing) * 3); + } + .pt-3 { + padding-top: calc(var(--spacing) * 3); + } + .pt-9 { + padding-top: calc(var(--spacing) * 9); + } + .pr-2 { + padding-right: calc(var(--spacing) * 2); + } + .pb-2 { + padding-bottom: calc(var(--spacing) * 2); + } + .pb-6 { + padding-bottom: calc(var(--spacing) * 6); + } + .pl-5 { + padding-left: calc(var(--spacing) * 5); + } + .text-center { + text-align: center; + } + .text-left { + text-align: left; + } + .text-2xl { + font-size: var(--text-2xl); + line-height: var(--tw-leading, var(--text-2xl--line-height)); + } + .text-3xl { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } + .text-4xl { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + .text-9xl { + font-size: var(--text-9xl); + line-height: var(--tw-leading, var(--text-9xl--line-height)); + } + .text-lg { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + .text-sm { + font-size: var(--text-sm); + line-height: var(--tw-leading, var(--text-sm--line-height)); + } + .text-xl { + font-size: var(--text-xl); + line-height: var(--tw-leading, var(--text-xl--line-height)); + } + .text-xs { + font-size: var(--text-xs); + line-height: var(--tw-leading, var(--text-xs--line-height)); + } + .leading-relaxed { + --tw-leading: var(--leading-relaxed); + line-height: var(--leading-relaxed); + } + .font-bold { + --tw-font-weight: var(--font-weight-bold); + font-weight: var(--font-weight-bold); + } + .font-medium { + --tw-font-weight: var(--font-weight-medium); + font-weight: var(--font-weight-medium); + } + .font-semibold { + --tw-font-weight: var(--font-weight-semibold); + font-weight: var(--font-weight-semibold); + } + .tracking-tight { + --tw-tracking: var(--tracking-tight); + letter-spacing: var(--tracking-tight); + } + .text-blue { + color: var(--blue); + } + .text-crust { + color: var(--crust); + } + .text-mantle { + color: var(--mantle); + } + .text-overlay0 { + color: var(--overlay0); + } + .text-overlay2 { + color: var(--overlay2); + } + .text-red { + color: var(--red); + } + .text-subtext0 { + color: var(--subtext0); + } + .text-subtext1 { + color: var(--subtext1); + } + .text-text { + color: var(--text); + } + .uppercase { + text-transform: uppercase; + } + .italic { + font-style: italic; + } + .underline { + text-decoration-line: underline; + } + .decoration-2 { + text-decoration-thickness: 2px; + } + .opacity-0 { + opacity: 0%; + } + .opacity-100 { + opacity: 100%; + } + .shadow-2xl { + --tw-shadow: 0 25px 50px -12px var(--tw-shadow-color, rgb(0 0 0 / 0.25)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-lg { + --tw-shadow: 0 10px 15px -3px var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 4px 6px -4px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-sm { + --tw-shadow: 0 1px 3px 0 var(--tw-shadow-color, rgb(0 0 0 / 0.1)), 0 1px 2px -1px var(--tw-shadow-color, rgb(0 0 0 / 0.1)); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + .shadow-black { + --tw-shadow-color: var(--color-black); + } + .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; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .transition-all { + transition-property: all; + transition-timing-function: var(--tw-ease, var(--default-transition-timing-function)); + transition-duration: var(--tw-duration, var(--default-transition-duration)); + } + .duration-200 { + --tw-duration: 200ms; + transition-duration: 200ms; + } + .duration-300 { + --tw-duration: 300ms; + transition-duration: 300ms; + } + .ease-in-out { + --tw-ease: var(--ease-in-out); + transition-timing-function: var(--ease-in-out); + } + .before\:me-6 { + &::before { + content: var(--tw-content); + margin-inline-end: calc(var(--spacing) * 6); + } + } + .before\:flex-1 { + &::before { + content: var(--tw-content); + flex: 1; + } + } + .before\:border-t { + &::before { + content: var(--tw-content); + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + } + .before\:border-overlay1 { + &::before { + content: var(--tw-content); + border-color: var(--overlay1); + } + } + .after\:ms-6 { + &::after { + content: var(--tw-content); + margin-inline-start: calc(var(--spacing) * 6); + } + } + .after\:flex-1 { + &::after { + content: var(--tw-content); + flex: 1; + } + } + .after\:border-t { + &::after { + content: var(--tw-content); + border-top-style: var(--tw-border-style); + border-top-width: 1px; + } + } + .after\:border-overlay1 { + &::after { + content: var(--tw-content); + border-color: var(--overlay1); + } + } + .hover\:cursor-pointer { + &:hover { + @media (hover: hover) { + cursor: pointer; + } + } + } + .hover\:bg-blue\/75 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--blue) 75%, transparent); + } + } + } + .hover\:bg-crust { + &:hover { + @media (hover: hover) { + background-color: var(--crust); + } + } + } + .hover\:bg-green\/75 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--green) 75%, transparent); + } + } + } + .hover\:bg-mantle { + &:hover { + @media (hover: hover) { + background-color: var(--mantle); + } + } + } + .hover\:bg-mauve\/75 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--mauve) 75%, transparent); + } + } + } + .hover\:bg-red\/25 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--red) 25%, transparent); + } + } + } + .hover\:bg-sapphire\/75 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--sapphire) 75%, transparent); + } + } + } + .hover\:bg-surface1 { + &:hover { + @media (hover: hover) { + background-color: var(--surface1); + } + } + } + .hover\:bg-surface2 { + &:hover { + @media (hover: hover) { + background-color: var(--surface2); + } + } + } + .hover\:bg-teal\/75 { + &:hover { + @media (hover: hover) { + background-color: color-mix(in oklab, var(--teal) 75%, transparent); + } + } + } + .hover\:text-green { + &:hover { + @media (hover: hover) { + color: var(--green); + } + } + } + .hover\:text-overlay2\/75 { + &:hover { + @media (hover: hover) { + color: color-mix(in oklab, var(--overlay2) 75%, transparent); + } + } + } + .hover\:text-subtext0 { + &:hover { + @media (hover: hover) { + color: var(--subtext0); + } + } + } + .hover\:text-subtext1 { + &:hover { + @media (hover: hover) { + color: var(--subtext1); + } + } + } + .hover\:underline { + &:hover { + @media (hover: hover) { + text-decoration-line: underline; + } + } + } + .focus\:border-blue { + &:focus { + border-color: var(--blue); + } + } + .focus\:underline { + &:focus { + text-decoration-line: underline; + } + } + .focus\:ring-2 { + &:focus { + --tw-ring-shadow: var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color, currentColor); + box-shadow: var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow); + } + } + .focus\:ring-blue { + &:focus { + --tw-ring-color: var(--blue); + } + } + .focus\:ring-blue-500 { + &:focus { + --tw-ring-color: var(--color-blue-500); + } + } + .focus\:outline-none { + &:focus { + --tw-outline-style: none; + outline-style: none; + } + } + .disabled\:pointer-events-none { + &:disabled { + pointer-events: none; + } + } + .disabled\:cursor-default { + &:disabled { + cursor: default; + } + } + .disabled\:bg-blue\/60 { + &:disabled { + background-color: color-mix(in oklab, var(--blue) 60%, transparent); + } + } + .disabled\:bg-green\/60 { + &:disabled { + background-color: color-mix(in oklab, var(--green) 60%, transparent); + } + } + .disabled\:opacity-50 { + &:disabled { + opacity: 50%; + } + } + .sm\:start-68 { + @media (width >= 40rem) { + inset-inline-start: calc(var(--spacing) * 68); + } + } + .sm\:end-6 { + @media (width >= 40rem) { + inset-inline-end: calc(var(--spacing) * 6); + } + } + .sm\:mt-0 { + @media (width >= 40rem) { + margin-top: calc(var(--spacing) * 0); + } + } + .sm\:ml-2 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 2); + } + } + .sm\:ml-5 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 5); + } + } + .sm\:ml-25 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 25); + } + } + .sm\:ml-26 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 26); + } + } + .sm\:ml-43 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 43); + } + } + .sm\:ml-45 { + @media (width >= 40rem) { + margin-left: calc(var(--spacing) * 45); + } + } + .sm\:block { + @media (width >= 40rem) { + display: block; + } + } + .sm\:flex { + @media (width >= 40rem) { + display: flex; + } + } + .sm\:hidden { + @media (width >= 40rem) { + display: none; + } + } + .sm\:inline { + @media (width >= 40rem) { + display: inline; + } + } + .sm\:flex-row { + @media (width >= 40rem) { + flex-direction: row; + } + } + .sm\:items-center { + @media (width >= 40rem) { + align-items: center; + } + } + .sm\:justify-between { + @media (width >= 40rem) { + justify-content: space-between; + } + } + .sm\:gap-2 { + @media (width >= 40rem) { + gap: calc(var(--spacing) * 2); + } + } + .sm\:p-7 { + @media (width >= 40rem) { + padding: calc(var(--spacing) * 7); + } + } + .sm\:px-6 { + @media (width >= 40rem) { + padding-inline: calc(var(--spacing) * 6); + } + } + .sm\:pt-2 { + @media (width >= 40rem) { + padding-top: calc(var(--spacing) * 2); + } + } + .sm\:text-4xl { + @media (width >= 40rem) { + font-size: var(--text-4xl); + line-height: var(--tw-leading, var(--text-4xl--line-height)); + } + } + .md\:mx-auto { + @media (width >= 48rem) { + margin-inline: auto; + } + } + .md\:mt-0 { + @media (width >= 48rem) { + margin-top: calc(var(--spacing) * 0); + } + } + .md\:ml-\[200px\] { + @media (width >= 48rem) { + margin-left: 200px; + } + } + .md\:flex { + @media (width >= 48rem) { + display: flex; + } + } + .md\:hidden { + @media (width >= 48rem) { + display: none; + } + } + .md\:h-30 { + @media (width >= 48rem) { + height: calc(var(--spacing) * 30); + } + } + .md\:w-30 { + @media (width >= 48rem) { + width: calc(var(--spacing) * 30); + } + } + .md\:w-\[180px\] { + @media (width >= 48rem) { + width: 180px; + } + } + .md\:w-\[300px\] { + @media (width >= 48rem) { + width: 300px; + } + } + .md\:gap-8 { + @media (width >= 48rem) { + gap: calc(var(--spacing) * 8); + } + } + .md\:rounded-lg { + @media (width >= 48rem) { + border-radius: var(--radius-lg); + } + } + .md\:rounded-md { + @media (width >= 48rem) { + border-radius: var(--radius-md); + } + } + .md\:bg-surface0 { + @media (width >= 48rem) { + background-color: var(--surface0); + } + } + .md\:p-2 { + @media (width >= 48rem) { + padding: calc(var(--spacing) * 2); + } + } + .md\:px-0 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 0); + } + } + .md\:px-5 { + @media (width >= 48rem) { + padding-inline: calc(var(--spacing) * 5); + } + } + .md\:pt-5 { + @media (width >= 48rem) { + padding-top: calc(var(--spacing) * 5); + } + } + .md\:text-3xl { + @media (width >= 48rem) { + font-size: var(--text-3xl); + line-height: var(--tw-leading, var(--text-3xl--line-height)); + } + } + .md\:text-lg { + @media (width >= 48rem) { + font-size: var(--text-lg); + line-height: var(--tw-leading, var(--text-lg--line-height)); + } + } + .lg\:end-8 { + @media (width >= 64rem) { + inset-inline-end: calc(var(--spacing) * 8); + } + } + .lg\:mt-0 { + @media (width >= 64rem) { + margin-top: calc(var(--spacing) * 0); + } + } + .lg\:flex { + @media (width >= 64rem) { + display: flex; + } + } + .lg\:inline { + @media (width >= 64rem) { + display: inline; + } + } + .lg\:items-end { + @media (width >= 64rem) { + align-items: flex-end; + } + } + .lg\:justify-between { + @media (width >= 64rem) { + justify-content: space-between; + } + } + .lg\:justify-end { + @media (width >= 64rem) { + justify-content: flex-end; + } + } + .lg\:justify-start { + @media (width >= 64rem) { + justify-content: flex-start; + } + } + .lg\:gap-12 { + @media (width >= 64rem) { + gap: calc(var(--spacing) * 12); + } + } + .lg\:px-8 { + @media (width >= 64rem) { + padding-inline: calc(var(--spacing) * 8); + } + } + .lg\:text-6xl { + @media (width >= 64rem) { + font-size: var(--text-6xl); + line-height: var(--tw-leading, var(--text-6xl--line-height)); + } + } +} +[x-cloak] { + display: none !important; +} +:root { + --rosewater: hsl(11, 59%, 67%); + --flamingo: hsl(0, 60%, 67%); + --pink: hsl(316, 73%, 69%); + --mauve: hsl(266, 85%, 58%); + --red: hsl(347, 87%, 44%); + --dark-red: hsl(343, 50%, 82%); + --maroon: hsl(355, 76%, 59%); + --peach: hsl(22, 99%, 52%); + --yellow: hsl(35, 77%, 49%); + --green: hsl(109, 58%, 40%); + --teal: hsl(183, 74%, 35%); + --sky: hsl(197, 97%, 46%); + --sapphire: hsl(189, 70%, 42%); + --blue: hsl(220, 91%, 54%); + --lavender: hsl(231, 97%, 72%); + --text: hsl(234, 16%, 35%); + --subtext1: hsl(233, 13%, 41%); + --subtext0: hsl(233, 10%, 47%); + --overlay2: hsl(232, 10%, 53%); + --overlay1: hsl(231, 10%, 59%); + --overlay0: hsl(228, 11%, 65%); + --surface2: hsl(227, 12%, 71%); + --surface1: hsl(225, 14%, 77%); + --surface0: hsl(223, 16%, 83%); + --base: hsl(220, 23%, 95%); + --mantle: hsl(220, 22%, 92%); + --crust: hsl(220, 21%, 89%); +} +.dark { + --rosewater: hsl(10, 56%, 91%); + --flamingo: hsl(0, 59%, 88%); + --pink: hsl(316, 72%, 86%); + --mauve: hsl(267, 84%, 81%); + --red: hsl(343, 81%, 75%); + --dark-red: hsl(316, 19%, 27%); + --maroon: hsl(350, 65%, 77%); + --peach: hsl(23, 92%, 75%); + --yellow: hsl(41, 86%, 83%); + --green: hsl(115, 54%, 76%); + --teal: hsl(170, 57%, 73%); + --sky: hsl(189, 71%, 73%); + --sapphire: hsl(199, 76%, 69%); + --blue: hsl(217, 92%, 76%); + --lavender: hsl(232, 97%, 85%); + --text: hsl(226, 64%, 88%); + --subtext1: hsl(227, 35%, 80%); + --subtext0: hsl(228, 24%, 72%); + --overlay2: hsl(228, 17%, 64%); + --overlay1: hsl(230, 13%, 55%); + --overlay0: hsl(231, 11%, 47%); + --surface2: hsl(233, 12%, 39%); + --surface1: hsl(234, 13%, 31%); + --surface0: hsl(237, 16%, 23%); + --base: hsl(240, 21%, 15%); + --mantle: hsl(240, 21%, 12%); + --crust: hsl(240, 23%, 9%); +} +.ubuntu-mono-regular { + font-family: "Ubuntu Mono", serif; + font-weight: 400; + font-style: normal; +} +.ubuntu-mono-bold { + font-family: "Ubuntu Mono", serif; + font-weight: 700; + font-style: normal; +} +.ubuntu-mono-regular-italic { + font-family: "Ubuntu Mono", serif; + font-weight: 400; + font-style: italic; +} +.ubuntu-mono-bold-italic { + font-family: "Ubuntu Mono", serif; + font-weight: 700; + font-style: italic; +} +@keyframes spin { + to { + transform: rotate(360deg); + } +} +@keyframes ping { + 75%, 100% { + transform: scale(2); + opacity: 0; + } +} +@keyframes pulse { + 50% { + opacity: 0.5; + } +} +@keyframes bounce { + 0%, 100% { + transform: translateY(-25%); + animation-timing-function: cubic-bezier(0.8, 0, 1, 1); + } + 50% { + transform: none; + animation-timing-function: cubic-bezier(0, 0, 0.2, 1); + } +} +@property --tw-translate-x { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-y { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-translate-z { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-rotate-x { + syntax: "*"; + inherits: false; + initial-value: rotateX(0); +} +@property --tw-rotate-y { + syntax: "*"; + inherits: false; + initial-value: rotateY(0); +} +@property --tw-rotate-z { + syntax: "*"; + inherits: false; + initial-value: rotateZ(0); +} +@property --tw-skew-x { + syntax: "*"; + inherits: false; + initial-value: skewX(0); +} +@property --tw-skew-y { + syntax: "*"; + inherits: false; + initial-value: skewY(0); +} +@property --tw-space-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-space-x-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-divide-y-reverse { + syntax: "*"; + inherits: false; + initial-value: 0; +} +@property --tw-border-style { + syntax: "*"; + inherits: false; + initial-value: solid; +} +@property --tw-leading { + syntax: "*"; + inherits: false; +} +@property --tw-font-weight { + syntax: "*"; + inherits: false; +} +@property --tw-tracking { + syntax: "*"; + inherits: false; +} +@property --tw-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-shadow-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-inset-ring-color { + syntax: "*"; + inherits: false; +} +@property --tw-inset-ring-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-ring-inset { + syntax: "*"; + inherits: false; +} +@property --tw-ring-offset-width { + syntax: ""; + inherits: false; + initial-value: 0px; +} +@property --tw-ring-offset-color { + syntax: "*"; + inherits: false; + initial-value: #fff; +} +@property --tw-ring-offset-shadow { + syntax: "*"; + inherits: false; + initial-value: 0 0 #0000; +} +@property --tw-duration { + syntax: "*"; + inherits: false; +} +@property --tw-ease { + syntax: "*"; + inherits: false; +} +@property --tw-content { + syntax: "*"; + initial-value: ""; + inherits: false; +} diff --git a/static/favicon.ico b/pkg/embedfs/files/favicon.ico similarity index 100% rename from static/favicon.ico rename to pkg/embedfs/files/favicon.ico diff --git a/jwt/create.go b/pkg/jwt/create.go similarity index 94% rename from jwt/create.go rename to pkg/jwt/create.go index 617abce..1ba66a4 100644 --- a/jwt/create.go +++ b/pkg/jwt/create.go @@ -3,8 +3,8 @@ package jwt import ( "time" - "projectreshoot/config" - "projectreshoot/db" + "projectreshoot/internal/models" + "projectreshoot/pkg/config" "github.com/golang-jwt/jwt" "github.com/google/uuid" @@ -14,7 +14,7 @@ import ( // Generates an access token for the provided user func GenerateAccessToken( config *config.Config, - user *db.User, + user *models.User, fresh bool, rememberMe bool, ) (tokenStr string, exp int64, err error) { @@ -54,7 +54,7 @@ func GenerateAccessToken( // Generates a refresh token for the provided user func GenerateRefreshToken( config *config.Config, - user *db.User, + user *models.User, rememberMe bool, ) (tokenStr string, exp int64, err error) { issuedAt := time.Now().Unix() diff --git a/jwt/parse.go b/pkg/jwt/parse.go similarity index 98% rename from jwt/parse.go rename to pkg/jwt/parse.go index 0446e85..fb56778 100644 --- a/jwt/parse.go +++ b/pkg/jwt/parse.go @@ -5,8 +5,8 @@ import ( "fmt" "time" - "projectreshoot/config" - "projectreshoot/db" + "projectreshoot/pkg/config" + "projectreshoot/pkg/db" "github.com/golang-jwt/jwt" "github.com/google/uuid" @@ -19,7 +19,7 @@ import ( func ParseAccessToken( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx db.SafeTX, tokenString string, ) (*AccessToken, error) { if tokenString == "" { @@ -92,7 +92,7 @@ func ParseAccessToken( func ParseRefreshToken( config *config.Config, ctx context.Context, - tx *db.SafeTX, + tx db.SafeTX, tokenString string, ) (*RefreshToken, error) { if tokenString == "" { diff --git a/jwt/revoke.go b/pkg/jwt/revoke.go similarity index 77% rename from jwt/revoke.go rename to pkg/jwt/revoke.go index 016f33e..64f4020 100644 --- a/jwt/revoke.go +++ b/pkg/jwt/revoke.go @@ -2,13 +2,13 @@ package jwt import ( "context" - "projectreshoot/db" + "projectreshoot/pkg/db" "github.com/pkg/errors" ) // Revoke a token by adding it to the database -func RevokeToken(ctx context.Context, tx *db.SafeTX, t Token) error { +func RevokeToken(ctx context.Context, tx *db.SafeWTX, t Token) error { jti := t.GetJTI() exp := t.GetEXP() query := `INSERT INTO jwtblacklist (jti, exp) VALUES (?, ?)` @@ -20,7 +20,7 @@ func RevokeToken(ctx context.Context, tx *db.SafeTX, t Token) error { } // Check if a token has been revoked. Returns true if not revoked. -func CheckTokenNotRevoked(ctx context.Context, tx *db.SafeTX, t Token) (bool, error) { +func CheckTokenNotRevoked(ctx context.Context, tx db.SafeTX, t Token) (bool, error) { jti := t.GetJTI() query := `SELECT 1 FROM jwtblacklist WHERE jti = ? LIMIT 1` rows, err := tx.Query(ctx, query, jti) diff --git a/jwt/tokens.go b/pkg/jwt/tokens.go similarity index 79% rename from jwt/tokens.go rename to pkg/jwt/tokens.go index ae5d97a..26bdcc8 100644 --- a/jwt/tokens.go +++ b/pkg/jwt/tokens.go @@ -2,7 +2,8 @@ package jwt import ( "context" - "projectreshoot/db" + "projectreshoot/internal/models" + "projectreshoot/pkg/db" "github.com/google/uuid" "github.com/pkg/errors" @@ -12,7 +13,7 @@ type Token interface { GetJTI() uuid.UUID GetEXP() int64 GetScope() string - GetUser(ctx context.Context, tx *db.SafeTX) (*db.User, error) + GetUser(ctx context.Context, tx db.SafeTX) (*models.User, error) } // Access token @@ -38,15 +39,15 @@ type RefreshToken struct { Scope string // Should be "refresh" } -func (a AccessToken) GetUser(ctx context.Context, tx *db.SafeTX) (*db.User, error) { - user, err := db.GetUserFromID(ctx, tx, a.SUB) +func (a AccessToken) GetUser(ctx context.Context, tx db.SafeTX) (*models.User, error) { + user, err := models.GetUserFromID(ctx, tx, a.SUB) if err != nil { return nil, errors.Wrap(err, "db.GetUserFromID") } return user, nil } -func (r RefreshToken) GetUser(ctx context.Context, tx *db.SafeTX) (*db.User, error) { - user, err := db.GetUserFromID(ctx, tx, r.SUB) +func (r RefreshToken) GetUser(ctx context.Context, tx db.SafeTX) (*models.User, error) { + user, err := models.GetUserFromID(ctx, tx, r.SUB) if err != nil { return nil, errors.Wrap(err, "db.GetUserFromID") } diff --git a/logging/logger.go b/pkg/logging/logger.go similarity index 100% rename from logging/logger.go rename to pkg/logging/logger.go diff --git a/tests/config.go b/pkg/tests/config.go similarity index 91% rename from tests/config.go rename to pkg/tests/config.go index 28f53d5..757d3b0 100644 --- a/tests/config.go +++ b/pkg/tests/config.go @@ -2,7 +2,7 @@ package tests import ( "os" - "projectreshoot/config" + "projectreshoot/pkg/config" "github.com/pkg/errors" ) diff --git a/pkg/tests/database.go b/pkg/tests/database.go new file mode 100644 index 0000000..d5856a6 --- /dev/null +++ b/pkg/tests/database.go @@ -0,0 +1,116 @@ +package tests + +import ( + "context" + "database/sql" + "fmt" + "io/fs" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/pressly/goose/v3" + + _ "modernc.org/sqlite" +) + +func findMigrations() (*fs.FS, error) { + dir, err := os.Getwd() + if err != nil { + return nil, err + } + + for { + if _, err := os.Stat(filepath.Join(dir, "Makefile")); err == nil { + migrationsdir := os.DirFS(filepath.Join(dir, "cmd", "migrate", "migrations")) + return &migrationsdir, nil + } + + parent := filepath.Dir(dir) + if parent == dir { // Reached root + return nil, errors.New("Unable to locate migrations directory") + } + dir = parent + } +} + +func findTestData() (string, error) { + dir, err := os.Getwd() + if err != nil { + return "", err + } + + for { + if _, err := os.Stat(filepath.Join(dir, "Makefile")); err == nil { + return filepath.Join(dir, "pkg", "tests", "testdata.sql"), nil + } + + parent := filepath.Dir(dir) + if parent == dir { // Reached root + return "", errors.New("Unable to locate test data") + } + dir = parent + } +} + +func migrateTestDB(wconn *sql.DB, version int64) error { + migrations, err := findMigrations() + if err != nil { + return errors.Wrap(err, "findMigrations") + } + provider, err := goose.NewProvider(goose.DialectSQLite3, wconn, *migrations) + if err != nil { + return errors.Wrap(err, "goose.NewProvider") + } + ctx := context.Background() + if _, err := provider.UpTo(ctx, version); err != nil { + return errors.Wrap(err, "provider.UpTo") + } + return nil +} + +func loadTestData(wconn *sql.DB) error { + dataPath, err := findTestData() + if err != nil { + return errors.Wrap(err, "findSchema") + } + sqlBytes, err := os.ReadFile(dataPath) + if err != nil { + return errors.Wrap(err, "os.ReadFile") + } + dataSQL := string(sqlBytes) + + _, err = wconn.Exec(dataSQL) + if err != nil { + return errors.Wrap(err, "tx.Exec") + } + return nil +} + +// Returns two db connection handles. First is a readwrite connection, second +// is a read only connection +func SetupTestDB(version int64) (*sql.DB, *sql.DB, error) { + opts := "_journal_mode=WAL&_synchronous=NORMAL&_txlock=IMMEDIATE" + file := fmt.Sprintf("file::memory:?cache=shared&%s", opts) + wconn, err := sql.Open("sqlite", file) + if err != nil { + return nil, nil, errors.Wrap(err, "sql.Open") + } + + err = migrateTestDB(wconn, version) + if err != nil { + return nil, nil, errors.Wrap(err, "migrateTestDB") + } + err = loadTestData(wconn) + if err != nil { + return nil, nil, errors.Wrap(err, "loadTestData") + } + + opts = "_synchronous=NORMAL&mode=ro" + file = fmt.Sprintf("file::memory:?cache=shared&%s", opts) + rconn, err := sql.Open("sqlite", file) + if err != nil { + return nil, nil, errors.Wrap(err, "sql.Open") + } + return wconn, rconn, nil +} diff --git a/tests/logger.go b/pkg/tests/logger.go similarity index 100% rename from tests/logger.go rename to pkg/tests/logger.go diff --git a/tests/testdata.sql b/pkg/tests/testdata.sql similarity index 100% rename from tests/testdata.sql rename to pkg/tests/testdata.sql diff --git a/tmdb/config.go b/pkg/tmdb/config.go similarity index 100% rename from tmdb/config.go rename to pkg/tmdb/config.go diff --git a/tmdb/credits.go b/pkg/tmdb/credits.go similarity index 100% rename from tmdb/credits.go rename to pkg/tmdb/credits.go diff --git a/tmdb/crew_functions.go b/pkg/tmdb/crew_functions.go similarity index 100% rename from tmdb/crew_functions.go rename to pkg/tmdb/crew_functions.go diff --git a/tmdb/movie.go b/pkg/tmdb/movie.go similarity index 100% rename from tmdb/movie.go rename to pkg/tmdb/movie.go diff --git a/tmdb/movie_functions.go b/pkg/tmdb/movie_functions.go similarity index 100% rename from tmdb/movie_functions.go rename to pkg/tmdb/movie_functions.go diff --git a/tmdb/request.go b/pkg/tmdb/request.go similarity index 100% rename from tmdb/request.go rename to pkg/tmdb/request.go diff --git a/tmdb/search.go b/pkg/tmdb/search.go similarity index 100% rename from tmdb/search.go rename to pkg/tmdb/search.go diff --git a/tmdb/structs.go b/pkg/tmdb/structs.go similarity index 94% rename from tmdb/structs.go rename to pkg/tmdb/structs.go index 0929dbd..3a68d08 100644 --- a/tmdb/structs.go +++ b/pkg/tmdb/structs.go @@ -1,9 +1,5 @@ package tmdb -import ( -// "encoding/json" -) - type Genre struct { ID int `json:"id"` Name string `json:"name"` diff --git a/tests/database.go b/tests/database.go deleted file mode 100644 index db417f9..0000000 --- a/tests/database.go +++ /dev/null @@ -1,90 +0,0 @@ -package tests - -import ( - "context" - "database/sql" - "io/fs" - "os" - "path/filepath" - - "github.com/pkg/errors" - "github.com/pressly/goose/v3" - - _ "modernc.org/sqlite" -) - -func findMigrations() (*fs.FS, error) { - dir, err := os.Getwd() - if err != nil { - return nil, err - } - - for { - if _, err := os.Stat(filepath.Join(dir, "main.go")); err == nil { - migrationsdir := os.DirFS(filepath.Join(dir, "migrate", "migrations")) - return &migrationsdir, nil - } - - parent := filepath.Dir(dir) - if parent == dir { // Reached root - return nil, errors.New("Unable to locate migrations directory") - } - dir = parent - } -} - -func findTestData() (string, error) { - dir, err := os.Getwd() - if err != nil { - return "", err - } - - for { - if _, err := os.Stat(filepath.Join(dir, "main.go")); err == nil { - return filepath.Join(dir, "tests", "testdata.sql"), nil - } - - parent := filepath.Dir(dir) - if parent == dir { // Reached root - return "", errors.New("Unable to locate test data") - } - dir = parent - } -} - -func SetupTestDB(version int64) (*sql.DB, error) { - conn, err := sql.Open("sqlite", "file::memory:?cache=shared") - if err != nil { - return nil, errors.Wrap(err, "sql.Open") - } - - migrations, err := findMigrations() - if err != nil { - return nil, errors.Wrap(err, "findMigrations") - } - provider, err := goose.NewProvider(goose.DialectSQLite3, conn, *migrations) - if err != nil { - return nil, errors.Wrap(err, "goose.NewProvider") - } - ctx := context.Background() - if _, err := provider.UpTo(ctx, version); err != nil { - return nil, errors.Wrap(err, "provider.UpTo") - } - - // Load the test data - dataPath, err := findTestData() - if err != nil { - return nil, errors.Wrap(err, "findSchema") - } - sqlBytes, err := os.ReadFile(dataPath) - if err != nil { - return nil, errors.Wrap(err, "os.ReadFile") - } - dataSQL := string(sqlBytes) - - _, err = conn.Exec(dataSQL) - if err != nil { - return nil, errors.Wrap(err, "tx.Exec") - } - return conn, nil -}