Files
Aether/backend/internal/files/store_favorites.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

130 lines
4.3 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 files
import (
"context"
"errors"
"fmt"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// Favoriten der Seitenleiste. Reihenfolge und Auf-/Zugeklappt-Zustand
// liegen serverseitig, damit sie auf jedem Gerät gleich sind.
// ── Favorites ─────────────────────────────────────────────────────────────────
func (s *Store) ListFavorites(ctx context.Context, userID uuid.UUID) ([]*Favorite, error) {
// Nur Favoriten anzeigen, die noch erreichbar sind. Ein Lesezeichen auf
// einen Ordner, den man nicht mehr öffnen darf – weil man die Gruppe
// verlassen hat oder die Freigabe zurückgezogen wurde – bliebe sonst als
// toter Eintrag in der Seitenleiste stehen und liefe beim Klick ins Leere.
rows, err := s.db.Query(ctx,
`WITH RECURSIVE shared_roots AS (
SELECT e.resource_folder_id AS id
FROM acl_entries e
WHERE e.resource_folder_id IS NOT NULL
AND (e.subject_user_id = $1
OR e.subject_group_id IN (SELECT group_id FROM group_members WHERE user_id = $1))
UNION
-- Nach unten aufspannen: Wer einen Ordner freigegeben bekommt, darf
-- auch alles darunter, und darf sich das ebenso merken.
SELECT fo.id FROM folders fo JOIN shared_roots sr ON fo.parent_id = sr.id
)
SELECT f.id, f.user_id, f.folder_id, f.position, f.is_expanded, f.created_at,
fo.name, fo.parent_id
FROM favorites f
JOIN folders fo ON fo.id = f.folder_id
WHERE f.user_id = $1
AND ( fo.owner_id = $1
OR fo.group_id IN (SELECT group_id FROM group_members WHERE user_id = $1)
OR fo.id IN (SELECT id FROM shared_roots) )
ORDER BY f.position`,
userID,
)
if err != nil {
return nil, fmt.Errorf("list favorites: %w", err)
}
defer rows.Close()
favs := make([]*Favorite, 0)
for rows.Next() {
var fav Favorite
if err := rows.Scan(
&fav.ID, &fav.UserID, &fav.FolderID, &fav.Position, &fav.IsExpanded, &fav.CreatedAt,
&fav.FolderName, &fav.FolderParentID,
); err != nil {
return nil, err
}
favs = append(favs, &fav)
}
return favs, rows.Err()
}
func (s *Store) AddFavorite(ctx context.Context, userID, folderID uuid.UUID) (*Favorite, error) {
var fav Favorite
err := s.db.QueryRow(ctx,
`INSERT INTO favorites (user_id, folder_id, position)
VALUES ($1, $2, (SELECT COALESCE(MAX(position)+1, 0) FROM favorites WHERE user_id=$1))
ON CONFLICT (user_id, folder_id) DO NOTHING
RETURNING id, user_id, folder_id, position, is_expanded, created_at`,
userID, folderID,
).Scan(&fav.ID, &fav.UserID, &fav.FolderID, &fav.Position, &fav.IsExpanded, &fav.CreatedAt)
if errors.Is(err, pgx.ErrNoRows) {
return nil, ErrNameTaken // already a favorite
}
if err != nil {
return nil, fmt.Errorf("add favorite: %w", err)
}
// Fetch folder name for response.
var f Folder
_ = s.db.QueryRow(ctx,
`SELECT id, owner_id, group_id, parent_id, name, created_at FROM folders WHERE id=$1`, folderID,
).Scan(&f.ID, &f.OwnerID, &f.GroupID, &f.ParentID, &f.Name, &f.CreatedAt)
fav.FolderName = f.Name
fav.FolderParentID = f.ParentID
return &fav, nil
}
func (s *Store) RemoveFavorite(ctx context.Context, userID, folderID uuid.UUID) error {
tag, err := s.db.Exec(ctx,
`DELETE FROM favorites WHERE user_id=$1 AND folder_id=$2`, userID, folderID)
if err != nil {
return fmt.Errorf("remove favorite: %w", err)
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func (s *Store) SetFavoriteExpanded(ctx context.Context, userID, folderID uuid.UUID, expanded bool) error {
tag, err := s.db.Exec(ctx,
`UPDATE favorites SET is_expanded=$3 WHERE user_id=$1 AND folder_id=$2`,
userID, folderID, expanded,
)
if err != nil {
return fmt.Errorf("set favorite expanded: %w", err)
}
if tag.RowsAffected() == 0 {
return ErrNotFound
}
return nil
}
func (s *Store) ReorderFavorites(ctx context.Context, userID uuid.UUID, folderIDs []uuid.UUID) error {
tx, err := s.db.Begin(ctx)
if err != nil {
return err
}
defer tx.Rollback(ctx)
for i, fid := range folderIDs {
if _, err := tx.Exec(ctx,
`UPDATE favorites SET position=$3 WHERE user_id=$1 AND folder_id=$2`,
userID, fid, i,
); err != nil {
return fmt.Errorf("reorder favorites: %w", err)
}
}
return tx.Commit(ctx)
}