-
Notifications
You must be signed in to change notification settings - Fork 1
feat: SaaS Production Ready - Version/Metrics Endpoints & Documentation Updates #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
0b5d6f5
25500d7
34f55a7
6e43915
cf77dd4
d4f5194
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| // Re-export supabase client from database.js for backward compatibility | ||
| export { supabase } from './database.js'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,6 +55,42 @@ app.get("/health", (req, res) => { | |
| }); | ||
| }); | ||
|
|
||
| // Version endpoint | ||
| app.get("/version", (req, res) => { | ||
| res.json({ | ||
| version: "1.0.0", | ||
| name: "ai-grant-crawler-backend", | ||
| buildTime: process.env.BUILD_TIME || new Date().toISOString(), | ||
| nodeVersion: process.version, | ||
| environment: env.nodeEnv, | ||
| }); | ||
| }); | ||
|
|
||
| // Metrics endpoint (basic) | ||
| const startTime = Date.now(); | ||
| let requestCount = 0; | ||
| app.use((req, res, next) => { | ||
| requestCount++; | ||
| next(); | ||
| }); | ||
|
Comment on lines
+69
to
+75
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check if the file exists and get its size
if [ -f "backend/src/server.js" ]; then
echo "=== File found ==="
wc -l backend/src/server.js
else
echo "File not found at backend/src/server.js"
# Search for server.js files
find . -name "server.js" -type f 2>/dev/null
fiRepository: Datakult0r/ai-grant-crawler-a2a-pro Length of output: 123 🏁 Script executed: #!/bin/bash
# Read the entire server.js file to see the code structure and route order
cat -n backend/src/server.jsRepository: Datakult0r/ai-grant-crawler-a2a-pro Length of output: 5254 Move the request counter middleware before the The middleware at line 72 is registered after these routes, so requests to 🤖 Prompt for AI Agents |
||
|
|
||
| app.get("/metrics", (req, res) => { | ||
| const uptime = Math.floor((Date.now() - startTime) / 1000); | ||
| const memoryUsage = process.memoryUsage(); | ||
|
|
||
| res.json({ | ||
| uptime: uptime, | ||
| uptimeFormatted: `${Math.floor(uptime / 3600)}h ${Math.floor((uptime % 3600) / 60)}m ${uptime % 60}s`, | ||
| requestCount: requestCount, | ||
| memory: { | ||
| heapUsed: Math.round(memoryUsage.heapUsed / 1024 / 1024) + " MB", | ||
| heapTotal: Math.round(memoryUsage.heapTotal / 1024 / 1024) + " MB", | ||
| rss: Math.round(memoryUsage.rss / 1024 / 1024) + " MB", | ||
| }, | ||
| environment: env.nodeEnv, | ||
| }); | ||
| }); | ||
|
|
||
| // API routes | ||
| app.use("/api/grants", grantsRouter); | ||
| app.use("/api/crawler", crawlerRouter); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -229,3 +229,19 @@ export async function sendEmail(to, subject, html) { | |
| return { success: false, error: err.message }; | ||
| } | ||
| } | ||
|
|
||
| // Wrapper functions for notification scheduler | ||
| export async function notifyDeadlineApproaching(email, grant, daysRemaining, unsubscribeToken = 'default') { | ||
| const { subject, html } = createDeadlineAlertEmail(grant, daysRemaining, unsubscribeToken); | ||
| return sendEmail(email, subject, html); | ||
| } | ||
|
|
||
| export async function notifyNewHighRelevanceGrant(email, grant, unsubscribeToken = 'default') { | ||
| const { subject, html } = createNewGrantAlertEmail([grant], unsubscribeToken); | ||
| return sendEmail(email, subject, html); | ||
| } | ||
|
|
||
| export async function sendWeeklyDigest(email, grants, proposals, stats, unsubscribeToken = 'default') { | ||
| const { subject, html } = createWeeklyDigestEmail(stats, grants.slice(0, 5), unsubscribeToken); | ||
| return sendEmail(email, subject, html); | ||
| } | ||
|
Comment on lines
+244
to
+247
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused
🛠️ Proposed fix-export async function sendWeeklyDigest(email, grants, proposals, stats, unsubscribeToken = 'default') {
- const { subject, html } = createWeeklyDigestEmail(stats, grants.slice(0, 5), unsubscribeToken);
+export async function sendWeeklyDigest(email, grants, stats, unsubscribeToken = 'default') {
+ const topGrants = Array.isArray(grants) ? grants.slice(0, 5) : [];
+ const { subject, html } = createWeeklyDigestEmail(stats, topGrants, unsubscribeToken);
return sendEmail(email, subject, html);
}If 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -7,12 +7,11 @@ This guide covers deploying the AI Grant Crawler application to production. | |
| The application consists of two main components: | ||
|
|
||
| 1. **Frontend** (SvelteKit) - Deployed to Vercel (automatic via GitHub integration) | ||
| 2. **Backend** (Node.js/Express) - Deployed to Fly.io (manual setup required) | ||
| 2. **Backend** (Node.js/Express) - Deployed to Railway or Fly.io | ||
|
|
||
| ## Prerequisites | ||
|
|
||
| - [Fly.io CLI](https://fly.io/docs/hands-on/install-flyctl/) installed | ||
| - Fly.io account created | ||
| - Railway account OR [Fly.io CLI](https://fly.io/docs/hands-on/install-flyctl/) installed | ||
| - Supabase project with database schema applied | ||
| - API keys for Gemini (required) and OpenRouter (optional) | ||
|
|
||
|
|
@@ -28,7 +27,46 @@ Set these in your Vercel project settings: | |
| PUBLIC_API_URL=https://your-backend.fly.dev/api | ||
| ``` | ||
|
|
||
| ## Backend Deployment (Fly.io) | ||
| ## Backend Deployment (Railway) - Recommended | ||
|
|
||
| Railway is the recommended deployment platform for the backend. A `railway.json` configuration file is already included. | ||
|
|
||
| ### 1. Connect Repository | ||
|
|
||
| 1. Go to [Railway Dashboard](https://railway.app/dashboard) | ||
| 2. Click "New Project" > "Deploy from GitHub repo" | ||
| 3. Select `Datakult0r/ai-grant-crawler-a2a-pro` | ||
| 4. Choose the `backend` directory as the root | ||
|
|
||
| ### 2. Set Environment Variables | ||
|
|
||
| In Railway Dashboard > Project > Variables: | ||
|
|
||
| ``` | ||
| # Required | ||
| SUPABASE_URL=https://your-project.supabase.co | ||
| SUPABASE_ANON_KEY=your-anon-key | ||
| GEMINI_API_KEY=your-gemini-key | ||
| PORT=3000 | ||
|
|
||
| # Optional | ||
| OPENROUTER_API_KEY=your-openrouter-key | ||
| LOW_COST_MODE=true | ||
| AI_RESEARCHER_ENABLED=false | ||
| IS_DEMO=false | ||
| ``` | ||
|
|
||
| ### 3. Deploy | ||
|
|
||
| Railway will automatically deploy when you push to the connected branch. You can also trigger manual deploys from the dashboard. | ||
|
|
||
| ### 4. Get Your Backend URL | ||
|
|
||
| After deployment, Railway provides a URL like `https://your-app.up.railway.app`. Use this for the frontend's `PUBLIC_API_URL`. | ||
|
|
||
| --- | ||
|
|
||
| ## Backend Deployment (Fly.io) - Alternative | ||
|
|
||
| ### 1. Install Fly CLI | ||
|
|
||
|
|
@@ -130,9 +168,23 @@ Access your app's metrics and logs at: https://fly.io/apps/your-app-name | |
|
|
||
| ### Health Check | ||
|
|
||
| The backend exposes a `/health` endpoint that returns: | ||
| - Database connectivity status | ||
| - API key configuration status | ||
| The backend exposes several monitoring endpoints: | ||
|
|
||
| **`/health`** - Basic health check | ||
| - Returns: `{ status: "ok", timestamp, environment }` | ||
|
|
||
| **`/version`** - Application version info | ||
| - Returns: `{ version, name, buildTime, nodeVersion, environment }` | ||
|
|
||
| **`/metrics`** - Runtime metrics | ||
| - Returns: `{ uptime, uptimeFormatted, requestCount, memory: { heapUsed, heapTotal, rss }, environment }` | ||
|
|
||
| Example: | ||
| ```bash | ||
| curl https://your-app.fly.dev/health | ||
| curl https://your-app.fly.dev/version | ||
| curl https://your-app.fly.dev/metrics | ||
| ``` | ||
|
Comment on lines
+171
to
+187
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Clarify The implementation returns memory values as strings with “ MB”, but the doc implies raw values. Please document the units/format (or adjust the API to return numeric values with explicit units). 📝 Suggested doc tweak **`/metrics`** - Runtime metrics
-- Returns: `{ uptime, uptimeFormatted, requestCount, memory: { heapUsed, heapTotal, rss }, environment }`
+- Returns: `{ uptime, uptimeFormatted, requestCount, memory: { heapUsed, heapTotal, rss }, environment }` (memory values are strings in MB)🤖 Prompt for AI Agents |
||
|
|
||
| ## Troubleshooting | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
cat -n backend/src/server.js | head -80Repository: Datakult0r/ai-grant-crawler-a2a-pro
Length of output: 3182
🏁 Script executed:
rg "BUILD_TIME" --type js --type json --type yaml --type shRepository: Datakult0r/ai-grant-crawler-a2a-pro
Length of output: 167
🏁 Script executed:
cat -n backend/src/config/env.js | head -50Repository: Datakult0r/ai-grant-crawler-a2a-pro
Length of output: 1628
🏁 Script executed:
Repository: Datakult0r/ai-grant-crawler-a2a-pro
Length of output: 2556
Make
buildTimestable across requests.The
buildTimevalue is computed on every request sinceBUILD_TIMEis not set as an environment variable. This means the endpoint returns the current time instead of the actual build time, and recalculates it unnecessarily for each request. Compute it once at module startup.🔧 Suggested fix
🤖 Prompt for AI Agents