Архітектура: multi-tenant, два флоу, AI-адаптація Architecture: multi-tenant, two flows, AI adaptation
Стек
- Next.js 16 — app router, server actions, proxy.ts (замість middleware).
- Prisma 7 + SQLite (через
@prisma/adapter-better-sqlite3) — для простоти і швидкості. При потребі мігруємо на Postgres. - iron-session — cookie-based auth, bcrypt для паролів.
- @anthropic-ai/sdk — Claude Sonnet 4.6 для AI-адаптації, з prompt caching.
- ffmpeg — воркер для переформатування відео.
- PM2 — процес-менеджер, те саме що в інших моїх проектах.
Схема БД (9 моделей)
User | Адмін платформи (я + майбутні співробітники) |
Brand | Tenant — один бренд-клієнт. Має власний aiSystemPrompt, defaultHashtags, заборонені фрази. |
SocialAccount | Підключений акаунт мережі для бренду. Зберігає OAuth-токени. |
MediaAsset | Оригінальне медіа (з IG або завантажене). Поле source розрізняє джерело. |
AdaptedCaption | AI-адаптований текст per-platform. |
TranscodedMedia | ffmpeg-виходи per-format-profile. |
PublishJob | Одне «завдання публікувати X в Y мереж». |
PublishTarget | Одна ціль публікації (платформа+акаунт+статус). |
AuditLog | Аудит-трейл для Meta App Review і compliance. |
Два флоу контенту
Flow A — Repost з соцмережі
- IG ingest-cron підтягує нові пости через Graph API (кожні 6 годин).
- Створюється
MediaAssetзsource='instagram'. - Окремий cron завантажує медіа-файли з CDN до того, як URL прострочаться.
- Користувач у дашборді бачить Inbox, натискає «Переопублікувати в X, Y, Z».
- AI пише адаптовані caption'и, ffmpeg робить формати, PublishJob стартує.
Flow B — Прямий upload
- Користувач завантажує фото/відео з ПК або мобільного.
MediaAssetзsource='upload'.- Далі той самий pipeline: AI + ffmpeg + PublishJob.
Connector-інтерфейс
Замість того, щоб писати різні pipeline для кожної платформи, в коді є один інтерфейс:
interface PlatformConnector {
name: 'instagram' | 'facebook' | 'tiktok' | 'telegram' | 'pinterest' | 'web';
connect(brandId, oauthParams): Promise<SocialAccount>;
disconnect(accountId): Promise<void>;
prepareMedia(asset, profile): Promise<TranscodedMedia>;
publish(target, caption, account): Promise<{ externalId, externalUrl }>;
getStatus(externalId, account): Promise<PublishStatus>;
refreshToken(account): Promise<SocialAccount>;
}
Додати нову платформу = написати один файл, що реалізує цей інтерфейс. Решта системи не змінюється.
AI-адаптація: per-brand, per-platform
Логіка Claude-виклику:
system: [BRAND.aiSystemPrompt] + [PLATFORM_STYLE_RULES]
cache: [BRAND.aiSystemPrompt] // кешується на 1 годину
user: "Adapt this caption for TikTok:\n\n[original caption]"
Prompt caching економить ~90% токенів на повторних запитах для того ж бренду — це критично для економіки (детальніше в пості про бізнес-модель).
Multi-tenancy
Один User (власник акаунту) може мати багато Brand. Наприклад, SMM-агенція обслуговує 10 клієнтів — заводить 10 брендів в одному акаунті, кожен зі своїми підключеннями і AI-тоном.
Дані ізольовані через brandId FK на всіх таблицях. Ніякого cross-brand доступу.
Self-marketing pipeline
Через 2-3 місяці, коли core-функціонал стабільний, додаємо шостий бренд — сам OneClick Social. Playwright знімає демо-сценарії в додатку, Claude пише сценарій реклами, ElevenLabs озвучує, ffmpeg монтує, далі той самий pipeline публікує скрізь. Платформа рекламує себе автоматично.
Stack
- Next.js 16 — app router, server actions, proxy.ts (instead of middleware).
- Prisma 7 + SQLite (via
@prisma/adapter-better-sqlite3) for simplicity. Will migrate to Postgres when needed. - iron-session — cookie-based auth, bcrypt for passwords.
- @anthropic-ai/sdk — Claude Sonnet 4.6 for AI adaptation with prompt caching.
- ffmpeg — worker for video re-formatting.
- PM2 — process manager, same as my other projects.
DB schema (9 models)
User | Platform admin (me + future staff) |
Brand | Tenant — one brand-customer. Has its own aiSystemPrompt, defaultHashtags, forbidden phrases. |
SocialAccount | Connected network account for a brand. Stores OAuth tokens. |
MediaAsset | Original media (from IG or uploaded). Field source distinguishes the origin. |
AdaptedCaption | AI-adapted text per platform. |
TranscodedMedia | ffmpeg outputs per format profile. |
PublishJob | One "publish X to networks Y" job. |
PublishTarget | One publication target (platform+account+status). |
AuditLog | Audit trail for Meta App Review and compliance. |
Two content flows
Flow A — Repost from a social network
- IG ingest cron pulls new posts via Graph API (every 6h).
- A
MediaAssetwithsource='instagram'is created. - Separate cron downloads media files from CDN before URLs expire.
- User opens Inbox and clicks "re-publish to X, Y, Z".
- AI writes adapted captions, ffmpeg produces formats, PublishJob kicks off.
Flow B — Direct upload
- User uploads a photo/video from PC or mobile.
MediaAssetwithsource='upload'.- Same pipeline: AI + ffmpeg + PublishJob.
Connector interface
Instead of writing a different pipeline for each platform, the code has one interface:
interface PlatformConnector {
name: 'instagram' | 'facebook' | 'tiktok' | 'telegram' | 'pinterest' | 'web';
connect(brandId, oauthParams): Promise<SocialAccount>;
disconnect(accountId): Promise<void>;
prepareMedia(asset, profile): Promise<TranscodedMedia>;
publish(target, caption, account): Promise<{ externalId, externalUrl }>;
getStatus(externalId, account): Promise<PublishStatus>;
refreshToken(account): Promise<SocialAccount>;
}
Adding a new platform = writing one file that implements this interface. The rest of the system doesn't change.
AI adaptation: per-brand, per-platform
Claude call shape:
system: [BRAND.aiSystemPrompt] + [PLATFORM_STYLE_RULES]
cache: [BRAND.aiSystemPrompt] // cached for 1 hour
user: "Adapt this caption for TikTok:\n\n[original caption]"
Prompt caching saves ~90% of tokens on repeated calls for the same brand — critical for economics (see business model post).
Multi-tenancy
One User (account owner) can have many Brands. E.g. an SMM agency serving 10 customers runs 10 brands inside one account, each with its own connections and AI tone.
Data is isolated via brandId FK on all tables. No cross-brand access.
Self-marketing pipeline
In 2-3 months, when the core works, we add a sixth brand — OneClick Social itself. Playwright records demo flows in the app, Claude writes ad scripts, ElevenLabs voices them, ffmpeg edits, then the same pipeline publishes everywhere. The platform markets itself automatically.