chore: sync local workspace changes

This commit is contained in:
2026-05-07 11:03:47 +09:00
parent 2df0ba30cb
commit 82c0d8a197
217 changed files with 44873 additions and 1678 deletions

View File

@@ -0,0 +1,239 @@
import type { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify';
import { z } from 'zod';
import { env } from '../config/env.js';
import {
copyResourceManagerItem,
createResourceManagerDirectory,
createResourceManagerFile,
deleteResourceManagerItem,
ensureResourceManagerRoot,
getResourceManagerTree,
listResourceManagerDirectory,
moveResourceManagerItem,
openResourceManagerPreviewStream,
readResourceManagerFile,
saveResourceManagerFile,
uploadResourceManagerFile,
} from '../services/resource-manager-service.js';
const queryPathSchema = z.object({
path: z.string().trim().optional().default(''),
});
const createDirectoryBodySchema = z.object({
parentPath: z.string().trim().optional().default(''),
name: z.string().trim().min(1).max(255),
});
const createFileBodySchema = z.object({
parentPath: z.string().trim().optional().default(''),
name: z.string().trim().min(1).max(255),
content: z.string().optional().default(''),
});
const saveFileBodySchema = z.object({
path: z.string().trim().min(1),
content: z.string(),
});
const uploadFileBodySchema = z.object({
parentPath: z.string().trim().optional().default(''),
fileName: z.string().trim().min(1).max(255),
contentBase64: z.string().trim().min(1),
});
const copyMoveBodySchema = z.object({
path: z.string().trim().min(1),
targetDirectoryPath: z.string().trim().optional().default(''),
nextName: z.string().trim().max(255).optional().nullable(),
});
function getRequestAccessToken(request: FastifyRequest) {
const tokenHeader = request.headers['x-access-token'];
return Array.isArray(tokenHeader) ? tokenHeader[0]?.trim() ?? '' : String(tokenHeader ?? '').trim();
}
function ensureAuthorized(request: FastifyRequest, reply: FastifyReply, tokenOverride?: string | null) {
if ((tokenOverride ?? getRequestAccessToken(request)) === env.SERVER_COMMAND_ACCESS_TOKEN) {
return true;
}
reply.status(403);
void reply.send({
message: '권한 토큰이 필요합니다.',
});
return false;
}
function resolveRepoRootPath() {
return env.SERVER_COMMAND_MAIN_PROJECT_ROOT || env.PLAN_MAIN_PROJECT_REPO_PATH || env.PLAN_GIT_REPO_PATH;
}
export async function registerResourceManagerRoutes(app: FastifyInstance) {
app.get('/api/resource-manager/tree', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const repoRootPath = resolveRepoRootPath();
await ensureResourceManagerRoot(repoRootPath);
return {
ok: true,
item: await getResourceManagerTree(repoRootPath),
};
});
app.get('/api/resource-manager/directory', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const query = queryPathSchema.parse(request.query ?? {});
return {
ok: true,
item: await listResourceManagerDirectory(resolveRepoRootPath(), query.path),
};
});
app.get('/api/resource-manager/file', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const query = queryPathSchema.extend({
path: z.string().trim().min(1),
}).parse(request.query ?? {});
return {
ok: true,
item: await readResourceManagerFile(resolveRepoRootPath(), query.path),
};
});
app.get('/api/resource-manager/preview/*', async (request, reply) => {
const query = z.object({
token: z.string().trim().optional(),
}).parse(request.query ?? {});
if (!ensureAuthorized(request, reply, query.token ?? null)) {
return;
}
const wildcard = String((request.params as { '*': string | undefined })['*'] ?? '').trim();
const preview = await openResourceManagerPreviewStream(resolveRepoRootPath(), decodeURIComponent(wildcard));
reply.header('Cache-Control', 'no-store');
reply.type(preview.contentType);
return reply.send(preview.stream);
});
app.post('/api/resource-manager/directories', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = createDirectoryBodySchema.parse(request.body ?? {});
await createResourceManagerDirectory(resolveRepoRootPath(), payload.parentPath, payload.name);
return {
ok: true,
};
});
app.post('/api/resource-manager/files', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = createFileBodySchema.parse(request.body ?? {});
await createResourceManagerFile(resolveRepoRootPath(), payload.parentPath, payload.name, payload.content);
return {
ok: true,
};
});
app.put('/api/resource-manager/files/content', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = saveFileBodySchema.parse(request.body ?? {});
await saveResourceManagerFile(resolveRepoRootPath(), payload.path, payload.content);
return {
ok: true,
};
});
app.post('/api/resource-manager/files/upload', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = uploadFileBodySchema.parse(request.body ?? {});
return {
ok: true,
item: await uploadResourceManagerFile(
resolveRepoRootPath(),
payload.parentPath,
payload.fileName,
payload.contentBase64,
),
};
});
app.post('/api/resource-manager/items/copy', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = copyMoveBodySchema.parse(request.body ?? {});
return {
ok: true,
item: await copyResourceManagerItem(
resolveRepoRootPath(),
payload.path,
payload.targetDirectoryPath,
payload.nextName,
),
};
});
app.post('/api/resource-manager/items/move', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const payload = copyMoveBodySchema.parse(request.body ?? {});
return {
ok: true,
item: await moveResourceManagerItem(
resolveRepoRootPath(),
payload.path,
payload.targetDirectoryPath,
payload.nextName,
),
};
});
app.delete('/api/resource-manager/items', async (request, reply) => {
if (!ensureAuthorized(request, reply)) {
return;
}
const query = queryPathSchema.extend({
path: z.string().trim().min(1),
}).parse(request.query ?? {});
await deleteResourceManagerItem(resolveRepoRootPath(), query.path);
return {
ok: true,
};
});
}