204 lines
5.6 KiB
TypeScript
Executable File
204 lines
5.6 KiB
TypeScript
Executable File
import { App, Context, staticFiles } from "fresh";
|
|
import { type State } from "./utils.ts";
|
|
import "$std/dotenv/load.ts";
|
|
import { checkDatabaseConnection } from "./db/db.ts";
|
|
|
|
const isDevelopment = Deno.env.get("DEVELOPMENT") === "true";
|
|
//console.log("Development mode:", isDevelopment);
|
|
|
|
/** Error page HTML for service unavailability */
|
|
const SERVICE_UNAVAILABLE_HTML = `<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Service Unavailable</title>
|
|
<style>
|
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
body {
|
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, sans-serif;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
min-height: 100vh;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
padding: 20px;
|
|
}
|
|
.container {
|
|
background: white;
|
|
border-radius: 16px;
|
|
padding: 48px;
|
|
max-width: 500px;
|
|
text-align: center;
|
|
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
|
}
|
|
.icon {
|
|
font-size: 64px;
|
|
margin-bottom: 24px;
|
|
}
|
|
h1 {
|
|
color: #1a202c;
|
|
font-size: 24px;
|
|
margin-bottom: 16px;
|
|
}
|
|
p {
|
|
color: #4a5568;
|
|
font-size: 16px;
|
|
line-height: 1.6;
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="icon">🔧</div>
|
|
<h1>The problem is on our side!</h1>
|
|
<p>We will fix this as soon as we can.</p>
|
|
</div>
|
|
</body>
|
|
</html>`;
|
|
|
|
/** Check if required KV cache files exist */
|
|
function checkKvCacheFiles(): boolean {
|
|
// const requiredFiles = ["./data/kv-cache-shm", "./data/kv-cache-wal"];
|
|
// for (const file of requiredFiles) {
|
|
// try {
|
|
// Deno.statSync(file);
|
|
// } catch {
|
|
// console.error(`⚠️ Required file missing: ${file}`);
|
|
// return false;
|
|
// }
|
|
// }
|
|
return true;
|
|
}
|
|
|
|
/** Perform all startup checks */
|
|
async function performStartupChecks(): Promise<boolean> {
|
|
//console.log("Checking startup requirements...");
|
|
|
|
// Check KV cache files
|
|
const kvFilesExist = checkKvCacheFiles();
|
|
if (!kvFilesExist) {
|
|
console.error(
|
|
"⚠️ KV cache files missing - app will display maintenance page",
|
|
);
|
|
return false;
|
|
}
|
|
//console.log("✅ KV cache files found");
|
|
|
|
// Check database connection
|
|
//console.log("Checking database connection...");
|
|
const dbAvailable = await checkDatabaseConnection();
|
|
if (!dbAvailable) {
|
|
console.error(
|
|
"⚠️ Database connection failed - app will display maintenance page",
|
|
);
|
|
return false;
|
|
}
|
|
//console.log("✅ Database connection successful");
|
|
|
|
return true;
|
|
}
|
|
|
|
// Run startup checks first
|
|
const isAppReady = await performStartupChecks();
|
|
|
|
// Create app based on startup check result
|
|
export const app = new App<State>();
|
|
|
|
if (isAppReady) {
|
|
// Full app with all routes and middleware
|
|
const { InitiPGSessionMiddleware } = await import(
|
|
"./services/stateMiddleware.ts"
|
|
);
|
|
|
|
app.use(staticFiles());
|
|
|
|
app.get("/health", (_ctx: Context<State>) => {
|
|
console.log("Health check");
|
|
return new Response("OK");
|
|
});
|
|
|
|
// app.post("/graph-update", async (ctx: Context<State>) => {
|
|
// console.log("Graph update");
|
|
// const body = await ctx.req.json();
|
|
// //console.log(body);
|
|
// //const { userId, roles, organization } = body;
|
|
|
|
// // await inMemoryCacheKv.set(["ciam", userId], {
|
|
// // userId,
|
|
// // roles,
|
|
// // organization,
|
|
// // lastSynced: Date.now(),
|
|
// // });
|
|
|
|
// //console.log(`Graph update applied for user ${userId}`);
|
|
// return new Response("Graph update stored in cache", { status: 202 });
|
|
// });
|
|
|
|
// TODO: add a well-known endpoint
|
|
// app.get("/.well-known/acme-challenge/:token", (ctx: Context<State>) => {
|
|
// const token = ctx.params.token;
|
|
// const content = `${token}.EXAMPLE_ACCOUNT_THUMBPRINT`;
|
|
// return new Response(content, {
|
|
// status: 200,
|
|
// headers: {
|
|
// "content-type": "text/plain; charset=utf-8",
|
|
// "cache-control": "public, max-age=0, must-revalidate",
|
|
// },
|
|
// });
|
|
// });
|
|
|
|
app.use(InitiPGSessionMiddleware());
|
|
app.fsRoutes();
|
|
|
|
//console.log("✅ Full application loaded");
|
|
} else {
|
|
// Maintenance mode - serve error page for all routes
|
|
app.use(staticFiles());
|
|
|
|
app.get("/health", () => {
|
|
return new Response("Service Unavailable", { status: 503 });
|
|
});
|
|
|
|
app.use(() => {
|
|
return new Response(SERVICE_UNAVAILABLE_HTML, {
|
|
status: 503,
|
|
headers: { "Content-Type": "text/html; charset=utf-8" },
|
|
});
|
|
});
|
|
|
|
console.log("⚠️ Maintenance mode - serving error page only");
|
|
|
|
// Start periodic database connection check (every 60 seconds)
|
|
setInterval(async () => {
|
|
console.log("🔄 Checking if database connection is restored...");
|
|
const dbAvailable = await checkDatabaseConnection();
|
|
if (dbAvailable) {
|
|
console.log("✅ Database connection restored! Restarting app...");
|
|
|
|
// Check if running in development mode
|
|
const restartCmd = isDevelopment ? "exec deno task dev" : "exec ./server";
|
|
|
|
// Use exec to replace shell with new process, keeping terminal connection
|
|
const command = new Deno.Command("sh", {
|
|
args: [
|
|
"-c",
|
|
`lsof -ti:8080 | xargs kill -9 2>/dev/null || true; ${restartCmd}`,
|
|
],
|
|
cwd: Deno.cwd(),
|
|
stdin: "inherit",
|
|
stdout: "inherit",
|
|
stderr: "inherit",
|
|
});
|
|
command.spawn();
|
|
Deno.exit(0);
|
|
} else {
|
|
console.log("❌ Database still unavailable, will retry in 60 seconds");
|
|
}
|
|
}, 60_000);
|
|
}
|
|
|
|
// if (import.meta.main) {
|
|
// await app.listen();
|
|
// }
|