Skip to content

Add support for auto-generating socials#4

Open
bholagabbar wants to merge 2 commits intomainfrom
feat/socials
Open

Add support for auto-generating socials#4
bholagabbar wants to merge 2 commits intomainfrom
feat/socials

Conversation

@bholagabbar
Copy link
Copy Markdown
Member

No description provided.

@vercel
Copy link
Copy Markdown

vercel Bot commented Apr 29, 2026

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

Project Deployment Actions Updated (UTC)
autogtm Ready Ready Preview, Comment Apr 29, 2026 9:59pm

Request Review

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR adds a full social media auto-generation pipeline: content ingestion via "dumps", AI-powered classification and post drafting (OpenAI), image/video generation (OpenAI + OpenRouter), weekly planning, and publishing through Postiz. The implementation spans new DB tables, 9 Inngest background functions, and ~20 API routes.

Several issues need attention before merging:

  • RLS policies allow cross-company data access — all 7 new tables use auth.role() = 'authenticated' with no company-scoping, so any authenticated user can read/write every company's social data.
  • Dead-code guard in socialWeeklyPlanner — the over-planning guard queries posts with limit: 1 then checks length > 5000, so it can never fire.
  • Unguarded JSON.parse in AI helpers — a malformed LLM response in classifySocialItems or draftSocialPost will throw an unhandled error, leaving dumps/posts permanently stuck in intermediate states with only 1 retry configured.

Confidence Score: 2/5

Not safe to merge — overly permissive RLS allows cross-tenant data access, and multiple P1 logic bugs affect the core automated pipeline.

Multiple P1 findings: the RLS cross-tenant security issue, the dead guard in socialWeeklyPlanner, the item double-assignment race in draft-one, and unguarded JSON parsing in two AI helpers that can stall the pipeline.

migrations/20260427_socials.sql (RLS policies), apps/autogtm/src/inngest/functions.ts (dead guard), packages/autogtm-core/src/ai/classifySocialItems.ts and draftSocialPost.ts (unguarded JSON.parse), apps/autogtm/src/app/api/companies/[id]/social/themes/[themeId]/draft-one/route.ts (item reservation race).

Security Review

  • Overly broad RLS (migrations/20260427_socials.sql) — all new social tables are gated only on auth.role() = 'authenticated' with no row-level company ownership check, allowing any authenticated user to access or mutate any company's social content, posts, and publish runs.
  • No authentication on API routes — all new routes under /api/companies/[id]/social/... trust the [id] URL param without verifying the caller owns that company, effectively making them unauthenticated from a business-logic standpoint.
  • No secrets are exposed in code; API keys are read from environment variables.

Important Files Changed

Filename Overview
migrations/20260427_socials.sql Adds 7 new social tables; RLS policies only check auth.role() = 'authenticated' with no company-level scoping, and used_for_post_id lacks a FK constraint.
apps/autogtm/src/inngest/functions.ts Adds 9 Inngest functions for the social pipeline; the socialWeeklyPlanner guard (allItems.length > 5000) can never trigger because the query uses limit: 1.
packages/autogtm-core/src/ai/classifySocialItems.ts AI classification helper; unguarded JSON.parse on model output will throw on malformed responses, leaving dumps stuck in processing state.
packages/autogtm-core/src/ai/draftSocialPost.ts Drafts social post copy via OpenAI; same unguarded JSON.parse issue that can leave posts stranded in planned status on model errors.
apps/autogtm/src/app/api/companies/[id]/social/themes/[themeId]/draft-one/route.ts Creates a post and fires async Inngest draft event but does not mark the source data item as reserved, leaving it eligible for re-use by concurrent requests or the weekly planner.
apps/autogtm/src/app/api/companies/[id]/social/week-plans/route.ts Week-plan generation endpoint; contains an inline dynamic createClient import that bypasses the shared getServiceSupabase() helper.
apps/autogtm/src/app/api/companies/[id]/social/_lib.ts Shared helpers for the social API routes; creates Supabase admin client directly with createClient rather than using the project's canonical helper.
packages/autogtm-core/src/db/socialsDbCalls.ts Comprehensive DB layer for social entities; all writes scope by company_id, covering themes, schedules, week plans, posts, and publish runs.
packages/autogtm-core/src/ai/generateSocialImage.ts Generates images via OpenAI DALL-E or videos via OpenRouter with polling; logic is sound with proper timeout and error handling.
packages/autogtm-core/src/socials/weeklyPlanner.ts Pure allocation algorithm; correctly handles pinned themes, weighted random selection, and inventory tracking with no external dependencies.

Comments Outside Diff (3)

  1. apps/autogtm/src/inngest/functions.ts, line 476-477 (link)

    P1 Guard never triggers — limit: 1 makes length ≤ 1 always

    listSocialPosts is called with limit: 1, so allItems can contain at most one element. The subsequent if (allItems.length > 5000) return can therefore never be true, meaning the circuit-breaker meant to prevent over-planning for companies with huge backlogs is completely dead code. Replace the query with one that returns a real count, or remove the guard entirely if it is no longer needed.

  2. migrations/20260427_socials.sql, line 133-185 (link)

    P1 security RLS policies allow any authenticated user to access every company's data

    All seven policies use auth.role() = 'authenticated' as the sole predicate, meaning any logged-in user can read and write every company's social themes, posts, schedules, and publish runs. There is no check tying rows to the requesting user's company. A proper policy should verify ownership, for example:

    create policy "Company members can manage social_posts"
      on social_posts for all
      using (
        company_id in (
          select company_id from company_members where user_id = auth.uid()
        )
      );

    Apply equivalent company-scoped predicates to all seven tables.

  3. packages/autogtm-core/src/ai/draftSocialPost.ts, line 169 (link)

    P1 Unguarded JSON.parse — parse failure leaves post stuck in planned status

    Same pattern as classifySocialItems: if the model returns text that does not parse as JSON, the error propagates out of the generate-draft-copy Inngest step. With retries: 1 the post is never updated to pending_review and remains in planned status indefinitely. Wrap the parse in a try/catch and re-throw with a descriptive message so monitoring can detect the failure.

Reviews (1): Last reviewed commit: "Add support for socials sectoin" | Re-trigger Greptile

Comment on lines +19 to +52
const items = await listSocialDataItems(companyId, {
status: 'classified',
themeId,
limit: 1,
});
const item = items[0];
if (!item) {
return NextResponse.json({ error: 'No ready ideas available for this theme' }, { status: 400 });
}

const scheduledFor = body.scheduled_for || new Date(Date.now() + 24 * 60 * 60 * 1000).toISOString();
const posts = await createSocialPosts([
{
company_id: companyId,
theme_id: themeId,
data_item_id: item.id,
week_plan_id: null,
slot_index: null,
scheduled_for: scheduledFor,
status: 'pending_review',
},
]);

const post = posts[0];
if (!post) {
return NextResponse.json({ error: 'Failed to create post' }, { status: 500 });
}

await inngest.send({
name: 'autogtm/social.draft-post',
data: { companyId, postId: post.id },
});

return NextResponse.json({
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Item not reserved before async drafting — double-assignment race condition

The selected item stays in classified status until the Inngest draftSocialPost function later calls updateSocialDataItem(..., { status: 'used' }). Between creating the post and the async function running, a second call to this endpoint (or the weekly planner's cron) can pick up the same item, assigning it to two different posts simultaneously. The weekly planner explicitly sets items to reserved synchronously before handing off to Inngest — this route should do the same.

raw_text: item.raw_text,
suggested_theme_id: item.suggested_theme_id || null,
structured: item.structured,
confidence: item.confidence,
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 No try/catch around JSON.parse — malformed LLM output throws and fails the whole dump

JSON.parse(jsonText) will throw a SyntaxError if the model returns non-JSON output (e.g. a refusal, rate-limit message, or partial response). The exception propagates out of classifySocialItems, crashes the processSocialDump Inngest step, and—because retries: 1—leaves the dump permanently stuck in processing status. Wrap the parse in a try/catch and either throw a clear error (so the dump is marked failed) or return an empty classification array.

Comment on lines +88 to +100
image_status text not null default 'not_generated' check (image_status in ('not_generated', 'generating', 'generated', 'failed')),
scheduled_for timestamptz not null,
status text not null default 'planned' check (
status in ('planned', 'pending_review', 'approved', 'image_ready', 'published', 'failed', 'cancelled')
),
postiz_post_id text,
postiz_release_id text,
error text,
published_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists social_posts_company_schedule_idx on social_posts(company_id, scheduled_for);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 used_for_post_id lacks a foreign-key constraint

social_data_items.used_for_post_id is declared as plain uuid without a reference to social_posts(id). If a post is deleted the column silently retains a dangling UUID, which can confuse queries that check this field to determine usage status. Add the FK:

  used_for_post_id uuid references social_posts(id) on delete set null,

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.

1 participant