Skip to content

Feat : promotion#198

Merged
ChristopherTrimboli merged 28 commits intodevfrom
feat/promotion
Jan 13, 2026
Merged

Feat : promotion#198
ChristopherTrimboli merged 28 commits intodevfrom
feat/promotion

Conversation

@samarth30
Copy link
Copy Markdown
Contributor

@samarth30 samarth30 commented Jan 7, 2026

This pull request introduces new APIs for Discord automation and improves the promotional asset generation endpoint. The main changes include adding endpoints to manage and trigger Discord automation for app announcements, updating the promotional asset generation API to support custom prompts and improved credit calculation, and enhancing cost estimation and asset storage.

New Discord Automation APIs:

  • Added a cron job endpoint (app/api/cron/social-automation/route.ts) for scheduled social media automation across Discord, Telegram, and Twitter, with secure authorization and logging.
  • Introduced endpoints to manage Discord automation for apps:

Promotional Asset Generation Improvements:

General Improvements:

These changes provide robust automation controls for Discord announcements and a more flexible, user-friendly promotional asset generation workflow.


Note

Introduces comprehensive social automation and upgrades to promotional assets.

  • Adds api/cron/social-automation to process scheduled Discord/Telegram/Twitter posts with interval windowing and concurrency limits
  • Discord: OAuth/callback, guilds/channels list & refresh, connect/disconnect/status; per-app automation GET/POST/DELETE and manual POST announcement
  • Telegram: connect/disconnect/status, scan chats, org-scoped webhook; per-app automation GET/POST/DELETE and manual POST announcement
  • Twitter: connect/callback/status/disconnect; per-app automation config and manual tweet POST
  • Promote assets API: supports customPrompt, uses pricing constants/estimator, deducts credits, refunds on failures, and stores generated assets on the app
  • New server actions to upload/delete promotional assets with blob storage and ownership validation

Written by Cursor Bugbot for commit dae5c1a. This will update automatically on new commits. Configure here.

- Add Telegram bot connection flow with token validation
- Create telegram_chats table to store detected channels/groups
- Add telegram_automation field to apps schema
- Implement webhook handler for incoming Telegram updates
- Add scan-chats endpoint to discover bot's chats via polling
- Create app-specific automation settings (auto-reply, auto-announce)
- Add AI-powered announcement and reply generation
- Build UI components for bot connection and chat selection
- Add comprehensive integration tests for all endpoints
- Add Discord OAuth flow for bot integration
- Create discord_guilds and discord_channels tables to manage server and channel data
- Implement Discord automation service for message posting and status management
- Add API endpoints for managing Discord connections, channels, and automation settings
- Enhance UI components for Discord integration in app settings and promotion dialogs
- Include comprehensive tests for Discord-related functionalities
…ation

- Extend PromotionConfigSchema to include telegramAutomation and discordAutomation fields
- Update validation logic to ensure required configurations are provided for Telegram and Discord channels
- Modify PromoteAppDialog to handle results from new automation channels
- Implement Discord automation execution logic in AppPromotionService
- Add error handling and logging for automation processes
- Update PromotionConfigSchema to include channelId and groupId for Telegram automation
- Implement logic to set webhook only for HTTPS URLs, with fallback for local development
- Add error handling and logging for Telegram automation setup
- Extend app promotion service to execute Telegram automation with new configuration options
…tomation cron

- Fix discordAutomation config not initialized when channel toggled
- Add input clamping for interval values (min 30/60, max 1440)
- Display 'Not configured' for placeholder URLs in review step
- Add post preview generation API (/api/v1/apps/[id]/promote/preview)
- Add social automation cron job for periodic announcements
- Initialize automation configs with valid defaults when selecting channel
- Add maxDuration=60 to promote route for long operations
- Fix type safety: remove unnecessary ! assertions, fix Record casts
- Add Quick Actions section with Post Now buttons
- Add CSP and image config for Discord CDN
- Add integration tests for promotion preview API

Fixes: interval validation, double-click protection, loading states
…eness

- Add website scraping to extract real context (title, description, features)
- Auto-detect product type (SaaS, AI, Web3, etc.) and industry
- Add optional custom prompt field for user-provided instructions
- Fix image generation model configuration (use model string directly)
- Add better error handling and logging with stream error capture
- Add prompt length truncation to prevent model issues
- Integrate promotional images into Discord and Telegram posts
- Add 25s timeout to Discord sendMessage for reliability
- Fix Telegram post route to handle empty request bodies
- Apply dark glassmorphism styling consistent with other modals
- Add inner scrollable content area to prevent outer modal scroll
- Update channel cards with dark theme colors and hover effects
- Style form inputs, buttons, and text for dark theme
- Remove auto-reply feature from Telegram automation config
- Fix footer styling with proper borders and backgrounds
- Resolve merge conflicts in db/schemas/apps.ts (keep both automation + deployment fields)
- Resolve merge conflicts in next.config.ts (merge CSP headers)
- Regenerate bun.lock
- Miniapp removed in upstream
@vercel
Copy link
Copy Markdown

vercel Bot commented Jan 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Review Updated (UTC)
eliza-cloud-v2 Error Error Jan 13, 2026 8:39pm
miniapp Error Error Jan 13, 2026 8:39pm

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jan 7, 2026

Important

Review skipped

Auto reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.


Note

🎁 Summarized by CodeRabbit Free

Your organization is on the Free plan. CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please upgrade your subscription to CodeRabbit Pro by visiting https://app.coderabbit.ai/login.

Comment @coderabbitai help to get the list of available commands and usage tips.

@samarth30 samarth30 closed this Jan 7, 2026
@claude
Copy link
Copy Markdown

claude Bot commented Jan 7, 2026

PR #198 Code Review: Social Media Automation Feature

Overview

This PR introduces comprehensive social media automation across Discord, Telegram, and Twitter platforms, including promotional asset generation with AI. The scope is significant: 78 files changed, 12,183 additions, 721 deletions.


✅ Strengths

1. Well-Structured Architecture

  • Clear separation of concerns with dedicated service layers for each platform
  • Consistent patterns across Discord, Telegram, and Twitter implementations
  • Good use of TypeScript types and interfaces

2. Security

  • ✅ Proper authentication via requireAuthOrApiKeyWithOrg on all API routes
  • ✅ Cron job protection with CRON_SECRET (consistent with existing cron patterns)
  • ✅ Organization-level credential isolation
  • ✅ Input validation with Zod schemas
  • ✅ Rate limiting with 25-second timeouts on external API calls

3. Database Design

  • Well-designed JSONB schema for automation configs in apps table (lines 165-232)
  • Appropriate defaults and type safety
  • Proper indexing strategy

4. Error Handling & Logging

  • Comprehensive logging throughout with structured context
  • Graceful error handling with fallbacks
  • Proper refund logic for failed asset generations

🔴 Critical Issues

1. SQL Injection Risk in Cron Job (High Priority)

File: app/api/cron/social-automation/route.ts:86-89

.where(
  sql`${apps.discord_automation}->>'enabled' = 'true' 
      OR ${apps.telegram_automation}->>'enabled' = 'true' 
      OR ${apps.twitter_automation}->>'enabled' = 'true'`
)

Issue: Using template literals with sql can be dangerous. While the immediate values are hardcoded strings here, this pattern is risky.

Fix: Use parameterized queries with proper Drizzle ORM operators:

import { or, eq, sql } from 'drizzle-orm';

.where(
  or(
    sql`${apps.discord_automation}->>'enabled' = ${true}`,
    sql`${apps.telegram_automation}->>'enabled' = ${true}`,
    sql`${apps.twitter_automation}->>'enabled' = ${true}`
  )
)

Or better yet, use Drizzle's JSON operators if available.

2. Missing Error Recovery in Cron Loop

File: app/api/cron/social-automation/route.ts:179-213

Issue: The cron job processes apps sequentially without try-catch around individual app processing. If one app throws an unexpected error, it could crash the entire cron job.

Fix: Wrap each app's processing in a try-catch:

for (const app of appsWithAutomation) {
  try {
    // Process Discord
    const discordResult = await processDiscordAutomation(app);
    // ... rest of processing
  } catch (error) {
    logger.error('[SocialAutomation Cron] App processing failed', {
      appId: app.id,
      error: error instanceof Error ? error.message : 'Unknown error',
    });
    // Continue to next app
  }
}

3. Race Condition in Credit Deduction

File: app/api/v1/apps/[id]/promote/assets/route.ts:68-91

Issue: Credits are deducted upfront, then asset generation occurs, then failed generations are refunded. If the process crashes between deduction and refund, credits are lost. There's also a race condition if the same endpoint is called concurrently.

Fix: Consider using database transactions or implementing idempotency keys for asset generation requests.


⚠️ Important Issues

4. Validation Gap in Interval Ranges

File: app/api/v1/apps/[id]/promote/route.ts:29-76

Issue: Schema validation doesn't enforce that intervalMin < intervalMax:

postIntervalMin: z.number().int().min(30).max(1440).default(90),
postIntervalMax: z.number().int().min(60).max(1440).default(150),

Fix: Add .refine() to enforce the relationship:

.refine(data => !data.postIntervalMin || !data.postIntervalMax || 
                 data.postIntervalMin < data.postIntervalMax, {
  message: "postIntervalMin must be less than postIntervalMax"
})

5. Non-Deterministic Randomness in Cron Timing

File: app/api/cron/social-automation/route.ts:75-77

const targetInterval =
  minInterval + Math.random() * (maxInterval - minInterval);

Issue: Using Math.random() for scheduling means intervals aren't reproducible or testable. This also means you can't predict when posts will actually happen.

Recommendation: Consider storing the nextScheduledAt timestamp in the database for predictability and debugging. Calculate it once when the last post completes.

6. Missing Transaction for Asset Persistence

File: app/api/v1/apps/[id]/promote/assets/route.ts:102-119

Issue: Assets are saved to the app record without a transaction. If this fails after credits were deducted, the user paid for assets they don't have stored.

Fix: Wrap credit operations and asset persistence in a transaction.


💡 Suggestions

7. Code Duplication in Automation Routes

The three automation route files (discord-automation/route.ts, telegram-automation/route.ts, twitter-automation/route.ts) share nearly identical structure. Consider creating a higher-order function or base class to reduce duplication.

8. Hardcoded AI Model References

  • Discord/Telegram use gpt-4o-mini (hardcoded in services)
  • Twitter uses anthropic/claude-sonnet-4 via gateway

Recommendation: Extract model selection to configuration for easier updates and A/B testing.

9. Missing Rate Limit Headers

API responses don't include rate limit headers (X-RateLimit-Remaining, etc.) which would help developers manage their usage.

10. Insufficient Type Safety in JSONB Fields

While TypeScript types exist for automation configs, there's no runtime validation when reading from the database. If data is corrupted or manually edited, it could cause issues.

Fix: Add Zod schema validation when reading automation configs from the database.

11. No Retry Logic for Failed Posts

When automation posts fail (network errors, API rate limits), they're logged but not retried. Consider implementing exponential backoff retry logic.

12. Promotional Asset Type Limitation

File: app/api/v1/apps/[id]/promote/assets/route.ts:53-56

const imageCount = parsed.data.includeCopy !== false ? 1 : 0; // 1 social card
const bannerCount = parsed.data.includeAdBanners ? 1 : 0; // 1 banner

Issue: Comments indicate "simplified: 1 social card + 1 banner" but this seems arbitrary. Users might want multiple variations for A/B testing.

Recommendation: Allow users to specify counts (with reasonable limits) or clearly document why only 1 of each.


📋 Minor Issues

13. Inconsistent Import Styles

// Sometimes:
import { NextRequest, NextResponse } from "next/server";

// Other times:
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";

Fix: Standardize on type-only imports where applicable for better tree-shaking.

14. Missing JSDoc Comments

While service files have good documentation, many API route handlers lack JSDoc comments explaining parameters and return types.

15. Trailing Commas Inconsistency

I see the recent commit 37fe89d3 addressed trailing commas in apps schema, but check for consistency across all new files.


🧪 Testing Concerns

Missing Test Coverage

No test files were added for:

  • Cron job scheduling logic
  • Asset generation and refund flows
  • Automation service post methods
  • Interval calculation logic

Recommendation: Add unit tests for critical paths, especially:

  • isAnnouncementDue() function with various time scenarios
  • Credit deduction/refund flows
  • Validation edge cases

🔒 Security Checklist


Performance Considerations

1. Cron Job Scaling

The cron processes apps sequentially. With many apps, this could exceed the 60-second maxDuration. Consider:

  • Processing in batches
  • Parallel processing with Promise.all() (but watch API rate limits)
  • Moving to a queue-based system (BullMQ, Inngest, etc.)

2. AI Generation Latency

Asset generation with AI is set to 60 seconds max duration. Monitor actual generation times and consider:

  • Background job processing for asset generation
  • Progress indicators for users
  • Caching strategies for similar requests

3. Database Query Optimization

The cron job's query could benefit from partial indexes:

CREATE INDEX idx_apps_discord_enabled ON apps ((discord_automation->>'enabled')) 
WHERE (discord_automation->>'enabled')::boolean = true;

📚 Documentation

Missing:

  • API endpoint documentation (OpenAPI/Swagger specs)
  • Environment variable documentation for new secrets (Twitter API keys, Discord bot tokens, Telegram bot tokens)
  • User guide for setting up automation
  • Rate limit documentation

Summary & Recommendation

This is a substantial and well-architected feature with good separation of concerns and consistent patterns. However, there are critical security and reliability issues that must be addressed before merging:

Must Fix Before Merge:

  1. ✋ SQL injection risk in cron job query
  2. ✋ Missing error recovery in cron loop
  3. ✋ Race condition in credit deduction/refund

Should Fix Before Merge:

  1. Validation gaps in interval ranges
  2. Non-deterministic scheduling logic
  3. Missing transactions for asset persistence

Consider for Follow-up:

  • Add comprehensive test coverage
  • Implement retry logic for failed posts
  • Add API documentation
  • Refactor duplicated automation route code
  • Implement queue-based processing for scalability

Overall Assessment: Approve with required changes ⚠️

Great work on the feature implementation! The architecture is solid, but please address the critical issues before merging to production.


Review generated with AI assistance - please verify all findings

Comment thread app/api/cron/social-automation/route.ts Outdated
organizationId: string;
userId: string;
redirectUrl?: string;
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error handling for JSON.parse in OAuth callback

Medium Severity

The JSON.parse(stateJson as string) call on the cached OAuth state is not wrapped in a try-catch block. If the cache returns a corrupted or non-JSON value, this will throw an unhandled exception and crash the callback handler, leaving users stuck with an error page during the Twitter OAuth flow instead of receiving a proper error redirect.

Fix in Cursor Fix in Web

Comment thread components/apps/app-promote.tsx
return NextResponse.json(
{ error: error instanceof Error ? error.message : "Failed to scan for chats" },
{ status: 500 }
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Telegram webhook not restored after scan failure

Medium Severity

The scan operation deletes the Telegram webhook at line 28 before fetching updates, but if any subsequent operation fails (database upserts, webhook re-registration, etc.), the catch block returns an error without restoring the webhook. This leaves the bot unable to receive real-time messages until a successful scan is performed, potentially breaking Telegram automation for affected users.

Fix in Cursor Fix in Web

Comment thread app/api/cron/social-automation/route.ts Outdated
Comment thread app/api/v1/apps/[id]/twitter-automation/route.ts
- Add centralized promotion-pricing.ts with 20% platform markup on AI costs
- Fix asset generation cost calculation in promote/assets route
- Update promote dialog to show 'Free' for automation setup
- Remove SEO and advertising references from promotion UI
- Fix Telegram disconnect bug - was calling delete with wrong params
- Add promotion pricing integration tests
…plugin

- Add credit billing for Discord/Telegram/Twitter AI post generation
- Revert agent-loader Twitter plugin injection (not needed for promotion)
- Replace @elizaos/plugin-twitter with twitter-api-v2 in package.json
- Update preview route to pass organization_id for billing
@samarth30 samarth30 reopened this Jan 8, 2026
Comment thread app/api/v1/apps/[id]/promote/assets/route.ts
user.organization_id,
id,
parsed.data.text,
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Twitter post endpoint ignores user-specified tweet type

Medium Severity

The request schema validates a type field with enum values "promotional", "engagement", "educational", and "announcement", but parsed.data.type is never passed to postAppTweet. The service defaults to "promotional" when generating tweets without custom text, completely ignoring the user's specified tweet type preference.

Fix in Cursor Fix in Web

Comment thread app/api/v1/apps/[id]/telegram-automation/post/route.ts
Comment thread app/api/v1/apps/[id]/discord-automation/route.ts
twitterAutomation: app.twitter_automation,
},
});
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Twitter automation routes missing error handling for service exceptions

Medium Severity

The Twitter automation routes (GET, POST, DELETE handlers and the post route) lack try-catch blocks around service calls, unlike their Discord and Telegram equivalents. When the automation service throws "App not found" or other errors, these routes return HTTP 500 instead of the appropriate status codes (404 for missing app, 400 for client errors). Discord and Telegram routes properly catch these exceptions and return correct status codes. Additionally, request.json() is called without error handling, causing malformed JSON to return 500 instead of 400.

Additional Locations (1)

Fix in Cursor Fix in Web

@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

Comprehensive Code Review - PR #198: Feat/promotion

Overview

This PR introduces significant new functionality for social media automation across Discord, Telegram, and Twitter, plus promotional asset generation. The implementation is comprehensive but has several critical security vulnerabilities and bugs that must be addressed before merging.

Recommendation: REQUEST CHANGES - Critical issues need resolution.


🔴 Critical Issues (Must Fix Before Merge)

1. SECURITY: Cron Authentication Bypass

File: app/api/cron/social-automation/route.ts:167-168

if (CRON_SECRET && authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

Issue: The condition allows completely unauthenticated access when CRON_SECRET is not set. An attacker can trigger this cron endpoint freely if the env var is missing.

Fix:

if (!CRON_SECRET || authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

2. BUG: Sequential Processing Will Timeout

File: app/api/cron/social-automation/route.ts:181-214

for (const app of appsWithAutomation) {
  const discordResult = await processDiscordAutomation(app);
  const telegramResult = await processTelegramAutomation(app);
  const twitterResult = await processTwitterAutomation(app);
}

Issue: With maxDuration = 60 seconds, processing apps sequentially means:

  • 20 apps × 3 platforms × 3 seconds each = 180 seconds (exceeds timeout)
  • Each automation involves AI generation (slow) + API calls

Fix: Process apps in parallel:

await Promise.allSettled(
  appsWithAutomation.map(async (app) => {
    const [discordResult, telegramResult, twitterResult] = await Promise.allSettled([
      processDiscordAutomation(app),
      processTelegramAutomation(app),
      processTwitterAutomation(app),
    ]);
    // Handle results...
  })
);

3. SECURITY: Credit Deduction Without Refund

Files:

  • lib/services/discord-automation/app-automation.ts:191-222
  • lib/services/telegram-automation/app-automation.ts:173-248
  • lib/services/twitter-automation/app-automation.ts:158-167

Issue: Credits are deducted before AI generation succeeds. If generation fails, users are charged but receive nothing.

const deduction = await creditsService.deductCredits({...});
if (!deduction.success) { throw new Error(...); }
const result = await generateText({...}); // If this fails, no refund!

Fix: Wrap AI calls in try-catch with refund:

const deduction = await creditsService.deductCredits({...});
if (!deduction.success) { throw new Error(...); }

try {
  const result = await generateText({...});
  return result;
} catch (error) {
  // Refund on failure
  await creditsService.refundCredits({
    organizationId,
    amount: cost,
    description: "Refund for failed AI generation",
  });
  throw error;
}

4. SECURITY: SSRF Vulnerability in Asset Generation

File: lib/services/app-promotion-assets.ts:83-244

const response = await fetch(url, {...});
const html = await response.text();

Issue: Fetches arbitrary URLs from app.website_url without validation. Attackers can:

  • Target internal services (http://localhost:6379, http://169.254.169.254/)
  • Download gigabytes of data (no size limit)
  • Scan internal network

Fix:

// Validate URL before fetching
const parsedUrl = new URL(url);
if (parsedUrl.protocol !== 'http:' && parsedUrl.protocol !== 'https:') {
  throw new Error('Invalid protocol');
}
if (parsedUrl.hostname === 'localhost' || parsedUrl.hostname === '127.0.0.1' || 
    parsedUrl.hostname.startsWith('169.254.') || parsedUrl.hostname.startsWith('10.') ||
    parsedUrl.hostname.startsWith('172.16.') || parsedUrl.hostname.startsWith('192.168.')) {
  throw new Error('Cannot fetch from internal IP addresses');
}

// Limit response size
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), 10_000);
const response = await fetch(url, { 
  signal: controller.signal,
  size: 5 * 1024 * 1024, // 5MB limit
});
clearTimeout(timeoutId);

5. SECURITY: Prompt Injection in Asset Generation

File: app/api/v1/apps/[id]/promote/assets/route.ts:32-34

customPrompt: z.string().max(1000).optional(),

Issue: User-provided customPrompt is injected directly into AI prompts without sanitization. Attackers can:

  • Generate inappropriate content
  • Extract information via prompt injection
  • Manipulate AI behavior

Fix: Either:

  1. Remove customPrompt feature entirely, OR
  2. Use predefined templates with parameter substitution, OR
  3. Implement strict content filtering + moderation after generation

🟠 High Priority Issues

6. BUG: Non-Deterministic Interval Calculation

Files:

  • app/api/cron/social-automation/route.ts:76-79
  • lib/services/discord-automation/app-automation.ts:357-363
const targetInterval = minInterval + Math.random() * (maxInterval - minInterval);
return minutesSince >= targetInterval;

Issue: Math.random() is called every time the function runs, creating a different target interval each check. This makes scheduling completely unreliable.

Fix: Calculate interval once when scheduling, or use deterministic calculation:

function isAnnouncementDue(config: AutomationConfig, type: "announcement" | "post"): boolean {
  if (!config.enabled) return false;
  
  const lastTime = type === "announcement" ? config.lastAnnouncementAt : config.lastPostAt;
  if (!lastTime) return true;
  
  const lastDate = new Date(lastTime);
  const now = new Date();
  const minutesSince = (now.getTime() - lastDate.getTime()) / (1000 * 60);
  
  const maxInterval = type === "announcement" 
    ? (config.announceIntervalMax ?? 240) 
    : (config.postIntervalMax ?? 240);
  
  // Simply check if we've exceeded max interval (deterministic)
  return minutesSince >= maxInterval;
}

7. BUG: Incorrect Billing Logic

File: app/api/v1/apps/[id]/promote/assets/route.ts:56-61

const imageCount = parsed.data.includeCopy !== false ? 1 : 0; // WRONG!
const bannerCount = parsed.data.includeAdBanners ? 1 : 0;
const totalImageCost = (imageCount + bannerCount) * PROMO_IMAGE_COST;
const copyCost = parsed.data.includeCopy !== false ? AD_COPY_GENERATION_COST : 0;

Issue: imageCount is based on includeCopy, but should always be 1 (social card). Users are incorrectly charged:

  • Request image only (includeCopy: false) → 0 images charged → FREE (wrong!)
  • Request copy only → 1 image charged (wrong!)

Fix:

const imageCount = 1; // Always generate 1 social card
const bannerCount = parsed.data.includeAdBanners ? 1 : 0;
const totalImageCost = (imageCount + bannerCount) * PROMO_IMAGE_COST;
const copyCost = parsed.data.includeCopy !== false ? AD_COPY_GENERATION_COST : 0;
const totalCost = totalImageCost + copyCost;

8. BUG: Asset Overwrite Loses Previous Data

File: app/api/v1/apps/[id]/promote/assets/route.ts:104-113

await appsService.update(id, {
  promotional_assets: promotionalAssets, // Completely overwrites!
});

Issue: Overwrites all existing promotional_assets. Users lose previously generated assets every time they generate new ones.

Fix:

// Fetch current assets
const currentAssets = app.promotional_assets || [];

// Append new assets
const updatedAssets = [
  ...currentAssets,
  ...promotionalAssets,
];

await appsService.update(id, {
  promotional_assets: updatedAssets,
});

9. SECURITY: Missing Rate Limiting

All automation services

Issue: No rate limiting on expensive operations:

  • AI generation can be triggered repeatedly
  • Could drain credits intentionally or via bugs
  • No per-org limits on automation frequency

Fix: Implement rate limiting:

import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "1 h"), // 10 posts per hour
});

// In postAnnouncement():
const identifier = `discord-post-${organizationId}`;
const { success } = await ratelimit.limit(identifier);
if (!success) {
  throw new Error("Rate limit exceeded");
}

🟡 Medium Priority Issues

10. Performance: Unbounded Cache Growth

File: lib/services/app-promotion-assets.ts:61-72

private websiteContextCache = new Map<string, WebsiteContext>();

Issue: No cache expiration, size limit, or LRU eviction. Memory leak risk.

Fix: Implement LRU cache with TTL:

import { LRUCache } from "lru-cache";

private websiteContextCache = new LRUCache<string, WebsiteContext>({
  max: 500, // Max 500 entries
  ttl: 1000 * 60 * 60, // 1 hour TTL
});

11. Error Handling: Missing Try-Catch

Multiple files - Examples:

  • lib/services/discord-automation/app-automation.ts:295-302
  • lib/services/app-promotion-assets.ts:357-365

Issue: External API calls lack error handling, causing unhandled promise rejections.

Fix: Wrap all external calls:

try {
  const result = await discordAutomationService.sendMessage(...);
} catch (error) {
  logger.error("Discord API error", { error });
  throw new Error("Failed to send Discord message");
}

12. Code Quality: Duplicated Default Configs

File: lib/services/telegram-automation/app-automation.ts

Lines 386-392 and 461-467 have identical default config definitions. DRY violation.

Fix:

const DEFAULT_TELEGRAM_CONFIG: TelegramAutomationConfig = {
  enabled: false,
  autoReply: true,
  autoAnnounce: false,
  announceIntervalMin: 120,
  announceIntervalMax: 240,
};

// Use in both places:
const currentConfig = app.telegram_automation || DEFAULT_TELEGRAM_CONFIG;

🟢 Low Priority / Nice-to-Have

13. Type Safety: Unsafe Casts

Multiple instances of as casts without runtime validation:

const currentConfig = (app.twitter_automation || {}) as TwitterAutomationConfig;

Fix: Use Zod for runtime validation or define proper defaults.


14. Documentation: Missing JSDoc

No function-level documentation for complex logic. Add JSDoc comments explaining:

  • Credit deduction logic
  • Interval calculation
  • Asset generation flow

15. Testing: No Test Coverage

Only 5 test files for 78 changed files. Need integration tests for:

  • Cron job processing
  • Credit deduction/refund flows
  • Asset generation
  • Social automation posting

📊 Test Coverage Observations

Existing tests (tests/integration/):

  • app-promote-assets.test.ts - Good coverage
  • promotion-pricing.test.ts - Good coverage
  • ❌ Missing: Discord automation tests
  • ❌ Missing: Telegram automation tests
  • ❌ Missing: Cron job tests
  • ❌ Missing: Error scenario tests

🎯 Recommendations

Before Merging:

  1. ✅ Fix all Critical issues (1-5)
  2. ✅ Fix High Priority bugs (6-9)
  3. ✅ Add rate limiting (issue Add Claude Code GitHub Workflow #9)
  4. ✅ Add integration tests for core flows
  5. ✅ Security review of prompt handling

Post-Merge Improvements:

  • Address medium/low priority issues
  • Add comprehensive documentation
  • Implement monitoring/alerting for automation failures
  • Add feature flags for gradual rollout

📝 Detailed File-by-File Notes

app/api/cron/social-automation/route.ts

Positives:

  • Well-structured with clear separation of concerns
  • Good logging throughout
  • Comprehensive statistics in response

Issues:

lib/services/discord-automation/app-automation.ts

Positives:

  • Good separation between service and app automation
  • Embed generation is well-structured
  • Helper functions are reusable

Issues:

lib/services/telegram-automation/app-automation.ts

Positives:

  • Photo attachment support is nice
  • Good use of Promise.race for timeout

Issues:

lib/services/twitter-automation/app-automation.ts

Positives:

  • Multiple tweet type support
  • OAuth 1.0a implementation looks correct

Issues:

lib/services/app-promotion-assets.ts

Positives:

  • Comprehensive asset generation
  • Website context extraction is clever
  • Good separation of image/banner generation

Issues:


🏁 Conclusion

This PR adds valuable functionality but requires significant security and reliability fixes before production deployment. The core architecture is sound, but the implementation has critical flaws that could lead to:

  • Unauthorized cron execution
  • User billing errors
  • Credit draining
  • Server-side request forgery
  • Service timeouts at scale

Overall Assessment: 6/10

  • Architecture: 8/10
  • Implementation: 5/10
  • Security: 4/10
  • Testing: 3/10

Please address critical issues #1-5 and high-priority issues #6-9 before requesting re-review.


Generated with Claude Code 🤖

- Fix cron auth bypass: require CRON_SECRET to be set
- Fix non-deterministic interval calculation in cron job
- Add credit refunds on AI generation failures (Discord, Telegram, Twitter)
- Add SSRF protection with URL validation in asset generation
- Fix asset cost calculation (always charge for social card)
- Pass channelId/groupId override to Telegram post service
- Add interval validation for single-bound cases (all platforms)
- Increase preview route timeout to 60s
- Add error handling with refunds in asset generation route
- Add specific error messages in Twitter post route
@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

Code Review for PR #198: Feat : promotion

This is a substantial PR adding Discord, Telegram, and Twitter automation capabilities for promotional activities. Overall, the implementation is well-structured with good test coverage, but there are several security, performance, and code quality concerns that should be addressed.


🔴 Critical Issues

1. SQL Injection Risk in Cron Job ⚠️

Location: app/api/cron/social-automation/route.ts:89-97

.where(
  sql`${apps.discord_automation}->'enabled' = 'true' 
      OR ${apps.telegram_automation}->'enabled' = 'true' 
      OR ${apps.twitter_automation}->'enabled' = 'true'`
)

Issue: Using raw SQL with column interpolation is risky. While the current usage appears safe, it's better practice to use Drizzle's query builder.

Recommendation:

import { or, sql } from "drizzle-orm";

.where(
  or(
    sql`${apps.discord_automation}->'enabled' = 'true'`,
    sql`${apps.telegram_automation}->'enabled' = 'true'`,
    sql`${apps.twitter_automation}->'enabled' = 'true'`
  )
)

2. Cron Secret Validation Issue

Location: app/api/cron/social-automation/route.ts:27, 173-175

Issue: If CRON_SECRET is undefined, the check could theoretically be bypassed by sending Bearer undefined.

Recommendation: Check if CRON_SECRET exists first, return 503 if not configured.

3. SSRF Protection Has Gaps

Location: lib/services/app-promotion-assets.ts:65-97

The SSRF validation is good but incomplete:

  • ✅ Blocks localhost, 127.0.0.1, [::1]
  • ✅ Blocks 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
  • ❌ Missing 169.254.0.0/16 (link-local / AWS metadata)
  • ❌ Missing IPv6 private ranges
  • ❌ Missing hostname variations (0.0.0.0, 127.0.0.2-255)

Recommendation: Expand validation to include these ranges.


🟡 High Priority Issues

4. Race Condition in Timing Logic

Location: app/api/cron/social-automation/route.ts:77-85

Issue: The comment mentions "hash-based probability" but implementation uses fixed midpoint (0.5). All apps will attempt to post at exactly the midpoint, potentially overwhelming rate limits.

Recommendation: Use app ID hash to determine distributed position in window.

5. Credit Deduction Timing Issue

Location: lib/services/discord-automation/app-automation.ts:190-200

Issue: Credits are deducted BEFORE AI generation. If generation succeeds but Discord API fails, credits aren't refunded. Only refunded if AI generation fails.

Recommendation: Reserve credits → generate → confirm or refund pattern.

6. Sequential Processing in Cron Job ⏱️

Location: app/api/cron/social-automation/route.ts:188-221

Issue: With maxDuration = 60 seconds and sequential processing, you can only process ~6-10 apps (assuming 2-3s per post × 3 platforms). Won't scale.

Recommendation: Process in parallel with concurrency limits (use p-limit or similar).

7. Preview Generation Cost Explosion 💰

Location: app/api/v1/apps/[id]/promote/preview/route.ts:73-157

Issue: Generating 3 previews across 3 platforms = 9 AI calls. With no rate limiting, user could spam endpoint and drain credits.

Recommendation:

  1. Add upfront cost check for all previews
  2. Implement rate limiting
  3. Consider caching results

🟢 Code Quality & Best Practices

8. Inconsistent Type Casting

Multiple patterns for automation config access. Recommend creating type guard helper.

9. Incomplete Error Handling

Location: app/api/v1/apps/[id]/promote/preview/route.ts:94-99

Errors collected but never logged. Add logging for debugging.

10. Magic Numbers

Multiple hardcoded values without constants:

  • 0.5 threshold, 120/240 minute defaults, 300 char limits, etc.

🔵 Performance Considerations

11. N+1 Query Pattern

getAutomationStatus fetches guild/channel individually. Add batch methods.

12. Website Context Caching

In-memory cache won't work in serverless. Use Redis/Vercel KV.

13. Large Response in Cron Job

Returns ALL results. With 100s of apps could be massive. Return only summary + failures.


✅ Test Coverage

Strengths:

  • ✅ Good integration test coverage (5 new test files, 86 test cases)
  • ✅ Tests cover authentication, validation, error cases
  • ✅ Pricing calculations tested

Gaps:

  • ❌ No tests for cron timing logic
  • ❌ No tests for SSRF validation
  • ❌ No tests for credit refund scenarios
  • ❌ Missing edge cases: rate limits, API failures

🎯 Security Summary

Risk Level Issue Impact
🔴 Critical SSRF validation gaps Server-side request forgery
🔴 Critical Cron secret bypass Unauthorized execution
🟡 High Credit timing Financial loss
🟡 High Preview cost Credit drain

📊 Overall Assessment

Code Quality: 7/10 - Well-structured, some inconsistencies
Security: 6/10 - Basic measures in place, critical gaps exist
Performance: 6/10 - Sequential processing limits scalability
Test Coverage: 8/10 - Excellent integration tests, missing unit tests


🎬 Recommendations Priority

Before Merge (Blocking):

  1. Fix cron secret validation (feat: Add versioned /api/v1 endpoints with secure auth, credit tracking, and usage auditing #2)
  2. Fix SSRF protection gaps (feat : Add generation metadata tracking and auditing across chat, image, and video APIs #3)
  3. Add rate limiting to preview endpoint (feat: Enhance Dashboard UI with Improved Visualizations and Components #7)

High Priority:
4. Implement parallel processing in cron (#6)
5. Fix credit refund logic (#5)
6. Fix timing race condition (#4)

Medium Priority:
7. Add Redis caching (#12)
8. Add unit tests for timing logic
9. Extract magic numbers (#10)


Great work on this comprehensive feature! The implementation is solid overall, but addressing the critical security issues before merge is essential.

content,
type: postTypes[i % postTypes.length],
timestamp: new Date().toISOString(),
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Preview API labels misrepresent Discord/Telegram content types

Medium Severity

For Discord and Telegram previews, postTypes arrays define labels like "promotional", "engagement", "educational" that are included in each preview's type field. However, generateAnnouncement doesn't accept a type parameter - it generates generic announcements. The output misleadingly labels previews with different types even though content generation doesn't vary by type. Unlike Twitter's implementation which correctly passes the type to generateAppTweet, Discord and Telegram previews will show the same style of content with incorrect type labels.

Additional Locations (1)

Fix in Cursor Fix in Web

const errorUrl = `${returnUrl}${returnUrl.includes("?") ? "&" : "?"}discord=error&message=${encodeURIComponent(
errorDescription || error
)}`;
return NextResponse.redirect(new URL(errorUrl, baseUrl));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Open redirect vulnerability in OAuth callback flows

Medium Severity

The Discord and Twitter OAuth flows accept user-controlled returnUrl/redirectUrl parameters without validating they are relative or same-origin URLs. In Discord's oauth route, returnUrl is taken from query params and embedded in state. In Twitter's connect route, redirectUrl comes from the request body. Both callback handlers then redirect to these URLs. An attacker can craft an OAuth link with a malicious external URL (e.g., https://evil.com), and after the OAuth flow completes, the victim is redirected to the attacker's site. This enables phishing attacks.

Additional Locations (2)

Fix in Cursor Fix in Web

- Fix SSRF protection: block 0.0.0.0, 127.x.x.x range, IPv6 private, multicast, reserved
- Fix timing race: use hash-based distribution for post scheduling
- Add parallel processing with concurrency limit (5) in cron job
- Use Drizzle or() helper for cleaner SQL query
- Add error logging in preview endpoint
- Extract magic numbers to constants
- Return only failures in cron response to reduce payload
@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

Code Review: PR #198 - Social Media Automation Features

Executive Summary

This PR adds comprehensive Discord, Telegram, and Twitter automation capabilities (13,070 additions across 79 files). While the implementation demonstrates solid architectural patterns and good TypeScript usage, there are critical security vulnerabilities that must be addressed before merging.

Overall Assessment:

  • Code Quality: B+ (good patterns, but code duplication)
  • ⚠️ Security: C+ (2 critical vulnerabilities)
  • Performance: B (generally good, some N+1 queries)
  • ⚠️ Test Coverage: C (missing critical path tests)

Recommendation:DO NOT MERGE until critical issues are resolved.


🚨 Critical Security Issues (Must Fix Before Merge)

1. CRON_SECRET Fail-Open Vulnerability

File: app/api/cron/social-automation/route.ts:277, 327

Issue: The authentication check fails open if CRON_SECRET is not configured:

const CRON_SECRET = process.env.CRON_SECRET;

if (!CRON_SECRET || authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

If CRON_SECRET is undefined/empty, any request with ANY authorization header will be accepted due to short-circuit evaluation.

Impact: Unauthenticated users can trigger expensive automation operations, potentially draining credits.

Fix:

if (!CRON_SECRET) {
  logger.error("[Cron] CRON_SECRET not configured");
  return NextResponse.json({ error: "Server misconfiguration" }, { status: 500 });
}
if (authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

2. Telegram Webhook Has No Authentication

File: app/api/v1/telegram/webhook/[orgId]/route.ts:24-65

Issue: The webhook endpoint accepts updates from anyone who knows an organization ID. There is zero authentication of incoming requests.

export async function POST(request: Request, { params }: RouteParams): Promise<NextResponse> {
  const { orgId } = await params;
  const botToken = await telegramAutomationService.getBotToken(orgId);
  // No verification that request came from Telegram!
  const update: Update = await request.json();
  await bot.handleUpdate(update);
}

Impact: Attackers can:

  1. Inject fake Telegram messages to trigger credit-consuming AI operations
  2. Manipulate chat membership data
  3. Execute DoS attacks by flooding the endpoint
  4. Potentially access organization data through crafted updates

Fix: Implement Telegram's official webhook security using X-Telegram-Bot-Api-Secret-Token:

// Set secret when registering webhook
await telegramAutomationService.setWebhook(url, secretToken);

// Verify in webhook handler
export async function POST(request: Request, { params }: RouteParams) {
  const { orgId } = await params;
  const secretToken = await telegramAutomationService.getWebhookSecret(orgId);
  const receivedToken = request.headers.get("x-telegram-bot-api-secret-token");
  
  if (!secretToken || receivedToken !== secretToken) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }
  // ... rest of handler
}

🔴 High Priority Issues

3. N+1 Query Problem in Discord Guild Fetching

File: lib/services/discord-automation/index.ts:173-186

Issue: Individual database queries for each guild's channels:

const guildsWithCounts = await Promise.all(
  guilds.map(async (guild) => {
    const channels = await discordChannelsRepository.findByGuild(
      organizationId,
      guild.guild_id
    );
    return { ...guild, channelCount: channels.length };
  })
);

Impact: For 10 guilds, this makes 11 queries (1 for guilds + 10 for channels).

Fix: Implement batch fetching:

// In repository
async findChannelCountsByOrg(orgId: string): Promise<Map<string, number>> {
  const result = await db
    .select({ guildId: channels.guild_id, count: count() })
    .from(channels)
    .where(eq(channels.organization_id, orgId))
    .groupBy(channels.guild_id);
  return new Map(result.map(r => [r.guildId, r.count]));
}

4. No Rate Limiting on Manual Post Endpoints

Files:

  • app/api/v1/apps/[id]/discord-automation/post/route.ts
  • app/api/v1/apps/[id]/telegram-automation/post/route.ts
  • app/api/v1/apps/[id]/twitter-automation/post/route.ts

Issue: Users can spam posts as fast as their credits allow.

Impact:

  • Exhaust Discord/Telegram/Twitter API rate limits
  • Enable credit fraud schemes
  • Create spam/abuse vector

Recommendation: Add rate limiting:

import { Ratelimit } from "@upstash/ratelimit";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "1 h"), // 10 posts per hour
});

const { success } = await ratelimit.limit(`automation:${user.organization_id}`);
if (!success) {
  return NextResponse.json({ error: "Rate limit exceeded" }, { status: 429 });
}

5. URL Injection Vulnerability

Files: Multiple automation service files

Issue: User-provided URLs (app.website_url, app.app_url) are embedded directly into posts without validation.

Example: lib/services/discord-automation/app-automation.ts:299

url: app.website_url || app.app_url, // No validation!

Impact: Potential for phishing or XSS if URLs contain malicious content.

Fix:

import { isValidUrl } from "@/lib/utils/validation";

const appUrl = app.website_url || app.app_url;
if (!appUrl || !isValidUrl(appUrl)) {
  throw new Error("Invalid or missing app URL");
}

🟡 Medium Priority Issues

6. Code Duplication - Interval Validation

Files:

  • app/api/v1/apps/[id]/discord-automation/route.ts:81-105
  • app/api/v1/apps/[id]/telegram-automation/route.ts:83-107
  • app/api/v1/apps/[id]/twitter-automation/route.ts:59-83

Issue: Identical interval validation logic duplicated 3 times with constants redefined in each file.

Fix: Extract to shared utility:

// lib/utils/automation-validation.ts
export const DEFAULT_INTERVAL_MIN = 120;
export const DEFAULT_INTERVAL_MAX = 240;

export function validateIntervals(config: {
  intervalMin?: number;
  intervalMax?: number;
}) {
  const { intervalMin, intervalMax } = config;
  
  if (intervalMin && intervalMax && intervalMin > intervalMax) {
    return { error: "Min interval must be less than max interval" };
  }
  
  if (intervalMax && intervalMax < DEFAULT_INTERVAL_MIN) {
    return { error: `Max interval must be >= ${DEFAULT_INTERVAL_MIN}` };
  }
  
  if (intervalMin && intervalMin > DEFAULT_INTERVAL_MAX) {
    return { error: `Min interval must be <= ${DEFAULT_INTERVAL_MAX}` };
  }
  
  return null;
}

7. Inconsistent Error Handling

File: app/api/v1/apps/[id]/twitter-automation/route.ts:33-38

Issue: Twitter automation GET endpoint lacks try-catch wrapper that Discord and Telegram have.

Fix: Add consistent error handling:

export async function GET(request: NextRequest, { params }: RouteParams) {
  try {
    const { user } = await requireAuthOrApiKeyWithOrg(request);
    const { id: appId } = await params;
    
    const status = await twitterAppAutomationService.getAutomationStatus(
      user.organization_id,
      appId
    );
    return NextResponse.json(status);
  } catch (error) {
    logger.error("[Twitter Automation] Failed to get status", {
      error: error instanceof Error ? error.message : "Unknown error",
    });
    return NextResponse.json(
      { error: "Failed to get automation status" },
      { status: 500 }
    );
  }
}

8. Concurrent Processing Risk

File: app/api/cron/social-automation/route.ts:27, 202

Issue: Up to 15 concurrent external API calls (5 apps × 3 platforms) could trigger rate limits.

const MAX_CONCURRENT_POSTS = 5;

const [discordResult, telegramResult, twitterResult] = await Promise.all([
  processDiscordAutomation(app),
  processTelegramAutomation(app),
  processTwitterAutomation(app),
]);

Recommendation:

  • Add jitter between requests
  • Monitor for 429 responses and implement exponential backoff
  • Consider reducing MAX_CONCURRENT_POSTS or processing platforms sequentially

⚠️ Test Coverage Gaps

Missing Critical Tests

  1. No Discord Automation Tests

    • Files app/api/v1/apps/[id]/discord-automation/* (178 lines) have ZERO test coverage
    • Need: tests/integration/discord-automation.test.ts
  2. No Cron Job Tests

    • File app/api/cron/social-automation/route.ts (344 lines) completely untested
    • Critical untested scenarios:
      • CRON_SECRET authentication
      • Concurrent processing logic
      • Hash-based time window distribution (lines 59-108)
      • Error recovery and partial failures
    • Need: tests/integration/social-automation-cron.test.ts
  3. No Service Layer Unit Tests

    • lib/services/discord-automation/app-automation.ts (383 lines)
    • lib/services/telegram-automation/app-automation.ts (524 lines)
    • lib/services/twitter-automation/app-automation.ts (332 lines)
    • Critical untested paths: AI generation, credit handling, message splitting
  4. Insufficient Edge Case Coverage

    • Tests skip many scenarios with console.log messages
    • Example: telegram-automation.test.ts:129 - "402 test requires account with no credits"
    • Use mocks/fixtures to test these consistently

✅ Positive Findings

Strong Patterns Worth Noting

  1. Credit Management: Excellent implementation with automatic refunds on failures

    // discord-automation/app-automation.ts:230-236
    if (failedImages > 0) {
      await creditsService.refundCredits({ ... });
    }
  2. Consistent Authentication: All endpoints properly use requireAuthOrApiKeyWithOrg()

  3. Structured Logging: Good use of contextual logging throughout

  4. Type Safety: Comprehensive TypeScript usage with proper interfaces

  5. Separation of Concerns: Clean service layer abstraction

  6. Pricing Centralization: New lib/promotion-pricing.ts centralizes all cost calculations with clear documentation


📋 Action Items Summary

Must Fix (Blocking)

  • Fix CRON_SECRET fail-open vulnerability (social-automation/route.ts:277,327)
  • Add Telegram webhook authentication (telegram/webhook/[orgId]/route.ts)
  • Add Discord automation integration tests

High Priority

  • Fix N+1 query in guild channel fetching (discord-automation/index.ts:173)
  • Add rate limiting to manual post endpoints
  • Add URL validation for user-provided links
  • Create cron job integration tests

Medium Priority

  • Extract duplicated interval validation logic to shared utility
  • Add try-catch to Twitter automation GET endpoint
  • Add service layer unit tests
  • Complete edge case test coverage
  • Review concurrent processing limits and add backoff

📚 Additional Notes

Database Indexes Needed

The cron job queries JSON fields which should have indexes:

CREATE INDEX idx_apps_discord_automation_enabled 
  ON apps ((discord_automation->>'enabled'));
CREATE INDEX idx_apps_telegram_automation_enabled 
  ON apps ((telegram_automation->>'enabled'));
CREATE INDEX idx_apps_twitter_automation_enabled 
  ON apps ((twitter_automation->>'enabled'));

Documentation Recommendations

Consider adding:

  • Webhook setup guide for Telegram (secret token generation)
  • Rate limit documentation for API consumers
  • Credit cost transparency in API docs

Reviewed by: Claude Sonnet 4.5
Review Date: 2026-01-08
Commit: 2af6f82

guildId,
state,
permissions || undefined
);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Discord OAuth state not validated enabling CSRF attack

High Severity

The Discord OAuth flow generates a state with a nonce but never stores it server-side for validation. The callback endpoint is unauthenticated and directly uses organizationId from the base64-decoded state without verification. An attacker can craft their own state containing a victim's organizationId, authorize their Discord server, and have it connected to the victim's organization. Compare to the Twitter OAuth implementation which properly stores state in cache and validates it during callback. The nonce provides no protection since there's nothing to validate it against.

Additional Locations (1)

Fix in Cursor Fix in Web

@claude
Copy link
Copy Markdown

claude Bot commented Jan 8, 2026

PR Review: Social Media Automation & Promotion Features

Overview

This PR introduces a comprehensive social media automation system for Discord, Telegram, and Twitter, along with enhanced promotional asset generation. The implementation is well-structured and covers multiple platforms with consistent patterns.

Stats: 81 files changed, +13,383 additions, -2,307 deletions


Code Quality & Best Practices

✅ Strengths

  1. Consistent Architecture: Service layer pattern is well-applied across all platforms (Discord/Telegram/Twitter)
  2. Good Error Handling: Try-catch blocks with proper refunds on failures
  3. Comprehensive Logging: Structured logging throughout with contextual information
  4. Type Safety: Strong TypeScript usage with proper interfaces and Zod validation
  5. Credit System Integration: Well-implemented deduction/refund flow with proper error handling
  6. Separation of Concerns: Clear separation between automation config, posting logic, and credit management

⚠️ Areas for Improvement

1. Cron Job Timing Logic (app/api/cron/social-automation/route.ts:103-107)

The hashToFraction function uses a simple hash that could have collisions:

function hashToFraction(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32-bit integer
  }
  return Math.abs(hash % 1000) / 1000;
}

Issue: hash = hash & hash is a no-op (bitwise AND with itself). Should be hash = hash | 0 to convert to 32-bit integer.

Recommendation: Use a proper hash function or document that collisions are acceptable for this use case.

2. Inconsistent Interval Validation (app/api/v1/apps/[id]/discord-automation/route.ts:83-103)

The interval validation has edge cases:

  • Only validates when BOTH min and max are provided, or one is provided
  • Doesn't validate that the final config has min < max after merging with current config
  • Default values could conflict with user-provided partial values

Recommendation: Validate the final merged config, not just the incoming values.

3. Race Condition in Concurrent Processing (app/api/cron/social-automation/route.ts:259-272)

async function processAppsWithConcurrency(
  apps: App[],
  concurrency: number
): Promise<ProcessResult[]> {
  const results: ProcessResult[] = [];
  for (let i = 0; i < apps.length; i += concurrency) {
    const batch = apps.slice(i, i + concurrency);
    const batchResults = await Promise.all(batch.map(processApp));
    results.push(...batchResults.flat());
  }
  return results;
}

Issue: Each app can post to 3 platforms concurrently, so MAX_CONCURRENT_POSTS = 5 could actually trigger 15 parallel operations (5 apps × 3 platforms).

Recommendation: Consider renaming to MAX_CONCURRENT_APPS or implement per-platform concurrency limits.


Potential Bugs & Issues

🐛 Critical Issues

1. Missing Error Handling for Missing CRON_SECRET

All cron endpoints check CRON_SECRET but don't handle the case where it's undefined in production:

if (!CRON_SECRET || authHeader !== `Bearer ${CRON_SECRET}`) {
  return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
}

Impact: If CRON_SECRET is not set, ALL requests will be rejected, breaking automation.

Recommendation: Add startup validation or fail-fast on missing env vars.

2. Promotional Image Selection Logic (lib/services/discord-automation/app-automation.ts:243-259)

const preferred = assets.find(
  (a) =>
    a.url &&
    (a.size.width === 1200 || // twitter_card, facebook_feed, linkedin
      a.type === "social_card")
);

Issue: The width check is fragile - if asset dimensions change, this silently breaks.

Recommendation: Check by type field explicitly or add dimension constants.

3. AI Generation Credit Race Condition (lib/services/discord-automation/app-automation.ts:196-238)

Credits are deducted BEFORE AI generation, but if generation succeeds and the database update fails (line 328), credits aren't refunded:

const deduction = await creditsService.deductCredits({...});
// ... AI generation ...
await appsRepository.update(appId, {...}); // Could fail here

Impact: User loses credits if DB update fails after successful generation.

Recommendation: Wrap in a transaction or add try-catch around DB update with refund.

⚠️ Medium Issues

4. Telegram Webhook Security (app/api/v1/telegram/webhook/[orgId]/route.ts)

The webhook doesn't validate that requests actually come from Telegram. Anyone with the orgId can send fake updates.

Recommendation: Implement Telegram's X-Telegram-Bot-Api-Secret-Token validation or validate request signatures.

5. Unbounded SQL Query (app/api/cron/social-automation/route.ts:110-121)

async function getAppsWithAutomation(): Promise<App[]> {
  return dbRead
    .select()
    .from(apps)
    .where(or(...));
}

Issue: No LIMIT - could load thousands of apps into memory at once.

Recommendation: Add pagination or streaming for large datasets.

6. Missing Input Sanitization

customPrompt (max 1000 chars) is passed directly to AI without sanitization. Could contain prompt injection attempts.

Recommendation: Add basic sanitization or content filtering before AI calls.


Security Concerns

🔒 Security Issues

1. Environment Variable Exposure

Multiple files directly access process.env without validation. Missing env vars could cause runtime errors or security issues.

Files affected:

  • app/api/v1/telegram/scan-chats/route.ts:89 - Falls back to public URL if internal URL missing
  • All cron jobs - Missing CRON_SECRET silently fails authentication

Recommendation:

  • Use a centralized config module with validation
  • Fail at startup if critical env vars are missing

2. OAuth Token Storage

Discord/Twitter OAuth tokens are stored but there's no mention of encryption at rest in the schema changes.

Files:

  • db/migrations/0010_add_discord_guilds.sql
  • OAuth callback handlers

Recommendation: Verify tokens are encrypted in the database or add encryption.

3. Rate Limiting

No rate limiting visible on:

  • /api/v1/apps/[id]/discord-automation/post/route.ts
  • /api/v1/apps/[id]/telegram-automation/post/route.ts
  • /api/v1/apps/[id]/twitter-automation/post/route.ts

Impact: Users could spam social platforms, getting the bot banned.

Recommendation: Add per-app, per-hour rate limits (e.g., 10 posts/hour).

4. SQL Injection Risk in Raw SQL

sql`${apps.discord_automation}->>'enabled' = 'true'`

While this specific case is safe (field reference), using sql template without parameterization is risky.

Recommendation: Use Drizzle's typed operators instead of raw SQL where possible.


Performance Considerations

⚡ Performance Issues

1. N+1 Query Problem

getAutomationStatus() makes separate queries for guild and channel names:

if (config.guildId) {
  const guild = await discordGuildsRepository.findByGuildId(...);
  guildName = guild?.guild_name;
}
if (config.channelId) {
  const channel = await discordChannelsRepository.findByChannelId(...);
  channelName = channel?.channel_name;
}

Impact: 3 DB queries per app for status checks.

Recommendation: Use a JOIN or batch fetch if checking multiple apps.

2. Cron Job Timeout Risk

maxDuration = 60 seconds for cron that processes unlimited apps:

const results = await processAppsWithConcurrency(
  appsWithAutomation,  // Could be 1000s of apps
  MAX_CONCURRENT_POSTS
);

Impact: With 1000 apps and slow AI calls, could exceed 60s timeout.

Recommendation:

  • Add a hard limit (e.g., process max 100 apps per run)
  • Use a queue system for large-scale deployments

3. Inefficient Asset Selection

getPromotionalImage() iterates assets twice:

const preferred = assets.find(...);
if (preferred?.url) return preferred.url;
const anyImage = assets.find((a) => a.url);

Recommendation: Single pass with priority ordering.

4. maxDuration Inconsistency

  • Cron job: maxDuration = 60 (needs it)
  • Preview endpoint: maxDuration = 60 (generates multiple AI posts)
  • Manual post endpoints: No maxDuration set (could timeout)

Recommendation: Set appropriate maxDuration on all AI-heavy endpoints.


Test Coverage

✅ Good Coverage

  • Integration tests for assets API (auth, validation, error cases)
  • Tests for Telegram/Twitter automation
  • Promotion pricing calculations

❌ Missing Tests

  1. Cron Job Logic: No tests for isAnnouncementDue(), hashToFraction(), concurrency handling
  2. Webhook Handlers: No tests for Telegram webhook processing
  3. OAuth Flows: No tests for Discord/Twitter OAuth callbacks
  4. Error Scenarios:
    • What happens when Discord/Telegram API is down?
    • Credit refund edge cases
    • Database transaction failures
  5. Edge Cases:
    • Apps with no promotional assets
    • Invalid channel IDs after channel deletion
    • Concurrent automation updates

Recommendation: Add unit tests for:

  • Cron scheduling logic (isAnnouncementDue)
  • Credit deduction/refund flows
  • Webhook security validation
  • Error handling paths

Additional Recommendations

1. Database Indexes

Good index coverage on:

  • discord_guilds_org_guild_idx
  • discord_channels indexes
  • telegram_chats indexes

Consider adding:

  • Index on apps.discord_automation->>'enabled' for faster cron queries (GIN index for JSONB)
  • Composite index on (organization_id, discord_automation->>'enabled') if queries filter by org

2. Observability

Good logging structure, but consider:

  • Metrics for automation success/failure rates
  • Alerts for high failure rates
  • Monitoring for cron job duration
  • Track credit refund frequency (high refunds = AI reliability issues)

3. Documentation

Missing documentation for:

  • Setup guide for Discord/Telegram/Twitter OAuth
  • Webhook configuration instructions
  • Cron job monitoring/alerting setup
  • Credit cost breakdown for users

4. Migrations

Migration files are clean and follow good practices:

  • ✅ Proper foreign keys with CASCADE
  • ✅ Good index coverage
  • ✅ Default values set
  • ⚠️ No rollback migrations provided

Recommendation: Consider adding down migrations for easier rollbacks.


Summary

Approval Status: ⚠️ Approved with Recommendations

This is a solid implementation with good architecture and comprehensive feature coverage. The main concerns are:

Must Fix Before Merge:

  1. Fix hashToFraction bit operation bug
  2. Add CRON_SECRET validation at startup
  3. Add rate limiting to manual post endpoints

Should Fix Soon:

  1. Implement Telegram webhook signature validation
  2. Add database transaction handling for credit refunds
  3. Set maxDuration on all AI endpoints
  4. Add query limits to prevent OOM

Nice to Have:

  1. Improve test coverage for cron logic
  2. Add metrics/monitoring
  3. Document OAuth setup process

Total Issues Found:

  • 🔴 Critical: 3
  • 🟡 Medium: 6
  • 🔵 Low: 8

The code is production-ready with the critical fixes applied. Great work on the comprehensive feature set! 🚀


Review generated by Claude Code

@claude
Copy link
Copy Markdown

claude Bot commented Jan 9, 2026

Code Review - PR #198: Social Media Automation & Promotion Features

I've completed a comprehensive review of this large feature PR. Overall, the implementation is solid with good security practices, but I've identified several areas for improvement.


✅ Strengths

Security

  • Excellent SSRF protection in lib/services/app-promotion-assets.ts:66-121 - comprehensive validation blocking:
    • Private IP ranges (10.x, 172.16-31.x, 192.168.x)
    • Loopback addresses (127.x, localhost, 0.0.0.0)
    • AWS metadata endpoints (169.254.x.x)
    • IPv6 private ranges and link-local
    • Multicast and reserved ranges
  • Proper cron authentication with CRON_SECRET validation at app/api/cron/social-automation/route.ts:277
  • Credit refund mechanisms properly implemented for failed operations

Code Quality

  • Well-structured service layer separation (Discord/Telegram/Twitter automation services)
  • Comprehensive error handling with try-catch blocks and error logging
  • Good use of TypeScript types and Zod validation schemas
  • Centralized pricing module (lib/promotion-pricing.ts) with clear cost calculations

Performance

  • Parallel processing with concurrency limits (MAX_CONCURRENT_POSTS = 5) in cron job
  • Hash-based distribution algorithm to prevent rate limit spikes (hashToFraction at line 59)
  • Website context caching to avoid redundant fetches

⚠️ Issues & Recommendations

🔴 Critical Issues

1. Race Condition in Credit Refunds

Location: app/api/v1/apps/[id]/promote/assets/route.ts:92-102

const successfulImages = result.assets.length;
const failedImages = imageCount + bannerCount - successfulImages;
if (failedImages > 0) {
  await creditsService.refundCredits({
    organizationId: user.organization_id,
    amount: failedImages * PROMO_IMAGE_COST,
    // ...
  });
}

Problem: If the catch block at line 134 is triggered AFTER partial refunds occur, credits could be refunded twice (partial + full).

Fix: Use a transaction or flag to track if partial refunds occurred:

let refundedAmount = 0;
// ... in try block
if (failedImages > 0) {
  refundedAmount = failedImages * PROMO_IMAGE_COST;
  await creditsService.refundCredits({...});
}
// ... in catch block
const remainingRefund = totalCost - refundedAmount;
if (remainingRefund > 0) {
  await creditsService.refundCredits({
    amount: remainingRefund,
    // ...
  });
}

2. Missing Timeout on External Fetch

Location: lib/services/app-promotion-assets.ts:149-150

const response = await Promise.race([
  fetch(url, {

The code uses Promise.race but doesn't show the timeout implementation in this snippet. Verify there's a proper timeout (e.g., 10s) to prevent hanging on slow/malicious URLs.

3. Weak Hash Function for Distribution

Location: app/api/cron/social-automation/route.ts:59-68

function hashToFraction(str: string): number {
  let hash = 0;
  for (let i = 0; i < str.length; i++) {
    const char = str.charCodeAt(i);
    hash = ((hash << 5) - hash) + char;
    hash = hash & hash; // Convert to 32-bit integer
  }
  return Math.abs(hash % 1000) / 1000;
}

Issues:

  • This is the DJB2a hash, which has poor distribution for UUIDs
  • Modulo bias: hash % 1000 doesn't give uniform distribution
  • Could cause clustering of posts

Better approach:

import crypto from 'crypto';

function hashToFraction(str: string): number {
  // Use crypto hash for better distribution
  const hash = crypto.createHash('sha256').update(str).digest();
  // Use first 4 bytes as uint32
  const value = hash.readUInt32BE(0);
  // Divide by max uint32 for 0-1 range
  return value / 0xFFFFFFFF;
}

🟡 Medium Priority Issues

4. Inconsistent Interval Validation

Locations:

  • app/api/v1/apps/[id]/discord-automation/route.ts:84-103
  • Similar code in telegram-automation and twitter-automation routes

The validation logic is duplicated across 3 files. Extract to a shared utility:

// lib/utils/automation-validation.ts
export function validateIntervalRange(
  min?: number,
  max?: number,
  defaultMin = 120,
  defaultMax = 240
): { valid: boolean; error?: string } {
  if (min && max && min > max) {
    return { valid: false, error: "Min interval must be less than max interval" };
  }
  if (max && !min && max < defaultMin) {
    return { valid: false, error: `Max must be >= ${defaultMin} (default min)` };
  }
  if (min && !max && min > defaultMax) {
    return { valid: false, error: `Min must be <= ${defaultMax} (default max)` };
  }
  return { valid: true };
}

5. Type Safety Issues

Location: app/api/v1/apps/[id]/promote/assets/route.ts:106

type: asset.type as "social_card" | "banner",

Using as casts bypasses TypeScript checking. Add runtime validation:

if (asset.type !== 'social_card' && asset.type !== 'banner') {
  logger.warn('[Promote Assets API] Unknown asset type', { type: asset.type });
  continue; // Skip unknown types
}
const promotionalAssets = result.assets
  .filter(asset => asset.type === 'social_card' || asset.type === 'banner')
  .map((asset) => ({
    type: asset.type,
    // ...
  }));

6. Database Query Inefficiency

Location: app/api/cron/social-automation/route.ts:110-121

async function getAppsWithAutomation(): Promise<App[]> {
  return dbRead
    .select()
    .from(apps)
    .where(
      or(
        sql\`${apps.discord_automation}->>'enabled' = 'true'\`,
        sql\`${apps.telegram_automation}->>'enabled' = 'true'\`,
        sql\`${apps.twitter_automation}->>'enabled' = 'true'\`
      )
    );
}

Issues:

  • No pagination - could load thousands of apps into memory
  • No index on JSONB fields (check migrations)

Recommendations:

  • Add GIN indexes on automation JSONB columns in migrations
  • Add pagination if app count could exceed 1000

7. Magic Numbers

Location: Throughout codebase

Constants are defined but not always used consistently:

  • app/api/v1/apps/[id]/promote/assets/route.ts:20 - maxDuration = 60
  • app/api/cron/social-automation/route.ts:30 - maxDuration = 60

Extract to shared config:

// lib/config/timeouts.ts
export const TIMEOUTS = {
  ASSET_GENERATION: 60,
  CRON_AUTOMATION: 60,
  DISCORD_MESSAGE: 25,
  PREVIEW_GENERATION: 60,
} as const;

🟢 Minor Issues

8. Inconsistent Error Messages

Some errors return generic messages, others are specific. Standardize:

// Bad
{ error: "Failed to generate assets. Credits have been refunded." }

// Better - include actionable info
{ 
  error: "Failed to generate assets",
  reason: error.message,
  creditsRefunded: totalCost,
  retryable: true
}

9. Missing JSDoc Comments

Key exported functions lack documentation:

  • isAnnouncementDue at app/api/cron/social-automation/route.ts:70
  • estimateAssetGenerationCost at lib/promotion-pricing.ts:45

Add JSDoc for better developer experience:

/**
 * Determines if an announcement is due based on interval settings and last post time.
 * Uses hash-based distribution to prevent thundering herd.
 * 
 * @param config - Automation configuration
 * @param type - Type of post (announcement or post)
 * @param appId - Optional app ID for deterministic distribution
 * @returns true if announcement should be posted
 */
function isAnnouncementDue(...)

10. Test Coverage Gaps

Integration tests exist but missing:

  • Cron job error handling tests
  • SSRF protection tests (critical!)
  • Credit refund edge cases
  • Hash distribution uniformity tests

📊 Performance Considerations

Positive

✅ Parallel processing with concurrency limits
✅ Website context caching
✅ Batch processing in cron job

Concerns

⚠️ Database N+1 queries possible - Verify automation services use batch queries when fetching guilds/channels
⚠️ Memory usage - Loading all apps with automation could be large; monitor in production


🧪 Test Coverage

Existing Tests (Good):

  • tests/integration/app-promote-assets.test.ts - Asset generation
  • tests/integration/promotion-preview.test.ts - Preview generation
  • tests/integration/promotion-pricing.test.ts - Pricing calculations
  • tests/integration/telegram-automation.test.ts - Telegram flows
  • tests/integration/twitter-automation.test.ts - Twitter flows

Missing Tests (Should Add):

  • SSRF protection validation
  • Cron job scheduling logic
  • Credit refund race conditions
  • Hash distribution fairness
  • Error handling in automation services

🔒 Security Summary

Category Status Notes
SSRF Protection ✅ Excellent Comprehensive IP filtering
Authentication ✅ Good Cron secret + org auth
Input Validation ✅ Good Zod schemas throughout
Rate Limiting ⚠️ Check Verify API routes have rate limits
Credit Handling ⚠️ Issue Race condition possible (see #1)
SQL Injection ✅ Safe Using Drizzle ORM

📝 Additional Notes

  1. Database Migrations: Verify indexes exist on:

    • apps.discord_automation->>'enabled'
    • apps.telegram_automation->>'enabled'
    • apps.twitter_automation->>'enabled'
  2. Monitoring: Add metrics for:

    • Cron job execution time
    • Failed automation posts
    • Credit refund frequency
  3. Documentation: Consider adding:

    • Architecture diagram for automation flow
    • Pricing calculation examples
    • Webhook security documentation

🎯 Recommendation

Status: Approve with Changes

This is a well-implemented feature with good security practices. The critical issues (credit refund race condition, hash distribution) should be fixed before merge, but don't block the overall PR. Medium priority items can be addressed in follow-up PRs.

Before Merge:

Post-Merge (Follow-ups):


Great work on this feature! The code quality is generally high and security considerations are well thought out. 🚀

…nstants and improved configuration handling

- Introduced automation-constants.ts to centralize default configuration values for Discord, Telegram, and Twitter.
- Updated Discord automation service to utilize new constants and improve OAuth configuration checks.
- Enhanced Telegram automation service with configuration defaults and announcement scheduling logic.
- Improved error handling and caching mechanisms for connection status in both Discord and Telegram services.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 9, 2026

Code Review - PR #198: Social Media Automation & Promotion Improvements

Summary

Comprehensive PR adding Discord, Telegram, and Twitter automation features, plus promotional asset generation improvements. Well-structured with good test coverage (966 lines of integration tests).

Overall: Approve with minor recommendations

Strengths

  • Excellent separation of concerns
  • Good error handling with credit refunds on failures
  • Proper authentication and input validation
  • Comprehensive testing (966 LOC across 4 test files)
  • Clean service layer architecture

Critical Issues

1. CRON_SECRET Security (app/api/cron/social-automation/route.ts:32)

  • No validation that CRON_SECRET is set in production
  • Fix: Add explicit check before comparing

2. Race Condition in Asset Upload (app/actions/apps.ts:72-77)

  • Read-modify-write race: concurrent uploads could lose data
  • Fix: Use database-level array append or optimistic locking

High Priority

3. File Upload DoS Risk - No rate limiting (add per-org limits)
4. Hardcoded Image Dimensions - Use sharp library to extract real dimensions
5. Telegram Webhook Errors - Always returns 200, preventing retries
6. Random Interval Logic Bug - Math.random() called on every check, use deterministic hash

Medium Priority

7. Credits deducted before generation - Consider incremental deduction
8. Hardcoded model names - Extract to config
9. No retry logic for failed posts
10. Unsafe type casts - Use Zod for runtime validation

Performance

Good: Concurrency limiting, batch processing, read replicas, proper maxDuration
Concerns: N+1 queries, no caching, large response payloads

Test Coverage

Excellent: 966 LOC integration tests
Missing: Race conditions, cron edge cases, webhook load tests

Recommendation

APPROVE after addressing critical items #1 and #2.

Great work on this feature!


await appsService.update(id, {
promotional_assets: promotionalAssets,
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asset generation overwrites existing promotional assets

Medium Severity

When saving generated promotional assets, the code replaces the entire promotional_assets field with only the newly generated assets, discarding any previously existing assets. This is inconsistent with uploadPromotionalAsset in app/actions/apps.ts which explicitly appends new uploads to existing assets. Users who upload custom promotional assets and later generate new ones will lose their uploaded assets. The generation endpoint likely needs to merge new assets with existing ones.

Additional Locations (1)

Fix in Cursor Fix in Web

});

const ASSET_GENERATION_COST = 0.05;
const COPY_GENERATION_COST = 0.02;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Asset generation sizes parameter validated but never used

Low Severity

The GenerateAssetsSchema defines a sizes field that allows users to specify which ad sizes to generate, and the GET endpoint returns availableSizes and recommendedSizes. However, when calling generateAssetBundle, the parsed.data.sizes value is never passed to the service. The service only accepts boolean flags includeSocialCards and includeAdBanners, meaning user-specified sizes are silently ignored and the API always generates a fixed set of assets regardless of what sizes were requested.

Additional Locations (1)

Fix in Cursor Fix in Web

…sitory

- Added methods to atomically append and remove promotional assets to/from an app's promotional_assets array, utilizing JSONB operations to prevent race conditions.
- Updated uploadPromotionalAsset and deletePromotionalAsset functions to leverage the new repository methods for improved reliability.
- Enhanced AppPromote component to extract image dimensions from uploaded files and pass them to the upload function.
@claude
Copy link
Copy Markdown

claude Bot commented Jan 9, 2026

PR Review: Social Media Automation Feature

Overview

This is a substantial PR adding end-to-end social media automation (Discord, Telegram, Twitter) with promotional asset generation. The implementation is generally well-structured with good security considerations and test coverage.

✅ Strengths

Security

  1. SSRF Protection - Excellent URL validation in lib/services/app-promotion-assets.ts:66-121:

    • Blocks localhost, private IPs (10.x, 172.16-31.x, 192.168.x, 127.x)
    • Blocks AWS metadata (169.254.x.x)
    • Blocks multicast and reserved ranges
    • Validates protocol (http/https only)
  2. Cron Authentication - Proper secret validation in app/api/cron/social-automation/route.ts:277:

    • Requires CRON_SECRET to be set
    • Returns 401 if missing or incorrect
  3. Authorization Checks - Consistent ownership verification across all endpoints

    • Apps actions verify app.organization_id !== user.organization_id
    • Proper requireAuthWithOrg and requireAuthOrApiKeyWithOrg usage

Code Quality

  1. Atomic Operations - Race condition prevention in db/repositories/apps.ts:602-662:

    • appendPromotionalAsset uses JSONB concatenation
    • removePromotionalAsset uses JSONB filtering
    • Avoids read-modify-write races
  2. Error Handling & Refunds - app/api/v1/apps/[id]/promote/assets/route.ts:83-131:

    • Credits refunded for failed generations
    • Proper partial/full failure handling
    • Transaction-like behavior
  3. Centralized Pricing - lib/promotion-pricing.ts:

    • All costs in one place with clear 20% markup
    • Well-documented pricing model
    • Good separation of base vs user-facing costs

Testing

  • Comprehensive integration tests for key features
  • Pricing tests verify markup calculations
  • Good coverage of error cases

🔍 Issues Found

Critical Issues

None identified - The PR appears production-ready from a security/correctness perspective.

Major Issues

  1. Cron Job Timing Race Condition - app/api/cron/social-automation/route.ts:55-108

    • FIXED: Uses hash-based distribution to prevent thundering herd
    • Good solution: hashToFraction(appId + type) spreads posts across time window
    • Consider: Document this algorithm more clearly for maintainers
  2. Integer Validation Missing - app/actions/apps.ts:68-69

    const width = widthStr ? parseInt(widthStr, 10) : 1200;
    const height = heightStr ? parseInt(heightStr, 10) : 630;
    • ⚠️ No validation that parseInt returns a valid number
    • parseInt("abc") returns NaN which could cause issues
    • Recommendation: Add validation:
    const width = widthStr ? Math.max(1, parseInt(widthStr, 10) || 1200) : 1200;
    const height = heightStr ? Math.max(1, parseInt(heightStr, 10) || 630) : 630;

Minor Issues

  1. Type Assertion Safety - app/actions/apps.ts:125

    const assetWithUrl = removedAsset as { url: string };
    • Could use type guard instead of assertion
    • Not critical since removePromotionalAsset returns typed data
  2. Magic Numbers - app/api/v1/apps/[id]/discord-automation/route.ts:81-104

    • Interval validation uses hardcoded 30, 1440, 120, 240
    • ✅ Some moved to constants in automation-constants.ts
    • Consider: Move validation bounds to constants as well
  3. Concurrent Processing - app/api/cron/social-automation/route.ts:259-273

    • MAX_CONCURRENT_POSTS = 5 hardcoded
    • Could make this configurable via env var for scaling
    • Not urgent but worth considering for production tuning
  4. Error Message Consistency - Multiple files

    • Some errors use generic "App not found" when ownership check fails
    • Security-wise this is good (doesn't leak info) but consider logging the actual reason

🚀 Performance Considerations

Good Practices

  1. Parallel Processing - Cron job processes platforms in parallel per app
  2. Concurrency Limiting - Batches of 5 apps prevent overwhelming external APIs
  3. Read Replicas - Properly uses dbRead for queries, dbWrite for mutations
  4. Caching - Website context cached to avoid re-fetching

Potential Optimizations

  1. Database Queries - app/api/cron/social-automation/route.ts:110-120

    • Fetches all apps with automation enabled
    • Consider pagination if this grows to thousands of apps
    • Current approach is fine for hundreds/low thousands
  2. Timeout Configuration - app/api/v1/apps/[id]/promote/assets/route.ts:19

    • maxDuration = 60 for asset generation is reasonable
    • Monitor in production for timeout issues

📊 Test Coverage

Well Tested

  • ✅ Promotion pricing (197 lines of tests)
  • ✅ Integration tests for Telegram, Twitter automation
  • ✅ Asset generation endpoint tests

Missing Tests

  • ⚠️ No tests for Discord automation endpoints
  • ⚠️ No tests for cron job logic (mocking would be needed)
  • ⚠️ SSRF protection not explicitly tested (should add tests for blocked URLs)

Recommendation: Add tests for:

// lib/services/app-promotion-assets.test.ts
test("blocks SSRF attempts", () => {
  expect(isValidExternalUrl("http://localhost:3000")).toBe(false);
  expect(isValidExternalUrl("http://127.0.0.1")).toBe(false);
  expect(isValidExternalUrl("http://169.254.169.254")).toBe(false);
  expect(isValidExternalUrl("http://10.0.0.1")).toBe(false);
  expect(isValidExternalUrl("http://example.com")).toBe(true);
});

🎯 Recommendations

Before Merge

  1. Add integer validation for width/height parsing (see Major Issue feat: Add versioned /api/v1 endpoints with secure auth, credit tracking, and usage auditing #2)
  2. Add SSRF protection tests to prevent regression
  3. Document hash-based scheduling algorithm for maintainers

Post-Merge (Non-blocking)

  1. Consider making MAX_CONCURRENT_POSTS configurable
  2. Add tests for Discord automation endpoints
  3. Monitor cron job performance in production
  4. Consider adding metrics/alerting for failed automation posts

📝 Code Style Notes

Code follows good TypeScript practices:

  • ✅ Proper type imports (import type)
  • ✅ Zod schemas for validation
  • ✅ JSDoc comments on public functions
  • ✅ Consistent error handling patterns
  • ✅ Proper async/await usage

Summary

This is high-quality work with excellent attention to security. The atomic operations, SSRF protection, and credit refund logic show careful consideration of edge cases.

Recommendation: Approve with minor changes

The only blocking issue is the integer validation for width/height. Everything else can be addressed post-merge or is already well-handled.

Great job on this feature! 🎉


Review conducted with focus on security, performance, code quality, and test coverage as requested.

- db/schemas/apps.ts: Keep promotion features (twitter/telegram/discord automation, promotional_assets) and add linked_character_ids from dev
- package.json: Use plugin-sql 1.7.1 from dev, keep twitter-api-v2 for promotions
- proxy.ts: Add all public paths from both branches
- bun.lock: Regenerated lockfile
@claude
Copy link
Copy Markdown

claude Bot commented Jan 13, 2026

PR Review: Feat - Promotion (Social Automation & Promotional Assets)

Overview

This PR introduces comprehensive social media automation (Discord, Telegram, Twitter) and enhanced promotional asset generation. The implementation is well-structured with 84 files changed (+13,729/-985 lines).


✅ Strengths

Code Quality

  • Excellent TypeScript usage: Strong typing throughout with proper interfaces and type safety
  • Consistent error handling: Proper try-catch blocks with detailed logging and credit refunds on failures
  • Good separation of concerns: Clean service layer architecture with distinct automation services
  • Comprehensive documentation: Well-commented code with JSDoc annotations
  • Proper async patterns: Good use of Promise.all for parallel operations

Security

  • ✓ CRON_SECRET protection: All cron endpoints properly validate authorization headers
  • ✓ Ownership validation: Consistent verification of app ownership before operations (app/actions/apps.ts:48-50)
  • ✓ Input validation: Zod schemas for all API inputs with proper error messages
  • ✓ File upload security: Type and size validation (10MB max) in app/actions/apps.ts:38-44
  • ✓ SQL injection prevention: Proper use of Drizzle ORM with parameterized queries

Performance

  • Smart rate limiting: Hash-based distribution prevents API rate limit spikes (app/api/cron/social-automation/route.ts:59-68)
  • Concurrency control: Batched processing with MAX_CONCURRENT_POSTS=5 limit
  • Efficient DB queries: Uses read replicas (dbRead) for queries, write DB (dbWrite) for mutations
  • Atomic JSONB operations: Race condition prevention in appendPromotionalAsset/removePromotionalAsset using SQL JSONB operators

Test Coverage

✓ Good test coverage with 5 new integration tests:

  • telegram-automation.test.ts
  • twitter-automation.test.ts
  • app-promote-assets.test.ts
  • promotion-preview.test.ts
  • promotion-pricing.test.ts

⚠️ Issues Found

🔴 Critical Issues

1. Missing parseInt Radix Validation

Location: app/actions/apps.ts:68-69

const width = widthStr ? parseInt(widthStr, 10) : 1200;
const height = heightStr ? parseInt(heightStr, 10) : 630;

Issue: No validation that parseInt succeeded. Malformed input returns NaN.
Impact: Could save invalid dimensions to database
Fix: Add validation:

const width = widthStr ? Math.max(1, parseInt(widthStr, 10) || 1200) : 1200;
const height = heightStr ? Math.max(1, parseInt(heightStr, 10) || 630) : 630;

2. Race Condition in removePromotionalAsset

Location: db/repositories/apps.ts (appendPromotionalAsset method shown in diff)
Issue: The removePromotionalAsset method does a read-then-write:

  1. Fetches app to find asset
  2. Updates array

If two deletions happen concurrently, both reads see the same asset, leading to inconsistent state.
Impact: Potential data inconsistency
Recommendation: Consider using a single atomic SQL operation with JSONB array filtering

3. Missing Error Handling for Credit Deduction

Location: lib/services/discord-automation/app-automation.ts:190-199
Issue: If deductCredits succeeds but AI generation throws before the try block, credits aren't refunded
Current flow:

const deduction = await creditsService.deductCredits(...); // Outside try
if (\!deduction.success) throw...

try {
  const result = await generateText(...); // Refund only happens here

Fix: Move deduction inside try block OR add broader error handling

🟡 Medium Priority Issues

4. Inconsistent Interval Validation

Location: app/api/v1/apps/[id]/discord-automation/route.ts:83-103
Issue: Complex validation logic with DEFAULT constants but only checks one field at a time
Recommendation: Extract to a validation helper function for reusability

5. Hard-coded MAX_ANNOUNCEMENT_LENGTH

Location: lib/services/discord-automation/app-automation.ts:28

const MAX_ANNOUNCEMENT_LENGTH = 300; // Max chars for AI-generated announcement

Issue: Used in prompt but actual truncation uses TRUNCATE_LENGTH=500
Recommendation: Either make these consistent or clarify the difference in comments

6. Potential Memory Issue with Large Result Sets

Location: app/api/cron/social-automation/route.ts:263-270
Issue: Processes all apps in memory, could be problematic with 1000+ apps
Current: Batch processing helps, but still loads all apps upfront
Recommendation: Consider pagination or streaming for very large datasets

🟢 Minor Issues / Suggestions

7. Type Assertion Instead of Type Guard

Location: app/actions/apps.ts:125

const assetWithUrl = removedAsset as { url: string };

Suggestion: Use proper type guard for safety

8. Magic Numbers in Pricing

Location: lib/promotion-pricing.ts:20-29
Observation: Constants like 0.0005, 0.01 lack context
Suggestion: Add comments explaining these are base API costs (already has good header comment, but inline comments would help)

9. Missing maxDuration on Discord Automation POST

Location: app/api/v1/apps/[id]/discord-automation/post/route.ts:12
Observation: Sets maxDuration = 60 but automation route has maxDuration = 30
Recommendation: Verify if 60s is necessary or if 30s is sufficient


📊 Performance Considerations

✅ Good Practices

  1. Read replica usage: Proper separation of read/write operations
  2. Parallel processing: Promise.all for platform automation (app/api/cron/social-automation/route.ts:202)
  3. Batch operations: Processes apps in batches of 5
  4. Deterministic hashing: Smart distribution algorithm prevents thundering herd

🔍 Potential Optimizations

  1. Cron job: Consider adding indexes on discord_automation, telegram_automation, twitter_automation JSONB fields if query is slow
  2. Asset generation: 60s timeout is reasonable for AI generation, but consider async queue for better UX

🔒 Security Assessment

✅ Strong Security Posture

  • Proper authentication on all endpoints
  • Ownership validation before mutations
  • Input sanitization via Zod schemas
  • No SQL injection vulnerabilities found
  • Secrets stored in environment variables
  • File upload restrictions (type, size)

Minor Recommendations

  1. Consider rate limiting on manual post endpoints to prevent abuse
  2. Add content moderation checks for AI-generated announcements
  3. Consider adding CSRF tokens for state-changing operations (if not already handled by framework)

🧪 Test Coverage Assessment

✅ Good Coverage

  • 5 integration tests added covering main functionality
  • Tests for pricing calculations
  • Tests for asset generation workflow
  • Platform-specific automation tests

Missing Test Cases

  1. Cron job error scenarios: What happens when all platforms fail?
  2. Race condition tests: Concurrent asset uploads/deletions
  3. Credit refund edge cases: Partial failures, timeout scenarios
  4. parseInt NaN handling: Malformed dimension inputs
  5. Rate limit behavior: When hitting platform API limits

📝 Recommendations Summary

Must Fix Before Merge

  1. ✅ Add NaN validation for parseInt in app/actions/apps.ts:68-69
  2. ✅ Review race condition in removePromotionalAsset
  3. ✅ Fix credit deduction error handling in discord-automation

Should Fix

  1. Extract interval validation to helper function
  2. Clarify MAX_ANNOUNCEMENT_LENGTH vs TRUNCATE_LENGTH
  3. Add test cases for error scenarios

Nice to Have

  1. Replace type assertion with type guard
  2. Add inline pricing comments
  3. Consider async queue for asset generation
  4. Add rate limiting on manual post endpoints

🎯 Overall Assessment

Quality: High ⭐⭐⭐⭐☆

This is a well-implemented feature with:

  • Strong architecture and separation of concerns
  • Good security practices
  • Comprehensive functionality
  • Reasonable test coverage

The critical issues are fixable with small changes. The codebase demonstrates solid TypeScript and async patterns. Once the parseInt validation and race condition are addressed, this will be production-ready.

Recommendation: Request changes for the 3 critical issues, then approve.


Reviewed with Claude Code 🤖
Automated analysis of 84 changed files focusing on security, performance, and correctness

announceIntervalMax: z.number().int().min(60).max(1440).default(120),
vibeStyle: z.string().max(100).optional(),
})
.optional(),
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Promote route missing interval range validation

Medium Severity

The PromotionConfigSchema accepts automation configurations with announceIntervalMin/announceIntervalMax (and postIntervalMin/postIntervalMax for Twitter), but unlike the individual automation routes (discord-automation, telegram-automation, twitter-automation), this endpoint doesn't validate that the minimum interval is less than the maximum. This allows submitting invalid configurations like { announceIntervalMin: 200, announceIntervalMax: 100 } that would be rejected by the direct automation endpoints, causing inconsistent API behavior.

Fix in Cursor Fix in Web

Comment thread app/actions/apps.ts
success: false,
error: error instanceof Error ? error.message : "Failed to upload asset",
};
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Uploaded blob orphaned if database update fails

Low Severity

In uploadPromotionalAsset, if uploadToBlob succeeds but appendPromotionalAsset subsequently fails, the uploaded blob remains in storage with no database reference to it. The catch block returns an error without cleaning up the orphaned blob. Over time, failed uploads accumulate as unreferenced storage objects that cannot be automatically cleaned up, causing gradual storage cost increases.

Fix in Cursor Fix in Web

@ChristopherTrimboli ChristopherTrimboli merged commit a97cd53 into dev Jan 13, 2026
4 of 5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants