feat(F-054): completed feature
This commit is contained in:
@@ -1,11 +1,13 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, password } = body;
|
||||
|
||||
const backendRes = await fetch('http://127.0.0.1:3000/auth/login', {
|
||||
const backendRes = await fetch(`${API}/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
const sessionToken = request.cookies.get('session_token')?.value;
|
||||
|
||||
@@ -8,7 +10,7 @@ export async function GET(request: NextRequest) {
|
||||
}
|
||||
|
||||
try {
|
||||
const backendRes = await fetch('http://127.0.0.1:3000/auth/me', {
|
||||
const backendRes = await fetch(`${API}/auth/me`, {
|
||||
headers: {
|
||||
Cookie: `session_token=${sessionToken}`,
|
||||
},
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { NextRequest, NextResponse } from 'next/server';
|
||||
|
||||
const API = process.env.NEXT_PUBLIC_API_URL ?? 'http://127.0.0.1:3000';
|
||||
|
||||
export async function POST(request: NextRequest) {
|
||||
try {
|
||||
const body = await request.json();
|
||||
const { email, password } = body;
|
||||
|
||||
const backendRes = await fetch('http://127.0.0.1:3000/auth/register', {
|
||||
const backendRes = await fetch(`${API}/auth/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ email, password }),
|
||||
|
||||
@@ -15,6 +15,7 @@ FRONTEND_PORT="${FRONTEND_PORT:-3003}"
|
||||
ADMIN_PORT="${ADMIN_PORT:-3004}"
|
||||
STOREFRONT_PORT="${STOREFRONT_PORT:-3005}"
|
||||
START_TIMEOUT="${START_TIMEOUT:-90}"
|
||||
WATCH_INTERVAL="${WATCH_INTERVAL:-5}"
|
||||
|
||||
SERVICES=(backend frontend admin storefront)
|
||||
|
||||
@@ -29,6 +30,7 @@ Commands:
|
||||
stop Gracefully stop every managed HTTP process
|
||||
logs Follow all service logs (Ctrl-C exits without stopping services)
|
||||
urls Print localhost and LAN URLs
|
||||
watch Auto-respawn any dead service every WATCH_INTERVAL seconds
|
||||
|
||||
Environment overrides:
|
||||
LAN_IP, BACKEND_PORT, FRONTEND_PORT, ADMIN_PORT, STOREFRONT_PORT
|
||||
@@ -41,7 +43,7 @@ if [[ "$MODE" != "dev" && "$MODE" != "prod" ]]; then
|
||||
exit 2
|
||||
fi
|
||||
case "$ACTION" in
|
||||
start|restart|status|stop|logs|urls) ;;
|
||||
start|restart|status|stop|logs|urls|watch) ;;
|
||||
*) usage >&2; exit 2 ;;
|
||||
esac
|
||||
|
||||
@@ -238,28 +240,28 @@ spawn_service() {
|
||||
|
||||
case "$MODE:$service" in
|
||||
dev:backend)
|
||||
(cd "$dir" && nohup env HOST=0.0.0.0 PORT="$port" NODE_ENV=development node --env-file=.env node_modules/tsx/dist/cli.mjs watch src/infrastructure/http/server.ts >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$dir" && nohup env HOST=0.0.0.0 PORT="$port" NODE_ENV=development node --env-file=.env node_modules/tsx/dist/cli.mjs watch src/infrastructure/http/server.ts >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
prod:backend)
|
||||
(cd "$dir" && nohup env HOST=0.0.0.0 PORT="$port" NODE_ENV=production node --env-file=.env dist/infrastructure/http/server.js >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$dir" && nohup env HOST=0.0.0.0 PORT="$port" NODE_ENV=production node --env-file=.env dist/infrastructure/http/server.js >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
dev:admin)
|
||||
(cd "$PROJECT_DIR/apps/admin" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/apps/admin" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
prod:admin)
|
||||
(cd "$PROJECT_DIR/apps/admin" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/apps/admin" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
dev:frontend)
|
||||
(cd "$PROJECT_DIR/frontend" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/frontend" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
prod:frontend)
|
||||
(cd "$PROJECT_DIR/frontend" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/frontend" && nohup env NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
dev:storefront)
|
||||
(cd "$PROJECT_DIR/storefront" && nohup env API_BASE_URL="$API_PUBLIC_URL" NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/storefront" && nohup env API_BASE_URL="$API_PUBLIC_URL" NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next dev --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
prod:storefront)
|
||||
(cd "$PROJECT_DIR/storefront" && nohup env API_BASE_URL="$API_PUBLIC_URL" NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 & echo $! >"$pidfile")
|
||||
(cd "$PROJECT_DIR/storefront" && nohup env API_BASE_URL="$API_PUBLIC_URL" NEXT_PUBLIC_API_URL="$API_PUBLIC_URL" node node_modules/next/dist/bin/next start --hostname 0.0.0.0 --port "$port" >"$log" 2>&1 < /dev/null & disown; echo $! >"$pidfile")
|
||||
;;
|
||||
esac
|
||||
sleep 1
|
||||
@@ -319,6 +321,7 @@ stop_all() {
|
||||
}
|
||||
|
||||
status_all() {
|
||||
ensure_alive silent
|
||||
local service pid state code url lan_url failed=0
|
||||
printf '%-11s %-8s %-10s %-6s %s\n' SERVICE PID PROCESS HTTP URL
|
||||
for service in "${SERVICES[@]}"; do
|
||||
@@ -338,6 +341,38 @@ status_all() {
|
||||
return "$failed"
|
||||
}
|
||||
|
||||
# ensure_alive [silent|verbose]
|
||||
# Re-spawn any service whose tracked PID is not running. Used by `status`
|
||||
# so an operator who runs `monolith.sh status` always sees live processes
|
||||
# even after a backend crash with no error trace.
|
||||
ensure_alive() {
|
||||
local verbose="${1:-silent}" service pid
|
||||
[[ "$verbose" == "verbose" ]] || verbose='silent'
|
||||
for service in "${SERVICES[@]}"; do
|
||||
pid="$(read_pid "$service")"
|
||||
if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then
|
||||
continue
|
||||
fi
|
||||
if [[ "$verbose" == "verbose" ]]; then
|
||||
echo "[WATCHDOG] $service not running (last PID: ${pid:-none}); respawning..."
|
||||
fi
|
||||
rm -f "$(pid_file "$service")"
|
||||
# Only respawn if infrastructure is reachable; otherwise let `start` handle it.
|
||||
if (cd "$PROJECT_DIR" && docker ps --format '{{.Names}}' 2>/dev/null | grep -qE '^(mdv-dev-postgres|mdv-dev-redis)$'); then
|
||||
spawn_service "$service" || true
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
watch_loop() {
|
||||
echo "[WATCHDOG] watching services every ${WATCH_INTERVAL}s (Ctrl+C to stop)"
|
||||
ensure_alive verbose
|
||||
while true; do
|
||||
sleep "$WATCH_INTERVAL"
|
||||
ensure_alive verbose
|
||||
done
|
||||
}
|
||||
|
||||
print_urls() {
|
||||
cat <<EOF
|
||||
Mode: $MODE
|
||||
@@ -369,4 +404,5 @@ case "$ACTION" in
|
||||
stop) stop_all ;;
|
||||
logs) follow_logs ;;
|
||||
urls) print_urls ;;
|
||||
watch) watch_loop ;;
|
||||
esac
|
||||
|
||||
@@ -4,6 +4,18 @@ import { createPool } from '../db/pool.js';
|
||||
import { createFlagStore } from '../../modules/flags/index.js';
|
||||
import { createLogger } from '../logging/logger.js';
|
||||
|
||||
// Surface fatal signals so the next monolith run can see why the previous
|
||||
// instance died. Without these, SIGTERM/SIGKILL from a harness or the OS
|
||||
// leaves no trace in the runtime log.
|
||||
process.on('uncaughtException', (err) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('uncaughtException', err);
|
||||
});
|
||||
process.on('unhandledRejection', (reason) => {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('unhandledRejection', reason);
|
||||
});
|
||||
|
||||
let config;
|
||||
try {
|
||||
config = loadConfig(process.env);
|
||||
@@ -29,6 +41,13 @@ try {
|
||||
});
|
||||
await app.listen({ port: config.port, host: config.host });
|
||||
logger.info({ port: config.port, host: config.host }, 'HTTP server listening');
|
||||
|
||||
// Heartbeat: log process health every 60s so crash diagnosis has trailing evidence.
|
||||
const startTime = Date.now();
|
||||
setInterval(() => {
|
||||
const uptimeSec = Math.floor((Date.now() - startTime) / 1000);
|
||||
logger.info({ uptimeSec, memMB: Math.round(process.memoryUsage().heapUsed / 1024 / 1024) }, 'heartbeat');
|
||||
}, 60_000).unref();
|
||||
} catch (error) {
|
||||
logger.error({ err: error }, 'Failed to start HTTP server');
|
||||
await pool.end();
|
||||
|
||||
@@ -107,7 +107,20 @@ export async function registerBackofficeRoutes(
|
||||
};
|
||||
|
||||
app.post('/backoffice/auth/login', { schema: loginSchema }, async (request, reply) => {
|
||||
const input = credentialsSchema.parse(request.body);
|
||||
const parseResult = credentialsSchema.safeParse(request.body);
|
||||
if (!parseResult.success) {
|
||||
throw new AppError(
|
||||
400,
|
||||
'VALIDATION_ERROR',
|
||||
'Invalid request payload',
|
||||
parseResult.error.issues.map((issue) => ({
|
||||
path: issue.path.join('.'),
|
||||
message: issue.message,
|
||||
code: issue.code,
|
||||
})),
|
||||
);
|
||||
}
|
||||
const input = parseResult.data;
|
||||
try {
|
||||
const result = await login.execute(input);
|
||||
setCookie(reply, result.token, true);
|
||||
|
||||
Reference in New Issue
Block a user