diff options
author | Steven Le Rouzic <steven.lerouzic@gmail.com> | 2024-04-14 23:51:04 +0200 |
---|---|---|
committer | Steven Le Rouzic <steven.lerouzic@gmail.com> | 2024-04-14 23:51:04 +0200 |
commit | 1e9b8d0fa114ce706c4fa05004991e516cbddcd7 (patch) | |
tree | 2ba185a018f7b9653e703cdebc90ed643b12f731 | |
parent | 3928c520e8b152f47c17ca75fcf5beed4f33f095 (diff) |
Add time to timer
-rw-r--r-- | model/time.go | 38 | ||||
-rw-r--r-- | model/timer.go | 11 | ||||
-rw-r--r-- | model/uuid.go | 55 | ||||
-rw-r--r-- | timer.go | 79 | ||||
-rw-r--r-- | view/timer.templ | 53 | ||||
-rw-r--r-- | view/timer_templ.go | 142 |
6 files changed, 302 insertions, 76 deletions
diff --git a/model/time.go b/model/time.go index baf3ce6..e0c399c 100644 --- a/model/time.go +++ b/model/time.go @@ -1,34 +1,42 @@ package model
import (
- "time"
- "errors"
- sqldriver "database/sql/driver"
+ sqldriver "database/sql/driver"
+ "errors"
+ "time"
)
type Time time.Time
func MakeTimeNow() Time {
- return Time(time.Now().UTC())
+ return Time(time.Now().UTC())
}
func (self Time) AsUTCString() string {
- return time.Time(self).Format(time.RFC3339)
+ return time.Time(self).Format(time.RFC3339)
}
func (self Time) Value() (sqldriver.Value, error) {
- return self.AsUTCString(), nil
+ return self.AsUTCString(), nil
}
func (self *Time) Scan(value any) error {
- if valueStr, ok := value.(string); ok {
- parsedTime, err := time.Parse(time.RFC3339, valueStr)
- if err == nil {
- *self = Time(parsedTime.UTC())
- }
- return err
- } else {
- return errors.New("Expected a string")
- }
+ if valueStr, ok := value.(string); ok {
+ parsedTime, err := time.Parse(time.RFC3339, valueStr)
+ if err == nil {
+ *self = Time(parsedTime.UTC())
+ }
+ return err
+ } else {
+ return errors.New("Expected a string")
+ }
+}
+
+func (from Time) Compare(to Time) int {
+ return time.Time(from).Compare(time.Time(to))
+}
+
+func (self *Time) Add(duration time.Duration) {
+ *self = Time(time.Time(*self).Add(duration))
}
diff --git a/model/timer.go b/model/timer.go index 4c81962..a47369d 100644 --- a/model/timer.go +++ b/model/timer.go @@ -1,9 +1,12 @@ package model
type Timer struct {
- Id UUID
- Name string
- StartTime Time
- EndTime Time
+ Id UUID
+ Name string
+ StartTime Time
+ EndTime Time
}
+func (self Timer) IsFinished() bool {
+ return MakeTimeNow().Compare(self.EndTime) >= 0
+}
diff --git a/model/uuid.go b/model/uuid.go index 4a25e41..6cade35 100644 --- a/model/uuid.go +++ b/model/uuid.go @@ -1,28 +1,27 @@ -package model
-
-import (
- sqldriver "database/sql/driver"
- "github.com/google/uuid"
-)
-
-type UUID struct {
- payload uuid.UUID
-}
-
-func MakeUUID() UUID {
- id, _ := uuid.NewV7()
- return UUID { payload: id }
-}
-
-func (self UUID) Value() (sqldriver.Value, error) {
- return self.payload[:], nil
-}
-
-func (self *UUID) Scan(value any) error {
- return self.payload.Scan(value)
-}
-
-func (self UUID) String() string {
- return self.payload.String()
-}
-
+package model + +import ( + sqldriver "database/sql/driver" + "github.com/google/uuid" +) + +type UUID struct { + payload uuid.UUID +} + +func MakeUUID() UUID { + id, _ := uuid.NewV7() + return UUID{payload: id} +} + +func (self UUID) Value() (sqldriver.Value, error) { + return self.payload[:], nil +} + +func (self *UUID) Scan(value any) error { + return self.payload.Scan(value) +} + +func (self UUID) String() string { + return self.payload.String() +} @@ -3,6 +3,7 @@ package main import ( "context" "database/sql" + "errors" "log" "net/http" "strconv" @@ -50,12 +51,12 @@ func initializeDatabase(db *sql.DB) error { return err } - err = insertTimer(tx, "My timer", 600) + err = insertTimer(tx, "My timer", 6) if err != nil { return err } - err = insertTimer(tx, "My timer2", 600) + err = insertTimer(tx, "My timer2", 6) if err != nil { return err } @@ -86,7 +87,7 @@ func initializeDatabase(db *sql.DB) error { return err } - _, err = tx.Exec(`INSERT INTO Timer VALUES ($1, $2, $3, $4)`, userId, "steven", userUuidStr, password) + _, err = tx.Exec(`INSERT INTO User VALUES ($1, $2, $3, $4)`, userId, "steven", userUuidStr, password) if err != nil { return err } @@ -139,7 +140,17 @@ func deleteTimer(db *sql.DB, idStr string) bool { } affected, err := res.RowsAffected() - return err == nil && affected > 0 + return err == nil && affected == 1 +} + +func updateTimerEndTime(db *sql.DB, id model.UUID, endTime model.Time) bool { + res, err := db.Exec("UPDATE Timer SET EndTime=$1 WHERE Id=$2", endTime, id) + if err != nil { + return false + } + + affected, err := res.RowsAffected() + return err == nil && affected == 1 } type Session struct { @@ -174,6 +185,65 @@ func (server *MyServer) handleTimer(w http.ResponseWriter, r *http.Request) { } } +func parseDuration(value string) (time.Duration, error) { + const nullDuration = time.Duration(0) + if len(value) == 0 { + return nullDuration, errors.New("Empty duration string") + } + + var unit time.Duration + switch value[len(value)-1] { + case 's': + unit = time.Second + case 'm': + unit = time.Minute + case 'h': + unit = time.Hour + case 'd': + unit = time.Duration(24) * time.Hour + case 'w': + unit = time.Duration(24*7) * time.Hour + default: + return nullDuration, errors.New("Invalid duration format") + } + + amount, err := strconv.ParseInt(value[0:len(value)-1], 10, 64) + if err != nil || amount < 0 { + return nullDuration, errors.New("Invalid duration value") + } + + return time.Duration(amount) * unit, nil +} + +func (server *MyServer) handleTimerAddTime(w http.ResponseWriter, r *http.Request) { + timer := queryTimer(server.db, r.PathValue("timerId")) + if timer == nil { + server.handleNotFound(w, r) + return + } + + if timer.IsFinished() { + w.WriteHeader(http.StatusBadRequest) + return + } + + duration, err := parseDuration(r.PathValue("timeToAdd")) + if err != nil { + w.WriteHeader(http.StatusBadRequest) + w.Write([]byte(err.Error())) + return + } + + timer.EndTime.Add(duration) + res := updateTimerEndTime(server.db, timer.Id, timer.EndTime) + if !res { + w.WriteHeader(http.StatusBadRequest) + return + } + + view.TimerView(*timer).Render(context.Background(), w) +} + func (server *MyServer) handleDeleteTimer(w http.ResponseWriter, r *http.Request) { success := deleteTimer(server.db, r.PathValue("timerId")) if !success { @@ -244,6 +314,7 @@ func main() { http.Handle("GET /static/", http.StripPrefix("/static/", fs)) http.HandleFunc("GET /timer/{timerId}", myServer.handleTimer) + http.HandleFunc("POST /timer/{timerId}/addTime/{timeToAdd}", myServer.handleTimerAddTime) http.HandleFunc("DELETE /timer/{timerId}", myServer.handleDeleteTimer) http.HandleFunc("PUT /timer", myServer.handlePutTimer) http.HandleFunc("GET /", myServer.handleMain) diff --git a/view/timer.templ b/view/timer.templ index 8d20933..a447d01 100644 --- a/view/timer.templ +++ b/view/timer.templ @@ -1,26 +1,43 @@ package view
import (
- "stevenlr.com/timer/model"
+ "fmt"
+ "stevenlr.com/timer/model"
)
templ TimerView(timer model.Timer) {
- <h1>This is timer { timer.Name } </h1>
- <p><a href="/">Back to list</a></p>
- <p>Start time: <local-date>{ timer.StartTime.AsUTCString() }</local-date></p>
- <p>End time: <local-date>{ timer.EndTime.AsUTCString() }</local-date></p>
- <p>
- Total time:
- <timer-countdown
- start={ timer.StartTime.AsUTCString() }
- end={ timer.EndTime.AsUTCString() }
- ></timer-countdown>
- </p>
- <p>
- Remaining time:
- <timer-countdown
- end={ timer.EndTime.AsUTCString() }
- ></timer-countdown>
- </p>
+ <div class="timer">
+ <h1>This is timer { timer.Name } </h1>
+ <p><a href="/">Back to list</a></p>
+ <p>Start time: <local-date>{ timer.StartTime.AsUTCString() }</local-date></p>
+ <p>End time: <local-date>{ timer.EndTime.AsUTCString() }</local-date></p>
+ <p>
+ Total time:
+ <timer-countdown
+ start={ timer.StartTime.AsUTCString() }
+ end={ timer.EndTime.AsUTCString() }
+ ></timer-countdown>
+ </p>
+ <p>
+ Remaining time:
+ <timer-countdown
+ end={ timer.EndTime.AsUTCString() }
+ ></timer-countdown>
+ </p>
+ if !timer.IsFinished() {
+ <h3>Add time</h3>
+ <p>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/15m") }>15 minutes</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/30m") }>30 minutes</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/1h") }>1 hour</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/2h") }>2 hours</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/6h") }>6 hours</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/12h") }>12 hours</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/1d") }>1 day</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/1w") }>1 week</button>
+ <button hx-target="closest .timer" hx-post={ fmt.Sprint("/timer/", timer.Id, "/addTime/4w") }>4 weeks</button>
+ </p>
+ }
+ </div>
}
diff --git a/view/timer_templ.go b/view/timer_templ.go index 55e5346..1cafffd 100644 --- a/view/timer_templ.go +++ b/view/timer_templ.go @@ -11,6 +11,7 @@ import "io" import "bytes" import ( + "fmt" "stevenlr.com/timer/model" ) @@ -27,14 +28,14 @@ func TimerView(timer model.Timer) templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h1>This is timer ") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<div class=\"timer\"><h1>This is timer ") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } var templ_7745c5c3_Var2 string templ_7745c5c3_Var2, templ_7745c5c3_Err = templ.JoinStringErrs(timer.Name) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 8, Col: 34} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 10, Col: 32} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var2)) if templ_7745c5c3_Err != nil { @@ -47,7 +48,7 @@ func TimerView(timer model.Timer) templ.Component { var templ_7745c5c3_Var3 string templ_7745c5c3_Var3, templ_7745c5c3_Err = templ.JoinStringErrs(timer.StartTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 10, Col: 62} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 12, Col: 60} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var3)) if templ_7745c5c3_Err != nil { @@ -60,7 +61,7 @@ func TimerView(timer model.Timer) templ.Component { var templ_7745c5c3_Var4 string templ_7745c5c3_Var4, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 11, Col: 58} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 13, Col: 56} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var4)) if templ_7745c5c3_Err != nil { @@ -73,7 +74,7 @@ func TimerView(timer model.Timer) templ.Component { var templ_7745c5c3_Var5 string templ_7745c5c3_Var5, templ_7745c5c3_Err = templ.JoinStringErrs(timer.StartTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 15, Col: 49} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 17, Col: 41} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var5)) if templ_7745c5c3_Err != nil { @@ -86,7 +87,7 @@ func TimerView(timer model.Timer) templ.Component { var templ_7745c5c3_Var6 string templ_7745c5c3_Var6, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 16, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 18, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var6)) if templ_7745c5c3_Err != nil { @@ -99,7 +100,7 @@ func TimerView(timer model.Timer) templ.Component { var templ_7745c5c3_Var7 string templ_7745c5c3_Var7, templ_7745c5c3_Err = templ.JoinStringErrs(timer.EndTime.AsUTCString()) if templ_7745c5c3_Err != nil { - return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 22, Col: 45} + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 24, Col: 37} } _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var7)) if templ_7745c5c3_Err != nil { @@ -109,6 +110,133 @@ func TimerView(timer model.Timer) templ.Component { if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } + if !timer.IsFinished() { + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("<h3>Add time</h3><p><button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var8 string + templ_7745c5c3_Var8, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/15m")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 30, Col: 96} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var8)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">15 minutes</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var9 string + templ_7745c5c3_Var9, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/30m")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 31, Col: 96} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var9)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">30 minutes</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var10 string + templ_7745c5c3_Var10, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/1h")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 32, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var10)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">1 hour</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var11 string + templ_7745c5c3_Var11, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/2h")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 33, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var11)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">2 hours</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var12 string + templ_7745c5c3_Var12, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/6h")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 34, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var12)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">6 hours</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var13 string + templ_7745c5c3_Var13, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/12h")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 35, Col: 96} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var13)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">12 hours</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var14 string + templ_7745c5c3_Var14, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/1d")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 36, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var14)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">1 day</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var15 string + templ_7745c5c3_Var15, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/1w")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 37, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var15)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">1 week</button> <button hx-target=\"closest .timer\" hx-post=\"") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + var templ_7745c5c3_Var16 string + templ_7745c5c3_Var16, templ_7745c5c3_Err = templ.JoinStringErrs(fmt.Sprint("/timer/", timer.Id, "/addTime/4w")) + if templ_7745c5c3_Err != nil { + return templ.Error{Err: templ_7745c5c3_Err, FileName: `view\timer.templ`, Line: 38, Col: 95} + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString(templ.EscapeString(templ_7745c5c3_Var16)) + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("\">4 weeks</button></p>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } + } + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("</div>") + if templ_7745c5c3_Err != nil { + return templ_7745c5c3_Err + } if !templ_7745c5c3_IsBuffer { _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteTo(templ_7745c5c3_W) } |