summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorGrailFinder <wohilas@gmail.com>2024-05-18 13:27:28 +0300
committerGrailFinder <wohilas@gmail.com>2024-05-18 13:27:28 +0300
commit2e9b18944eac3dcaf8a006594cb338d94c07a447 (patch)
tree7715140a6951407d13781e0a5bbf83a6e8f9dbe9 /internal
parentff86222fc9ab85fb4c5c5e8a063083595b323761 (diff)
Feat: auth; login; signup; migrate to sqlite
Diffstat (limited to 'internal')
-rw-r--r--internal/database/migrations/001_init.up.sql11
-rw-r--r--internal/database/repos/action.go10
-rw-r--r--internal/database/repos/userscore.go4
-rw-r--r--internal/database/sql/main.go69
-rw-r--r--internal/handlers/auth.go56
-rw-r--r--internal/handlers/main.go46
-rw-r--r--internal/models/models.go1
-rw-r--r--internal/server/router.go2
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)