feat: health check
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m42s
All checks were successful
Build and Push Docker Image / build (push) Successful in 1m42s
This commit is contained in:
@@ -14,7 +14,10 @@ COPY . .
|
||||
RUN pnpm build
|
||||
|
||||
FROM base AS runtime
|
||||
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/.output ./.output
|
||||
EXPOSE 3000
|
||||
HEALTHCHECK --interval=30s --timeout=10s --start-period=15s --retries=3 \
|
||||
CMD curl -sf http://localhost:3000/api/health || exit 1
|
||||
CMD ["node", ".output/server/index.mjs"]
|
||||
|
||||
89
server/api/health.get.ts
Normal file
89
server/api/health.get.ts
Normal file
@@ -0,0 +1,89 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const gallerySchema = z.array(
|
||||
z.object({
|
||||
filename: z.string(),
|
||||
url: z.string(),
|
||||
width: z.number(),
|
||||
height: z.number(),
|
||||
exif: z.object({
|
||||
date: z.coerce.date(),
|
||||
camera: z.string(),
|
||||
lens: z.string(),
|
||||
settings: z.object({
|
||||
aperture: z.string(),
|
||||
shutter: z.string(),
|
||||
iso: z.string(),
|
||||
focalLength: z.string(),
|
||||
}),
|
||||
}),
|
||||
}),
|
||||
).nonempty();
|
||||
|
||||
const projectSchema = z.array(
|
||||
z.object({
|
||||
order: z.number(),
|
||||
scope: z.enum(["hobby", "work"]),
|
||||
title: z.string(),
|
||||
link: z.string().url(),
|
||||
technologies: z.array(z.string()),
|
||||
en: z.object({
|
||||
description: z.string(),
|
||||
summary: z.string(),
|
||||
tasks: z.array(z.string()),
|
||||
}),
|
||||
fr: z.object({
|
||||
description: z.string(),
|
||||
summary: z.string(),
|
||||
tasks: z.array(z.string()),
|
||||
}),
|
||||
}),
|
||||
).nonempty();
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const checks: Record<string, { ok: boolean; error?: string }> = {};
|
||||
const baseURL = `http://localhost:${process.env.PORT ?? 3000}`;
|
||||
|
||||
try {
|
||||
const raw = await $fetch("/api/gallery", { baseURL });
|
||||
const result = gallerySchema.safeParse(raw);
|
||||
checks.gallery = {
|
||||
ok: result.success,
|
||||
error: result.success ? undefined : result.error.message,
|
||||
};
|
||||
} catch (err) {
|
||||
checks.gallery = { ok: false, error: String(err) };
|
||||
}
|
||||
|
||||
try {
|
||||
const html = await $fetch<string>("/gallery", { baseURL });
|
||||
checks.galleryPage = {
|
||||
ok: html.includes("</html>"),
|
||||
error: html.includes("</html>") ? undefined : "response is not valid HTML",
|
||||
};
|
||||
} catch (err) {
|
||||
checks.galleryPage = { ok: false, error: String(err) };
|
||||
}
|
||||
|
||||
try {
|
||||
const items = await queryCollection(event, "projects").all();
|
||||
const result = projectSchema.safeParse(items);
|
||||
checks.projects = {
|
||||
ok: result.success,
|
||||
error: result.success ? undefined : result.error.message,
|
||||
};
|
||||
} catch (err) {
|
||||
checks.projects = { ok: false, error: String(err) };
|
||||
}
|
||||
|
||||
const allOk = Object.values(checks).every((c) => c.ok);
|
||||
|
||||
if (!allOk) {
|
||||
throw createError({
|
||||
statusCode: 503,
|
||||
data: { status: "unhealthy", checks },
|
||||
});
|
||||
}
|
||||
|
||||
return { status: "ok", checks };
|
||||
});
|
||||
Reference in New Issue
Block a user