add simple admin service
Build and deploy / deploy (push) Successful in 16s

This commit is contained in:
2026-05-18 02:48:47 +03:00
parent 10a83d39a2
commit 3a3b2bcfc0
84 changed files with 2134 additions and 5 deletions
+71
View File
@@ -0,0 +1,71 @@
import { paths } from '../config/paths.mjs';
import {
handleCreateArticle,
handleDeleteArticle,
handleListArticles,
handleUpdateArticle,
} from '../entities/article/articleHandlers.mjs';
import { handleListAssets, handleUploadAsset } from '../entities/asset/assetHandlers.mjs';
import { httpError } from '../shared/http/httpError.mjs';
import { sendError } from '../shared/http/sendError.mjs';
import { serveFile } from '../shared/static/serveFile.mjs';
export async function handleRequest(request, response) {
try {
return await routeRequest(request, response);
} catch (error) {
return sendError(response, error);
}
}
async function routeRequest(request, response) {
const url = new URL(request.url || '/', `http://${request.headers.host || 'localhost'}`);
if (url.pathname === '/api/articles' && request.method === 'GET') {
return await handleListArticles(request, response);
}
if (url.pathname === '/api/articles' && request.method === 'POST') {
return await handleCreateArticle(request, response);
}
if (url.pathname === '/api/assets' && request.method === 'GET') {
return await handleListAssets(request, response);
}
if (url.pathname === '/api/assets' && request.method === 'POST') {
return await handleUploadAsset(request, response);
}
const articleRoute = url.pathname.match(/^\/api\/articles\/(.+)$/);
if (articleRoute) {
return await routeArticleBySlug(request, response, decodeURIComponent(articleRoute[1]));
}
return await routeStaticFile(request, response, url.pathname);
}
async function routeArticleBySlug(request, response, slug) {
if (request.method === 'PUT') {
return await handleUpdateArticle(request, response, slug);
}
if (request.method === 'DELETE') {
return await handleDeleteArticle(request, response, slug);
}
throw httpError(405, 'Method not allowed');
}
async function routeStaticFile(request, response, pathname) {
if (request.method !== 'GET' && request.method !== 'HEAD') {
throw httpError(405, 'Method not allowed');
}
if (pathname.startsWith('/assets/')) {
return await serveFile(response, paths.webPublicRoot, pathname, request.method);
}
const adminPath = pathname === '/' ? '/index.html' : pathname;
return await serveFile(response, paths.adminPublicRoot, adminPath, request.method);
}
+30
View File
@@ -0,0 +1,30 @@
import { createServer } from 'node:http';
import { paths } from '../config/paths.mjs';
import { serverConfig } from '../config/server.mjs';
import { handleRequest } from './requestHandler.mjs';
export function startServer() {
const server = createServer(handleRequest);
server.on('error', (error) => {
if (error.code === 'EADDRINUSE') {
console.error(`Port ${serverConfig.port} is already in use. Run with ADMIN_PORT=3334 npm run admin:posts.`);
process.exit(1);
}
throw error;
});
server.listen(serverConfig.port, serverConfig.host, () => {
console.log(`Local posts admin: http://${serverConfig.host}:${serverConfig.port}`);
console.log(`Editing: ${paths.articlesFile}`);
});
for (const signal of ['SIGINT', 'SIGTERM']) {
process.on(signal, () => {
server.close(() => process.exit(0));
});
}
return server;
}