b29a268b1d
- Move completed plan files to .plans/done/ - Move 18 open plan files to .plans/open/ - Update .gitignore to exclude .verified_plans temp file - Verified all 18 open plans still describe unimplemented issues
92 lines
5.8 KiB
Markdown
92 lines
5.8 KiB
Markdown
# 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 `<FileExplorer :member-id="Number(id)" />`
|
|
- 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
|