import Fastify from 'fastify'; import mercurius from 'mercurius'; import { config } from './config.js'; import { prisma } from './prisma.js'; import { resolvers, schema } from './graphql/schema.js'; import { fetchTelegramPhoto, handleTelegramBotWebhook, } from './auth/telegram-bot-login.js'; const app = Fastify({ logger: true, bodyLimit: 25 * 1024 * 1024, }); app.register(mercurius, { schema, resolvers, graphiql: true, context: async (request) => { const initDataHeader = request.headers['x-telegram-init-data']; const loginDataHeader = request.headers['x-telegram-login-data']; const sessionTokenHeader = request.headers['x-mapflow-session-token']; return { telegramInitData: Array.isArray(initDataHeader) ? initDataHeader[0] : initDataHeader, telegramLoginData: Array.isArray(loginDataHeader) ? loginDataHeader[0] : loginDataHeader, mapflowSessionToken: Array.isArray(sessionTokenHeader) ? sessionTokenHeader[0] : sessionTokenHeader, }; }, }); app.get('/health', async () => ({ ok: true })); app.post('/telegram/webhook', async (request, reply) => { const secretHeader = request.headers['x-telegram-bot-api-secret-token']; const secretToken = Array.isArray(secretHeader) ? secretHeader[0] : secretHeader; await handleTelegramBotWebhook(request.body as never, secretToken); return reply.send({ ok: true }); }); app.get('/telegram/photo/:fileId', async (request, reply) => { const params = request.params as { fileId: string }; const photo = await fetchTelegramPhoto(params.fileId); reply.header('content-type', photo.contentType); reply.header('cache-control', 'public, max-age=86400'); reply.header('access-control-allow-origin', '*'); reply.header('cross-origin-resource-policy', 'cross-origin'); return reply.send(photo.bytes); }); app.get('/audio/voice-experiences/:experienceId', async (request, reply) => { const params = request.params as { experienceId: string }; const query = request.query as { token?: string }; if (!query.token) { return reply.code(401).send({ error: 'Audio token is required.' }); } const experience = await prisma.voiceExperience.findUnique({ where: { id: params.experienceId }, select: { audioAccessToken: true, audioContentBase64: true, audioMimeType: true, }, }); if ( !experience || experience.audioAccessToken !== query.token || !experience.audioContentBase64 || !experience.audioMimeType ) { return reply.code(404).send({ error: 'Audio was not found.' }); } reply.header('content-type', experience.audioMimeType); reply.header('cache-control', 'no-store'); return reply.send(Buffer.from(experience.audioContentBase64, 'base64')); }); app.addHook('onClose', async () => { await prisma.$disconnect(); }); await app.listen({ host: config.host, port: config.port });