chore: sync local workspace changes
This commit is contained in:
239
etc/servers/work-server/src/routes/resource-manager.ts
Normal file
239
etc/servers/work-server/src/routes/resource-manager.ts
Normal 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,
|
||||
};
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user