Files
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

132 lines
3.8 KiB
Go
Raw Permalink 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 files
import (
"encoding/json"
"errors"
_ "image/gif"
_ "image/jpeg"
_ "image/png"
"net/http"
"github.com/google/uuid"
"aether/internal/auth"
"aether/pkg/response"
)
// Favoriten der Seitenleiste: anlegen, entfernen, sortieren, auf- und
// zuklappen. Der Zustand liegt serverseitig, damit er auf jedem Gerät gleich ist.
// GET /api/v1/favorites
func (h *Handler) ListFavorites(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
favs, err := h.store.ListFavorites(r.Context(), user.ID)
if err != nil {
response.Error(w, http.StatusInternalServerError, "database error")
return
}
if favs == nil {
favs = []*Favorite{}
}
response.JSON(w, http.StatusOK, favs)
}
// POST /api/v1/favorites
func (h *Handler) AddFavorite(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
var body struct {
FolderID string `json:"folder_id"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil || body.FolderID == "" {
response.Error(w, http.StatusBadRequest, "folder_id required")
return
}
folderID, err := uuid.Parse(body.FolderID)
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid folder_id")
return
}
// Lesezugriff genügt: Ein Favorit ist ein Lesezeichen, keine Änderung.
// Die frühere Besitzprüfung schloss Gruppenordner und geteilte Ordner aus –
// sie gehören dem Anfragenden nicht, sind aber genau die, die man sich
// merken will.
if _, ok := h.resolveFolder(w, r, &folderID, false); !ok {
return
}
fav, err := h.store.AddFavorite(r.Context(), user.ID, folderID)
if err != nil {
response.Error(w, http.StatusInternalServerError, "database error")
return
}
response.JSON(w, http.StatusCreated, fav)
}
// DELETE /api/v1/favorites/{folderId}
func (h *Handler) RemoveFavorite(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
folderID, err := parseID(r, "folderId")
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid folder id")
return
}
if err := h.store.RemoveFavorite(r.Context(), user.ID, folderID); errors.Is(err, ErrNotFound) {
response.Error(w, http.StatusNotFound, "favorite not found")
return
} else if err != nil {
response.Error(w, http.StatusInternalServerError, "database error")
return
}
response.NoContent(w)
}
// PATCH /api/v1/favorites/{folderId}/expand
func (h *Handler) SetFavoriteExpanded(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
folderID, err := parseID(r, "folderId")
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid folder id")
return
}
var body struct {
Expanded bool `json:"expanded"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
response.Error(w, http.StatusBadRequest, "invalid body")
return
}
if err := h.store.SetFavoriteExpanded(r.Context(), user.ID, folderID, body.Expanded); errors.Is(err, ErrNotFound) {
response.Error(w, http.StatusNotFound, "favorite not found")
return
} else if err != nil {
response.Error(w, http.StatusInternalServerError, "database error")
return
}
response.NoContent(w)
}
// PATCH /api/v1/favorites/reorder
func (h *Handler) ReorderFavorites(w http.ResponseWriter, r *http.Request) {
user := auth.UserFromContext(r.Context())
var body struct {
FolderIDs []string `json:"folder_ids"`
}
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
response.Error(w, http.StatusBadRequest, "invalid body")
return
}
ids := make([]uuid.UUID, 0, len(body.FolderIDs))
for _, s := range body.FolderIDs {
id, err := uuid.Parse(s)
if err != nil {
response.Error(w, http.StatusBadRequest, "invalid folder_id: "+s)
return
}
ids = append(ids, id)
}
if err := h.store.ReorderFavorites(r.Context(), user.ID, ids); err != nil {
response.Error(w, http.StatusInternalServerError, "database error")
return
}
response.NoContent(w)
}