DB migration & timer token

This commit is contained in:
2024-04-16 22:48:07 +02:00
parent 9da246e91e
commit 4e715ab1e1
8 changed files with 97 additions and 39 deletions

View File

@ -6,6 +6,7 @@ type Timer struct {
StartTime Time StartTime Time
EndTime Time EndTime Time
Owner UUID Owner UUID
Token string
} }
func (self Timer) IsFinished() bool { func (self Timer) IsFinished() bool {

View File

@ -9,3 +9,4 @@ body {
.error { .error {
color: red; color: red;
} }

BIN
timer.db Normal file

Binary file not shown.

104
timer.go
View File

@ -20,16 +20,34 @@ import (
"stevenlr.com/timer/view" "stevenlr.com/timer/view"
) )
func generateRandomString(len int) (string, error) {
bin := make([]byte, len)
_, err := rand.Read(bin)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(bin), nil
}
func generateSessionId() (string, error) {
return generateRandomString(66)
}
func generateTimerToken() (string, error) {
return generateRandomString(66)
}
func insertTimer(tx *sql.Tx, name string, seconds int, ownerId model.UUID) error { func insertTimer(tx *sql.Tx, name string, seconds int, ownerId model.UUID) error {
now := model.MakeTimeNow() now := model.MakeTimeNow()
end := model.Time(time.Time(now).Add(time.Duration(seconds) * time.Second)) end := model.Time(time.Time(now).Add(time.Duration(seconds) * time.Second))
id := model.MakeUUID() id := model.MakeUUID()
token, _ := generateTimerToken()
_, err := tx.Exec(` _, err := tx.Exec(`
INSERT INTO Timer VALUES ($1, $2, $3, $4, $5)`, id, name, now, end, ownerId) INSERT INTO Timer VALUES ($1, $2, $3, $4, $5, $6)`, id, name, now, end, ownerId, token)
return err return err
} }
func initializeDatabase(db *sql.DB) error { func initializeDatabaseV1(db *sql.DB) error {
tx, err := db.Begin() tx, err := db.Begin()
if err != nil { if err != nil {
return err return err
@ -48,19 +66,13 @@ func initializeDatabase(db *sql.DB) error {
StartTime TEXT NOT NULL, StartTime TEXT NOT NULL,
EndTime TEXT NOT NULL, EndTime TEXT NOT NULL,
Owner BLOB NOT NULL, Owner BLOB NOT NULL,
PRIMARY KEY (id) Token TEXT NOT NULL UNIQUE,
PRIMARY KEY (Id)
)`) )`)
if err != nil { if err != nil {
return err return err
} }
userUuidStr := "7015cee7-89a5-4057-b7c9-7e0128ad5086"
var userId model.UUID
err = userId.Scan(userUuidStr)
if err != nil {
return err
}
_, err = tx.Exec(` _, err = tx.Exec(`
CREATE TABLE User ( CREATE TABLE User (
Id BLOB NOT NULL UNIQUE, Id BLOB NOT NULL UNIQUE,
@ -73,14 +85,19 @@ func initializeDatabase(db *sql.DB) error {
return err return err
} }
userPasswordClear := "steven" userName := "admin"
userPassword := "admin"
password, err := bcrypt.GenerateFromPassword([]byte(userUuidStr+userPasswordClear), bcrypt.MinCost) salt, err := generateRandomString(33)
if err != nil { if err != nil {
return err return err
} }
_, err = tx.Exec(`INSERT INTO User VALUES ($1, $2, $3, $4)`, userId, "steven", userUuidStr, password) password, err := bcrypt.GenerateFromPassword([]byte(salt+userPassword), bcrypt.MinCost)
if err != nil {
return err
}
_, err = tx.Exec(`INSERT INTO User VALUES ($1, $2, $3, $4)`, model.MakeUUID(), userName, salt, password)
if err != nil { if err != nil {
return err return err
} }
@ -88,6 +105,50 @@ func initializeDatabase(db *sql.DB) error {
return tx.Commit() return tx.Commit()
} }
func migrateDatabaseV2(db *sql.DB) error {
tx, err := db.Begin()
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.Exec(`PRAGMA user_version = 2`)
if err != nil {
return err
}
_, err = tx.Exec("CREATE INDEX TimerTokenIndex ON Timer(Token)")
if err != nil {
return err
}
return tx.Commit()
}
func initializeDatabase(db *sql.DB) error {
initialVersion := 0
row := db.QueryRow("PRAGMA user_version")
row.Scan(&initialVersion)
if initialVersion < 1 {
log.Println("Initializing DB V1")
err := initializeDatabaseV1(db)
if err != nil {
return err
}
}
if initialVersion < 2 {
log.Println("Migrating DB to V2")
err := migrateDatabaseV2(db)
if err != nil {
return err
}
}
return nil
}
func queryAllTimers(db *sql.DB, owner model.UUID) []model.Timer { func queryAllTimers(db *sql.DB, owner model.UUID) []model.Timer {
rows, err := db.Query("SELECT Id, Name FROM Timer WHERE Owner=$1", owner) rows, err := db.Query("SELECT Id, Name FROM Timer WHERE Owner=$1", owner)
if err != nil { if err != nil {
@ -135,10 +196,10 @@ func queryTimer(db *sql.DB, idStr string, userId model.UUID) *model.Timer {
return nil return nil
} }
row := db.QueryRow("SELECT Id, Name, StartTime, EndTime, Owner FROM Timer WHERE Id=$1 AND Owner=$2", id, userId) row := db.QueryRow("SELECT Id, Name, StartTime, EndTime, Owner, Token FROM Timer WHERE Id=$1 AND Owner=$2", id, userId)
var t model.Timer var t model.Timer
if err := row.Scan(&t.Id, &t.Name, &t.StartTime, &t.EndTime, &t.Owner); err == nil { if err := row.Scan(&t.Id, &t.Name, &t.StartTime, &t.EndTime, &t.Owner, &t.Token); err == nil {
return &t return &t
} }
@ -372,15 +433,6 @@ func (server *MyServer) handlePutTimer(w http.ResponseWriter, r *http.Request) {
view.TimersList(timers, user != nil).Render(context.Background(), w) view.TimersList(timers, user != nil).Render(context.Background(), w)
} }
func generateSessionId() (string, error) {
bin := make([]byte, 64)
_, err := rand.Read(bin)
if err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(bin), nil
}
func (server *MyServer) handlePostLogin(w http.ResponseWriter, r *http.Request) { func (server *MyServer) handlePostLogin(w http.ResponseWriter, r *http.Request) {
if server.findCurrentUser(w, r) != nil { if server.findCurrentUser(w, r) != nil {
w.Header().Add("HX-Redirect", "/") w.Header().Add("HX-Redirect", "/")
@ -433,7 +485,7 @@ func (server *MyServer) handlePostLogout(w http.ResponseWriter, r *http.Request)
func main() { func main() {
log.Println("Starting...") log.Println("Starting...")
db, err := sql.Open("sqlite3", ":memory:") db, err := sql.Open("sqlite3", "file:timer.db")
if err != nil { if err != nil {
log.Fatalln(err) log.Fatalln(err)
} }

View File

@ -13,7 +13,7 @@ templ LoginFormError(currentUser *model.User, err string) {
<input type="password" name="password" placeholder="Password" /> <input type="password" name="password" placeholder="Password" />
<button type="submit">Sign in</button> <button type="submit">Sign in</button>
if err != "" { if err != "" {
<span style="color:red;">{ err }</span> <span class="error">{ err }</span>
} }
</p> </p>
</form> </form>

View File

@ -37,14 +37,14 @@ func LoginFormError(currentUser *model.User, err string) templ.Component {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
if err != "" { if err != "" {
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span style=\"color:red;\">") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<span class=\"error\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
var templ_7745c5c3_Var2 string var templ_7745c5c3_Var2 string
templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(err) templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(err)
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\login.templ`, Line: 16, Col: 50} return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\login.templ`, Line: 16, Col: 45}
} }
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2))
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {

View File

@ -37,11 +37,11 @@ templ TimerCreateForm(timerName string, err string) {
templ TimersList(timers []model.Timer, isSignedIn bool) { templ TimersList(timers []model.Timer, isSignedIn bool) {
<div class="timers-list"> <div class="timers-list">
<h1>Timers</h1>
for _, t := range timers {
@timer(t)
}
if isSignedIn { if isSignedIn {
<h1>Timers</h1>
for _, t := range timers {
@timer(t)
}
<h4>Create timer</h4> <h4>Create timer</h4>
@TimerCreateForm("", "") @TimerCreateForm("", "")
} }

View File

@ -147,18 +147,22 @@ func TimersList(timers []model.Timer, isSignedIn bool) templ.Component {
templ_7745c5c3_Var8 = templ.NopComponent templ_7745c5c3_Var8 = templ.NopComponent
} }
ctx = templ.ClearChildren(ctx) ctx = templ.ClearChildren(ctx)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"timers-list\"><h1>Timers</h1>") _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"timers-list\">")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
for _, t := range timers { if isSignedIn {
templ_7745c5c3_Err = timer(t).Render(ctx, templ_7745c5c3_Buffer) _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>Timers</h1>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }
} for _, t := range timers {
if isSignedIn { templ_7745c5c3_Err = timer(t).Render(ctx, templ_7745c5c3_Buffer)
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h4>Create timer</h4>") if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err
}
}
_, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(" <h4>Create timer</h4>")
if templ_7745c5c3_Err != nil { if templ_7745c5c3_Err != nil {
return templ_7745c5c3_Err return templ_7745c5c3_Err
} }