# Electronic Document Management System (EDMS) - Technical Guide ## Table of Contents 1. [System Overview](#system-overview) 2. [Architecture](#architecture) 3. [Technology Stack](#technology-stack) 4. [Database Schema](#database-schema) 5. [Installation & Setup](#installation--setup) 6. [Development Environment](#development-environment) 7. [Component Structure](#component-structure) 8. [API & Data Management](#api--data-management) 9. [Security & Authentication](#security--authentication) 10. [Deployment](#deployment) 11. [Maintenance & Monitoring](#maintenance--monitoring) 12. [Extension & Customization](#extension--customization) ## System Overview The Electronic Document Management System (EDMS) is a modern web application built with Nuxt.js 3 and Vue.js 3, designed to provide a comprehensive document management solution for organizations. The system implements a hierarchical document organization structure with role-based access control (RBAC) and supports integration with external authentication providers like Authentik. ### Key Technical Features - **Modern Frontend**: Vue.js 3 with Composition API and script setup syntax - **SSR/SPA Support**: Nuxt.js 3 for optimal performance and SEO - **Database**: Prisma ORM with MySQL/PostgreSQL support and migrations - **Styling**: TailwindCSS with custom component system - **State Management**: Pinia for reactive state management with persistence - **Authentication**: Flexible authentication with JWT tokens and RBAC - **File Management**: Server-side file handling with metadata and versioning - **Real-time Features**: Vue reactivity for live updates and notifications - **Responsive Design**: Mobile-first approach with adaptive layouts ## Architecture ### System Architecture ``` ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ │ Frontend │ │ Backend │ │ Database │ │ (Nuxt.js) │◄──►│ (Server API) │◄──►│ (MySQL/PG) │ │ │ │ │ │ │ │ • Vue.js 3 │ │ • Prisma ORM │ │ • User Data │ │ • TailwindCSS │ │ • File System │ │ • Documents │ │ • Pinia Store │ │ • Authentication│ │ • Permissions │ │ • Components │ │ • API Routes │ │ • Audit Logs │ └─────────────────┘ └─────────────────┘ └─────────────────┘ │ │ │ │ ┌─────────────────┐ │ │ │ File Storage │ │ └──────────────►│ • Documents │◄────────────┘ │ • Versions │ │ • Thumbnails │ │ • Temp Files │ └─────────────────┘ ``` ### Application Flow 1. **Authentication**: User authenticates via configured authentication provider 2. **Authorization**: System validates user permissions against RBAC rules 3. **Navigation**: User navigates document hierarchy with tree-based structure 4. **Data Fetching**: Pinia store manages API calls with caching and error handling 5. **Rendering**: Vue components render UI with reactive updates 6. **File Operations**: Server handles secure file upload/download/preview 7. **Access Control**: System enforces granular document permissions 8. **Audit Trail**: All actions are logged for compliance and security ## Technology Stack ### Frontend Dependencies ```json { "core": { "nuxt": "^3.6.5", "vue": "^3.x", "@pinia/nuxt": "^0.4.11", "@pinia-plugin-persistedstate/nuxt": "^1.1.1" }, "styling": { "@nuxtjs/tailwindcss": "^6.8.0", "sass": "^1.62.0", "postcss-import": "^15.1.0" }, "forms": { "@formkit/nuxt": "^1.0.0", "@formkit/themes": "^1.0.0", "@formkit/addons": "^1.0.0", "@formkit/pro": "^0.115.3" }, "ui": { "apexcharts": "^3.36.0", "vue3-apexcharts": "^1.4.1", "@vueuse/core": "^9.5.0", "@vueuse/nuxt": "^9.5.0", "floating-vue": "^2.0.0-beta.24", "vue3-dropzone": "^2.0.1" }, "utilities": { "luxon": "^3.1.0", "uuid": "^10.0.0", "crypto-js": "^4.1.1", "@shimyshack/uid": "^0.1.7" } } ``` ### Backend Dependencies ```json { "database": { "@prisma/client": "^5.1.1", "prisma": "^5.1.1" }, "authentication": { "jsonwebtoken": "^8.5.1" }, "file-handling": { "jspdf": "^2.5.1" }, "security": { "nuxt-security": "^0.13.0" } } ``` ### Development Tools - **ESLint**: Code linting with Vue.js specific rules - **TypeScript**: Type safety with Nuxt TypeScript integration - **Prettier**: Consistent code formatting - **Vite**: Lightning-fast build tool and HMR - **PostCSS**: Advanced CSS processing with plugins ## Database Schema ### Core Models #### User Management ```prisma model user { userID Int @id @default(autoincrement()) userSecretKey String? @db.VarChar(255) userUsername String? @unique @db.VarChar(255) userPassword String? @db.VarChar(255) userFullName String? @db.VarChar(255) userEmail String? @unique @db.VarChar(255) userPhone String? @db.VarChar(255) userStatus String? @db.VarChar(255) userCreatedDate DateTime? @default(now()) @db.DateTime(0) userModifiedDate DateTime? @updatedAt @db.DateTime(0) // Relationships userrole userrole[] accessRequests AccessRequest[] permissions AccessPermission[] documents Document[] @relation("DocumentCreator") cabinets Cabinet[] @relation("CabinetCreator") drawers Drawer[] @relation("DrawerCreator") folders Folder[] @relation("FolderCreator") subfolders Subfolder[] @relation("SubfolderCreator") } model role { roleID Int @id @default(autoincrement()) roleName String @unique @db.VarChar(255) roleDescription String? @db.Text rolePermissions Json? // Flexible permissions structure roleCreatedDate DateTime @default(now()) @db.DateTime(0) roleModifiedDate DateTime @updatedAt @db.DateTime(0) userrole userrole[] permissions AccessPermission[] } ``` #### Document Hierarchy ```prisma // Cabinet → Drawer → Folder → Subfolder → Document model Cabinet { id Int @id @default(autoincrement()) name String @db.VarChar(255) description String? @db.Text metadata Json? // Flexible metadata storage createdAt DateTime @default(now()) @db.DateTime(0) updatedAt DateTime @updatedAt @db.DateTime(0) createdBy Int status String @default("active") @db.VarChar(50) user user @relation("CabinetCreator", fields: [createdBy], references: [userID]) drawers Drawer[] permissions AccessPermission[] @@index([createdBy]) @@index([status]) } model Document { id Int @id @default(autoincrement()) name String @db.VarChar(255) description String? @db.Text fileSize Int @default(0) fileType String @db.VarChar(100) fileExtension String @db.VarChar(20) filePath String @db.VarChar(500) thumbnailPath String? @db.VarChar(500) checksum String? @db.VarChar(64) version Int @default(1) isTemplate Boolean @default(false) isPublic Boolean @default(false) metadata Json? // Custom metadata fields tags String? @db.Text folderId Int? subfolderId Int? createdAt DateTime @default(now()) @db.DateTime(0) updatedAt DateTime @updatedAt @db.DateTime(0) createdBy Int status String @default("active") @db.VarChar(50) folder Folder? @relation("FolderDocuments", fields: [folderId], references: [id], onDelete: SetNull) subfolder Subfolder? @relation("SubfolderDocuments", fields: [subfolderId], references: [id], onDelete: SetNull) user user @relation("DocumentCreator", fields: [createdBy], references: [userID]) accessRequests AccessRequest[] permissions AccessPermission[] versions DocumentVersion[] @@index([folderId]) @@index([subfolderId]) @@index([createdBy]) @@index([fileType]) @@index([status]) @@fulltext([name, description, tags]) } ``` #### Access Control & Workflow ```prisma model AccessPermission { id Int @id @default(autoincrement()) userId Int? roleId Int? documentId Int? cabinetId Int? drawerId Int? folderId Int? subfolderId Int? permissionLevel String @db.VarChar(50) // view, download, print, edit, full, admin conditions Json? // Additional permission conditions createdAt DateTime @default(now()) @db.DateTime(0) updatedAt DateTime @updatedAt @db.DateTime(0) expiresAt DateTime? @db.DateTime(0) createdBy Int? user user? @relation(fields: [userId], references: [userID], onDelete: SetNull) role role? @relation(fields: [roleId], references: [roleID], onDelete: SetNull) document Document? @relation(fields: [documentId], references: [id], onDelete: Cascade) cabinet Cabinet? @relation(fields: [cabinetId], references: [id], onDelete: Cascade) drawer Drawer? @relation(fields: [drawerId], references: [id], onDelete: Cascade) folder Folder? @relation(fields: [folderId], references: [id], onDelete: Cascade) subfolder Subfolder? @relation(fields: [subfolderId], references: [id], onDelete: Cascade) @@index([userId]) @@index([roleId]) @@index([documentId]) @@index([expiresAt]) } model AccessRequest { id Int @id @default(autoincrement()) documentId Int userId Int requestedLevel String @db.VarChar(50) // view, download, print, edit, full duration String? @db.VarChar(50) // 7d, 14d, 30d, 60d, 90d, permanent justification String? @db.Text status String @default("pending") @db.VarChar(50) // pending, approved, rejected, expired responseNote String? @db.Text requestedAt DateTime @default(now()) @db.DateTime(0) respondedAt DateTime? @db.DateTime(0) respondedBy Int? expiresAt DateTime? @db.DateTime(0) document Document @relation(fields: [documentId], references: [id], onDelete: Cascade) user user @relation(fields: [userId], references: [userID]) @@index([documentId]) @@index([userId]) @@index([status]) } ``` ## Installation & Setup ### Prerequisites - Node.js 18+ and npm/yarn/pnpm - MySQL 8.0+ or PostgreSQL 13+ - Git for version control - Optional: Docker for containerized deployment ### Installation Steps 1. **Clone Repository** ```bash git clone cd edms ``` 2. **Install Dependencies** ```bash # Using npm npm install # Using yarn yarn install # Using pnpm pnpm install ``` 3. **Environment Configuration** Create `.env` file: ```env # Database Configuration DATABASE_URL="mysql://username:password@localhost:3306/edms_db" # For PostgreSQL: DATABASE_URL="postgresql://username:password@localhost:5432/edms_db" # Authentication JWT_SECRET="your-jwt-secret-key-min-256-bits" SESSION_SECRET="your-session-secret-key" # External Authentication (Optional) AUTHENTIK_BASE_URL="https://auth.yourdomain.com" AUTHENTIK_CLIENT_ID="your-client-id" AUTHENTIK_CLIENT_SECRET="your-client-secret" # File Storage UPLOAD_PATH="/var/uploads/edms" TEMP_PATH="/var/tmp/edms" MAX_FILE_SIZE="104857600" # 100MB in bytes ALLOWED_FILE_TYPES="pdf,doc,docx,xls,xlsx,ppt,pptx,txt,jpg,jpeg,png,gif" # Application Configuration NUXT_SECRET_KEY="your-nuxt-app-secret" BASE_URL="http://localhost:3000" NODE_ENV="development" # Security CORS_ORIGIN="http://localhost:3000" RATE_LIMIT_MAX="100" # requests per window RATE_LIMIT_WINDOW="15" # minutes # Monitoring & Logging LOG_LEVEL="info" LOG_FILE="/var/log/edms/app.log" ``` 4. **Database Setup** ```bash # Generate Prisma client npx prisma generate # Run database migrations npx prisma db push # Seed initial data (optional) npx prisma db seed # View database in Prisma Studio (optional) npx prisma studio ``` 5. **Development Server** ```bash # Start development server npm run dev # Access application at http://localhost:3000 ``` ## Development Environment ### Project Structure ``` edms/ ├── components/ # Vue components │ ├── dms/ # DMS-specific components │ │ ├── explorer/ # Document explorer interface │ │ ├── dialogs/ # Modal dialogs and forms │ │ ├── viewers/ # Document preview components │ │ └── navigation/ # Tree and breadcrumb navigation │ ├── layouts/ # Layout components │ ├── formkit/ # FormKit custom components │ └── [Rs*].vue # Reusable UI components (RsButton, RsCard, etc.) ├── pages/ # Nuxt pages (file-based routing) │ ├── dms/ # DMS-related pages │ ├── dashboard/ # Dashboard and analytics │ ├── login/ # Authentication pages │ └── index.vue # Homepage ├── stores/ # Pinia state stores │ ├── dms.js # Document management store │ ├── auth.js # Authentication store │ └── app.js # Global application store ├── middleware/ # Route middleware │ ├── auth.js # Authentication middleware │ └── rbac.js # Role-based access control ├── layouts/ # Nuxt layouts │ ├── default.vue # Default application layout │ └── auth.vue # Authentication layout ├── server/ # Server-side code │ ├── api/ # API routes │ └── middleware/ # Server middleware ├── prisma/ # Database configuration │ ├── schema.prisma # Database schema │ └── migrations/ # Database migrations ├── public/ # Static assets ├── assets/ # Build-time assets ├── plugins/ # Nuxt plugins ├── composables/ # Vue composables └── utils/ # Utility functions ``` ### Component Architecture #### Core EDMS Components 1. **DMSExplorer.vue**: Main document browser with tree navigation, content views, and details panel 2. **DMSTreeView.vue**: Recursive tree navigation for hierarchical structure 3. **DMSDocumentViewer.vue**: Multi-format document viewer with zoom and controls 4. **DMSAccessRequestDialog.vue**: Access request form with approval workflow 5. **DMSUploadDialog.vue**: File upload interface with metadata assignment #### Custom UI Components (Rs Prefix) - **RsButton.vue**: Standardized button component with variants - **RsCard.vue**: Container component with consistent styling - **RsModal.vue**: Modal dialog base component - **RsTable.vue**: Data table with sorting and filtering - **RsDropdown.vue**: Dropdown menu component ### State Management (Pinia) #### DMS Store (`stores/dms.js`) ```javascript export const useDmsStore = defineStore('dms', { state: () => ({ // Navigation state currentPath: '/', pathHistory: [], currentItems: [], selectedItem: null, // UI state treeExpanded: {}, searchQuery: '', viewMode: 'list', // list, grid, details sortOrder: 'asc', sortField: 'name', activeTab: 'all', // all, public, private, personal // Loading states isLoading: false, isUploading: false, uploadProgress: 0, // Dialog states showUploadDialog: false, showAccessRequestDialog: false, accessRequestItem: null }), actions: { // Navigation actions async navigateTo(path) { this.isLoading = true try { this.pathHistory.push(this.currentPath) this.currentPath = path await this.loadItems() } finally { this.isLoading = false } }, async loadItems() { const response = await $fetch('/api/dms/items', { query: { path: this.currentPath, search: this.searchQuery, tab: this.activeTab } }) this.currentItems = response.items }, // File operations async uploadFile(file, metadata) { this.isUploading = true try { const formData = new FormData() formData.append('file', file) formData.append('metadata', JSON.stringify(metadata)) formData.append('path', this.currentPath) const response = await $fetch('/api/dms/upload', { method: 'POST', body: formData, onUploadProgress: (progress) => { this.uploadProgress = Math.round((progress.loaded / progress.total) * 100) } }) await this.loadItems() return response } finally { this.isUploading = false this.uploadProgress = 0 } }, async requestAccess(item, requestData) { return await $fetch('/api/dms/access-request', { method: 'POST', body: { documentId: item.id, requestedLevel: requestData.accessType, duration: requestData.duration, justification: requestData.justification } }) }, async searchDocuments(query) { this.searchQuery = query await this.loadItems() } }, getters: { filteredItems: (state) => { let items = state.currentItems // Apply tab filter if (state.activeTab !== 'all') { items = items.filter(item => item.accessType === state.activeTab) } // Apply search filter if (state.searchQuery) { items = items.filter(item => item.name.toLowerCase().includes(state.searchQuery.toLowerCase()) || item.description?.toLowerCase().includes(state.searchQuery.toLowerCase()) ) } // Apply sorting items.sort((a, b) => { const aVal = a[state.sortField] const bVal = b[state.sortField] const modifier = state.sortOrder === 'asc' ? 1 : -1 return aVal < bVal ? -modifier : aVal > bVal ? modifier : 0 }) return items }, currentBreadcrumbs: (state) => { const parts = state.currentPath.split('/').filter(Boolean) return parts.map((part, index) => ({ name: part, path: '/' + parts.slice(0, index + 1).join('/') })) }, canNavigateBack: (state) => state.pathHistory.length > 0 }, persist: { storage: persistedState.localStorage, pick: ['treeExpanded', 'viewMode', 'sortOrder', 'sortField'] } }) ``` ## API & Data Management ### Server API Routes #### Document Management ```javascript // server/api/dms/documents/[id].get.js export default defineEventHandler(async (event) => { const id = getRouterParam(event, 'id') // Fetch document with access control }) // server/api/dms/upload.post.js export default defineEventHandler(async (event) => { // Handle file upload with validation }) // server/api/dms/search.get.js export default defineEventHandler(async (event) => { const query = getQuery(event) // Perform document search }) ``` #### Access Control ```javascript // server/api/dms/access-request.post.js export default defineEventHandler(async (event) => { // Process access requests }) // server/api/dms/permissions/[id].get.js export default defineEventHandler(async (event) => { // Get user permissions for item }) ``` ### Data Validation #### Prisma Schema Validation - Field constraints and types - Relationship integrity - Index optimization #### Frontend Validation ```javascript // FormKit validation rules const documentValidation = { title: 'required|length:3,255', description: 'length:0,1000', fileType: 'required|in:pdf,doc,docx,xls,xlsx,jpg,png', accessLevel: 'required|in:public,private,personal' } ``` ## Security & Authentication ### Authentication Flow 1. **User Login**: Redirect to Authentik 2. **Token Exchange**: Receive JWT token 3. **Token Validation**: Verify on each request 4. **Session Management**: Store in secure cookie 5. **Token Refresh**: Automatic renewal ### Authorization Levels - **System Admin**: Full system access - **Department Admin**: Department-wide permissions - **Document Owner**: Full access to owned documents - **User**: Access based on granted permissions ### File Security - **Path Traversal Protection**: Validate file paths - **File Type Validation**: Whitelist allowed formats - **Size Limits**: Prevent large file uploads - **Virus Scanning**: Optional integration - **Access Logging**: Track file access ### Data Protection - **SQL Injection Prevention**: Parameterized queries via Prisma - **XSS Protection**: Input sanitization - **CSRF Protection**: Token validation - **Secure Headers**: Security-focused HTTP headers ## Deployment ### Production Build ```bash # Build for production npm run build # Generate static files (if needed) npm run generate # Preview production build npm run preview ``` ### Environment Configuration #### Production Environment Variables ```env NODE_ENV=production DATABASE_URL="mysql://prod_user:password@db.server:3306/edms_prod" NUXT_SECRET_KEY="production-secret-key" BASE_URL="https://dms.jkr-kotabharu.gov.my" ``` #### Docker Deployment ```dockerfile FROM node:18-alpine WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY . . RUN npm run build EXPOSE 3000 CMD ["npm", "run", "start"] ``` ### Nginx Configuration ```nginx server { listen 80; server_name dms.jkr-kotabharu.gov.my; location / { proxy_pass http://localhost:3000; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; } location /uploads/ { alias /var/www/uploads/; expires 1y; } } ``` ## Maintenance & Monitoring ### Database Maintenance ```sql -- Regular maintenance queries OPTIMIZE TABLE document; ANALYZE TABLE access_permission; -- Cleanup old access requests DELETE FROM access_request WHERE status = 'rejected' AND requested_at < DATE_SUB(NOW(), INTERVAL 30 DAY); ``` ### Backup Strategy - **Database Backups**: Daily automated backups - **File Storage Backups**: Incremental file backups - **Configuration Backups**: Version control for configs ### Monitoring - **Application Performance**: Response times, error rates - **Database Performance**: Query performance, connection counts - **File Storage**: Disk usage, file integrity - **Security Events**: Failed logins, access violations ### Logging ```javascript // Server-side logging import { createLogger } from 'winston' const logger = createLogger({ level: 'info', format: winston.format.json(), transports: [ new winston.transports.File({ filename: 'error.log', level: 'error' }), new winston.transports.File({ filename: 'combined.log' }) ] }) ``` ## Extension & Customization ### Adding New Document Types 1. Update Prisma schema with new file types 2. Add validation rules for the file type 3. Implement viewer component for the format 4. Update upload dialog to support the type ### Custom Access Control 1. Extend AccessPermission model 2. Implement custom permission logic 3. Update UI to reflect new permissions 4. Add admin interface for permission management ### Integration Points - **External Authentication**: LDAP/Active Directory - **Document Processing**: OCR, text extraction - **Workflow Systems**: Approval workflows - **Notification Systems**: Email/SMS notifications - **Analytics**: Document usage analytics ### API Extensions ```javascript // Custom API endpoint example // server/api/custom/analytics.get.js export default defineEventHandler(async (event) => { // Custom analytics logic return { totalDocuments: count, storageUsed: size, activeUsers: userCount } }) ``` ### Theme Customization ```javascript // tailwind.config.js extensions module.exports = { theme: { extend: { colors: { primary: { 50: '#eff6ff', 500: '#3b82f6', 900: '#1e3a8a' } } } } } ``` --- **Document Version**: 2.0 **Last Updated**: December 2024 **System Version**: EDMS v1.0 **Technology Stack**: Nuxt.js 3, Vue.js 3, Prisma, TailwindCSS **Database**: MySQL/PostgreSQL **Authentication**: JWT with optional external providers