generated from corrad-software/corrad-af-2024
25 KiB
25 KiB
Electronic Document Management System (EDMS) - Technical Guide
Table of Contents
- System Overview
- Architecture
- Technology Stack
- Database Schema
- Installation & Setup
- Development Environment
- Component Structure
- API & Data Management
- Security & Authentication
- Deployment
- Maintenance & Monitoring
- 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
- Authentication: User authenticates via configured authentication provider
- Authorization: System validates user permissions against RBAC rules
- Navigation: User navigates document hierarchy with tree-based structure
- Data Fetching: Pinia store manages API calls with caching and error handling
- Rendering: Vue components render UI with reactive updates
- File Operations: Server handles secure file upload/download/preview
- Access Control: System enforces granular document permissions
- Audit Trail: All actions are logged for compliance and security
Technology Stack
Frontend Dependencies
{
"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
{
"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
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
// 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
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
- Clone Repository
git clone <repository-url>
cd edms
- Install Dependencies
# Using npm
npm install
# Using yarn
yarn install
# Using pnpm
pnpm install
- Environment Configuration
Create
.env
file:
# 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"
- Database Setup
# 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
- Development Server
# 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
- DMSExplorer.vue: Main document browser with tree navigation, content views, and details panel
- DMSTreeView.vue: Recursive tree navigation for hierarchical structure
- DMSDocumentViewer.vue: Multi-format document viewer with zoom and controls
- DMSAccessRequestDialog.vue: Access request form with approval workflow
- 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
)
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
// 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
// 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
// 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
- User Login: Redirect to Authentik
- Token Exchange: Receive JWT token
- Token Validation: Verify on each request
- Session Management: Store in secure cookie
- 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
# 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
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
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
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
-- 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
// 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
- Update Prisma schema with new file types
- Add validation rules for the file type
- Implement viewer component for the format
- Update upload dialog to support the type
Custom Access Control
- Extend AccessPermission model
- Implement custom permission logic
- Update UI to reflect new permissions
- 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
// 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
// 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