mitbringsl/backend/internal/httpapi/me.go
Tronax 729865be78
Feature: Settings screen with account, theme, profile, reset + credits
Backend:
- New GET/PUT /api/me endpoint to read and update the authenticated
  user's profile (currently display_name). UserStore.UpdateDisplayName.
- Wired into the authed router.

App:
- New SettingsScreen + SettingsViewModel with five sections:
  * Account & Sync: login status, email, server URL, logout, connect.
  * Profile: edit display name (pushed to PUT /api/me when logged in).
  * Appearance: System / Light / Dark theme switch, persisted in
    SessionManager and applied via MitbringslTheme(sessionManager).
  * Data: 'Reset local data' wipes Room tables (server data kept).
  * About: version, 'Developed by Janik Dietz', and credits to
    GLM-5.2 + Gemini 3.6 Flash.
- Theme.kt now reads the user's theme preference (StateFlow) instead
  of only the system default; MainActivity passes SessionManager in.
- Navigation: new SettingsNavKey; settings gear icon in ListsScreen
  top bar; Settings links back to the Sync/Account screen.
- DTOs/API: UpdateMeRequestDto + getMe()/updateMe() for /api/me.
2026-08-06 10:39:12 +02:00

76 lines
2.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package httpapi
import (
"log/slog"
"net/http"
"strings"
"github.com/mitbringsl/backend/internal/auth"
)
// MeHandler exposes the authenticated user's own profile (/api/me).
type MeHandler struct {
users *auth.UserStore
}
// NewMeHandler constructs a MeHandler.
func NewMeHandler(users *auth.UserStore) *MeHandler {
return &MeHandler{users: users}
}
type updateMeRequest struct {
DisplayName *string `json:"display_name"` // nil = unchanged, "" = clear
}
// Get returns the current user's profile.
// GET /api/me
func (h *MeHandler) Get(w http.ResponseWriter, r *http.Request) {
userID, ok := userIDFromCtx(r)
if !ok {
renderError(w, http.StatusUnauthorized, "Unauthorized", "Missing user context.")
return
}
u, err := h.users.GetUserByID(r.Context(), userID)
if err != nil {
slog.Error("get me failed", "error", err, "user_id", userID)
renderError(w, http.StatusInternalServerError, "Internal error", "Could not load user.")
return
}
renderJSON(w, http.StatusOK, toUserDTO(u))
}
// Update changes editable fields of the current user (currently display_name).
// PUT /api/me
func (h *MeHandler) Update(w http.ResponseWriter, r *http.Request) {
userID, ok := userIDFromCtx(r)
if !ok {
renderError(w, http.StatusUnauthorized, "Unauthorized", "Missing user context.")
return
}
var req updateMeRequest
if !decodeJSON(w, r, &req) {
return
}
// Only update when display_name is provided in the body (pointer non-nil).
// Trimming to keep stored names tidy; an empty string clears the field.
if req.DisplayName == nil {
// Nothing to change return the current profile.
u, err := h.users.GetUserByID(r.Context(), userID)
if err != nil {
renderError(w, http.StatusInternalServerError, "Internal error", "Could not load user.")
return
}
renderJSON(w, http.StatusOK, toUserDTO(u))
return
}
name := strings.TrimSpace(*req.DisplayName)
u, err := h.users.UpdateDisplayName(r.Context(), userID, name)
if err != nil {
slog.Error("update me failed", "error", err, "user_id", userID)
renderError(w, http.StatusInternalServerError, "Internal error", "Could not update user.")
return
}
renderJSON(w, http.StatusOK, toUserDTO(u))
}