How to Set Up Document Management with File Attachments in TREK: A Complete Guide
Enable the Documents addon in TREK's admin panel, configure file_upload permissions, and use the built-in FilesService API or React useFileManager hook to upload, link, and manage attachments in real-time.
TREK implements document management as a first-class addon called Documents, providing a complete solution for handling file attachments within trip planning workflows. This guide walks through the architecture spanning NestJS 11 backend services and React 19 frontend hooks, showing exactly how to enable and configure document management with file attachments in TREK according to the source code in mauriceboe/TREK.
Backend Architecture: FilesService and REST API
The backend implements a robust file handling layer using NestJS decorators and custom interceptors, storing metadata in SQLite while persisting binaries on the filesystem.
Core Service Implementation
The FilesService class in server/src/nest/files/files.service.ts handles the core business logic for document management. It manages disk storage under the uploads/ directory, where files are saved with UUID-based filenames to prevent collisions. The service performs permission checks against file_upload, file_edit, and file_delete permissions, implements soft-delete/restore functionality, and manages linking files to places or reservations.
Every mutation operation triggers WebSocket broadcasts via the broadcast method, emitting events like file:created, file:updated, or file:deleted to synchronize all connected clients in real-time.
REST Endpoints and Upload Flow
The FilesController in server/src/nest/files/files.controller.ts exposes routes under /api/trips/:tripId/files. The upload flow follows this sequence:
- Request validation: The controller enforces trip ownership, demo-mode restrictions, and MIME type validation before accepting multipart data.
- File interception:
FileInterceptor(configured withUPLOADconstants) saves the binary touploads/<uuid>.<ext>. - Metadata persistence:
FilesService.createFilewrites a row to the SQLitefilestable containingmime_type,original_name, optionalplace_id/reservation_id, and the uploader's user ID. - Real-time sync: A
file:createdWebSocket event fires, allowing the frontend to instantly display the new attachment.
Configuration and Security Constraints
Security constraints are defined in server/src/services/fileService.ts:
- Maximum file size:
MAX_FILE_SIZE = 50 * 1024 * 1024(50 MiB) - Blocked extensions:
.svg,.exe, and other dangerous file types are rejected - Allowed types: Admin-configurable via the
ALLOWED_FILE_TYPESenvironment variable or UI settings
Permissions are declared in server/src/nest/services/permissions.ts with three file-related keys:
file_upload(default:trip_member)file_edit(default:trip_member)file_delete(default:admin)
Frontend Implementation: useFileManager Hook
The React frontend encapsulates file management logic in a dedicated hook that handles drag-and-drop, paste operations, and permission-gated UI controls.
Drag-and-Drop and Upload Handling
The useFileManager hook in client/src/components/Files/useFileManager.ts integrates react-dropzone for drag-and-drop functionality. Key implementation details include:
- Size enforcement: The dropzone respects the 50 MiB limit (
maxSize: 50 * 1024 * 1024) defined in the backend constants. - Paste handling: The hook intercepts clipboard paste events and validates content before upload.
- Auto-assignment: After successful upload, the hook automatically opens the "assign" modal to link the file to a place or reservation (see lines 18-22 of the hook).
The hook communicates with filesApi in client/src/api/client.ts, which provides methods for list, upload, toggleStar, restore, permanentDelete, emptyTrash, and link operations.
Permission Gates and UI Controls
Before enabling upload buttons or delete actions, the UI checks useCanDo from client/src/store/permissionsStore.ts. This utility returns a boolean indicating whether the current user holds the required permission level (trip_member, trip_owner, or admin) for the specific file operation.
The FilesPanel.tsx component renders filter tabs (All, PDF, Images, Docs, Starred, Collab), a lightbox for image previews, and modals for linking files to itinerary items.
Enabling and Configuring the Documents Addon
Document management requires explicit activation and configuration through the admin interface.
Activating the Addon in Admin Panel
Navigate to Admin → Addons and toggle the Documents addon to enable file attachment functionality system-wide. This exposes the Files tab in the trip planner interface and activates the REST endpoints.
Configuring Upload Limits and Allowed File Types
Admins can adjust constraints by modifying server/src/services/fileService.ts:
// server/src/services/fileService.ts
export const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50 MiB default
export const BLOCKED_EXTENSIONS = ['.svg', '.exe', '.bat', '.sh'];
Allowed file types can be configured via environment variables or the Admin-Addons UI page, which writes to the application's configuration store.
Permission Levels for File Operations
Permission defaults are configured in server/src/nest/services/permissions.ts:
// server/src/nest/services/permissions.ts
export const PERMISSIONS = [
{ key: 'file_upload', defaultLevel: 'trip_member', allowedLevels: ['admin', 'trip_owner', 'trip_member'] },
{ key: 'file_edit', defaultLevel: 'trip_member', allowedLevels: ['admin', 'trip_owner', 'trip_member'] },
{ key: 'file_delete', defaultLevel: 'admin', allowedLevels: ['admin'] },
];
Changing defaultLevel or allowedLevels instantly updates the UI because useCanDo reads this runtime configuration to enable or disable action buttons.
Practical Implementation Examples
Uploading and Linking Files via REST API
Use the /api/trips/:tripId/files endpoint to upload and immediately associate files with itinerary items:
# Upload a PDF and link to reservation #42
curl -X POST "http://localhost:3000/api/trips/7/files" \
-H "Authorization: Bearer <jwt>" \
-F "file=@invoice.pdf;type=application/pdf" \
-F "reservation_id=42" \
-H "x-socket-id: abc123"
# Star the uploaded file
curl -X PATCH "http://localhost:3000/api/trips/7/files/23/star" \
-H "Authorization: Bearer <jwt>" \
-H "x-socket-id: abc123"
# List all non-trashed files
curl -X GET "http://localhost:3000/api/trips/7/files" \
-H "Authorization: Bearer <jwt>"
React Component Integration
Implement document management in custom components using the useFileManager hook:
import { useFileManager } from '@/components/Files/useFileManager';
import { filesApi } from '@/api/client';
function TripFiles({ tripId, places, reservations }) {
const { files, uploading, filterType, setFilterType, handleStar, handleDelete } =
useFileManager({
tripId,
places,
reservations,
onUpload: (fd) => filesApi.upload(tripId, fd),
onDelete: (id) => filesApi.remove(tripId, id),
});
return (
<section>
<header>
<select value={filterType} onChange={e => setFilterType(e.target.value)}>
<option value="all">All</option>
<option value="pdf">PDF</option>
<option value="image">Images</option>
<option value="doc">Docs</option>
</select>
</header>
{uploading && <p>Uploading…</p>}
<ul>
{files.map(f => (
<li key={f.id}>
{f.name}
<button onClick={() => handleStar(f.id)}>
{f.starred ? '★' : '☆'}
</button>
<button onClick={() => handleDelete(f.id)}>Trash</button>
</li>
))}
</ul>
</section>
);
}
Customizing Permission Levels
To restrict file deletion to administrators only, modify the permissions array:
// server/src/nest/services/permissions.ts
{
key: 'file_delete',
defaultLevel: 'admin',
allowedLevels: ['admin', 'trip_owner'] // Expand if needed
}
Link existing files to places using the linkFile API wrapper:
// Link to a specific place
import { linkFile } from '@/api/client';
await linkFile(tripId, fileId, { place_id: 12 });
// Hits POST /api/trips/:tripId/files/:fileId/link
Summary
- Enable the Documents addon via Admin → Addons to activate file attachment functionality.
- Configure storage limits in
server/src/services/fileService.ts(default 50 MiB) and define allowed file types. - Set permission levels in
server/src/nest/services/permissions.tsto control who can upload, edit, or delete files. - Use
FilesServicefor backend operations with automatic WebSocket synchronization. - Implement
useFileManagerin React components to handle drag-and-drop, filtering, and permission-gated actions. - Store files on the filesystem under
uploads/with UUID filenames while tracking metadata in SQLite.
Frequently Asked Questions
What file types are supported for upload in TREK?
TREK supports configurable file types defined by the admin via the ALLOWED_FILE_TYPES setting. By default, the system blocks dangerous extensions like .svg, .exe, .bat, and .sh as defined in server/src/services/fileService.ts. Administrators can whitelist specific MIME types through the Admin → Addons page or environment variables.
How does TREK handle file storage and security?
Uploaded files are stored on the server's filesystem under the uploads/ directory with UUID-based filenames to prevent directory traversal attacks. Metadata including original names, MIME types, and ownership is stored in the SQLite database at data/travel.db. The FilesController enforces trip ownership validation and permission checks before any file operation, ensuring users can only access documents within trips they belong to.
Can I link uploaded files to specific places or reservations?
Yes, the FilesService supports linking files to places and reservations through the place_id and reservation_id foreign keys. When uploading via POST /api/trips/:tripId/files, include these IDs as form fields. Alternatively, use the linkFile API method or the UI modal that appears automatically after upload to associate existing files with itinerary items.
What is the maximum file size allowed in TREK document management?
The default maximum file size is 50 MiB (50 * 1024 * 1024 bytes), enforced by both the frontend useFileManager hook and the backend FilesController. Administrators can modify this limit by changing the MAX_FILE_SIZE constant in server/src/services/fileService.ts and ensuring the frontend dropzone configuration matches the new value.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →