import { Type, type FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebox' import config from '../../config.ts' import knex from '../../lib/knex.ts' import emitter from '../../lib/emitter.ts' import sendMail from '../../lib/send_mail.ts' import Queries from '../../services/invites/queries.ts' import inviteEmailTemplate from '../../templates/emails/invite.ts' import { RoleSchema } from './roles.ts' export const InviteSchema = Type.Object({ id: Type.Number(), email: Type.String({ format: 'email' }), token: Type.String(), roles: Type.Array(RoleSchema), createdAt: Type.String(), createdById: Type.Number(), createdBy: Type.Object({}), consumedAt: [Type.String(), Type.Null()], consumedById: [Type.Number(), Type.Null()], consumedBy: [Type.Object({}), Type.Null()], }) const invitesPlugin: FastifyPluginCallbackTypebox = (fastify, _option, done) => { const queries = Queries({ emitter, knex }) fastify.addHook('onRequest', fastify.auth) fastify.route({ url: '/', method: 'GET', schema: { querystring: InviteSchema, response: { 200: InviteSchema, }, }, handler(request) { return queries.find(request.query) }, }) fastify.route({ url: '/', method: 'POST', schema: { body: { type: Type.Object({ email: Type.String({ format: 'email' }), roles: Type.Array(Type.Number()), }), properties: { email: { type: 'string' }, roles: { type: 'array', items: { type: 'integer' } }, }, required: ['email', 'roles'], }, response: { 201: InviteSchema }, }, async handler(request, reply) { const trx = await knex.transaction() const invite = await queries.create( { ...request.body, createdById: request.session.userId, }, trx, ) try { const link = `${new URL(config.auth.paths.register, config.site.url)}?email=${invite.email}&token=${invite.token}` await sendMail({ to: invite.email, subject: `Invite to ${config.site.title} Admin`, html: await inviteEmailTemplate({ link }).text(), }) await trx.commit() return reply.header('Location', `${request.url}/${invite.id}`).status(201).send(invite) } catch (err) { await trx.rollback() throw err } }, }) fastify.route({ url: '/:id', method: 'DELETE', schema: { params: Type.Object({ id: Type.Number(), }), response: { 204: {}, 404: {}, }, }, handler(request, reply) { return queries.removeById(request.params.id).then((count) => reply.status(count > 0 ? 204 : 404).send()) }, }) done() } export default invitesPlugin