@almadar/server 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +72 -0
- package/dist/index--XhXIuTh.d.ts +120 -0
- package/dist/index-D8fohXsO.d.ts +178 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +1015 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/index.d.ts +4 -0
- package/dist/lib/index.js +279 -0
- package/dist/lib/index.js.map +1 -0
- package/dist/middleware/index.d.ts +71 -0
- package/dist/middleware/index.js +308 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/services/index.d.ts +2 -0
- package/dist/services/index.js +632 -0
- package/dist/services/index.js.map +1 -0
- package/dist/utils/index.d.ts +78 -0
- package/dist/utils/index.js +106 -0
- package/dist/utils/index.js.map +1 -0
- package/package.json +84 -0
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
import { z, ZodError } from 'zod';
|
|
2
|
+
import dotenv from 'dotenv';
|
|
3
|
+
import admin from 'firebase-admin';
|
|
4
|
+
|
|
5
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
6
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
7
|
+
}) : x)(function(x) {
|
|
8
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
9
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
+
});
|
|
11
|
+
dotenv.config();
|
|
12
|
+
var envSchema = z.object({
|
|
13
|
+
NODE_ENV: z.enum(["development", "production", "test"]).default("development"),
|
|
14
|
+
PORT: z.string().default("3030").transform((val) => parseInt(val, 10)),
|
|
15
|
+
CORS_ORIGIN: z.string().default("http://localhost:5173").transform((val) => val.includes(",") ? val.split(",").map((s) => s.trim()) : val),
|
|
16
|
+
// Database (Prisma/SQL) - optional
|
|
17
|
+
DATABASE_URL: z.string().optional(),
|
|
18
|
+
// Firebase/Firestore configuration
|
|
19
|
+
FIREBASE_PROJECT_ID: z.string().optional(),
|
|
20
|
+
FIREBASE_CLIENT_EMAIL: z.string().optional(),
|
|
21
|
+
FIREBASE_PRIVATE_KEY: z.string().optional(),
|
|
22
|
+
FIREBASE_SERVICE_ACCOUNT_PATH: z.string().optional(),
|
|
23
|
+
FIRESTORE_EMULATOR_HOST: z.string().optional(),
|
|
24
|
+
FIREBASE_AUTH_EMULATOR_HOST: z.string().optional(),
|
|
25
|
+
// API configuration
|
|
26
|
+
API_PREFIX: z.string().default("/api"),
|
|
27
|
+
// Mock data configuration
|
|
28
|
+
USE_MOCK_DATA: z.string().default("true").transform((v) => v === "true"),
|
|
29
|
+
MOCK_SEED: z.string().optional().transform((v) => v ? parseInt(v, 10) : void 0)
|
|
30
|
+
});
|
|
31
|
+
var parsed = envSchema.safeParse(process.env);
|
|
32
|
+
if (!parsed.success) {
|
|
33
|
+
console.error("\u274C Invalid environment variables:", parsed.error.flatten().fieldErrors);
|
|
34
|
+
throw new Error("Invalid environment variables");
|
|
35
|
+
}
|
|
36
|
+
var env = parsed.data;
|
|
37
|
+
|
|
38
|
+
// src/lib/logger.ts
|
|
39
|
+
var colors = {
|
|
40
|
+
debug: "\x1B[36m",
|
|
41
|
+
// Cyan
|
|
42
|
+
info: "\x1B[32m",
|
|
43
|
+
// Green
|
|
44
|
+
warn: "\x1B[33m",
|
|
45
|
+
// Yellow
|
|
46
|
+
error: "\x1B[31m",
|
|
47
|
+
// Red
|
|
48
|
+
reset: "\x1B[0m"
|
|
49
|
+
};
|
|
50
|
+
var shouldLog = (level) => {
|
|
51
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
52
|
+
const minLevel = env.NODE_ENV === "production" ? "info" : "debug";
|
|
53
|
+
return levels.indexOf(level) >= levels.indexOf(minLevel);
|
|
54
|
+
};
|
|
55
|
+
var formatMessage = (level, message, meta) => {
|
|
56
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
57
|
+
const color = colors[level];
|
|
58
|
+
const prefix = `${color}[${level.toUpperCase()}]${colors.reset}`;
|
|
59
|
+
const metaStr = meta ? ` ${JSON.stringify(meta)}` : "";
|
|
60
|
+
return `${timestamp} ${prefix} ${message}${metaStr}`;
|
|
61
|
+
};
|
|
62
|
+
var logger = {
|
|
63
|
+
debug: (message, meta) => {
|
|
64
|
+
if (shouldLog("debug")) {
|
|
65
|
+
console.log(formatMessage("debug", message, meta));
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
info: (message, meta) => {
|
|
69
|
+
if (shouldLog("info")) {
|
|
70
|
+
console.log(formatMessage("info", message, meta));
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
warn: (message, meta) => {
|
|
74
|
+
if (shouldLog("warn")) {
|
|
75
|
+
console.warn(formatMessage("warn", message, meta));
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
error: (message, meta) => {
|
|
79
|
+
if (shouldLog("error")) {
|
|
80
|
+
console.error(formatMessage("error", message, meta));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
// src/middleware/errorHandler.ts
|
|
86
|
+
var AppError = class extends Error {
|
|
87
|
+
constructor(statusCode, message, code) {
|
|
88
|
+
super(message);
|
|
89
|
+
this.statusCode = statusCode;
|
|
90
|
+
this.message = message;
|
|
91
|
+
this.code = code;
|
|
92
|
+
this.name = "AppError";
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
var NotFoundError = class extends AppError {
|
|
96
|
+
constructor(message = "Resource not found") {
|
|
97
|
+
super(404, message, "NOT_FOUND");
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
var ValidationError = class extends AppError {
|
|
101
|
+
constructor(message = "Validation failed") {
|
|
102
|
+
super(400, message, "VALIDATION_ERROR");
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
var UnauthorizedError = class extends AppError {
|
|
106
|
+
constructor(message = "Unauthorized") {
|
|
107
|
+
super(401, message, "UNAUTHORIZED");
|
|
108
|
+
}
|
|
109
|
+
};
|
|
110
|
+
var ForbiddenError = class extends AppError {
|
|
111
|
+
constructor(message = "Forbidden") {
|
|
112
|
+
super(403, message, "FORBIDDEN");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var ConflictError = class extends AppError {
|
|
116
|
+
constructor(message = "Resource conflict") {
|
|
117
|
+
super(409, message, "CONFLICT");
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
var errorHandler = (err, _req, res, _next) => {
|
|
121
|
+
logger.error("Error:", { name: err.name, message: err.message, stack: err.stack });
|
|
122
|
+
if (err instanceof ZodError) {
|
|
123
|
+
res.status(400).json({
|
|
124
|
+
success: false,
|
|
125
|
+
error: "Validation failed",
|
|
126
|
+
code: "VALIDATION_ERROR",
|
|
127
|
+
details: err.errors.map((e) => ({
|
|
128
|
+
path: e.path.join("."),
|
|
129
|
+
message: e.message
|
|
130
|
+
}))
|
|
131
|
+
});
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
if (err instanceof AppError) {
|
|
135
|
+
res.status(err.statusCode).json({
|
|
136
|
+
success: false,
|
|
137
|
+
error: err.message,
|
|
138
|
+
code: err.code
|
|
139
|
+
});
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
if (err.name === "FirebaseError" || err.name === "FirestoreError") {
|
|
143
|
+
res.status(500).json({
|
|
144
|
+
success: false,
|
|
145
|
+
error: "Database error",
|
|
146
|
+
code: "DATABASE_ERROR"
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
res.status(500).json({
|
|
151
|
+
success: false,
|
|
152
|
+
error: "Internal server error",
|
|
153
|
+
code: "INTERNAL_ERROR"
|
|
154
|
+
});
|
|
155
|
+
};
|
|
156
|
+
var asyncHandler = (fn) => (req, res, next) => {
|
|
157
|
+
Promise.resolve(fn(req, res, next)).catch(next);
|
|
158
|
+
};
|
|
159
|
+
var notFoundHandler = (req, res) => {
|
|
160
|
+
res.status(404).json({
|
|
161
|
+
success: false,
|
|
162
|
+
error: `Route ${req.method} ${req.path} not found`,
|
|
163
|
+
code: "ROUTE_NOT_FOUND"
|
|
164
|
+
});
|
|
165
|
+
};
|
|
166
|
+
var validateBody = (schema) => async (req, res, next) => {
|
|
167
|
+
try {
|
|
168
|
+
req.body = await schema.parseAsync(req.body);
|
|
169
|
+
next();
|
|
170
|
+
} catch (error) {
|
|
171
|
+
if (error instanceof ZodError) {
|
|
172
|
+
res.status(400).json({
|
|
173
|
+
success: false,
|
|
174
|
+
error: "Validation failed",
|
|
175
|
+
code: "VALIDATION_ERROR",
|
|
176
|
+
details: error.errors.map((e) => ({
|
|
177
|
+
path: e.path.join("."),
|
|
178
|
+
message: e.message
|
|
179
|
+
}))
|
|
180
|
+
});
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
next(error);
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
var validateQuery = (schema) => async (req, res, next) => {
|
|
187
|
+
try {
|
|
188
|
+
req.query = await schema.parseAsync(req.query);
|
|
189
|
+
next();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (error instanceof ZodError) {
|
|
192
|
+
res.status(400).json({
|
|
193
|
+
success: false,
|
|
194
|
+
error: "Invalid query parameters",
|
|
195
|
+
code: "VALIDATION_ERROR",
|
|
196
|
+
details: error.errors.map((e) => ({
|
|
197
|
+
path: e.path.join("."),
|
|
198
|
+
message: e.message
|
|
199
|
+
}))
|
|
200
|
+
});
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
next(error);
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
var validateParams = (schema) => async (req, res, next) => {
|
|
207
|
+
try {
|
|
208
|
+
req.params = await schema.parseAsync(req.params);
|
|
209
|
+
next();
|
|
210
|
+
} catch (error) {
|
|
211
|
+
if (error instanceof ZodError) {
|
|
212
|
+
res.status(400).json({
|
|
213
|
+
success: false,
|
|
214
|
+
error: "Invalid path parameters",
|
|
215
|
+
code: "VALIDATION_ERROR",
|
|
216
|
+
details: error.errors.map((e) => ({
|
|
217
|
+
path: e.path.join("."),
|
|
218
|
+
message: e.message
|
|
219
|
+
}))
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
next(error);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
var firebaseApp = null;
|
|
227
|
+
function initializeFirebase() {
|
|
228
|
+
if (firebaseApp) {
|
|
229
|
+
return firebaseApp;
|
|
230
|
+
}
|
|
231
|
+
if (admin.apps.length > 0) {
|
|
232
|
+
firebaseApp = admin.apps[0];
|
|
233
|
+
return firebaseApp;
|
|
234
|
+
}
|
|
235
|
+
if (env.FIRESTORE_EMULATOR_HOST) {
|
|
236
|
+
firebaseApp = admin.initializeApp({
|
|
237
|
+
projectId: env.FIREBASE_PROJECT_ID || "demo-project"
|
|
238
|
+
});
|
|
239
|
+
console.log(`\u{1F527} Firebase Admin initialized for emulator: ${env.FIRESTORE_EMULATOR_HOST}`);
|
|
240
|
+
return firebaseApp;
|
|
241
|
+
}
|
|
242
|
+
const serviceAccountPath = env.FIREBASE_SERVICE_ACCOUNT_PATH;
|
|
243
|
+
if (serviceAccountPath) {
|
|
244
|
+
const serviceAccount = __require(serviceAccountPath);
|
|
245
|
+
firebaseApp = admin.initializeApp({
|
|
246
|
+
credential: admin.credential.cert(serviceAccount),
|
|
247
|
+
projectId: env.FIREBASE_PROJECT_ID
|
|
248
|
+
});
|
|
249
|
+
} else if (env.FIREBASE_PROJECT_ID && env.FIREBASE_CLIENT_EMAIL && env.FIREBASE_PRIVATE_KEY) {
|
|
250
|
+
firebaseApp = admin.initializeApp({
|
|
251
|
+
credential: admin.credential.cert({
|
|
252
|
+
projectId: env.FIREBASE_PROJECT_ID,
|
|
253
|
+
clientEmail: env.FIREBASE_CLIENT_EMAIL,
|
|
254
|
+
privateKey: env.FIREBASE_PRIVATE_KEY.replace(/\\n/g, "\n")
|
|
255
|
+
}),
|
|
256
|
+
projectId: env.FIREBASE_PROJECT_ID
|
|
257
|
+
});
|
|
258
|
+
} else if (env.FIREBASE_PROJECT_ID) {
|
|
259
|
+
firebaseApp = admin.initializeApp({
|
|
260
|
+
credential: admin.credential.applicationDefault(),
|
|
261
|
+
projectId: env.FIREBASE_PROJECT_ID
|
|
262
|
+
});
|
|
263
|
+
} else {
|
|
264
|
+
firebaseApp = admin.initializeApp({
|
|
265
|
+
projectId: "demo-project"
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return firebaseApp;
|
|
269
|
+
}
|
|
270
|
+
function getFirestore() {
|
|
271
|
+
const app = initializeFirebase();
|
|
272
|
+
const db2 = app.firestore();
|
|
273
|
+
if (env.FIRESTORE_EMULATOR_HOST) {
|
|
274
|
+
db2.settings({
|
|
275
|
+
host: env.FIRESTORE_EMULATOR_HOST,
|
|
276
|
+
ssl: false
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
return db2;
|
|
280
|
+
}
|
|
281
|
+
function getAuth() {
|
|
282
|
+
const app = initializeFirebase();
|
|
283
|
+
return app.auth();
|
|
284
|
+
}
|
|
285
|
+
getFirestore();
|
|
286
|
+
|
|
287
|
+
// src/middleware/authenticateFirebase.ts
|
|
288
|
+
var BEARER_PREFIX = "Bearer ";
|
|
289
|
+
async function authenticateFirebase(req, res, next) {
|
|
290
|
+
try {
|
|
291
|
+
const authorization = req.headers.authorization;
|
|
292
|
+
if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
|
|
293
|
+
return res.status(401).json({ error: "Authorization header missing or malformed" });
|
|
294
|
+
}
|
|
295
|
+
const token = authorization.slice(BEARER_PREFIX.length);
|
|
296
|
+
const decodedToken = await getAuth().verifyIdToken(token);
|
|
297
|
+
req.firebaseUser = decodedToken;
|
|
298
|
+
res.locals.firebaseUser = decodedToken;
|
|
299
|
+
return next();
|
|
300
|
+
} catch (error) {
|
|
301
|
+
console.error("Firebase authentication failed:", error);
|
|
302
|
+
return res.status(401).json({ error: "Unauthorized" });
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export { AppError, ConflictError, ForbiddenError, NotFoundError, UnauthorizedError, ValidationError, asyncHandler, authenticateFirebase, errorHandler, notFoundHandler, validateBody, validateParams, validateQuery };
|
|
307
|
+
//# sourceMappingURL=index.js.map
|
|
308
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/lib/env.ts","../../src/lib/logger.ts","../../src/middleware/errorHandler.ts","../../src/middleware/validation.ts","../../src/lib/db.ts","../../src/middleware/authenticateFirebase.ts"],"names":["ZodError","db"],"mappings":"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","file":"index.js","sourcesContent":["import { z } from 'zod';\nimport dotenv from 'dotenv';\n\n// Load environment variables\ndotenv.config();\n\nconst envSchema = z.object({\n NODE_ENV: z.enum(['development', 'production', 'test']).default('development'),\n PORT: z\n .string()\n .default('3030')\n .transform((val) => parseInt(val, 10)),\n CORS_ORIGIN: z\n .string()\n .default('http://localhost:5173')\n .transform((val) => (val.includes(',') ? val.split(',').map((s) => s.trim()) : val)),\n \n // Database (Prisma/SQL) - optional\n DATABASE_URL: z.string().optional(),\n \n // Firebase/Firestore configuration\n FIREBASE_PROJECT_ID: z.string().optional(),\n FIREBASE_CLIENT_EMAIL: z.string().optional(),\n FIREBASE_PRIVATE_KEY: z.string().optional(),\n FIREBASE_SERVICE_ACCOUNT_PATH: z.string().optional(),\n FIRESTORE_EMULATOR_HOST: z.string().optional(),\n FIREBASE_AUTH_EMULATOR_HOST: z.string().optional(),\n \n // API configuration\n API_PREFIX: z.string().default('/api'),\n\n // Mock data configuration\n USE_MOCK_DATA: z.string().default('true').transform((v) => v === 'true'),\n MOCK_SEED: z\n .string()\n .optional()\n .transform((v) => (v ? parseInt(v, 10) : undefined)),\n});\n\nconst parsed = envSchema.safeParse(process.env);\n\nif (!parsed.success) {\n console.error('❌ Invalid environment variables:', parsed.error.flatten().fieldErrors);\n throw new Error('Invalid environment variables');\n}\n\nexport const env = parsed.data;\n","import { env } from './env.js';\n\ntype LogLevel = 'debug' | 'info' | 'warn' | 'error';\n\nconst colors = {\n debug: '\\x1b[36m', // Cyan\n info: '\\x1b[32m', // Green\n warn: '\\x1b[33m', // Yellow\n error: '\\x1b[31m', // Red\n reset: '\\x1b[0m',\n};\n\nconst shouldLog = (level: LogLevel): boolean => {\n const levels: LogLevel[] = ['debug', 'info', 'warn', 'error'];\n const minLevel = env.NODE_ENV === 'production' ? 'info' : 'debug';\n return levels.indexOf(level) >= levels.indexOf(minLevel);\n};\n\nconst formatMessage = (level: LogLevel, message: string, meta?: unknown): string => {\n const timestamp = new Date().toISOString();\n const color = colors[level];\n const prefix = `${color}[${level.toUpperCase()}]${colors.reset}`;\n const metaStr = meta ? ` ${JSON.stringify(meta)}` : '';\n return `${timestamp} ${prefix} ${message}${metaStr}`;\n};\n\nexport const logger = {\n debug: (message: string, meta?: unknown) => {\n if (shouldLog('debug')) {\n console.log(formatMessage('debug', message, meta));\n }\n },\n info: (message: string, meta?: unknown) => {\n if (shouldLog('info')) {\n console.log(formatMessage('info', message, meta));\n }\n },\n warn: (message: string, meta?: unknown) => {\n if (shouldLog('warn')) {\n console.warn(formatMessage('warn', message, meta));\n }\n },\n error: (message: string, meta?: unknown) => {\n if (shouldLog('error')) {\n console.error(formatMessage('error', message, meta));\n }\n },\n};\n","import { Request, Response, NextFunction } from 'express';\nimport { ZodError } from 'zod';\nimport { logger } from '../lib/logger.js';\n\n/**\n * Base application error class\n */\nexport class AppError extends Error {\n constructor(\n public statusCode: number,\n public message: string,\n public code?: string\n ) {\n super(message);\n this.name = 'AppError';\n }\n}\n\n/**\n * 404 Not Found error\n */\nexport class NotFoundError extends AppError {\n constructor(message: string = 'Resource not found') {\n super(404, message, 'NOT_FOUND');\n }\n}\n\n/**\n * 400 Bad Request / Validation error\n */\nexport class ValidationError extends AppError {\n constructor(message: string = 'Validation failed') {\n super(400, message, 'VALIDATION_ERROR');\n }\n}\n\n/**\n * 401 Unauthorized error\n */\nexport class UnauthorizedError extends AppError {\n constructor(message: string = 'Unauthorized') {\n super(401, message, 'UNAUTHORIZED');\n }\n}\n\n/**\n * 403 Forbidden error\n */\nexport class ForbiddenError extends AppError {\n constructor(message: string = 'Forbidden') {\n super(403, message, 'FORBIDDEN');\n }\n}\n\n/**\n * 409 Conflict error\n */\nexport class ConflictError extends AppError {\n constructor(message: string = 'Resource conflict') {\n super(409, message, 'CONFLICT');\n }\n}\n\n/**\n * Global error handler middleware\n */\nexport const errorHandler = (\n err: Error,\n _req: Request,\n res: Response,\n _next: NextFunction\n): void => {\n logger.error('Error:', { name: err.name, message: err.message, stack: err.stack });\n\n // Zod validation errors\n if (err instanceof ZodError) {\n res.status(400).json({\n success: false,\n error: 'Validation failed',\n code: 'VALIDATION_ERROR',\n details: err.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n })),\n });\n return;\n }\n\n // Custom application errors\n if (err instanceof AppError) {\n res.status(err.statusCode).json({\n success: false,\n error: err.message,\n code: err.code,\n });\n return;\n }\n\n // Firebase/Firestore errors\n if (err.name === 'FirebaseError' || err.name === 'FirestoreError') {\n res.status(500).json({\n success: false,\n error: 'Database error',\n code: 'DATABASE_ERROR',\n });\n return;\n }\n\n // Unknown errors\n res.status(500).json({\n success: false,\n error: 'Internal server error',\n code: 'INTERNAL_ERROR',\n });\n};\n\n/**\n * Async handler wrapper to catch errors in async route handlers\n */\nexport const asyncHandler =\n (fn: (req: Request, res: Response, next: NextFunction) => Promise<unknown>) =>\n (req: Request, res: Response, next: NextFunction) => {\n Promise.resolve(fn(req, res, next)).catch(next);\n };\n\n/**\n * 404 handler for unmatched routes\n */\nexport const notFoundHandler = (req: Request, res: Response): void => {\n res.status(404).json({\n success: false,\n error: `Route ${req.method} ${req.path} not found`,\n code: 'ROUTE_NOT_FOUND',\n });\n};\n","import { Request, Response, NextFunction } from 'express';\nimport { AnyZodObject, ZodError } from 'zod';\n\n/**\n * Middleware to validate request body against a Zod schema\n */\nexport const validateBody =\n (schema: AnyZodObject) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n req.body = await schema.parseAsync(req.body);\n next();\n } catch (error) {\n if (error instanceof ZodError) {\n res.status(400).json({\n success: false,\n error: 'Validation failed',\n code: 'VALIDATION_ERROR',\n details: error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n })),\n });\n return;\n }\n next(error);\n }\n };\n\n/**\n * Middleware to validate request query parameters against a Zod schema\n */\nexport const validateQuery =\n (schema: AnyZodObject) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n req.query = await schema.parseAsync(req.query);\n next();\n } catch (error) {\n if (error instanceof ZodError) {\n res.status(400).json({\n success: false,\n error: 'Invalid query parameters',\n code: 'VALIDATION_ERROR',\n details: error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n })),\n });\n return;\n }\n next(error);\n }\n };\n\n/**\n * Middleware to validate request params against a Zod schema\n */\nexport const validateParams =\n (schema: AnyZodObject) => async (req: Request, res: Response, next: NextFunction): Promise<void> => {\n try {\n req.params = await schema.parseAsync(req.params);\n next();\n } catch (error) {\n if (error instanceof ZodError) {\n res.status(400).json({\n success: false,\n error: 'Invalid path parameters',\n code: 'VALIDATION_ERROR',\n details: error.errors.map((e) => ({\n path: e.path.join('.'),\n message: e.message,\n })),\n });\n return;\n }\n next(error);\n }\n };\n","/**\n * Database Configuration\n * \n * Supports both Prisma (SQL) and Firebase Admin (Firestore)\n * Generated apps can use either depending on the database option\n */\n\nimport admin from 'firebase-admin';\nimport { env } from './env.js';\n\n// ============ Firebase Admin / Firestore ============\n\nlet firebaseApp: admin.app.App | null = null;\n\n/**\n * Initialize Firebase Admin SDK\n */\nfunction initializeFirebase(): admin.app.App {\n if (firebaseApp) {\n return firebaseApp;\n }\n\n // Check if already initialized\n if (admin.apps.length > 0) {\n firebaseApp = admin.apps[0]!;\n return firebaseApp;\n }\n\n // Check for emulator mode FIRST (no credentials needed)\n if (env.FIRESTORE_EMULATOR_HOST) {\n // Emulator mode - no credentials needed\n firebaseApp = admin.initializeApp({\n projectId: env.FIREBASE_PROJECT_ID || 'demo-project',\n });\n console.log(`🔧 Firebase Admin initialized for emulator: ${env.FIRESTORE_EMULATOR_HOST}`);\n return firebaseApp;\n }\n\n // Production mode - need credentials\n const serviceAccountPath = env.FIREBASE_SERVICE_ACCOUNT_PATH;\n \n if (serviceAccountPath) {\n // Use service account file\n // eslint-disable-next-line @typescript-eslint/no-var-requires\n const serviceAccount = require(serviceAccountPath);\n firebaseApp = admin.initializeApp({\n credential: admin.credential.cert(serviceAccount),\n projectId: env.FIREBASE_PROJECT_ID,\n });\n } else if (env.FIREBASE_PROJECT_ID && env.FIREBASE_CLIENT_EMAIL && env.FIREBASE_PRIVATE_KEY) {\n // Use inline service account credentials\n firebaseApp = admin.initializeApp({\n credential: admin.credential.cert({\n projectId: env.FIREBASE_PROJECT_ID,\n clientEmail: env.FIREBASE_CLIENT_EMAIL,\n privateKey: env.FIREBASE_PRIVATE_KEY.replace(/\\\\n/g, '\\n'),\n }),\n projectId: env.FIREBASE_PROJECT_ID,\n });\n } else if (env.FIREBASE_PROJECT_ID) {\n // Use application default credentials (for Cloud Run, etc.)\n firebaseApp = admin.initializeApp({\n credential: admin.credential.applicationDefault(),\n projectId: env.FIREBASE_PROJECT_ID,\n });\n } else {\n // Emulator mode - use default credentials\n firebaseApp = admin.initializeApp({\n projectId: 'demo-project',\n });\n }\n\n return firebaseApp;\n}\n\n/**\n * Get Firestore instance\n */\nexport function getFirestore(): admin.firestore.Firestore {\n const app = initializeFirebase();\n const db = app.firestore();\n \n // Connect to emulator if configured\n if (env.FIRESTORE_EMULATOR_HOST) {\n db.settings({\n host: env.FIRESTORE_EMULATOR_HOST,\n ssl: false,\n });\n }\n \n return db;\n}\n\n/**\n * Get Firebase Auth instance\n */\nexport function getAuth(): admin.auth.Auth {\n const app = initializeFirebase();\n return app.auth();\n}\n\n// ============ Prisma (Optional - for SQL databases) ============\n\n// Uncomment if using Prisma with SQL database\n/*\nimport { PrismaClient } from '@prisma/client';\n\ndeclare global {\n // eslint-disable-next-line no-var\n var __db: PrismaClient | undefined;\n}\n\nconst createPrismaClient = () => {\n return new PrismaClient({\n log: env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],\n });\n};\n\nexport const db = globalThis.__db ?? createPrismaClient();\n\nif (env.NODE_ENV !== 'production') {\n globalThis.__db = db;\n}\n*/\n\n// Re-export admin for convenience\nexport { admin };\n\n// Export db instance for handler convenience\nexport const db = getFirestore();\n","import { NextFunction, Request, Response } from 'express';\nimport { getAuth } from '../lib/db.js';\n\nconst BEARER_PREFIX = 'Bearer ';\n\nexport async function authenticateFirebase(req: Request, res: Response, next: NextFunction) {\n try {\n const authorization = req.headers.authorization;\n\n if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {\n return res.status(401).json({ error: 'Authorization header missing or malformed' });\n }\n\n const token = authorization.slice(BEARER_PREFIX.length);\n const decodedToken = await getAuth().verifyIdToken(token);\n\n req.firebaseUser = decodedToken;\n res.locals.firebaseUser = decodedToken;\n\n return next();\n } catch (error) {\n console.error('Firebase authentication failed:', error);\n return res.status(401).json({ error: 'Unauthorized' });\n }\n}\n\nexport default authenticateFirebase;\n\n\n"]}
|