Refactor account server actions using the enhanced action helper
The enhanced action helper has been utilized to refactor account-related server actions across the codebase. This change aims to streamline the server-side handling of user accounts, team accounts, and related functionality. As a result, various account-related server actions have now been wrapped with the helper, providing uniformity and consistency in action handling.
This commit is contained in:
@@ -28,7 +28,8 @@ export const createPersonalAccountCheckoutSession = enhanceAction(
|
|||||||
* @name createPersonalAccountBillingPortalSession
|
* @name createPersonalAccountBillingPortalSession
|
||||||
* @description Creates a billing Portal session for a personal account
|
* @description Creates a billing Portal session for a personal account
|
||||||
*/
|
*/
|
||||||
export async function createPersonalAccountBillingPortalSession() {
|
export const createPersonalAccountBillingPortalSession = enhanceAction(
|
||||||
|
async () => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const service = createUserBillingService(client);
|
const service = createUserBillingService(client);
|
||||||
|
|
||||||
@@ -36,4 +37,6 @@ export async function createPersonalAccountBillingPortalSession() {
|
|||||||
const url = await service.createBillingPortalSession();
|
const url = await service.createBillingPortalSession();
|
||||||
|
|
||||||
return redirect(url);
|
return redirect(url);
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,8 +2,7 @@
|
|||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
|
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
// billing imports
|
// billing imports
|
||||||
@@ -17,23 +16,25 @@ import { createTeamBillingService } from './team-billing.service';
|
|||||||
* @name createTeamAccountCheckoutSession
|
* @name createTeamAccountCheckoutSession
|
||||||
* @description Creates a checkout session for a team account.
|
* @description Creates a checkout session for a team account.
|
||||||
*/
|
*/
|
||||||
export async function createTeamAccountCheckoutSession(
|
export const createTeamAccountCheckoutSession = enhanceAction(
|
||||||
params: z.infer<typeof TeamCheckoutSchema>,
|
(data) => {
|
||||||
) {
|
|
||||||
const data = TeamCheckoutSchema.parse(params);
|
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const service = createTeamBillingService(client);
|
const service = createTeamBillingService(client);
|
||||||
|
|
||||||
return service.createCheckout(data);
|
return service.createCheckout(data);
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: TeamCheckoutSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name createBillingPortalSession
|
* @name createBillingPortalSession
|
||||||
* @description Creates a Billing Session Portal and redirects the user to the
|
* @description Creates a Billing Session Portal and redirects the user to the
|
||||||
* provider's hosted instance
|
* provider's hosted instance
|
||||||
*/
|
*/
|
||||||
export async function createBillingPortalSession(formData: FormData) {
|
export const createBillingPortalSession = enhanceAction(
|
||||||
|
async (formData: FormData) => {
|
||||||
const params = TeamBillingPortalSchema.parse(Object.fromEntries(formData));
|
const params = TeamBillingPortalSchema.parse(Object.fromEntries(formData));
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
@@ -43,4 +44,6 @@ export async function createBillingPortalSession(formData: FormData) {
|
|||||||
const url = await service.createBillingPortalSession(params);
|
const url = await service.createBillingPortalSession(params);
|
||||||
|
|
||||||
return redirect(url);
|
return redirect(url);
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
"@kit/eslint-config": "workspace:*",
|
"@kit/eslint-config": "workspace:*",
|
||||||
"@kit/mailers": "workspace:^",
|
"@kit/mailers": "workspace:^",
|
||||||
"@kit/monitoring": "workspace:^",
|
"@kit/monitoring": "workspace:^",
|
||||||
|
"@kit/next": "workspace:^",
|
||||||
"@kit/prettier-config": "workspace:*",
|
"@kit/prettier-config": "workspace:*",
|
||||||
"@kit/shared": "workspace:^",
|
"@kit/shared": "workspace:^",
|
||||||
"@kit/supabase": "workspace:^",
|
"@kit/supabase": "workspace:^",
|
||||||
|
|||||||
@@ -5,8 +5,7 @@ import { redirect } from 'next/navigation';
|
|||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { getLogger } from '@kit/shared/logger';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { DeletePersonalAccountSchema } from '../schema/delete-personal-account.schema';
|
import { DeletePersonalAccountSchema } from '../schema/delete-personal-account.schema';
|
||||||
@@ -22,7 +21,8 @@ export async function refreshAuthSession() {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deletePersonalAccountAction(formData: FormData) {
|
export const deletePersonalAccountAction = enhanceAction(
|
||||||
|
async (formData: FormData, user) => {
|
||||||
// validate the form data
|
// validate the form data
|
||||||
const { success } = DeletePersonalAccountSchema.safeParse(
|
const { success } = DeletePersonalAccountSchema.safeParse(
|
||||||
Object.fromEntries(formData.entries()),
|
Object.fromEntries(formData.entries()),
|
||||||
@@ -33,24 +33,6 @@ export async function deletePersonalAccountAction(formData: FormData) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const auth = await requireUser(client);
|
|
||||||
|
|
||||||
if (auth.error) {
|
|
||||||
const logger = await getLogger();
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
{
|
|
||||||
error: auth.error,
|
|
||||||
},
|
|
||||||
`User is not authenticated. Redirecting to login page.`,
|
|
||||||
);
|
|
||||||
|
|
||||||
redirect(auth.redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
// retrieve user ID and email
|
|
||||||
const userId = auth.data.id;
|
|
||||||
const userEmail = auth.data.email ?? null;
|
|
||||||
|
|
||||||
// create a new instance of the personal accounts service
|
// create a new instance of the personal accounts service
|
||||||
const service = createDeletePersonalAccountService();
|
const service = createDeletePersonalAccountService();
|
||||||
@@ -58,8 +40,8 @@ export async function deletePersonalAccountAction(formData: FormData) {
|
|||||||
// delete the user's account and cancel all subscriptions
|
// delete the user's account and cancel all subscriptions
|
||||||
await service.deletePersonalAccount({
|
await service.deletePersonalAccount({
|
||||||
adminClient: getSupabaseServerActionClient({ admin: true }),
|
adminClient: getSupabaseServerActionClient({ admin: true }),
|
||||||
userId,
|
userId: user.id,
|
||||||
userEmail,
|
userEmail: user.email ?? null,
|
||||||
emailSettings,
|
emailSettings,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -71,7 +53,9 @@ export async function deletePersonalAccountAction(formData: FormData) {
|
|||||||
|
|
||||||
// redirect to the home page
|
// redirect to the home page
|
||||||
redirect('/');
|
redirect('/');
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
function getEmailSettingsFromEnvironment() {
|
function getEmailSettingsFromEnvironment() {
|
||||||
return z
|
return z
|
||||||
|
|||||||
@@ -24,6 +24,7 @@
|
|||||||
"@kit/eslint-config": "workspace:*",
|
"@kit/eslint-config": "workspace:*",
|
||||||
"@kit/mailers": "workspace:^",
|
"@kit/mailers": "workspace:^",
|
||||||
"@kit/monitoring": "workspace:*",
|
"@kit/monitoring": "workspace:*",
|
||||||
|
"@kit/next": "workspace:^",
|
||||||
"@kit/prettier-config": "workspace:*",
|
"@kit/prettier-config": "workspace:*",
|
||||||
"@kit/shared": "workspace:^",
|
"@kit/shared": "workspace:^",
|
||||||
"@kit/supabase": "workspace:^",
|
"@kit/supabase": "workspace:^",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation';
|
|||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { CreateTeamSchema } from '../../schema/create-team.schema';
|
import { CreateTeamSchema } from '../../schema/create-team.schema';
|
||||||
@@ -17,24 +17,14 @@ const TEAM_ACCOUNTS_HOME_PATH = z
|
|||||||
.min(1)
|
.min(1)
|
||||||
.parse(process.env.TEAM_ACCOUNTS_HOME_PATH);
|
.parse(process.env.TEAM_ACCOUNTS_HOME_PATH);
|
||||||
|
|
||||||
export async function createOrganizationAccountAction(
|
export const createOrganizationAccountAction = enhanceAction(
|
||||||
params: z.infer<typeof CreateTeamSchema>,
|
async (params, user) => {
|
||||||
) {
|
|
||||||
const { name: accountName } = CreateTeamSchema.parse(params);
|
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const auth = await requireUser(client);
|
|
||||||
|
|
||||||
if (auth.error) {
|
|
||||||
redirect(auth.redirectTo);
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = auth.data.id;
|
|
||||||
const service = createCreateTeamAccountService(client);
|
const service = createCreateTeamAccountService(client);
|
||||||
|
|
||||||
const { data, error } = await service.createNewOrganizationAccount({
|
const { data, error } = await service.createNewOrganizationAccount({
|
||||||
name: accountName,
|
name: params.name,
|
||||||
userId,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
@@ -44,4 +34,8 @@ export async function createOrganizationAccountAction(
|
|||||||
const accountHomePath = TEAM_ACCOUNTS_HOME_PATH + '/' + data.slug;
|
const accountHomePath = TEAM_ACCOUNTS_HOME_PATH + '/' + data.slug;
|
||||||
|
|
||||||
redirect(accountHomePath);
|
redirect(accountHomePath);
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: CreateTeamSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -4,27 +4,28 @@ import { redirect } from 'next/navigation';
|
|||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { SupabaseClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { Database } from '@kit/supabase/database';
|
import { Database } from '@kit/supabase/database';
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { DeleteTeamAccountSchema } from '../../schema/delete-team-account.schema';
|
import { DeleteTeamAccountSchema } from '../../schema/delete-team-account.schema';
|
||||||
import { createDeleteTeamAccountService } from '../services/delete-team-account.service';
|
import { createDeleteTeamAccountService } from '../services/delete-team-account.service';
|
||||||
|
|
||||||
export async function deleteTeamAccountAction(formData: FormData) {
|
export const deleteTeamAccountAction = enhanceAction(
|
||||||
|
async (formData: FormData, user) => {
|
||||||
const params = DeleteTeamAccountSchema.parse(
|
const params = DeleteTeamAccountSchema.parse(
|
||||||
Object.fromEntries(formData.entries()),
|
Object.fromEntries(formData.entries()),
|
||||||
);
|
);
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const auth = await requireUser(client);
|
const userId = user.id;
|
||||||
|
const accountId = params.accountId;
|
||||||
if (auth.error) {
|
|
||||||
throw new Error('Authentication required');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the user has the necessary permissions to delete the team account
|
// Check if the user has the necessary permissions to delete the team account
|
||||||
await assertUserPermissionsToDeleteTeamAccount(client, params.accountId);
|
await assertUserPermissionsToDeleteTeamAccount(client, {
|
||||||
|
accountId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
|
||||||
// Get the Supabase client and create a new service instance.
|
// Get the Supabase client and create a new service instance.
|
||||||
const service = createDeleteTeamAccountService();
|
const service = createDeleteTeamAccountService();
|
||||||
@@ -35,32 +36,29 @@ export async function deleteTeamAccountAction(formData: FormData) {
|
|||||||
admin: true,
|
admin: true,
|
||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
accountId: params.accountId,
|
accountId,
|
||||||
userId: auth.data.id,
|
userId,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
return redirect('/home');
|
return redirect('/home');
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
async function assertUserPermissionsToDeleteTeamAccount(
|
async function assertUserPermissionsToDeleteTeamAccount(
|
||||||
client: SupabaseClient<Database>,
|
client: SupabaseClient<Database>,
|
||||||
accountId: string,
|
params: {
|
||||||
|
accountId: string;
|
||||||
|
userId: string;
|
||||||
|
},
|
||||||
) {
|
) {
|
||||||
const auth = await requireUser(client);
|
|
||||||
|
|
||||||
if (auth.error ?? !auth.data.id) {
|
|
||||||
throw new Error('Authentication required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const userId = auth.data.id;
|
|
||||||
|
|
||||||
const { data, error } = await client
|
const { data, error } = await client
|
||||||
.from('accounts')
|
.from('accounts')
|
||||||
.select('id')
|
.select('id')
|
||||||
.eq('primary_owner_user_id', userId)
|
.eq('primary_owner_user_id', params.userId)
|
||||||
.eq('is_personal_account', false)
|
.eq('is_personal_account', false)
|
||||||
.eq('id', accountId);
|
.eq('id', params.accountId);
|
||||||
|
|
||||||
if (error ?? !data) {
|
if (error ?? !data) {
|
||||||
throw new Error('Account not found');
|
throw new Error('Account not found');
|
||||||
|
|||||||
@@ -3,33 +3,29 @@
|
|||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { LeaveTeamAccountSchema } from '../../schema/leave-team-account.schema';
|
import { LeaveTeamAccountSchema } from '../../schema/leave-team-account.schema';
|
||||||
import { createLeaveTeamAccountService } from '../services/leave-team-account.service';
|
import { createLeaveTeamAccountService } from '../services/leave-team-account.service';
|
||||||
|
|
||||||
export async function leaveTeamAccountAction(formData: FormData) {
|
export const leaveTeamAccountAction = enhanceAction(
|
||||||
|
async (formData: FormData, user) => {
|
||||||
const body = Object.fromEntries(formData.entries());
|
const body = Object.fromEntries(formData.entries());
|
||||||
const params = LeaveTeamAccountSchema.parse(body);
|
const params = LeaveTeamAccountSchema.parse(body);
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
|
||||||
const auth = await requireUser(client);
|
|
||||||
|
|
||||||
if (auth.error) {
|
|
||||||
throw new Error('Authentication required');
|
|
||||||
}
|
|
||||||
|
|
||||||
const service = createLeaveTeamAccountService(
|
const service = createLeaveTeamAccountService(
|
||||||
getSupabaseServerActionClient({ admin: true }),
|
getSupabaseServerActionClient({ admin: true }),
|
||||||
);
|
);
|
||||||
|
|
||||||
await service.leaveTeamAccount({
|
await service.leaveTeamAccount({
|
||||||
accountId: params.accountId,
|
accountId: params.accountId,
|
||||||
userId: auth.data.id,
|
userId: user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
revalidatePath('/home/[account]', 'layout');
|
revalidatePath('/home/[account]', 'layout');
|
||||||
|
|
||||||
return redirect('/home');
|
return redirect('/home');
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|||||||
@@ -2,17 +2,15 @@
|
|||||||
|
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
|
|
||||||
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
|
||||||
|
|
||||||
import { UpdateTeamNameSchema } from '../../schema/update-team-name.schema';
|
import { UpdateTeamNameSchema } from '../../schema/update-team-name.schema';
|
||||||
|
|
||||||
export async function updateTeamAccountName(
|
export const updateTeamAccountName = enhanceAction(
|
||||||
params: z.infer<typeof UpdateTeamNameSchema>,
|
async (params) => {
|
||||||
) {
|
|
||||||
const client = getSupabaseServerComponentClient();
|
const client = getSupabaseServerComponentClient();
|
||||||
const { name, slug, path } = UpdateTeamNameSchema.parse(params);
|
const { name, path, slug } = params;
|
||||||
|
|
||||||
const { error, data } = await client
|
const { error, data } = await client
|
||||||
.from('accounts')
|
.from('accounts')
|
||||||
@@ -39,4 +37,8 @@ export async function updateTeamAccountName(
|
|||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: UpdateTeamNameSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -3,12 +3,9 @@
|
|||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
import { redirect } from 'next/navigation';
|
import { redirect } from 'next/navigation';
|
||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
|
|
||||||
import { Database } from '@kit/supabase/database';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { AcceptInvitationSchema } from '../../schema/accept-invitation.schema';
|
import { AcceptInvitationSchema } from '../../schema/accept-invitation.schema';
|
||||||
@@ -20,93 +17,91 @@ import { createAccountInvitationsService } from '../services/account-invitations
|
|||||||
import { createAccountPerSeatBillingService } from '../services/account-per-seat-billing.service';
|
import { createAccountPerSeatBillingService } from '../services/account-per-seat-billing.service';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates invitations for inviting members.
|
* @name createInvitationsAction
|
||||||
|
* @description Creates invitations for inviting members.
|
||||||
*/
|
*/
|
||||||
export async function createInvitationsAction(params: {
|
export const createInvitationsAction = enhanceAction(
|
||||||
accountSlug: string;
|
async (params) => {
|
||||||
invitations: z.infer<typeof InviteMembersSchema>['invitations'];
|
|
||||||
}) {
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
|
|
||||||
await assertSession(client);
|
|
||||||
|
|
||||||
const { invitations } = InviteMembersSchema.parse({
|
|
||||||
invitations: params.invitations,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Create the service
|
// Create the service
|
||||||
const service = createAccountInvitationsService(client);
|
const service = createAccountInvitationsService(client);
|
||||||
|
|
||||||
// send invitations
|
// send invitations
|
||||||
await service.sendInvitations({
|
await service.sendInvitations(params);
|
||||||
invitations,
|
|
||||||
accountSlug: params.accountSlug,
|
|
||||||
});
|
|
||||||
|
|
||||||
revalidateMemberPage();
|
revalidateMemberPage();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: InviteMembersSchema.and(
|
||||||
|
z.object({
|
||||||
|
accountSlug: z.string().min(1),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deletes an invitation specified by the invitation ID.
|
* @name deleteInvitationAction
|
||||||
*
|
* @description Deletes an invitation specified by the invitation ID.
|
||||||
* @param {Object} params - The parameters for the method.
|
|
||||||
* @param {string} params.invitationId - The ID of the invitation to be deleted.
|
|
||||||
*
|
|
||||||
* @return {Object} - The result of the delete operation.
|
|
||||||
*/
|
*/
|
||||||
export async function deleteInvitationAction(
|
export const deleteInvitationAction = enhanceAction(
|
||||||
params: z.infer<typeof DeleteInvitationSchema>,
|
async (data) => {
|
||||||
) {
|
|
||||||
const invitation = DeleteInvitationSchema.parse(params);
|
|
||||||
|
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const { data, error } = await client.auth.getUser();
|
|
||||||
|
|
||||||
if (error ?? !data.user) {
|
|
||||||
throw new Error(`Authentication required`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const service = createAccountInvitationsService(client);
|
const service = createAccountInvitationsService(client);
|
||||||
|
|
||||||
// Delete the invitation
|
// Delete the invitation
|
||||||
await service.deleteInvitation(invitation);
|
await service.deleteInvitation(data);
|
||||||
|
|
||||||
revalidateMemberPage();
|
revalidateMemberPage();
|
||||||
|
|
||||||
return { success: true };
|
return {
|
||||||
}
|
success: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema: DeleteInvitationSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export async function updateInvitationAction(
|
/**
|
||||||
params: z.infer<typeof UpdateInvitationSchema>,
|
* @name updateInvitationAction
|
||||||
) {
|
* @description Updates an invitation.
|
||||||
|
*/
|
||||||
|
export const updateInvitationAction = enhanceAction(
|
||||||
|
async (invitation) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const invitation = UpdateInvitationSchema.parse(params);
|
|
||||||
|
|
||||||
await assertSession(client);
|
|
||||||
|
|
||||||
const service = createAccountInvitationsService(client);
|
const service = createAccountInvitationsService(client);
|
||||||
|
|
||||||
await service.updateInvitation(invitation);
|
await service.updateInvitation(invitation);
|
||||||
|
|
||||||
revalidateMemberPage();
|
revalidateMemberPage();
|
||||||
|
|
||||||
return { success: true };
|
return {
|
||||||
}
|
success: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
{
|
||||||
|
schema: UpdateInvitationSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export async function acceptInvitationAction(data: FormData) {
|
/**
|
||||||
|
* @name acceptInvitationAction
|
||||||
|
* @description Accepts an invitation to join a team.
|
||||||
|
*/
|
||||||
|
export const acceptInvitationAction = enhanceAction(
|
||||||
|
async (data: FormData, user) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
|
|
||||||
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
|
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
|
||||||
Object.fromEntries(data),
|
Object.fromEntries(data),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Ensure the user is authenticated
|
|
||||||
const user = await assertSession(client);
|
|
||||||
|
|
||||||
// create the services
|
// create the services
|
||||||
const perSeatBillingService = createAccountPerSeatBillingService(client);
|
const perSeatBillingService = createAccountPerSeatBillingService(client);
|
||||||
const service = createAccountInvitationsService(client);
|
const service = createAccountInvitationsService(client);
|
||||||
@@ -129,16 +124,19 @@ export async function acceptInvitationAction(data: FormData) {
|
|||||||
await perSeatBillingService.increaseSeats(accountId);
|
await perSeatBillingService.increaseSeats(accountId);
|
||||||
|
|
||||||
return redirect(nextPath);
|
return redirect(nextPath);
|
||||||
}
|
},
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
export async function renewInvitationAction(
|
/**
|
||||||
params: z.infer<typeof RenewInvitationSchema>,
|
* @name renewInvitationAction
|
||||||
) {
|
* @description Renews an invitation.
|
||||||
|
*/
|
||||||
|
export const renewInvitationAction = enhanceAction(
|
||||||
|
async (params) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const { invitationId } = RenewInvitationSchema.parse(params);
|
const { invitationId } = RenewInvitationSchema.parse(params);
|
||||||
|
|
||||||
await assertSession(client);
|
|
||||||
|
|
||||||
const service = createAccountInvitationsService(client);
|
const service = createAccountInvitationsService(client);
|
||||||
|
|
||||||
// Renew the invitation
|
// Renew the invitation
|
||||||
@@ -149,17 +147,11 @@ export async function renewInvitationAction(
|
|||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
{
|
||||||
async function assertSession(client: SupabaseClient<Database>) {
|
schema: RenewInvitationSchema,
|
||||||
const { error, data } = await requireUser(client);
|
},
|
||||||
|
);
|
||||||
if (error) {
|
|
||||||
throw new Error(`Authentication required`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function revalidateMemberPage() {
|
function revalidateMemberPage() {
|
||||||
revalidatePath('/home/[account]/members', 'page');
|
revalidatePath('/home/[account]/members', 'page');
|
||||||
|
|||||||
@@ -2,11 +2,7 @@
|
|||||||
|
|
||||||
import { revalidatePath } from 'next/cache';
|
import { revalidatePath } from 'next/cache';
|
||||||
|
|
||||||
import { SupabaseClient } from '@supabase/supabase-js';
|
import { enhanceAction } from '@kit/next/actions';
|
||||||
|
|
||||||
import { z } from 'zod';
|
|
||||||
|
|
||||||
import { Database } from '@kit/supabase/database';
|
|
||||||
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
|
||||||
|
|
||||||
import { RemoveMemberSchema } from '../../schema/remove-member.schema';
|
import { RemoveMemberSchema } from '../../schema/remove-member.schema';
|
||||||
@@ -14,18 +10,13 @@ import { TransferOwnershipConfirmationSchema } from '../../schema/transfer-owner
|
|||||||
import { UpdateMemberRoleSchema } from '../../schema/update-member-role.schema';
|
import { UpdateMemberRoleSchema } from '../../schema/update-member-role.schema';
|
||||||
import { createAccountMembersService } from '../services/account-members.service';
|
import { createAccountMembersService } from '../services/account-members.service';
|
||||||
|
|
||||||
export async function removeMemberFromAccountAction(
|
/**
|
||||||
params: z.infer<typeof RemoveMemberSchema>,
|
* @name removeMemberFromAccountAction
|
||||||
) {
|
* @description Removes a member from an account.
|
||||||
|
*/
|
||||||
|
export const removeMemberFromAccountAction = enhanceAction(
|
||||||
|
async ({ accountId, userId }) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
const { data, error } = await client.auth.getUser();
|
|
||||||
|
|
||||||
if (error ?? !data.user) {
|
|
||||||
throw new Error(`Authentication required`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const { accountId, userId } = RemoveMemberSchema.parse(params);
|
|
||||||
|
|
||||||
const service = createAccountMembersService(client);
|
const service = createAccountMembersService(client);
|
||||||
|
|
||||||
await service.removeMemberFromAccount({
|
await service.removeMemberFromAccount({
|
||||||
@@ -37,48 +28,46 @@ export async function removeMemberFromAccountAction(
|
|||||||
revalidatePath('/home/[account]', 'layout');
|
revalidatePath('/home/[account]', 'layout');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: RemoveMemberSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export async function updateMemberRoleAction(
|
/**
|
||||||
params: z.infer<typeof UpdateMemberRoleSchema>,
|
* @name updateMemberRoleAction
|
||||||
) {
|
* @description Updates the role of a member in an account.
|
||||||
|
*/
|
||||||
|
export const updateMemberRoleAction = enhanceAction(
|
||||||
|
async (data) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
|
|
||||||
await assertSession(client);
|
|
||||||
|
|
||||||
const service = createAccountMembersService(client);
|
const service = createAccountMembersService(client);
|
||||||
const adminClient = getSupabaseServerActionClient({ admin: true });
|
const adminClient = getSupabaseServerActionClient({ admin: true });
|
||||||
|
|
||||||
// update the role of the member
|
// update the role of the member
|
||||||
await service.updateMemberRole(
|
await service.updateMemberRole(data, adminClient);
|
||||||
{
|
|
||||||
accountId: params.accountId,
|
|
||||||
userId: params.userId,
|
|
||||||
role: params.role,
|
|
||||||
},
|
|
||||||
adminClient,
|
|
||||||
);
|
|
||||||
|
|
||||||
// revalidate all pages that depend on the account
|
// revalidate all pages that depend on the account
|
||||||
revalidatePath('/home/[account]', 'layout');
|
revalidatePath('/home/[account]', 'layout');
|
||||||
|
|
||||||
return { success: true };
|
return { success: true };
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
schema: UpdateMemberRoleSchema,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
export async function transferOwnershipAction(
|
/**
|
||||||
params: z.infer<typeof TransferOwnershipConfirmationSchema>,
|
* @name transferOwnershipAction
|
||||||
) {
|
* @description Transfers the ownership of an account to another member.
|
||||||
|
*/
|
||||||
|
export const transferOwnershipAction = enhanceAction(
|
||||||
|
async (data) => {
|
||||||
const client = getSupabaseServerActionClient();
|
const client = getSupabaseServerActionClient();
|
||||||
|
|
||||||
const { accountId, userId } =
|
|
||||||
TransferOwnershipConfirmationSchema.parse(params);
|
|
||||||
|
|
||||||
// assert that the user is authenticated
|
|
||||||
await assertSession(client);
|
|
||||||
|
|
||||||
// assert that the user is the owner of the account
|
// assert that the user is the owner of the account
|
||||||
const { data: isOwner, error } = await client.rpc('is_account_owner', {
|
const { data: isOwner, error } = await client.rpc('is_account_owner', {
|
||||||
account_id: accountId,
|
account_id: data.accountId,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (error ?? !isOwner) {
|
if (error ?? !isOwner) {
|
||||||
@@ -93,14 +82,8 @@ export async function transferOwnershipAction(
|
|||||||
// so we proceed with the transfer of ownership with admin privileges
|
// so we proceed with the transfer of ownership with admin privileges
|
||||||
const adminClient = getSupabaseServerActionClient({ admin: true });
|
const adminClient = getSupabaseServerActionClient({ admin: true });
|
||||||
|
|
||||||
await service.transferOwnership(
|
// transfer the ownership of the account
|
||||||
{
|
await service.transferOwnership(data, adminClient);
|
||||||
accountId,
|
|
||||||
userId,
|
|
||||||
confirmation: params.confirmation,
|
|
||||||
},
|
|
||||||
adminClient,
|
|
||||||
);
|
|
||||||
|
|
||||||
// revalidate all pages that depend on the account
|
// revalidate all pages that depend on the account
|
||||||
revalidatePath('/home/[account]', 'layout');
|
revalidatePath('/home/[account]', 'layout');
|
||||||
@@ -108,12 +91,8 @@ export async function transferOwnershipAction(
|
|||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
}
|
},
|
||||||
|
{
|
||||||
async function assertSession(client: SupabaseClient<Database>) {
|
schema: TransferOwnershipConfirmationSchema,
|
||||||
const { data, error } = await client.auth.getUser();
|
},
|
||||||
|
);
|
||||||
if (error ?? !data.user) {
|
|
||||||
throw new Error(`Authentication required`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation';
|
|||||||
|
|
||||||
import type { User } from '@supabase/supabase-js';
|
import type { User } from '@supabase/supabase-js';
|
||||||
|
|
||||||
import { z } from 'zod';
|
import { ZodType, z } from 'zod';
|
||||||
|
|
||||||
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
|
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
|
||||||
import { requireUser } from '@kit/supabase/require-user';
|
import { requireUser } from '@kit/supabase/require-user';
|
||||||
@@ -12,6 +12,12 @@ import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-clie
|
|||||||
|
|
||||||
import { captureException, zodParseFactory } from '../utils';
|
import { captureException, zodParseFactory } from '../utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name IS_CAPTCHA_SETUP
|
||||||
|
* @description Check if the CAPTCHA is setup
|
||||||
|
*/
|
||||||
|
const IS_CAPTCHA_SETUP = !!process.env.CAPTCHA_SECRET_TOKEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @name enhanceAction
|
* @name enhanceAction
|
||||||
@@ -24,19 +30,21 @@ export function enhanceAction<
|
|||||||
auth?: boolean;
|
auth?: boolean;
|
||||||
captcha?: boolean;
|
captcha?: boolean;
|
||||||
captureException?: boolean;
|
captureException?: boolean;
|
||||||
schema: z.ZodType<
|
schema?: z.ZodType<
|
||||||
Config['captcha'] extends true ? Args & { captchaToken: string } : Args,
|
Config['captcha'] extends true ? Args & { captchaToken: string } : Args,
|
||||||
z.ZodTypeDef
|
z.ZodTypeDef
|
||||||
>;
|
>;
|
||||||
},
|
},
|
||||||
>(
|
>(
|
||||||
fn: (
|
fn: (
|
||||||
params: z.infer<Config['schema']>,
|
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
|
||||||
user: Config['auth'] extends false ? undefined : User,
|
user: Config['auth'] extends false ? undefined : User,
|
||||||
) => Response | Promise<Response>,
|
) => Response | Promise<Response>,
|
||||||
config: Config,
|
config: Config,
|
||||||
) {
|
) {
|
||||||
return async (params: z.infer<Config['schema']>) => {
|
return async (
|
||||||
|
params: Config['schema'] extends ZodType ? z.infer<Config['schema']> : Args,
|
||||||
|
) => {
|
||||||
type UserParam = Config['auth'] extends false ? undefined : User;
|
type UserParam = Config['auth'] extends false ? undefined : User;
|
||||||
|
|
||||||
const requireAuth = config.auth ?? true;
|
const requireAuth = config.auth ?? true;
|
||||||
@@ -56,11 +64,15 @@ export function enhanceAction<
|
|||||||
}
|
}
|
||||||
|
|
||||||
// validate the schema
|
// validate the schema
|
||||||
const parsed = zodParseFactory(config.schema);
|
const data = config.schema
|
||||||
const data = parsed(params);
|
? zodParseFactory(config.schema)(params)
|
||||||
|
: params;
|
||||||
|
|
||||||
|
// verify captcha unless it's explicitly disabled
|
||||||
// verify the captcha token if required
|
// verify the captcha token if required
|
||||||
if (config.captcha) {
|
const verifyCaptcha = config.captcha ?? IS_CAPTCHA_SETUP;
|
||||||
|
|
||||||
|
if (verifyCaptcha) {
|
||||||
const token = (data as Args & { captchaToken: string }).captchaToken;
|
const token = (data as Args & { captchaToken: string }).captchaToken;
|
||||||
|
|
||||||
// Verify the CAPTCHA token. It will throw an error if the token is invalid.
|
// Verify the CAPTCHA token. It will throw an error if the token is invalid.
|
||||||
|
|||||||
@@ -19,6 +19,12 @@ interface HandlerParams<Body> {
|
|||||||
body: Body;
|
body: Body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @name IS_CAPTCHA_SETUP
|
||||||
|
* @description Check if the CAPTCHA is setup
|
||||||
|
*/
|
||||||
|
const IS_CAPTCHA_SETUP = !!process.env.CAPTCHA_SECRET_TOKEN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enhanced route handler function.
|
* Enhanced route handler function.
|
||||||
*
|
*
|
||||||
@@ -63,8 +69,10 @@ export const enhanceRouteHandler = <
|
|||||||
* This function takes a request object as an argument and returns a response object.
|
* This function takes a request object as an argument and returns a response object.
|
||||||
*/
|
*/
|
||||||
return async function routeHandler(request: NextRequest) {
|
return async function routeHandler(request: NextRequest) {
|
||||||
// Verify the captcha token if required
|
const shouldVerifyCaptcha = params?.captcha ?? IS_CAPTCHA_SETUP;
|
||||||
if (params?.captcha) {
|
|
||||||
|
// Verify the captcha token if required and setup
|
||||||
|
if (shouldVerifyCaptcha) {
|
||||||
const token = captchaTokenGetter(request);
|
const token = captchaTokenGetter(request);
|
||||||
|
|
||||||
// If the captcha token is not provided, return a 400 response.
|
// If the captcha token is not provided, return a 400 response.
|
||||||
|
|||||||
6
pnpm-lock.yaml
generated
6
pnpm-lock.yaml
generated
@@ -561,6 +561,9 @@ importers:
|
|||||||
'@kit/monitoring':
|
'@kit/monitoring':
|
||||||
specifier: workspace:^
|
specifier: workspace:^
|
||||||
version: link:../../monitoring/api
|
version: link:../../monitoring/api
|
||||||
|
'@kit/next':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../next
|
||||||
'@kit/prettier-config':
|
'@kit/prettier-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../tooling/prettier
|
version: link:../../../tooling/prettier
|
||||||
@@ -772,6 +775,9 @@ importers:
|
|||||||
'@kit/monitoring':
|
'@kit/monitoring':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../monitoring/api
|
version: link:../../monitoring/api
|
||||||
|
'@kit/next':
|
||||||
|
specifier: workspace:^
|
||||||
|
version: link:../../next
|
||||||
'@kit/prettier-config':
|
'@kit/prettier-config':
|
||||||
specifier: workspace:*
|
specifier: workspace:*
|
||||||
version: link:../../../tooling/prettier
|
version: link:../../../tooling/prettier
|
||||||
|
|||||||
Reference in New Issue
Block a user