# Implementation Plan: Issue #52 - FileLinkService + FileExplorer component ## Summary Implement a FileLinkService for managing Nextcloud file folder links per member/Lager, with configurable path patterns, auto-create toggle, and a FileExplorer Vue component that displays folder contents inline on the member detail page "Dateien" tab. This integrates with Nextcloud's `IRootFolder` for file system operations. ## Files to Create/Modify ### New Files 1. **`lib/Service/FileLinkService.php`** - Core service for member/Lager folder path computation, folder listing, and auto-creation via `IRootFolder` 2. **`lib/Controller/FileController.php`** - REST controller for file browsing and settings endpoints 3. **`src/components/FileExplorer.vue`** - Vue 3 inline file browser component for member detail "Dateien" tab 4. **`src/stores/files.js`** - Pinia store for file browsing state 5. **`tests/Unit/FileLinkServiceTest.php`** - Unit tests for path computation and validation ### Modified Files 6. **`appinfo/routes.php`** - Add file-related API routes 7. **`src/views/MemberDetail.vue`** - Wire up "Dateien" tab to use FileExplorer component instead of placeholder ## Step-by-Step Plan ### Step 1: Create FileLinkService.php - Constructor: inject `IRootFolder`, `IConfig` (for app settings), `LoggerInterface` - `getMemberFolderPath(Member $member): string` - compute path from configurable base + pattern (e.g., `/Verein/Mitglieder/{Nachname}_{Vorname}/`) - `ensureMemberFolder(Member $member): bool` - create folder if auto-create enabled and folder doesn't exist; return true if folder exists after call - `listFolderContents(string $path): array` - list files in a folder via `IRootFolder->getUserFolder()`, return array of `[name, size, mtime, mimetype, path]` - `getMemberFiles(Member $member): array` - convenience: compute path + list contents - `getLagerFolderPath(Lager $lager): string` - same pattern for Lager folders - Settings helpers: `getBasePath(): string`, `getSubfolderPattern(): string`, `isAutoCreateEnabled(): bool`, `getLagerBasePath(): string` - Settings are stored via `IConfig::setAppValue/getAppValue` with keys: - `file_base_path` (default: `/Verein/Mitglieder/`) - `file_subfolder_pattern` (default: `{Nachname}_{Vorname}`) - `file_auto_create` (default: `false`) - `file_lager_base_path` (default: `/Verein/Lager/`) ### Step 2: Create FileController.php Following exact pattern from LagerController: - Constructor: inject `FileLinkService`, `IRequest`, `LoggerInterface` - `getSettings()` - GET `/api/v1/files/settings` - return current file settings - `updateSettings()` - PUT `/api/v1/files/settings` - update file settings (admin only) - `memberFiles(int $memberId)` - GET `/api/v1/members/{memberId}/files` - list member folder contents - `ensureFolder(int $memberId)` - POST `/api/v1/members/{memberId}/files/ensure-folder` - create member folder if auto-create - `lagerFiles(int $lagerId)` - GET `/api/v1/lager/{lagerId}/files/browse` - browse Lager folder ### Step 3: Add Routes to appinfo/routes.php Add file-related routes in a new section: ```php // -- Files integration --- ['name' => 'file#getSettings', 'url' => '/api/v1/files/settings', 'verb' => 'GET'], ['name' => 'file#updateSettings', 'url' => '/api/v1/files/settings', 'verb' => 'PUT'], ['name' => 'file#memberFiles', 'url' => '/api/v1/members/{memberId}/files', 'verb' => 'GET'], ['name' => 'file#ensureFolder', 'url' => '/api/v1/members/{memberId}/files/ensure-folder', 'verb' => 'POST'], ['name' => 'file#lagerFiles', 'url' => '/api/v1/lager/{lagerId}/files/browse', 'verb' => 'GET'], ``` ### Step 4: Create Pinia store (src/stores/files.js) - State: `files`, `loading`, `error`, `folderExists`, `settings` - Actions: `fetchMemberFiles(memberId)`, `ensureMemberFolder(memberId)`, `fetchSettings()`, `updateSettings(settings)` - Uses axios pattern consistent with other stores (e.g., members.js) ### Step 5: Create FileExplorer.vue component - Props: `memberId` (Number, required) - On mount: fetch member files via store - Display: table with columns: Dateiname, Groesse, Geaendert - Click file: open in NC Files (new tab via `/apps/files/?dir=...&openfile=...`) - "In Nextcloud Files oeffnen" button: navigate to folder in NC Files - Warning banner when folder doesn't exist + "Ordner erstellen" button (calls ensureFolder) - Loading spinner while fetching - Empty state when folder exists but is empty - Uses `@nextcloud/vue` NcButton, NcLoadingIcon, NcEmptyContent ### Step 6: Wire MemberDetail.vue "Dateien" tab - Import FileExplorer component - In the tab content section, when `activeTab === 'files'`, render `` - Remove the placeholder "Wird in einem spaeteren Milestone implementiert" for the files tab ### Step 7: Create Unit Test - Test `getMemberFolderPath()` with various patterns - Test path pattern substitution - Test settings defaults ## Acceptance Criteria Verification Checklist 1. [ ] FileLinkService.php exists with getMemberFolderPath, ensureMemberFolder, listFolderContents, getMemberFiles, getLagerFolderPath methods 2. [ ] FileController.php exists with getSettings, updateSettings, memberFiles, ensureFolder, lagerFiles endpoints 3. [ ] Routes added to appinfo/routes.php for all 5 file endpoints 4. [ ] FileExplorer.vue component renders file list with name, size, modified date columns 5. [ ] FileExplorer.vue shows warning state when folder doesn't exist 6. [ ] FileExplorer.vue has "In Nextcloud Files oeffnen" button that generates correct NC Files URL 7. [ ] MemberDetail.vue "Dateien" tab renders FileExplorer instead of placeholder 8. [ ] Settings configurable: base path, subfolder pattern, auto-create toggle, Lager base path 9. [ ] Pinia store (files.js) manages state for file browsing 10. [ ] Unit test validates path computation logic