Files
Aether/backend/internal/admin/groupfolder_handler.go
T
HoneyandClaude Opus 5 31a027c93a feat(aether): initial commit – self-hosted Cloud, Phasen 1–7
Go-Backend (chi, pgx, golang-migrate) + React/TS-Frontend als PWA.
Enthalten: Auth inkl. OIDC, Dateien mit Versionen und Papierkorb,
Freigaben (Links + intern), WebDAV, Gruppenordner, Admin/Theming,
Volltextsuche mit OCR, eigener PDF-Betrachter und WOPI-Office
(Collabora/OnlyOffice).

Nicht im Repo: deploy/.env (Sitzungsschlüssel, DB- und Admin-Passwort)
sowie alle Daten- und Build-Verzeichnisse – siehe .gitignore.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-24 09:25:36 +02:00

116 lines
3.5 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 admin
import (
"encoding/json"
"errors"
"net/http"
"strings"
"github.com/go-chi/chi/v5"
"github.com/google/uuid"
"github.com/rs/zerolog/log"
"aether/internal/audit"
"aether/internal/files"
"aether/pkg/response"
)
// Gruppenordner werden ausschließlich hier verwaltet. Bewusst nicht in der
// Dateiansicht: Ein Gruppenordner gehört keinem der Mitglieder, also soll ihn
// auch keines von ihnen entfernen können.
// GET /api/v1/admin/group-folders
func (h *Handler) ListGroupFolders(w http.ResponseWriter, r *http.Request) {
if _, ok := requireAdmin(r); !ok {
response.Error(w, http.StatusForbidden, "admin required")
return
}
list, err := h.files.ListGroupFolders(r.Context())
if err != nil {
response.Error(w, http.StatusInternalServerError, "could not load group folders")
return
}
response.JSON(w, http.StatusOK, list)
}
// POST /api/v1/admin/group-folders
func (h *Handler) CreateGroupFolder(w http.ResponseWriter, r *http.Request) {
if _, ok := requireAdmin(r); !ok {
response.Error(w, http.StatusForbidden, "admin required")
return
}
var body struct {
GroupID string `json:"group_id"`
Name string `json:"name"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
response.Error(w, http.StatusBadRequest, "invalid body")
return
}
groupID, err := uuid.Parse(body.GroupID)
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid group_id")
return
}
// Gleiche Namensregeln wie überall – der Name wird Teil des Ablagepfads.
name := files.SanitizeFilename(strings.TrimSpace(body.Name))
if name == "" {
response.Error(w, http.StatusBadRequest, "name required")
return
}
folder, err := h.files.CreateGroupFolder(r.Context(), groupID, name)
if errors.Is(err, files.ErrGroupFolderExists) {
response.Error(w, http.StatusConflict, "this group already has a folder")
return
}
if errors.Is(err, files.ErrNameTaken) {
response.Error(w, http.StatusConflict, "a folder with that name already exists")
return
}
if err != nil {
response.Error(w, http.StatusInternalServerError, "could not create group folder")
return
}
h.logAdmin(r, audit.ActionGroupFolderCreate, "folder", folder.ID.String(), folder.Name,
"group "+groupID.String())
response.JSON(w, http.StatusCreated, folder)
}
// DELETE /api/v1/admin/group-folders/{id}
func (h *Handler) DeleteGroupFolder(w http.ResponseWriter, r *http.Request) {
if _, ok := requireAdmin(r); !ok {
response.Error(w, http.StatusForbidden, "admin required")
return
}
id, err := uuid.Parse(chi.URLParam(r, "id"))
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid folder id")
return
}
groupID, err := h.files.DeleteGroupFolderTree(r.Context(), id)
if errors.Is(err, files.ErrNotFound) {
response.Error(w, http.StatusNotFound, "group folder not found")
return
}
if err != nil {
response.Error(w, http.StatusInternalServerError, "could not delete group folder")
return
}
// Speicher erst nach dem Datenbank-Commit, und am Stück statt Blob für
// Blob: Da eine Gruppe genau einen Ordner hat, ist `groups/<id>` exakt ihr
// Bereich – inklusive Papierkorb und Versionen. Einzeln gelöscht blieben
// leere Verzeichnisse zurück.
if err := h.storage.DeletePrefix(r.Context(), "groups/"+groupID.String()); err != nil {
log.Ctx(r.Context()).Warn().Err(err).Str("group_id", groupID.String()).
Msg("group folder deleted but storage remains")
}
h.logAdmin(r, audit.ActionGroupFolderDelete, "folder", id.String(), "",
"group "+groupID.String()+" and all its files")
w.WriteHeader(http.StatusNoContent)
}