summaryrefslogtreecommitdiff
path: root/locker.go
diff options
context:
space:
mode:
Diffstat (limited to 'locker.go')
-rw-r--r--locker.go323
1 files changed, 323 insertions, 0 deletions
diff --git a/locker.go b/locker.go
new file mode 100644
index 0000000..7bc6cc4
--- /dev/null
+++ b/locker.go
@@ -0,0 +1,323 @@
+package main
+
+import (
+ "context"
+ "database/sql"
+ "fmt"
+ "log"
+ "net/http"
+ "strings"
+
+ "golang.org/x/crypto/bcrypt"
+
+ _ "github.com/mattn/go-sqlite3"
+
+ "stevenlr.com/locker/model"
+ "stevenlr.com/locker/utils"
+ "stevenlr.com/locker/view"
+)
+
+type LockServer struct {
+ db *sql.DB
+ sessions Sessions
+}
+
+func (server *LockServer) findCurrentUser(w http.ResponseWriter, r *http.Request) *model.User {
+ return server.sessions.FindCurrentUser(server.db, w, r)
+}
+
+func (server *LockServer) handleNotFound(w http.ResponseWriter, _ *http.Request) {
+ w.WriteHeader(http.StatusNotFound)
+ view.Error404().Render(context.Background(), w)
+}
+
+func (server *LockServer) handleMain(w http.ResponseWriter, r *http.Request) {
+ currentUser := server.findCurrentUser(w, r)
+ if r.URL.Path == "/" {
+ locks := make([]model.Lock, 0)
+ if currentUser != nil {
+ locks = model.GetLocksForUser(server.db, currentUser.Id)
+ }
+ view.Main(view.LocksList(locks, currentUser != nil), currentUser).Render(context.Background(), w)
+ } else {
+ server.handleNotFound(w, r)
+ }
+}
+
+func (server *LockServer) handleLock(w http.ResponseWriter, r *http.Request) {
+ currentUser := server.findCurrentUser(w, r)
+ if currentUser == nil {
+ server.handleNotFound(w, r)
+ return
+ }
+
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ server.handleNotFound(w, r)
+ return
+ }
+
+ lock := model.GetLockForUser(server.db, id, currentUser.Id)
+ if lock != nil && lock.Owner == currentUser.Id {
+ view.Main(view.LockView(*lock), currentUser).Render(context.Background(), w)
+ } else {
+ server.handleNotFound(w, r)
+ }
+}
+
+func (server *LockServer) handleLockAddTimeCommon(w http.ResponseWriter, r *http.Request, lock *model.Lock) bool {
+ if lock.IsFinished() {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte("Lock already finished"))
+ return false
+ }
+
+ duration, err := utils.ParseDuration(r.FormValue("timeToAdd"))
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ w.Write([]byte(err.Error()))
+ return false
+ }
+
+ lock.EndTime.Add(duration)
+ res := model.UpdateLockEndTime(server.db, lock.Id, lock.EndTime, lock.Owner)
+ if !res {
+ w.WriteHeader(http.StatusInternalServerError)
+ return false
+ }
+
+ return true
+}
+
+func (server *LockServer) handleLockAddTime(w http.ResponseWriter, r *http.Request) {
+ currentUser := server.findCurrentUser(w, r)
+ if currentUser == nil {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ lock := model.GetLockForUser(server.db, id, currentUser.Id)
+ if lock == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ if !server.handleLockAddTimeCommon(w, r, lock) {
+ return
+ }
+
+ view.LockInfo(*lock).Render(context.Background(), w)
+}
+
+func (server *LockServer) handleApiLockAddTime(w http.ResponseWriter, r *http.Request) {
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ lock := model.GetLockWithToken(server.db, id, r.FormValue("token"))
+ if lock == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ if !server.handleLockAddTimeCommon(w, r, lock) {
+ return
+ }
+}
+
+func (server *LockServer) handleGetLockToken(w http.ResponseWriter, r *http.Request) {
+ currentUser := server.findCurrentUser(w, r)
+ if currentUser == nil {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ lock := model.GetLockForUser(server.db, id, currentUser.Id)
+ if lock == nil {
+ server.handleNotFound(w, r)
+ return
+ }
+
+ w.Write([]byte(fmt.Sprint("<code>", lock.Token, "</code>")))
+}
+
+func (server *LockServer) handleResetLockToken(w http.ResponseWriter, r *http.Request) {
+ currentUser := server.findCurrentUser(w, r)
+ if currentUser == nil {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ lock := model.GetLockForUser(server.db, id, currentUser.Id)
+ if lock == nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ res := model.RegenerateLockToken(server.db, lock.Id, currentUser.Id)
+ if !res {
+ w.WriteHeader(http.StatusInternalServerError)
+ return
+ }
+
+ view.LockTokenForm(*lock).Render(context.Background(), w)
+}
+
+func (server *LockServer) handleDeleteLock(w http.ResponseWriter, r *http.Request) {
+ user := server.findCurrentUser(w, r)
+ if user == nil {
+ w.WriteHeader(http.StatusUnauthorized)
+ return
+ }
+
+ var id model.UUID
+ if err := id.Scan(r.PathValue("lockId")); err != nil {
+ w.WriteHeader(http.StatusNotFound)
+ return
+ }
+
+ success := model.DeleteLock(server.db, id, user.Id)
+ if !success {
+ w.WriteHeader(http.StatusNotFound)
+ }
+}
+
+func (server *LockServer) handleCreateLock(w http.ResponseWriter, r *http.Request) {
+ lockName := strings.TrimSpace(r.FormValue("lockName"))
+
+ user := server.findCurrentUser(w, r)
+ if user == nil {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LockCreateForm(lockName, "You are not signed in").Render(context.Background(), w)
+ return
+ }
+
+ days, err := utils.ParseNumber(r.FormValue("days"))
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LockCreateForm(lockName, "Error parsing days").Render(context.Background(), w)
+ return
+ }
+
+ hours, err := utils.ParseNumber(r.FormValue("hours"))
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LockCreateForm(lockName, "Error parsing hours").Render(context.Background(), w)
+ return
+ }
+
+ tx, err := server.db.Begin()
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ view.LockCreateForm(lockName, "Internal server error").Render(context.Background(), w)
+ return
+ }
+ defer tx.Rollback()
+
+ if lockName == "" {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LockCreateForm("", "Lock name cannot be empty").Render(context.Background(), w)
+ return
+ }
+
+ err = model.InsertLock(tx, lockName, int(((max(days, 0)*24)+max(hours, 0))*3600), user.Id)
+ if err != nil {
+ w.WriteHeader(http.StatusInternalServerError)
+ view.LockCreateForm(lockName, "Internal server error").Render(context.Background(), w)
+ return
+ }
+
+ tx.Commit()
+
+ locks := model.GetLocksForUser(server.db, user.Id)
+ view.LocksList(locks, user != nil).Render(context.Background(), w)
+}
+
+func (server *LockServer) handlePostLogin(w http.ResponseWriter, r *http.Request) {
+ if server.findCurrentUser(w, r) != nil {
+ utils.HtmxRedirect(w, "/")
+ return
+ }
+
+ userName := r.FormValue("user")
+ userPass := r.FormValue("password")
+
+ user := model.GetUserByName(server.db, userName)
+ if user == nil {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LoginFormError(nil, "Incorrect credentials").Render(context.Background(), w)
+ return
+ }
+
+ err := bcrypt.CompareHashAndPassword(user.Password, []byte(userPass))
+ if err != nil {
+ w.WriteHeader(http.StatusBadRequest)
+ view.LoginFormError(nil, "Incorrect credentials").Render(context.Background(), w)
+ return
+ }
+
+ if err := server.sessions.StartSession(user.Id, w); err == nil {
+ utils.HtmxRedirect(w, "/")
+ } else {
+ w.WriteHeader(http.StatusInternalServerError)
+ view.LoginFormError(nil, "Internal server error").Render(context.Background(), w)
+ }
+}
+
+func (server *LockServer) handlePostLogout(w http.ResponseWriter, r *http.Request) {
+ server.sessions.EndSession(w, r)
+ utils.HtmxRedirect(w, "/")
+}
+
+func main() {
+ log.Println("Starting...")
+
+ db, err := sql.Open("sqlite3", "file:lock.db")
+ if err != nil {
+ log.Fatalln(err)
+ }
+ defer db.Close()
+
+ if err := InitializeDatabase(db); err != nil {
+ log.Fatalln(err)
+ }
+
+ myServer := LockServer{db: db, sessions: MakeSessions()}
+
+ fs := http.FileServer(http.Dir("static/"))
+ http.Handle("GET /static/", http.StripPrefix("/static/", fs))
+
+ http.HandleFunc("POST /login", myServer.handlePostLogin)
+ http.HandleFunc("POST /logout", myServer.handlePostLogout)
+ http.HandleFunc("GET /lock/{lockId}", myServer.handleLock)
+ http.HandleFunc("POST /lock/{lockId}/addTime", myServer.handleLockAddTime)
+ http.HandleFunc("POST /api/lock/{lockId}/addTime", myServer.handleApiLockAddTime)
+ http.HandleFunc("DELETE /lock/{lockId}", myServer.handleDeleteLock)
+ http.HandleFunc("POST /lock/{lockId}/resetToken", myServer.handleResetLockToken)
+ http.HandleFunc("GET /lock/{lockId}/token", myServer.handleGetLockToken)
+ http.HandleFunc("PUT /lock", myServer.handleCreateLock)
+ http.HandleFunc("GET /", myServer.handleMain)
+
+ log.Println("Started!")
+ http.ListenAndServe("0.0.0.0:80", nil)
+}