import 'server-only'; import { SupabaseClient } from '@supabase/supabase-js'; import * as z from 'zod'; import { getLogger } from '@kit/shared/logger'; import { Database } from '@kit/supabase/database'; export function createDeletePersonalAccountService() { return new DeletePersonalAccountService(); } /** * @name DeletePersonalAccountService * @description Service for managing accounts in the application * @param Database - The Supabase database type to use * @example * const client = getSupabaseClient(); * const accountsService = new DeletePersonalAccountService(); */ class DeletePersonalAccountService { private namespace = 'accounts.delete'; /** * @name deletePersonalAccount * Delete personal account of a user. * This will delete the user from the authentication provider and cancel all subscriptions. * * Permissions are not checked here, as they are checked in the server action. * USE WITH CAUTION. THE USER MUST HAVE THE NECESSARY PERMISSIONS. */ async deletePersonalAccount(params: { adminClient: SupabaseClient; account: { id: string; email: string | null; }; }) { const logger = await getLogger(); const userId = params.account.id; const ctx = { userId, name: this.namespace }; logger.info( ctx, 'User requested to delete their personal account. Processing...', ); // execute the deletion of the user try { const response = await params.adminClient.auth.admin.deleteUser(userId); if (response.error) { throw response.error; } logger.info(ctx, 'User successfully deleted!'); if (params.account.email) { // dispatch the delete account email. Errors are handled in the method. await this.dispatchDeleteAccountEmail({ email: params.account.email, id: params.account.id, }); } return { success: true, }; } catch (error) { logger.error( { ...ctx, error, }, 'Encountered an error deleting user', ); throw new Error('Error deleting user', { cause: error }); } } private async dispatchDeleteAccountEmail(account: { email: string; id: string; }) { const logger = await getLogger(); const ctx = { name: this.namespace, userId: account.id }; try { logger.info(ctx, 'Sending delete account email...'); await this.sendDeleteAccountEmail(account); logger.info(ctx, 'Delete account email sent successfully'); } catch (error) { logger.error( { ...ctx, error, }, 'Failed to send delete account email', ); } } private async sendDeleteAccountEmail(account: { email: string }) { const emailSettings = this.getEmailSettings(); const { renderAccountDeleteEmail } = await import('@kit/email-templates'); const { getMailer } = await import('@kit/mailers'); const mailer = await getMailer(); const { html, subject } = await renderAccountDeleteEmail({ productName: emailSettings.productName, }); await mailer.sendEmail({ from: emailSettings.fromEmail, html, subject, to: account.email, }); } private getEmailSettings() { const productName = process.env.NEXT_PUBLIC_PRODUCT_NAME; const fromEmail = process.env.EMAIL_SENDER; return z .object({ productName: z .string({ error: 'NEXT_PUBLIC_PRODUCT_NAME is required', }) .min(1), fromEmail: z .string({ error: 'EMAIL_SENDER is required', }) .min(1), }) .parse({ productName, fromEmail, }); } }