feat: add account and card lifecycle controls
Test and publish / verify (push) Successful in 2m31s

This commit is contained in:
2026-07-29 12:50:13 +03:30
parent eba6b3565c
commit 71d253fb03
6 changed files with 502 additions and 33 deletions
+54 -3
View File
@@ -96,6 +96,7 @@ type PageData struct {
ReportTotals ReportTotals
Board []BoardColumn
BoardTags []BoardTag
ArchivedTasks []BoardTask
}
type Stats struct{ Present, Remote, Leave int }
@@ -270,6 +271,8 @@ func (s *Server) routes(mux *http.ServeMux) {
mux.HandleFunc("GET /board", s.requireAuth(s.boardPage))
mux.HandleFunc("POST /board/tasks", s.requireAuth(s.csrf(s.createBoardTask)))
mux.HandleFunc("POST /board/tasks/{id}/move", s.requireAuth(s.csrf(s.moveBoardTask)))
mux.HandleFunc("POST /board/tasks/{id}/archive", s.requireAuth(s.csrf(s.archiveBoardTask)))
mux.HandleFunc("POST /board/tasks/{id}/restore", s.requireAuth(s.csrf(s.restoreBoardTask)))
mux.HandleFunc("POST /board/tasks/{id}/delete", s.requireAuth(s.csrf(s.deleteBoardTask)))
mux.HandleFunc("POST /board/tasks/{id}/tags", s.requireAuth(s.csrf(s.setBoardTaskTags)))
mux.HandleFunc("POST /board/tags", s.requireAuth(s.csrf(s.createBoardTag)))
@@ -278,6 +281,7 @@ func (s *Server) routes(mux *http.ServeMux) {
mux.HandleFunc("POST /admin/requests/{id}/review", s.requireAdmin(s.csrf(s.reviewRequest)))
mux.HandleFunc("GET /admin/users", s.requireAdmin(s.usersPage))
mux.HandleFunc("POST /admin/users", s.requireAdmin(s.csrf(s.createUser)))
mux.HandleFunc("POST /admin/users/{id}/status", s.requireAdmin(s.csrf(s.setUserStatus)))
mux.HandleFunc("GET /reports", s.requireAdmin(s.reportPage))
mux.HandleFunc("GET /reports/attendance.csv", s.requireAdmin(s.reportCSV))
mux.HandleFunc("GET /reports/work-updates.csv", s.requireAdmin(s.workUpdatesCSV))
@@ -744,7 +748,12 @@ func (s *Server) boardPage(w http.ResponseWriter, r *http.Request) {
http.Error(w, "could not load team board", http.StatusInternalServerError)
return
}
users, err := s.store.Users()
archivedTasks, err := s.store.ArchivedBoardTasks()
if err != nil {
http.Error(w, "could not load archived cards", http.StatusInternalServerError)
return
}
users, err := s.store.ActiveUsers()
if err != nil {
http.Error(w, "could not load teammates", http.StatusInternalServerError)
return
@@ -770,7 +779,8 @@ func (s *Server) boardPage(w http.ResponseWriter, r *http.Request) {
}
s.render(w, "board.html", PageData{
Title: "Team board", User: currentUser(r), CSRF: csrfToken(r), Users: users, Board: columns, BoardTags: tags,
TodayJalali: jalali.FromTime(time.Now()).String(), Error: r.URL.Query().Get("error"),
ArchivedTasks: archivedTasks,
TodayJalali: jalali.FromTime(time.Now()).String(), Error: r.URL.Query().Get("error"),
Flash: r.URL.Query().Get("flash"), SelectedSection: "board",
})
}
@@ -848,7 +858,29 @@ func (s *Server) deleteBoardTask(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/board?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
http.Redirect(w, r, "/board?flash=Task+removed", http.StatusSeeOther)
http.Redirect(w, r, "/board?flash=Task+permanently+deleted", http.StatusSeeOther)
}
func (s *Server) archiveBoardTask(w http.ResponseWriter, r *http.Request) {
s.setBoardTaskArchived(w, r, true)
}
func (s *Server) restoreBoardTask(w http.ResponseWriter, r *http.Request) {
s.setBoardTaskArchived(w, r, false)
}
func (s *Server) setBoardTaskArchived(w http.ResponseWriter, r *http.Request, archived bool) {
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
user := currentUser(r)
if err := s.store.SetBoardTaskArchived(id, user.ID, user.Role == "admin", archived); err != nil {
http.Redirect(w, r, "/board?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
message := "Task+restored"
if archived {
message = "Task+archived"
}
http.Redirect(w, r, "/board?flash="+message, http.StatusSeeOther)
}
func (s *Server) setBoardTaskTags(w http.ResponseWriter, r *http.Request) {
@@ -920,6 +952,25 @@ func (s *Server) createUser(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/admin/users?flash=Teammate+account+created", http.StatusSeeOther)
}
func (s *Server) setUserStatus(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
action := r.FormValue("action")
if action != "lock" && action != "unlock" {
http.Redirect(w, r, "/admin/users?error=Choose+a+valid+account+action", http.StatusSeeOther)
return
}
active := action == "unlock"
if err := s.store.SetUserActive(id, currentUser(r).ID, active); err != nil {
http.Redirect(w, r, "/admin/users?error="+url.QueryEscape(err.Error()), http.StatusSeeOther)
return
}
message := "Account+locked"
if active {
message = "Account+unlocked"
}
http.Redirect(w, r, "/admin/users?flash="+message, http.StatusSeeOther)
}
func (s *Server) reportPage(w http.ResponseWriter, r *http.Request) {
now := time.Now()
start := now.AddDate(0, -1, 0).Format("2006-01-02")