feat: FileLinkService + FileExplorer component (Closes #52)
This commit was merged in pull request #112.
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user