careermate 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +256 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/apps/mcp/src/index.ts +66 -0
- package/apps/web/DESIGN_GUIDE.md +105 -0
- package/apps/web/UI_CONTRACT.md +44 -0
- package/apps/web/public/app.js +118 -0
- package/apps/web/public/fonts/PretendardVariable.woff2 +0 -0
- package/apps/web/public/index.html +41 -0
- package/apps/web/public/lib.js +282 -0
- package/apps/web/public/pages/applications.js +98 -0
- package/apps/web/public/pages/documents.js +446 -0
- package/apps/web/public/pages/home.js +263 -0
- package/apps/web/public/pages/interview.js +230 -0
- package/apps/web/public/pages/jobs.js +494 -0
- package/apps/web/public/pages/profile.js +576 -0
- package/apps/web/public/pages/settings.js +233 -0
- package/apps/web/public/styles.css +426 -0
- package/apps/web/src/exports.ts +68 -0
- package/apps/web/src/http.ts +180 -0
- package/apps/web/src/index.ts +49 -0
- package/apps/web/src/info.ts +50 -0
- package/apps/web/src/routes.ts +350 -0
- package/apps/web/src/security.ts +102 -0
- package/apps/web/src/server.ts +141 -0
- package/apps/web/src/settings.ts +88 -0
- package/bin/careermate.mjs +74 -0
- package/dist/careermate.mcpb +0 -0
- package/dist/install-page/index.html +474 -0
- package/dist/install-page/style.css +391 -0
- package/dist/install-page/vercel.json +20 -0
- package/dist/mcp-smoke.err +3 -0
- package/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/README.md +219 -0
- package/dist/mcpb-stage/dist/install-page/index.html +434 -0
- package/dist/mcpb-stage/dist/install-page/style.css +407 -0
- package/dist/mcpb-stage/dist/install-page/vercel.json +20 -0
- package/dist/mcpb-stage/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/dist/public/app.js +118 -0
- package/dist/mcpb-stage/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/mcpb-stage/dist/public/index.html +41 -0
- package/dist/mcpb-stage/dist/public/lib.js +282 -0
- package/dist/mcpb-stage/dist/public/pages/applications.js +98 -0
- package/dist/mcpb-stage/dist/public/pages/documents.js +446 -0
- package/dist/mcpb-stage/dist/public/pages/home.js +263 -0
- package/dist/mcpb-stage/dist/public/pages/interview.js +230 -0
- package/dist/mcpb-stage/dist/public/pages/jobs.js +494 -0
- package/dist/mcpb-stage/dist/public/pages/profile.js +576 -0
- package/dist/mcpb-stage/dist/public/pages/settings.js +233 -0
- package/dist/mcpb-stage/dist/public/styles.css +420 -0
- package/dist/mcpb-stage/dist/web.mjs +7240 -0
- package/dist/mcpb-stage/manifest.json +40 -0
- package/dist/public/app.js +118 -0
- package/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/public/index.html +41 -0
- package/dist/public/lib.js +282 -0
- package/dist/public/pages/applications.js +98 -0
- package/dist/public/pages/documents.js +446 -0
- package/dist/public/pages/home.js +263 -0
- package/dist/public/pages/interview.js +230 -0
- package/dist/public/pages/jobs.js +494 -0
- package/dist/public/pages/profile.js +576 -0
- package/dist/public/pages/settings.js +233 -0
- package/dist/public/styles.css +426 -0
- package/dist/web.mjs +7240 -0
- package/docs/ARCHITECTURE.md +208 -0
- package/docs/CHANGES_V1.md +103 -0
- package/docs/DATA_MODEL.md +460 -0
- package/docs/DECISIONS.md +277 -0
- package/docs/DEMO.md +242 -0
- package/docs/INSTALL.md +148 -0
- package/docs/INSTALL_AND_USAGE.md +99 -0
- package/docs/MCP_TOOLS.md +233 -0
- package/docs/ROADMAP.md +134 -0
- package/docs/START_WORKFLOW.md +125 -0
- package/docs/SUPPORTED_AI_APPS.md +60 -0
- package/docs/TODO.md +57 -0
- package/docs/UX_NOTES.md +247 -0
- package/docs/WORKFLOWS.md +200 -0
- package/install-page/index.html +474 -0
- package/install-page/style.css +391 -0
- package/install-page/vercel.json +20 -0
- package/package.json +68 -0
- package/packages/core/src/context.ts +74 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/onboarding.ts +81 -0
- package/packages/core/src/services.ts +146 -0
- package/packages/core/src/summary.ts +104 -0
- package/packages/db/src/connection.ts +46 -0
- package/packages/db/src/index.ts +22 -0
- package/packages/db/src/paths.ts +41 -0
- package/packages/db/src/repositories.ts +828 -0
- package/packages/db/src/runtime.ts +58 -0
- package/packages/db/src/schema.ts +189 -0
- package/packages/exporters/src/html.ts +113 -0
- package/packages/exporters/src/index.ts +364 -0
- package/packages/exporters/src/markdown.ts +178 -0
- package/packages/mcp-tools/src/bridge.ts +83 -0
- package/packages/mcp-tools/src/index.ts +8 -0
- package/packages/mcp-tools/src/result.ts +49 -0
- package/packages/mcp-tools/src/tools.ts +455 -0
- package/packages/parsers/src/html.ts +86 -0
- package/packages/parsers/src/index.ts +228 -0
- package/packages/parsers/src/keywords.ts +151 -0
- package/packages/prompts/src/humanize.ts +59 -0
- package/packages/prompts/src/index.ts +82 -0
- package/packages/prompts/src/install.ts +43 -0
- package/packages/prompts/src/onboarding.ts +35 -0
- package/packages/prompts/src/system.ts +53 -0
- package/packages/shared/src/enums.ts +103 -0
- package/packages/shared/src/index.ts +18 -0
- package/packages/shared/src/schemas.ts +398 -0
- package/packages/workflows/src/definitions.ts +107 -0
- package/packages/workflows/src/index.ts +39 -0
- package/scripts/build-dist.mjs +62 -0
- package/scripts/build-mcpb.mjs +70 -0
- package/scripts/doctor.ts +81 -0
- package/scripts/init.ts +342 -0
- package/scripts/mcp-probe.ts +55 -0
- package/scripts/migrate.ts +6 -0
- package/scripts/run.mjs +33 -0
- package/scripts/seed.ts +129 -0
- package/scripts/test.ts +117 -0
- package/scripts/ui-smoke.ts +73 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin adapter over @careermate/exporters. Single integration point so the route
|
|
3
|
+
* layer doesn't depend on the exporter package's internal shape. Resolves the
|
|
4
|
+
* records a given export needs and returns a ready-to-download payload.
|
|
5
|
+
*/
|
|
6
|
+
import {
|
|
7
|
+
coverLetterToMarkdown,
|
|
8
|
+
coverLetterToHtml,
|
|
9
|
+
resumeToMarkdown,
|
|
10
|
+
profileToMarkdown,
|
|
11
|
+
interviewPrepToMarkdown,
|
|
12
|
+
type ExportResult,
|
|
13
|
+
} from '@careermate/exporters';
|
|
14
|
+
import {
|
|
15
|
+
coverLetterRepo,
|
|
16
|
+
documentRepo,
|
|
17
|
+
jobRepo,
|
|
18
|
+
profileRepo,
|
|
19
|
+
experienceRepo,
|
|
20
|
+
projectRepo,
|
|
21
|
+
skillRepo,
|
|
22
|
+
interviewRepo,
|
|
23
|
+
} from '@careermate/db';
|
|
24
|
+
import { HttpError } from './http.ts';
|
|
25
|
+
|
|
26
|
+
export type ExportFormat = 'md' | 'html' | 'txt';
|
|
27
|
+
|
|
28
|
+
function asFormat(result: ExportResult, format: ExportFormat): ExportResult {
|
|
29
|
+
if (format === 'txt') {
|
|
30
|
+
// Markdown is already readable as plain text; just relabel.
|
|
31
|
+
return {
|
|
32
|
+
filename: result.filename.replace(/\.(md|html)$/i, '.txt'),
|
|
33
|
+
mimeType: 'text/plain; charset=utf-8',
|
|
34
|
+
content: result.content,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
return result;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function exportCoverLetter(id: string, format: ExportFormat): ExportResult {
|
|
41
|
+
const cl = coverLetterRepo.get(id, true);
|
|
42
|
+
if (!cl) throw new HttpError(404, '자기소개서를 찾을 수 없습니다.');
|
|
43
|
+
const job = cl.job_id ? jobRepo.get(cl.job_id) : null;
|
|
44
|
+
const profile = profileRepo.get();
|
|
45
|
+
if (format === 'html') return coverLetterToHtml(cl, { job, profile });
|
|
46
|
+
return asFormat(coverLetterToMarkdown(cl, { job, profile }), format);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function exportDocument(id: string, format: ExportFormat): ExportResult {
|
|
50
|
+
const doc = documentRepo.get(id);
|
|
51
|
+
if (!doc) throw new HttpError(404, '문서를 찾을 수 없습니다.');
|
|
52
|
+
return asFormat(resumeToMarkdown(doc, profileRepo.get()), format);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function exportProfile(format: ExportFormat): ExportResult {
|
|
56
|
+
const profile = profileRepo.get();
|
|
57
|
+
if (!profile) throw new HttpError(404, '프로필이 없습니다.');
|
|
58
|
+
return asFormat(
|
|
59
|
+
profileToMarkdown(profile, experienceRepo.list(), projectRepo.list(), skillRepo.list()),
|
|
60
|
+
format,
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function exportInterview(jobId: string, format: ExportFormat): ExportResult {
|
|
65
|
+
const prep = interviewRepo.getByJob(jobId);
|
|
66
|
+
if (!prep) throw new HttpError(404, '면접 준비 자료가 없습니다.');
|
|
67
|
+
return asFormat(interviewPrepToMarkdown(prep, jobRepo.get(jobId)), format);
|
|
68
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal HTTP plumbing built on node:http — a tiny router, JSON helpers,
|
|
3
|
+
* size-limited body parsing, and path-traversal-safe static file serving.
|
|
4
|
+
* Kept dependency-free so installation is a single `npm install`.
|
|
5
|
+
*/
|
|
6
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import path from 'node:path';
|
|
9
|
+
import { ZodError, type ZodType } from 'zod';
|
|
10
|
+
|
|
11
|
+
export interface Ctx {
|
|
12
|
+
req: IncomingMessage;
|
|
13
|
+
res: ServerResponse;
|
|
14
|
+
params: Record<string, string>;
|
|
15
|
+
query: URLSearchParams;
|
|
16
|
+
url: URL;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type Handler = (ctx: Ctx) => unknown | Promise<unknown>;
|
|
20
|
+
|
|
21
|
+
interface Route {
|
|
22
|
+
method: string;
|
|
23
|
+
pattern: RegExp;
|
|
24
|
+
keys: string[];
|
|
25
|
+
handler: Handler;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const MAX_BODY = 8 * 1024 * 1024; // 8 MB — generous for pasted résumés, bounded against abuse.
|
|
29
|
+
|
|
30
|
+
export class Router {
|
|
31
|
+
private routes: Route[] = [];
|
|
32
|
+
|
|
33
|
+
add(method: string, path: string, handler: Handler): this {
|
|
34
|
+
const keys: string[] = [];
|
|
35
|
+
const pattern = new RegExp(
|
|
36
|
+
'^' +
|
|
37
|
+
path.replace(/:[^/]+/g, (m) => {
|
|
38
|
+
keys.push(m.slice(1));
|
|
39
|
+
return '([^/]+)';
|
|
40
|
+
}) +
|
|
41
|
+
'/?$',
|
|
42
|
+
);
|
|
43
|
+
this.routes.push({ method: method.toUpperCase(), pattern, keys, handler });
|
|
44
|
+
return this;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
get(p: string, h: Handler) { return this.add('GET', p, h); }
|
|
48
|
+
post(p: string, h: Handler) { return this.add('POST', p, h); }
|
|
49
|
+
put(p: string, h: Handler) { return this.add('PUT', p, h); }
|
|
50
|
+
patch(p: string, h: Handler) { return this.add('PATCH', p, h); }
|
|
51
|
+
delete(p: string, h: Handler) { return this.add('DELETE', p, h); }
|
|
52
|
+
|
|
53
|
+
match(method: string, pathname: string): { handler: Handler; params: Record<string, string> } | null {
|
|
54
|
+
for (const r of this.routes) {
|
|
55
|
+
if (r.method !== method) continue;
|
|
56
|
+
const m = r.pattern.exec(pathname);
|
|
57
|
+
if (!m) continue;
|
|
58
|
+
const params: Record<string, string> = {};
|
|
59
|
+
r.keys.forEach((k, i) => (params[k] = decodeURIComponent(m[i + 1]!)));
|
|
60
|
+
return { handler: r.handler, params };
|
|
61
|
+
}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/* ----------------------------------------------------------- response helpers */
|
|
67
|
+
|
|
68
|
+
export function sendJson(res: ServerResponse, status: number, data: unknown): void {
|
|
69
|
+
const body = JSON.stringify(data);
|
|
70
|
+
res.writeHead(status, {
|
|
71
|
+
'Content-Type': 'application/json; charset=utf-8',
|
|
72
|
+
'Cache-Control': 'no-store',
|
|
73
|
+
'X-Content-Type-Options': 'nosniff',
|
|
74
|
+
});
|
|
75
|
+
res.end(body);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function sendDownload(res: ServerResponse, filename: string, mimeType: string, content: string): void {
|
|
79
|
+
const safe = filename.replace(/[^\w.\-가-힣 ]/g, '_');
|
|
80
|
+
res.writeHead(200, {
|
|
81
|
+
'Content-Type': mimeType,
|
|
82
|
+
'Content-Disposition': `attachment; filename*=UTF-8''${encodeURIComponent(safe)}`,
|
|
83
|
+
'Cache-Control': 'no-store',
|
|
84
|
+
'X-Content-Type-Options': 'nosniff',
|
|
85
|
+
});
|
|
86
|
+
res.end(content);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Thrown by handlers to produce a clean error response. */
|
|
90
|
+
export class HttpError extends Error {
|
|
91
|
+
constructor(public status: number, message: string, public code?: string) {
|
|
92
|
+
super(message);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export async function readJsonBody<T>(req: IncomingMessage, schema?: ZodType<T>): Promise<T> {
|
|
97
|
+
const raw = await readBody(req);
|
|
98
|
+
if (!raw) return (schema ? schema.parse({}) : {}) as T;
|
|
99
|
+
let parsed: unknown;
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(raw);
|
|
102
|
+
} catch {
|
|
103
|
+
throw new HttpError(400, '잘못된 JSON 형식입니다.', 'bad_json');
|
|
104
|
+
}
|
|
105
|
+
if (!schema) return parsed as T;
|
|
106
|
+
try {
|
|
107
|
+
return schema.parse(parsed);
|
|
108
|
+
} catch (e) {
|
|
109
|
+
if (e instanceof ZodError) {
|
|
110
|
+
const msg = e.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ');
|
|
111
|
+
throw new HttpError(400, `입력값 검증 실패 — ${msg}`, 'validation');
|
|
112
|
+
}
|
|
113
|
+
throw e;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function readBody(req: IncomingMessage): Promise<string> {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
let size = 0;
|
|
120
|
+
const chunks: Buffer[] = [];
|
|
121
|
+
req.on('data', (c: Buffer) => {
|
|
122
|
+
size += c.length;
|
|
123
|
+
if (size > MAX_BODY) {
|
|
124
|
+
reject(new HttpError(413, '요청 본문이 너무 큽니다.', 'too_large'));
|
|
125
|
+
req.destroy();
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
chunks.push(c);
|
|
129
|
+
});
|
|
130
|
+
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
131
|
+
req.on('error', reject);
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* -------------------------------------------------------------- static files */
|
|
136
|
+
|
|
137
|
+
const MIME: Record<string, string> = {
|
|
138
|
+
'.html': 'text/html; charset=utf-8',
|
|
139
|
+
'.js': 'text/javascript; charset=utf-8',
|
|
140
|
+
'.mjs': 'text/javascript; charset=utf-8',
|
|
141
|
+
'.css': 'text/css; charset=utf-8',
|
|
142
|
+
'.json': 'application/json; charset=utf-8',
|
|
143
|
+
'.svg': 'image/svg+xml',
|
|
144
|
+
'.png': 'image/png',
|
|
145
|
+
'.jpg': 'image/jpeg',
|
|
146
|
+
'.ico': 'image/x-icon',
|
|
147
|
+
'.woff2': 'font/woff2',
|
|
148
|
+
'.map': 'application/json',
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Serve a file from `root`, refusing any path that escapes it. Returns false if
|
|
153
|
+
* not found so the caller can fall back (e.g. SPA index).
|
|
154
|
+
*/
|
|
155
|
+
export function serveStatic(
|
|
156
|
+
res: ServerResponse,
|
|
157
|
+
root: string,
|
|
158
|
+
relPath: string,
|
|
159
|
+
transform?: (html: string) => string,
|
|
160
|
+
): boolean {
|
|
161
|
+
const cleaned = decodeURIComponent(relPath).replace(/\0/g, '');
|
|
162
|
+
const resolved = path.resolve(root, '.' + path.posix.normalize('/' + cleaned));
|
|
163
|
+
// Path-traversal guard: resolved file must stay within root.
|
|
164
|
+
if (resolved !== root && !resolved.startsWith(root + path.sep)) {
|
|
165
|
+
res.writeHead(403).end('Forbidden');
|
|
166
|
+
return true;
|
|
167
|
+
}
|
|
168
|
+
if (!fs.existsSync(resolved) || !fs.statSync(resolved).isFile()) return false;
|
|
169
|
+
const ext = path.extname(resolved).toLowerCase();
|
|
170
|
+
const mime = MIME[ext] ?? 'application/octet-stream';
|
|
171
|
+
let payload: Buffer | string = fs.readFileSync(resolved);
|
|
172
|
+
if (transform && (ext === '.html')) payload = transform(payload.toString('utf8'));
|
|
173
|
+
res.writeHead(200, {
|
|
174
|
+
'Content-Type': mime,
|
|
175
|
+
'X-Content-Type-Options': 'nosniff',
|
|
176
|
+
'Cache-Control': ext === '.html' ? 'no-store' : 'no-cache',
|
|
177
|
+
});
|
|
178
|
+
res.end(payload);
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CareerMate local app entry point.
|
|
3
|
+
*
|
|
4
|
+
* npm start → starts the dashboard at http://127.0.0.1:<port> and opens
|
|
5
|
+
* your browser. The MCP server (apps/mcp) is launched
|
|
6
|
+
* separately by your AI client and shares the same database.
|
|
7
|
+
*/
|
|
8
|
+
import { spawn } from 'node:child_process';
|
|
9
|
+
import { startServer } from './server.ts';
|
|
10
|
+
import { getDataDir } from '@careermate/db';
|
|
11
|
+
import { APP_VERSION } from './info.ts';
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PORT = Number(process.env.CAREERMATE_PORT ?? 4319);
|
|
14
|
+
|
|
15
|
+
function openBrowser(url: string): void {
|
|
16
|
+
if (process.env.CAREERMATE_NO_OPEN === '1') return;
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
try {
|
|
19
|
+
if (platform === 'win32') {
|
|
20
|
+
spawn('cmd', ['/c', 'start', '', url], { stdio: 'ignore', detached: true }).unref();
|
|
21
|
+
} else if (platform === 'darwin') {
|
|
22
|
+
spawn('open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
23
|
+
} else {
|
|
24
|
+
spawn('xdg-open', [url], { stdio: 'ignore', detached: true }).unref();
|
|
25
|
+
}
|
|
26
|
+
} catch {
|
|
27
|
+
/* opening the browser is best-effort */
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async function main(): Promise<void> {
|
|
32
|
+
const { url } = await startServer(DEFAULT_PORT);
|
|
33
|
+
const line = '─'.repeat(54);
|
|
34
|
+
console.log(`\n┌${line}┐`);
|
|
35
|
+
console.log(` CareerMate v${APP_VERSION} — 내 커리어 흐름 관리 도구`);
|
|
36
|
+
console.log(`${' '.repeat(2)}${'┄'.repeat(52)}`);
|
|
37
|
+
console.log(` 대시보드 ${url}`);
|
|
38
|
+
console.log(` 설치 안내 ${url}/install`);
|
|
39
|
+
console.log(` 데이터 위치 ${getDataDir()}`);
|
|
40
|
+
console.log(` (모든 데이터는 이 컴퓨터에만 저장됩니다)`);
|
|
41
|
+
console.log(`└${line}┘\n`);
|
|
42
|
+
console.log(`종료하려면 Ctrl+C 를 누르세요.`);
|
|
43
|
+
openBrowser(url);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
main().catch((err) => {
|
|
47
|
+
console.error('CareerMate 시작 실패:', err instanceof Error ? err.message : err);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
});
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server + environment metadata surfaced to the Settings page and the /api/health
|
|
3
|
+
* endpoint. Lets non-technical users *see* where their data lives, which is a
|
|
4
|
+
* core privacy promise: everything is on this machine.
|
|
5
|
+
*/
|
|
6
|
+
import { getDataDir, getDbPath, getExportsDir, getBackupsDir } from '@careermate/db';
|
|
7
|
+
import {
|
|
8
|
+
profileRepo,
|
|
9
|
+
experienceRepo,
|
|
10
|
+
projectRepo,
|
|
11
|
+
skillRepo,
|
|
12
|
+
documentRepo,
|
|
13
|
+
coverLetterRepo,
|
|
14
|
+
jobRepo,
|
|
15
|
+
applicationRepo,
|
|
16
|
+
interviewRepo,
|
|
17
|
+
} from '@careermate/db';
|
|
18
|
+
|
|
19
|
+
export const APP_NAME = 'CareerMate';
|
|
20
|
+
export const APP_VERSION = '0.1.0';
|
|
21
|
+
|
|
22
|
+
let _port = 0;
|
|
23
|
+
export function setServerPort(port: number): void {
|
|
24
|
+
_port = port;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function getServerInfo() {
|
|
28
|
+
return {
|
|
29
|
+
name: APP_NAME,
|
|
30
|
+
version: APP_VERSION,
|
|
31
|
+
port: _port,
|
|
32
|
+
url: `http://127.0.0.1:${_port}`,
|
|
33
|
+
data_dir: getDataDir(),
|
|
34
|
+
db_path: getDbPath(),
|
|
35
|
+
exports_dir: getExportsDir(),
|
|
36
|
+
backups_dir: getBackupsDir(),
|
|
37
|
+
node_version: process.version,
|
|
38
|
+
counts: {
|
|
39
|
+
profile: profileRepo.get() ? 1 : 0,
|
|
40
|
+
experiences: experienceRepo.list().length,
|
|
41
|
+
projects: projectRepo.list().length,
|
|
42
|
+
skills: skillRepo.list().length,
|
|
43
|
+
documents: documentRepo.list().length,
|
|
44
|
+
cover_letters: coverLetterRepo.list().length,
|
|
45
|
+
jobs: jobRepo.list().length,
|
|
46
|
+
applications: applicationRepo.list().length,
|
|
47
|
+
interview_preps: interviewRepo.list().length,
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|