ultisuite-backend/internal/api/users/handlers.go
R3D347HR4Y 1d063237b9
Some checks are pending
CI / Go tests (push) Waiting to run
CI / Integration tests (push) Waiting to run
CI / DB migrations (push) Waiting to run
feat(transcription): integrate Faster Whisper for Jitsi transcriptions
- Added support for Faster Whisper transcription via Jigasi and Skynet.
- Updated .env.example to include new environment variables for transcription settings.
- Enhanced Jitsi Docker Compose configuration to include Skynet and Jigasi services.
- Introduced new API endpoints for managing organizational folders in the drive service.
- Updated Nextcloud initialization script to enable external file mounting.
- Improved error handling and response structures in the drive API.
- Added new properties for organization settings related to transcription and agenda management.
2026-06-12 19:10:18 +02:00

151 lines
4.4 KiB
Go

package users
import (
"encoding/json"
"errors"
"log/slog"
"net/http"
"github.com/go-chi/chi/v5"
"github.com/jackc/pgx/v5"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/ultisuite/ulti-backend/internal/api/apiresponse"
"github.com/ultisuite/ulti-backend/internal/api/apivalidate"
"github.com/ultisuite/ulti-backend/internal/api/middleware"
"github.com/ultisuite/ulti-backend/internal/permission"
platformusers "github.com/ultisuite/ulti-backend/internal/users"
"github.com/ultisuite/ulti-backend/internal/orgpolicy"
)
type Handler struct {
db *pgxpool.Pool
logger *slog.Logger
orgPolicy *orgpolicy.Loader
}
func NewHandler(db *pgxpool.Pool) *Handler {
return &Handler{
db: db,
orgPolicy: orgpolicy.NewLoader(db, nil),
logger: slog.Default().With("component", "users-api"),
}
}
func (h *Handler) Routes() chi.Router {
r := chi.NewRouter()
r.Get("/me", h.Me)
r.Put("/me/avatar", h.PutAvatar)
r.Delete("/me/avatar", h.DeleteAvatar)
return r
}
func (h *Handler) Me(w http.ResponseWriter, r *http.Request) {
claims := middleware.ClaimsFromContext(r.Context())
if claims == nil {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
return
}
state, err := platformusers.GetAccountState(r.Context(), h.db, claims.Sub)
if err != nil {
h.logger.Error("read account state", "error", err)
apivalidate.WriteInternal(w, r)
return
}
role := permission.DeriveAccountRole(state.PlatformAdmin, state.Status)
orgAgenda, err := h.orgPolicy.PublicAgendaPolicy(r.Context())
if err != nil {
h.logger.Warn("read org agenda policy", "error", err)
orgAgenda = orgpolicy.PublicAgendaPolicy{
DefaultThemeMode: "system",
DefaultVideoProvider: "ultimeet",
ConfiguredVideoProviders: []string{"ultimeet"},
}
}
orgDrive, err := h.orgPolicy.PublicDrivePolicy(r.Context())
if err != nil {
h.logger.Warn("read org drive policy", "error", err)
orgDrive = orgpolicy.PublicDrivePolicy{}
}
avatarURL, err := platformusers.GetAvatarURL(r.Context(), h.db, claims.Sub)
if err != nil {
h.logger.Warn("read user avatar", "error", err)
}
payload := map[string]any{
"sub": claims.Sub,
"email": claims.Email,
"name": claims.Name,
"status": state.Status,
"platform_admin": state.PlatformAdmin,
"role": role,
"groups": claims.Groups,
"org_agenda": orgAgenda,
"org_drive": orgDrive,
}
if avatarURL != "" {
payload["avatar_url"] = avatarURL
}
apiresponse.WriteJSON(w, http.StatusOK, payload)
}
func (h *Handler) PutAvatar(w http.ResponseWriter, r *http.Request) {
claims := middleware.ClaimsFromContext(r.Context())
if claims == nil {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
return
}
var body struct {
AvatarURL string `json:"avatar_url"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, "invalid json body", nil)
return
}
if err := platformusers.SetAvatarURL(r.Context(), h.db, claims.Sub, body.AvatarURL); err != nil {
switch {
case errors.Is(err, platformusers.ErrAvatarTooLarge):
apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, "avatar too large (max 512 KiB)", nil)
case errors.Is(err, platformusers.ErrAvatarInvalid):
apiresponse.WriteError(w, r, http.StatusBadRequest, apiresponse.CodeInvalidRequest, "invalid avatar image", nil)
case errors.Is(err, pgx.ErrNoRows):
apivalidate.WriteNotFound(w, r, "user not found")
default:
h.logger.Error("set user avatar", "error", err)
apivalidate.WriteInternal(w, r)
}
return
}
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{
"avatar_url": body.AvatarURL,
})
}
func (h *Handler) DeleteAvatar(w http.ResponseWriter, r *http.Request) {
claims := middleware.ClaimsFromContext(r.Context())
if claims == nil {
apiresponse.WriteError(w, r, http.StatusUnauthorized, apiresponse.CodeAuthUnauthorized, "unauthorized", nil)
return
}
if err := platformusers.ClearAvatarURL(r.Context(), h.db, claims.Sub); err != nil {
if errors.Is(err, pgx.ErrNoRows) {
apivalidate.WriteNotFound(w, r, "user not found")
return
}
h.logger.Error("clear user avatar", "error", err)
apivalidate.WriteInternal(w, r)
return
}
apiresponse.WriteJSON(w, http.StatusOK, map[string]any{"ok": true})
}