87 lines
2.8 KiB
TypeScript
87 lines
2.8 KiB
TypeScript
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 });
|