package files import ( "context" "errors" "fmt" "github.com/google/uuid" "github.com/jackc/pgx/v5" ) // Auflösung von Pfaden zu Ordnern – gebraucht von WebDAV, das über Pfade // adressiert statt über IDs. // ── WebDAV helpers ──────────────────────────────────────────────────────────── // FolderByPath resolves a path like ["photos", "2024"] to a Folder. // Returns a zero-ID folder for an empty parts slice (root). func (s *Store) FolderByPath(ctx context.Context, principalID uuid.UUID, parts []string) (*Folder, error) { return s.FolderByPathFrom(ctx, principalID, nil, parts) } // FolderByPathFrom löst Pfadteile ab einem bestimmten Ordner auf. base == nil // heißt: ab der Wurzel des Prinzipals. // // Der Einstiegspunkt ist nötig, weil WebDAV geteilte Ordner unter einem // virtuellen Pfad einhängt: Dort beginnt der Weg nicht bei der Wurzel des // Eigentümers, sondern genau bei dem Ordner, der freigegeben wurde – alles // darüber geht den Empfänger nichts an. func (s *Store) FolderByPathFrom(ctx context.Context, principalID uuid.UUID, base *uuid.UUID, parts []string) (*Folder, error) { if len(parts) == 0 { if base == nil { return &Folder{OwnerID: &principalID, Name: "/"}, nil } return s.folderByID(ctx, *base) } parentID := base var current *Folder for _, part := range parts { var f Folder var err error if parentID == nil { err = s.db.QueryRow(ctx, `SELECT id, owner_id, group_id, parent_id, name, created_at FROM folders WHERE (owner_id = $1 OR group_id = $1) AND parent_id IS NULL AND name=$2`, principalID, part, ).Scan(&f.ID, &f.OwnerID, &f.GroupID, &f.ParentID, &f.Name, &f.CreatedAt) } else { err = s.db.QueryRow(ctx, `SELECT id, owner_id, group_id, parent_id, name, created_at FROM folders WHERE (owner_id = $1 OR group_id = $1) AND parent_id=$2 AND name=$3`, principalID, parentID, part, ).Scan(&f.ID, &f.OwnerID, &f.GroupID, &f.ParentID, &f.Name, &f.CreatedAt) } if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, fmt.Errorf("folder by path: %w", err) } parentID = &f.ID current = &f } return current, nil } func (s *Store) folderByID(ctx context.Context, id uuid.UUID) (*Folder, error) { var f Folder err := s.db.QueryRow(ctx, `SELECT id, owner_id, group_id, parent_id, name, created_at FROM folders WHERE id = $1`, id, ).Scan(&f.ID, &f.OwnerID, &f.GroupID, &f.ParentID, &f.Name, &f.CreatedAt) if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } if err != nil { return nil, fmt.Errorf("folder by id: %w", err) } return &f, nil } // FileInFolder finds a non-deleted file by name within a folder (nil = root). func (s *Store) FileInFolder(ctx context.Context, ownerID uuid.UUID, folderID *uuid.UUID, name string) (*File, error) { var f File var err error if folderID == nil { err = scanFile(s.db.QueryRow(ctx, `SELECT `+fileCols+` FROM files WHERE (owner_id = $1 OR group_id = $1) AND folder_id IS NULL AND name=$2 AND is_deleted=false`, ownerID, name, ), &f) } else { err = scanFile(s.db.QueryRow(ctx, `SELECT `+fileCols+` FROM files WHERE (owner_id = $1 OR group_id = $1) AND folder_id=$2 AND name=$3 AND is_deleted=false`, ownerID, folderID, name, ), &f) } if errors.Is(err, pgx.ErrNoRows) { return nil, ErrNotFound } return &f, err }