diff --git a/apps/api/src/controllers/Actions.ts b/apps/api/src/controllers/Actions.ts
index 55d5ea2..5b3272b 100644
--- a/apps/api/src/controllers/Actions.ts
+++ b/apps/api/src/controllers/Actions.ts
@@ -1,6 +1,6 @@
import {Controller, Middleware, Post} from '@overnightjs/core';
import {ActionSchemas} from '@plunk/shared';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requirePublicKey, requireSecretKey} from '../middleware/auth.js';
@@ -10,6 +10,7 @@ import {DomainService} from '../services/DomainService.js';
import {EmailService} from '../services/EmailService.js';
import {EventService} from '../services/EventService.js';
import {NotFound} from '../exceptions/index.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
/**
* Public API Actions Controller
@@ -47,7 +48,8 @@ export class Actions {
*/
@Post('track')
@Middleware([requirePublicKey])
- public async track(req: Request, res: Response) {
+ @CatchAsync
+ public async track(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
// Zod validation - errors automatically handled by global error handler
@@ -152,7 +154,8 @@ export class Actions {
*/
@Post('send')
@Middleware([requireSecretKey])
- public async send(req: Request, res: Response) {
+ @CatchAsync
+ public async send(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
// Zod validation - errors automatically handled by global error handler
diff --git a/apps/api/src/controllers/Activity.ts b/apps/api/src/controllers/Activity.ts
index 1810eb9..2c0b61b 100644
--- a/apps/api/src/controllers/Activity.ts
+++ b/apps/api/src/controllers/Activity.ts
@@ -1,9 +1,10 @@
import {Controller, Get, Middleware} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {ActivityService, ActivityType} from '../services/ActivityService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('activity')
export class Activity {
@@ -21,7 +22,8 @@ export class Activity {
*/
@Get('')
@Middleware([requireAuth])
- public async getActivities(req: Request, res: Response) {
+ @CatchAsync
+ public async getActivities(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
const cursor = req.query.cursor as string | undefined;
@@ -61,7 +63,8 @@ export class Activity {
*/
@Get('stats')
@Middleware([requireAuth])
- public async getStats(req: Request, res: Response) {
+ @CatchAsync
+ public async getStats(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
@@ -80,7 +83,8 @@ export class Activity {
*/
@Get('recent-count')
@Middleware([requireAuth])
- public async getRecentCount(req: Request, res: Response) {
+ @CatchAsync
+ public async getRecentCount(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const minutes = Math.min(parseInt(req.query.minutes as string) || 5, 60); // Max 60 minutes
@@ -95,7 +99,8 @@ export class Activity {
*/
@Get('types')
@Middleware([requireAuth])
- public async getTypes(_req: Request, res: Response) {
+ @CatchAsync
+ public async getTypes(_req: Request, res: Response, next: NextFunction) {
const types = Object.values(ActivityType);
return res.status(200).json({types});
}
@@ -110,7 +115,8 @@ export class Activity {
*/
@Get('upcoming')
@Middleware([requireAuth])
- public async getUpcoming(req: Request, res: Response) {
+ @CatchAsync
+ public async getUpcoming(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
const daysAhead = Math.min(parseInt(req.query.daysAhead as string) || 30, 90);
diff --git a/apps/api/src/controllers/Analytics.ts b/apps/api/src/controllers/Analytics.ts
index bd4b91e..bd932db 100644
--- a/apps/api/src/controllers/Analytics.ts
+++ b/apps/api/src/controllers/Analytics.ts
@@ -1,9 +1,10 @@
import {Controller, Get, Middleware} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {AnalyticsService} from '../services/AnalyticsService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('analytics')
export class Analytics {
@@ -19,7 +20,8 @@ export class Analytics {
*/
@Get('timeseries')
@Middleware([requireAuth])
- public async getTimeSeries(req: Request, res: Response) {
+ @CatchAsync
+ public async getTimeSeries(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
@@ -40,7 +42,8 @@ export class Analytics {
*/
@Get('top-campaigns')
@Middleware([requireAuth])
- public async getTopCampaigns(req: Request, res: Response) {
+ @CatchAsync
+ public async getTopCampaigns(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const limit = Math.min(parseInt(req.query.limit as string) || 10, 50);
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
diff --git a/apps/api/src/controllers/Auth.ts b/apps/api/src/controllers/Auth.ts
index 3eb77f0..b49c32b 100644
--- a/apps/api/src/controllers/Auth.ts
+++ b/apps/api/src/controllers/Auth.ts
@@ -1,6 +1,6 @@
import {Controller, Get, Post} from '@overnightjs/core';
import {AuthenticationSchemas} from '@plunk/shared';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {GITHUB_OAUTH_ENABLED, GOOGLE_OAUTH_ENABLED} from '../app/constants.js';
import {prisma} from '../database/prisma.js';
@@ -9,11 +9,13 @@ import {jwt} from '../middleware/auth.js';
import {AuthService} from '../services/AuthService.js';
import {UserService} from '../services/UserService.js';
import {Keys} from '../services/keys.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('auth')
export class Auth {
@Post('login')
- public async login(req: Request, res: Response) {
+ @CatchAsync
+ public async login(req: Request, res: Response, next: NextFunction) {
const {email, password} = AuthenticationSchemas.login.parse(req.body);
const user = await UserService.email(email);
@@ -43,7 +45,8 @@ export class Auth {
}
@Post('signup')
- public async signup(req: Request, res: Response) {
+ @CatchAsync
+ public async signup(req: Request, res: Response, next: NextFunction) {
const {email, password} = AuthenticationSchemas.login.parse(req.body);
const user = await UserService.email(email);
diff --git a/apps/api/src/controllers/Campaigns.ts b/apps/api/src/controllers/Campaigns.ts
index b57df9c..e487231 100644
--- a/apps/api/src/controllers/Campaigns.ts
+++ b/apps/api/src/controllers/Campaigns.ts
@@ -1,7 +1,7 @@
import {Controller, Delete, Get, Middleware, Post, Put} from '@overnightjs/core';
import {CampaignAudienceType, CampaignStatus} from '@plunk/db';
import {CampaignSchemas} from '@plunk/shared';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {HttpException} from '../exceptions/index.js';
import type {AuthResponse} from '../middleware/auth.js';
@@ -9,6 +9,7 @@ import {requireAuth} from '../middleware/auth.js';
import {CampaignService} from '../services/CampaignService.js';
import {DomainService} from '../services/DomainService.js';
import {type SegmentFilter} from '../services/SegmentService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('campaigns')
export class Campaigns {
@@ -18,7 +19,8 @@ export class Campaigns {
*/
@Post('')
@Middleware([requireAuth])
- private async create(req: Request, res: Response) {
+ @CatchAsync
+ private async create(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceFilter, segmentId} =
CampaignSchemas.create.parse(req.body);
@@ -60,7 +62,8 @@ export class Campaigns {
*/
@Get('')
@Middleware([requireAuth])
- private async list(req: Request, res: Response) {
+ @CatchAsync
+ private async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const status = req.query.status as CampaignStatus | undefined;
const page = parseInt(req.query.page as string) || 1;
@@ -92,7 +95,8 @@ export class Campaigns {
*/
@Get(':id')
@Middleware([requireAuth])
- private async get(req: Request, res: Response) {
+ @CatchAsync
+ private async get(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -110,7 +114,8 @@ export class Campaigns {
*/
@Put(':id')
@Middleware([requireAuth])
- private async update(req: Request, res: Response) {
+ @CatchAsync
+ private async update(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceFilter, segmentId} =
@@ -155,7 +160,8 @@ export class Campaigns {
*/
@Delete(':id')
@Middleware([requireAuth])
- private async delete(req: Request, res: Response) {
+ @CatchAsync
+ private async delete(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -173,7 +179,8 @@ export class Campaigns {
*/
@Post(':id/duplicate')
@Middleware([requireAuth])
- private async duplicate(req: Request, res: Response) {
+ @CatchAsync
+ private async duplicate(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -192,7 +199,8 @@ export class Campaigns {
*/
@Post(':id/send')
@Middleware([requireAuth])
- private async send(req: Request, res: Response) {
+ @CatchAsync
+ private async send(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
const scheduledFor = req.body?.scheduledFor;
@@ -222,7 +230,8 @@ export class Campaigns {
*/
@Post(':id/cancel')
@Middleware([requireAuth])
- private async cancel(req: Request, res: Response) {
+ @CatchAsync
+ private async cancel(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -241,7 +250,8 @@ export class Campaigns {
*/
@Get(':id/stats')
@Middleware([requireAuth])
- private async stats(req: Request, res: Response) {
+ @CatchAsync
+ private async stats(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -259,7 +269,8 @@ export class Campaigns {
*/
@Post(':id/test')
@Middleware([requireAuth])
- private async sendTest(req: Request, res: Response) {
+ @CatchAsync
+ private async sendTest(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
const {email} = CampaignSchemas.sendTest.parse(req.body);
diff --git a/apps/api/src/controllers/Contacts.ts b/apps/api/src/controllers/Contacts.ts
index 7450085..714aeec 100644
--- a/apps/api/src/controllers/Contacts.ts
+++ b/apps/api/src/controllers/Contacts.ts
@@ -1,11 +1,12 @@
import {Controller, Delete, Get, Middleware, Patch, Post} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import multer from 'multer';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {ContactService} from '../services/ContactService.js';
import {QueueService} from '../services/QueueService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
// Configure multer for file uploads (memory storage)
const upload = multer({
@@ -31,7 +32,8 @@ export class Contacts {
*/
@Get('')
@Middleware([requireAuth])
- public async list(req: Request, res: Response) {
+ @CatchAsync
+ public async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
const cursor = req.query.cursor as string | undefined;
@@ -48,7 +50,8 @@ export class Contacts {
*/
@Get('fields')
@Middleware([requireAuth])
- public async getAvailableFields(req: Request, res: Response) {
+ @CatchAsync
+ public async getAvailableFields(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
try {
@@ -73,7 +76,8 @@ export class Contacts {
*/
@Get('fields/:field/values')
@Middleware([requireAuth])
- public async getFieldValues(req: Request, res: Response) {
+ @CatchAsync
+ public async getFieldValues(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const field = req.params.field;
const limit = Math.min(parseInt(req.query.limit as string) || 100, 200);
@@ -105,7 +109,8 @@ export class Contacts {
*/
@Get(':id')
@Middleware([requireAuth])
- public async get(req: Request, res: Response) {
+ @CatchAsync
+ public async get(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const contactId = req.params.id;
@@ -124,7 +129,8 @@ export class Contacts {
*/
@Post('')
@Middleware([requireAuth])
- public async create(req: Request, res: Response) {
+ @CatchAsync
+ public async create(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {email, data, subscribed} = req.body;
@@ -153,7 +159,8 @@ export class Contacts {
*/
@Patch(':id')
@Middleware([requireAuth])
- public async update(req: Request, res: Response) {
+ @CatchAsync
+ public async update(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const contactId = req.params.id;
const {email, data, subscribed} = req.body;
@@ -173,7 +180,8 @@ export class Contacts {
*/
@Delete(':id')
@Middleware([requireAuth])
- public async delete(req: Request, res: Response) {
+ @CatchAsync
+ public async delete(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const contactId = req.params.id;
@@ -191,7 +199,8 @@ export class Contacts {
* PUBLIC: Get contact information (no auth required)
*/
@Get('public/:id')
- public async getPublic(req: Request, res: Response) {
+ @CatchAsync
+ public async getPublic(req: Request, res: Response, next: NextFunction) {
const contactId = req.params.id;
if (!contactId) {
@@ -212,7 +221,8 @@ export class Contacts {
* PUBLIC: Subscribe a contact (no auth required)
*/
@Post('public/:id/subscribe')
- public async subscribePublic(req: Request, res: Response) {
+ @CatchAsync
+ public async subscribePublic(req: Request, res: Response, next: NextFunction) {
const contactId = req.params.id;
if (!contactId) {
@@ -233,7 +243,8 @@ export class Contacts {
* PUBLIC: Unsubscribe a contact (no auth required)
*/
@Post('public/:id/unsubscribe')
- public async unsubscribePublic(req: Request, res: Response) {
+ @CatchAsync
+ public async unsubscribePublic(req: Request, res: Response, next: NextFunction) {
const contactId = req.params.id;
if (!contactId) {
@@ -255,7 +266,8 @@ export class Contacts {
*/
@Post('import')
@Middleware([requireAuth, upload.single('file')])
- public async importCsv(req: Request, res: Response) {
+ @CatchAsync
+ public async importCsv(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
if (!req.file) {
@@ -288,7 +300,8 @@ export class Contacts {
*/
@Get('import/:jobId')
@Middleware([requireAuth])
- public async getImportStatus(req: Request, res: Response) {
+ @CatchAsync
+ public async getImportStatus(req: Request, res: Response, next: NextFunction) {
const jobId = req.params.jobId;
if (!jobId) {
diff --git a/apps/api/src/controllers/Domains.ts b/apps/api/src/controllers/Domains.ts
index fd2bfd0..160881c 100644
--- a/apps/api/src/controllers/Domains.ts
+++ b/apps/api/src/controllers/Domains.ts
@@ -1,6 +1,6 @@
import {Controller, Delete, Get, Middleware, Post} from '@overnightjs/core';
import {DomainSchemas, UtilitySchemas} from '@plunk/shared';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {redis} from '../database/redis.js';
import {NotFound} from '../exceptions/index.js';
@@ -9,6 +9,7 @@ import {isAuthenticated} from '../middleware/auth.js';
import {DomainService} from '../services/DomainService.js';
import {Keys} from '../services/keys.js';
import {prisma} from '../database/prisma.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('domains')
export class Domains {
@@ -17,7 +18,8 @@ export class Domains {
*/
@Get('project/:projectId')
@Middleware([isAuthenticated])
- public async getProjectDomains(req: Request, res: Response) {
+ @CatchAsync
+ public async getProjectDomains(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {projectId} = DomainSchemas.projectId.parse(req.params);
@@ -43,7 +45,8 @@ export class Domains {
*/
@Post('')
@Middleware([isAuthenticated])
- public async addDomain(req: Request, res: Response) {
+ @CatchAsync
+ public async addDomain(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {projectId, domain} = DomainSchemas.create.parse(req.body);
@@ -102,7 +105,8 @@ export class Domains {
*/
@Get(':id/verify')
@Middleware([isAuthenticated])
- public async checkVerification(req: Request, res: Response) {
+ @CatchAsync
+ public async checkVerification(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = UtilitySchemas.id.parse(req.params);
@@ -138,7 +142,8 @@ export class Domains {
*/
@Delete(':id')
@Middleware([isAuthenticated])
- public async removeDomain(req: Request, res: Response) {
+ @CatchAsync
+ public async removeDomain(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = UtilitySchemas.id.parse(req.params);
diff --git a/apps/api/src/controllers/Events.ts b/apps/api/src/controllers/Events.ts
index 137a45f..c3c1fab 100644
--- a/apps/api/src/controllers/Events.ts
+++ b/apps/api/src/controllers/Events.ts
@@ -1,9 +1,10 @@
import {Controller, Get, Middleware, Post} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {EventService} from '../services/EventService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('events')
export class Events {
@@ -13,7 +14,8 @@ export class Events {
*/
@Post('track')
@Middleware([requireAuth])
- public async track(req: Request, res: Response) {
+ @CatchAsync
+ public async track(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {name, contactId, emailId, data} = req.body;
@@ -32,7 +34,8 @@ export class Events {
*/
@Get('')
@Middleware([requireAuth])
- public async list(req: Request, res: Response) {
+ @CatchAsync
+ public async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const eventName = req.query.eventName as string | undefined;
const limit = parseInt(req.query.limit as string) || 100;
@@ -48,7 +51,8 @@ export class Events {
*/
@Get('stats')
@Middleware([requireAuth])
- public async stats(req: Request, res: Response) {
+ @CatchAsync
+ public async stats(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
@@ -64,7 +68,8 @@ export class Events {
*/
@Get('contact/:contactId')
@Middleware([requireAuth])
- public async getContactEvents(req: Request, res: Response) {
+ @CatchAsync
+ public async getContactEvents(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const contactId = req.params.contactId;
const limit = parseInt(req.query.limit as string) || 50;
@@ -84,7 +89,8 @@ export class Events {
*/
@Get('names')
@Middleware([requireAuth])
- public async getEventNames(req: Request, res: Response) {
+ @CatchAsync
+ public async getEventNames(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const eventNames = await EventService.getUniqueEventNames(auth.projectId!);
diff --git a/apps/api/src/controllers/Oauth/Github.ts b/apps/api/src/controllers/Oauth/Github.ts
index 0bc7637..7867677 100644
--- a/apps/api/src/controllers/Oauth/Github.ts
+++ b/apps/api/src/controllers/Oauth/Github.ts
@@ -1,5 +1,5 @@
import {Controller, Get} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {
API_URI,
@@ -11,6 +11,7 @@ import {
import {prisma} from '../../database/prisma.js';
import {jwt} from '../../middleware/auth.js';
import {UserService} from '../../services/UserService.js';
+import {CatchAsync} from '../../utils/asyncHandler.js';
@Controller('github')
export class Github {
@@ -31,7 +32,8 @@ export class Github {
}
@Get('callback')
- public async callback(req: Request, res: Response) {
+ @CatchAsync
+ public async callback(req: Request, res: Response, next: NextFunction) {
if (!GITHUB_OAUTH_ENABLED) {
return res.status(404).json({error: 'GitHub OAuth is not configured'});
}
diff --git a/apps/api/src/controllers/Oauth/Google.ts b/apps/api/src/controllers/Oauth/Google.ts
index 97b6977..c57fe3b 100644
--- a/apps/api/src/controllers/Oauth/Google.ts
+++ b/apps/api/src/controllers/Oauth/Google.ts
@@ -1,5 +1,5 @@
import {Controller, Get} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {
API_URI,
@@ -11,6 +11,7 @@ import {
import {prisma} from '../../database/prisma.js';
import {jwt} from '../../middleware/auth.js';
import {UserService} from '../../services/UserService.js';
+import {CatchAsync} from '../../utils/asyncHandler.js';
@Controller('google')
export class Google {
@@ -26,7 +27,8 @@ export class Google {
}
@Get('callback')
- public async callback(req: Request, res: Response) {
+ @CatchAsync
+ public async callback(req: Request, res: Response, next: NextFunction) {
if (!GOOGLE_OAUTH_ENABLED) {
return res.status(404).json({error: 'Google OAuth is not configured'});
}
diff --git a/apps/api/src/controllers/Projects.ts b/apps/api/src/controllers/Projects.ts
index 1104855..9ec7ca7 100644
--- a/apps/api/src/controllers/Projects.ts
+++ b/apps/api/src/controllers/Projects.ts
@@ -1,10 +1,11 @@
import {Controller, Get, Middleware} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {prisma} from '../database/prisma.js';
import {HttpException} from '../exceptions/index.js';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('projects')
export class Projects {
@@ -14,7 +15,8 @@ export class Projects {
*/
@Get(':id/setup-state')
@Middleware([requireAuth])
- private async getSetupState(req: Request, res: Response) {
+ @CatchAsync
+ private async getSetupState(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -86,7 +88,8 @@ export class Projects {
*/
@Get(':id/members')
@Middleware([requireAuth])
- private async getMembers(req: Request, res: Response) {
+ @CatchAsync
+ private async getMembers(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
diff --git a/apps/api/src/controllers/Segments.ts b/apps/api/src/controllers/Segments.ts
index 062cd59..95c2508 100644
--- a/apps/api/src/controllers/Segments.ts
+++ b/apps/api/src/controllers/Segments.ts
@@ -1,9 +1,10 @@
import {Controller, Delete, Get, Middleware, Patch, Post} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {SegmentService} from '../services/SegmentService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('segments')
export class Segments {
@@ -13,7 +14,8 @@ export class Segments {
*/
@Get('')
@Middleware([requireAuth])
- public async list(req: Request, res: Response) {
+ @CatchAsync
+ public async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segments = await SegmentService.list(auth.projectId!);
@@ -27,7 +29,8 @@ export class Segments {
*/
@Get(':id')
@Middleware([requireAuth])
- public async get(req: Request, res: Response) {
+ @CatchAsync
+ public async get(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
@@ -46,7 +49,8 @@ export class Segments {
*/
@Get(':id/contacts')
@Middleware([requireAuth])
- public async getContacts(req: Request, res: Response) {
+ @CatchAsync
+ public async getContacts(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
const page = parseInt(req.query.page as string) || 1;
@@ -67,7 +71,8 @@ export class Segments {
*/
@Post('')
@Middleware([requireAuth])
- public async create(req: Request, res: Response) {
+ @CatchAsync
+ public async create(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {name, description, filters, trackMembership} = req.body;
@@ -95,7 +100,8 @@ export class Segments {
*/
@Patch(':id')
@Middleware([requireAuth])
- public async update(req: Request, res: Response) {
+ @CatchAsync
+ public async update(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
const {name, description, filters, trackMembership} = req.body;
@@ -124,7 +130,8 @@ export class Segments {
*/
@Delete(':id')
@Middleware([requireAuth])
- public async delete(req: Request, res: Response) {
+ @CatchAsync
+ public async delete(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
@@ -143,7 +150,8 @@ export class Segments {
*/
@Post(':id/compute')
@Middleware([requireAuth])
- public async compute(req: Request, res: Response) {
+ @CatchAsync
+ public async compute(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
@@ -162,7 +170,8 @@ export class Segments {
*/
@Post(':id/refresh')
@Middleware([requireAuth])
- public async refresh(req: Request, res: Response) {
+ @CatchAsync
+ public async refresh(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const segmentId = req.params.id;
diff --git a/apps/api/src/controllers/Templates.ts b/apps/api/src/controllers/Templates.ts
index 9611296..42b9c90 100644
--- a/apps/api/src/controllers/Templates.ts
+++ b/apps/api/src/controllers/Templates.ts
@@ -1,11 +1,12 @@
import {Controller, Delete, Get, Middleware, Patch, Post} from '@overnightjs/core';
import {TemplateType} from '@plunk/db';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {DomainService} from '../services/DomainService.js';
import {TemplateService} from '../services/TemplateService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('templates')
export class Templates {
@@ -15,7 +16,8 @@ export class Templates {
*/
@Get('')
@Middleware([requireAuth])
- public async list(req: Request, res: Response) {
+ @CatchAsync
+ public async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const page = parseInt(req.query.page as string) || 1;
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
@@ -33,7 +35,8 @@ export class Templates {
*/
@Get(':id')
@Middleware([requireAuth])
- public async get(req: Request, res: Response) {
+ @CatchAsync
+ public async get(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const templateId = req.params.id;
@@ -52,7 +55,8 @@ export class Templates {
*/
@Post('')
@Middleware([requireAuth])
- public async create(req: Request, res: Response) {
+ @CatchAsync
+ public async create(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
@@ -95,7 +99,8 @@ export class Templates {
*/
@Patch(':id')
@Middleware([requireAuth])
- public async update(req: Request, res: Response) {
+ @CatchAsync
+ public async update(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const templateId = req.params.id;
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
@@ -129,7 +134,8 @@ export class Templates {
*/
@Delete(':id')
@Middleware([requireAuth])
- public async delete(req: Request, res: Response) {
+ @CatchAsync
+ public async delete(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const templateId = req.params.id;
@@ -148,7 +154,8 @@ export class Templates {
*/
@Post(':id/duplicate')
@Middleware([requireAuth])
- public async duplicate(req: Request, res: Response) {
+ @CatchAsync
+ public async duplicate(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const templateId = req.params.id;
@@ -167,7 +174,8 @@ export class Templates {
*/
@Get(':id/usage')
@Middleware([requireAuth])
- public async getUsage(req: Request, res: Response) {
+ @CatchAsync
+ public async getUsage(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const templateId = req.params.id;
diff --git a/apps/api/src/controllers/Uploads.ts b/apps/api/src/controllers/Uploads.ts
index d776f4b..e01ec83 100644
--- a/apps/api/src/controllers/Uploads.ts
+++ b/apps/api/src/controllers/Uploads.ts
@@ -1,10 +1,11 @@
import {Controller, Middleware, Post} from '@overnightjs/core';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import multer from 'multer';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import * as S3Service from '../services/S3Service.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
// Configure multer for file uploads (memory storage)
const upload = multer({
@@ -32,7 +33,8 @@ export class Uploads {
*/
@Post('image')
@Middleware([requireAuth, upload.single('image')])
- public async uploadImage(req: Request, res: Response) {
+ @CatchAsync
+ public async uploadImage(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
try {
diff --git a/apps/api/src/controllers/Users.ts b/apps/api/src/controllers/Users.ts
index 067a31e..9ef21a9 100644
--- a/apps/api/src/controllers/Users.ts
+++ b/apps/api/src/controllers/Users.ts
@@ -2,7 +2,7 @@ import {randomBytes} from 'node:crypto';
import {Controller, Get, Middleware, Patch, Post, Put} from '@overnightjs/core';
import {BillingLimitSchemas, ProjectSchemas} from '@plunk/shared';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import {
DASHBOARD_URI,
@@ -23,12 +23,14 @@ import {isAuthenticated} from '../middleware/auth.js';
import {BillingLimitService} from '../services/BillingLimitService.js';
import {SecurityService} from '../services/SecurityService.js';
import {UserService} from '../services/UserService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('users')
export class Users {
@Get('@me')
@Middleware([isAuthenticated])
- public async me(req: Request, res: Response) {
+ @CatchAsync
+ public async me(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
if (!auth.userId) {
@@ -46,7 +48,8 @@ export class Users {
@Get('@me/projects')
@Middleware([isAuthenticated])
- public async meProjects(req: Request, res: Response) {
+ @CatchAsync
+ public async meProjects(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
if (!auth.userId) {
@@ -60,7 +63,8 @@ export class Users {
@Post('@me/projects')
@Middleware([isAuthenticated])
- public async createProject(req: Request, res: Response) {
+ @CatchAsync
+ public async createProject(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
if (!auth.userId) {
@@ -93,7 +97,8 @@ export class Users {
@Patch('@me/projects/:id')
@Middleware([isAuthenticated])
- public async updateProject(req: Request, res: Response) {
+ @CatchAsync
+ public async updateProject(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
const data = ProjectSchemas.update.parse(req.body);
@@ -124,7 +129,8 @@ export class Users {
@Post('@me/projects/:id/regenerate-keys')
@Middleware([isAuthenticated])
- public async regenerateProjectKeys(req: Request, res: Response) {
+ @CatchAsync
+ public async regenerateProjectKeys(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -161,7 +167,8 @@ export class Users {
@Post('@me/projects/:id/checkout')
@Middleware([isAuthenticated])
- public async createCheckoutSession(req: Request, res: Response) {
+ @CatchAsync
+ public async createCheckoutSession(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -239,7 +246,8 @@ export class Users {
@Post('@me/projects/:id/billing-portal')
@Middleware([isAuthenticated])
- public async createBillingPortalSession(req: Request, res: Response) {
+ @CatchAsync
+ public async createBillingPortalSession(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -288,7 +296,8 @@ export class Users {
@Get('@me/projects/:id/billing-limits')
@Middleware([isAuthenticated])
- public async getBillingLimits(req: Request, res: Response) {
+ @CatchAsync
+ public async getBillingLimits(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -320,7 +329,8 @@ export class Users {
@Put('@me/projects/:id/billing-limits')
@Middleware([isAuthenticated])
- public async updateBillingLimits(req: Request, res: Response) {
+ @CatchAsync
+ public async updateBillingLimits(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -386,7 +396,8 @@ export class Users {
@Get('@me/projects/:id/billing-consumption')
@Middleware([isAuthenticated])
- public async getBillingConsumption(req: Request, res: Response) {
+ @CatchAsync
+ public async getBillingConsumption(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -485,7 +496,8 @@ export class Users {
@Get('@me/projects/:id/billing-invoices')
@Middleware([isAuthenticated])
- public async getBillingInvoices(req: Request, res: Response) {
+ @CatchAsync
+ public async getBillingInvoices(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
@@ -572,7 +584,8 @@ export class Users {
@Get('@me/projects/:id/security')
@Middleware([isAuthenticated])
- public async getSecurityHealth(req: Request, res: Response) {
+ @CatchAsync
+ public async getSecurityHealth(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {id} = req.params;
diff --git a/apps/api/src/controllers/Webhooks.ts b/apps/api/src/controllers/Webhooks.ts
index d7cb5d0..2aa359b 100644
--- a/apps/api/src/controllers/Webhooks.ts
+++ b/apps/api/src/controllers/Webhooks.ts
@@ -1,7 +1,7 @@
import {Controller, Post} from '@overnightjs/core';
import type {Prisma} from '@plunk/db';
import {EmailStatus} from '@plunk/db';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import signale from 'signale';
import type Stripe from 'stripe';
@@ -9,6 +9,7 @@ import {STRIPE_ENABLED, STRIPE_WEBHOOK_SECRET} from '../app/constants.js';
import {stripe} from '../app/stripe.js';
import {prisma} from '../database/prisma.js';
import {SecurityService} from '../services/SecurityService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
/**
* Webhooks Controller
@@ -21,7 +22,8 @@ export class Webhooks {
* Handles email events: delivery, open, click, bounce, complaint
*/
@Post('sns')
- public async receiveSNSWebhook(req: Request, res: Response) {
+ @CatchAsync
+ public async receiveSNSWebhook(req: Request, res: Response, next: NextFunction) {
try {
// Parse the SNS message body
const body = JSON.parse(req.body.Message);
@@ -147,7 +149,8 @@ export class Webhooks {
* Handles subscription and payment events: checkout.session.completed, invoice.paid, etc.
*/
@Post('incoming/stripe')
- public async receiveStripeWebhook(req: Request, res: Response) {
+ @CatchAsync
+ public async receiveStripeWebhook(req: Request, res: Response, next: NextFunction) {
// Return 404 if billing is disabled
if (!STRIPE_ENABLED || !stripe) {
signale.warn('[WEBHOOK] Stripe webhook received but billing is disabled');
diff --git a/apps/api/src/controllers/Workflows.ts b/apps/api/src/controllers/Workflows.ts
index a8f240f..810f2ea 100644
--- a/apps/api/src/controllers/Workflows.ts
+++ b/apps/api/src/controllers/Workflows.ts
@@ -1,10 +1,11 @@
import {Controller, Delete, Get, Middleware, Patch, Post} from '@overnightjs/core';
import {WorkflowExecutionStatus} from '@plunk/db';
-import type {Request, Response} from 'express';
+import type {NextFunction, Request, Response} from 'express';
import type {AuthResponse} from '../middleware/auth.js';
import {requireAuth} from '../middleware/auth.js';
import {WorkflowService} from '../services/WorkflowService.js';
+import {CatchAsync} from '../utils/asyncHandler.js';
@Controller('workflows')
export class Workflows {
@@ -14,7 +15,8 @@ export class Workflows {
*/
@Get('')
@Middleware([requireAuth])
- public async list(req: Request, res: Response) {
+ @CatchAsync
+ public async list(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const page = parseInt(req.query.page as string) || 1;
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
@@ -31,7 +33,8 @@ export class Workflows {
*/
@Get(':id')
@Middleware([requireAuth])
- public async get(req: Request, res: Response) {
+ @CatchAsync
+ public async get(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
@@ -50,7 +53,8 @@ export class Workflows {
*/
@Post('')
@Middleware([requireAuth])
- public async create(req: Request, res: Response) {
+ @CatchAsync
+ public async create(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const {name, description, eventName, enabled, allowReentry} = req.body;
@@ -79,7 +83,8 @@ export class Workflows {
*/
@Patch(':id')
@Middleware([requireAuth])
- public async update(req: Request, res: Response) {
+ @CatchAsync
+ public async update(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const {name, description, triggerType, triggerConfig, enabled, allowReentry} = req.body;
@@ -106,7 +111,8 @@ export class Workflows {
*/
@Delete(':id')
@Middleware([requireAuth])
- public async delete(req: Request, res: Response) {
+ @CatchAsync
+ public async delete(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
@@ -125,7 +131,8 @@ export class Workflows {
*/
@Post(':id/steps')
@Middleware([requireAuth])
- public async addStep(req: Request, res: Response) {
+ @CatchAsync
+ public async addStep(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const {type, name, position, config, templateId, autoConnect} = req.body;
@@ -156,7 +163,8 @@ export class Workflows {
*/
@Patch(':id/steps/:stepId')
@Middleware([requireAuth])
- public async updateStep(req: Request, res: Response) {
+ @CatchAsync
+ public async updateStep(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const stepId = req.params.stepId;
@@ -182,7 +190,8 @@ export class Workflows {
*/
@Delete(':id/steps/:stepId')
@Middleware([requireAuth])
- public async deleteStep(req: Request, res: Response) {
+ @CatchAsync
+ public async deleteStep(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const stepId = req.params.stepId;
@@ -202,7 +211,8 @@ export class Workflows {
*/
@Post(':id/transitions')
@Middleware([requireAuth])
- public async createTransition(req: Request, res: Response) {
+ @CatchAsync
+ public async createTransition(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const {fromStepId, toStepId, condition, priority} = req.body;
@@ -231,7 +241,8 @@ export class Workflows {
*/
@Delete(':id/transitions/:transitionId')
@Middleware([requireAuth])
- public async deleteTransition(req: Request, res: Response) {
+ @CatchAsync
+ public async deleteTransition(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const transitionId = req.params.transitionId;
@@ -251,7 +262,8 @@ export class Workflows {
*/
@Post(':id/executions')
@Middleware([requireAuth])
- public async startExecution(req: Request, res: Response) {
+ @CatchAsync
+ public async startExecution(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const {contactId, context} = req.body;
@@ -275,7 +287,8 @@ export class Workflows {
*/
@Get(':id/executions')
@Middleware([requireAuth])
- public async listExecutions(req: Request, res: Response) {
+ @CatchAsync
+ public async listExecutions(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const page = parseInt(req.query.page as string) || 1;
@@ -297,7 +310,8 @@ export class Workflows {
*/
@Get(':id/executions/:executionId')
@Middleware([requireAuth])
- public async getExecution(req: Request, res: Response) {
+ @CatchAsync
+ public async getExecution(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const executionId = req.params.executionId;
@@ -317,7 +331,8 @@ export class Workflows {
*/
@Delete(':id/executions/:executionId')
@Middleware([requireAuth])
- public async cancelExecution(req: Request, res: Response) {
+ @CatchAsync
+ public async cancelExecution(req: Request, res: Response, next: NextFunction) {
const auth = res.locals.auth as AuthResponse;
const workflowId = req.params.id;
const executionId = req.params.executionId;
diff --git a/apps/api/src/utils/asyncHandler.ts b/apps/api/src/utils/asyncHandler.ts
new file mode 100644
index 0000000..b5b7b7f
--- /dev/null
+++ b/apps/api/src/utils/asyncHandler.ts
@@ -0,0 +1,24 @@
+import type {NextFunction, Request, Response} from 'express';
+
+/**
+ * Method decorator to catch async errors in OvernightJS controllers
+ * Wraps async controller methods to ensure errors are passed to Express error handler
+ *
+ * Usage:
+ * @Post('route')
+ * @CatchAsync
+ * public async myMethod(req: Request, res: Response) { ... }
+ *
+ * This decorator ensures all errors (sync and async) thrown in the method
+ * are caught and passed to the Express error middleware, fixing the hanging
+ * request issue when Zod validation fails.
+ */
+export function CatchAsync(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
+ const originalMethod = descriptor.value;
+
+ descriptor.value = function (req: Request, res: Response, next: NextFunction) {
+ Promise.resolve(originalMethod.call(this, req, res, next)).catch(next);
+ };
+
+ return descriptor;
+}
diff --git a/apps/landing/next-env.d.ts b/apps/landing/next-env.d.ts
index 1970904..7996d35 100644
--- a/apps/landing/next-env.d.ts
+++ b/apps/landing/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index 1970904..7996d35 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,6 +1,6 @@
///
///
-import "./.next/types/routes.d.ts";
+import "./.next/dev/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/pages/api-reference/config/typescript for more information.