Refactored Supabase Clients using the new recommended approach by Supabase by centralizing all clients around one single implementation. (#51)

The previous clients have been marked as deprecated and will be removed at some point.
This commit is contained in:
Giancarlo Buomprisco
2024-08-14 17:13:59 +08:00
committed by GitHub
parent 2f0c4b4ae3
commit ba6e649461
41 changed files with 209 additions and 168 deletions

View File

@@ -6,15 +6,15 @@ import { redirect } from 'next/navigation';
import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { DeletePersonalAccountSchema } from '../schema/delete-personal-account.schema';
import { createDeletePersonalAccountService } from './services/delete-personal-account.service';
const emailSettings = getEmailSettingsFromEnvironment();
export async function refreshAuthSession() {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
await client.auth.refreshSession();
@@ -32,7 +32,7 @@ export const deletePersonalAccountAction = enhanceAction(
throw new Error('Invalid form data');
}
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
// create a new instance of the personal accounts service
const service = createDeletePersonalAccountService();
@@ -42,7 +42,7 @@ export const deletePersonalAccountAction = enhanceAction(
// delete the user's account and cancel all subscriptions
await service.deletePersonalAccount({
adminClient: getSupabaseServerActionClient({ admin: true }),
adminClient: getSupabaseServerAdminClient(),
userId: user.id,
userEmail: user.email ?? null,
emailSettings,

View File

@@ -7,7 +7,7 @@ import {
} from 'lucide-react';
import { Database } from '@kit/supabase/database';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { Alert, AlertDescription, AlertTitle } from '@kit/ui/alert';
import { AppBreadcrumbs } from '@kit/ui/app-breadcrumbs';
import { Badge } from '@kit/ui/badge';
@@ -49,9 +49,7 @@ export function AdminAccountPage(props: {
}
async function PersonalAccountPage(props: { account: Account }) {
const client = getSupabaseServerComponentClient({
admin: true,
});
const client = getSupabaseServerAdminClient();
const memberships = await getMemberships(props.account.id);
const { data, error } = await client.auth.admin.getUserById(props.account.id);
@@ -196,9 +194,7 @@ async function TeamAccountPage(props: {
}
async function SubscriptionsTable(props: { accountId: string }) {
const client = getSupabaseServerComponentClient({
admin: true,
});
const client = getSupabaseServerAdminClient();
const { data: subscription, error } = await client
.from('subscriptions')
@@ -345,9 +341,7 @@ async function SubscriptionsTable(props: { accountId: string }) {
}
async function getMemberships(userId: string) {
const client = getSupabaseServerComponentClient({
admin: true,
});
const client = getSupabaseServerAdminClient();
const memberships = await client
.from('accounts_memberships')
@@ -370,9 +364,7 @@ async function getMemberships(userId: string) {
}
async function getMembers(accountSlug: string) {
const client = getSupabaseServerComponentClient({
admin: true,
});
const client = getSupabaseServerAdminClient();
const members = await client.rpc('get_account_members', {
account_slug: accountSlug,

View File

@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { isSuperAdmin } from '../lib/server/utils/is-super-admin';
@@ -15,7 +15,7 @@ export function AdminGuard<Params extends object>(
Component: LayoutOrPageComponent<Params>,
) {
return async function AdminGuardServerComponentWrapper(params: Params) {
const client = getSupabaseServerComponentClient();
const client = getSupabaseServerClient();
const isUserSuperAdmin = await isSuperAdmin(client);
// if the user is not a super-admin, we redirect to a 404

View File

@@ -5,7 +5,8 @@ import { redirect } from 'next/navigation';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import {
BanUserSchema,
@@ -150,14 +151,14 @@ export const deleteAccountAction = adminAction(
);
function getAdminAuthService() {
const client = getSupabaseServerActionClient();
const adminClient = getSupabaseServerActionClient({ admin: true });
const client = getSupabaseServerClient();
const adminClient = getSupabaseServerAdminClient();
return createAdminAuthUserService(client, adminClient);
}
function getAdminAccountsService() {
const adminClient = getSupabaseServerActionClient({ admin: true });
const adminClient = getSupabaseServerAdminClient();
return createAdminAccountsService(adminClient);
}

View File

@@ -2,7 +2,7 @@ import 'server-only';
import { cache } from 'react';
import { getSupabaseServerComponentClient } from '@kit/supabase/server-component-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { createAdminDashboardService } from '../services/admin-dashboard.service';
@@ -14,7 +14,7 @@ import { createAdminDashboardService } from '../services/admin-dashboard.service
export const loadAdminDashboard = cache(adminDashboardLoader);
function adminDashboardLoader() {
const client = getSupabaseServerComponentClient({ admin: true });
const client = getSupabaseServerClient();
const service = createAdminDashboardService(client);
return service.getDashboardData();

View File

@@ -1,6 +1,6 @@
import { notFound } from 'next/navigation';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { isSuperAdmin } from './is-super-admin';
@@ -11,7 +11,7 @@ import { isSuperAdmin } from './is-super-admin';
*/
export function adminAction<Args, Response>(fn: (params: Args) => Response) {
return async (params: Args) => {
const isAdmin = await isSuperAdmin(getSupabaseServerActionClient());
const isAdmin = await isSuperAdmin(getSupabaseServerClient());
if (!isAdmin) {
notFound();

View File

@@ -4,7 +4,7 @@ import { redirect } from 'next/navigation';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { CreateTeamSchema } from '../../schema/create-team.schema';
import { createCreateTeamAccountService } from '../services/create-team-account.service';
@@ -12,7 +12,7 @@ import { createCreateTeamAccountService } from '../services/create-team-account.
export const createTeamAccountAction = enhanceAction(
async ({ name }, user) => {
const logger = await getLogger();
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const service = createCreateTeamAccountService(client);
const ctx = {

View File

@@ -4,7 +4,7 @@ import { revalidatePath } from 'next/cache';
import { redirect } from 'next/navigation';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { LeaveTeamAccountSchema } from '../../schema/leave-team-account.schema';
import { createLeaveTeamAccountService } from '../services/leave-team-account.service';
@@ -15,7 +15,7 @@ export const leaveTeamAccountAction = enhanceAction(
const params = LeaveTeamAccountSchema.parse(body);
const service = createLeaveTeamAccountService(
getSupabaseServerActionClient({ admin: true }),
getSupabaseServerAdminClient(),
);
await service.leaveTeamAccount({

View File

@@ -4,13 +4,13 @@ import { redirect } from 'next/navigation';
import { enhanceAction } from '@kit/next/actions';
import { getLogger } from '@kit/shared/logger';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { UpdateTeamNameSchema } from '../../schema/update-team-name.schema';
export const updateTeamAccountName = enhanceAction(
async (params) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const logger = await getLogger();
const { name, path, slug } = params;

View File

@@ -6,7 +6,8 @@ import { redirect } from 'next/navigation';
import { z } from 'zod';
import { enhanceAction } from '@kit/next/actions';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerAdminClient } from '@kit/supabase/server-admin-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { AcceptInvitationSchema } from '../../schema/accept-invitation.schema';
import { DeleteInvitationSchema } from '../../schema/delete-invitation.schema';
@@ -22,7 +23,7 @@ import { createAccountPerSeatBillingService } from '../services/account-per-seat
*/
export const createInvitationsAction = enhanceAction(
async (params) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
// Create the service
const service = createAccountInvitationsService(client);
@@ -51,7 +52,7 @@ export const createInvitationsAction = enhanceAction(
*/
export const deleteInvitationAction = enhanceAction(
async (data) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const service = createAccountInvitationsService(client);
// Delete the invitation
@@ -74,7 +75,7 @@ export const deleteInvitationAction = enhanceAction(
*/
export const updateInvitationAction = enhanceAction(
async (invitation) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const service = createAccountInvitationsService(client);
await service.updateInvitation(invitation);
@@ -96,7 +97,7 @@ export const updateInvitationAction = enhanceAction(
*/
export const acceptInvitationAction = enhanceAction(
async (data: FormData, user) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const { inviteToken, nextPath } = AcceptInvitationSchema.parse(
Object.fromEntries(data),
@@ -106,14 +107,14 @@ export const acceptInvitationAction = enhanceAction(
const perSeatBillingService = createAccountPerSeatBillingService(client);
const service = createAccountInvitationsService(client);
// use admin client to accept invitation
const adminClient = getSupabaseServerAdminClient();
// Accept the invitation
const accountId = await service.acceptInvitationToTeam(
getSupabaseServerActionClient({ admin: true }),
{
inviteToken,
userId: user.id,
},
);
const accountId = await service.acceptInvitationToTeam(adminClient, {
inviteToken,
userId: user.id,
});
// If the account ID is not present, throw an error
if (!accountId) {
@@ -134,7 +135,7 @@ export const acceptInvitationAction = enhanceAction(
*/
export const renewInvitationAction = enhanceAction(
async (params) => {
const client = getSupabaseServerActionClient();
const client = getSupabaseServerClient();
const { invitationId } = RenewInvitationSchema.parse(params);
const service = createAccountInvitationsService(client);

View File

@@ -9,7 +9,7 @@ import { ZodType, z } from 'zod';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseServerActionClient } from '@kit/supabase/server-actions-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { captureException, zodParseFactory } from '../utils';
@@ -63,7 +63,7 @@ export function enhanceAction<
// verify the user is authenticated if required
if (requireAuth) {
// verify the user is authenticated if required
const auth = await requireUser(getSupabaseServerActionClient());
const auth = await requireUser(getSupabaseServerClient());
// If the user is not authenticated, redirect to the specified URL.
if (!auth.data) {

View File

@@ -10,7 +10,7 @@ import { z } from 'zod';
import { verifyCaptchaToken } from '@kit/auth/captcha/server';
import { requireUser } from '@kit/supabase/require-user';
import { getSupabaseRouteHandlerClient } from '@kit/supabase/route-handler-client';
import { getSupabaseServerClient } from '@kit/supabase/server-client';
import { captureException, zodParseFactory } from '../utils';
@@ -96,7 +96,7 @@ export const enhanceRouteHandler = <
}
}
const client = getSupabaseRouteHandlerClient();
const client = getSupabaseServerClient();
const shouldVerifyAuth = params?.auth ?? true;

View File

@@ -10,11 +10,13 @@
},
"prettier": "@kit/prettier-config",
"exports": {
"./middleware-client": "./src/clients/middleware.client.ts",
"./server-actions-client": "./src/clients/server-actions.client.ts",
"./route-handler-client": "./src/clients/route-handler.client.ts",
"./server-component-client": "./src/clients/server-component.client.ts",
"./browser-client": "./src/clients/browser.client.ts",
"./server-client": "./src/clients/server-client.ts",
"./server-admin-client": "./src/clients/server-admin-client.ts",
"./middleware-client": "./src/clients/middleware-client.ts",
"./server-actions-client": "./src/clients/server-actions-client.ts",
"./route-handler-client": "./src/clients/route-handler-client.ts",
"./server-component-client": "./src/clients/server-component-client.ts",
"./browser-client": "./src/clients/browser-client.ts",
"./check-requires-mfa": "./src/check-requires-mfa.ts",
"./require-user": "./src/require-user.ts",
"./hooks/*": "./src/hooks/*.ts",

View File

@@ -0,0 +1,38 @@
import 'server-only';
import { type NextRequest, NextResponse } from 'next/server';
import { createServerClient } from '@supabase/ssr';
import { Database } from '../database.types';
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
/**
* Creates a middleware client for Supabase.
*
* @param {NextRequest} request - The Next.js request object.
* @param {NextResponse} response - The Next.js response object.
*/
export function createMiddlewareClient<GenericSchema = Database>(
request: NextRequest,
response: NextResponse,
) {
const keys = getSupabaseClientKeys();
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
cookies: {
getAll() {
return request.cookies.getAll();
},
setAll(cookiesToSet) {
cookiesToSet.forEach(({ name, value }) =>
request.cookies.set(name, value),
);
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options),
);
},
},
});
}

View File

@@ -1,65 +0,0 @@
import { type NextRequest, NextResponse } from 'next/server';
import { type CookieOptions, createServerClient } from '@supabase/ssr';
import { Database } from '../database.types';
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
/**
* Creates a middleware client for Supabase.
*
* @param {NextRequest} request - The Next.js request object.
* @param {NextResponse} response - The Next.js response object.
*/
export function createMiddlewareClient<GenericSchema = Database>(
request: NextRequest,
response: NextResponse,
) {
const keys = getSupabaseClientKeys();
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
cookies: getCookieStrategy(request, response),
});
}
function getCookieStrategy(request: NextRequest, response: NextResponse) {
return {
set: (name: string, value: string, options: CookieOptions) => {
request.cookies.set({ name, value, ...options });
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value,
...options,
});
},
get: (name: string) => {
return request.cookies.get(name)?.value;
},
remove: (name: string, options: CookieOptions) => {
request.cookies.set({
name,
value: '',
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value: '',
...options,
});
},
};
}

View File

@@ -20,6 +20,7 @@ const keys = getSupabaseClientKeys();
/**
* @name getSupabaseRouteHandlerClient
* @deprecated Use `getSupabaseServerClient` instead.
* @description Get a Supabase client for use in the Route Handler Routes
*/
export function getSupabaseRouteHandlerClient<GenericSchema = Database>(

View File

@@ -25,6 +25,11 @@ function createServerSupabaseClient<
});
}
/**
* @name getSupabaseServerComponentClient
* @deprecated Use `getSupabaseServerClient` instead.
* @param params
*/
export function getSupabaseServerActionClient<
GenericSchema extends Database = Database,
>(params?: { admin: boolean }) {

View File

@@ -0,0 +1,31 @@
import 'server-only';
import { unstable_noStore as noStore } from 'next/cache';
import { createClient } from '@supabase/supabase-js';
import { Database } from '../database.types';
import {
getServiceRoleKey,
warnServiceRoleKeyUsage,
} from '../get-service-role-key';
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
/**
* @name getSupabaseServerAdminClient
* @description Get a Supabase client for use in the Server with admin access to the database.
*/
export function getSupabaseServerAdminClient<GenericSchema = Database>() {
noStore();
warnServiceRoleKeyUsage();
const url = getSupabaseClientKeys().url;
return createClient<GenericSchema>(url, getServiceRoleKey(), {
auth: {
persistSession: false,
detectSessionInUrl: false,
autoRefreshToken: false,
},
});
}

View File

@@ -0,0 +1,39 @@
import 'server-only';
import { unstable_noStore as noStore } from 'next/cache';
import { cookies } from 'next/headers';
import { createServerClient } from '@supabase/ssr';
import { Database } from '../database.types';
import { getSupabaseClientKeys } from '../get-supabase-client-keys';
/**
* @name getSupabaseServerClient
* @description Creates a Supabase client for use in the Server.
*/
export function getSupabaseServerClient<GenericSchema = Database>() {
noStore();
const cookieStore = cookies();
const keys = getSupabaseClientKeys();
return createServerClient<GenericSchema>(keys.url, keys.anonKey, {
cookies: {
getAll() {
return cookieStore.getAll();
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options),
);
} catch {
// The `setAll` method was called from a Server Component.
// This can be ignored if you have middleware refreshing
// user sessions.
}
},
},
});
}

View File

@@ -1,6 +1,6 @@
import { useMemo } from 'react';
import { getSupabaseBrowserClient } from '../clients/browser.client';
import { getSupabaseBrowserClient } from '../clients/browser-client';
export function useSupabase() {
return useMemo(() => getSupabaseBrowserClient(), []);