CognxSafeTrack commited on
Commit
9b8717a
·
1 Parent(s): 1fa2a07

chore: deploy manual fixes for TS strict mode

Browse files
apps/api/src/index.ts CHANGED
@@ -64,7 +64,7 @@ const registerRoutes = async () => {
64
  if (request.method === 'OPTIONS') return;
65
 
66
  const apiKey = process.env.ADMIN_API_KEY;
67
-
68
  // Allow Admin API Key
69
  if (apiKey && request.headers['x-api-key'] === apiKey) return;
70
 
@@ -131,18 +131,18 @@ server.get('/health', async (_req, reply) => {
131
  const start = async () => {
132
  try {
133
  await registerRoutes();
134
-
135
  await setupRateLimit(server);
136
 
137
  const port = parseInt(process.env.PORT || '8080');
138
  logger.info(`[STARTUP] Attempting to listen on port ${port}...`);
139
-
140
  await server.listen({ port, host: '0.0.0.0' });
141
  logger.info(`🚀 Server listening on http://0.0.0.0:${port}`);
142
 
143
  // Background tasks (after listen, safe to start)
144
  startCleanupCron();
145
-
146
  } catch (err: any) {
147
  logger.error({ err }, '[STARTUP] ❌ FATAL ERROR DURING STARTUP');
148
  process.exit(1);
@@ -152,7 +152,7 @@ const start = async () => {
152
  // ── Graceful Shutdown ─────────────────────────────────────────────────────────
153
  const handleShutdown = async (signal: string) => {
154
  logger.info(`[SHUTDOWN] 🛑 Received ${signal}. Starting graceful shutdown...`);
155
-
156
  // 1. Stop accepting new requests
157
  await server.close();
158
  logger.info('[SHUTDOWN] HTTP server closed.');
 
64
  if (request.method === 'OPTIONS') return;
65
 
66
  const apiKey = process.env.ADMIN_API_KEY;
67
+
68
  // Allow Admin API Key
69
  if (apiKey && request.headers['x-api-key'] === apiKey) return;
70
 
 
131
  const start = async () => {
132
  try {
133
  await registerRoutes();
134
+
135
  await setupRateLimit(server);
136
 
137
  const port = parseInt(process.env.PORT || '8080');
138
  logger.info(`[STARTUP] Attempting to listen on port ${port}...`);
139
+
140
  await server.listen({ port, host: '0.0.0.0' });
141
  logger.info(`🚀 Server listening on http://0.0.0.0:${port}`);
142
 
143
  // Background tasks (after listen, safe to start)
144
  startCleanupCron();
145
+
146
  } catch (err: any) {
147
  logger.error({ err }, '[STARTUP] ❌ FATAL ERROR DURING STARTUP');
148
  process.exit(1);
 
152
  // ── Graceful Shutdown ─────────────────────────────────────────────────────────
153
  const handleShutdown = async (signal: string) => {
154
  logger.info(`[SHUTDOWN] 🛑 Received ${signal}. Starting graceful shutdown...`);
155
+
156
  // 1. Stop accepting new requests
157
  await server.close();
158
  logger.info('[SHUTDOWN] HTTP server closed.');
apps/api/src/routes/admin.ts CHANGED
@@ -58,7 +58,7 @@ export async function adminRoutes(fastify: FastifyInstance) {
58
  fastify.get('/users', async (req, reply) => {
59
  const parsed = PaginationSchema.safeParse(req.query);
60
  if (!parsed.success) return reply.code(400).send({ error: 'Invalid query parameters', details: parsed.error.flatten() });
61
-
62
  const { page, limit } = parsed.data;
63
 
64
  const [users, total] = await Promise.all([
 
58
  fastify.get('/users', async (req, reply) => {
59
  const parsed = PaginationSchema.safeParse(req.query);
60
  if (!parsed.success) return reply.code(400).send({ error: 'Invalid query parameters', details: parsed.error.flatten() });
61
+
62
  const { page, limit } = parsed.data;
63
 
64
  const [users, total] = await Promise.all([
apps/api/src/routes/analytics.ts CHANGED
@@ -10,7 +10,7 @@ export async function analyticsRoutes(fastify: FastifyInstance) {
10
  */
11
  fastify.get('/usage', async (req, reply) => {
12
  const organizationId = (req as any).organizationId;
13
-
14
  if (!organizationId) {
15
  return reply.code(400).send({ error: 'Organization ID is required' });
16
  }
@@ -27,16 +27,16 @@ export async function analyticsRoutes(fastify: FastifyInstance) {
27
  prisma.message.count({ where: { organizationId, direction: 'INBOUND' } }),
28
  prisma.message.count({ where: { organizationId, direction: 'OUTBOUND' } }),
29
  prisma.user.count({ where: { organizationId } }),
30
- prisma.user.count({
31
- where: {
32
- organizationId,
33
- lastActivityAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
34
- }
35
  })
36
  ]);
37
 
38
  // Estimate costs (Simplified: 1000 tokens avg per message interaction)
39
- const estimatedTokens = totalMessages * 1000;
40
 
41
  return {
42
  messages: {
@@ -80,8 +80,8 @@ export async function analyticsRoutes(fastify: FastifyInstance) {
80
  const completed = enrollments.filter(e => e.status === 'COMPLETED').length;
81
  const active = enrollments.filter(e => e.status === 'ACTIVE').length;
82
 
83
- const averageProgress = total > 0
84
- ? enrollments.reduce((acc, curr) => acc + curr.currentDay, 0) / total
85
  : 0;
86
 
87
  const scores = await prisma.userProgress.aggregate({
@@ -139,10 +139,10 @@ export async function analyticsRoutes(fastify: FastifyInstance) {
139
  });
140
 
141
  const total = counts.SENT + counts.DELIVERED + counts.READ + counts.FAILED;
142
-
143
  // Funnel logic: DELIVERED usually implies it was SENT, etc.
144
  // But here we count specific statuses.
145
-
146
  return {
147
  summary: {
148
  total,
 
10
  */
11
  fastify.get('/usage', async (req, reply) => {
12
  const organizationId = (req as any).organizationId;
13
+
14
  if (!organizationId) {
15
  return reply.code(400).send({ error: 'Organization ID is required' });
16
  }
 
27
  prisma.message.count({ where: { organizationId, direction: 'INBOUND' } }),
28
  prisma.message.count({ where: { organizationId, direction: 'OUTBOUND' } }),
29
  prisma.user.count({ where: { organizationId } }),
30
+ prisma.user.count({
31
+ where: {
32
+ organizationId,
33
+ lastActivityAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) }
34
+ }
35
  })
36
  ]);
37
 
38
  // Estimate costs (Simplified: 1000 tokens avg per message interaction)
39
+ const estimatedTokens = totalMessages * 1000;
40
 
41
  return {
42
  messages: {
 
80
  const completed = enrollments.filter(e => e.status === 'COMPLETED').length;
81
  const active = enrollments.filter(e => e.status === 'ACTIVE').length;
82
 
83
+ const averageProgress = total > 0
84
+ ? enrollments.reduce((acc, curr) => acc + curr.currentDay, 0) / total
85
  : 0;
86
 
87
  const scores = await prisma.userProgress.aggregate({
 
139
  });
140
 
141
  const total = counts.SENT + counts.DELIVERED + counts.READ + counts.FAILED;
142
+
143
  // Funnel logic: DELIVERED usually implies it was SENT, etc.
144
  // But here we count specific statuses.
145
+
146
  return {
147
  summary: {
148
  total,
apps/api/src/routes/organizations.ts CHANGED
@@ -41,7 +41,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
41
  fastify.post('/', async (req, reply) => {
42
  const body = OrganizationCreationSchema.safeParse(req.body);
43
  if (!body.success) return reply.code(400).send({ error: body.error.flatten() });
44
-
45
  const { adminEmail, adminName, slug, mode, ...orgData } = body.data;
46
 
47
  // Check if slug already exists
@@ -49,7 +49,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
49
  if (existing) return reply.code(400).send({ error: 'Slug already taken' });
50
 
51
  const data = encryptSecrets(orgData);
52
-
53
  // Use a transaction to ensure Org, Admin and Email Queueing are linked
54
  const result = await prisma.$transaction(async (tx) => {
55
  const org = await tx.organization.create({
@@ -73,7 +73,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
73
  // Send Welcome Email (async via BullMQ) inside transaction to link with creation success
74
  const loginUrl = `https://${slug}.xamle.studio/login`;
75
  const resetUrl = `https://${slug}.xamle.studio/reset-password`;
76
-
77
  await scheduleEmail({
78
  to: adminEmail,
79
  subject: `Bienvenue chez Xamlé Studio - ${org.name}`,
@@ -158,12 +158,12 @@ export async function organizationRoutes(fastify: FastifyInstance) {
158
  await tx.whatsAppPhoneNumber.upsert({
159
  where: { id: phoneNumberId },
160
  update: {
161
- number: phoneNumber || '',
162
  organizationId: id
163
  },
164
  create: {
165
  id: phoneNumberId,
166
- number: phoneNumber || '',
167
  organizationId: id
168
  }
169
  });
@@ -215,7 +215,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
215
  fastify.post('/:id/index-kb', async (req, reply) => {
216
  const { id } = req.params as { id: string };
217
  const org = await prisma.organization.findUnique({ where: { id } });
218
-
219
  if (!org || !org.knowledgeBaseUrl) {
220
  return reply.code(400).send({ error: 'Organization or Knowledge Base URL not found' });
221
  }
@@ -301,7 +301,7 @@ export async function organizationRoutes(fastify: FastifyInstance) {
301
  // 10. CRM: Delete Single Contact
302
  fastify.delete('/:id/contacts/:contactId', async (req, reply) => {
303
  const { id: organizationId, contactId } = req.params as { id: string; contactId: string };
304
-
305
  try {
306
  await prisma.contact.delete({
307
  where: { id: contactId, organizationId } // Security check
 
41
  fastify.post('/', async (req, reply) => {
42
  const body = OrganizationCreationSchema.safeParse(req.body);
43
  if (!body.success) return reply.code(400).send({ error: body.error.flatten() });
44
+
45
  const { adminEmail, adminName, slug, mode, ...orgData } = body.data;
46
 
47
  // Check if slug already exists
 
49
  if (existing) return reply.code(400).send({ error: 'Slug already taken' });
50
 
51
  const data = encryptSecrets(orgData);
52
+
53
  // Use a transaction to ensure Org, Admin and Email Queueing are linked
54
  const result = await prisma.$transaction(async (tx) => {
55
  const org = await tx.organization.create({
 
73
  // Send Welcome Email (async via BullMQ) inside transaction to link with creation success
74
  const loginUrl = `https://${slug}.xamle.studio/login`;
75
  const resetUrl = `https://${slug}.xamle.studio/reset-password`;
76
+
77
  await scheduleEmail({
78
  to: adminEmail,
79
  subject: `Bienvenue chez Xamlé Studio - ${org.name}`,
 
158
  await tx.whatsAppPhoneNumber.upsert({
159
  where: { id: phoneNumberId },
160
  update: {
161
+ displayPhone: phoneNumber || '',
162
  organizationId: id
163
  },
164
  create: {
165
  id: phoneNumberId,
166
+ displayPhone: phoneNumber || '',
167
  organizationId: id
168
  }
169
  });
 
215
  fastify.post('/:id/index-kb', async (req, reply) => {
216
  const { id } = req.params as { id: string };
217
  const org = await prisma.organization.findUnique({ where: { id } });
218
+
219
  if (!org || !org.knowledgeBaseUrl) {
220
  return reply.code(400).send({ error: 'Organization or Knowledge Base URL not found' });
221
  }
 
301
  // 10. CRM: Delete Single Contact
302
  fastify.delete('/:id/contacts/:contactId', async (req, reply) => {
303
  const { id: organizationId, contactId } = req.params as { id: string; contactId: string };
304
+
305
  try {
306
  await prisma.contact.delete({
307
  where: { id: contactId, organizationId } // Security check
apps/api/src/routes/payments.ts CHANGED
@@ -65,24 +65,24 @@ export async function paymentRoutes(fastify: FastifyInstance) {
65
  return reply.status(500).send({ error: 'Failed to create organization checkout session' });
66
  }
67
  });
68
-
69
  // Create a Billing Portal Session
70
  fastify.post('/customer-portal', async (request, reply) => {
71
  const organizationId = (request as any).organizationId;
72
  if (!organizationId) {
73
  return reply.status(400).send({ error: 'Missing organizationId' });
74
  }
75
-
76
  try {
77
  const org = await prisma.organization.findUnique({
78
  where: { id: organizationId },
79
  select: { stripeCustomerId: true }
80
  });
81
-
82
  if (!org?.stripeCustomerId) {
83
  return reply.status(400).send({ error: 'No active subscription found for this organization' });
84
  }
85
-
86
  const portalUrl = await stripeService.createCustomerPortalSession(org.stripeCustomerId);
87
  return { success: true, url: portalUrl };
88
  } catch (error) {
@@ -125,7 +125,7 @@ export async function stripeWebhookRoute(fastify: FastifyInstance) {
125
  }
126
 
127
  // --- Handle Events ---
128
-
129
  // 1. Single Payments (Student enrolling in premium track)
130
  if (event.type === 'checkout.session.completed') {
131
  const session = event.data.object as any;
@@ -172,7 +172,7 @@ export async function stripeWebhookRoute(fastify: FastifyInstance) {
172
  // This was a subscription checkout for an organization
173
  await prisma.organization.update({
174
  where: { id: orgId },
175
- data: {
176
  stripeCustomerId: session.customer as string,
177
  subscriptionStatus: 'ACTIVE'
178
  }
 
65
  return reply.status(500).send({ error: 'Failed to create organization checkout session' });
66
  }
67
  });
68
+
69
  // Create a Billing Portal Session
70
  fastify.post('/customer-portal', async (request, reply) => {
71
  const organizationId = (request as any).organizationId;
72
  if (!organizationId) {
73
  return reply.status(400).send({ error: 'Missing organizationId' });
74
  }
75
+
76
  try {
77
  const org = await prisma.organization.findUnique({
78
  where: { id: organizationId },
79
  select: { stripeCustomerId: true }
80
  });
81
+
82
  if (!org?.stripeCustomerId) {
83
  return reply.status(400).send({ error: 'No active subscription found for this organization' });
84
  }
85
+
86
  const portalUrl = await stripeService.createCustomerPortalSession(org.stripeCustomerId);
87
  return { success: true, url: portalUrl };
88
  } catch (error) {
 
125
  }
126
 
127
  // --- Handle Events ---
128
+
129
  // 1. Single Payments (Student enrolling in premium track)
130
  if (event.type === 'checkout.session.completed') {
131
  const session = event.data.object as any;
 
172
  // This was a subscription checkout for an organization
173
  await prisma.organization.update({
174
  where: { id: orgId },
175
+ data: {
176
  stripeCustomerId: session.customer as string,
177
  subscriptionStatus: 'ACTIVE'
178
  }
packages/shared-types/tsconfig.tsbuildinfo DELETED
@@ -1 +0,0 @@
1
- {"root":["./src/index.ts"],"version":"5.9.3"}