Add better segmenting
This commit is contained in:
@@ -8,7 +8,6 @@ import type {AuthResponse} from '../middleware/auth.js';
|
||||
import {requireAuth} from '../middleware/auth.js';
|
||||
import {CampaignService} from '../services/CampaignService.js';
|
||||
import {DomainService} from '../services/DomainService.js';
|
||||
import {type SegmentFilter} from '../services/SegmentService.js';
|
||||
import {CatchAsync} from '../utils/asyncHandler.js';
|
||||
|
||||
@Controller('campaigns')
|
||||
@@ -22,7 +21,7 @@ export class Campaigns {
|
||||
@CatchAsync
|
||||
private async create(req: Request, res: Response, next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceFilter, segmentId} =
|
||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
||||
CampaignSchemas.create.parse(req.body);
|
||||
|
||||
// Validate audience-specific fields
|
||||
@@ -30,8 +29,8 @@ export class Campaigns {
|
||||
throw new HttpException(400, 'Segment ID is required for SEGMENT audience type');
|
||||
}
|
||||
|
||||
if (audienceType === CampaignAudienceType.FILTERED && !audienceFilter) {
|
||||
throw new HttpException(400, 'Audience filter is required for FILTERED audience type');
|
||||
if (audienceType === CampaignAudienceType.FILTERED && !audienceCondition) {
|
||||
throw new HttpException(400, 'Audience condition is required for FILTERED audience type');
|
||||
}
|
||||
|
||||
// Verify domain ownership and verification
|
||||
@@ -46,7 +45,7 @@ export class Campaigns {
|
||||
fromName,
|
||||
replyTo,
|
||||
audienceType,
|
||||
audienceFilter: audienceFilter as SegmentFilter[] | undefined,
|
||||
audienceCondition,
|
||||
segmentId,
|
||||
});
|
||||
|
||||
@@ -118,7 +117,7 @@ export class Campaigns {
|
||||
private async update(req: Request, res: Response, next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
const {id} = req.params;
|
||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceFilter, segmentId} =
|
||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
||||
req.body;
|
||||
|
||||
// Validate audience-specific fields if audienceType is being updated
|
||||
@@ -126,8 +125,8 @@ export class Campaigns {
|
||||
throw new HttpException(400, 'Segment ID is required for SEGMENT audience type');
|
||||
}
|
||||
|
||||
if (audienceType === CampaignAudienceType.FILTERED && audienceFilter === undefined) {
|
||||
throw new HttpException(400, 'Audience filter is required for FILTERED audience type');
|
||||
if (audienceType === CampaignAudienceType.FILTERED && audienceCondition === undefined) {
|
||||
throw new HttpException(400, 'Audience condition is required for FILTERED audience type');
|
||||
}
|
||||
|
||||
// Verify domain ownership and verification if 'from' is being updated
|
||||
@@ -144,7 +143,7 @@ export class Campaigns {
|
||||
fromName,
|
||||
replyTo,
|
||||
audienceType,
|
||||
audienceFilter,
|
||||
audienceCondition,
|
||||
segmentId,
|
||||
});
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ export class Contacts {
|
||||
/**
|
||||
* GET /contacts/fields
|
||||
* Get all available contact fields (both standard and custom fields from data JSON)
|
||||
* Returns field names with inferred types (string, number, boolean, date)
|
||||
*/
|
||||
@Get('fields')
|
||||
@Middleware([requireAuth])
|
||||
@@ -55,11 +56,11 @@ export class Contacts {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
|
||||
try {
|
||||
const fields = await ContactService.getAvailableFields(auth.projectId!);
|
||||
const fieldsWithTypes = await ContactService.getAvailableFields(auth.projectId!);
|
||||
|
||||
return res.status(200).json({
|
||||
fields,
|
||||
count: fields.length,
|
||||
fields: fieldsWithTypes,
|
||||
count: fieldsWithTypes.length,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('[CONTACTS] Failed to get available fields:', error);
|
||||
|
||||
@@ -74,20 +74,20 @@ export class Segments {
|
||||
@CatchAsync
|
||||
public async create(req: Request, res: Response, next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
const {name, description, filters, trackMembership} = req.body;
|
||||
const {name, description, condition, trackMembership} = req.body;
|
||||
|
||||
if (!name) {
|
||||
return res.status(400).json({error: 'Name is required'});
|
||||
}
|
||||
|
||||
if (!filters || !Array.isArray(filters)) {
|
||||
return res.status(400).json({error: 'Filters must be an array'});
|
||||
if (!condition || typeof condition !== 'object') {
|
||||
return res.status(400).json({error: 'Condition is required and must be an object'});
|
||||
}
|
||||
|
||||
const segment = await SegmentService.create(auth.projectId!, {
|
||||
name,
|
||||
description,
|
||||
filters,
|
||||
condition,
|
||||
trackMembership,
|
||||
});
|
||||
|
||||
@@ -104,20 +104,20 @@ export class Segments {
|
||||
public async update(req: Request, res: Response, next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
const segmentId = req.params.id;
|
||||
const {name, description, filters, trackMembership} = req.body;
|
||||
const {name, description, condition, trackMembership} = req.body;
|
||||
|
||||
if (!segmentId) {
|
||||
return res.status(400).json({error: 'Segment ID is required'});
|
||||
}
|
||||
|
||||
if (filters !== undefined && !Array.isArray(filters)) {
|
||||
return res.status(400).json({error: 'Filters must be an array'});
|
||||
if (condition !== undefined && typeof condition !== 'object') {
|
||||
return res.status(400).json({error: 'Condition must be an object'});
|
||||
}
|
||||
|
||||
const segment = await SegmentService.update(auth.projectId!, segmentId, {
|
||||
name,
|
||||
description,
|
||||
filters,
|
||||
condition,
|
||||
trackMembership,
|
||||
});
|
||||
|
||||
|
||||
@@ -370,4 +370,24 @@ export class Workflows {
|
||||
|
||||
return res.status(200).json(execution);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /workflows/:id/executions/cancel-all
|
||||
* Cancel all active executions for a workflow
|
||||
*/
|
||||
@Post(':id/executions/cancel-all')
|
||||
@Middleware([requireAuth])
|
||||
@CatchAsync
|
||||
public async cancelAllExecutions(req: Request, res: Response, next: NextFunction) {
|
||||
const auth = res.locals.auth as AuthResponse;
|
||||
const workflowId = req.params.id;
|
||||
|
||||
if (!workflowId) {
|
||||
return res.status(400).json({error: 'Workflow ID is required'});
|
||||
}
|
||||
|
||||
const result = await WorkflowService.cancelAllExecutions(auth.projectId!, workflowId);
|
||||
|
||||
return res.status(200).json(result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user