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) }