@almadar/server 2.0.4 → 2.0.5

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/dist/index.js CHANGED
@@ -3,8 +3,8 @@ import dotenv from 'dotenv';
3
3
  import { Router } from 'express';
4
4
  import admin from 'firebase-admin';
5
5
  export { default as admin } from 'firebase-admin';
6
- import { WebSocketServer, WebSocket } from 'ws';
7
6
  import { faker } from '@faker-js/faker';
7
+ import { WebSocketServer, WebSocket } from 'ws';
8
8
  import { diffSchemas, categorizeRemovals, detectPageContentReduction, isDestructiveChange, hasSignificantPageReduction, requiresConfirmation } from '@almadar/core';
9
9
  import { getObservabilityCollector, MemoryManager, SessionManager, getMultiUserManager, createWorkflowToolWrapper, createSkillAgent, createUserContext, getStateSyncManager } from '@almadar/agent';
10
10
 
@@ -452,27 +452,6 @@ var EventPersistence = class {
452
452
  return this.store;
453
453
  }
454
454
  };
455
- function debugEventsRouter() {
456
- const router2 = Router();
457
- if (process.env.NODE_ENV !== "development") {
458
- return router2;
459
- }
460
- router2.get("/event-log", (_req, res) => {
461
- const limit = parseInt(String(_req.query.limit) || "50", 10);
462
- const events = getServerEventBus().getRecentEvents(limit);
463
- res.json({ count: events.length, events });
464
- });
465
- router2.delete("/event-log", (_req, res) => {
466
- getServerEventBus().clearEventLog();
467
- res.json({ cleared: true });
468
- });
469
- router2.get("/listeners", (_req, res) => {
470
- const counts = getServerEventBus().getListenerCounts();
471
- const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
472
- res.json({ total, events: counts });
473
- });
474
- return router2;
475
- }
476
455
  function initializeFirebase() {
477
456
  if (admin.apps.length > 0) {
478
457
  return admin.app();
@@ -537,380 +516,117 @@ var db = new Proxy({}, {
537
516
  return typeof value === "function" ? value.bind(firestore) : value;
538
517
  }
539
518
  });
540
- var wss = null;
541
- function setupEventBroadcast(server, path = "/ws/events") {
542
- if (wss) {
543
- logger.warn("[WebSocket] Server already initialized");
544
- return wss;
545
- }
546
- wss = new WebSocketServer({ server, path });
547
- logger.info(`[WebSocket] Server listening at ${path}`);
548
- wss.on("connection", (ws, req) => {
549
- const clientId = req.headers["sec-websocket-key"] || "unknown";
550
- logger.debug(`[WebSocket] Client connected: ${clientId}`);
551
- ws.send(
552
- JSON.stringify({
553
- type: "CONNECTED",
554
- timestamp: Date.now(),
555
- message: "Connected to event stream"
556
- })
557
- );
558
- ws.on("message", (data) => {
559
- try {
560
- const message = JSON.parse(data.toString());
561
- logger.debug(`[WebSocket] Received from ${clientId}:`, message);
562
- if (message.type && message.payload) {
563
- getServerEventBus().emit(message.type, message.payload, {
564
- orbital: "client",
565
- entity: clientId
566
- });
567
- }
568
- } catch (error) {
569
- logger.error(`[WebSocket] Failed to parse message:`, error);
570
- }
571
- });
572
- ws.on("close", () => {
573
- logger.debug(`[WebSocket] Client disconnected: ${clientId}`);
574
- });
575
- ws.on("error", (error) => {
576
- logger.error(`[WebSocket] Client error:`, error);
577
- });
578
- });
579
- getServerEventBus().on("*", (event) => {
580
- if (!wss) return;
581
- const typedEvent = event;
582
- const message = JSON.stringify({
583
- type: typedEvent.type,
584
- payload: typedEvent.payload,
585
- timestamp: typedEvent.timestamp,
586
- source: typedEvent.source
587
- });
588
- let broadcastCount = 0;
589
- wss.clients.forEach((client) => {
590
- if (client.readyState === WebSocket.OPEN) {
591
- client.send(message);
592
- broadcastCount++;
593
- }
594
- });
595
- if (broadcastCount > 0) {
596
- logger.debug(`[WebSocket] Broadcast ${typedEvent.type} to ${broadcastCount} client(s)`);
597
- }
598
- });
599
- return wss;
600
- }
601
- function getWebSocketServer() {
602
- return wss;
603
- }
604
- function closeWebSocketServer() {
605
- return new Promise((resolve, reject) => {
606
- if (!wss) {
607
- resolve();
608
- return;
519
+ var MockDataService = class {
520
+ stores = /* @__PURE__ */ new Map();
521
+ schemas = /* @__PURE__ */ new Map();
522
+ idCounters = /* @__PURE__ */ new Map();
523
+ constructor() {
524
+ if (env.MOCK_SEED !== void 0) {
525
+ faker.seed(env.MOCK_SEED);
526
+ logger.info(`[Mock] Using seed: ${env.MOCK_SEED}`);
609
527
  }
610
- wss.close((err) => {
611
- if (err) {
612
- reject(err);
613
- } else {
614
- wss = null;
615
- resolve();
616
- }
617
- });
618
- });
619
- }
620
- function getConnectedClientCount() {
621
- if (!wss) return 0;
622
- return wss.clients.size;
623
- }
624
- var AppError = class extends Error {
625
- constructor(statusCode, message, code) {
626
- super(message);
627
- this.statusCode = statusCode;
628
- this.message = message;
629
- this.code = code;
630
- this.name = "AppError";
631
528
  }
632
- };
633
- var NotFoundError = class extends AppError {
634
- constructor(message = "Resource not found") {
635
- super(404, message, "NOT_FOUND");
529
+ // ============================================================================
530
+ // Store Management
531
+ // ============================================================================
532
+ /**
533
+ * Initialize store for an entity.
534
+ */
535
+ getStore(entityName) {
536
+ const normalized = entityName.toLowerCase();
537
+ if (!this.stores.has(normalized)) {
538
+ this.stores.set(normalized, /* @__PURE__ */ new Map());
539
+ this.idCounters.set(normalized, 0);
540
+ }
541
+ return this.stores.get(normalized);
636
542
  }
637
- };
638
- var ValidationError = class extends AppError {
639
- constructor(message = "Validation failed") {
640
- super(400, message, "VALIDATION_ERROR");
543
+ /**
544
+ * Generate next ID for an entity.
545
+ */
546
+ nextId(entityName) {
547
+ const normalized = entityName.toLowerCase();
548
+ const counter = (this.idCounters.get(normalized) ?? 0) + 1;
549
+ this.idCounters.set(normalized, counter);
550
+ return `mock-${normalized}-${counter}`;
641
551
  }
642
- };
643
- var UnauthorizedError = class extends AppError {
644
- constructor(message = "Unauthorized") {
645
- super(401, message, "UNAUTHORIZED");
552
+ // ============================================================================
553
+ // Schema & Seeding
554
+ // ============================================================================
555
+ /**
556
+ * Register an entity schema.
557
+ */
558
+ registerSchema(entityName, schema) {
559
+ this.schemas.set(entityName.toLowerCase(), schema);
646
560
  }
647
- };
648
- var ForbiddenError = class extends AppError {
649
- constructor(message = "Forbidden") {
650
- super(403, message, "FORBIDDEN");
561
+ /**
562
+ * Seed an entity with mock data.
563
+ */
564
+ seed(entityName, fields, count = 10) {
565
+ const store = this.getStore(entityName);
566
+ const normalized = entityName.toLowerCase();
567
+ logger.info(`[Mock] Seeding ${count} ${entityName}...`);
568
+ for (let i = 0; i < count; i++) {
569
+ const item = this.generateMockItem(normalized, fields, i + 1);
570
+ store.set(item.id, item);
571
+ }
651
572
  }
652
- };
653
- var ConflictError = class extends AppError {
654
- constructor(message = "Resource conflict") {
655
- super(409, message, "CONFLICT");
573
+ /**
574
+ * Generate a single mock item based on field schemas.
575
+ */
576
+ generateMockItem(entityName, fields, index) {
577
+ const id = this.nextId(entityName);
578
+ const now = /* @__PURE__ */ new Date();
579
+ const item = {
580
+ id,
581
+ createdAt: faker.date.past({ years: 1 }),
582
+ updatedAt: now
583
+ };
584
+ for (const field of fields) {
585
+ if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
586
+ continue;
587
+ }
588
+ item[field.name] = this.generateFieldValue(entityName, field, index);
589
+ }
590
+ return item;
656
591
  }
657
- };
658
- var errorHandler = (err, _req, res, _next) => {
659
- logger.error("Error:", { name: err.name, message: err.message, stack: err.stack });
660
- if (err instanceof ZodError) {
661
- res.status(400).json({
662
- success: false,
663
- error: "Validation failed",
664
- code: "VALIDATION_ERROR",
665
- details: err.errors.map((e) => ({
666
- path: e.path.join("."),
667
- message: e.message
668
- }))
669
- });
670
- return;
671
- }
672
- if (err instanceof AppError) {
673
- res.status(err.statusCode).json({
674
- success: false,
675
- error: err.message,
676
- code: err.code
677
- });
678
- return;
679
- }
680
- if (err.name === "FirebaseError" || err.name === "FirestoreError") {
681
- res.status(500).json({
682
- success: false,
683
- error: "Database error",
684
- code: "DATABASE_ERROR"
685
- });
686
- return;
687
- }
688
- res.status(500).json({
689
- success: false,
690
- error: "Internal server error",
691
- code: "INTERNAL_ERROR"
692
- });
693
- };
694
- var asyncHandler = (fn) => (req, res, next) => {
695
- Promise.resolve(fn(req, res, next)).catch(next);
696
- };
697
- var notFoundHandler = (req, res) => {
698
- res.status(404).json({
699
- success: false,
700
- error: `Route ${req.method} ${req.path} not found`,
701
- code: "ROUTE_NOT_FOUND"
702
- });
703
- };
704
- var validateBody = (schema) => async (req, res, next) => {
705
- try {
706
- req.body = await schema.parseAsync(req.body);
707
- next();
708
- } catch (error) {
709
- if (error instanceof ZodError) {
710
- res.status(400).json({
711
- success: false,
712
- error: "Validation failed",
713
- code: "VALIDATION_ERROR",
714
- details: error.errors.map((e) => ({
715
- path: e.path.join("."),
716
- message: e.message
717
- }))
718
- });
719
- return;
720
- }
721
- next(error);
722
- }
723
- };
724
- var validateQuery = (schema) => async (req, res, next) => {
725
- try {
726
- req.query = await schema.parseAsync(req.query);
727
- next();
728
- } catch (error) {
729
- if (error instanceof ZodError) {
730
- res.status(400).json({
731
- success: false,
732
- error: "Invalid query parameters",
733
- code: "VALIDATION_ERROR",
734
- details: error.errors.map((e) => ({
735
- path: e.path.join("."),
736
- message: e.message
737
- }))
738
- });
739
- return;
740
- }
741
- next(error);
742
- }
743
- };
744
- var validateParams = (schema) => async (req, res, next) => {
745
- try {
746
- req.params = await schema.parseAsync(req.params);
747
- next();
748
- } catch (error) {
749
- if (error instanceof ZodError) {
750
- res.status(400).json({
751
- success: false,
752
- error: "Invalid path parameters",
753
- code: "VALIDATION_ERROR",
754
- details: error.errors.map((e) => ({
755
- path: e.path.join("."),
756
- message: e.message
757
- }))
758
- });
759
- return;
760
- }
761
- next(error);
762
- }
763
- };
764
-
765
- // src/middleware/authenticateFirebase.ts
766
- var BEARER_PREFIX = "Bearer ";
767
- var DEV_USER = {
768
- uid: "dev-user-001",
769
- email: "dev@localhost",
770
- email_verified: true,
771
- aud: "dev-project",
772
- auth_time: Math.floor(Date.now() / 1e3),
773
- exp: Math.floor(Date.now() / 1e3) + 3600,
774
- iat: Math.floor(Date.now() / 1e3),
775
- iss: "https://securetoken.google.com/dev-project",
776
- sub: "dev-user-001",
777
- firebase: {
778
- identities: {},
779
- sign_in_provider: "custom"
780
- }
781
- };
782
- async function authenticateFirebase(req, res, next) {
783
- const authorization = req.headers.authorization;
784
- if (env.NODE_ENV === "development" && (!authorization || !authorization.startsWith(BEARER_PREFIX))) {
785
- req.firebaseUser = DEV_USER;
786
- res.locals.firebaseUser = DEV_USER;
787
- return next();
788
- }
789
- try {
790
- if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
791
- return res.status(401).json({ error: "Authorization header missing or malformed" });
792
- }
793
- const token = authorization.slice(BEARER_PREFIX.length);
794
- const decodedToken = await getAuth().verifyIdToken(token);
795
- req.firebaseUser = decodedToken;
796
- res.locals.firebaseUser = decodedToken;
797
- return next();
798
- } catch (error) {
799
- console.error("Firebase authentication failed:", error);
800
- return res.status(401).json({ error: "Unauthorized" });
801
- }
802
- }
803
- var MockDataService = class {
804
- stores = /* @__PURE__ */ new Map();
805
- schemas = /* @__PURE__ */ new Map();
806
- idCounters = /* @__PURE__ */ new Map();
807
- constructor() {
808
- if (env.MOCK_SEED !== void 0) {
809
- faker.seed(env.MOCK_SEED);
810
- logger.info(`[Mock] Using seed: ${env.MOCK_SEED}`);
811
- }
812
- }
813
- // ============================================================================
814
- // Store Management
815
- // ============================================================================
816
- /**
817
- * Initialize store for an entity.
818
- */
819
- getStore(entityName) {
820
- const normalized = entityName.toLowerCase();
821
- if (!this.stores.has(normalized)) {
822
- this.stores.set(normalized, /* @__PURE__ */ new Map());
823
- this.idCounters.set(normalized, 0);
824
- }
825
- return this.stores.get(normalized);
826
- }
827
- /**
828
- * Generate next ID for an entity.
829
- */
830
- nextId(entityName) {
831
- const normalized = entityName.toLowerCase();
832
- const counter = (this.idCounters.get(normalized) ?? 0) + 1;
833
- this.idCounters.set(normalized, counter);
834
- return `mock-${normalized}-${counter}`;
835
- }
836
- // ============================================================================
837
- // Schema & Seeding
838
- // ============================================================================
839
- /**
840
- * Register an entity schema.
841
- */
842
- registerSchema(entityName, schema) {
843
- this.schemas.set(entityName.toLowerCase(), schema);
844
- }
845
- /**
846
- * Seed an entity with mock data.
847
- */
848
- seed(entityName, fields, count = 10) {
849
- const store = this.getStore(entityName);
850
- const normalized = entityName.toLowerCase();
851
- logger.info(`[Mock] Seeding ${count} ${entityName}...`);
852
- for (let i = 0; i < count; i++) {
853
- const item = this.generateMockItem(normalized, fields, i + 1);
854
- store.set(item.id, item);
855
- }
856
- }
857
- /**
858
- * Generate a single mock item based on field schemas.
859
- */
860
- generateMockItem(entityName, fields, index) {
861
- const id = this.nextId(entityName);
862
- const now = /* @__PURE__ */ new Date();
863
- const item = {
864
- id,
865
- createdAt: faker.date.past({ years: 1 }),
866
- updatedAt: now
867
- };
868
- for (const field of fields) {
869
- if (field.name === "id" || field.name === "createdAt" || field.name === "updatedAt") {
870
- continue;
871
- }
872
- item[field.name] = this.generateFieldValue(entityName, field, index);
873
- }
874
- return item;
875
- }
876
- /**
877
- * Generate a mock value for a field based on its schema.
878
- */
879
- generateFieldValue(entityName, field, index) {
880
- if (!field.required && Math.random() > 0.8) {
881
- return void 0;
882
- }
883
- switch (field.type) {
884
- case "string":
885
- return this.generateStringValue(entityName, field, index);
886
- case "number":
887
- return faker.number.int({
888
- min: field.min ?? 0,
889
- max: field.max ?? 1e3
890
- });
891
- case "boolean":
892
- return faker.datatype.boolean();
893
- case "date":
894
- return this.generateDateValue(field);
895
- case "enum":
896
- if (field.enumValues && field.enumValues.length > 0) {
897
- return faker.helpers.arrayElement(field.enumValues);
898
- }
899
- return null;
900
- case "relation":
901
- if (field.relatedEntity) {
902
- const relatedStore = this.stores.get(field.relatedEntity.toLowerCase());
903
- if (relatedStore && relatedStore.size > 0) {
904
- const ids = Array.from(relatedStore.keys());
905
- return faker.helpers.arrayElement(ids);
906
- }
907
- }
908
- return null;
909
- case "array":
910
- return [];
911
- default:
912
- return null;
913
- }
592
+ /**
593
+ * Generate a mock value for a field based on its schema.
594
+ */
595
+ generateFieldValue(entityName, field, index) {
596
+ if (!field.required && Math.random() > 0.8) {
597
+ return void 0;
598
+ }
599
+ switch (field.type) {
600
+ case "string":
601
+ return this.generateStringValue(entityName, field, index);
602
+ case "number":
603
+ return faker.number.int({
604
+ min: field.min ?? 0,
605
+ max: field.max ?? 1e3
606
+ });
607
+ case "boolean":
608
+ return faker.datatype.boolean();
609
+ case "date":
610
+ return this.generateDateValue(field);
611
+ case "enum":
612
+ if (field.enumValues && field.enumValues.length > 0) {
613
+ return faker.helpers.arrayElement(field.enumValues);
614
+ }
615
+ return null;
616
+ case "relation":
617
+ if (field.relatedEntity) {
618
+ const relatedStore = this.stores.get(field.relatedEntity.toLowerCase());
619
+ if (relatedStore && relatedStore.size > 0) {
620
+ const ids = Array.from(relatedStore.keys());
621
+ return faker.helpers.arrayElement(ids);
622
+ }
623
+ }
624
+ return null;
625
+ case "array":
626
+ return [];
627
+ default:
628
+ return null;
629
+ }
914
630
  }
915
631
  /**
916
632
  * Generate a string value based on field name heuristics.
@@ -1473,6 +1189,307 @@ function seedMockData(entities) {
1473
1189
  logger.info("[DataService] Mock data seeding complete");
1474
1190
  }
1475
1191
 
1192
+ // src/lib/debugRouter.ts
1193
+ function debugEventsRouter() {
1194
+ const router2 = Router();
1195
+ if (process.env.NODE_ENV !== "development") {
1196
+ return router2;
1197
+ }
1198
+ router2.get("/event-log", (_req, res) => {
1199
+ const limit = parseInt(String(_req.query.limit) || "50", 10);
1200
+ const events = getServerEventBus().getRecentEvents(limit);
1201
+ res.json({ count: events.length, events });
1202
+ });
1203
+ router2.delete("/event-log", (_req, res) => {
1204
+ getServerEventBus().clearEventLog();
1205
+ res.json({ cleared: true });
1206
+ });
1207
+ router2.get("/listeners", (_req, res) => {
1208
+ const counts = getServerEventBus().getListenerCounts();
1209
+ const total = Object.values(counts).reduce((sum, n) => sum + n, 0);
1210
+ res.json({ total, events: counts });
1211
+ });
1212
+ router2.post("/seed", (req, res) => {
1213
+ const { entities } = req.body;
1214
+ if (!entities || !Array.isArray(entities)) {
1215
+ res.status(400).json({ error: 'Body must have "entities" array' });
1216
+ return;
1217
+ }
1218
+ const configs = entities.map((e) => ({
1219
+ name: e.name,
1220
+ fields: e.fields,
1221
+ seedCount: e.seedCount ?? 5
1222
+ }));
1223
+ seedMockData(configs);
1224
+ const summary = configs.map((c) => `${c.name}(${c.seedCount})`).join(", ");
1225
+ res.json({ seeded: true, summary });
1226
+ });
1227
+ return router2;
1228
+ }
1229
+ var wss = null;
1230
+ function setupEventBroadcast(server, path = "/ws/events") {
1231
+ if (wss) {
1232
+ logger.warn("[WebSocket] Server already initialized");
1233
+ return wss;
1234
+ }
1235
+ wss = new WebSocketServer({ server, path });
1236
+ logger.info(`[WebSocket] Server listening at ${path}`);
1237
+ wss.on("connection", (ws, req) => {
1238
+ const clientId = req.headers["sec-websocket-key"] || "unknown";
1239
+ logger.debug(`[WebSocket] Client connected: ${clientId}`);
1240
+ ws.send(
1241
+ JSON.stringify({
1242
+ type: "CONNECTED",
1243
+ timestamp: Date.now(),
1244
+ message: "Connected to event stream"
1245
+ })
1246
+ );
1247
+ ws.on("message", (data) => {
1248
+ try {
1249
+ const message = JSON.parse(data.toString());
1250
+ logger.debug(`[WebSocket] Received from ${clientId}:`, message);
1251
+ if (message.type && message.payload) {
1252
+ getServerEventBus().emit(message.type, message.payload, {
1253
+ orbital: "client",
1254
+ entity: clientId
1255
+ });
1256
+ }
1257
+ } catch (error) {
1258
+ logger.error(`[WebSocket] Failed to parse message:`, error);
1259
+ }
1260
+ });
1261
+ ws.on("close", () => {
1262
+ logger.debug(`[WebSocket] Client disconnected: ${clientId}`);
1263
+ });
1264
+ ws.on("error", (error) => {
1265
+ logger.error(`[WebSocket] Client error:`, error);
1266
+ });
1267
+ });
1268
+ getServerEventBus().on("*", (event) => {
1269
+ if (!wss) return;
1270
+ const typedEvent = event;
1271
+ const message = JSON.stringify({
1272
+ type: typedEvent.type,
1273
+ payload: typedEvent.payload,
1274
+ timestamp: typedEvent.timestamp,
1275
+ source: typedEvent.source
1276
+ });
1277
+ let broadcastCount = 0;
1278
+ wss.clients.forEach((client) => {
1279
+ if (client.readyState === WebSocket.OPEN) {
1280
+ client.send(message);
1281
+ broadcastCount++;
1282
+ }
1283
+ });
1284
+ if (broadcastCount > 0) {
1285
+ logger.debug(`[WebSocket] Broadcast ${typedEvent.type} to ${broadcastCount} client(s)`);
1286
+ }
1287
+ });
1288
+ return wss;
1289
+ }
1290
+ function getWebSocketServer() {
1291
+ return wss;
1292
+ }
1293
+ function closeWebSocketServer() {
1294
+ return new Promise((resolve, reject) => {
1295
+ if (!wss) {
1296
+ resolve();
1297
+ return;
1298
+ }
1299
+ wss.close((err) => {
1300
+ if (err) {
1301
+ reject(err);
1302
+ } else {
1303
+ wss = null;
1304
+ resolve();
1305
+ }
1306
+ });
1307
+ });
1308
+ }
1309
+ function getConnectedClientCount() {
1310
+ if (!wss) return 0;
1311
+ return wss.clients.size;
1312
+ }
1313
+ var AppError = class extends Error {
1314
+ constructor(statusCode, message, code) {
1315
+ super(message);
1316
+ this.statusCode = statusCode;
1317
+ this.message = message;
1318
+ this.code = code;
1319
+ this.name = "AppError";
1320
+ }
1321
+ };
1322
+ var NotFoundError = class extends AppError {
1323
+ constructor(message = "Resource not found") {
1324
+ super(404, message, "NOT_FOUND");
1325
+ }
1326
+ };
1327
+ var ValidationError = class extends AppError {
1328
+ constructor(message = "Validation failed") {
1329
+ super(400, message, "VALIDATION_ERROR");
1330
+ }
1331
+ };
1332
+ var UnauthorizedError = class extends AppError {
1333
+ constructor(message = "Unauthorized") {
1334
+ super(401, message, "UNAUTHORIZED");
1335
+ }
1336
+ };
1337
+ var ForbiddenError = class extends AppError {
1338
+ constructor(message = "Forbidden") {
1339
+ super(403, message, "FORBIDDEN");
1340
+ }
1341
+ };
1342
+ var ConflictError = class extends AppError {
1343
+ constructor(message = "Resource conflict") {
1344
+ super(409, message, "CONFLICT");
1345
+ }
1346
+ };
1347
+ var errorHandler = (err, _req, res, _next) => {
1348
+ logger.error("Error:", { name: err.name, message: err.message, stack: err.stack });
1349
+ if (err instanceof ZodError) {
1350
+ res.status(400).json({
1351
+ success: false,
1352
+ error: "Validation failed",
1353
+ code: "VALIDATION_ERROR",
1354
+ details: err.errors.map((e) => ({
1355
+ path: e.path.join("."),
1356
+ message: e.message
1357
+ }))
1358
+ });
1359
+ return;
1360
+ }
1361
+ if (err instanceof AppError) {
1362
+ res.status(err.statusCode).json({
1363
+ success: false,
1364
+ error: err.message,
1365
+ code: err.code
1366
+ });
1367
+ return;
1368
+ }
1369
+ if (err.name === "FirebaseError" || err.name === "FirestoreError") {
1370
+ res.status(500).json({
1371
+ success: false,
1372
+ error: "Database error",
1373
+ code: "DATABASE_ERROR"
1374
+ });
1375
+ return;
1376
+ }
1377
+ res.status(500).json({
1378
+ success: false,
1379
+ error: "Internal server error",
1380
+ code: "INTERNAL_ERROR"
1381
+ });
1382
+ };
1383
+ var asyncHandler = (fn) => (req, res, next) => {
1384
+ Promise.resolve(fn(req, res, next)).catch(next);
1385
+ };
1386
+ var notFoundHandler = (req, res) => {
1387
+ res.status(404).json({
1388
+ success: false,
1389
+ error: `Route ${req.method} ${req.path} not found`,
1390
+ code: "ROUTE_NOT_FOUND"
1391
+ });
1392
+ };
1393
+ var validateBody = (schema) => async (req, res, next) => {
1394
+ try {
1395
+ req.body = await schema.parseAsync(req.body);
1396
+ next();
1397
+ } catch (error) {
1398
+ if (error instanceof ZodError) {
1399
+ res.status(400).json({
1400
+ success: false,
1401
+ error: "Validation failed",
1402
+ code: "VALIDATION_ERROR",
1403
+ details: error.errors.map((e) => ({
1404
+ path: e.path.join("."),
1405
+ message: e.message
1406
+ }))
1407
+ });
1408
+ return;
1409
+ }
1410
+ next(error);
1411
+ }
1412
+ };
1413
+ var validateQuery = (schema) => async (req, res, next) => {
1414
+ try {
1415
+ req.query = await schema.parseAsync(req.query);
1416
+ next();
1417
+ } catch (error) {
1418
+ if (error instanceof ZodError) {
1419
+ res.status(400).json({
1420
+ success: false,
1421
+ error: "Invalid query parameters",
1422
+ code: "VALIDATION_ERROR",
1423
+ details: error.errors.map((e) => ({
1424
+ path: e.path.join("."),
1425
+ message: e.message
1426
+ }))
1427
+ });
1428
+ return;
1429
+ }
1430
+ next(error);
1431
+ }
1432
+ };
1433
+ var validateParams = (schema) => async (req, res, next) => {
1434
+ try {
1435
+ req.params = await schema.parseAsync(req.params);
1436
+ next();
1437
+ } catch (error) {
1438
+ if (error instanceof ZodError) {
1439
+ res.status(400).json({
1440
+ success: false,
1441
+ error: "Invalid path parameters",
1442
+ code: "VALIDATION_ERROR",
1443
+ details: error.errors.map((e) => ({
1444
+ path: e.path.join("."),
1445
+ message: e.message
1446
+ }))
1447
+ });
1448
+ return;
1449
+ }
1450
+ next(error);
1451
+ }
1452
+ };
1453
+
1454
+ // src/middleware/authenticateFirebase.ts
1455
+ var BEARER_PREFIX = "Bearer ";
1456
+ var DEV_USER = {
1457
+ uid: "dev-user-001",
1458
+ email: "dev@localhost",
1459
+ email_verified: true,
1460
+ aud: "dev-project",
1461
+ auth_time: Math.floor(Date.now() / 1e3),
1462
+ exp: Math.floor(Date.now() / 1e3) + 3600,
1463
+ iat: Math.floor(Date.now() / 1e3),
1464
+ iss: "https://securetoken.google.com/dev-project",
1465
+ sub: "dev-user-001",
1466
+ firebase: {
1467
+ identities: {},
1468
+ sign_in_provider: "custom"
1469
+ }
1470
+ };
1471
+ async function authenticateFirebase(req, res, next) {
1472
+ const authorization = req.headers.authorization;
1473
+ if (env.NODE_ENV === "development" && (!authorization || !authorization.startsWith(BEARER_PREFIX))) {
1474
+ req.firebaseUser = DEV_USER;
1475
+ res.locals.firebaseUser = DEV_USER;
1476
+ return next();
1477
+ }
1478
+ try {
1479
+ if (!authorization || !authorization.startsWith(BEARER_PREFIX)) {
1480
+ return res.status(401).json({ error: "Authorization header missing or malformed" });
1481
+ }
1482
+ const token = authorization.slice(BEARER_PREFIX.length);
1483
+ const decodedToken = await getAuth().verifyIdToken(token);
1484
+ req.firebaseUser = decodedToken;
1485
+ res.locals.firebaseUser = decodedToken;
1486
+ return next();
1487
+ } catch (error) {
1488
+ console.error("Firebase authentication failed:", error);
1489
+ return res.status(401).json({ error: "Unauthorized" });
1490
+ }
1491
+ }
1492
+
1476
1493
  // src/stores/firestoreFormat.ts
1477
1494
  function toFirestoreFormat(schema) {
1478
1495
  const data = { ...schema };