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)
| Route | Method | Purpose |
|---|---|---|
/api/social/posts | GET | List posts (paginated, filterable by status/campaign/platform) |
/api/social/posts | POST | Create post (draft/scheduled/published), enforces tier post limit |
/api/social/posts/[id] | GET | Get single post with media and platform overrides |
/api/social/posts/[id] | PUT | Update post (body, schedule, platforms, media) |
/api/social/posts/[id]/publish | POST | Publish immediately to all target platforms |
/api/social/posts/[id]/retry | POST | Retry 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)
| Route | Method | Purpose |
|---|---|---|
/api/social/platforms | GET | List connected accounts for owner |
/api/social/platforms/connect | POST | Initiate OAuth flow (generates PKCE for X/TikTok, signs state JWT) |
/api/social/platforms/callback | GET | OAuth callback handler (validates state, exchanges code, stores encrypted tokens) |
/api/social/platforms/[id] | DELETE | Disconnect account (deletes social_accounts row) |
/api/social/platforms/data-deletion | POST | GDPR/CCPA data deletion webhook handler |
Platform connect checks the tier platform limit before redirecting to the OAuth provider.
AI Generation (3 routes)
| Route | Method | Purpose |
|---|---|---|
/api/social/ai/generate | POST | Generate new social post from church context + prompt |
/api/social/ai/repurpose | POST | Transform blog/sermon/illustration into platform-specific post |
/api/social/ai/suggest | POST | Suggest 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)
| Route | Method | Purpose |
|---|---|---|
/api/social/campaigns | GET | List campaigns for owner |
/api/social/campaigns | POST | Create campaign (name, color, description) |
/api/social/campaigns/[id] | PUT / DELETE | Update or delete campaign |
Campaigns are organizational containers -- posts reference a campaign_id for grouping.
Other Routes (11 routes)
| Route | Method | Purpose |
|---|---|---|
/api/social/analytics | GET | Platform analytics (impressions, engagement, followers) |
/api/social/billing | GET | Current subscription status, usage, limits |
/api/social/checkout | POST | Create Stripe Checkout session for tier upgrade |
/api/social/content-sources | GET / POST | Manage content sources (RSS feeds, SermonWise, ITW) |
/api/social/media/upload | POST | Upload media to Supabase Storage, returns asset record |
/api/social/schedule | GET | Get scheduled posts for calendar view (date range query) |
/api/social/settings | GET / PUT | Account settings, notification preferences |
/api/social/webhook | POST | Stripe 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_emailfor 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
| Platform | OAuth Provider | Client ID Env Var | PKCE |
|---|---|---|---|
| Meta OAuth v21.0 | META_APP_ID | N | |
| Meta OAuth v21.0 | META_APP_ID | N | |
| LinkedIn OAuth 2.0 | LINKEDIN_CLIENT_ID | N | |
| YouTube | Google OAuth 2.0 | GOOGLE_CLIENT_ID | N |
| X / Twitter | Twitter OAuth 2.0 | X_CLIENT_ID | Y |
| Threads | Threads OAuth | META_APP_ID | N |
| Pinterest OAuth | PINTEREST_APP_ID | N | |
| TikTok | TikTok OAuth 2.0 | TIKTOK_CLIENT_KEY | Y |
Cron Jobs
6 Vercel Cron jobs handle background processing. All authenticate via CRON_SECRET bearer
token (or INTERNAL_SOCIAL_KEY for social-specific auth).
| Cron | Schedule | Route | Purpose |
|---|---|---|---|
| publish | Every 5 min | /api/social/cron/publish | Find scheduled posts where scheduled_at has passed, publish to platforms |
| token-refresh | Every 30 min | /api/social/cron/token-refresh | Refresh OAuth tokens expiring within 24h |
| usage-reset | 1st of month | /api/social/cron/usage-reset | Reset posts_used_this_month and ai_used_this_month to 0 |
| trial-expiry | Daily | /api/social/cron/trial-expiry | Downgrade expired trial subscriptions to free tier |
| analytics-sync | Every 6h | /api/social/cron/analytics-sync | Fetch platform metrics, insert social_analytics_snapshots |
| auto-draft | Daily | /api/social/cron/auto-draft | Generate 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
| Table | Key Columns | Purpose |
|---|---|---|
social_subscriptions | tier, 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_accounts | platform_id, platform_account_id, account_name/handle/avatar, status, access_token (encrypted), refresh_token (encrypted), expires_at, follower_count | Connected platform accounts with encrypted OAuth tokens |
social_posts | body, 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_campaigns | name, color, description | Organizational grouping containers for posts |
social_media_assets | storage_path, media_type, file_size, dimensions (JSONB) | Uploaded images/videos in Supabase Storage |
social_analytics_snapshots | account_id (FK), platform_id, snapshot_date, impressions, engagements, clicks, follower_count, follower_change | Daily platform metrics populated by analytics-sync cron |
social_oauth_nonces | nonce (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
| Function | Purpose |
|---|---|
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:
| File | Purpose | Key Exports |
|---|---|---|
social-brand.ts | Brand identity config | socialBrand (name, domain, colors, nav) |
social-pricing.ts | Tier definitions + access checks | socialPricing, SOCIAL_TIER_LIMITS, canAccessSocial(), checkPostLimit(), checkAILimit(), checkPlatformLimit() |
social-auth.ts | Owner resolution + scoping | SocialOwner, resolveOwner(), ownerFilter(), ownerInsertFields(), getOwnerEmail() |
social-queries.ts | Supabase CRUD operations | getSubscription(), upsertSubscription(), getConnectedAccounts(), getAccountWithTokens(), disconnectAccount() |
social-platforms.ts | Platform specs + char limits | SOCIAL_PLATFORMS, PLATFORM_IDS, getPlatformConfig(), getCharacterCount() |
social-publisher.ts | Platform API publish calls | publishToAllPlatforms(), per-platform publish functions |
social-ai.ts | Claude API content generation | Generate posts, repurpose content, suggest hashtags/times |
social-oauth.ts | Token exchange + metadata fetch | exchangeCodeForTokens(), per-platform exchange functions, refreshAccessToken() |
social-crypto.ts | AES-256-GCM token encryption | encryptToken(), decryptToken() |
social-notifications.ts | Email notifications | sendPublishFailureEmail(), sendTrialExpiryEmail() |
social-content-sources.ts | RSS/feed fetching | Fetch content from external sources for auto-draft |
social-content-adapter.ts | Content transformation | Transform blog/sermon/illustration into social post format |
Components
11 React components in churchwiseai-web/src/components/social/:
| Component | Purpose |
|---|---|
AIGeneratePanel | Inline AI generation UI (prompt input, tone selector, generate button) |
OnboardingWizard | First-run wizard (connect platforms, choose tier, set preferences) |
PlatformPreviewCard | Live preview of post on each target platform (character count, image crop) |
MediaUploader | Drag-and-drop media upload with progress indicator |
PublishConfirmModal | Confirmation dialog before publishing (platform list, schedule time) |
SocialAppHeader | App shell header with navigation, brand logo, user menu |
AnalyticsSummaryCard | Metric card (impressions, engagement rate, follower growth) |
PostStatusBadge | Color-coded status pill (draft, scheduled, published, failed) |
NotificationToast | Toast notifications for publish success/failure, limit warnings |
CrossPromoBanner | Upsell banner to SermonWise/ITW for content repurposing |
ContentSourceCard | Card 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 Var | Purpose |
|---|---|
SOCIAL_OAUTH_STATE_SECRET | HMAC-SHA256 key for signing OAuth state JWTs |
SOCIAL_TOKEN_ENCRYPTION_KEY | AES-256 key (32 bytes hex) for encrypting platform tokens at rest |
INTERNAL_SOCIAL_KEY | Shared secret for cron job authentication (timing-safe HMAC) |
ANTHROPIC_API_KEY | Claude API for AI content generation |
CRON_SECRET | Vercel 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)
| Blocker | Status | What's Needed |
|---|---|---|
| OAuth app registrations | Not started | Register developer apps on all 8 platforms, get approved for required scopes |
| Stripe live products | Not started | Create 3 products with monthly + annual prices in live mode |
| sharewiseai.com DNS | Not verified | Point domain to Vercel, verify in Vercel dashboard |
| Supabase tables | Not created | Run migration to create all social_* tables and RPCs |
| Env vars on Vercel | Not set | All 18+ env vars listed above must be configured |
| MailerLite/Resend | Not wired | Email sending for notifications (publish failure, trial expiry) |
See Also
- ShareWiseAI Overview -- product identity, pricing, current state, competitive positioning
- Chatbot Architecture -- sibling product architecture for comparison
- SermonWise AI Overview -- content source for repurposing pipeline
- Pricing (canonical) -- all Stripe IDs and billing rules
- Strategy -- how ShareWiseAI fits the company roadmap