feat(phase5): scheduling/availability - DB, API, UI in server settings

This commit is contained in:
2026-06-30 11:29:43 -04:00
parent 74d2153ffa
commit 6db4e3cc2d
6 changed files with 296 additions and 8 deletions
+161
View File
@@ -0,0 +1,161 @@
package auth
import (
"encoding/json"
"net/http"
"time"
"git.dustin.coffee/hobokenchicken/dumpsterChat/internal/middleware"
"github.com/go-chi/chi/v5"
)
type availabilitySlot struct {
DayOfWeek string `json:"day_of_week"`
StartTime string `json:"start_time"` // HH:MM
EndTime string `json:"end_time"` // HH:MM
}
func (h *Handler) GetMyAvailability(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.UserIDFromContext(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
serverID := r.URL.Query().Get("server_id")
if serverID == "" {
http.Error(w, "server_id query param required", http.StatusBadRequest)
return
}
rows, err := h.db.Query(
`SELECT day_of_week, start_time, end_time FROM availability
WHERE user_id = $1 AND server_id = $2
ORDER BY array_position(ARRAY['monday','tuesday','wednesday','thursday','friday','saturday','sunday'], day_of_week), start_time`,
userID, serverID,
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
var slots []availabilitySlot
for rows.Next() {
var s availabilitySlot
var st, et time.Time
if err := rows.Scan(&s.DayOfWeek, &st, &et); err != nil {
continue
}
s.StartTime = st.Format("15:04")
s.EndTime = et.Format("15:04")
slots = append(slots, s)
}
if slots == nil {
slots = []availabilitySlot{}
}
json.NewEncoder(w).Encode(slots)
}
func (h *Handler) PutMyAvailability(w http.ResponseWriter, r *http.Request) {
userID, ok := middleware.UserIDFromContext(r.Context())
if !ok {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
serverID := r.URL.Query().Get("server_id")
if serverID == "" {
http.Error(w, "server_id query param required", http.StatusBadRequest)
return
}
var slots []availabilitySlot
if err := json.NewDecoder(r.Body).Decode(&slots); err != nil {
http.Error(w, "invalid json", http.StatusBadRequest)
return
}
tx, err := h.db.Begin()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer tx.Rollback()
if _, err := tx.Exec(`DELETE FROM availability WHERE user_id = $1 AND server_id = $2`, userID, serverID); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
for _, s := range slots {
_, err := tx.Exec(
`INSERT INTO availability (user_id, server_id, day_of_week, start_time, end_time) VALUES ($1,$2,$3,$4::time,$5::time)`,
userID, serverID, s.DayOfWeek, s.StartTime, s.EndTime,
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
}
if err := tx.Commit(); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(slots)
}
// GetServerAvailability returns all members' availability for a server, aggregated by user.
func (h *Handler) GetServerAvailability(w http.ResponseWriter, r *http.Request) {
serverID := chi.URLParam(r, "serverID")
rows, err := h.db.Query(
`SELECT a.user_id, u.display_name, a.day_of_week, a.start_time, a.end_time
FROM availability a
JOIN users u ON u.id = a.user_id
WHERE a.server_id = $1
ORDER BY u.display_name, array_position(ARRAY['monday','tuesday','wednesday','thursday','friday','saturday','sunday'], a.day_of_week), a.start_time`,
serverID,
)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer rows.Close()
type slot struct {
DayOfWeek string `json:"day_of_week"`
StartTime string `json:"start_time"`
EndTime string `json:"end_time"`
}
type userAvail struct {
UserID string `json:"user_id"`
Username string `json:"username"`
Slots []slot `json:"slots"`
}
userMap := make(map[string]*userAvail)
for rows.Next() {
var uid, uname, dow string
var st, et time.Time
if err := rows.Scan(&uid, &uname, &dow, &st, &et); err != nil {
continue
}
ua, ok := userMap[uid]
if !ok {
ua = &userAvail{UserID: uid, Username: uname}
userMap[uid] = ua
}
ua.Slots = append(ua.Slots, slot{DayOfWeek: dow, StartTime: st.Format("15:04"), EndTime: et.Format("15:04")})
}
result := make([]userAvail, 0, len(userMap))
for _, ua := range userMap {
result = append(result, *ua)
}
if result == nil {
result = []userAvail{}
}
json.NewEncoder(w).Encode(result)
}