Skip to main content

Knowledge > Products > ShareWiseAI > Architecture

ShareWiseAI Architecture

System Overview

ShareWiseAI is a multi-tenant social media SaaS built entirely within the churchwiseai-web Next.js 16 app. It has no standalone backend, no separate database, and no dedicated infrastructure. All API routes are Next.js serverless functions on Vercel, all data lives in the shared Supabase instance, and the frontend is served via hostname rewrite from sharewiseai.com to /social/* paths.

The architecture follows a layered pattern:

sharewiseai.com (hostname)
└── middleware.ts (rewrite to /social/*)
└── /social/app/* pages (React 19 client components)
└── /api/social/* routes (Next.js serverless)
├── social-auth.ts (owner resolution)
├── social-queries.ts (Supabase CRUD)
├── social-pricing.ts (tier enforcement)
├── social-publisher.ts (platform API calls)
├── social-ai.ts (Claude content generation)
├── social-oauth.ts (token exchange + refresh)
└── social-crypto.ts (token encryption)

API Routes

28 total API routes organized by domain. All routes live under churchwiseai-web/src/app/api/social/.

Posts (6 routes)

RouteMethodPurpose
/api/social/postsGETList posts (paginated, filterable by status/campaign/platform)
/api/social/postsPOSTCreate post (draft/scheduled/published), enforces tier post limit
/api/social/posts/[id]GETGet single post with media and platform overrides
/api/social/posts/[id]PUTUpdate post (body, schedule, platforms, media)
/api/social/posts/[id]/publishPOSTPublish immediately to all target platforms
/api/social/posts/[id]/retryPOSTRetry failed publish (re-attempts only failed platforms)

All post routes resolve the owner via resolveOwner() and scope queries with ownerFilter(). Creating a post checks the monthly post limit via checkPostLimit(tier, usedThisMonth) and increments posts_used_this_month on the subscription via the increment_social_usage RPC.

Platforms (5 routes)

RouteMethodPurpose
/api/social/platformsGETList connected accounts for owner
/api/social/platforms/connectPOSTInitiate OAuth flow (generates PKCE for X/TikTok, signs state JWT)
/api/social/platforms/callbackGETOAuth callback handler (validates state, exchanges code, stores encrypted tokens)
/api/social/platforms/[id]DELETEDisconnect account (deletes social_accounts row)
/api/social/platforms/data-deletionPOSTGDPR/CCPA data deletion webhook handler

Platform connect checks the tier platform limit before redirecting to the OAuth provider.

AI Generation (3 routes)

RouteMethodPurpose
/api/social/ai/generatePOSTGenerate new social post from church context + prompt
/api/social/ai/repurposePOSTTransform blog/sermon/illustration into platform-specific post
/api/social/ai/suggestPOSTSuggest hashtags, best posting time, content ideas

All AI routes enforce the monthly AI generation limit via checkAILimit(tier, usedThisMonth) and increment ai_used_this_month on the subscription.

LLM: Uses Anthropic Claude API (ANTHROPIC_API_KEY). Content generation is theology-aware -- prompts include the church's denomination and theological tradition when available.

Campaigns (3 routes)

RouteMethodPurpose
/api/social/campaignsGETList campaigns for owner
/api/social/campaignsPOSTCreate campaign (name, color, description)
/api/social/campaigns/[id]PUT / DELETEUpdate or delete campaign

Campaigns are organizational containers -- posts reference a campaign_id for grouping.

Other Routes (11 routes)

RouteMethodPurpose
/api/social/analyticsGETPlatform analytics (impressions, engagement, followers)
/api/social/billingGETCurrent subscription status, usage, limits
/api/social/checkoutPOSTCreate Stripe Checkout session for tier upgrade
/api/social/content-sourcesGET / POSTManage content sources (RSS feeds, SermonWise, ITW)
/api/social/media/uploadPOSTUpload media to Supabase Storage, returns asset record
/api/social/scheduleGETGet scheduled posts for calendar view (date range query)
/api/social/settingsGET / PUTAccount settings, notification preferences
/api/social/webhookPOSTStripe webhook for subscription lifecycle events

Auth Architecture

Three authentication systems coexist, resolved in priority order by resolveOwner() in social-auth.ts:

Resolution Order

1. Supabase Auth (session cookie)
└── SaaS users visiting /social/app/*
└── Returns: { type: 'user', userId: string }

2. Admin token (x-admin-token header)
└── Church dashboard customers (PewSearch integration)
└── Validates against premium_churches.admin_token WHERE status='active'
└── Returns: { type: 'church', churchId: string }

3. Internal property key (x-property-key + x-property-id headers)
└── Cron jobs, internal automation
└── Timing-safe HMAC comparison against INTERNAL_SOCIAL_KEY env var
└── Buffer-padded to prevent length oracle attacks
└── Returns: { type: 'property', propertyId: string }

4. No match → returns null → API responds 401

Owner Scoping

The SocialOwner discriminated union drives all data isolation:

  • ownerFilter(query, owner) -- appends .eq('user_id', ...) or .eq('church_id', ...) or .eq('property_id', ...) to any Supabase query, ensuring row-level data isolation.
  • ownerInsertFields(owner) -- returns the correct { user_id } / { church_id } / { property_id } object for INSERT operations.
  • getOwnerEmail(owner) -- resolves the email address for notifications (Supabase Auth admin API for users, premium_churches.contact_email for churches, null for properties).

OAuth Flow

The OAuth flow connects third-party social platforms to ShareWiseAI accounts.

Step-by-Step

1. User clicks "Connect [Platform]" in /social/app/platforms
└── POST /api/social/platforms/connect { platform_id: "facebook" }

2. Server validates:
└── Owner authenticated (resolveOwner)
└── Platform limit not exceeded (checkPlatformLimit)
└── Platform ID is valid (OAUTH_CONFIGS lookup)

3. Server generates auth redirect:
└── For X/TikTok: Generate PKCE pair (code_verifier + code_challenge)
└── Store nonce in social_oauth_nonces table (owner, platform, 10-min expiry)
└── Sign state JWT using jose (HMAC-SHA256 with SOCIAL_OAUTH_STATE_SECRET)
State payload: { nonce, owner_type, owner_id, platform, code_verifier? }
└── Build authorize URL with redirect_uri, scope, client_id, state

4. Redirect user to platform authorize endpoint

5. User grants permissions → platform redirects to callback

6. GET /api/social/platforms/callback?code=XXX&state=YYY
└── Verify state JWT (signature + expiry)
└── Validate nonce exists and hasn't been consumed (replay protection)
└── Delete nonce from social_oauth_nonces

7. Exchange authorization code for tokens:
└── exchangeCodeForTokens(platform, code, codeVerifier?)
└── Platform-specific token exchange (Facebook, Instagram, LinkedIn, YouTube, X, Threads, Pinterest, TikTok)
└── Fetch account metadata (name, handle, avatar, follower count)

8. Store account:
└── Encrypt access_token and refresh_token with AES-256-GCM (social-crypto.ts)
└── INSERT into social_accounts (owner fields, platform, tokens, metadata)

9. Redirect user back to /social/app/platforms with success message

PKCE Support

X/Twitter and TikTok require PKCE (Proof Key for Code Exchange) for OAuth 2.0:

code_verifier = randomBytes(32).toString('base64url')
code_challenge = SHA256(code_verifier).toString('base64url')

The code_verifier is embedded in the signed state JWT (never exposed to the browser). During callback, it is extracted from the state and sent with the token exchange request.

Token Encryption

All platform tokens are encrypted at rest using AES-256-GCM (social-crypto.ts):

ALGORITHM = 'aes-256-gcm'
KEY = SOCIAL_TOKEN_ENCRYPTION_KEY (32 bytes, hex-encoded env var)

encryptToken(plaintext):
iv = randomBytes(12)
data = AES-256-GCM encrypt(plaintext, key, iv)
tag = cipher.getAuthTag()
return base64(iv) + ':' + base64(tag) + ':' + base64(data)

decryptToken(encoded):
split on ':'
validate iv length (12 bytes)
AES-256-GCM decrypt(data, key, iv, tag)
return plaintext

Platform Configuration

Each platform is defined in social-platforms.ts as a SocialPlatformConfig with text limits, media constraints, scheduling/analytics support, and hashtag style. Key constraints:

  • Scheduling supported: Facebook, Instagram, YouTube, LinkedIn, Pinterest
  • No native scheduling API: X/Twitter, Threads, TikTok (server-side cron publishes at scheduled time)
  • PKCE required: X/Twitter, TikTok (OAuth 2.0 with code challenge)
  • Shared OAuth app: Facebook, Instagram, Threads all use META_APP_ID

Helper functions: getPlatformConfig(platformId) returns config or null; getCharacterCount(text, platformId) returns { count, max, over } for real-time UI validation.

OAuth Provider Mapping

PlatformOAuth ProviderClient ID Env VarPKCE
FacebookMeta OAuth v21.0META_APP_IDN
InstagramMeta OAuth v21.0META_APP_IDN
LinkedInLinkedIn OAuth 2.0LINKEDIN_CLIENT_IDN
YouTubeGoogle OAuth 2.0GOOGLE_CLIENT_IDN
X / TwitterTwitter OAuth 2.0X_CLIENT_IDY
ThreadsThreads OAuthMETA_APP_IDN
PinterestPinterest OAuthPINTEREST_APP_IDN
TikTokTikTok OAuth 2.0TIKTOK_CLIENT_KEYY

Cron Jobs

6 Vercel Cron jobs handle background processing. All authenticate via CRON_SECRET bearer token (or INTERNAL_SOCIAL_KEY for social-specific auth).

CronScheduleRoutePurpose
publishEvery 5 min/api/social/cron/publishFind scheduled posts where scheduled_at has passed, publish to platforms
token-refreshEvery 30 min/api/social/cron/token-refreshRefresh OAuth tokens expiring within 24h
usage-reset1st of month/api/social/cron/usage-resetReset posts_used_this_month and ai_used_this_month to 0
trial-expiryDaily/api/social/cron/trial-expiryDowngrade expired trial subscriptions to free tier
analytics-syncEvery 6h/api/social/cron/analytics-syncFetch platform metrics, insert social_analytics_snapshots
auto-draftDaily/api/social/cron/auto-draftGenerate AI draft posts from content sources (partially stubbed)

Publish Cron Detail

The publish cron is the most critical job. Its flow:

1. Verify CRON_SECRET bearer token
2. Call Supabase RPC: get_due_scheduled_posts(p_limit: 20)
└── Uses FOR UPDATE SKIP LOCKED to prevent double-processing
3. For each due post:
a. Set status = 'publishing' (optimistic lock)
b. Call publishToAllPlatforms(post, accounts, tokens)
└── Iterates target_platform_ids[]
└── Decrypt tokens for each platform account
└── Call platform-specific publish API
└── Collect per-platform results (success/failure)
c. Update post status:
└── All succeeded → status = 'published', published_at = now()
└── Some failed → status = 'partially_failed'
└── All failed → status = 'failed'
d. On failure: send publish failure email via sendPublishFailureEmail()
4. Return summary: { published: N, failed: M }

Database Schema

All tables use the social_ prefix. Owner columns (user_id, church_id, property_id) are nullable -- exactly one is set per row, determined by the SocialOwner type.

Table Summary

TableKey ColumnsPurpose
social_subscriptionstier, status, trial_ends_at, posts_used_this_month, ai_used_this_month, stripe_subscription_id, config (JSONB)Billing tier and monthly usage per owner
social_accountsplatform_id, platform_account_id, account_name/handle/avatar, status, access_token (encrypted), refresh_token (encrypted), expires_at, follower_countConnected platform accounts with encrypted OAuth tokens
social_postsbody, platform_overrides (JSONB), status, scheduled_at, published_at, target_platform_ids[], media_asset_ids[], campaign_id, source_type, ai_generated, publish_results (JSONB)All posts (draft, scheduled, publishing, published, failed, partially_failed)
social_campaignsname, color, descriptionOrganizational grouping containers for posts
social_media_assetsstorage_path, media_type, file_size, dimensions (JSONB)Uploaded images/videos in Supabase Storage
social_analytics_snapshotsaccount_id (FK), platform_id, snapshot_date, impressions, engagements, clicks, follower_count, follower_changeDaily platform metrics populated by analytics-sync cron
social_oauth_noncesnonce (PK), platform, owner_type, owner_id, expires_at (10-min TTL)Anti-replay protection for OAuth state tokens

Post Status Lifecycle

draft → scheduled → publishing → published
→ partially_failed → (retry) → published
→ failed → (retry) → published

Database RPCs

FunctionPurpose
increment_social_usage(p_subscription_id, p_field)Atomically increment posts_used_this_month or ai_used_this_month
get_due_scheduled_posts(p_limit)Return scheduled posts where scheduled_at <= now() with FOR UPDATE SKIP LOCKED

Library Files

12 library files in churchwiseai-web/src/lib/ handle all ShareWiseAI business logic:

FilePurposeKey Exports
social-brand.tsBrand identity configsocialBrand (name, domain, colors, nav)
social-pricing.tsTier definitions + access checkssocialPricing, SOCIAL_TIER_LIMITS, canAccessSocial(), checkPostLimit(), checkAILimit(), checkPlatformLimit()
social-auth.tsOwner resolution + scopingSocialOwner, resolveOwner(), ownerFilter(), ownerInsertFields(), getOwnerEmail()
social-queries.tsSupabase CRUD operationsgetSubscription(), upsertSubscription(), getConnectedAccounts(), getAccountWithTokens(), disconnectAccount()
social-platforms.tsPlatform specs + char limitsSOCIAL_PLATFORMS, PLATFORM_IDS, getPlatformConfig(), getCharacterCount()
social-publisher.tsPlatform API publish callspublishToAllPlatforms(), per-platform publish functions
social-ai.tsClaude API content generationGenerate posts, repurpose content, suggest hashtags/times
social-oauth.tsToken exchange + metadata fetchexchangeCodeForTokens(), per-platform exchange functions, refreshAccessToken()
social-crypto.tsAES-256-GCM token encryptionencryptToken(), decryptToken()
social-notifications.tsEmail notificationssendPublishFailureEmail(), sendTrialExpiryEmail()
social-content-sources.tsRSS/feed fetchingFetch content from external sources for auto-draft
social-content-adapter.tsContent transformationTransform blog/sermon/illustration into social post format

Components

11 React components in churchwiseai-web/src/components/social/:

ComponentPurpose
AIGeneratePanelInline AI generation UI (prompt input, tone selector, generate button)
OnboardingWizardFirst-run wizard (connect platforms, choose tier, set preferences)
PlatformPreviewCardLive preview of post on each target platform (character count, image crop)
MediaUploaderDrag-and-drop media upload with progress indicator
PublishConfirmModalConfirmation dialog before publishing (platform list, schedule time)
SocialAppHeaderApp shell header with navigation, brand logo, user menu
AnalyticsSummaryCardMetric card (impressions, engagement rate, follower growth)
PostStatusBadgeColor-coded status pill (draft, scheduled, published, failed)
NotificationToastToast notifications for publish success/failure, limit warnings
CrossPromoBannerUpsell banner to SermonWise/ITW for content repurposing
ContentSourceCardCard for managing content sources (RSS URL, sync status)

Environment Variables Required

18+ env vars must be configured on Vercel for ShareWiseAI to function.

Platform OAuth (12 vars): META_APP_ID + META_APP_SECRET (Facebook/Instagram/Threads), LINKEDIN_CLIENT_ID + SECRET, GOOGLE_CLIENT_ID + SECRET (YouTube), X_CLIENT_ID + SECRET, PINTEREST_APP_ID + SECRET, TIKTOK_CLIENT_KEY + SECRET.

Application secrets (5 vars):

Env VarPurpose
SOCIAL_OAUTH_STATE_SECRETHMAC-SHA256 key for signing OAuth state JWTs
SOCIAL_TOKEN_ENCRYPTION_KEYAES-256 key (32 bytes hex) for encrypting platform tokens at rest
INTERNAL_SOCIAL_KEYShared secret for cron job authentication (timing-safe HMAC)
ANTHROPIC_API_KEYClaude API for AI content generation
CRON_SECRETVercel Cron job authentication bearer token

Stripe price IDs (3 monthly vars, not yet created): STRIPE_SOCIAL_PRO_PRICE_ID, STRIPE_SOCIAL_BUSINESS_PRICE_ID, STRIPE_SOCIAL_AGENCY_PRICE_ID. Annual billing vars (STRIPE_SOCIAL_*_ANNUAL_PRICE_ID) are not yet in Stripe — annual pricing is planned but not live (product is Coming Soon).


Implementation Status

Built (MVP)

  • Marketing landing page + pricing page
  • Login/signup pages (Coming Soon stubs)
  • App dashboard (8 tabs: Dashboard, Calendar, Compose, Library, Analytics, Campaigns, Platforms, Settings)
  • Post composer with platform previews and character count validation
  • Content calendar with scheduled post display
  • Platform analytics dashboard (impressions, engagement, followers)
  • Campaign management (create, edit, delete, assign posts)
  • Platform OAuth connect/disconnect for all 8 platforms
  • AI content generation (generate, repurpose, suggest)
  • Stripe checkout integration for tier upgrades
  • Tier enforcement (post limits, AI limits, platform limits, feature access)
  • Usage quota tracking with atomic increment RPC
  • 6 cron jobs (publish, token-refresh, usage-reset, trial-expiry, analytics-sync, auto-draft)
  • AES-256-GCM token encryption at rest
  • OAuth state JWT signing with PKCE for X/TikTok
  • OAuth nonce replay protection
  • Owner-scoped data isolation (user/church/property)
  • Email notifications for publish failures and trial expiry
  • Cross-promo banners for SermonWise/ITW content repurposing
  • 11 reusable React components

Stubbed or Partial

  • X / Twitter scheduling: No native scheduling API. Publish cron handles server-side timed publish, but UI should warn users. TODO in social-platforms.ts.
  • Threads publish: Limited API surface, partially implemented.
  • TikTok publish: Limited API surface, partially implemented.
  • Analytics sync: Cron exists, but per-platform fetch logic is partially stubbed (Facebook/Instagram working, others pending).
  • Library page: Route exists (/social/app/library), UI is a stub.
  • AI Advisor page: Route exists (/social/app/ai-advisor), UI is a stub.
  • Auto-draft cron: Route exists, content source fetching partially implemented.

Not Started

  • Content sources management UI (backend route exists, frontend pending)
  • White-label reports (Agency tier feature)
  • Multi-workspace support (Agency tier feature, DB schema supports it but no UI)
  • Recurring schedule rules (Pro+ feature, no scheduler logic yet)
  • Customer support dashboard integration
  • Platform-specific rich previews (currently text-only preview cards)
  • Bulk post operations (select multiple, bulk schedule/delete)

Launch Blockers (Technical)

BlockerStatusWhat's Needed
OAuth app registrationsNot startedRegister developer apps on all 8 platforms, get approved for required scopes
Stripe live productsNot startedCreate 3 products with monthly + annual prices in live mode
sharewiseai.com DNSNot verifiedPoint domain to Vercel, verify in Vercel dashboard
Supabase tablesNot createdRun migration to create all social_* tables and RPCs
Env vars on VercelNot setAll 18+ env vars listed above must be configured
MailerLite/ResendNot wiredEmail sending for notifications (publish failure, trial expiry)

See Also