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
5.8 KiB
5.8 KiB
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
lib/Service/FileLinkService.php- Core service for member/Lager folder path computation, folder listing, and auto-creation viaIRootFolderlib/Controller/FileController.php- REST controller for file browsing and settings endpointssrc/components/FileExplorer.vue- Vue 3 inline file browser component for member detail "Dateien" tabsrc/stores/files.js- Pinia store for file browsing statetests/Unit/FileLinkServiceTest.php- Unit tests for path computation and validation
Modified Files
appinfo/routes.php- Add file-related API routessrc/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 calllistFolderContents(string $path): array- list files in a folder viaIRootFolder->getUserFolder(), return array of[name, size, mtime, mimetype, path]getMemberFiles(Member $member): array- convenience: compute path + list contentsgetLagerFolderPath(Lager $lager): string- same pattern for Lager folders- Settings helpers:
getBasePath(): string,getSubfolderPattern(): string,isAutoCreateEnabled(): bool,getLagerBasePath(): string - Settings are stored via
IConfig::setAppValue/getAppValuewith 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 settingsupdateSettings()- PUT/api/v1/files/settings- update file settings (admin only)memberFiles(int $memberId)- GET/api/v1/members/{memberId}/files- list member folder contentsensureFolder(int $memberId)- POST/api/v1/members/{memberId}/files/ensure-folder- create member folder if auto-createlagerFiles(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:
// -- 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/vueNcButton, 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
- FileLinkService.php exists with getMemberFolderPath, ensureMemberFolder, listFolderContents, getMemberFiles, getLagerFolderPath methods
- FileController.php exists with getSettings, updateSettings, memberFiles, ensureFolder, lagerFiles endpoints
- Routes added to appinfo/routes.php for all 5 file endpoints
- FileExplorer.vue component renders file list with name, size, modified date columns
- FileExplorer.vue shows warning state when folder doesn't exist
- FileExplorer.vue has "In Nextcloud Files oeffnen" button that generates correct NC Files URL
- MemberDetail.vue "Dateien" tab renders FileExplorer instead of placeholder
- Settings configurable: base path, subfolder pattern, auto-create toggle, Lager base path
- Pinia store (files.js) manages state for file browsing
- Unit test validates path computation logic