Initial commit

This commit is contained in:
Igor Martyniuk
2026-06-25 15:53:49 +03:00
commit 1bb0b7e68a
1517 changed files with 218469 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
# File Manager Module
The File Manager module provides authenticated file browsing and file operations for the admin app. It has a backend API for storage operations and a frontend UI for browsing, previewing, uploading, renaming, deleting, duplicating, and editing supported files.
## Source Map
| Area | Path |
| ---------------------- | ------------------------------------------------------------------------------------- |
| Backend module | `backend/app/base_modules/FileManager/` |
| Backend storage config | `backend/config/drive.ts` |
| Backend S3 adapter | `backend/app/base_modules/FileManager/services/storage_service/s3_storage_service.ts` |
| Frontend module | `apps/admin/src/core/FileManager/` |
| Frontend file assets | `apps/admin/public/media/files/` |
| Frontend routes | `apps/admin/src/core/FileManager/config/routing.tsx` |
## Documentation
| Page | Purpose |
| ------------------------- | ---------------------------------------------------------------------------------- |
| [Backend](./backend.md) | API routes, services, storage adapter contract, permissions, and extension points. |
| [Frontend](./frontend.md) | UI structure, React Query and MobX, cache updates, pages, and widgets. |
## Main Capabilities
- Browse folders and files.
- Filter, sort, and paginate directory entries.
- Preview file details and text content for supported extensions.
- Stream raw files for preview or download.
- Create folders.
- Upload files with progress and cancellation support.
- Rename, delete, and duplicate entries.
- Search entries.
- Open an image editor for image files.
- Pick files through reusable frontend picker components.
- Insert files into RichText editor through reusable frontend picker components.
- Use local filesystem storage or S3-compatible storage through Adonis Drive.
## High-Level Flow
```txt
Admin route
-> FileManager frontend page
-> React Query API hook
-> /file-manager backend route
-> FileManagerController
-> FileManagerService
-> StorageService adapter
-> Adonis Drive fs or s3 disk
```
## Data Contract
Backend responses use `Entry` objects:
```ts
type Entry = FileEntry | FolderEntry;
type FileEntry = {
type: "file";
name: string;
path: string;
url: string;
size?: number;
updatedAt?: string;
};
type FolderEntry = {
type: "folder";
name: string;
path: string;
updatedAt?: string;
};
```
The frontend maps DTOs into frontend entities and adds UI-oriented metadata such as `parent` and `extension`.
## Ownership Rules
- Backend owns storage, validation, authorization, path safety, and API response shape.
- Frontend owns navigation, selection, view settings, operation progress, and user interactions.
- React Query owns server state in the frontend.
- MobX stores own local UI and operation state in the frontend.
- Storage adapters must implement `storage_service_interface.ts`; callers should depend on that interface, not a concrete disk implementation.
- The active storage adapter is selected by `DRIVE_DISK`.
## Common Change Guide
| Change | Start Here |
| --------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Add or change an API route | `backend/app/base_modules/FileManager/routes.ts` and `controller/file_manager_controller.ts` |
| Change path validation or naming rules | `services/path_service.ts` |
| Change list filtering, sorting, or pagination | `services/entry_list_service.ts` and frontend query payloads |
| Add or change a storage backend | `services/storage_service/storage_service_interface.ts`, `create_storage_service.ts`, and [Backend storage docs](./backend.md#storage) |
| Add a frontend operation | `apps/admin/src/core/FileManager/data/queries/` and relevant store/widget |
| Add file type icons | `apps/admin/public/media/files/` and `shared/file/fileTypeImportMap.ts` |
| Add preview support | Backend `file_preview_service.ts` and frontend `view/ui/FilePreview/` |
## Planned Work
- Add more non-filesystem storage adapters such as:
- Google Cloud Storage
- Supabase Storage
- Digital Ocean Spaces
- Cloudflare R2
- Add testing notes once module-level backend and frontend tests are established.
- Add libs for rendering large lists.
+368
View File
@@ -0,0 +1,368 @@
# File Manager Backend
The backend module exposes authenticated file operations under `/file-manager`. It validates requests, authorizes actions, normalizes paths, orchestrates file-manager behavior, and delegates raw storage work to a storage adapter.
Source root:
```txt
backend/app/base_modules/FileManager/
```
## Request Flow
```txt
routes.ts
-> controller/file_manager_controller.ts
-> validator/*
-> policy/file_manager_policy.ts
-> services/file_manager_service.ts
-> services/storage_service/storage_service_interface.ts
-> services/storage_service/create_storage_service.ts
-> services/storage_service/fs_storage_service.ts | services/storage_service/s3_storage_service.ts
-> Adonis Drive
```
The controller should stay thin. It handles HTTP concerns: auth policy checks, request validation, path extraction, status codes, and response payload shape.
`FileManagerService` owns business orchestration. Storage adapters own raw persistence operations.
## Routes
All routes are prefixed with `/file-manager` and use `middleware.auth()`.
| Method | Route | Controller Method | Purpose |
| -------- | ------------------ | ----------------- | ------------------------------------------------------- |
| `GET` | `/` | `index` | List root directory entries. |
| `GET` | `/*` | `index` | List entries for a nested directory. |
| `GET` | `/files/details/*` | `show` | Return file metadata and optional text preview content. |
| `GET` | `/files/raw/*` | `raw` | Stream raw file content. |
| `GET` | `/search?q=value` | `search` | Search entries recursively. |
| `POST` | `/folders` | `createFolder` | Create a folder. |
| `POST` | `/files` | `upload` | Upload a file. |
| `POST` | `/files/duplicate` | `duplicate` | Duplicate one or more **file** entries. |
| `PUT` | `/entries/rename` | `rename` | Rename or move an entry within its parent path. |
| `DELETE` | `/*` | `destroy` | Delete an entry. |
## Authorization
Policy file:
```txt
policy/file_manager_policy.ts
```
Controller methods call policy abilities before performing work:
| Ability | Used By |
| ---------- | ---------------------------------------------- |
| `viewList` | List entries, search entries. |
| `view` | File details, raw file stream. |
| `create` | Create folder, upload file, duplicate entries. |
| `update` | Rename entry. |
| `delete` | Delete entry. |
When adding a new operation, add or reuse an explicit policy ability before adding service logic.
## Validation
Validators live in:
```txt
validator/
```
Current validators:
| File | Purpose |
| -------------------------------- | ----------------------------------------------------------------- |
| `list_validator.ts` | Validates filter, sort, order, max results, and pagination token. |
| `create_directory_validator.ts` | Validates parent path and folder name. |
| `rename_entry_validator.ts` | Validates source path and new name. |
| `upload_file_validator.ts` | Validates upload path and file payload. |
| `duplicate_entries_validator.ts` | Validates duplicate source paths. |
| `schemas.ts` | Shared validation schema helpers. |
Path extraction from wildcard routes goes through:
```txt
helpers/get_validated_path_from_request.ts
```
Keep path validation centralized. Avoid parsing wildcard paths directly in controllers or storage adapters.
## Services
### `file_manager_service.ts`
Main orchestration service.
Responsibilities:
- List directory entries with filtering, sorting, and pagination.
- Compose file detail responses with preview content.
- Create directory response entries.
- Validate and move entries.
- Generate unique upload names.
- Generate duplicate copy names.
- Search entries.
- Coordinate storage adapter calls.
### `path_service.ts`
Shared path and filename utility service.
Responsibilities:
- Convert client paths to storage paths.
- Convert storage paths to client paths.
- Join paths safely.
- Get dirname and basename.
- Parse file extension metadata.
- Sanitize filenames.
- Generate unique names.
Use this service for path behavior instead of string manipulation in feature code.
### `entry_list_service.ts`
Applies list behavior to raw storage entries.
Responsibilities:
- Filter entries by category.
- Sort entries.
- Keep folders before files.
- Paginate entries.
- Encode and decode pagination tokens.
Frontend list controls must stay compatible with this service's accepted filter and sort values.
### `file_preview_service.ts`
Adds text preview content to previewable files.
Previewable text extensions are currently defined in:
```txt
services/file_preview_service.ts
```
If a file type needs rich frontend preview, keep the backend response small and stream raw content through `/files/raw/*` when possible.
### `entry_search_service.ts`
Searches entries by path and includes matching parent folders so the frontend can display useful context.
Search receives an abort signal from the controller. Long-running search implementations should respect that signal.
## Storage
Storage abstractions live in:
```txt
services/storage_service/
```
### `storage_service_interface.ts`
Defines the storage adapter contract. Adapters expose raw operations:
- List directory entries.
- List recursive entries.
- Check entry existence.
- Get file metadata.
- Read file content.
- Stream file content.
- Write file.
- Create directory.
- Move entry.
- Delete entry.
- Copy entry.
Services should call the interface, not a concrete adapter.
### `fs_storage_service.ts`
Filesystem storage adapter.
Responsibilities:
- Call the Adonis Drive filesystem disk.
- Map filesystem objects to File Manager entries.
- Handle file streams and metadata.
- Create, move, copy, and delete filesystem entries.
- Preserve filesystem directory marker behavior.
### `s3_storage_service.ts`
S3-compatible storage adapter.
Responsibilities:
- Call the Adonis Drive `s3` disk.
- Map S3 objects and common prefixes to File Manager entries.
- Upload multipart files by streaming the temporary upload file into S3 with `putStream`.
- Read file content and streams from S3.
- Create folders with directory marker objects.
- Move folders by moving every object under the folder prefix.
- Delete files with exact object deletion and folders with folder-prefix deletion.
- Copy file entries.
S3 has no native directories. The adapter uses the same `.keep` marker convention as the filesystem adapter so empty folders can exist.
Folder operations must use slash-terminated prefixes. For example, deleting `test/` must not delete objects such as `test123.txt`.
### `fs_directory_marker_service.ts`
Encapsulates the filesystem directory marker convention.
Current marker:
```txt
.keep
```
Do not duplicate marker logic outside this service. Storage adapters that need virtual folders should use this marker service behind the storage interface.
### `create_storage_service.ts`
Storage service factory.
Current supported storage:
```txt
fs
s3
```
The active adapter is selected by `DRIVE_DISK`.
```env
DRIVE_DISK=fs
```
or:
```env
DRIVE_DISK=s3
```
## Drive Configuration
Adonis Drive config lives in:
```txt
backend/config/drive.ts
```
Configured disks:
| Disk | Driver | Purpose |
| ---- | ------------- | ---------------------------------------------------- |
| `fs` | `services.fs` | Local development and filesystem-backed storage. |
| `s3` | `services.s3` | S3-compatible object storage, including local MinIO. |
The S3 disk expects these environment variables:
| Variable | Local Docker Value | Purpose |
| ----------------------- | --------------------------------------------------------------------- | --------------------------------------------- |
| `AWS_ACCESS_KEY_ID` | `minioadmin` | S3 access key. |
| `AWS_SECRET_ACCESS_KEY` | `minioadmin` | S3 secret key. |
| `AWS_REGION` | `us-east-1` | S3 region. |
| `S3_BUCKET` | `crux-drive` | Bucket used by the file manager. |
| `S3_ENDPOINT` | `http://minio:9000` in Docker, `http://localhost:9000` outside Docker | S3 API endpoint. |
| `S3_CDN_URL` | `http://localhost:9000/crux-drive/` | Browser-facing public URL base for file URLs. |
For MinIO, the S3 disk uses path-style addressing:
```ts
forcePathStyle: true;
```
The adapter also disables ACL API calls:
```ts
supportsACL: false;
```
Bucket public-read behavior is handled by the Docker bucket setup container.
## Local S3 Simulation
Local S3 is simulated with MinIO in:
```txt
docker-compose.yml
```
Services:
| Service | Purpose |
| --------------------- | --------------------------------------------------------------------------------------- |
| `minio` | Runs the S3-compatible object storage server. |
| `minio_create_bucket` | One-shot setup container that creates the bucket and enables anonymous download access. |
MinIO console:
```txt
http://localhost:9001
```
Default local credentials:
```txt
user: minioadmin
pass: minioadmin
```
The `minio_create_bucket` container is expected to exit after setup. A stopped container with successful bucket logs is normal.
To rebuild after dependency or Dockerfile changes:
```sh
docker compose build --no-cache
docker compose up
```
## Response Shape
Directory and file operations return `Entry` objects:
```ts
interface EntryBase {
name: string;
path: string;
updatedAt?: string;
}
interface FileEntry extends EntryBase {
type: "file";
size?: number;
url: string;
}
interface FolderEntry extends EntryBase {
type: "folder";
}
```
List responses include pagination metadata:
```ts
{
data: Entry[];
meta: {
nextPaginationToken: string | null;
};
}
```
## Backend Change Checklist
Before changing backend behavior:
1. Confirm the policy ability for the operation.
2. Add or update a validator for all request input.
3. Keep client paths and storage paths separated through `path_service.ts`.
4. Update the frontend DTO or mapper if response shape changes.
5. Update this documentation when routes, permissions, storage contracts, or response shapes change.
+439
View File
@@ -0,0 +1,439 @@
# File Manager Frontend
The frontend module implements the admin file manager UI. It provides file browsing, previews, uploads, deletion, duplication, renaming, image editing, settings, and reusable file picker widgets.
This module follows the general [React frontend architecture](../../apps-architecture/react-frontend/Readme.md) inside its own module boundary.
Source root:
```txt
apps/admin/src/core/FileManager/
```
## Main Principle
React Query owns server state. MobX owns local UI and operation state.
Server state examples:
- Directory entries.
- File details.
- Raw file blobs.
- Search results.
- Create, rename, delete, duplicate, and upload mutations.
Local state examples:
- Active path.
- Selected paths.
- Expanded tree paths.
- Filter and sort controls.
- Dialog open state.
- Upload and delete operation progress.
- Cached lookup of already loaded entries for UI behavior.
## Directory Structure
```txt
FileManager/
config/ Routes, menu entries, messages, translations.
data/
api/ Raw HTTP methods and DTOs.
hooks/ Feature hooks that combine queries with stores.
mappers/ DTO-to-entity mapping.
queries/ React Query hooks, keys, mutations, and cache helpers.
stores/ MobX local state stores.
entities/ Domain entities and settings types.
providers/ Feature providers and context hooks.
pages/ Route-level screens.
shared/ FileManager utilities that do not depend on view code.
view/ UI components, widgets, forms, and layouts.
```
## Routes
Frontend route config:
```txt
config/routing.tsx
```
Mounted module path:
```txt
/file-manager/*
```
Current child routes:
| Route | Page | Purpose |
| -------------------------------- | ------------------- | ----------------------------------- |
| `/file-manager/drive/explorer/*` | `DriveExplorerPage` | Browse folders and files. |
| `/file-manager/drive/viewer/-/*` | `DriveViewerPage` | View file details and preview. |
| `/file-manager/drive/settings` | `DriveSettingsPage` | Configure file-manager UI settings. |
| `/file-manager/editor/image/-/*` | `ImageEditorPage` | Edit image files. |
Path helpers live in:
```txt
shared/routing/driveLinks.ts
shared/routing/openEntryNewTab.ts
shared/path/leadingSlash.ts
```
Use these helpers instead of manually building file-manager URLs.
## API Boundary
Raw backend calls are centralized in:
```txt
data/api/api.ts
data/api/dto.ts
```
The frontend calls the backend routes documented in [backend.md](./backend.md):
| API Method | Backend Route |
| ----------------- | ------------------------------------ |
| `getEntries` | `GET /file-manager/*` |
| `getFile` | `GET /file-manager/files/details/*` |
| `getRawFile` | `GET /file-manager/files/raw/*` |
| `createDirectory` | `POST /file-manager/folders` |
| `renameEntry` | `PUT /file-manager/entries/rename` |
| `removeEntry` | `DELETE /file-manager/*` |
| `uploadFile` | `POST /file-manager/files` |
| `duplicateFiles` | `POST /file-manager/files/duplicate` |
| `searchEntries` | `GET /file-manager/search` |
DTOs should be mapped before they reach UI code. Entity types live in:
```txt
entities/entry.ts
data/mappers/entryMapper.ts
```
## Data Flow
### Reading Entries
Directory entries are fetched through:
```ts
useGetEntries();
```
The higher-level hook:
```ts
useEntries();
```
does three things:
1. Builds the query payload from `ExplorerStore`.
2. Fetches entries with React Query.
3. Syncs loaded entries into `EntriesStore`.
`EntriesStore` is not the fetch owner. It is an index of loaded entries used by the sidebar tree, selection lookups, active entry lookup, and operation state.
### Deep Links
When opening a nested folder directly, `useEntries()` preloads ancestor folders through React Query. This keeps the sidebar tree and entry index usable for paths like:
```txt
/folder/subfolder/current
```
### Mutations
Mutations use React Query hooks:
- `useCreateDirectory`
- `useRenameEntry`
- `useDeleteEntry`
- `useDuplicateFiles`
- `useUploadFile`
After a successful mutation, the UI updates local cache and stores directly. It does not invalidate entries queries after every operation.
This avoids unnecessary refetches when many items are created, deleted, or uploaded in sequence.
## Query Cache Updates
Entries cache helpers live in:
```txt
data/queries/cache.ts
```
Current helpers:
```ts
addEntriesToCache(parentPath, entries);
removeEntriesFromCache(paths);
renameEntryInCache(prevPath, renamedEntry);
```
These helpers update existing React Query cache data in memory. They do not trigger a network request.
Important behavior:
- Added entries are inserted into cached lists for the matching parent folder.
- Added entries are filter-aware.
- Deleted entries are removed from all cached entry pages.
- Renamed entries are updated across cached entry pages.
Tradeoff: because the module avoids refetching, exact backend sort order may not be perfect after local cache insertion until the folder is naturally refetched or reloaded.
## Stores
### `EntriesStore`
File:
```txt
data/stores/entriesStore.ts
```
Purpose:
- Keep an index of loaded entries by path.
- Provide child lookup by parent path.
- Support selected-entry and active-entry lookup.
- Support local remove and rename updates for already loaded subtrees.
It should not fetch data.
### `ExplorerStore`
File:
```txt
data/stores/explorerStore.ts
```
Purpose:
- Active path.
- Selected paths.
- Expanded tree paths.
- Breadcrumbs.
- Entries filter.
- Sort field and direction.
### `DriveDialogStore`
File:
```txt
data/stores/driveDialogStore.ts
```
Purpose:
- Create-directory dialog state.
- Rename-entry dialog state.
- Current entry selected for rename.
### `UploadOperationStore`
File:
```txt
data/stores/uploadOperationStore.ts
```
Purpose:
- Upload queue.
- Upload progress.
- Upload retry, remove, and abort.
- Upload operation card visibility.
It receives the React Query mutation function from the view layer and manages only operation state.
### `DeleteOperationStore`
File:
```txt
data/stores/deleteOperationStore.ts
```
Purpose:
- Delete queue.
- Delete progress.
- Delete retry and remove.
- Delete operation card visibility.
It receives the React Query mutation function from the view layer and manages only operation state.
### `SettingsStore`
File:
```txt
data/stores/settingsStore.ts
```
Purpose:
- Persisted view mode.
- Compact mode.
- Sidebar visibility.
- Grid size and related display preferences.
## Providers
`DriveProvider` creates and exposes feature stores:
```ts
entriesStore;
explorerStore;
driveDialogStore;
uploadOperationStore;
deleteOperationStore;
```
Use:
```ts
const { entriesStore, explorerStore } = useDrive();
```
`SettingsProvider` owns persisted settings such as view mode, compact mode, sidebar visibility, and grid size.
## Pages And Widgets
### `DriveExplorerPage`
Main file browser page. Composes:
- `DriveProvider`
- Explorer toolbar.
- Explorer content.
- Explorer sidebar.
- Footer.
- Operation watcher.
### `DriveViewerPage`
File details and preview page. Uses:
```ts
useGetFile();
```
### `DriveSettingsPage`
Settings page for persisted file-manager UI preferences.
### `ImageEditorPage`
Image editor page. Uses:
```ts
useGetFile();
useGetRawFile();
useUploadFile();
```
Image editor config and helpers live in the widget layer:
```txt
view/widgets/ImageEditor/config.ts
view/widgets/ImageEditor/helpers.ts
```
This prevents `view` code from importing from `pages`.
### `FilePickerDialog`
Reusable picker widgets live in:
```txt
view/widgets/FilePickerDialog/
```
Keep picker behavior feature-scoped, but pass reusable UI state through props and context instead of coupling low-level UI components to page-level stores.
## View Layer Rules
Feature widgets may use File Manager providers such as `useDrive()` and `useSettings()`.
Lower-level reusable UI components should stay prop-driven. Examples:
- `ExplorerFileEntryGrid`
- `ExplorerFolderEntryList`
- `ExplorerToolbarFilterDropdown`
- `FileManagerFileIcon`
- `FilePreview`
Do not import backend DTOs into view components. View components should consume frontend entities and props.
## File Types And Icons
File helpers live in:
```txt
shared/file/
```
File icons live in:
```txt
apps/admin/public/media/files/
```
To add or change an icon:
1. Add the asset under `apps/admin/public/media/files/`.
2. Update `shared/file/fileTypeImportMap.ts`.
3. Confirm `FileManagerFileIcon` renders the expected fallback for unknown extensions.
## Adding A New Query
1. Add or reuse a method in:
```txt
data/api/api.ts
```
2. Add a key in:
```txt
data/queries/keys.ts
```
3. Create a query file:
```txt
data/queries/example.ts
```
4. Map DTOs to entities before returning app-facing data.
Do not import MobX stores or providers inside `data/queries`.
## Adding A New Mutation
1. Add a raw API method in `data/api/api.ts`.
2. Add a mutation hook in `data/queries`.
3. In the consuming widget or operation store flow, call `mutateAsync`.
4. Update `EntriesStore` if lookup state must change.
5. Update `data/queries/cache.ts` helpers if visible query cache must change.
Do not invalidate entries queries after each operation unless a full refetch is intentionally required.
## Frontend Change Checklist
Before changing frontend behavior:
1. Identify whether the data is server state or local UI state.
2. Keep backend calls in `data/api`.
3. Keep React Query hooks free of MobX provider imports.
4. Map DTOs before passing data to view components.
5. Update cache helpers when mutation results should appear immediately.
6. Update backend docs if the frontend change requires a backend contract change.