diff --git a/DOCX_TEMPLATES_README.md b/DOCX_TEMPLATES_README.md index 4132a94..3b714b1 100644 --- a/DOCX_TEMPLATES_README.md +++ b/DOCX_TEMPLATES_README.md @@ -168,6 +168,32 @@ If you need additional transformations (like extracting different parts of codes - Maximum file size: 10MB - **For repeated information**: If you need the same data to appear multiple times, create unique placeholders like `{project_name_header}` and `{project_name_footer}` and provide the same value for both +## Storage & Persistence + +Templates are stored in two locations for persistence in Docker environments: + +### Database Storage +- **Location**: `data/database.sqlite` (table: `docx_templates`) +- **Content**: Template metadata (name, description, file paths, timestamps) +- **Persistence**: Handled by Docker volume mount `./data:/app/data` + +### File Storage +- **Location**: `templates/` (host) → `/app/templates/` (container) +- **Content**: Actual DOCX template files +- **Persistence**: Handled by Docker volume mount `./templates:/app/templates` +- **Web Access**: Files are served via `/api/templates/download/{filename}` + +### Docker Volume Mounts +Both development and production Docker setups include volume mounts to ensure template persistence across container restarts: + +```yaml +volumes: + - ./data:/app/data # Database + - ./templates:/app/templates # Template files + - ./uploads:/app/public/uploads # Other uploads + - ./backups:/app/backups # Backup files +``` + ## Troubleshooting - **Duplicate tags error**: Each placeholder can only be used once in the template. If you need the same information to appear multiple times, use unique placeholders like `{project_name_1}` and `{project_name_2}` with the same data value. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fd4fccc..8d31c86 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -14,6 +14,7 @@ services: volumes: - ./data:/app/data - ./uploads:/app/public/uploads + - ./templates:/app/templates - ./backups:/app/backups environment: - NODE_ENV=production diff --git a/docker-compose.yml b/docker-compose.yml index 517dc64..92f3190 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,6 +12,8 @@ services: - /app/node_modules - ./data:/app/data - ./backups:/app/backups + - ./uploads:/app/public/uploads + - ./templates:/app/templates environment: - NODE_ENV=development - TZ=Europe/Warsaw diff --git a/docker-entrypoint-dev.sh b/docker-entrypoint-dev.sh index a324776..8bcdb65 100644 --- a/docker-entrypoint-dev.sh +++ b/docker-entrypoint-dev.sh @@ -13,9 +13,15 @@ mkdir -p /app/public/uploads/contracts mkdir -p /app/public/uploads/projects mkdir -p /app/public/uploads/tasks +# Ensure templates directory exists +mkdir -p /app/templates + # Set proper permissions for uploads directory chmod -R 755 /app/public/uploads +# Set proper permissions for templates directory +chmod -R 755 /app/templates + # Create admin account if it doesn't exist echo "🔧 Setting up admin account..." node scripts/create-admin.js diff --git a/docker-entrypoint.sh b/docker-entrypoint.sh index a576fca..025efb3 100644 --- a/docker-entrypoint.sh +++ b/docker-entrypoint.sh @@ -13,9 +13,15 @@ mkdir -p /app/public/uploads/contracts mkdir -p /app/public/uploads/projects mkdir -p /app/public/uploads/tasks +# Ensure templates directory exists +mkdir -p /app/templates + # Set proper permissions for uploads directory chmod -R 755 /app/public/uploads +# Set proper permissions for templates directory +chmod -R 755 /app/templates + # Create admin account if it doesn't exist echo "🔧 Setting up admin account..." node scripts/create-admin.js diff --git a/src/app/api/templates/download/[filename]/route.js b/src/app/api/templates/download/[filename]/route.js new file mode 100644 index 0000000..d75a545 --- /dev/null +++ b/src/app/api/templates/download/[filename]/route.js @@ -0,0 +1,61 @@ +import { NextRequest, NextResponse } from "next/server"; +import { readFile } from "fs/promises"; +import { existsSync } from "fs"; +import path from "path"; +import db from "@/lib/db"; + +export async function GET(request, { params }) { + try { + const { filename } = params; + + if (!filename) { + return NextResponse.json( + { error: "Filename is required" }, + { status: 400 } + ); + } + + // Get template info from database + const template = db.prepare(` + SELECT * FROM docx_templates WHERE stored_filename = ? AND is_active = 1 + `).get(filename); + + if (!template) { + return NextResponse.json( + { error: "Template not found" }, + { status: 404 } + ); + } + + // Check if file exists + const filePath = path.join(process.cwd(), "templates", filename); + if (!existsSync(filePath)) { + return NextResponse.json( + { error: "Template file not found" }, + { status: 404 } + ); + } + + // Read file + const fileBuffer = await readFile(filePath); + + // Return file with proper headers + const response = new NextResponse(fileBuffer, { + status: 200, + headers: { + "Content-Type": template.mime_type || "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "Content-Disposition": `attachment; filename="${template.original_filename}"`, + "Content-Length": fileBuffer.length.toString(), + }, + }); + + return response; + + } catch (error) { + console.error("Template download error:", error); + return NextResponse.json( + { error: "Failed to download template" }, + { status: 500 } + ); + } +} \ No newline at end of file diff --git a/src/app/api/templates/route.js b/src/app/api/templates/route.js index f9e430b..d8f049d 100644 --- a/src/app/api/templates/route.js +++ b/src/app/api/templates/route.js @@ -4,7 +4,7 @@ import { existsSync } from "fs"; import path from "path"; import db from "@/lib/db"; -const TEMPLATES_DIR = path.join(process.cwd(), "public", "templates"); +const TEMPLATES_DIR = path.join(process.cwd(), "templates"); const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB const ALLOWED_TYPES = [ "application/vnd.openxmlformats-officedocument.wordprocessingml.document" @@ -49,7 +49,7 @@ export async function POST(request) { const sanitizedOriginalName = file.name.replace(/[^a-zA-Z0-9.-]/g, "_"); const storedFilename = `${timestamp}_${sanitizedOriginalName}`; const filePath = path.join(TEMPLATES_DIR, storedFilename); - const relativePath = `/templates/${storedFilename}`; + const relativePath = `templates/${storedFilename}`; // Save file const bytes = await file.arrayBuffer(); diff --git a/src/app/projects/page.js b/src/app/projects/page.js index 9d71d37..e07c795 100644 --- a/src/app/projects/page.js +++ b/src/app/projects/page.js @@ -68,7 +68,7 @@ export default function ProjectListPage() { // Apply status filter if (filters.status !== 'all') { if (filters.status === 'not_finished') { - filtered = filtered.filter(project => project.project_status !== 'fulfilled'); + filtered = filtered.filter(project => project.project_status !== 'fulfilled' && project.project_status !== 'cancelled'); } else { filtered = filtered.filter(project => project.project_status === filters.status); } diff --git a/src/components/TemplateList.js b/src/components/TemplateList.js index a3d86d7..5b9cb0d 100644 --- a/src/components/TemplateList.js +++ b/src/components/TemplateList.js @@ -151,7 +151,7 @@ export default function TemplateList({ templates, onTemplateDeleted, onTemplateU