chore: Remove comments

This commit is contained in:
Dries Augustyns
2026-01-01 14:08:08 +01:00
parent 76786b2eae
commit da7f3e5718
19 changed files with 22 additions and 96 deletions
+7 -1
View File
@@ -156,4 +156,10 @@ Required for builds and deployment (see turbo.json and .env.example):
allows the same Docker image to be used across different environments by simply changing environment variables at allows the same Docker image to be used across different environments by simply changing environment variables at
runtime runtime
- **Frontend Variables**: Next.js apps use `NEXT_PUBLIC_*` prefixed variables that are embedded at build time for - **Frontend Variables**: Next.js apps use `NEXT_PUBLIC_*` prefixed variables that are embedded at build time for
client-side access 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.
-1
View File
@@ -23,7 +23,6 @@ export class Campaigns {
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);
// Validate audience-specific fields
if (audienceType === CampaignAudienceType.SEGMENT && !segmentId) { if (audienceType === CampaignAudienceType.SEGMENT && !segmentId) {
throw new HttpException(400, 'Segment ID is required for SEGMENT audience type'); throw new HttpException(400, 'Segment ID is required for SEGMENT audience type');
} }
-1
View File
@@ -14,7 +14,6 @@ const upload = multer({
fileSize: 5 * 1024 * 1024, // 5MB max file size fileSize: 5 * 1024 * 1024, // 5MB max file size
}, },
fileFilter: (_req, file, cb) => { fileFilter: (_req, file, cb) => {
// Only accept CSV files
if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) { if (file.mimetype === 'text/csv' || file.originalname.endsWith('.csv')) {
cb(null, true); cb(null, true);
} else { } else {
-1
View File
@@ -13,7 +13,6 @@ const upload = multer({
fileSize: 10 * 1024 * 1024, // 10MB max file size fileSize: 10 * 1024 * 1024, // 10MB max file size
}, },
fileFilter: (_req, file, cb) => { fileFilter: (_req, file, cb) => {
// Only accept image files
const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml']; const allowedMimeTypes = ['image/jpeg', 'image/jpg', 'image/png', 'image/gif', 'image/webp', 'image/svg+xml'];
if (allowedMimeTypes.includes(file.mimetype)) { if (allowedMimeTypes.includes(file.mimetype)) {
@@ -29,12 +29,6 @@ describe('Email Processor', () => {
status: EmailStatus.PENDING, 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 // Simulate processing
await prisma.email.update({ await prisma.email.update({
@@ -16,7 +16,6 @@ describe('Request Logger Middleware', () => {
projectId = project.id; projectId = project.id;
userId = user.id; userId = user.id;
// Mock request object
req = { req = {
method: 'POST', method: 'POST',
path: '/v1/send', path: '/v1/send',
@@ -334,12 +333,7 @@ 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));
// 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 // Restore original value
if (originalEnv !== undefined) { if (originalEnv !== undefined) {
process.env.REQUEST_LOGGING = originalEnv; process.env.REQUEST_LOGGING = originalEnv;
@@ -68,8 +68,6 @@ export class AnalyticsService {
return JSON.parse(cached); 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< const result = await prisma.$queryRaw<
{ {
date: Date; date: Date;
-1
View File
@@ -46,7 +46,6 @@ export async function initializeBucket(): Promise<void> {
let bucketExists = true; let bucketExists = true;
try { try {
// Check if bucket exists
await s3Client.send( await s3Client.send(
new HeadBucketCommand({ new HeadBucketCommand({
Bucket: S3_BUCKET, Bucket: S3_BUCKET,
-1
View File
@@ -9,7 +9,6 @@ import {HttpException} from '../exceptions/index.js';
import {EventService} from './EventService.js'; import {EventService} from './EventService.js';
import {NtfyService} from './NtfyService.js'; import {NtfyService} from './NtfyService.js';
// Re-export types for use in other services
export type {FilterCondition, FilterGroup, SegmentFilter} from '@plunk/types'; export type {FilterCondition, FilterGroup, SegmentFilter} from '@plunk/types';
/** /**
@@ -20,7 +20,6 @@ import {EmailService} from './EmailService.js';
import {NtfyService} from './NtfyService.js'; import {NtfyService} from './NtfyService.js';
import {QueueService} from './QueueService.js'; import {QueueService} from './QueueService.js';
// Type aliases for workflow execution context
type StepConfig = Prisma.JsonValue; type StepConfig = Prisma.JsonValue;
type StepResult = Record<string, unknown>; type StepResult = Record<string, unknown>;
type WorkflowExecutionWithRelations = WorkflowExecution & {contact: Contact; workflow: Workflow}; type WorkflowExecutionWithRelations = WorkflowExecution & {contact: Contact; workflow: Workflow};
+1 -2
View File
@@ -27,8 +27,7 @@ export function ActivityFeed({typeFilter, dateRangeDays = 30, contactId}: Activi
return date.toISOString(); return date.toISOString();
}, [dateRangeDays]); }, [dateRangeDays]);
// Fetch activities const fetchActivities = useCallback(
const fetchActivities = useCallback(
async (cursor?: string) => { async (cursor?: string) => {
try { try {
if (cursor) { if (cursor) {
+2 -4
View File
@@ -50,8 +50,7 @@ export function useAnalytics(options: UseAnalyticsOptions = {}): AnalyticsData {
}, [days, options.startDate, options.endDate]); }, [days, options.startDate, options.endDate]);
/* eslint-enable react-hooks/purity */ /* eslint-enable react-hooks/purity */
// Fetch activity stats const {
const {
data: stats, data: stats,
error: statsError, error: statsError,
isLoading: statsLoading, isLoading: statsLoading,
@@ -61,8 +60,7 @@ export function useAnalytics(options: UseAnalyticsOptions = {}): AnalyticsData {
dedupingInterval: 10000, // Prevent duplicate requests within 10 seconds dedupingInterval: 10000, // Prevent duplicate requests within 10 seconds
}); });
// Fetch time series data (if endpoint exists) const {
const {
data: timeSeries, data: timeSeries,
error: timeSeriesError, error: timeSeriesError,
isLoading: timeSeriesLoading, isLoading: timeSeriesLoading,
+1 -2
View File
@@ -46,8 +46,7 @@ export function useContactFields() {
}, },
); );
// Extract just the field names as strings const fieldNames = (data?.fields || []).map(f => f.field);
const fieldNames = (data?.fields || []).map(f => f.field);
return { return {
fields: fieldNames, fields: fieldNames,
+6 -12
View File
@@ -72,8 +72,7 @@ export default function AnalyticsPage() {
return {startDate: start.toISOString(), endDate: end.toISOString()}; return {startDate: start.toISOString(), endDate: end.toISOString()};
}, [days]); }, [days]);
// Fetch campaign stats from API const {data: campaignStats} = useSWR<{
const {data: campaignStats} = useSWR<{
total: number; total: number;
active: number; active: number;
completed: number; completed: number;
@@ -85,8 +84,7 @@ export default function AnalyticsPage() {
dedupingInterval: 10000, dedupingInterval: 10000,
}); });
// Fetch top events from API const {data: topEvents} = useSWR<
const {data: topEvents} = useSWR<
{ {
name: string; name: string;
count: number; count: number;
@@ -98,8 +96,7 @@ export default function AnalyticsPage() {
dedupingInterval: 10000, dedupingInterval: 10000,
}); });
// Fetch top campaigns from API const {data: topCampaigns} = useSWR<
const {data: topCampaigns} = useSWR<
{ {
id: string; id: string;
subject: string; subject: string;
@@ -115,8 +112,7 @@ export default function AnalyticsPage() {
dedupingInterval: 10000, dedupingInterval: 10000,
}); });
// Process time series data for charts const chartData = useMemo(() => {
const chartData = useMemo(() => {
if (timeSeries && timeSeries.length > 0) { if (timeSeries && timeSeries.length > 0) {
return timeSeries.map(point => ({ return timeSeries.map(point => ({
date: new Date(point.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'}), date: new Date(point.date).toLocaleDateString('en-US', {month: 'short', day: 'numeric'}),
@@ -131,13 +127,11 @@ export default function AnalyticsPage() {
return []; return [];
}, [timeSeries]); }, [timeSeries]);
// Check if we have any real data const hasData = useMemo(() => {
const hasData = useMemo(() => {
return chartData.some(point => point.emails > 0 || point.opens > 0 || point.clicks > 0); return chartData.some(point => point.emails > 0 || point.opens > 0 || point.clicks > 0);
}, [chartData]); }, [chartData]);
// Calculate cumulative totals const cumulativeTotals = useMemo(() => {
const cumulativeTotals = useMemo(() => {
return chartData.reduce( return chartData.reduce(
(acc, day) => ({ (acc, day) => ({
emails: acc.emails + (day.emails || 0), emails: acc.emails + (day.emails || 0),
+1 -2
View File
@@ -67,8 +67,7 @@ export default function ContactsPage() {
{revalidateOnFocus: false}, {revalidateOnFocus: false},
); );
// Update contacts when data changes useEffect(() => {
useEffect(() => {
if (data) { if (data) {
setContacts(data.data); setContacts(data.data);
if (!cursor) { if (!cursor) {
+1 -2
View File
@@ -33,8 +33,7 @@ export default function Manage() {
const data = await network.fetch<ContactInfo>('GET', `/contacts/public/${id}`); const data = await network.fetch<ContactInfo>('GET', `/contacts/public/${id}`);
setContact(data); setContact(data);
// Load translations for the project's language const t = await createTranslator(data.language || 'en');
const t = await createTranslator(data.language || 'en');
setTranslator(t); setTranslator(t);
setError(null); setError(null);
+1 -2
View File
@@ -57,8 +57,7 @@ export default function SegmentDetailPage() {
const [isComputing, setIsComputing] = useState(false); const [isComputing, setIsComputing] = useState(false);
const [showDeleteDialog, setShowDeleteDialog] = useState(false); const [showDeleteDialog, setShowDeleteDialog] = useState(false);
// Initialize form when segment loads useEffect(() => {
useEffect(() => {
if (segment) { if (segment) {
setName(segment.name); setName(segment.name);
setDescription(segment.description || ''); setDescription(segment.description || '');
+1 -2
View File
@@ -1556,8 +1556,7 @@ function EditStepDialog({step, workflowId, open, onOpenChange, onSuccess}: EditS
const [name, setName] = useState(step.name); const [name, setName] = useState(step.name);
const [isSubmitting, setIsSubmitting] = useState(false); 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 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 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'; const bgColor = STEP_TYPE_BG[step.type as keyof typeof STEP_TYPE_BG] || '#f3f4f6';
-46
View File
@@ -7,58 +7,12 @@
import {Prisma} from '@plunk/db'; 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 { export function toPrismaJson<T>(value: T | null | undefined): Prisma.InputJsonValue {
// Prisma.InputJsonValue accepts: string | number | boolean | null | JsonObject | JsonArray // Prisma.InputJsonValue accepts: string | number | boolean | null | JsonObject | JsonArray
// We trust that T is JSON-serializable at runtime (including null) // We trust that T is JSON-serializable at runtime (including null)
return value as unknown as Prisma.InputJsonValue; 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 { export function fromPrismaJson<T>(value: Prisma.JsonValue): T {
return value as unknown as T; return value as unknown as T;
} }