-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathnode.ts
More file actions
125 lines (115 loc) · 4.37 KB
/
node.ts
File metadata and controls
125 lines (115 loc) · 4.37 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
// Set runtime FIRST (before any imports)
process.env.RUNTIME = "nodejs";
import * as Sentry from "@sentry/node";
import { serve } from "@hono/node-server";
import { createHonoApp } from "@/hono/app";
import { runMigrationsIfNeeded } from "@/db/migrate-local";
import { initCronJobs } from "@/cron/scheduler";
import { initializeAdmin } from "@/services/admin-init";
import { createDatabase } from "@/db/client";
import { getSentryConfig } from "@/config/sentry";
import type { Env } from "@/types";
// Load environment
const env: Env = {
RUNTIME: "nodejs",
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET,
DATABASE_PATH: process.env.DATABASE_PATH || "./data/tuvix.db",
PORT: process.env.PORT || "3001",
CORS_ORIGIN: process.env.CORS_ORIGIN,
NODE_ENV: process.env.NODE_ENV,
BASE_URL: process.env.BASE_URL,
BETTER_AUTH_URL: process.env.BETTER_AUTH_URL || process.env.BASE_URL,
COOKIE_DOMAIN: process.env.COOKIE_DOMAIN,
RESEND_API_KEY: process.env.RESEND_API_KEY,
EMAIL_FROM: process.env.EMAIL_FROM,
ALLOW_FIRST_USER_ADMIN: process.env.ALLOW_FIRST_USER_ADMIN,
ADMIN_USERNAME: process.env.ADMIN_USERNAME,
ADMIN_EMAIL: process.env.ADMIN_EMAIL,
ADMIN_PASSWORD: process.env.ADMIN_PASSWORD,
SENTRY_DSN: process.env.SENTRY_DSN,
SENTRY_ENVIRONMENT: process.env.SENTRY_ENVIRONMENT,
SENTRY_RELEASE: process.env.SENTRY_RELEASE,
};
// Validate required env vars
if (!env.BETTER_AUTH_SECRET) {
console.error(
"❌ FATAL: BETTER_AUTH_SECRET environment variable is required.\n" +
" Generate: openssl rand -base64 32"
);
process.exit(1);
}
if (env.NODE_ENV === "production" && env.BETTER_AUTH_SECRET.length < 32) {
console.warn("⚠️ WARNING: BETTER_AUTH_SECRET should be >=32 characters");
}
// Initialize Sentry (optional)
if (env.SENTRY_DSN) {
const sentryConfig = getSentryConfig(env);
if (sentryConfig) {
Sentry.init({
...sentryConfig,
integrations: [
// Configure HTTP integration to NOT capture request bodies for tRPC routes
// tRPC needs to read the body stream, and Sentry's body capture consumes it
// See: https://docs.sentry.io/platforms/javascript/guides/node/configuration/integrations/http/
Sentry.httpIntegration({
// Ignore request bodies for tRPC routes - tRPC needs to read the stream itself
// If Sentry reads it first, the stream is consumed and tRPC receives undefined
ignoreIncomingRequestBody: (url) => url.includes("/trpc"),
}),
Sentry.nativeNodeFetchIntegration(),
// Vercel AI SDK integration for automatic AI span tracking
// Captures token usage, model info, latency, and errors from AI SDK calls
Sentry.vercelAIIntegration({
recordInputs: true, // Safe: only used for pro/enterprise users with opt-in
recordOutputs: true, // Captures structured category suggestions
}),
// Automatically capture console.log, console.warn, and console.error as logs
Sentry.consoleLoggingIntegration({ levels: ["log", "warn", "error"] }),
// Hono error capturing integration
Sentry.honoIntegration(),
],
});
console.log(
"✅ Sentry initialized (metrics, AI tracking, console logging, HTTP tracing, tRPC tracing enabled)"
);
}
}
// Main initialization
void (async () => {
try {
// Run migrations
await runMigrationsIfNeeded(env);
console.log("✅ Migrations completed");
// Create Hono app
const app = createHonoApp({
env,
sentry: Sentry,
runtime: "nodejs",
});
// Initialize cron jobs (background)
initCronJobs(env).catch((error) => {
console.error("Failed to initialize cron:", error);
});
// Initialize admin
const db = createDatabase(env);
initializeAdmin(db, env)
.then((result) => {
if (result.created) {
console.log(`✅ ${result.message}`);
}
})
.catch((error) => {
console.error("❌ Failed to initialize admin:", error);
});
// Start server
const port = parseInt(env.PORT || "3001", 10);
serve({ fetch: app.fetch, port }, () => {
console.log(`🚀 Hono Server (Node.js) on http://localhost:${port}`);
console.log(`📊 Health: http://localhost:${port}/health`);
console.log(`🔌 tRPC: http://localhost:${port}/trpc`);
});
} catch (error) {
console.error("❌ Failed to start:", error);
process.exit(1);
}
})();