Refactor route handlers and CMS clients
Refactored the route handlers to use a new `enhanceRouteHandler` function for better control over request handlers and user authentication. CMS clients are now created using factory functions for better encapsulation and control over instance creation. Renamed `client.ts` in 'keystatic' to `keystatic-client.ts`.
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
import { getBillingEventHandlerService } from '@kit/billing-gateway';
|
import { getBillingEventHandlerService } from '@kit/billing-gateway';
|
||||||
|
import { enhanceRouteHandler } from '@kit/next/routes';
|
||||||
import { getLogger } from '@kit/shared/logger';
|
import { getLogger } from '@kit/shared/logger';
|
||||||
import { getSupabaseRouteHandlerClient } from '@kit/supabase/route-handler-client';
|
import { getSupabaseRouteHandlerClient } from '@kit/supabase/route-handler-client';
|
||||||
|
|
||||||
@@ -7,43 +8,42 @@ import billingConfig from '~/config/billing.config';
|
|||||||
/**
|
/**
|
||||||
* @description Handle the webhooks from Stripe related to checkouts
|
* @description Handle the webhooks from Stripe related to checkouts
|
||||||
*/
|
*/
|
||||||
export async function POST(request: Request) {
|
export const POST = enhanceRouteHandler(
|
||||||
const provider = billingConfig.provider;
|
async ({ request }) => {
|
||||||
const logger = await getLogger();
|
const provider = billingConfig.provider;
|
||||||
|
const logger = await getLogger();
|
||||||
|
|
||||||
const ctx = {
|
const ctx = {
|
||||||
name: 'billing.webhook',
|
name: 'billing.webhook',
|
||||||
provider,
|
provider,
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.info(ctx, `Received billing webhook. Processing...`);
|
logger.info(ctx, `Received billing webhook. Processing...`);
|
||||||
|
|
||||||
const supabaseClientProvider = () =>
|
const supabaseClientProvider = () =>
|
||||||
getSupabaseRouteHandlerClient({ admin: true });
|
getSupabaseRouteHandlerClient({ admin: true });
|
||||||
|
|
||||||
const service = await getBillingEventHandlerService(
|
const service = await getBillingEventHandlerService(
|
||||||
supabaseClientProvider,
|
supabaseClientProvider,
|
||||||
provider,
|
provider,
|
||||||
billingConfig,
|
billingConfig,
|
||||||
);
|
|
||||||
|
|
||||||
try {
|
|
||||||
await service.handleWebhookEvent(request);
|
|
||||||
|
|
||||||
logger.info(ctx, `Successfully processed billing webhook`);
|
|
||||||
|
|
||||||
return new Response('OK', { status: 200 });
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
|
|
||||||
logger.error(
|
|
||||||
{
|
|
||||||
...ctx,
|
|
||||||
error: JSON.stringify(error),
|
|
||||||
},
|
|
||||||
`Failed to process billing webhook`,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return new Response('Error', { status: 500 });
|
try {
|
||||||
}
|
await service.handleWebhookEvent(request);
|
||||||
}
|
|
||||||
|
logger.info(ctx, `Successfully processed billing webhook`);
|
||||||
|
|
||||||
|
return new Response('OK', { status: 200 });
|
||||||
|
} catch (error) {
|
||||||
|
logger.error(ctx, `Failed to process billing webhook`, error);
|
||||||
|
|
||||||
|
return new Response('Failed to process billing webhook', {
|
||||||
|
status: 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
auth: false,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,18 +1,21 @@
|
|||||||
import { getDatabaseWebhookHandlerService } from '@kit/database-webhooks';
|
import { getDatabaseWebhookHandlerService } from '@kit/database-webhooks';
|
||||||
|
import { enhanceRouteHandler } from '@kit/next/routes';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name POST
|
* @name POST
|
||||||
* @description POST handler for the webhook route that handles the webhook event
|
* @description POST handler for the webhook route that handles the webhook event
|
||||||
*/
|
*/
|
||||||
export async function POST(request: Request) {
|
export const POST = enhanceRouteHandler(async ({ request }) => {
|
||||||
const service = getDatabaseWebhookHandlerService();
|
const service = getDatabaseWebhookHandlerService();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// handle the webhook event
|
// handle the webhook event
|
||||||
await service.handleWebhook(request);
|
await service.handleWebhook(request);
|
||||||
|
|
||||||
|
// return a successful response
|
||||||
return new Response(null, { status: 200 });
|
return new Response(null, { status: 200 });
|
||||||
} catch {
|
} catch (error) {
|
||||||
|
// return an error response
|
||||||
return new Response(null, { status: 500 });
|
return new Response(null, { status: 500 });
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ export class StripeWebhookHandlerService
|
|||||||
|
|
||||||
const stripe = await this.loadStripe();
|
const stripe = await this.loadStripe();
|
||||||
|
|
||||||
const event = stripe.webhooks.constructEvent(
|
const event = await stripe.webhooks.constructEventAsync(
|
||||||
body,
|
body,
|
||||||
signature,
|
signature,
|
||||||
webhooksSecret,
|
webhooksSecret,
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ async function cmsClientFactory(type: CmsType) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function getWordpressClient() {
|
async function getWordpressClient() {
|
||||||
const { WordpressClient } = await import('../../wordpress/src/wp-client');
|
const { createWordpressClient } = await import(
|
||||||
|
'../../wordpress/src/wp-client'
|
||||||
|
);
|
||||||
|
|
||||||
return new WordpressClient();
|
return createWordpressClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getKeystaticClient() {
|
async function getKeystaticClient() {
|
||||||
@@ -38,9 +40,11 @@ async function getKeystaticClient() {
|
|||||||
process.env.NEXT_RUNTIME === 'nodejs' ||
|
process.env.NEXT_RUNTIME === 'nodejs' ||
|
||||||
process.env.KEYSTATIC_STORAGE_KIND !== 'local'
|
process.env.KEYSTATIC_STORAGE_KIND !== 'local'
|
||||||
) {
|
) {
|
||||||
const { KeystaticClient } = await import('../../keystatic/src/client');
|
const { createKeystaticClient } = await import(
|
||||||
|
'../../keystatic/src/keystatic-client'
|
||||||
|
);
|
||||||
|
|
||||||
return new KeystaticClient();
|
return createKeystaticClient();
|
||||||
}
|
}
|
||||||
|
|
||||||
console.error(
|
console.error(
|
||||||
|
|||||||
@@ -1,2 +1,2 @@
|
|||||||
export * from './client';
|
export * from './keystatic-client';
|
||||||
export * from './content-renderer';
|
export * from './content-renderer';
|
||||||
|
|||||||
@@ -3,7 +3,11 @@ import { Cms, CmsClient } from '@kit/cms';
|
|||||||
import { createKeystaticReader } from './create-reader';
|
import { createKeystaticReader } from './create-reader';
|
||||||
import { PostEntryProps } from './keystatic.config';
|
import { PostEntryProps } from './keystatic.config';
|
||||||
|
|
||||||
export class KeystaticClient implements CmsClient {
|
export function createKeystaticClient() {
|
||||||
|
return new KeystaticClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
class KeystaticClient implements CmsClient {
|
||||||
async getContentItems(options: Cms.GetContentItemsOptions) {
|
async getContentItems(options: Cms.GetContentItemsOptions) {
|
||||||
const reader = await createKeystaticReader();
|
const reader = await createKeystaticReader();
|
||||||
|
|
||||||
@@ -8,17 +8,19 @@ import { Cms, CmsClient } from '@kit/cms';
|
|||||||
|
|
||||||
import GetTagsOptions = Cms.GetTagsOptions;
|
import GetTagsOptions = Cms.GetTagsOptions;
|
||||||
|
|
||||||
|
export function createWordpressClient(
|
||||||
|
apiUrl = process.env.WORDPRESS_API_URL as string,
|
||||||
|
) {
|
||||||
|
return new WordpressClient(apiUrl);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @name WordpressClient
|
* @name WordpressClient
|
||||||
* @description Represents a client for interacting with a Wordpress CMS.
|
* @description Represents a client for interacting with a Wordpress CMS.
|
||||||
* Implements the CmsClient interface.
|
* Implements the CmsClient interface.
|
||||||
*/
|
*/
|
||||||
export class WordpressClient implements CmsClient {
|
class WordpressClient implements CmsClient {
|
||||||
private readonly apiUrl: string;
|
constructor(private readonly apiUrl: string) {}
|
||||||
|
|
||||||
constructor(apiUrl = process.env.WORDPRESS_API_URL as string) {
|
|
||||||
this.apiUrl = apiUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves content items from a CMS based on the provided options.
|
* Retrieves content items from a CMS based on the provided options.
|
||||||
|
|||||||
@@ -14,9 +14,19 @@ import { getSupabaseRouteHandlerClient } from '@kit/supabase/route-handler-clien
|
|||||||
|
|
||||||
import { captureException, zodParseFactory } from '../utils';
|
import { captureException, zodParseFactory } from '../utils';
|
||||||
|
|
||||||
interface HandlerParams<Body> {
|
interface Config<Schema> {
|
||||||
|
auth?: boolean;
|
||||||
|
captcha?: boolean;
|
||||||
|
captureException?: boolean;
|
||||||
|
schema?: Schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface HandlerParams<
|
||||||
|
Body extends object,
|
||||||
|
RequireAuth extends boolean | undefined,
|
||||||
|
> {
|
||||||
request: NextRequest;
|
request: NextRequest;
|
||||||
user: User;
|
user: RequireAuth extends false ? undefined : User;
|
||||||
body: Body;
|
body: Body;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,22 +51,21 @@ interface HandlerParams<Body> {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
export const enhanceRouteHandler = <
|
export const enhanceRouteHandler = <
|
||||||
Body,
|
Body extends object,
|
||||||
Schema extends z.ZodType<Body, z.ZodTypeDef>,
|
Schema extends z.ZodType<Body, z.ZodTypeDef>,
|
||||||
|
Params extends Config<Schema> = Config<Schema>,
|
||||||
>(
|
>(
|
||||||
// Route handler function
|
// Route handler function
|
||||||
handler:
|
handler:
|
||||||
| ((params: HandlerParams<z.infer<Schema>>) => NextResponse | Response)
|
|
||||||
| ((
|
| ((
|
||||||
params: HandlerParams<z.infer<Schema>>,
|
params: HandlerParams<z.infer<Schema>, Params['auth']>,
|
||||||
|
) => NextResponse | Response)
|
||||||
|
| ((
|
||||||
|
params: HandlerParams<z.infer<Schema>, Params['auth']>,
|
||||||
) => Promise<NextResponse | Response>),
|
) => Promise<NextResponse | Response>),
|
||||||
|
|
||||||
// Parameters object
|
// Parameters object
|
||||||
params?: {
|
params?: Params,
|
||||||
captcha?: boolean;
|
|
||||||
captureException?: boolean;
|
|
||||||
schema?: Schema;
|
|
||||||
},
|
|
||||||
) => {
|
) => {
|
||||||
/**
|
/**
|
||||||
* Route handler function.
|
* Route handler function.
|
||||||
@@ -64,6 +73,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) {
|
||||||
|
type UserParam = Params['auth'] extends false ? undefined : User;
|
||||||
|
|
||||||
|
let user: UserParam = undefined as UserParam;
|
||||||
|
|
||||||
// Check if the captcha token should be verified
|
// Check if the captcha token should be verified
|
||||||
const shouldVerifyCaptcha = params?.captcha ?? false;
|
const shouldVerifyCaptcha = params?.captcha ?? false;
|
||||||
|
|
||||||
@@ -80,15 +93,22 @@ export const enhanceRouteHandler = <
|
|||||||
}
|
}
|
||||||
|
|
||||||
const client = getSupabaseRouteHandlerClient();
|
const client = getSupabaseRouteHandlerClient();
|
||||||
const auth = await requireUser(client);
|
|
||||||
|
|
||||||
// If the user is not authenticated, redirect to the specified URL.
|
const shouldVerifyAuth = params?.auth ?? true;
|
||||||
if (auth.error) {
|
|
||||||
return redirect(auth.redirectTo);
|
// Check if the user should be authenticated
|
||||||
|
if (shouldVerifyAuth) {
|
||||||
|
// Get the authenticated user
|
||||||
|
const auth = await requireUser(client);
|
||||||
|
|
||||||
|
// If the user is not authenticated, redirect to the specified URL.
|
||||||
|
if (auth.error) {
|
||||||
|
return redirect(auth.redirectTo);
|
||||||
|
}
|
||||||
|
|
||||||
|
user = auth.data as UserParam;
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = auth.data;
|
|
||||||
|
|
||||||
// clone the request to read the body
|
// clone the request to read the body
|
||||||
// so that we can pass it to the handler safely
|
// so that we can pass it to the handler safely
|
||||||
let body = await request.clone().json();
|
let body = await request.clone().json();
|
||||||
|
|||||||
Reference in New Issue
Block a user