import { type Prisma, type ServiceUserRole } from '@prisma/client' import { ActionError } from 'astro:actions' import { z } from 'zod' import { defineProtectedAction } from '../../lib/defineProtectedAction' import { saveFileLocally } from '../../lib/fileStorage' import { prisma } from '../../lib/prisma' const selectUserReturnFields = { id: true, name: true, displayName: true, link: true, picture: true, admin: true, verified: true, moderator: true, verifiedLink: true, secretTokenHash: true, totalKarma: true, createdAt: true, updatedAt: true, spammer: true, } as const satisfies Prisma.UserSelect export const adminUserActions = { search: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ name: z.string().min(1, 'User name is required'), }), handler: async (input) => { const user = await prisma.user.findUnique({ where: { name: input.name }, select: selectUserReturnFields, }) return { user } }, }), update: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ id: z.number().int().positive(), name: z.string().min(1, 'Name is required').max(255, 'Name must be less than 255 characters'), link: z .string() .url('Invalid URL') .nullable() .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing .transform((val) => val || null), pictureFile: z.instanceof(File).optional(), type: z.array(z.enum(['admin', 'moderator', 'spammer'])), verifiedLink: z .string() .url('Invalid URL') .nullable() .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing .transform((val) => val || null), displayName: z .string() .max(50, 'Display Name must be less than 50 characters') .nullable() .default(null) // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing .transform((val) => val || null), }), handler: async ({ id, pictureFile, type, ...valuesToUpdate }) => { const user = await prisma.user.findUnique({ where: { id, }, select: { id: true, }, }) if (!user) { throw new ActionError({ code: 'BAD_REQUEST', message: 'User not found', }) } const pictureUrl = pictureFile && pictureFile.size > 0 ? await saveFileLocally(pictureFile, pictureFile.name, 'users/pictures/') : null const updatedUser = await prisma.user.update({ where: { id: user.id }, data: { name: valuesToUpdate.name, link: valuesToUpdate.link, verifiedLink: valuesToUpdate.verifiedLink, displayName: valuesToUpdate.displayName, verified: !!valuesToUpdate.verifiedLink, picture: pictureUrl, admin: type.includes('admin'), moderator: type.includes('moderator'), spammer: type.includes('spammer'), }, select: selectUserReturnFields, }) return { updatedUser, } }, }), internalNotes: { add: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ userId: z.coerce.number().int().positive(), content: z.string().min(1).max(1000), }), handler: async (input, context) => { const note = await prisma.internalUserNote.create({ data: { content: input.content, userId: input.userId, addedByUserId: context.locals.user.id, }, select: { id: true, }, }) return { note } }, }), delete: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ noteId: z.coerce.number().int().positive(), }), handler: async (input) => { await prisma.internalUserNote.delete({ where: { id: input.noteId, }, }) }, }), update: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ noteId: z.coerce.number().int().positive(), content: z.string().min(1).max(1000), }), handler: async (input, context) => { const note = await prisma.internalUserNote.update({ where: { id: input.noteId, }, data: { content: input.content, addedByUserId: context.locals.user.id, }, select: { id: true, }, }) return { note } }, }), }, serviceAffiliations: { add: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ userId: z.coerce.number().int().positive(), serviceId: z.coerce.number().int().positive(), role: z.enum(['OWNER', 'ADMIN', 'MODERATOR', 'SUPPORT', 'TEAM_MEMBER']), }), handler: async (input) => { // Check if the user exists const user = await prisma.user.findUnique({ where: { id: input.userId }, select: { id: true }, }) if (!user) { throw new ActionError({ code: 'BAD_REQUEST', message: 'User not found', }) } // Check if the service exists const service = await prisma.service.findUnique({ where: { id: input.serviceId }, select: { id: true, name: true }, }) if (!service) { throw new ActionError({ code: 'BAD_REQUEST', message: 'Service not found', }) } try { // Check if the service affiliation already exists const existingAffiliation = await prisma.serviceUser.findUnique({ where: { userId_serviceId: { userId: input.userId, serviceId: input.serviceId, }, }, }) let serviceAffiliation if (existingAffiliation) { // Update existing affiliation serviceAffiliation = await prisma.serviceUser.update({ where: { userId_serviceId: { userId: input.userId, serviceId: input.serviceId, }, }, data: { role: input.role as ServiceUserRole, }, }) return { serviceAffiliation, serviceName: service.name, updated: true } } else { // Create new affiliation serviceAffiliation = await prisma.serviceUser.create({ data: { userId: input.userId, serviceId: input.serviceId, role: input.role as ServiceUserRole, }, }) return { serviceAffiliation, serviceName: service.name } } } catch (error) { console.error('Error managing service affiliation:', error) throw new ActionError({ code: 'INTERNAL_SERVER_ERROR', message: 'Error managing service affiliation', }) } }, }), remove: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ id: z.coerce.number().int().positive(), }), handler: async (input) => { const serviceAffiliation = await prisma.serviceUser.delete({ where: { id: input.id, }, include: { service: { select: { name: true, }, }, }, }) return { serviceAffiliation } }, }), }, karmaTransactions: { add: defineProtectedAction({ accept: 'form', permissions: 'admin', input: z.object({ userId: z.coerce.number().int().positive(), points: z.coerce.number().int(), description: z.string().min(1, 'Description is required'), }), handler: async (input, context) => { // Check if the user exists const user = await prisma.user.findUnique({ where: { id: input.userId }, select: { id: true }, }) if (!user) { throw new ActionError({ code: 'BAD_REQUEST', message: 'User not found', }) } await prisma.karmaTransaction.create({ data: { userId: input.userId, points: input.points, action: 'MANUAL_ADJUSTMENT', description: input.description, grantedByUserId: context.locals.user.id, }, }) }, }), }, }