diff options
Diffstat (limited to 'locker.go')
-rw-r--r-- | locker.go | 323 |
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) +} |