Skip to content

Commit e01bfa7

Browse files
committed
Fix serverless deployment issues - Remove .listen() call that caused FUNCTION_INVOCATION_FAILED - Add better error handling and logging - Optimize database connection for serverless - Improve CORS configuration - Add health check endpoint - Better environment variable validation
1 parent e7fb7d3 commit e01bfa7

12 files changed

Lines changed: 148 additions & 14 deletions

File tree

apps/backend/api/elysia-handler.ts

Whitespace-only changes.

apps/backend/api/index.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import type { VercelRequest, VercelResponse } from "@vercel/node";
2+
import { app } from "../src/index";
3+
4+
export default async function handler(req: VercelRequest, res: VercelResponse) {
5+
try {
6+
const url = new URL(req.url!, `http://${req.headers.host}`);
7+
8+
// Create a Request object that Elysia can handle
9+
const request = new Request(url.href, {
10+
method: req.method,
11+
headers: new Headers(req.headers as Record<string, string>),
12+
body: req.method !== "GET" && req.method !== "HEAD" ? JSON.stringify(req.body) : undefined,
13+
});
14+
15+
const response = await app.handle(request);
16+
17+
// Set status code
18+
res.status(response.status);
19+
20+
// Set headers
21+
response.headers.forEach((value, key) => {
22+
res.setHeader(key, value);
23+
});
24+
25+
// Send response body
26+
const text = await response.text();
27+
res.send(text);
28+
} catch (error) {
29+
console.error("Error handling request:", error);
30+
console.error(
31+
"Error stack:",
32+
error instanceof Error ? error.stack : "No stack trace available"
33+
);
34+
res.status(500).json({
35+
error: "Internal server error",
36+
message: error instanceof Error ? error.message : "Unknown error",
37+
});
38+
}
39+
}

apps/backend/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,8 @@
4040
"zod": "^3.25.56"
4141
},
4242
"devDependencies": {
43-
"@eventer/typescript-config": "workspace:^",
4443
"@types/minimatch": "^6.0.0",
44+
"@vercel/node": "^3.0.0",
4545
"bun-types": "latest",
4646
"dotenv": "^16.5.0",
4747
"drizzle-kit": "^0.31.1",

apps/backend/src/env.ts

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,19 @@ config();
55

66
const envSchema = z.object({
77
PORT: z.coerce.number().default(4000),
8-
DATABASE_URL: z.string(),
8+
DATABASE_URL: z.string().min(1, "DATABASE_URL is required"),
99
SUPABASE_URL: z.string().default(""),
1010
SUPABASE_KEY: z.string().default(""),
1111
CORS_ORIGIN: z.string().default("http://localhost:3000"),
1212
});
1313

14-
export const env = envSchema.parse(process.env);
14+
// Add better error handling for environment validation
15+
let env: z.infer<typeof envSchema>;
16+
try {
17+
env = envSchema.parse(process.env);
18+
} catch (error) {
19+
console.error("Environment validation failed:", error);
20+
throw new Error("Invalid environment configuration");
21+
}
22+
23+
export { env };

apps/backend/src/index.ts

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,42 @@ import { eventRouter } from "./modules/event";
88
import { homeRouter } from "./modules/home";
99
import { userRouter } from "./modules/user";
1010

11+
const port = env.PORT ?? Number(process.env.PORT) ?? 3000;
12+
1113
const app = new Elysia()
12-
.use(cors({ origin: env.CORS_ORIGIN, credentials: true }))
14+
.onError(({ code, error, set }) => {
15+
console.error(`Error [${code}]:`, error);
16+
17+
if (code === "VALIDATION") {
18+
set.status = 400;
19+
return { error: "Validation Error", message: error.toString() };
20+
}
21+
22+
set.status = 500;
23+
return { error: "Internal Server Error", message: error.toString() };
24+
})
25+
.use(
26+
cors({
27+
origin: [
28+
"https://eventer-web-git-feat-implement-ge-0f5106-methasit-puns-projects.vercel.app",
29+
"https://eventer-web-red.vercel.app", // optionally add production URL
30+
"http://localhost:3000", // for local development
31+
/.*\.vercel\.app$/, // Allow all Vercel preview deployments
32+
],
33+
credentials: true,
34+
})
35+
)
1336
.use(swagger())
1437
.group("/api", (app) =>
1538
app.use(homeRouter).use(eventRouter).use(userRouter).use(agendaRouter).use(authRouter)
16-
)
17-
.listen(env.PORT, () => {
18-
console.log(`Server is running on http://localhost:${env.PORT}/api`);
39+
);
40+
41+
// Only listen if not in serverless environment (for local development)
42+
if (process.env.NODE_ENV !== "production" && !process.env.VERCEL) {
43+
app.listen(port, () => {
44+
console.log(`Server is running on http://localhost:${port}/api`);
1945
});
46+
}
2047

2148
export { app };
2249
export type AppType = typeof app;

apps/backend/src/infrastructure/db/index.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,13 @@ import postgres from "postgres";
33
import { env } from "#backend/env";
44
import * as schema from "./schema";
55

6-
const client = postgres(env.DATABASE_URL);
6+
// Configure for serverless with connection pooling
7+
const client = postgres(env.DATABASE_URL, {
8+
max: 1, // Limit connections for serverless
9+
idle_timeout: 20,
10+
max_lifetime: 60 * 30, // 30 minutes
11+
});
12+
713
export const db = drizzle(client, { schema });
814

915
export type DrizzleClient = typeof db;

apps/backend/src/modules/agenda/agenda.repository.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class AgendaRepository {
1414
const newAgenda: AgendaType = {
1515
...agendaData,
1616
id: uuidv4(),
17+
actualEndTime: null,
1718
};
1819
await this.db.insert(agenda).values(newAgenda);
1920
return newAgenda;

apps/backend/src/modules/event/event.repository.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,15 @@ class EventRepository {
1414
await this.db.insert(events).values({
1515
...event,
1616
id,
17+
startDate: new Date(event.startDate),
18+
endDate: new Date(event.endDate),
1719
});
1820

1921
return {
2022
...event,
2123
id,
24+
startDate: new Date(event.startDate),
25+
endDate: new Date(event.endDate),
2226
};
2327
} catch (error) {
2428
if (error instanceof Error) {
@@ -47,7 +51,18 @@ class EventRepository {
4751

4852
async update(id: string, event: UpdateEventDTO): Promise<EventType> {
4953
try {
50-
const updated = await this.db.update(events).set(event).where(eq(events.id, id)).returning();
54+
const updateData: any = { ...event };
55+
if (event.startDate) {
56+
updateData.startDate = new Date(event.startDate);
57+
}
58+
if (event.endDate) {
59+
updateData.endDate = new Date(event.endDate);
60+
}
61+
const updated = await this.db
62+
.update(events)
63+
.set(updateData)
64+
.where(eq(events.id, id))
65+
.returning();
5166

5267
if (updated.length === 0 || !updated[0]) {
5368
throw new Error("Event not found");

apps/backend/src/modules/event/event.route.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,26 @@ export const eventRouter = new Elysia({ prefix: "/event" })
3636
.post(
3737
"/",
3838
async ({ body }) => {
39+
// Handle date conversion explicitly
40+
let startDate: string;
41+
let endDate: string;
42+
43+
if (typeof body.startDate === "string") {
44+
startDate = body.startDate;
45+
} else {
46+
startDate = (body.startDate as any).toISOString();
47+
}
48+
49+
if (typeof body.endDate === "string") {
50+
endDate = body.endDate;
51+
} else {
52+
endDate = (body.endDate as any).toISOString();
53+
}
54+
3955
const transformedData = {
4056
...body,
41-
startDate: new Date(body.startDate),
42-
endDate: new Date(body.endDate),
57+
startDate,
58+
endDate,
4359
description: body.description ?? null,
4460
createdBy: "user_placeholder", // TODO: Fix auth context injection
4561
};

apps/backend/src/modules/event/services/crud-event.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,10 @@ export async function listEvents(
3030
);
3131
}
3232
if (query?.startDate) {
33-
filtered = filtered.filter((event) => new Date(event.startDate) >= query.startDate);
33+
filtered = filtered.filter((event) => new Date(event.startDate) >= new Date(query.startDate!));
3434
}
3535
if (query?.endDate) {
36-
filtered = filtered.filter((event) => new Date(event.endDate) <= query.endDate);
36+
filtered = filtered.filter((event) => new Date(event.endDate) <= new Date(query.endDate!));
3737
}
3838
if (query?.location) {
3939
filtered = filtered.filter((event) =>

0 commit comments

Comments
 (0)