Files
myeasycms-v2/packages/features/accounts/src/server/services/delete-personal-account.service.ts
Giancarlo Buomprisco 7ebff31475 Next.js Supabase V3 (#463)
Version 3 of the kit:
- Radix UI replaced with Base UI (using the Shadcn UI patterns)
- next-intl replaces react-i18next
- enhanceAction deprecated; usage moved to next-safe-action
- main layout now wrapped with [locale] path segment
- Teams only mode
- Layout updates
- Zod v4
- Next.js 16.2
- Typescript 6
- All other dependencies updated
- Removed deprecated Edge CSRF
- Dynamic Github Action runner
2026-03-24 13:40:38 +08:00

150 lines
3.7 KiB
TypeScript

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<Database>;
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,
});
}
}