Fix linting errors
This commit is contained in:
@@ -1,16 +1,16 @@
|
|||||||
import {describe, it, expect, beforeEach} from 'vitest';
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
import {ActionSchemas} from '@plunk/shared';
|
import {ActionSchemas} from '@plunk/shared';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
import {
|
import {
|
||||||
ErrorCode,
|
|
||||||
NotFound,
|
|
||||||
ValidationError,
|
|
||||||
NotAuthenticated,
|
|
||||||
NotAllowed,
|
|
||||||
RateLimitError,
|
|
||||||
ConflictError,
|
|
||||||
BadRequest,
|
BadRequest,
|
||||||
|
ConflictError,
|
||||||
|
ErrorCode,
|
||||||
HttpException,
|
HttpException,
|
||||||
|
NotAllowed,
|
||||||
|
NotAuthenticated,
|
||||||
|
NotFound,
|
||||||
|
RateLimitError,
|
||||||
|
ValidationError
|
||||||
} from '../../exceptions/index.js';
|
} from '../../exceptions/index.js';
|
||||||
import {EmailService} from '../../services/EmailService.js';
|
import {EmailService} from '../../services/EmailService.js';
|
||||||
|
|
||||||
@@ -69,8 +69,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should validate required fields for /v1/send', () => {
|
it('should validate required fields for /v1/send', () => {
|
||||||
|
|
||||||
|
|
||||||
const result = ActionSchemas.send.safeParse({});
|
const result = ActionSchemas.send.safeParse({});
|
||||||
|
|
||||||
expect(result.success).toBe(false);
|
expect(result.success).toBe(false);
|
||||||
@@ -80,8 +78,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should validate subject and body required when no template', () => {
|
it('should validate subject and body required when no template', () => {
|
||||||
|
|
||||||
|
|
||||||
const result = ActionSchemas.send.safeParse({
|
const result = ActionSchemas.send.safeParse({
|
||||||
to: '[email protected]',
|
to: '[email protected]',
|
||||||
// Missing subject, body, and template
|
// Missing subject, body, and template
|
||||||
@@ -94,8 +90,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should validate required fields for /v1/track', () => {
|
it('should validate required fields for /v1/track', () => {
|
||||||
|
|
||||||
|
|
||||||
const result = ActionSchemas.track.safeParse({
|
const result = ActionSchemas.track.safeParse({
|
||||||
email: '[email protected]',
|
email: '[email protected]',
|
||||||
// Missing event name
|
// Missing event name
|
||||||
@@ -271,8 +265,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
// ========================================
|
// ========================================
|
||||||
describe('Custom HTTP Exception Types', () => {
|
describe('Custom HTTP Exception Types', () => {
|
||||||
it('should structure NotFound errors correctly', () => {
|
it('should structure NotFound errors correctly', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new NotFound('Template', 'abc-123');
|
const error = new NotFound('Template', 'abc-123');
|
||||||
|
|
||||||
expect(error.code).toBe(404);
|
expect(error.code).toBe(404);
|
||||||
@@ -283,8 +275,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should map resources to specific error codes', () => {
|
it('should map resources to specific error codes', () => {
|
||||||
|
|
||||||
|
|
||||||
const testCases = [
|
const testCases = [
|
||||||
{resource: 'contact', expectedCode: ErrorCode.CONTACT_NOT_FOUND},
|
{resource: 'contact', expectedCode: ErrorCode.CONTACT_NOT_FOUND},
|
||||||
{resource: 'template', expectedCode: ErrorCode.TEMPLATE_NOT_FOUND},
|
{resource: 'template', expectedCode: ErrorCode.TEMPLATE_NOT_FOUND},
|
||||||
@@ -300,8 +290,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure ValidationError with field details', () => {
|
it('should structure ValidationError with field details', () => {
|
||||||
|
|
||||||
|
|
||||||
const fieldErrors = [
|
const fieldErrors = [
|
||||||
{field: 'email', message: 'Invalid email format', code: 'invalid_email'},
|
{field: 'email', message: 'Invalid email format', code: 'invalid_email'},
|
||||||
{field: 'data.firstName', message: 'Required field', code: 'required'},
|
{field: 'data.firstName', message: 'Required field', code: 'required'},
|
||||||
@@ -315,8 +303,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure NotAuthenticated errors', () => {
|
it('should structure NotAuthenticated errors', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new NotAuthenticated();
|
const error = new NotAuthenticated();
|
||||||
|
|
||||||
expect(error.code).toBe(401);
|
expect(error.code).toBe(401);
|
||||||
@@ -324,8 +310,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure NotAllowed errors', () => {
|
it('should structure NotAllowed errors', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new NotAllowed('Cannot perform action', 'Insufficient permissions');
|
const error = new NotAllowed('Cannot perform action', 'Insufficient permissions');
|
||||||
|
|
||||||
expect(error.code).toBe(403);
|
expect(error.code).toBe(403);
|
||||||
@@ -334,8 +318,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure RateLimitError', () => {
|
it('should structure RateLimitError', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new RateLimitError('Too many requests', 60);
|
const error = new RateLimitError('Too many requests', 60);
|
||||||
|
|
||||||
expect(error.code).toBe(429);
|
expect(error.code).toBe(429);
|
||||||
@@ -344,8 +326,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure ConflictError', () => {
|
it('should structure ConflictError', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new ConflictError('Contact exists', {email: '[email protected]'});
|
const error = new ConflictError('Contact exists', {email: '[email protected]'});
|
||||||
|
|
||||||
expect(error.code).toBe(409);
|
expect(error.code).toBe(409);
|
||||||
@@ -354,8 +334,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should structure BadRequest errors', () => {
|
it('should structure BadRequest errors', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new BadRequest('Invalid format', ErrorCode.INVALID_REQUEST_BODY, {
|
const error = new BadRequest('Invalid format', ErrorCode.INVALID_REQUEST_BODY, {
|
||||||
expected: 'JSON',
|
expected: 'JSON',
|
||||||
});
|
});
|
||||||
@@ -381,7 +359,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
type: 'MARKETING',
|
type: 'MARKETING',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
EmailService.sendTransactionalEmail({
|
EmailService.sendTransactionalEmail({
|
||||||
projectId,
|
projectId,
|
||||||
@@ -395,7 +372,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should return NotFound when template does not exist', async () => {
|
it('should return NotFound when template does not exist', async () => {
|
||||||
const contact = await factories.createContact({projectId});
|
|
||||||
const nonExistentId = '00000000-0000-0000-0000-000000000000';
|
const nonExistentId = '00000000-0000-0000-0000-000000000000';
|
||||||
|
|
||||||
const template = await prisma.template.findUnique({
|
const template = await prisma.template.findUnique({
|
||||||
@@ -413,8 +389,6 @@ describe('Actions API Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should handle billing limit exceeded', () => {
|
it('should handle billing limit exceeded', () => {
|
||||||
|
|
||||||
|
|
||||||
const error = new HttpException(429, 'Billing limit exceeded');
|
const error = new HttpException(429, 'Billing limit exceeded');
|
||||||
|
|
||||||
expect(error.code).toBe(429);
|
expect(error.code).toBe(429);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {describe, it, expect, beforeEach, vi} from 'vitest';
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
import {DomainService} from '../../services/DomainService.js';
|
import {DomainService} from '../../services/DomainService.js';
|
||||||
import * as SESService from '../../services/SESService.js';
|
import * as SESService from '../../services/SESService.js';
|
||||||
@@ -71,7 +71,7 @@ describe('Domain Verification and Ownership Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should allow access when user is a member of the project that owns the domain', async () => {
|
it('should allow access when user is a member of the project that owns the domain', async () => {
|
||||||
const {user, project} = await factories.createUserWithProject();
|
const {project} = await factories.createUserWithProject();
|
||||||
const domain = 'shared-domain.com';
|
const domain = 'shared-domain.com';
|
||||||
|
|
||||||
// User 1 adds the domain to their project
|
// User 1 adds the domain to their project
|
||||||
@@ -101,7 +101,7 @@ describe('Domain Verification and Ownership Tests', () => {
|
|||||||
describe('Preventing Unauthorized Domain Linking', () => {
|
describe('Preventing Unauthorized Domain Linking', () => {
|
||||||
it('should prevent linking a domain to another project when user is not a member', async () => {
|
it('should prevent linking a domain to another project when user is not a member', async () => {
|
||||||
const {project: project1} = await factories.createUserWithProject();
|
const {project: project1} = await factories.createUserWithProject();
|
||||||
const {user: user2, project: project2} = await factories.createUserWithProject();
|
const {user: user2} = await factories.createUserWithProject();
|
||||||
const domain = 'protected-domain.com';
|
const domain = 'protected-domain.com';
|
||||||
|
|
||||||
// Project 1 adds the domain
|
// Project 1 adds the domain
|
||||||
@@ -172,9 +172,7 @@ describe('Domain Verification and Ownership Tests', () => {
|
|||||||
await DomainService.addDomain(project.id, domain);
|
await DomainService.addDomain(project.id, domain);
|
||||||
|
|
||||||
// Try to verify email domain
|
// Try to verify email domain
|
||||||
await expect(
|
await expect(DomainService.verifyEmailDomain(`sender@${domain}`, project.id)).rejects.toThrow(/not verified/i);
|
||||||
DomainService.verifyEmailDomain(`sender@${domain}`, project.id),
|
|
||||||
).rejects.toThrow(/not verified/i);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow using verified domain for sending emails', async () => {
|
it('should allow using verified domain for sending emails', async () => {
|
||||||
@@ -210,9 +208,9 @@ describe('Domain Verification and Ownership Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Try to use from different project
|
// Try to use from different project
|
||||||
await expect(
|
await expect(DomainService.verifyEmailDomain(`sender@${domain}`, project2.id)).rejects.toThrow(
|
||||||
DomainService.verifyEmailDomain(`sender@${domain}`, project2.id),
|
/belongs to a different project/i,
|
||||||
).rejects.toThrow(/belongs to a different project/i);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prevent using unregistered domain', async () => {
|
it('should prevent using unregistered domain', async () => {
|
||||||
@@ -220,9 +218,7 @@ describe('Domain Verification and Ownership Tests', () => {
|
|||||||
const domain = 'not-registered.com';
|
const domain = 'not-registered.com';
|
||||||
|
|
||||||
// Try to use domain that was never added
|
// Try to use domain that was never added
|
||||||
await expect(
|
await expect(DomainService.verifyEmailDomain(`sender@${domain}`, project.id)).rejects.toThrow(/not registered/i);
|
||||||
DomainService.verifyEmailDomain(`sender@${domain}`, project.id),
|
|
||||||
).rejects.toThrow(/not registered/i);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -19,7 +19,7 @@ import {
|
|||||||
SMTP_ENABLED,
|
SMTP_ENABLED,
|
||||||
STRIPE_ENABLED,
|
STRIPE_ENABLED,
|
||||||
TRACKING_TOGGLE_ENABLED,
|
TRACKING_TOGGLE_ENABLED,
|
||||||
WIKI_URI
|
WIKI_URI,
|
||||||
} from './app/constants.js';
|
} from './app/constants.js';
|
||||||
import {Actions} from './controllers/Actions.js';
|
import {Actions} from './controllers/Actions.js';
|
||||||
import {Activity} from './controllers/Activity.js';
|
import {Activity} from './controllers/Activity.js';
|
||||||
@@ -159,7 +159,7 @@ server.app.use((error: Error, req: Request, res: Response, _next: NextFunction)
|
|||||||
field: err.path.join('.'),
|
field: err.path.join('.'),
|
||||||
message: err.message,
|
message: err.message,
|
||||||
code: err.code,
|
code: err.code,
|
||||||
received: err.code !== 'invalid_type' ? undefined : (err as any).received,
|
received: err.code !== 'invalid_type' ? undefined : 'received' in err ? err.received : undefined,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const statusCode = 422;
|
const statusCode = 422;
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export class Actions {
|
|||||||
@Post('track')
|
@Post('track')
|
||||||
@Middleware([requirePublicKey])
|
@Middleware([requirePublicKey])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async track(req: Request, res: Response, next: NextFunction) {
|
public async track(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
// Zod validation - errors automatically handled by global error handler
|
// Zod validation - errors automatically handled by global error handler
|
||||||
@@ -155,7 +155,7 @@ export class Actions {
|
|||||||
@Post('send')
|
@Post('send')
|
||||||
@Middleware([requireSecretKey])
|
@Middleware([requireSecretKey])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async send(req: Request, res: Response, next: NextFunction) {
|
public async send(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
// Zod validation - errors automatically handled by global error handler
|
// Zod validation - errors automatically handled by global error handler
|
||||||
@@ -244,12 +244,7 @@ export class Actions {
|
|||||||
: (data as Record<string, unknown> | undefined);
|
: (data as Record<string, unknown> | undefined);
|
||||||
|
|
||||||
// Create or update contact with metadata
|
// Create or update contact with metadata
|
||||||
const contact = await ContactService.upsert(
|
const contact = await ContactService.upsert(auth.projectId, recipient.email, recipientData, subscribed);
|
||||||
auth.projectId,
|
|
||||||
recipient.email,
|
|
||||||
recipientData,
|
|
||||||
subscribed,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get merged data including non-persistent fields for template rendering
|
// Get merged data including non-persistent fields for template rendering
|
||||||
const mergedData = ContactService.getMergedData(contact, data as Record<string, unknown> | undefined);
|
const mergedData = ContactService.getMergedData(contact, data as Record<string, unknown> | undefined);
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export class Activity {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getActivities(req: Request, res: Response, next: NextFunction) {
|
public async getActivities(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
|
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
|
||||||
const cursor = req.query.cursor as string | undefined;
|
const cursor = req.query.cursor as string | undefined;
|
||||||
@@ -64,7 +64,7 @@ export class Activity {
|
|||||||
@Get('stats')
|
@Get('stats')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getStats(req: Request, res: Response, next: NextFunction) {
|
public async getStats(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
|
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;
|
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
|
||||||
@@ -84,7 +84,7 @@ export class Activity {
|
|||||||
@Get('recent-count')
|
@Get('recent-count')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getRecentCount(req: Request, res: Response, next: NextFunction) {
|
public async getRecentCount(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const minutes = Math.min(parseInt(req.query.minutes as string) || 5, 60); // Max 60 minutes
|
const minutes = Math.min(parseInt(req.query.minutes as string) || 5, 60); // Max 60 minutes
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ export class Activity {
|
|||||||
@Get('types')
|
@Get('types')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getTypes(_req: Request, res: Response, next: NextFunction) {
|
public async getTypes(_req: Request, res: Response, _next: NextFunction) {
|
||||||
const types = Object.values(ActivityType);
|
const types = Object.values(ActivityType);
|
||||||
return res.status(200).json({types});
|
return res.status(200).json({types});
|
||||||
}
|
}
|
||||||
@@ -116,7 +116,7 @@ export class Activity {
|
|||||||
@Get('upcoming')
|
@Get('upcoming')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getUpcoming(req: Request, res: Response, next: NextFunction) {
|
public async getUpcoming(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
|
const limit = Math.min(parseInt(req.query.limit as string) || 50, 100);
|
||||||
const daysAhead = Math.min(parseInt(req.query.daysAhead as string) || 30, 90);
|
const daysAhead = Math.min(parseInt(req.query.daysAhead as string) || 30, 90);
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import {CatchAsync} from '../utils/asyncHandler.js';
|
|||||||
export class Auth {
|
export class Auth {
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async login(req: Request, res: Response, next: NextFunction) {
|
public async login(req: Request, res: Response, _next: NextFunction) {
|
||||||
const {email, password} = AuthenticationSchemas.login.parse(req.body);
|
const {email, password} = AuthenticationSchemas.login.parse(req.body);
|
||||||
|
|
||||||
const user = await UserService.email(email);
|
const user = await UserService.email(email);
|
||||||
@@ -46,7 +46,7 @@ export class Auth {
|
|||||||
|
|
||||||
@Post('signup')
|
@Post('signup')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async signup(req: Request, res: Response, next: NextFunction) {
|
public async signup(req: Request, res: Response, _next: NextFunction) {
|
||||||
const {email, password} = AuthenticationSchemas.login.parse(req.body);
|
const {email, password} = AuthenticationSchemas.login.parse(req.body);
|
||||||
|
|
||||||
const user = await UserService.email(email);
|
const user = await UserService.email(email);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class Campaigns {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async create(req: Request, res: Response, next: NextFunction) {
|
private async create(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
||||||
CampaignSchemas.create.parse(req.body);
|
CampaignSchemas.create.parse(req.body);
|
||||||
@@ -62,7 +62,7 @@ export class Campaigns {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async list(req: Request, res: Response, next: NextFunction) {
|
private async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const status = req.query.status as CampaignStatus | undefined;
|
const status = req.query.status as CampaignStatus | undefined;
|
||||||
const page = parseInt(req.query.page as string) || 1;
|
const page = parseInt(req.query.page as string) || 1;
|
||||||
@@ -95,7 +95,7 @@ export class Campaigns {
|
|||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async get(req: Request, res: Response, next: NextFunction) {
|
private async get(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -114,7 +114,7 @@ export class Campaigns {
|
|||||||
@Put(':id')
|
@Put(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async update(req: Request, res: Response, next: NextFunction) {
|
private async update(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
||||||
@@ -160,7 +160,7 @@ export class Campaigns {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async delete(req: Request, res: Response, next: NextFunction) {
|
private async delete(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -179,7 +179,7 @@ export class Campaigns {
|
|||||||
@Post(':id/duplicate')
|
@Post(':id/duplicate')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async duplicate(req: Request, res: Response, next: NextFunction) {
|
private async duplicate(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -199,7 +199,7 @@ export class Campaigns {
|
|||||||
@Post(':id/send')
|
@Post(':id/send')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async send(req: Request, res: Response, next: NextFunction) {
|
private async send(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
const scheduledFor = req.body?.scheduledFor;
|
const scheduledFor = req.body?.scheduledFor;
|
||||||
@@ -230,7 +230,7 @@ export class Campaigns {
|
|||||||
@Post(':id/cancel')
|
@Post(':id/cancel')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async cancel(req: Request, res: Response, next: NextFunction) {
|
private async cancel(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -250,7 +250,7 @@ export class Campaigns {
|
|||||||
@Get(':id/stats')
|
@Get(':id/stats')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async stats(req: Request, res: Response, next: NextFunction) {
|
private async stats(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -269,7 +269,7 @@ export class Campaigns {
|
|||||||
@Post(':id/test')
|
@Post(':id/test')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async sendTest(req: Request, res: Response, next: NextFunction) {
|
private async sendTest(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
const {email} = CampaignSchemas.sendTest.parse(req.body);
|
const {email} = CampaignSchemas.sendTest.parse(req.body);
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class Contacts {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async list(req: Request, res: Response, next: NextFunction) {
|
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
|
const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
|
||||||
const cursor = req.query.cursor as string | undefined;
|
const cursor = req.query.cursor as string | undefined;
|
||||||
@@ -52,7 +52,7 @@ export class Contacts {
|
|||||||
@Get('fields')
|
@Get('fields')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getAvailableFields(req: Request, res: Response, next: NextFunction) {
|
public async getAvailableFields(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -78,7 +78,7 @@ export class Contacts {
|
|||||||
@Get('fields/:field/values')
|
@Get('fields/:field/values')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getFieldValues(req: Request, res: Response, next: NextFunction) {
|
public async getFieldValues(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const field = req.params.field;
|
const field = req.params.field;
|
||||||
const limit = Math.min(parseInt(req.query.limit as string) || 100, 200);
|
const limit = Math.min(parseInt(req.query.limit as string) || 100, 200);
|
||||||
@@ -111,7 +111,7 @@ export class Contacts {
|
|||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async get(req: Request, res: Response, next: NextFunction) {
|
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
|
|
||||||
@@ -131,7 +131,7 @@ export class Contacts {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async create(req: Request, res: Response, next: NextFunction) {
|
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {email, data, subscribed} = req.body;
|
const {email, data, subscribed} = req.body;
|
||||||
|
|
||||||
@@ -161,7 +161,7 @@ export class Contacts {
|
|||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async update(req: Request, res: Response, next: NextFunction) {
|
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
const {email, data, subscribed} = req.body;
|
const {email, data, subscribed} = req.body;
|
||||||
@@ -182,7 +182,7 @@ export class Contacts {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async delete(req: Request, res: Response, next: NextFunction) {
|
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
|
|
||||||
@@ -201,7 +201,7 @@ export class Contacts {
|
|||||||
*/
|
*/
|
||||||
@Get('public/:id')
|
@Get('public/:id')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getPublic(req: Request, res: Response, next: NextFunction) {
|
public async getPublic(req: Request, res: Response, _next: NextFunction) {
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
|
|
||||||
if (!contactId) {
|
if (!contactId) {
|
||||||
@@ -223,7 +223,7 @@ export class Contacts {
|
|||||||
*/
|
*/
|
||||||
@Post('public/:id/subscribe')
|
@Post('public/:id/subscribe')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async subscribePublic(req: Request, res: Response, next: NextFunction) {
|
public async subscribePublic(req: Request, res: Response, _next: NextFunction) {
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
|
|
||||||
if (!contactId) {
|
if (!contactId) {
|
||||||
@@ -245,7 +245,7 @@ export class Contacts {
|
|||||||
*/
|
*/
|
||||||
@Post('public/:id/unsubscribe')
|
@Post('public/:id/unsubscribe')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async unsubscribePublic(req: Request, res: Response, next: NextFunction) {
|
public async unsubscribePublic(req: Request, res: Response, _next: NextFunction) {
|
||||||
const contactId = req.params.id;
|
const contactId = req.params.id;
|
||||||
|
|
||||||
if (!contactId) {
|
if (!contactId) {
|
||||||
@@ -268,7 +268,7 @@ export class Contacts {
|
|||||||
@Post('import')
|
@Post('import')
|
||||||
@Middleware([requireAuth, upload.single('file')])
|
@Middleware([requireAuth, upload.single('file')])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async importCsv(req: Request, res: Response, next: NextFunction) {
|
public async importCsv(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
if (!req.file) {
|
if (!req.file) {
|
||||||
@@ -302,7 +302,7 @@ export class Contacts {
|
|||||||
@Get('import/:jobId')
|
@Get('import/:jobId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getImportStatus(req: Request, res: Response, next: NextFunction) {
|
public async getImportStatus(req: Request, res: Response, _next: NextFunction) {
|
||||||
const jobId = req.params.jobId;
|
const jobId = req.params.jobId;
|
||||||
|
|
||||||
if (!jobId) {
|
if (!jobId) {
|
||||||
@@ -333,7 +333,7 @@ export class Contacts {
|
|||||||
@Get('fields/:field/usage')
|
@Get('fields/:field/usage')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getFieldUsage(req: Request, res: Response, next: NextFunction) {
|
public async getFieldUsage(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const field = req.params.field;
|
const field = req.params.field;
|
||||||
|
|
||||||
@@ -360,7 +360,7 @@ export class Contacts {
|
|||||||
@Delete('fields/:field')
|
@Delete('fields/:field')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async deleteField(req: Request, res: Response, next: NextFunction) {
|
public async deleteField(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const field = req.params.field;
|
const field = req.params.field;
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ export class Domains {
|
|||||||
@Get('project/:projectId')
|
@Get('project/:projectId')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getProjectDomains(req: Request, res: Response, next: NextFunction) {
|
public async getProjectDomains(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {projectId} = DomainSchemas.projectId.parse(req.params);
|
const {projectId} = DomainSchemas.projectId.parse(req.params);
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ export class Domains {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async addDomain(req: Request, res: Response, next: NextFunction) {
|
public async addDomain(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {projectId, domain} = DomainSchemas.create.parse(req.body);
|
const {projectId, domain} = DomainSchemas.create.parse(req.body);
|
||||||
|
|
||||||
@@ -106,7 +106,7 @@ export class Domains {
|
|||||||
@Get(':id/verify')
|
@Get(':id/verify')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async checkVerification(req: Request, res: Response, next: NextFunction) {
|
public async checkVerification(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = UtilitySchemas.id.parse(req.params);
|
const {id} = UtilitySchemas.id.parse(req.params);
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ export class Domains {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async removeDomain(req: Request, res: Response, next: NextFunction) {
|
public async removeDomain(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = UtilitySchemas.id.parse(req.params);
|
const {id} = UtilitySchemas.id.parse(req.params);
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class Events {
|
|||||||
@Post('track')
|
@Post('track')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async track(req: Request, res: Response, next: NextFunction) {
|
public async track(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {name, contactId, emailId, data} = req.body;
|
const {name, contactId, emailId, data} = req.body;
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ export class Events {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async list(req: Request, res: Response, next: NextFunction) {
|
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const eventName = req.query.eventName as string | undefined;
|
const eventName = req.query.eventName as string | undefined;
|
||||||
const limit = parseInt(req.query.limit as string) || 100;
|
const limit = parseInt(req.query.limit as string) || 100;
|
||||||
@@ -52,7 +52,7 @@ export class Events {
|
|||||||
@Get('stats')
|
@Get('stats')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async stats(req: Request, res: Response, next: NextFunction) {
|
public async stats(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const startDate = req.query.startDate ? new Date(req.query.startDate as string) : undefined;
|
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;
|
const endDate = req.query.endDate ? new Date(req.query.endDate as string) : undefined;
|
||||||
@@ -69,7 +69,7 @@ export class Events {
|
|||||||
@Get('contact/:contactId')
|
@Get('contact/:contactId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getContactEvents(req: Request, res: Response, next: NextFunction) {
|
public async getContactEvents(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const contactId = req.params.contactId;
|
const contactId = req.params.contactId;
|
||||||
const limit = parseInt(req.query.limit as string) || 50;
|
const limit = parseInt(req.query.limit as string) || 50;
|
||||||
@@ -90,7 +90,7 @@ export class Events {
|
|||||||
@Get('names')
|
@Get('names')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getEventNames(req: Request, res: Response, next: NextFunction) {
|
public async getEventNames(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
const eventNames = await EventService.getUniqueEventNames(auth.projectId!);
|
const eventNames = await EventService.getUniqueEventNames(auth.projectId!);
|
||||||
@@ -106,7 +106,7 @@ export class Events {
|
|||||||
@Get(':eventName/usage')
|
@Get(':eventName/usage')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getEventUsage(req: Request, res: Response, next: NextFunction) {
|
public async getEventUsage(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const eventName = req.params.eventName;
|
const eventName = req.params.eventName;
|
||||||
|
|
||||||
@@ -133,7 +133,7 @@ export class Events {
|
|||||||
@Delete(':eventName')
|
@Delete(':eventName')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async deleteEvent(req: Request, res: Response, next: NextFunction) {
|
public async deleteEvent(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const eventName = req.params.eventName;
|
const eventName = req.params.eventName;
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export class Github {
|
|||||||
|
|
||||||
@Get('callback')
|
@Get('callback')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async callback(req: Request, res: Response, next: NextFunction) {
|
public async callback(req: Request, res: Response, _next: NextFunction) {
|
||||||
if (!GITHUB_OAUTH_ENABLED) {
|
if (!GITHUB_OAUTH_ENABLED) {
|
||||||
return res.status(404).json({error: 'GitHub OAuth is not configured'});
|
return res.status(404).json({error: 'GitHub OAuth is not configured'});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ export class Google {
|
|||||||
|
|
||||||
@Get('callback')
|
@Get('callback')
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async callback(req: Request, res: Response, next: NextFunction) {
|
public async callback(req: Request, res: Response, _next: NextFunction) {
|
||||||
if (!GOOGLE_OAUTH_ENABLED) {
|
if (!GOOGLE_OAUTH_ENABLED) {
|
||||||
return res.status(404).json({error: 'Google OAuth is not configured'});
|
return res.status(404).json({error: 'Google OAuth is not configured'});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class Projects {
|
|||||||
@Get(':id/setup-state')
|
@Get(':id/setup-state')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async getSetupState(req: Request, res: Response, next: NextFunction) {
|
private async getSetupState(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -89,7 +89,7 @@ export class Projects {
|
|||||||
@Get(':id/members')
|
@Get(':id/members')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
private async getMembers(req: Request, res: Response, next: NextFunction) {
|
private async getMembers(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ export class Segments {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async list(req: Request, res: Response, next: NextFunction) {
|
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
const segments = await SegmentService.list(auth.projectId!);
|
const segments = await SegmentService.list(auth.projectId!);
|
||||||
@@ -30,7 +30,7 @@ export class Segments {
|
|||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async get(req: Request, res: Response, next: NextFunction) {
|
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ export class Segments {
|
|||||||
@Get(':id/contacts')
|
@Get(':id/contacts')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getContacts(req: Request, res: Response, next: NextFunction) {
|
public async getContacts(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
const page = parseInt(req.query.page as string) || 1;
|
const page = parseInt(req.query.page as string) || 1;
|
||||||
@@ -72,7 +72,7 @@ export class Segments {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async create(req: Request, res: Response, next: NextFunction) {
|
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {name, description, condition, trackMembership} = req.body;
|
const {name, description, condition, trackMembership} = req.body;
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ export class Segments {
|
|||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async update(req: Request, res: Response, next: NextFunction) {
|
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
const {name, description, condition, trackMembership} = req.body;
|
const {name, description, condition, trackMembership} = req.body;
|
||||||
@@ -131,7 +131,7 @@ export class Segments {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async delete(req: Request, res: Response, next: NextFunction) {
|
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
|
|
||||||
@@ -151,7 +151,7 @@ export class Segments {
|
|||||||
@Post(':id/compute')
|
@Post(':id/compute')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async compute(req: Request, res: Response, next: NextFunction) {
|
public async compute(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
|
|
||||||
@@ -171,7 +171,7 @@ export class Segments {
|
|||||||
@Post(':id/refresh')
|
@Post(':id/refresh')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async refresh(req: Request, res: Response, next: NextFunction) {
|
public async refresh(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const segmentId = req.params.id;
|
const segmentId = req.params.id;
|
||||||
|
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class Templates {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async list(req: Request, res: Response, next: NextFunction) {
|
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const page = parseInt(req.query.page as string) || 1;
|
const page = parseInt(req.query.page as string) || 1;
|
||||||
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
|
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
|
||||||
@@ -36,7 +36,7 @@ export class Templates {
|
|||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async get(req: Request, res: Response, next: NextFunction) {
|
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const templateId = req.params.id;
|
const templateId = req.params.id;
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@ export class Templates {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async create(req: Request, res: Response, next: NextFunction) {
|
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
|
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ export class Templates {
|
|||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async update(req: Request, res: Response, next: NextFunction) {
|
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const templateId = req.params.id;
|
const templateId = req.params.id;
|
||||||
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
|
const {name, description, subject, body, from, fromName, replyTo, type} = req.body;
|
||||||
@@ -135,7 +135,7 @@ export class Templates {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async delete(req: Request, res: Response, next: NextFunction) {
|
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const templateId = req.params.id;
|
const templateId = req.params.id;
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ export class Templates {
|
|||||||
@Post(':id/duplicate')
|
@Post(':id/duplicate')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async duplicate(req: Request, res: Response, next: NextFunction) {
|
public async duplicate(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const templateId = req.params.id;
|
const templateId = req.params.id;
|
||||||
|
|
||||||
@@ -175,7 +175,7 @@ export class Templates {
|
|||||||
@Get(':id/usage')
|
@Get(':id/usage')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getUsage(req: Request, res: Response, next: NextFunction) {
|
public async getUsage(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const templateId = req.params.id;
|
const templateId = req.params.id;
|
||||||
|
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class Uploads {
|
|||||||
@Post('image')
|
@Post('image')
|
||||||
@Middleware([requireAuth, upload.single('image')])
|
@Middleware([requireAuth, upload.single('image')])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async uploadImage(req: Request, res: Response, next: NextFunction) {
|
public async uploadImage(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ export class Users {
|
|||||||
@Get('@me')
|
@Get('@me')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async me(req: Request, res: Response, next: NextFunction) {
|
public async me(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
if (!auth.userId) {
|
if (!auth.userId) {
|
||||||
@@ -39,7 +39,7 @@ export class Users {
|
|||||||
@Get('@me/projects')
|
@Get('@me/projects')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async meProjects(req: Request, res: Response, next: NextFunction) {
|
public async meProjects(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
if (!auth.userId) {
|
if (!auth.userId) {
|
||||||
@@ -54,7 +54,7 @@ export class Users {
|
|||||||
@Post('@me/projects')
|
@Post('@me/projects')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async createProject(req: Request, res: Response, next: NextFunction) {
|
public async createProject(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
|
|
||||||
if (!auth.userId) {
|
if (!auth.userId) {
|
||||||
@@ -88,7 +88,7 @@ export class Users {
|
|||||||
@Patch('@me/projects/:id')
|
@Patch('@me/projects/:id')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async updateProject(req: Request, res: Response, next: NextFunction) {
|
public async updateProject(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
const data = ProjectSchemas.update.parse(req.body);
|
const data = ProjectSchemas.update.parse(req.body);
|
||||||
@@ -120,7 +120,7 @@ export class Users {
|
|||||||
@Post('@me/projects/:id/regenerate-keys')
|
@Post('@me/projects/:id/regenerate-keys')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async regenerateProjectKeys(req: Request, res: Response, next: NextFunction) {
|
public async regenerateProjectKeys(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -158,7 +158,7 @@ export class Users {
|
|||||||
@Post('@me/projects/:id/checkout')
|
@Post('@me/projects/:id/checkout')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async createCheckoutSession(req: Request, res: Response, next: NextFunction) {
|
public async createCheckoutSession(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -237,7 +237,7 @@ export class Users {
|
|||||||
@Post('@me/projects/:id/billing-portal')
|
@Post('@me/projects/:id/billing-portal')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async createBillingPortalSession(req: Request, res: Response, next: NextFunction) {
|
public async createBillingPortalSession(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -287,7 +287,7 @@ export class Users {
|
|||||||
@Get('@me/projects/:id/billing-limits')
|
@Get('@me/projects/:id/billing-limits')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getBillingLimits(req: Request, res: Response, next: NextFunction) {
|
public async getBillingLimits(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ export class Users {
|
|||||||
@Put('@me/projects/:id/billing-limits')
|
@Put('@me/projects/:id/billing-limits')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async updateBillingLimits(req: Request, res: Response, next: NextFunction) {
|
public async updateBillingLimits(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -387,7 +387,7 @@ export class Users {
|
|||||||
@Get('@me/projects/:id/billing-consumption')
|
@Get('@me/projects/:id/billing-consumption')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getBillingConsumption(req: Request, res: Response, next: NextFunction) {
|
public async getBillingConsumption(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -487,7 +487,7 @@ export class Users {
|
|||||||
@Get('@me/projects/:id/billing-invoices')
|
@Get('@me/projects/:id/billing-invoices')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getBillingInvoices(req: Request, res: Response, next: NextFunction) {
|
public async getBillingInvoices(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -575,7 +575,7 @@ export class Users {
|
|||||||
@Get('@me/projects/:id/security')
|
@Get('@me/projects/:id/security')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getSecurityHealth(req: Request, res: Response, next: NextFunction) {
|
public async getSecurityHealth(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -608,7 +608,7 @@ export class Users {
|
|||||||
@Post('@me/projects/:id/reset')
|
@Post('@me/projects/:id/reset')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async resetProject(req: Request, res: Response, next: NextFunction) {
|
public async resetProject(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
@@ -684,7 +684,7 @@ export class Users {
|
|||||||
@Delete('@me/projects/:id')
|
@Delete('@me/projects/:id')
|
||||||
@Middleware([isAuthenticated])
|
@Middleware([isAuthenticated])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async deleteProject(req: Request, res: Response, next: NextFunction) {
|
public async deleteProject(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {id} = req.params;
|
const {id} = req.params;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {Controller, Post} from '@overnightjs/core';
|
import {Controller, Post} from '@overnightjs/core';
|
||||||
import type {Prisma} from '@plunk/db';
|
import type {Prisma} from '@plunk/db';
|
||||||
import {EmailStatus} from '@plunk/db';
|
import {EmailStatus} from '@plunk/db';
|
||||||
import type {NextFunction, Request, Response} from 'express';
|
import type {Request, Response} from 'express';
|
||||||
import signale from 'signale';
|
import signale from 'signale';
|
||||||
import type Stripe from 'stripe';
|
import type Stripe from 'stripe';
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ export class Workflows {
|
|||||||
@Get('')
|
@Get('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async list(req: Request, res: Response, next: NextFunction) {
|
public async list(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const page = parseInt(req.query.page as string) || 1;
|
const page = parseInt(req.query.page as string) || 1;
|
||||||
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
|
const pageSize = Math.min(parseInt(req.query.pageSize as string) || 20, 100);
|
||||||
@@ -36,7 +36,7 @@ export class Workflows {
|
|||||||
@Get('fields')
|
@Get('fields')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getAvailableFields(req: Request, res: Response, next: NextFunction) {
|
public async getAvailableFields(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const eventName = req.query.eventName as string | undefined;
|
const eventName = req.query.eventName as string | undefined;
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ export class Workflows {
|
|||||||
@Get(':id')
|
@Get(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async get(req: Request, res: Response, next: NextFunction) {
|
public async get(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ export class Workflows {
|
|||||||
@Post('')
|
@Post('')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async create(req: Request, res: Response, next: NextFunction) {
|
public async create(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const {name, description, eventName, enabled, allowReentry} = req.body;
|
const {name, description, eventName, enabled, allowReentry} = req.body;
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ export class Workflows {
|
|||||||
@Patch(':id')
|
@Patch(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async update(req: Request, res: Response, next: NextFunction) {
|
public async update(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const {name, description, triggerType, triggerConfig, enabled, allowReentry} = req.body;
|
const {name, description, triggerType, triggerConfig, enabled, allowReentry} = req.body;
|
||||||
@@ -137,7 +137,7 @@ export class Workflows {
|
|||||||
@Delete(':id')
|
@Delete(':id')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async delete(req: Request, res: Response, next: NextFunction) {
|
public async delete(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
|
|
||||||
@@ -157,7 +157,7 @@ export class Workflows {
|
|||||||
@Post(':id/steps')
|
@Post(':id/steps')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async addStep(req: Request, res: Response, next: NextFunction) {
|
public async addStep(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const {type, name, position, config, templateId, autoConnect} = req.body;
|
const {type, name, position, config, templateId, autoConnect} = req.body;
|
||||||
@@ -189,7 +189,7 @@ export class Workflows {
|
|||||||
@Patch(':id/steps/:stepId')
|
@Patch(':id/steps/:stepId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async updateStep(req: Request, res: Response, next: NextFunction) {
|
public async updateStep(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const stepId = req.params.stepId;
|
const stepId = req.params.stepId;
|
||||||
@@ -216,7 +216,7 @@ export class Workflows {
|
|||||||
@Delete(':id/steps/:stepId')
|
@Delete(':id/steps/:stepId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async deleteStep(req: Request, res: Response, next: NextFunction) {
|
public async deleteStep(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const stepId = req.params.stepId;
|
const stepId = req.params.stepId;
|
||||||
@@ -237,7 +237,7 @@ export class Workflows {
|
|||||||
@Post(':id/transitions')
|
@Post(':id/transitions')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async createTransition(req: Request, res: Response, next: NextFunction) {
|
public async createTransition(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const {fromStepId, toStepId, condition, priority} = req.body;
|
const {fromStepId, toStepId, condition, priority} = req.body;
|
||||||
@@ -267,7 +267,7 @@ export class Workflows {
|
|||||||
@Delete(':id/transitions/:transitionId')
|
@Delete(':id/transitions/:transitionId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async deleteTransition(req: Request, res: Response, next: NextFunction) {
|
public async deleteTransition(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const transitionId = req.params.transitionId;
|
const transitionId = req.params.transitionId;
|
||||||
@@ -288,7 +288,7 @@ export class Workflows {
|
|||||||
@Post(':id/executions')
|
@Post(':id/executions')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async startExecution(req: Request, res: Response, next: NextFunction) {
|
public async startExecution(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const {contactId, context} = req.body;
|
const {contactId, context} = req.body;
|
||||||
@@ -313,7 +313,7 @@ export class Workflows {
|
|||||||
@Get(':id/executions')
|
@Get(':id/executions')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async listExecutions(req: Request, res: Response, next: NextFunction) {
|
public async listExecutions(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const page = parseInt(req.query.page as string) || 1;
|
const page = parseInt(req.query.page as string) || 1;
|
||||||
@@ -336,7 +336,7 @@ export class Workflows {
|
|||||||
@Get(':id/executions/:executionId')
|
@Get(':id/executions/:executionId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async getExecution(req: Request, res: Response, next: NextFunction) {
|
public async getExecution(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const executionId = req.params.executionId;
|
const executionId = req.params.executionId;
|
||||||
@@ -357,7 +357,7 @@ export class Workflows {
|
|||||||
@Delete(':id/executions/:executionId')
|
@Delete(':id/executions/:executionId')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async cancelExecution(req: Request, res: Response, next: NextFunction) {
|
public async cancelExecution(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
const executionId = req.params.executionId;
|
const executionId = req.params.executionId;
|
||||||
@@ -378,7 +378,7 @@ export class Workflows {
|
|||||||
@Post(':id/executions/cancel-all')
|
@Post(':id/executions/cancel-all')
|
||||||
@Middleware([requireAuth])
|
@Middleware([requireAuth])
|
||||||
@CatchAsync
|
@CatchAsync
|
||||||
public async cancelAllExecutions(req: Request, res: Response, next: NextFunction) {
|
public async cancelAllExecutions(req: Request, res: Response, _next: NextFunction) {
|
||||||
const auth = res.locals.auth as AuthResponse;
|
const auth = res.locals.auth as AuthResponse;
|
||||||
const workflowId = req.params.id;
|
const workflowId = req.params.id;
|
||||||
|
|
||||||
|
|||||||
@@ -1,17 +1,14 @@
|
|||||||
import {beforeEach, describe, expect, it} from 'vitest';
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories} from '../../../../../test/helpers';
|
||||||
import {EventService} from '../../services/EventService';
|
import {EventService} from '../../services/EventService';
|
||||||
import {WorkflowService} from '../../services/WorkflowService';
|
import {WorkflowService} from '../../services/WorkflowService';
|
||||||
|
|
||||||
describe('Workflows Controller', () => {
|
describe('Workflows Controller', () => {
|
||||||
let projectId: string;
|
let projectId: string;
|
||||||
let userId: string;
|
|
||||||
const prisma = getPrismaClient();
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const {project, user} = await factories.createUserWithProject();
|
const {project} = await factories.createUserWithProject();
|
||||||
projectId = project.id;
|
projectId = project.id;
|
||||||
userId = user.id;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import {describe, it, expect, beforeEach, vi} from 'vitest';
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {EmailStatus, EmailSourceType} from '@plunk/db';
|
|
||||||
import {factories, getPrismaClient, createServiceMocks} from '../../../../../test/helpers';
|
|
||||||
import type {Prisma} from '@plunk/db';
|
import type {Prisma} from '@plunk/db';
|
||||||
|
import {EmailSourceType, EmailStatus} from '@plunk/db';
|
||||||
|
import {createServiceMocks, factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
// Mock MeterService
|
// Mock MeterService
|
||||||
vi.mock('../../services/MeterService.js', () => ({
|
vi.mock('../../services/MeterService.js', () => ({
|
||||||
@@ -286,7 +286,9 @@ describe('Email Processor', () => {
|
|||||||
|
|
||||||
expect(emailWithAttachmentsData?.attachments).toBeDefined();
|
expect(emailWithAttachmentsData?.attachments).toBeDefined();
|
||||||
expect(Array.isArray(emailWithAttachmentsData?.attachments)).toBe(true);
|
expect(Array.isArray(emailWithAttachmentsData?.attachments)).toBe(true);
|
||||||
expect((emailWithAttachmentsData?.attachments as any[]).length).toBeGreaterThan(0);
|
expect(
|
||||||
|
Array.isArray(emailWithAttachmentsData?.attachments) ? emailWithAttachmentsData.attachments.length : 0,
|
||||||
|
).toBeGreaterThan(0);
|
||||||
|
|
||||||
expect(emailWithoutAttachmentsData?.attachments).toBeNull();
|
expect(emailWithoutAttachmentsData?.attachments).toBeNull();
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {describe, it, expect, beforeEach, vi, afterEach} from 'vitest';
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import type {NextFunction, Request, Response} from 'express';
|
import type {NextFunction, Request, Response} from 'express';
|
||||||
import {databaseRequestLogger} from '../requestLogger.js';
|
import {databaseRequestLogger} from '../requestLogger.js';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
@@ -21,7 +21,7 @@ describe('Request Logger Middleware', () => {
|
|||||||
method: 'POST',
|
method: 'POST',
|
||||||
path: '/v1/send',
|
path: '/v1/send',
|
||||||
ip: '192.168.1.100',
|
ip: '192.168.1.100',
|
||||||
socket: {remoteAddress: '192.168.1.100'} as any,
|
socket: {remoteAddress: '192.168.1.100'} as Request['socket'],
|
||||||
get: vi.fn((header: string) => {
|
get: vi.fn((header: string) => {
|
||||||
if (header === 'user-agent') {
|
if (header === 'user-agent') {
|
||||||
return 'Mozilla/5.0 Test Browser';
|
return 'Mozilla/5.0 Test Browser';
|
||||||
@@ -50,7 +50,7 @@ describe('Request Logger Middleware', () => {
|
|||||||
set statusCode(value: number) {
|
set statusCode(value: number) {
|
||||||
statusCode = value;
|
statusCode = value;
|
||||||
},
|
},
|
||||||
json: vi.fn(function (this: any, body: any) {
|
json: vi.fn(function (this: Response, body: unknown) {
|
||||||
return body;
|
return body;
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
@@ -69,7 +69,7 @@ describe('Request Logger Middleware', () => {
|
|||||||
// ========================================
|
// ========================================
|
||||||
describe('Successful Request Logging', () => {
|
describe('Successful Request Logging', () => {
|
||||||
it('should log successful API request to database', async () => {
|
it('should log successful API request to database', async () => {
|
||||||
const middleware = databaseRequestLogger(req as Request, res as Response, next);
|
databaseRequestLogger(req as Request, res as Response, next);
|
||||||
|
|
||||||
// Middleware should call next immediately
|
// Middleware should call next immediately
|
||||||
expect(next).toHaveBeenCalled();
|
expect(next).toHaveBeenCalled();
|
||||||
@@ -334,10 +334,11 @@ describe('Request Logger Middleware', () => {
|
|||||||
await res.json!({success: true});
|
await res.json!({success: true});
|
||||||
await new Promise(resolve => setTimeout(resolve, 100));
|
await new Promise(resolve => setTimeout(resolve, 100));
|
||||||
|
|
||||||
// Should not log when disabled
|
// TODO: Add assertion to verify request was NOT logged when disabled
|
||||||
const loggedRequest = await prisma.apiRequest.findUnique({
|
// const loggedRequest = await prisma.apiRequest.findUnique({
|
||||||
where: {id: 'test-request-id-123'},
|
// where: {id: 'test-request-id-123'},
|
||||||
});
|
// });
|
||||||
|
// expect(loggedRequest).toBeNull();
|
||||||
|
|
||||||
// Restore original value
|
// Restore original value
|
||||||
if (originalEnv !== undefined) {
|
if (originalEnv !== undefined) {
|
||||||
@@ -378,7 +379,7 @@ describe('Request Logger Middleware', () => {
|
|||||||
const resInvalid = {
|
const resInvalid = {
|
||||||
...res,
|
...res,
|
||||||
locals: {
|
locals: {
|
||||||
requestId: null as any, // Invalid request ID - will cause database error
|
requestId: null as unknown, // Invalid request ID - will cause database error
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -460,7 +461,7 @@ describe('Request Logger Middleware', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should calculate response size from JSON body', async () => {
|
it('should calculate response size from JSON body', async () => {
|
||||||
const jsonMock = vi.fn(function (this: any, body: any) {
|
const jsonMock = vi.fn(function (this: Response, body: unknown) {
|
||||||
return body;
|
return body;
|
||||||
});
|
});
|
||||||
const resLarge = {
|
const resLarge = {
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ export const databaseRequestLogger = (req: Request, res: Response, next: NextFun
|
|||||||
|
|
||||||
// Capture the original res.json to log after response
|
// Capture the original res.json to log after response
|
||||||
const originalJson = res.json.bind(res);
|
const originalJson = res.json.bind(res);
|
||||||
res.json = function (body: any) {
|
res.json = function (body: unknown) {
|
||||||
// Call original json method first
|
// Call original json method first
|
||||||
const result = originalJson(body);
|
const result = originalJson(body);
|
||||||
|
|
||||||
@@ -124,7 +124,12 @@ export const databaseRequestLogger = (req: Request, res: Response, next: NextFun
|
|||||||
* Log the request to the database
|
* Log the request to the database
|
||||||
* This runs asynchronously and failures are logged but don't affect the response
|
* This runs asynchronously and failures are logged but don't affect the response
|
||||||
*/
|
*/
|
||||||
async function logRequestToDatabase(req: Request, res: Response, startTime: number, responseBody: any): Promise<void> {
|
async function logRequestToDatabase(
|
||||||
|
req: Request,
|
||||||
|
res: Response,
|
||||||
|
startTime: number,
|
||||||
|
responseBody: unknown,
|
||||||
|
): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const requestId = res.locals.requestId as string | undefined;
|
const requestId = res.locals.requestId as string | undefined;
|
||||||
const auth = res.locals.auth as {type?: string; userId?: string; projectId?: string} | undefined;
|
const auth = res.locals.auth as {type?: string; userId?: string; projectId?: string} | undefined;
|
||||||
@@ -136,9 +141,17 @@ async function logRequestToDatabase(req: Request, res: Response, startTime: numb
|
|||||||
let errorCode: string | undefined;
|
let errorCode: string | undefined;
|
||||||
let errorMessage: string | undefined;
|
let errorMessage: string | undefined;
|
||||||
|
|
||||||
if (statusCode >= 400 && responseBody?.error) {
|
if (
|
||||||
errorCode = responseBody.error.code;
|
statusCode >= 400 &&
|
||||||
errorMessage = responseBody.error.message;
|
responseBody &&
|
||||||
|
typeof responseBody === 'object' &&
|
||||||
|
'error' in responseBody &&
|
||||||
|
responseBody.error &&
|
||||||
|
typeof responseBody.error === 'object'
|
||||||
|
) {
|
||||||
|
const error = responseBody.error as {code?: string; message?: string};
|
||||||
|
errorCode = error.code;
|
||||||
|
errorMessage = error.message;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate request/response sizes (approximate)
|
// Calculate request/response sizes (approximate)
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import {type Contact, Prisma} from '@plunk/db';
|
import {type Contact, Prisma} from '@plunk/db';
|
||||||
|
import type {FilterCondition, FilterGroup} from '@plunk/types';
|
||||||
|
|
||||||
import {prisma} from '../database/prisma.js';
|
import {prisma} from '../database/prisma.js';
|
||||||
import {HttpException} from '../exceptions/index.js';
|
import {HttpException} from '../exceptions/index.js';
|
||||||
@@ -144,8 +145,7 @@ export class ContactService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Track subscription status change
|
// Track subscription status change
|
||||||
const isSubscriptionChanging =
|
const isSubscriptionChanging = data.subscribed !== undefined && existing.subscribed !== data.subscribed;
|
||||||
data.subscribed !== undefined && existing.subscribed !== data.subscribed;
|
|
||||||
const wasSubscribed = existing.subscribed;
|
const wasSubscribed = existing.subscribed;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -249,8 +249,7 @@ export class ContactService {
|
|||||||
|
|
||||||
if (existing) {
|
if (existing) {
|
||||||
// Track subscription status change
|
// Track subscription status change
|
||||||
const isSubscriptionChanging =
|
const isSubscriptionChanging = subscribed !== undefined && existing.subscribed !== subscribed;
|
||||||
subscribed !== undefined && existing.subscribed !== subscribed;
|
|
||||||
const wasSubscribed = existing.subscribed;
|
const wasSubscribed = existing.subscribed;
|
||||||
|
|
||||||
const updated = await prisma.contact.update({
|
const updated = await prisma.contact.update({
|
||||||
@@ -561,16 +560,10 @@ export class ContactService {
|
|||||||
|
|
||||||
// Check which segments use this field
|
// Check which segments use this field
|
||||||
const usedInSegments = segments.filter(segment => {
|
const usedInSegments = segments.filter(segment => {
|
||||||
const condition = segment.condition as any;
|
const condition = segment.condition as FilterCondition | null;
|
||||||
return this.fieldUsedInCondition(field, condition);
|
return this.fieldUsedInCondition(field, condition);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Get all campaigns for the project (emails)
|
|
||||||
const campaigns = await prisma.email.findMany({
|
|
||||||
where: {projectId},
|
|
||||||
select: {id: true, subject: true},
|
|
||||||
});
|
|
||||||
|
|
||||||
// For now, we'll check if campaigns use the field in their subject or body
|
// For now, we'll check if campaigns use the field in their subject or body
|
||||||
// This is a simplified check - you might want to enhance this based on your campaign structure
|
// This is a simplified check - you might want to enhance this based on your campaign structure
|
||||||
const usedInCampaigns: Array<{id: string; name: string}> = [];
|
const usedInCampaigns: Array<{id: string; name: string}> = [];
|
||||||
@@ -604,51 +597,6 @@ export class ContactService {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper: Check if a field is used in a filter condition (recursive)
|
|
||||||
*/
|
|
||||||
private static fieldUsedInCondition(field: string, condition: any): boolean {
|
|
||||||
if (!condition || typeof condition !== 'object') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check groups in the condition
|
|
||||||
if (Array.isArray(condition.groups)) {
|
|
||||||
for (const group of condition.groups) {
|
|
||||||
if (this.fieldUsedInGroup(field, group)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Helper: Check if a field is used in a filter group (recursive)
|
|
||||||
*/
|
|
||||||
private static fieldUsedInGroup(field: string, group: any): boolean {
|
|
||||||
if (!group || typeof group !== 'object') {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check filters in the group
|
|
||||||
if (Array.isArray(group.filters)) {
|
|
||||||
for (const filter of group.filters) {
|
|
||||||
if (filter.field === field) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check nested conditions
|
|
||||||
if (group.conditions) {
|
|
||||||
return this.fieldUsedInCondition(field, group.conditions);
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delete a custom field from all contacts
|
* Delete a custom field from all contacts
|
||||||
* WARNING: This is destructive and cannot be undone
|
* WARNING: This is destructive and cannot be undone
|
||||||
@@ -686,4 +634,49 @@ export class ContactService {
|
|||||||
|
|
||||||
return {deletedFrom: result};
|
return {deletedFrom: result};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: Check if a field is used in a filter condition (recursive)
|
||||||
|
*/
|
||||||
|
private static fieldUsedInCondition(field: string, condition: FilterCondition | null): boolean {
|
||||||
|
if (!condition || typeof condition !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check groups in the condition
|
||||||
|
if (Array.isArray(condition.groups)) {
|
||||||
|
for (const group of condition.groups) {
|
||||||
|
if (this.fieldUsedInGroup(field, group)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper: Check if a field is used in a filter group (recursive)
|
||||||
|
*/
|
||||||
|
private static fieldUsedInGroup(field: string, group: FilterGroup): boolean {
|
||||||
|
if (!group || typeof group !== 'object') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check filters in the group
|
||||||
|
if (Array.isArray(group.filters)) {
|
||||||
|
for (const filter of group.filters) {
|
||||||
|
if (filter.field === field) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check nested conditions
|
||||||
|
if (group.conditions) {
|
||||||
|
return this.fieldUsedInCondition(field, group.conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import type {Event} from '@plunk/db';
|
import type {Event} from '@plunk/db';
|
||||||
import {Prisma} from '@plunk/db';
|
import {Prisma} from '@plunk/db';
|
||||||
|
import type {FilterCondition, FilterGroup} from '@plunk/types';
|
||||||
|
|
||||||
import {prisma} from '../database/prisma.js';
|
import {prisma} from '../database/prisma.js';
|
||||||
import {redis} from '../database/redis.js';
|
import {redis} from '../database/redis.js';
|
||||||
@@ -177,6 +178,139 @@ export class EventService {
|
|||||||
return Array.from(fieldSet).sort();
|
return Array.from(fieldSet).sort();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an event is used in any segments or workflows
|
||||||
|
* Returns usage information including which segments/workflows use the event
|
||||||
|
*
|
||||||
|
* @param projectId - The project ID
|
||||||
|
* @param eventName - The event name to check (e.g., "purchase.completed", "user.signup")
|
||||||
|
* @returns Usage information
|
||||||
|
*/
|
||||||
|
public static async getEventUsage(
|
||||||
|
projectId: string,
|
||||||
|
eventName: string,
|
||||||
|
): Promise<{
|
||||||
|
usedInSegments: Array<{id: string; name: string}>;
|
||||||
|
usedInWorkflows: Array<{id: string; name: string}>;
|
||||||
|
totalCount: number;
|
||||||
|
uniqueContacts: number;
|
||||||
|
canDelete: boolean;
|
||||||
|
}> {
|
||||||
|
// Get all segments for the project
|
||||||
|
const segments = await prisma.segment.findMany({
|
||||||
|
where: {projectId},
|
||||||
|
select: {id: true, name: true, condition: true},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Check which segments use this event
|
||||||
|
const usedInSegments = segments.filter(segment => {
|
||||||
|
const condition = segment.condition as FilterCondition | null;
|
||||||
|
return this.eventUsedInCondition(eventName, condition);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get workflows that use this event as a trigger or wait condition
|
||||||
|
const workflows = await prisma.workflow.findMany({
|
||||||
|
where: {
|
||||||
|
projectId,
|
||||||
|
OR: [
|
||||||
|
// Event as trigger
|
||||||
|
{
|
||||||
|
triggerType: 'EVENT',
|
||||||
|
triggerConfig: {
|
||||||
|
path: ['eventName'],
|
||||||
|
equals: eventName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
select: {id: true, name: true},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Also check workflow steps that wait for events
|
||||||
|
const workflowStepsWithEvent = await prisma.workflowStep.findMany({
|
||||||
|
where: {
|
||||||
|
workflow: {projectId},
|
||||||
|
type: 'WAIT_FOR_EVENT',
|
||||||
|
config: {
|
||||||
|
path: ['eventName'],
|
||||||
|
equals: eventName,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
workflow: {
|
||||||
|
select: {id: true, name: true},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const usedInWorkflows = [...workflows, ...workflowStepsWithEvent.map(step => step.workflow)].reduce(
|
||||||
|
(acc, workflow) => {
|
||||||
|
// Deduplicate by id
|
||||||
|
if (!acc.find((w: {id: string; name: string}) => w.id === workflow.id)) {
|
||||||
|
acc.push(workflow);
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
[] as Array<{id: string; name: string}>,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Get event statistics
|
||||||
|
const [totalCount, uniqueContacts] = await Promise.all([
|
||||||
|
prisma.event.count({
|
||||||
|
where: {projectId, name: eventName},
|
||||||
|
}),
|
||||||
|
prisma.event
|
||||||
|
.groupBy({
|
||||||
|
by: ['contactId'],
|
||||||
|
where: {projectId, name: eventName, contactId: {not: null}},
|
||||||
|
})
|
||||||
|
.then(results => results.length),
|
||||||
|
]);
|
||||||
|
|
||||||
|
const canDelete = usedInSegments.length === 0 && usedInWorkflows.length === 0;
|
||||||
|
|
||||||
|
return {
|
||||||
|
usedInSegments,
|
||||||
|
usedInWorkflows,
|
||||||
|
totalCount,
|
||||||
|
uniqueContacts,
|
||||||
|
canDelete,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all events with a specific name
|
||||||
|
* WARNING: This is destructive and cannot be undone
|
||||||
|
* Should only be called after verifying the event is not in use
|
||||||
|
*
|
||||||
|
* @param projectId - The project ID
|
||||||
|
* @param eventName - The event name to delete
|
||||||
|
*/
|
||||||
|
public static async deleteEvent(projectId: string, eventName: string): Promise<{deletedCount: number}> {
|
||||||
|
// Prevent deletion of system events
|
||||||
|
if (eventName.startsWith('email.') || eventName.startsWith('segment.')) {
|
||||||
|
throw new Error('Cannot delete system events (email.* or segment.*)');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if event is in use
|
||||||
|
const usage = await this.getEventUsage(projectId, eventName);
|
||||||
|
if (!usage.canDelete) {
|
||||||
|
throw new Error(
|
||||||
|
`Cannot delete event: used in ${usage.usedInSegments.length} segment(s) and ${usage.usedInWorkflows.length} workflow(s)`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete all events with this name
|
||||||
|
const result = await prisma.event.deleteMany({
|
||||||
|
where: {
|
||||||
|
projectId,
|
||||||
|
name: eventName,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return {deletedCount: result.count};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Trigger workflows based on an event
|
* Trigger workflows based on an event
|
||||||
* Uses Redis caching for enabled workflows to improve performance
|
* Uses Redis caching for enabled workflows to improve performance
|
||||||
@@ -321,113 +455,10 @@ export class EventService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if an event is used in any segments or workflows
|
|
||||||
* Returns usage information including which segments/workflows use the event
|
|
||||||
*
|
|
||||||
* @param projectId - The project ID
|
|
||||||
* @param eventName - The event name to check (e.g., "purchase.completed", "user.signup")
|
|
||||||
* @returns Usage information
|
|
||||||
*/
|
|
||||||
public static async getEventUsage(
|
|
||||||
projectId: string,
|
|
||||||
eventName: string,
|
|
||||||
): Promise<{
|
|
||||||
usedInSegments: Array<{id: string; name: string}>;
|
|
||||||
usedInWorkflows: Array<{id: string; name: string}>;
|
|
||||||
totalCount: number;
|
|
||||||
uniqueContacts: number;
|
|
||||||
canDelete: boolean;
|
|
||||||
}> {
|
|
||||||
// Get all segments for the project
|
|
||||||
const segments = await prisma.segment.findMany({
|
|
||||||
where: {projectId},
|
|
||||||
select: {id: true, name: true, condition: true},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Check which segments use this event
|
|
||||||
const usedInSegments = segments.filter(segment => {
|
|
||||||
const condition = segment.condition as any;
|
|
||||||
return this.eventUsedInCondition(eventName, condition);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Get workflows that use this event as a trigger or wait condition
|
|
||||||
const workflows = await prisma.workflow.findMany({
|
|
||||||
where: {
|
|
||||||
projectId,
|
|
||||||
OR: [
|
|
||||||
// Event as trigger
|
|
||||||
{
|
|
||||||
triggerType: 'EVENT',
|
|
||||||
triggerConfig: {
|
|
||||||
path: ['eventName'],
|
|
||||||
equals: eventName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
select: {id: true, name: true},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Also check workflow steps that wait for events
|
|
||||||
const workflowStepsWithEvent = await prisma.workflowStep.findMany({
|
|
||||||
where: {
|
|
||||||
workflow: {projectId},
|
|
||||||
type: 'WAIT_FOR_EVENT',
|
|
||||||
config: {
|
|
||||||
path: ['eventName'],
|
|
||||||
equals: eventName,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
include: {
|
|
||||||
workflow: {
|
|
||||||
select: {id: true, name: true},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const usedInWorkflows = [
|
|
||||||
...workflows,
|
|
||||||
...workflowStepsWithEvent.map(step => step.workflow),
|
|
||||||
].reduce(
|
|
||||||
(acc, workflow) => {
|
|
||||||
// Deduplicate by id
|
|
||||||
if (!acc.find((w: {id: string; name: string}) => w.id === workflow.id)) {
|
|
||||||
acc.push(workflow);
|
|
||||||
}
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
[] as Array<{id: string; name: string}>,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Get event statistics
|
|
||||||
const [totalCount, uniqueContacts] = await Promise.all([
|
|
||||||
prisma.event.count({
|
|
||||||
where: {projectId, name: eventName},
|
|
||||||
}),
|
|
||||||
prisma.event
|
|
||||||
.groupBy({
|
|
||||||
by: ['contactId'],
|
|
||||||
where: {projectId, name: eventName, contactId: {not: null}},
|
|
||||||
})
|
|
||||||
.then(results => results.length),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const canDelete = usedInSegments.length === 0 && usedInWorkflows.length === 0;
|
|
||||||
|
|
||||||
return {
|
|
||||||
usedInSegments,
|
|
||||||
usedInWorkflows,
|
|
||||||
totalCount,
|
|
||||||
uniqueContacts,
|
|
||||||
canDelete,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Helper: Check if an event is used in a filter condition (recursive)
|
* Helper: Check if an event is used in a filter condition (recursive)
|
||||||
*/
|
*/
|
||||||
private static eventUsedInCondition(eventName: string, condition: any): boolean {
|
private static eventUsedInCondition(eventName: string, condition: FilterCondition | null): boolean {
|
||||||
if (!condition || typeof condition !== 'object') {
|
if (!condition || typeof condition !== 'object') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -447,7 +478,7 @@ export class EventService {
|
|||||||
/**
|
/**
|
||||||
* Helper: Check if an event is used in a filter group (recursive)
|
* Helper: Check if an event is used in a filter group (recursive)
|
||||||
*/
|
*/
|
||||||
private static eventUsedInGroup(eventName: string, group: any): boolean {
|
private static eventUsedInGroup(eventName: string, group: FilterGroup): boolean {
|
||||||
if (!group || typeof group !== 'object') {
|
if (!group || typeof group !== 'object') {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -469,37 +500,4 @@ export class EventService {
|
|||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all events with a specific name
|
|
||||||
* WARNING: This is destructive and cannot be undone
|
|
||||||
* Should only be called after verifying the event is not in use
|
|
||||||
*
|
|
||||||
* @param projectId - The project ID
|
|
||||||
* @param eventName - The event name to delete
|
|
||||||
*/
|
|
||||||
public static async deleteEvent(projectId: string, eventName: string): Promise<{deletedCount: number}> {
|
|
||||||
// Prevent deletion of system events
|
|
||||||
if (eventName.startsWith('email.') || eventName.startsWith('segment.')) {
|
|
||||||
throw new Error('Cannot delete system events (email.* or segment.*)');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if event is in use
|
|
||||||
const usage = await this.getEventUsage(projectId, eventName);
|
|
||||||
if (!usage.canDelete) {
|
|
||||||
throw new Error(
|
|
||||||
`Cannot delete event: used in ${usage.usedInSegments.length} segment(s) and ${usage.usedInWorkflows.length} workflow(s)`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Delete all events with this name
|
|
||||||
const result = await prisma.event.deleteMany({
|
|
||||||
where: {
|
|
||||||
projectId,
|
|
||||||
name: eventName,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {deletedCount: result.count};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {describe, it, expect, beforeEach, vi, afterEach} from 'vitest';
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {WorkflowTriggerType, WorkflowExecutionStatus} from '@plunk/db';
|
import {WorkflowExecutionStatus, WorkflowTriggerType} from '@plunk/db';
|
||||||
import {EventService} from '../EventService';
|
import {EventService} from '../EventService';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ describe('EventService', () => {
|
|||||||
// Clear Redis mock
|
// Clear Redis mock
|
||||||
const {redis} = await import('../../database/redis');
|
const {redis} = await import('../../database/redis');
|
||||||
if ('clear' in redis) {
|
if ('clear' in redis) {
|
||||||
(redis as any).clear();
|
(redis as unknown as {clear: () => void}).clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -441,7 +441,6 @@ describe('EventService', () => {
|
|||||||
const contact = await factories.createContact({projectId});
|
const contact = await factories.createContact({projectId});
|
||||||
|
|
||||||
const oldDate = new Date('2024-01-01');
|
const oldDate = new Date('2024-01-01');
|
||||||
const recentDate = new Date('2024-06-01');
|
|
||||||
|
|
||||||
// Create old event directly
|
// Create old event directly
|
||||||
await prisma.event.create({
|
await prisma.event.create({
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, it, expect, beforeEach } from 'vitest';
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
import {SegmentService} from '../SegmentService';
|
import {SegmentService} from '../SegmentService';
|
||||||
import { factories, getPrismaClient } from '../../../../../test/helpers';
|
import {factories} from '../../../../../test/helpers';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Comprehensive Operator Tests for Segment Filtering
|
* Comprehensive Operator Tests for Segment Filtering
|
||||||
@@ -16,7 +16,6 @@ import { factories, getPrismaClient } from '../../../../../test/helpers';
|
|||||||
*/
|
*/
|
||||||
describe('SegmentService - Comprehensive Operator Tests', () => {
|
describe('SegmentService - Comprehensive Operator Tests', () => {
|
||||||
let projectId: string;
|
let projectId: string;
|
||||||
const prisma = getPrismaClient();
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const {project} = await factories.createUserWithProject();
|
const {project} = await factories.createUserWithProject();
|
||||||
@@ -163,7 +162,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
// notEquals only matches where field exists and has different value
|
// notEquals only matches where field exists and has different value
|
||||||
expect(ids).toContain(withDifferentValue.id);
|
expect(ids).toContain(withDifferentValue.id);
|
||||||
expect(ids).not.toContain(withMatchingField.id);
|
expect(ids).not.toContain(withMatchingField.id);
|
||||||
@@ -210,7 +209,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(match1.id);
|
expect(ids).toContain(match1.id);
|
||||||
expect(ids).toContain(match2.id);
|
expect(ids).toContain(match2.id);
|
||||||
expect(result.contacts).toHaveLength(2);
|
expect(result.contacts).toHaveLength(2);
|
||||||
@@ -289,7 +288,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
// notContains only matches where field exists and doesn't contain substring
|
// notContains only matches where field exists and doesn't contain substring
|
||||||
expect(ids).toContain(withDifferentValue.id);
|
expect(ids).toContain(withDifferentValue.id);
|
||||||
expect(ids).not.toContain(withMatchingSubstring.id);
|
expect(ids).not.toContain(withMatchingSubstring.id);
|
||||||
@@ -345,7 +344,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(high.id);
|
expect(ids).toContain(high.id);
|
||||||
expect(ids).toContain(veryHigh.id);
|
expect(ids).toContain(veryHigh.id);
|
||||||
expect(result.contacts).toHaveLength(2);
|
expect(result.contacts).toHaveLength(2);
|
||||||
@@ -424,7 +423,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(equal.id);
|
expect(ids).toContain(equal.id);
|
||||||
expect(ids).toContain(greater.id);
|
expect(ids).toContain(greater.id);
|
||||||
expect(result.contacts).toHaveLength(2);
|
expect(result.contacts).toHaveLength(2);
|
||||||
@@ -451,7 +450,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(low.id);
|
expect(ids).toContain(low.id);
|
||||||
expect(ids).toContain(veryLow.id);
|
expect(ids).toContain(veryLow.id);
|
||||||
expect(result.contacts).toHaveLength(2);
|
expect(result.contacts).toHaveLength(2);
|
||||||
@@ -492,7 +491,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(equal.id);
|
expect(ids).toContain(equal.id);
|
||||||
expect(ids).toContain(less.id);
|
expect(ids).toContain(less.id);
|
||||||
expect(result.contacts).toHaveLength(2);
|
expect(result.contacts).toHaveLength(2);
|
||||||
@@ -501,7 +500,8 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
|
|
||||||
describe('Numeric edge cases', () => {
|
describe('Numeric edge cases', () => {
|
||||||
it('should handle zero values correctly', async () => {
|
it('should handle zero values correctly', async () => {
|
||||||
const zero = await factories.createContact({
|
// Create a contact with balance 0 (should NOT match greaterThan 0)
|
||||||
|
await factories.createContact({
|
||||||
projectId,
|
projectId,
|
||||||
data: {balance: 0},
|
data: {balance: 0},
|
||||||
});
|
});
|
||||||
@@ -718,7 +718,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(recent.id);
|
expect(ids).toContain(recent.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -737,7 +737,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(veryRecent.id);
|
expect(ids).toContain(veryRecent.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -756,7 +756,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(justNow.id);
|
expect(ids).toContain(justNow.id);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -768,7 +768,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
describe('Date Comparison Operators', () => {
|
describe('Date Comparison Operators', () => {
|
||||||
it('should support greaterThan for dates', async () => {
|
it('should support greaterThan for dates', async () => {
|
||||||
const older = await factories.createContact({projectId});
|
const older = await factories.createContact({projectId});
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
const newer = await factories.createContact({projectId});
|
const newer = await factories.createContact({projectId});
|
||||||
|
|
||||||
const segment = await factories.createSegment(projectId, {
|
const segment = await factories.createSegment(projectId, {
|
||||||
@@ -782,16 +782,16 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(newer.id);
|
expect(ids).toContain(newer.id);
|
||||||
expect(ids).not.toContain(older.id);
|
expect(ids).not.toContain(older.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should support lessThanOrEqual for dates', async () => {
|
it('should support lessThanOrEqual for dates', async () => {
|
||||||
const first = await factories.createContact({projectId});
|
const first = await factories.createContact({projectId});
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
const second = await factories.createContact({projectId});
|
const second = await factories.createContact({projectId});
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise(resolve => setTimeout(resolve, 10));
|
||||||
const third = await factories.createContact({projectId});
|
const third = await factories.createContact({projectId});
|
||||||
|
|
||||||
const segment = await factories.createSegment(projectId, {
|
const segment = await factories.createSegment(projectId, {
|
||||||
@@ -805,7 +805,7 @@ describe('SegmentService - Comprehensive Operator Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const result = await SegmentService.getContacts(projectId, segment.id);
|
const result = await SegmentService.getContacts(projectId, segment.id);
|
||||||
const ids = result.contacts.map((c) => c.id);
|
const ids = result.contacts.map(c => c.id);
|
||||||
expect(ids).toContain(first.id);
|
expect(ids).toContain(first.id);
|
||||||
expect(ids).toContain(second.id);
|
expect(ids).toContain(second.id);
|
||||||
expect(ids).not.toContain(third.id);
|
expect(ids).not.toContain(third.id);
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import {describe, it, expect, beforeEach} from 'vitest';
|
import {beforeEach, describe, expect, it} from 'vitest';
|
||||||
import {SegmentService} from '../SegmentService';
|
import {type SegmentFilter, SegmentService} from '../SegmentService';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
|
// Type for intentionally invalid filter input (used for testing validation)
|
||||||
|
type InvalidFilterInput = Partial<SegmentFilter>;
|
||||||
|
|
||||||
describe('SegmentService', () => {
|
describe('SegmentService', () => {
|
||||||
let projectId: string;
|
let projectId: string;
|
||||||
const prisma = getPrismaClient();
|
const prisma = getPrismaClient();
|
||||||
@@ -395,10 +398,10 @@ describe('SegmentService', () => {
|
|||||||
name: 'Invalid Segment',
|
name: 'Invalid Segment',
|
||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
// Intentionally missing field, cast to any to bypass compile-time validation
|
// Intentionally missing field, cast to bypass compile-time validation
|
||||||
operator: 'equals',
|
operator: 'equals',
|
||||||
value: 'test',
|
value: 'test',
|
||||||
} as any,
|
} as InvalidFilterInput as SegmentFilter,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/field is required/i);
|
).rejects.toThrow(/field is required/i);
|
||||||
@@ -411,10 +414,10 @@ describe('SegmentService', () => {
|
|||||||
filters: [
|
filters: [
|
||||||
{
|
{
|
||||||
field: 'email',
|
field: 'email',
|
||||||
// Intentionally invalid operator, cast to any
|
// Intentionally invalid operator
|
||||||
operator: 'DROP TABLE contacts;',
|
operator: 'DROP TABLE contacts;',
|
||||||
value: 'test',
|
value: 'test',
|
||||||
} as any,
|
} as InvalidFilterInput as SegmentFilter,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/invalid operator/i);
|
).rejects.toThrow(/invalid operator/i);
|
||||||
@@ -428,8 +431,8 @@ describe('SegmentService', () => {
|
|||||||
{
|
{
|
||||||
field: 'email',
|
field: 'email',
|
||||||
operator: 'equals',
|
operator: 'equals',
|
||||||
// Value intentionally omitted, cast to any
|
// Value intentionally omitted
|
||||||
} as any,
|
} as InvalidFilterInput as SegmentFilter,
|
||||||
],
|
],
|
||||||
}),
|
}),
|
||||||
).rejects.toThrow(/requires a value/i);
|
).rejects.toThrow(/requires a value/i);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {describe, it, expect, beforeEach, vi} from 'vitest';
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {WorkflowStepType, StepExecutionStatus, WorkflowExecutionStatus} from '@plunk/db';
|
import {WorkflowExecutionStatus, WorkflowStepType} from '@plunk/db';
|
||||||
import {WorkflowExecutionService} from '../WorkflowExecutionService';
|
import {WorkflowExecutionService} from '../WorkflowExecutionService';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
@@ -124,7 +124,8 @@ describe('Workflow CONDITION Step - Comprehensive Operator Tests', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return (stepExecution?.output as any)?.branch || 'unknown';
|
const output = stepExecution?.output as {branch?: string} | null;
|
||||||
|
return output?.branch || 'unknown';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {describe, it, expect, beforeEach, vi} from 'vitest';
|
import {beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {WorkflowStepType, StepExecutionStatus, WorkflowExecutionStatus, TemplateType, Prisma} from '@plunk/db';
|
import {Prisma, StepExecutionStatus, WorkflowExecutionStatus, WorkflowStepType} from '@plunk/db';
|
||||||
import {WorkflowExecutionService} from '../WorkflowExecutionService';
|
import {WorkflowExecutionService} from '../WorkflowExecutionService';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
@@ -140,7 +140,7 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
||||||
expect(conditionExec).toBeDefined();
|
expect(conditionExec).toBeDefined();
|
||||||
expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
|
expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
|
||||||
expect((conditionExec?.output as any)?.branch).toBe('yes');
|
expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('yes');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should follow NO branch when condition evaluates to false', async () => {
|
it('should follow NO branch when condition evaluates to false', async () => {
|
||||||
@@ -229,7 +229,7 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
|
|
||||||
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
||||||
expect(conditionExec).toBeDefined();
|
expect(conditionExec).toBeDefined();
|
||||||
expect((conditionExec?.output as any)?.branch).toBe('no');
|
expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('no');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle complex nested conditions', async () => {
|
it('should handle complex nested conditions', async () => {
|
||||||
@@ -356,8 +356,8 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
expect(stepExecutions.length).toBeGreaterThanOrEqual(3);
|
expect(stepExecutions.length).toBeGreaterThanOrEqual(3);
|
||||||
const conditions = stepExecutions.filter(se => se.step.type === WorkflowStepType.CONDITION);
|
const conditions = stepExecutions.filter(se => se.step.type === WorkflowStepType.CONDITION);
|
||||||
expect(conditions).toHaveLength(2);
|
expect(conditions).toHaveLength(2);
|
||||||
expect((conditions[0].output as any)?.branch).toBe('yes'); // US = yes
|
expect((conditions[0].output as {branch?: string} | null)?.branch).toBe('yes'); // US = yes
|
||||||
expect((conditions[1].output as any)?.branch).toBe('yes'); // Premium = yes
|
expect((conditions[1].output as {branch?: string} | null)?.branch).toBe('yes'); // Premium = yes
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -695,7 +695,7 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
// Should have: TRIGGER, CONDITION, and Path A (since segment = 'A')
|
// Should have: TRIGGER, CONDITION, and Path A (since segment = 'A')
|
||||||
expect(stepExecutions.length).toBeGreaterThanOrEqual(2);
|
expect(stepExecutions.length).toBeGreaterThanOrEqual(2);
|
||||||
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
const conditionExec = stepExecutions.find(se => se.step.type === WorkflowStepType.CONDITION);
|
||||||
expect((conditionExec?.output as any)?.branch).toBe('yes');
|
expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('yes');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -813,7 +813,7 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
|
expect(conditionExec?.status).toBe(StepExecutionStatus.COMPLETED);
|
||||||
expect((conditionExec?.output as any)?.branch).toBe('no');
|
expect((conditionExec?.output as {branch?: string} | null)?.branch).toBe('no');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1078,7 +1078,11 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
|
data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
|
||||||
});
|
});
|
||||||
await prisma.workflowTransition.create({
|
await prisma.workflowTransition.create({
|
||||||
data: {fromStepId: conditionStep.id, toStepId: exitStep.id, condition: {branch: 'yes'} as Prisma.InputJsonValue},
|
data: {
|
||||||
|
fromStepId: conditionStep.id,
|
||||||
|
toStepId: exitStep.id,
|
||||||
|
condition: {branch: 'yes'} as Prisma.InputJsonValue,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const execution = await prisma.workflowExecution.create({
|
const execution = await prisma.workflowExecution.create({
|
||||||
@@ -1140,7 +1144,11 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
|
data: {fromStepId: triggerStep!.id, toStepId: conditionStep.id},
|
||||||
});
|
});
|
||||||
await prisma.workflowTransition.create({
|
await prisma.workflowTransition.create({
|
||||||
data: {fromStepId: conditionStep.id, toStepId: exitStep.id, condition: {branch: 'yes'} as Prisma.InputJsonValue},
|
data: {
|
||||||
|
fromStepId: conditionStep.id,
|
||||||
|
toStepId: exitStep.id,
|
||||||
|
condition: {branch: 'yes'} as Prisma.InputJsonValue,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const execution = await prisma.workflowExecution.create({
|
const execution = await prisma.workflowExecution.create({
|
||||||
@@ -1178,7 +1186,7 @@ describe('WorkflowExecutionService - Integration Tests', () => {
|
|||||||
status: 200,
|
status: 200,
|
||||||
text: async () => JSON.stringify({success: true}),
|
text: async () => JSON.stringify({success: true}),
|
||||||
}));
|
}));
|
||||||
global.fetch = mockFetch as any;
|
global.fetch = mockFetch as unknown as typeof fetch;
|
||||||
|
|
||||||
const contact = await factories.createContact({
|
const contact = await factories.createContact({
|
||||||
projectId,
|
projectId,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {describe, it, expect, beforeEach, afterEach, vi} from 'vitest';
|
import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest';
|
||||||
import {WorkflowTriggerType, WorkflowStepType, WorkflowExecutionStatus} from '@plunk/db';
|
import {WorkflowExecutionStatus, WorkflowStepType, WorkflowTriggerType} from '@plunk/db';
|
||||||
import {WorkflowService} from '../WorkflowService';
|
import {WorkflowService} from '../WorkflowService';
|
||||||
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
import {factories, getPrismaClient} from '../../../../../test/helpers';
|
||||||
|
|
||||||
@@ -58,7 +58,7 @@ describe('WorkflowService', () => {
|
|||||||
afterEach(async () => {
|
afterEach(async () => {
|
||||||
const {redis} = await import('../../database/redis');
|
const {redis} = await import('../../database/redis');
|
||||||
if ('clear' in redis) {
|
if ('clear' in redis) {
|
||||||
(redis as any).clear();
|
(redis as unknown as {clear: () => void}).clear();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -233,9 +233,11 @@ describe('WorkflowService', () => {
|
|||||||
|
|
||||||
const result = await WorkflowService.list(projectId);
|
const result = await WorkflowService.list(projectId);
|
||||||
|
|
||||||
const found = result.workflows.find(w => w.id === workflow.id);
|
const found = result.workflows.find(w => w.id === workflow.id) as
|
||||||
expect((found as any)._count.steps).toBe(3); // TRIGGER + 2 added
|
| ((typeof result.workflows)[number] & {_count: {steps: number; executions: number}})
|
||||||
expect((found as any)._count.executions).toBe(1);
|
| undefined;
|
||||||
|
expect(found?._count.steps).toBe(3); // TRIGGER + 2 added
|
||||||
|
expect(found?._count.executions).toBe(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type {NextFunction, Request, Response} from 'express';
|
|||||||
* are caught and passed to the Express error middleware, fixing the hanging
|
* are caught and passed to the Express error middleware, fixing the hanging
|
||||||
* request issue when Zod validation fails.
|
* request issue when Zod validation fails.
|
||||||
*/
|
*/
|
||||||
export function CatchAsync(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
|
export function CatchAsync(target: object, propertyKey: string, descriptor: PropertyDescriptor) {
|
||||||
const originalMethod = descriptor.value;
|
const originalMethod = descriptor.value;
|
||||||
|
|
||||||
descriptor.value = function (req: Request, res: Response, next: NextFunction) {
|
descriptor.value = function (req: Request, res: Response, next: NextFunction) {
|
||||||
|
|||||||
@@ -155,7 +155,7 @@ export const requestLogger = (req: Request, res: Response, next: NextFunction) =
|
|||||||
|
|
||||||
// Capture the original res.json to log responses
|
// Capture the original res.json to log responses
|
||||||
const originalJson = res.json.bind(res);
|
const originalJson = res.json.bind(res);
|
||||||
res.json = function (body: any) {
|
res.json = function (body: unknown) {
|
||||||
const duration = Date.now() - startTime;
|
const duration = Date.now() - startTime;
|
||||||
logger.response(req, res, res.statusCode, duration);
|
logger.response(req, res, res.statusCode, duration);
|
||||||
return originalJson(body);
|
return originalJson(body);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import 'dotenv/config';
|
import 'dotenv/config';
|
||||||
import {simpleParser} from 'mailparser';
|
import {type AddressObject, simpleParser} from 'mailparser';
|
||||||
import signale from 'signale';
|
import signale from 'signale';
|
||||||
import {
|
import {
|
||||||
SMTPServer,
|
SMTPServer,
|
||||||
@@ -7,8 +7,8 @@ import {
|
|||||||
type SMTPServerAuthentication,
|
type SMTPServerAuthentication,
|
||||||
type SMTPServerAuthenticationResponse,
|
type SMTPServerAuthenticationResponse,
|
||||||
type SMTPServerDataStream,
|
type SMTPServerDataStream,
|
||||||
type SMTPServerSession,
|
|
||||||
type SMTPServerOptions,
|
type SMTPServerOptions,
|
||||||
|
type SMTPServerSession,
|
||||||
} from 'smtp-server';
|
} from 'smtp-server';
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
import path from 'path';
|
import path from 'path';
|
||||||
@@ -61,8 +61,9 @@ function loadFromTraefikAcme(): {key: Buffer; cert: Buffer} | null {
|
|||||||
key: string;
|
key: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const certData = certificates.find((cert: TraefikCert) =>
|
const certData = certificates.find(
|
||||||
(typeof cert.domain === 'object' && cert.domain?.main === SMTP_DOMAIN) || cert.domain === SMTP_DOMAIN
|
(cert: TraefikCert) =>
|
||||||
|
(typeof cert.domain === 'object' && cert.domain?.main === SMTP_DOMAIN) || cert.domain === SMTP_DOMAIN,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!certData) {
|
if (!certData) {
|
||||||
@@ -294,11 +295,11 @@ function handleData(
|
|||||||
const recipientNameMap = new Map<string, string>();
|
const recipientNameMap = new Map<string, string>();
|
||||||
|
|
||||||
// Helper to extract addresses from AddressObject
|
// Helper to extract addresses from AddressObject
|
||||||
const extractAddresses = (addressObj: any) => {
|
const extractAddresses = (addressObj: AddressObject | AddressObject[] | undefined) => {
|
||||||
if (!addressObj) return [];
|
if (!addressObj) return [];
|
||||||
// Handle both single AddressObject and array
|
// Handle both single AddressObject and array
|
||||||
const addresses = Array.isArray(addressObj) ? addressObj : [addressObj];
|
const addresses = Array.isArray(addressObj) ? addressObj : [addressObj];
|
||||||
return addresses.flatMap((obj: any) => obj.value || []);
|
return addresses.flatMap(obj => obj.value || []);
|
||||||
};
|
};
|
||||||
|
|
||||||
if (parsed.to) {
|
if (parsed.to) {
|
||||||
|
|||||||
Reference in New Issue
Block a user