135 lines
3.4 KiB
TypeScript
135 lines
3.4 KiB
TypeScript
import * as z from 'zod'
|
|
import type { FastifyPluginCallbackZod } from 'fastify-type-provider-zod'
|
|
import { jsonArrayFrom } from 'kysely/helpers/postgres'
|
|
import config from '../../config.ts'
|
|
import sendMail from '../../lib/send_mail.ts'
|
|
import { generateToken } from '../../plugins/auth/helpers.ts'
|
|
import inviteEmailTemplate from '../../templates/emails/invite.ts'
|
|
|
|
import { InviteSchema, RoleSchema } from '../../schemas/db.ts'
|
|
|
|
const invitesPlugin: FastifyPluginCallbackZod = (fastify, _option, done) => {
|
|
const { db } = fastify
|
|
|
|
// fastify.addHook('onRequest', fastify.auth)
|
|
|
|
fastify.route({
|
|
url: '/',
|
|
method: 'GET',
|
|
schema: {
|
|
response: {
|
|
200: z.array(
|
|
InviteSchema.extend({
|
|
roles: z.array(RoleSchema.pick({ id: true, name: true })),
|
|
}),
|
|
),
|
|
},
|
|
},
|
|
handler() {
|
|
return db
|
|
.selectFrom('invite as i')
|
|
.selectAll()
|
|
.select((eb) => [
|
|
jsonArrayFrom(
|
|
eb
|
|
.selectFrom('role as r')
|
|
.innerJoin('invites_roles as ir', 'ir.roleId', 'r.id')
|
|
.select(['r.id', 'r.name'])
|
|
.whereRef('r.id', '=', 'ir.roleId'),
|
|
).as('roles'),
|
|
])
|
|
.execute()
|
|
},
|
|
})
|
|
|
|
fastify.route({
|
|
url: '/',
|
|
method: 'POST',
|
|
schema: {
|
|
body: z.object({
|
|
email: z.email(),
|
|
roles: z.array(z.coerce.number()),
|
|
}),
|
|
response: {
|
|
201: InviteSchema.extend({
|
|
roles: z.array(RoleSchema.pick({ id: true, name: true })),
|
|
}),
|
|
},
|
|
},
|
|
async handler(request, reply) {
|
|
const trx = await db.startTransaction().execute()
|
|
|
|
const token = generateToken()
|
|
|
|
const invite = await trx
|
|
.insertInto('invite')
|
|
.values({ email: request.body.email, token, createdById: request.session.userId })
|
|
.returningAll()
|
|
.executeTakeFirstOrThrow()
|
|
|
|
await trx
|
|
.insertInto('invites_roles')
|
|
.values(
|
|
request.body.roles.map((roleId) => ({
|
|
inviteId: invite.id,
|
|
roleId,
|
|
})),
|
|
)
|
|
.execute()
|
|
|
|
try {
|
|
const roles = await trx
|
|
.selectFrom('role as r')
|
|
.select(['id', 'name'])
|
|
.innerJoin('invites_roles as ir', 'ir.roleId', 'r.id')
|
|
.where('ir.inviteId', '=', invite.id)
|
|
.execute()
|
|
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().execute()
|
|
|
|
return reply
|
|
.header('Location', `${request.url}/${invite.id}`)
|
|
.status(201)
|
|
.send({
|
|
...invite,
|
|
roles,
|
|
})
|
|
} catch (err) {
|
|
await trx.rollback().execute()
|
|
|
|
throw err
|
|
}
|
|
},
|
|
})
|
|
|
|
fastify.route({
|
|
url: '/:id',
|
|
method: 'DELETE',
|
|
schema: {
|
|
params: z.object({
|
|
id: z.number(),
|
|
}),
|
|
response: {
|
|
204: {},
|
|
404: {},
|
|
},
|
|
},
|
|
async handler(request, reply) {
|
|
const result = await db.deleteFrom('invite').where('id', '=', request.params.id).executeTakeFirstOrThrow()
|
|
|
|
return reply.status(result.numDeletedRows > 0 ? 204 : 404).send()
|
|
},
|
|
})
|
|
|
|
done()
|
|
}
|
|
|
|
export default invitesPlugin
|