CognxSafeTrack commited on
Commit ·
a59f28c
1
Parent(s): dced83b
feat: add Dockerfile and script for Hugging Face Spaces
Browse files- Dockerfile +46 -0
- apps/api/src/index.ts +3 -2
- apps/api/src/services/queue.ts +27 -0
- apps/api/src/services/whatsapp.ts +5 -3
- apps/whatsapp-worker/src/index.ts +40 -6
- scripts/start-backend.sh +13 -0
Dockerfile
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
FROM node:18-alpine
|
| 2 |
+
|
| 3 |
+
WORKDIR /app
|
| 4 |
+
|
| 5 |
+
# Install pnpm and global dependencies
|
| 6 |
+
RUN npm install -g pnpm turbo
|
| 7 |
+
|
| 8 |
+
# Copy root package files
|
| 9 |
+
COPY package.json pnpm-workspace.yaml turbo.json ./
|
| 10 |
+
|
| 11 |
+
# Copy package configurations
|
| 12 |
+
COPY packages/tsconfig packages/tsconfig
|
| 13 |
+
COPY packages/database packages/database
|
| 14 |
+
COPY packages/shared-types packages/shared-types
|
| 15 |
+
|
| 16 |
+
# Copy app configurations
|
| 17 |
+
COPY apps/api/package.json apps/api/package.json
|
| 18 |
+
COPY apps/whatsapp-worker/package.json apps/whatsapp-worker/package.json
|
| 19 |
+
COPY apps/api/tsconfig.json apps/api/tsconfig.json
|
| 20 |
+
COPY apps/whatsapp-worker/tsconfig.json apps/whatsapp-worker/tsconfig.json
|
| 21 |
+
|
| 22 |
+
# Install dependencies
|
| 23 |
+
RUN pnpm install
|
| 24 |
+
|
| 25 |
+
# Copy source code
|
| 26 |
+
COPY packages/database packages/database
|
| 27 |
+
COPY packages/shared-types packages/shared-types
|
| 28 |
+
COPY apps/api apps/api
|
| 29 |
+
COPY apps/whatsapp-worker apps/whatsapp-worker
|
| 30 |
+
COPY scripts/start-backend.sh scripts/start-backend.sh
|
| 31 |
+
|
| 32 |
+
# Generate Prisma Client
|
| 33 |
+
RUN pnpm db:generate
|
| 34 |
+
|
| 35 |
+
# Build packages and apps
|
| 36 |
+
RUN pnpm build --filter=api --filter=whatsapp-worker...
|
| 37 |
+
|
| 38 |
+
# Make script executable
|
| 39 |
+
RUN chmod +x scripts/start-backend.sh
|
| 40 |
+
|
| 41 |
+
# Expose port (HF Spaces uses 7860)
|
| 42 |
+
EXPOSE 7860
|
| 43 |
+
ENV PORT=7860
|
| 44 |
+
|
| 45 |
+
# Start command
|
| 46 |
+
CMD ["./scripts/start-backend.sh"]
|
apps/api/src/index.ts
CHANGED
|
@@ -17,8 +17,9 @@ server.get('/health', async (request, reply) => {
|
|
| 17 |
|
| 18 |
const start = async () => {
|
| 19 |
try {
|
| 20 |
-
|
| 21 |
-
|
|
|
|
| 22 |
} catch (err) {
|
| 23 |
server.log.error(err);
|
| 24 |
process.exit(1);
|
|
|
|
| 17 |
|
| 18 |
const start = async () => {
|
| 19 |
try {
|
| 20 |
+
const port = parseInt(process.env.PORT || '3001');
|
| 21 |
+
await server.listen({ port, host: '0.0.0.0' });
|
| 22 |
+
console.log(`Server listening on http://0.0.0.0:${port}`);
|
| 23 |
} catch (err) {
|
| 24 |
server.log.error(err);
|
| 25 |
process.exit(1);
|
apps/api/src/services/queue.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { Queue } from 'bullmq';
|
| 2 |
+
|
| 3 |
+
const connection = {
|
| 4 |
+
host: process.env.REDIS_HOST || 'localhost',
|
| 5 |
+
port: parseInt(process.env.REDIS_PORT || '6379'),
|
| 6 |
+
};
|
| 7 |
+
|
| 8 |
+
export const whatsappQueue = new Queue('whatsapp-queue', { connection });
|
| 9 |
+
|
| 10 |
+
export async function scheduleMessage(userId: string, text: string, delayMs: number = 0) {
|
| 11 |
+
await whatsappQueue.add('send-message', {
|
| 12 |
+
userId,
|
| 13 |
+
text
|
| 14 |
+
}, {
|
| 15 |
+
delay: delayMs
|
| 16 |
+
});
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
export async function scheduleTrackDay(userId: string, trackId: string, dayNumber: number, delayMs: number = 0) {
|
| 20 |
+
await whatsappQueue.add('send-content', {
|
| 21 |
+
userId,
|
| 22 |
+
trackId,
|
| 23 |
+
dayNumber
|
| 24 |
+
}, {
|
| 25 |
+
delay: delayMs
|
| 26 |
+
});
|
| 27 |
+
}
|
apps/api/src/services/whatsapp.ts
CHANGED
|
@@ -77,9 +77,11 @@ export class WhatsAppService {
|
|
| 77 |
|
| 78 |
console.log(`User ${user.id} enrolled in ${defaultTrack.title}`);
|
| 79 |
|
| 80 |
-
//
|
| 81 |
-
//
|
| 82 |
-
|
|
|
|
|
|
|
| 83 |
} else {
|
| 84 |
console.log('Unknown command, sending menu...');
|
| 85 |
// TODO: Send "Envoyez INSCRIPTION pour commencer"
|
|
|
|
| 77 |
|
| 78 |
console.log(`User ${user.id} enrolled in ${defaultTrack.title}`);
|
| 79 |
|
| 80 |
+
// Schedule Day 1 Content immediately (or short delay)
|
| 81 |
+
// Import dynamically to avoid circular deps if necessary, or just import at top
|
| 82 |
+
const { scheduleTrackDay } = await import('./queue');
|
| 83 |
+
await scheduleTrackDay(user.id, defaultTrack.id, 1, 0);
|
| 84 |
+
|
| 85 |
} else {
|
| 86 |
console.log('Unknown command, sending menu...');
|
| 87 |
// TODO: Send "Envoyez INSCRIPTION pour commencer"
|
apps/whatsapp-worker/src/index.ts
CHANGED
|
@@ -1,18 +1,52 @@
|
|
| 1 |
-
import { Worker } from 'bullmq';
|
| 2 |
import dotenv from 'dotenv';
|
|
|
|
| 3 |
|
| 4 |
dotenv.config();
|
| 5 |
|
|
|
|
|
|
|
| 6 |
const connection = {
|
| 7 |
host: process.env.REDIS_HOST || 'localhost',
|
| 8 |
port: parseInt(process.env.REDIS_PORT || '6379'),
|
| 9 |
};
|
| 10 |
|
| 11 |
-
const worker = new Worker('whatsapp-queue', async job => {
|
| 12 |
-
console.log('Processing job:', job.
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
}, { connection });
|
| 17 |
|
| 18 |
console.log('WhatsApp Worker started...');
|
|
|
|
| 1 |
+
import { Worker, Job } from 'bullmq';
|
| 2 |
import dotenv from 'dotenv';
|
| 3 |
+
import { PrismaClient } from '@prisma/client';
|
| 4 |
|
| 5 |
dotenv.config();
|
| 6 |
|
| 7 |
+
const prisma = new PrismaClient();
|
| 8 |
+
|
| 9 |
const connection = {
|
| 10 |
host: process.env.REDIS_HOST || 'localhost',
|
| 11 |
port: parseInt(process.env.REDIS_PORT || '6379'),
|
| 12 |
};
|
| 13 |
|
| 14 |
+
const worker = new Worker('whatsapp-queue', async (job: Job) => {
|
| 15 |
+
console.log('Processing job:', job.name, job.id);
|
| 16 |
+
|
| 17 |
+
try {
|
| 18 |
+
if (job.name === 'send-message') {
|
| 19 |
+
const { userId, text } = job.data;
|
| 20 |
+
// TODO: Call WhatsApp Cloud API to send text
|
| 21 |
+
console.log(`[MOCK SEND] To User ${userId}: "${text}"`);
|
| 22 |
+
}
|
| 23 |
+
else if (job.name === 'send-content') {
|
| 24 |
+
const { userId, trackId, dayNumber } = job.data;
|
| 25 |
+
|
| 26 |
+
const trackDay = await prisma.trackDay.findFirst({
|
| 27 |
+
where: { trackId, dayNumber }
|
| 28 |
+
});
|
| 29 |
+
|
| 30 |
+
if (trackDay && trackDay.textContent) {
|
| 31 |
+
// TODO: Call WhatsApp Cloud API
|
| 32 |
+
console.log(`[MOCK SEND CONTENT] To User ${userId}: "${trackDay.textContent}"`);
|
| 33 |
+
|
| 34 |
+
// Update enrollment progress
|
| 35 |
+
await prisma.enrollment.updateMany({
|
| 36 |
+
where: { userId, trackId },
|
| 37 |
+
data: {
|
| 38 |
+
currentDay: dayNumber,
|
| 39 |
+
lastActivityAt: new Date()
|
| 40 |
+
}
|
| 41 |
+
});
|
| 42 |
+
} else {
|
| 43 |
+
console.error(`Content not found for Track ${trackId} Day ${dayNumber}`);
|
| 44 |
+
}
|
| 45 |
+
}
|
| 46 |
+
} catch (error) {
|
| 47 |
+
console.error(`Job ${job.id} failed:`, error);
|
| 48 |
+
throw error;
|
| 49 |
+
}
|
| 50 |
}, { connection });
|
| 51 |
|
| 52 |
console.log('WhatsApp Worker started...');
|
scripts/start-backend.sh
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
|
| 3 |
+
# Migrate database
|
| 4 |
+
echo "Running database migrations..."
|
| 5 |
+
pnpm db:push
|
| 6 |
+
|
| 7 |
+
# Start Worker in background
|
| 8 |
+
echo "Starting WhatsApp Worker..."
|
| 9 |
+
node apps/whatsapp-worker/dist/index.js &
|
| 10 |
+
|
| 11 |
+
# Start API in foreground
|
| 12 |
+
echo "Starting API..."
|
| 13 |
+
node apps/api/dist/index.js
|