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("", lock.Token, ""))) } 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:8080", nil) }