package repository import ( "context" "database/sql" "fmt" "rag/file-system/internal/domain/model" ) type FolderRepoImpl struct { db *sql.DB } func NewFolderRepository(db *sql.DB) *FolderRepoImpl { return &FolderRepoImpl{db: db} } func (r *FolderRepoImpl) Create(ctx context.Context, folder *model.Folder) error { _, err := r.db.ExecContext(ctx, `INSERT INTO folders (id, parent_id, name, owner_id, created_at, updated_at) VALUES ($1, $2, $3, $4, $5, $6)`, folder.ID, folder.ParentID, folder.Name, folder.OwnerID, folder.CreatedAt, folder.UpdatedAt) if err != nil { return fmt.Errorf("failed to create folder: %w", err) } return nil } func (r *FolderRepoImpl) GetByID(ctx context.Context, id string) (*model.Folder, error) { var f model.Folder var parentID sql.NullString err := r.db.QueryRowContext(ctx, `SELECT id, parent_id, name, owner_id, created_at, updated_at FROM folders WHERE id = $1`, id). Scan(&f.ID, &parentID, &f.Name, &f.OwnerID, &f.CreatedAt, &f.UpdatedAt) if err == sql.ErrNoRows { return nil, nil } if err != nil { return nil, fmt.Errorf("failed to get folder: %w", err) } if parentID.Valid { f.ParentID = &parentID.String } return &f, nil } func (r *FolderRepoImpl) GetWithChildren(ctx context.Context, id string, ownerID string) (*model.FolderWithChildren, error) { folder, err := r.GetByID(ctx, id) if err != nil { return nil, err } if folder == nil || folder.OwnerID != ownerID { return nil, nil } subFolders, err := r.queryFolders(ctx, `SELECT id, parent_id, name, owner_id, created_at, updated_at FROM folders WHERE parent_id = $1 AND owner_id = $2 ORDER BY name`, id, ownerID) if err != nil { return nil, err } files, err := r.queryFiles(ctx, `SELECT id, folder_id, name, s3_key, s3_bucket, size, content_type, owner_id, created_at, updated_at FROM files WHERE folder_id = $1 AND owner_id = $2 ORDER BY name`, id, ownerID) if err != nil { return nil, err } return &model.FolderWithChildren{ Folder: *folder, SubFolders: subFolders, Files: files, }, nil } func (r *FolderRepoImpl) GetTree(ctx context.Context, ownerID string) ([]model.Folder, error) { return r.queryFolders(ctx, `SELECT id, parent_id, name, owner_id, created_at, updated_at FROM folders WHERE owner_id = $1 ORDER BY name`, ownerID) } func (r *FolderRepoImpl) Update(ctx context.Context, folder *model.Folder) error { _, err := r.db.ExecContext(ctx, `UPDATE folders SET name = $1, updated_at = $2 WHERE id = $3`, folder.Name, folder.UpdatedAt, folder.ID) if err != nil { return fmt.Errorf("failed to update folder: %w", err) } return nil } func (r *FolderRepoImpl) Delete(ctx context.Context, id string, ownerID string) error { result, err := r.db.ExecContext(ctx, `DELETE FROM folders WHERE id = $1 AND owner_id = $2`, id, ownerID) if err != nil { return fmt.Errorf("failed to delete folder: %w", err) } rows, _ := result.RowsAffected() if rows == 0 { return fmt.Errorf("folder not found or not owned by user") } return nil } func (r *FolderRepoImpl) GetDescendantFileS3Keys(ctx context.Context, id string, ownerID string) ([]model.FileMeta, error) { query := ` WITH RECURSIVE descendants AS ( SELECT id FROM folders WHERE id = $1 AND owner_id = $2 UNION SELECT f.id FROM folders f INNER JOIN descendants d ON f.parent_id = d.id ) SELECT fi.id, fi.folder_id, fi.name, fi.s3_key, fi.s3_bucket, fi.size, fi.content_type, fi.owner_id, fi.created_at, fi.updated_at FROM files fi INNER JOIN descendants d ON fi.folder_id = d.id WHERE fi.owner_id = $2` return r.queryFiles(ctx, query, id, ownerID) } func (r *FolderRepoImpl) queryFolders(ctx context.Context, query string, args ...interface{}) ([]model.Folder, error) { rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("failed to query folders: %w", err) } defer rows.Close() var folders []model.Folder for rows.Next() { var f model.Folder var parentID sql.NullString if err := rows.Scan(&f.ID, &parentID, &f.Name, &f.OwnerID, &f.CreatedAt, &f.UpdatedAt); err != nil { return nil, fmt.Errorf("failed to scan folder: %w", err) } if parentID.Valid { f.ParentID = &parentID.String } folders = append(folders, f) } return folders, rows.Err() } func (r *FolderRepoImpl) queryFiles(ctx context.Context, query string, args ...interface{}) ([]model.FileMeta, error) { rows, err := r.db.QueryContext(ctx, query, args...) if err != nil { return nil, fmt.Errorf("failed to query files: %w", err) } defer rows.Close() var files []model.FileMeta for rows.Next() { var f model.FileMeta if err := rows.Scan(&f.ID, &f.FolderID, &f.Name, &f.S3Key, &f.S3Bucket, &f.Size, &f.ContentType, &f.OwnerID, &f.CreatedAt, &f.UpdatedAt); err != nil { return nil, fmt.Errorf("failed to scan file: %w", err) } files = append(files, f) } return files, rows.Err() }