update default location of uploaded file to be at data/

This commit is contained in:
DaKheera47 2026-01-22 20:53:08 +00:00
parent de4df0ffcf
commit 6955a77af8
4 changed files with 30 additions and 11 deletions

View File

@ -14,8 +14,6 @@ services:
volumes: volumes:
# Persist database and generated PDFs # Persist database and generated PDFs
- ./data:/app/data - ./data:/app/data
# Base resume JSON (read-only)
- ./resume-generator/base.json:/app/resume-generator/base.json:ro
environment: environment:
# Server config # Server config
- NODE_ENV=production - NODE_ENV=production

View File

@ -1,8 +1,9 @@
import { Router, Request, Response } from 'express'; import { Router, Request, Response } from 'express';
import { access, mkdir, writeFile } from 'fs/promises'; import { mkdir, stat, writeFile } from 'fs/promises';
import { dirname } from 'path'; import { dirname } from 'path';
import { extractProjectsFromProfile } from '../../services/resumeProjects.js'; import { extractProjectsFromProfile } from '../../services/resumeProjects.js';
import { clearProfileCache, DEFAULT_PROFILE_PATH, getProfile } from '../../services/profile.js'; import { clearProfileCache, DEFAULT_PROFILE_PATH, getProfile } from '../../services/profile.js';
import { resumeDataSchema } from '@shared/rxresume-schema.js';
export const profileRouter = Router(); export const profileRouter = Router();
@ -38,8 +39,9 @@ profileRouter.get('/', async (req: Request, res: Response) => {
*/ */
profileRouter.get('/status', async (_req: Request, res: Response) => { profileRouter.get('/status', async (_req: Request, res: Response) => {
try { try {
await access(DEFAULT_PROFILE_PATH); const fileInfo = await stat(DEFAULT_PROFILE_PATH);
res.json({ success: true, data: { exists: true, error: null } }); const exists = fileInfo.isFile() && fileInfo.size > 0;
res.json({ success: true, data: { exists, error: exists ? null : 'Resume file is empty' } });
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'; const message = error instanceof Error ? error.message : 'Unknown error';
res.json({ success: true, data: { exists: false, error: message } }); res.json({ success: true, data: { exists: false, error: message } });
@ -57,13 +59,30 @@ profileRouter.post('/upload', async (req: Request, res: Response) => {
throw new Error('Invalid profile payload. Expected a JSON object.'); throw new Error('Invalid profile payload. Expected a JSON object.');
} }
const parsed = resumeDataSchema.safeParse(profile);
if (!parsed.success) {
const details = parsed.error.issues[0]?.message ?? 'Resume JSON does not match the RxResume schema.';
throw new Error(`Invalid resume JSON: ${details}`);
}
const existing = await stat(DEFAULT_PROFILE_PATH).catch(() => null);
if (existing && existing.isDirectory()) {
throw new Error('Resume path is a directory. Remove it and upload again.');
}
await mkdir(dirname(DEFAULT_PROFILE_PATH), { recursive: true }); await mkdir(dirname(DEFAULT_PROFILE_PATH), { recursive: true });
await writeFile(DEFAULT_PROFILE_PATH, JSON.stringify(profile, null, 2), 'utf-8'); await writeFile(DEFAULT_PROFILE_PATH, JSON.stringify(parsed.data, null, 2), 'utf-8');
clearProfileCache(); clearProfileCache();
res.json({ success: true, data: { exists: true, error: null } }); res.json({ success: true, data: { exists: true, error: null } });
} catch (error) { } catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error'; let message = error instanceof Error ? error.message : 'Unknown error';
if (error && typeof error === 'object' && 'code' in error) {
const code = (error as { code?: string }).code;
if (code === 'EROFS') {
message = 'Resume path is read-only. Remove the bind mount and restart the container.';
}
}
res.status(400).json({ success: false, error: message }); res.status(400).json({ success: false, error: message });
} }
}); });

View File

@ -1,9 +1,9 @@
import { readFile } from 'fs/promises'; import { readFile } from 'fs/promises';
import { join, dirname } from 'path'; import { join } from 'path';
import { fileURLToPath } from 'url';
const __dirname = dirname(fileURLToPath(import.meta.url)); import { getDataDir } from '../config/dataDir.js';
export const DEFAULT_PROFILE_PATH = process.env.RESUME_PROFILE_PATH || join(__dirname, '../../../../resume-generator/base.json');
export const DEFAULT_PROFILE_PATH = process.env.RESUME_PROFILE_PATH || join(getDataDir(), 'resume.json');
let cachedProfile: any = null; let cachedProfile: any = null;
let cachedProfilePath: string | null = null; let cachedProfilePath: string | null = null;

View File

@ -1,6 +1,7 @@
// combined types from: https://github.com/amruthpillai/reactive-resume/tree/v4.5.5/libs/schema/src // combined types from: https://github.com/amruthpillai/reactive-resume/tree/v4.5.5/libs/schema/src
import { z } from "zod"; import { z } from "zod";
import { createId } from '@paralleldrive/cuid2';
// --- Shared --- // --- Shared ---
@ -10,6 +11,7 @@ export type FilterKeys<T, Condition> = {
export const idSchema = z export const idSchema = z
.string() .string()
.length(24)
.cuid2() .cuid2()
.describe("Unique identifier for the item (CUID2 format)"); .describe("Unique identifier for the item (CUID2 format)");