Files
paliad/internal/handlers/users.go
m 460736ad1e refactor(t-paliad-092): rename Go module path patholo → paliad
F-6 from t-paliad-074 architecture audit. The Gitea repo was renamed
m/patholo → mAi/paliad → m/paliad, but go.mod still declared
`mgit.msbls.de/m/patholo` and every internal import echoed the
pre-rebrand name.

Sweep:
- go.mod: module path → mgit.msbls.de/m/paliad
- All *.go files: imports rewritten via sed
- README.md, docs/design-kanzlai-integration.md: mAi/paliad → m/paliad
- Frontend issue-reference comments (mAi/paliad#N → m/paliad#N) in
  i18n.ts, theme.ts, sidebar.ts, app.ts, Sidebar.tsx, PWAHead.tsx,
  global.css

Verified: go build/vet/test ./... clean, bun run build clean,
no remaining mgit.msbls.de/m/patholo or mAi/paliad references
outside docs that intentionally describe the rename history.
2026-04-30 16:46:31 +02:00

116 lines
3.6 KiB
Go

package handlers
import (
"encoding/json"
"errors"
"net/http"
"mgit.msbls.de/m/paliad/internal/auth"
"mgit.msbls.de/m/paliad/internal/services"
)
// GET /api/me — returns the caller's paliad.users row (or 404 if onboarding
// hasn't happened yet). The frontend uses this to gate role-specific UI
// (partner/admin-only delete, partner-only firm_wide_visible checkbox, etc.).
//
// The 404 body includes the caller's JWT email so the onboarding form can
// pre-fill the display name from the email prefix without a second request.
func handleGetMe(w http.ResponseWriter, r *http.Request) {
if !requireDB(w) {
return
}
uid, ok := requireUser(w, r)
if !ok {
return
}
u, err := dbSvc.users.GetByID(r.Context(), uid)
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
return
}
if u == nil {
body := map[string]string{
"error": "no paliad.users row — onboarding required",
}
if claims, ok := auth.ClaimsFromContext(r.Context()); ok {
body["email"] = claims.Email
}
writeJSON(w, http.StatusNotFound, body)
return
}
writeJSON(w, http.StatusOK, u)
}
// PATCH /api/me — mutates the caller's paliad.users row. The settings page
// sends only the fields the user touched; other fields stay as-is. Email is
// *not* updatable here — auth.users owns the email and any attempt to set it
// via this endpoint is ignored (the decode silently drops unknown fields).
func handleUpdateMe(w http.ResponseWriter, r *http.Request) {
if !requireDB(w) {
return
}
uid, ok := requireUser(w, r)
if !ok {
return
}
// Decode into a permissive map first so we can detect (and reject) a
// client that *tried* to change their email — keeping behaviour explicit
// is friendlier than a silent no-op.
var peek map[string]json.RawMessage
if err := json.NewDecoder(r.Body).Decode(&peek); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
return
}
if _, tryingEmail := peek["email"]; tryingEmail {
writeJSON(w, http.StatusBadRequest, map[string]string{
"error": "email cannot be changed — contact an administrator",
})
return
}
var input services.UpdateProfileInput
// Re-serialise and decode into the typed struct so strict field mapping
// applies without a second body read.
if raw, err := json.Marshal(peek); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
return
} else if err := json.Unmarshal(raw, &input); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON"})
return
}
u, err := dbSvc.users.UpdateProfile(r.Context(), uid, input)
if err != nil {
switch {
case errors.Is(err, services.ErrUserNotOnboarded):
writeJSON(w, http.StatusNotFound, map[string]string{
"error": "no paliad.users row — onboarding required",
})
default:
writeJSON(w, http.StatusBadRequest, map[string]string{"error": err.Error()})
}
return
}
writeJSON(w, http.StatusOK, u)
}
// GET /api/users — minimal user list for the collaborator picker. Only callable
// by authenticated users. Response is the full models.User list (email +
// display_name + office + role).
func handleListUsers(w http.ResponseWriter, r *http.Request) {
if !requireDB(w) {
return
}
if _, ok := requireUser(w, r); !ok {
return
}
users, err := dbSvc.users.List(r.Context())
if err != nil {
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
return
}
writeJSON(w, http.StatusOK, users)
}
// Removed — superseded by handleListProjectEvents in projects.go.