chore: Remove comments
This commit is contained in:
@@ -157,3 +157,9 @@ Required for builds and deployment (see turbo.json and .env.example):
|
||||
runtime
|
||||
- **Frontend Variables**: Next.js apps use `NEXT_PUBLIC_*` prefixed variables that are embedded at build time for
|
||||
client-side access
|
||||
|
||||
## Plugins
|
||||
There are two plugins installed for you to use.
|
||||
|
||||
- frontend-design: This plugin can help you to create polished user interfaces. Use it when working on design-related tasks.
|
||||
- superpowers: This plugin can help you with advanced tasks such as refactorings, new features or architectural changes. Use it when you need extra assistance beyond basic coding.
|
||||
@@ -23,7 +23,6 @@ export class Campaigns {
|
||||
const {name, description, subject, body, from, fromName, replyTo, audienceType, audienceCondition, segmentId} =
|
||||
CampaignSchemas.create.parse(req.body);
|
||||
|
||||
// Validate audience-specific fields
|
||||
if (audienceType === CampaignAudienceType.SEGMENT && !segmentId) {
|
||||
throw new HttpException(400, 'Segment ID is required for SEGMENT audience type');
|
||||
}
|
||||
|
||||
@@ -14,7 +14,6 @@ const upload = multer({
|
||||
fileSize: 5 * 1024 * 1024, // 5MB max file size
|
||||
},
|
||||
fileFilter: (_req, file, cb) => {
|
||||
// Only accept CSV files
|
||||
if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,6 @@ const upload = multer({
|
||||
fileSize: 10 * 1024 * 1024, // 10MB max file size
|
||||
},
|
||||
fileFilter: (_req, file, cb) => {
|
||||
// Only accept image files
|
||||
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
|
||||
|
||||
if (allowedMimeTypes.includes(file.mimetype)) {
|
||||
|
||||
@@ -29,12 +29,6 @@ describe('Email Processor', () => {
|
||||
status: EmailStatus.PENDING,
|
||||
});
|
||||
|
||||
// Mock the email processor logic
|
||||
// In a real implementation, you would:
|
||||
// 1. Create job tester
|
||||
// 2. Mock SES service
|
||||
// 3. Process the job
|
||||
// 4. Verify status changes
|
||||
|
||||
// Simulate processing
|
||||
await prisma.email.update({
|
||||
|
||||
@@ -16,7 +16,6 @@ describe('Request Logger Middleware', () => {
|
||||
projectId = project.id;
|
||||
userId = user.id;
|
||||
|
||||
// Mock request object
|
||||
req = {
|
||||
method: 'POST',
|
||||
path: '/v1/send',
|
||||
@@ -334,11 +333,6 @@ describe('Request Logger Middleware', () => {
|
||||
await res.json!({success: true});
|
||||
await new Promise(resolve => setTimeout(resolve, 100));
|
||||
|
||||
// TODO: Add assertion to verify request was NOT logged when disabled
|
||||
// const loggedRequest = await prisma.apiRequest.findUnique({
|
||||
// where: {id: 'test-request-id-123'},
|
||||
// });
|
||||
// expect(loggedRequest).toBeNull();
|
||||
|
||||
// Restore original value
|
||||
if (originalEnv !== undefined) {
|
||||
|
||||
@@ -68,8 +68,6 @@ export class AnalyticsService {
|
||||
return JSON.parse(cached);
|
||||
}
|
||||
|
||||
// Raw SQL query for efficient daily aggregation
|
||||
// Using raw SQL because Prisma's groupBy is less efficient for date truncation
|
||||
const result = await prisma.$queryRaw<
|
||||
{
|
||||
date: Date;
|
||||
|
||||
@@ -46,7 +46,6 @@ export async function initializeBucket(): Promise<void> {
|
||||
let bucketExists = true;
|
||||
|
||||
try {
|
||||
// Check if bucket exists
|
||||
await s3Client.send(
|
||||
new HeadBucketCommand({
|
||||
Bucket: S3_BUCKET,
|
||||
|
||||
@@ -9,7 +9,6 @@ import {HttpException} from '../exceptions/index.js';
|
||||
import {EventService} from './EventService.js';
|
||||
import {NtfyService} from './NtfyService.js';
|
||||
|
||||
// Re-export types for use in other services
|
||||
export type {FilterCondition, FilterGroup, SegmentFilter} from '@plunk/types';
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,6 @@ import {EmailService} from './EmailService.js';
|
||||
import {NtfyService} from './NtfyService.js';
|
||||
import {QueueService} from './QueueService.js';
|
||||
|
||||
// Type aliases for workflow execution context
|
||||
type StepConfig = Prisma.JsonValue;
|
||||
type StepResult = Record<string, unknown>;
|
||||
type WorkflowExecutionWithRelations = WorkflowExecution & {contact: Contact; workflow: Workflow};
|
||||
|
||||
@@ -27,7 +27,6 @@ export function ActivityFeed({typeFilter, dateRangeDays = 30, contactId}: Activi
|
||||
return date.toISOString();
|
||||
}, [dateRangeDays]);
|
||||
|
||||
// Fetch activities
|
||||
const fetchActivities = useCallback(
|
||||
async (cursor?: string) => {
|
||||
try {
|
||||
|
||||
@@ -50,7 +50,6 @@ export function useAnalytics(options: UseAnalyticsOptions = {}): AnalyticsData {
|
||||
}, [days, options.startDate, options.endDate]);
|
||||
/* eslint-enable react-hooks/purity */
|
||||
|
||||
// Fetch activity stats
|
||||
const {
|
||||
data: stats,
|
||||
error: statsError,
|
||||
@@ -61,7 +60,6 @@ export function useAnalytics(options: UseAnalyticsOptions = {}): AnalyticsData {
|
||||
dedupingInterval: 10000, // Prevent duplicate requests within 10 seconds
|
||||
});
|
||||
|
||||
// Fetch time series data (if endpoint exists)
|
||||
const {
|
||||
data: timeSeries,
|
||||
error: timeSeriesError,
|
||||
|
||||
@@ -46,7 +46,6 @@ export function useContactFields() {
|
||||
},
|
||||
);
|
||||
|
||||
// Extract just the field names as strings
|
||||
const fieldNames = (data?.fields || []).map(f => f.field);
|
||||
|
||||
return {
|
||||
|
||||
@@ -72,7 +72,6 @@ export default function AnalyticsPage() {
|
||||
return {startDate: start.toISOString(), endDate: end.toISOString()};
|
||||
}, [days]);
|
||||
|
||||
// Fetch campaign stats from API
|
||||
const {data: campaignStats} = useSWR<{
|
||||
total: number;
|
||||
active: number;
|
||||
@@ -85,7 +84,6 @@ export default function AnalyticsPage() {
|
||||
dedupingInterval: 10000,
|
||||
});
|
||||
|
||||
// Fetch top events from API
|
||||
const {data: topEvents} = useSWR<
|
||||
{
|
||||
name: string;
|
||||
@@ -98,7 +96,6 @@ export default function AnalyticsPage() {
|
||||
dedupingInterval: 10000,
|
||||
});
|
||||
|
||||
// Fetch top campaigns from API
|
||||
const {data: topCampaigns} = useSWR<
|
||||
{
|
||||
id: string;
|
||||
@@ -115,7 +112,6 @@ export default function AnalyticsPage() {
|
||||
dedupingInterval: 10000,
|
||||
});
|
||||
|
||||
// Process time series data for charts
|
||||
const chartData = useMemo(() => {
|
||||
if (timeSeries && timeSeries.length > 0) {
|
||||
return timeSeries.map(point => ({
|
||||
@@ -131,12 +127,10 @@ export default function AnalyticsPage() {
|
||||
return [];
|
||||
}, [timeSeries]);
|
||||
|
||||
// Check if we have any real data
|
||||
const hasData = useMemo(() => {
|
||||
return chartData.some(point => point.emails > 0 || point.opens > 0 || point.clicks > 0);
|
||||
}, [chartData]);
|
||||
|
||||
// Calculate cumulative totals
|
||||
const cumulativeTotals = useMemo(() => {
|
||||
return chartData.reduce(
|
||||
(acc, day) => ({
|
||||
|
||||
@@ -67,7 +67,6 @@ export default function ContactsPage() {
|
||||
{revalidateOnFocus: false},
|
||||
);
|
||||
|
||||
// Update contacts when data changes
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setContacts(data.data);
|
||||
|
||||
@@ -33,7 +33,6 @@ export default function Manage() {
|
||||
const data = await network.fetch<ContactInfo>('GET', `/contacts/public/${id}`);
|
||||
setContact(data);
|
||||
|
||||
// Load translations for the project's language
|
||||
const t = await createTranslator(data.language || 'en');
|
||||
setTranslator(t);
|
||||
|
||||
|
||||
@@ -57,7 +57,6 @@ export default function SegmentDetailPage() {
|
||||
const [isComputing, setIsComputing] = useState(false);
|
||||
const [showDeleteDialog, setShowDeleteDialog] = useState(false);
|
||||
|
||||
// Initialize form when segment loads
|
||||
useEffect(() => {
|
||||
if (segment) {
|
||||
setName(segment.name);
|
||||
|
||||
@@ -1556,7 +1556,6 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
|
||||
const [name, setName] = useState(step.name);
|
||||
const [isSubmitting, setIsSubmitting] = useState(false);
|
||||
|
||||
// Get icon and colors for this step type
|
||||
const Icon = STEP_TYPE_ICONS[step.type as keyof typeof STEP_TYPE_ICONS] || GitBranch;
|
||||
const color = STEP_TYPE_COLORS[step.type as keyof typeof STEP_TYPE_COLORS] || '#6b7280';
|
||||
const bgColor = STEP_TYPE_BG[step.type as keyof typeof STEP_TYPE_BG] || '#f3f4f6';
|
||||
|
||||
@@ -7,58 +7,12 @@
|
||||
|
||||
import {Prisma} from '@plunk/db';
|
||||
|
||||
/**
|
||||
* Safely convert a value to Prisma.InputJsonValue for storing in JSON fields
|
||||
*
|
||||
* This helper provides better type safety than direct casting while acknowledging
|
||||
* that Prisma cannot validate the JSON structure at compile time.
|
||||
*
|
||||
* @template T - The type being stored (for documentation purposes)
|
||||
* @param value - The value to convert to Prisma JSON format
|
||||
* @returns The value as Prisma.InputJsonValue
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* // Filter condition (complex nested object)
|
||||
* const condition: FilterCondition = { logic: 'AND', groups: [...] };
|
||||
* await prisma.segment.create({
|
||||
* data: {
|
||||
* condition: toPrismaJson(condition)
|
||||
* }
|
||||
* });
|
||||
*
|
||||
* // Simple object
|
||||
* const headers = { 'X-Custom': 'value' };
|
||||
* await prisma.email.create({
|
||||
* data: {
|
||||
* headers: toPrismaJson(headers)
|
||||
* }
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
export function toPrismaJson<T>(value: T | null | undefined): Prisma.InputJsonValue {
|
||||
// Prisma.InputJsonValue accepts: string | number | boolean | null | JsonObject | JsonArray
|
||||
// We trust that T is JSON-serializable at runtime (including null)
|
||||
return value as unknown as Prisma.InputJsonValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Safely convert Prisma.JsonValue to a typed value when reading from JSON fields
|
||||
*
|
||||
* IMPORTANT: This does NOT perform runtime validation. It's a type-safe way to
|
||||
* document what type you expect, but the caller must validate if needed.
|
||||
*
|
||||
* @template T - The expected type
|
||||
* @param value - The JSON value from Prisma
|
||||
* @returns The value as type T
|
||||
*
|
||||
* @example
|
||||
* ```typescript
|
||||
* const segment = await prisma.segment.findUnique({ where: { id } });
|
||||
* const condition = fromPrismaJson<FilterCondition>(segment.condition);
|
||||
* // condition is now typed as FilterCondition (but not validated)
|
||||
* ```
|
||||
*/
|
||||
export function fromPrismaJson<T>(value: Prisma.JsonValue): T {
|
||||
return value as unknown as T;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user