diff options
author | GrailFinder <wohilas@gmail.com> | 2024-05-18 13:27:28 +0300 |
---|---|---|
committer | GrailFinder <wohilas@gmail.com> | 2024-05-18 13:27:28 +0300 |
commit | 2e9b18944eac3dcaf8a006594cb338d94c07a447 (patch) | |
tree | 7715140a6951407d13781e0a5bbf83a6e8f9dbe9 /internal | |
parent | ff86222fc9ab85fb4c5c5e8a063083595b323761 (diff) |
Feat: auth; login; signup; migrate to sqlite
Diffstat (limited to 'internal')
-rw-r--r-- | internal/database/migrations/001_init.up.sql | 11 | ||||
-rw-r--r-- | internal/database/repos/action.go | 10 | ||||
-rw-r--r-- | internal/database/repos/userscore.go | 4 | ||||
-rw-r--r-- | internal/database/sql/main.go | 69 | ||||
-rw-r--r-- | internal/handlers/auth.go | 56 | ||||
-rw-r--r-- | internal/handlers/main.go | 46 | ||||
-rw-r--r-- | internal/models/models.go | 1 | ||||
-rw-r--r-- | internal/server/router.go | 2 |
8 files changed, 105 insertions, 94 deletions
diff --git a/internal/database/migrations/001_init.up.sql b/internal/database/migrations/001_init.up.sql index 80ebcad..f7e41c1 100644 --- a/internal/database/migrations/001_init.up.sql +++ b/internal/database/migrations/001_init.up.sql @@ -1,21 +1,22 @@ BEGIN; CREATE TABLE user_score ( - id INT GENERATED BY DEFAULT AS IDENTITY, + id INTEGER PRIMARY KEY AUTOINCREMENT, username TEXT UNIQUE NOT NULL, - burn_time TIMESTAMP NOT NULL DEFAULT NOW() + interval '1 day', + password TEXT NOT NULL, + burn_time TIMESTAMP NOT NULL, score SMALLINT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW() + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE action ( - id INT GENERATED BY DEFAULT AS IDENTITY, + id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, magnitude SMALLINT NOT NULL DEFAULT 1, repeatable BOOLEAN NOT NULL DEFAULT FALSE, type TEXT NOT NULL, done BOOLEAN NOT NULL DEFAULT FALSE, username TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, UNIQUE(username, name), CONSTRAINT fk_user_score FOREIGN KEY(username) diff --git a/internal/database/repos/action.go b/internal/database/repos/action.go index 6024dd5..49ad95e 100644 --- a/internal/database/repos/action.go +++ b/internal/database/repos/action.go @@ -5,6 +5,7 @@ import "apjournal/internal/models" type ActionRepo interface { DBActionCreate(req *models.Action) error DBActionList(username string) ([]models.Action, error) + DBActionGetByName(name string) (*models.Action, error) DBActionDone(name string) error DBActionsToReset() error } @@ -25,6 +26,15 @@ func (p *Provider) DBActionList(username string) ([]models.Action, error) { return resp, nil } +func (p *Provider) DBActionGetByName(name string) (*models.Action, error) { + resp := models.Action{} + query := "SELECT * FROM action WHERE name=$1;" + if err := p.db.Get(&resp, query, name); err != nil { + return nil, err + } + return &resp, nil +} + func (p *Provider) DBActionDone(name string) error { // should reset at burn time stmt := "UPDATE action SET done=true WHERE name=$1;" diff --git a/internal/database/repos/userscore.go b/internal/database/repos/userscore.go index 5c09004..2baf99f 100644 --- a/internal/database/repos/userscore.go +++ b/internal/database/repos/userscore.go @@ -10,8 +10,8 @@ type UserScoreRepo interface { func (p *Provider) DBUserScoreCreate(req *models.UserScore) error { _, err := p.db.NamedExec(` - INSERT INTO user_score(username, burn_time, score) - VALUES (:username, :burn_time, :score);`, req) + INSERT INTO user_score(username, burn_time, score, password) + VALUES (:username, :burn_time, :score, :password);`, req) return err } diff --git a/internal/database/sql/main.go b/internal/database/sql/main.go index 80d5f5c..5a523f6 100644 --- a/internal/database/sql/main.go +++ b/internal/database/sql/main.go @@ -1,23 +1,20 @@ package database import ( - "apjournal/internal/database/migrations" "os" "time" - "github.com/jmoiron/sqlx" - - // driver postgres for migrations "log/slog" - "github.com/golang-migrate/migrate" - _ "github.com/golang-migrate/migrate/database/postgres" - bindata "github.com/golang-migrate/migrate/source/go_bindata" - _ "github.com/jackc/pgx/v5/stdlib" // register pgx driver + "github.com/jmoiron/sqlx" + _ "github.com/mattn/go-sqlite3" "github.com/pkg/errors" ) -var log = slog.New(slog.NewJSONHandler(os.Stdout, nil)) +var ( + log = slog.New(slog.NewJSONHandler(os.Stdout, nil)) + dbDriver = "sqlite3" +) type DB struct { Conn *sqlx.DB @@ -40,38 +37,17 @@ func closeConn(conn *sqlx.DB) error { func Init(DBURI string) (*DB, error) { var result DB var err error - result.Conn, err = openDBConnection(DBURI, "pgx") + result.Conn, err = openDBConnection(DBURI, dbDriver) if err != nil { return nil, err } result.URI = DBURI - if err := testConnection(result.Conn); err != nil { return nil, err } return &result, nil } -func InitWithMigrate(DBURI string, up bool) (*DB, error) { - var ( - result DB - err error - ) - result.Conn, err = openDBConnection(DBURI, "pgx") - if err != nil { - return nil, err - } - result.URI = DBURI - - if err = testConnection(result.Conn); err != nil { - return nil, err - } - if err = result.Migrate(DBURI, up); err != nil { - return nil, err - } - return &result, nil -} - func openDBConnection(dbURI, driver string) (*sqlx.DB, error) { conn, err := sqlx.Open(driver, dbURI) if err != nil { @@ -88,38 +64,9 @@ func testConnection(conn *sqlx.DB) error { return nil } -func (db *DB) Migrate(url string, up bool) error { - source := bindata.Resource(migrations.AssetNames(), migrations.Asset) - driver, err := bindata.WithInstance(source) - if err != nil { - return errors.WithStack(errors.WithMessage(err, - "unable to instantiate driver from bindata")) - } - migration, err := migrate.NewWithSourceInstance("go-bindata", - driver, url) - if err != nil { - return errors.WithStack(errors.WithMessage(err, - "unable to start migration")) - } - if up { - if err = migration.Up(); err != nil && err.Error() != "no change" { - return errors.WithStack(errors.WithMessage(err, - "unable to migrate up")) - } - } else { - if err = migration.Down(); err != nil && - err.Error() != "no change" { - return errors.WithStack(errors.WithMessage(err, - "unable to migrate down")) - } - } - return nil -} - func (d *DB) PingRoutine(interval time.Duration) { ticker := time.NewTicker(interval) done := make(chan bool) - for { select { case <-done: @@ -131,7 +78,7 @@ func (d *DB) PingRoutine(interval time.Duration) { if err := closeConn(d.Conn); err != nil { log.Error("failed to close db connection", "error", err, "ping_at", t) } - d.Conn, err = openDBConnection(d.URI, "pgx") + d.Conn, err = openDBConnection(d.URI, dbDriver) if err != nil { log.Error("failed to reconnect", "error", err, "ping_at", t) } diff --git a/internal/handlers/auth.go b/internal/handlers/auth.go index e7eca50..0287960 100644 --- a/internal/handlers/auth.go +++ b/internal/handlers/auth.go @@ -11,6 +11,8 @@ import ( "net/http" "strings" "time" + + "golang.org/x/crypto/bcrypt" ) func abortWithError(w http.ResponseWriter, msg string) { @@ -18,7 +20,7 @@ func abortWithError(w http.ResponseWriter, msg string) { tmpl.ExecuteTemplate(w, "error", msg) } -func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { +func (h *Handlers) HandleSignup(w http.ResponseWriter, r *http.Request) { r.ParseForm() username := r.PostFormValue("username") if username == "" { @@ -34,7 +36,24 @@ func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { abortWithError(w, msg) return } + // TODO: make sure username does not exists cleanName := utils.RemoveSpacesFromStr(username) + hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 8) + // create user in db + now := time.Now() + nextMidnight := time.Date(now.Year(), now.Month(), now.Day(), + 0, 0, 0, 0, time.UTC).Add(time.Hour * 24) + newUser := &models.UserScore{ + Username: cleanName, Password: string(hashedPassword), + BurnTime: nextMidnight, CreatedAt: now, + } + if err := h.repo.DBUserScoreCreate(newUser); err != nil { + msg := "failed to create user" + h.log.Error(msg, "user", newUser) + abortWithError(w, msg) + return + } + // TODO: login user cookie, err := h.makeCookie(cleanName, r.RemoteAddr) if err != nil { h.log.Error("failed to login", "error", err) @@ -47,12 +66,33 @@ func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { if err != nil { panic(err) } + tmpl.ExecuteTemplate(w, "main", newUser) +} + +func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { + r.ParseForm() + username := r.PostFormValue("username") + if username == "" { + msg := "username not provided" + h.log.Error(msg) + abortWithError(w, msg) + return + } + password := r.PostFormValue("password") + if password == "" { + msg := "password not provided" + h.log.Error(msg) + abortWithError(w, msg) + return + } + cleanName := utils.RemoveSpacesFromStr(username) + tmpl, err := template.ParseGlob("components/*.html") + if err != nil { + panic(err) + } userScore, err := h.repo.DBUserScoreGet(cleanName) if err != nil { h.log.Warn("got db err", "err", err) - if err := h.repo.DBUserScoreCreate(&us); err != nil { - panic(err) - } tmpl.ExecuteTemplate(w, "main", nil) return } @@ -60,6 +100,14 @@ func (h *Handlers) HandleLogin(w http.ResponseWriter, r *http.Request) { if err != nil { panic(err) } + cookie, err := h.makeCookie(cleanName, r.RemoteAddr) + if err != nil { + h.log.Error("failed to login", "error", err) + abortWithError(w, err.Error()) + return + } + http.SetCookie(w, cookie) + // http.Redirect(w, r, "/", 302) tmpl.ExecuteTemplate(w, "main", userScore) } diff --git a/internal/handlers/main.go b/internal/handlers/main.go index aa9db4f..e87c74f 100644 --- a/internal/handlers/main.go +++ b/internal/handlers/main.go @@ -39,13 +39,6 @@ func NewHandlers( return h } -// FIXME: global userscore for test -var us = models.UserScore{ - Username: "test", - BurnTime: time.Now().Add(time.Duration(24) * time.Hour), - CreatedAt: time.Now(), -} - func (h *Handlers) Ping(w http.ResponseWriter, r *http.Request) { h.log.Info("got ping request") w.Write([]byte("pong")) @@ -70,10 +63,7 @@ func (h *Handlers) MainPage(w http.ResponseWriter, r *http.Request) { userScore, err := h.repo.DBUserScoreGet(username) if err != nil { h.log.Warn("got db err", "err", err) - if err := h.repo.DBUserScoreCreate(&us); err != nil { - panic(err) - } - tmpl.ExecuteTemplate(w, "main", us) + tmpl.ExecuteTemplate(w, "main", nil) return } userScore.Actions, err = h.repo.DBActionList(username) @@ -120,8 +110,10 @@ func (h *Handlers) HandleForm(w http.ResponseWriter, r *http.Request) { Repeatable: repeat, CreatedAt: time.Now(), } - // TODO: get username from ctx - userScore, err := h.repo.DBUserScoreGet("test") + // get username from ctx + username := r.Context().Value("username").(string) + h.log.Info("got username from ctx", "username", username) + userScore, err := h.repo.DBUserScoreGet(username) if err != nil { panic(err) } @@ -135,11 +127,11 @@ func (h *Handlers) HandleForm(w http.ResponseWriter, r *http.Request) { func (h *Handlers) UserScoreWithActionsByUsername( username string, ) (*models.UserScore, error) { - userScore, err := h.repo.DBUserScoreGet("test") + userScore, err := h.repo.DBUserScoreGet(username) if err != nil { return nil, err } - list, err := h.repo.DBActionList("test") + list, err := h.repo.DBActionList(username) if err != nil { return nil, err } @@ -149,18 +141,30 @@ func (h *Handlers) UserScoreWithActionsByUsername( func (h *Handlers) HandleDoneAction(w http.ResponseWriter, r *http.Request) { r.ParseForm() - h.log.Info("got done request", "payload", r.PostForm) actionName := r.PostFormValue("name") - h.log.Info("got postform request", "name", actionName) + username := r.Context().Value("username").(string) + h.log.Info("got postform request", "name", actionName, + "username", username) + userScore, err := h.UserScoreWithActionsByUsername(username) + if err != nil { + panic(err) + } + // get action by name + action, err := h.repo.DBActionGetByName(actionName) + magnitude := int8(action.Magnitude) + if action.Type == models.ActionTypeMinus { + magnitude *= -1 + } + // change counter of user score + userScore.Score += magnitude + // disable action if repetable if err := h.repo.DBActionDone(actionName); err != nil { panic(err) } - userScore, err := h.UserScoreWithActionsByUsername("test") - if err != nil { + // update score in db + if err := h.repo.DBUserScoreUpdate(userScore); err != nil { panic(err) } - // change counter of user score - // get action by name tmpl := template.Must(template.ParseGlob("components/*.html")) tmpl.ExecuteTemplate(w, "main", userScore) } diff --git a/internal/models/models.go b/internal/models/models.go index bd38eaf..71fb358 100644 --- a/internal/models/models.go +++ b/internal/models/models.go @@ -13,6 +13,7 @@ type ( UserScore struct { ID uint32 `db:"id"` Username string `db:"username"` + Password string `db:"password"` Actions []Action BurnTime time.Time `db:"burn_time"` Score int8 `db:"score"` diff --git a/internal/server/router.go b/internal/server/router.go index 3e8785f..ae68418 100644 --- a/internal/server/router.go +++ b/internal/server/router.go @@ -24,7 +24,7 @@ func (srv *server) ListenToRequests() { mux.HandleFunc("POST /", h.HandleForm) mux.HandleFunc("POST /done", h.HandleDoneAction) mux.HandleFunc("POST /login", h.HandleLogin) - // mux.HandleFunc("POST /signup", h.HandleLogin) + mux.HandleFunc("POST /signup", h.HandleSignup) // ====== elements ====== mux.HandleFunc("GET /showform", h.ServeShowForm) |