Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/server-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ jobs:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
PQL_API_KEY: "op://Product ACT/pql-docs-bot/pql-api-key"
JWT_SECRET: "op://Product ACT/pql-docs-bot/jwt-secret"
SENTRY_DSN: "op://Product ACT/pql-docs-bot/sentry-dsn"

- name: Setup Bun
uses: oven-sh/setup-bun@v1
Expand Down Expand Up @@ -49,6 +50,7 @@ jobs:
OP_SERVICE_ACCOUNT_TOKEN: ${{ secrets.OP_SERVICE_ACCOUNT_TOKEN }}
PQL_API_KEY: "op://Product ACT/pql-docs-bot/pql-api-key"
JWT_SECRET: "op://Product ACT/pql-docs-bot/jwt-secret"
SENTRY_DSN: "op://Product ACT/pql-docs-bot/sentry-dsn"

- name: Build Docker image
run: |
Expand All @@ -62,6 +64,8 @@ jobs:
-p 4000:4000 \
-e SERVER_PORT=4000 \
-e PQL_API_KEY="${PQL_API_KEY}" \
-e JWT_SECRET="${JWT_SECRET}" \
-e SENTRY_DSN="${SENTRY_DSN}" \
docs-bot-server:test

sleep 10
Expand Down
179 changes: 175 additions & 4 deletions bun.lock

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion server/.env.template
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
SERVER_PORT=4000
NGROK_AUTHTOKEN=""
PQL_API_KEY=""
PQL_API_KEY=""
JWT_SECRET=""
SENTRY_DSN=""
2 changes: 2 additions & 0 deletions server/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ ENV NODE_ENV=production
ARG SERVER_PORT=4000
ARG PQL_API_KEY
ARG JWT_SECRET
ARG SENTRY_DSN

ENV SERVER_PORT=${SERVER_PORT}
ENV PQL_API_KEY=${PQL_API_KEY}
ENV JWT_SECRET=${JWT_SECRET}
ENV SENTRY_DSN=${SENTRY_DSN}



Expand Down
3 changes: 3 additions & 0 deletions server/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ Required in `.env`:

- `SERVER_PORT` - Server port (default: 4000)
- `PQL_API_KEY` - PromptQL API key
- `JWT_SECRET` - JWT secret for authentication
- `SENTRY_DSN` - Sentry DSN for error tracking (optional)

## Integration

Expand All @@ -58,3 +60,4 @@ The server acts as a proxy between frontend clients and the PromptQL backend, ha
- CORS for browser requests
- Streaming response formatting
- Error handling and logging
- Error tracking with Sentry integration
4 changes: 3 additions & 1 deletion server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,9 @@
"@elysiajs/cors": "^1.3.3",
"@elysiajs/jwt": "^1.3.2",
"@hasura/promptql": "^0.4.0",
"elysia": "^1.3.8",
"@sentry/bun": "^10.5.0",
"@sinclair/typebox": "^0.34.40",
"elysia": "^1.3.11",
"jsonwebtoken": "^9.0.2"
}
}
37 changes: 35 additions & 2 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,38 @@
import * as Sentry from "@sentry/bun";
import { Elysia } from "elysia";
import { routes } from "./routes";

// Initialize Sentry
Sentry.init({
dsn: process.env.SENTRY_DSN,
environment: process.env.NODE_ENV || "development",
tracesSampleRate: 1.0,
});

const PORT = process.env.PORT ?? process.env.SERVER_PORT ?? "4000";

const app = new Elysia()
.onError(({ error, code, set }) => {
// Capture error in Sentry
Sentry.captureException(error);

console.error("Server error:", error);

// Return appropriate error response
if (code === "VALIDATION") {
set.status = 400;
return { error: "Validation failed", message: error.message };
}

if (code === "NOT_FOUND") {
set.status = 404;
return { error: "Not found" };
}

// Generic server error
set.status = 500;
return { error: "Internal server error" };
})
.onRequest(({ set }) => {
set.headers["Access-Control-Allow-Origin"] = "*";
set.headers["Access-Control-Allow-Methods"] = "GET, POST, OPTIONS";
Expand All @@ -28,16 +57,20 @@ try {
console.log(`🤖 Ready to handle chat requests!`);
} catch (error) {
console.error("❌ Failed to start server:", error);
Sentry.captureException(error);
await Sentry.flush(2000);
process.exit(1);
}

// Graceful shutdown
process.on("SIGTERM", () => {
process.on("SIGTERM", async () => {
console.log("🛑 SIGTERM received, shutting down gracefully");
await Sentry.flush(2000);
process.exit(0);
});

process.on("SIGINT", () => {
process.on("SIGINT", async () => {
console.log("🛑 SIGINT received, shutting down gracefully");
await Sentry.flush(2000);
process.exit(0);
});
7 changes: 7 additions & 0 deletions server/src/routes/chat/handlers/streamHandler.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Sentry from "@sentry/bun";
import type { Context } from "elysia";

export interface StreamState {
Expand Down Expand Up @@ -29,6 +30,12 @@ export function createStreamHandler(conversationId: string, controller: Readable
state.clientDisconnected = true;
} else {
console.error("Failed to enqueue data:", error);
Sentry.captureException(error, {
tags: {
component: "stream_handler",
action: "enqueue_data",
},
});
throw error;
}
}
Expand Down
15 changes: 15 additions & 0 deletions server/src/routes/chat/sendMessage.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as Sentry from "@sentry/bun";
import type { Context } from "elysia";
import { createPromptQLClientV2 } from "@hasura/promptql";
import { createStreamHandler } from "./handlers/streamHandler";
Expand Down Expand Up @@ -148,6 +149,13 @@ export const sendMessage = async ({ body, params, set, request }: Context) => {
})
.catch((error) => {
logRequestError(requestId, error);
Sentry.captureException(error, {
tags: {
requestId,
conversationId,
component: "chat_stream",
},
});
})
.finally(() => {
try {
Expand All @@ -164,6 +172,13 @@ export const sendMessage = async ({ body, params, set, request }: Context) => {
return new Response(stream);
} catch (error) {
safeCleanup();
Sentry.captureException(error, {
tags: {
requestId,
conversationId,
component: "chat_handler",
},
});
throw error;
}
};