Services
The API uses a service layer pattern. Route handlers delegate to services, which contain all business logic. Services are the single source of truth for data operations -- routes never call Prisma directly.
Service Location
All services are in apps/api/src/services/. There are 57 service files organized by domain.
Core Services
| Service | Responsibility | Key Functions |
|---|---|---|
auth.service | Authentication, JWT issuance, OAuth flows, OTP | register(), login(), refreshToken(), oauthCallback() |
account.service | User account management | getProfile(), updateProfile(), deleteAccount() |
workspace.service | Workspace CRUD, member management, invitations | create(), addMember(), updateRole(), removeMember() |
notification.service | Push and in-app notifications | send(), markRead(), getUnread() |
audit-log.service | Audit trail recording | log(), query() |
Content Services
| Service | Responsibility | Key Functions |
|---|---|---|
post.service | Post CRUD, publishing pipeline | create(), publish(), duplicate(), getAll() |
scheduler.service | Schedule management, trigger publishing | schedule(), cancel(), reschedule() |
media.service | File upload to S3, storage, optimization | upload(), delete(), getUrl() |
brand-voice.service | Brand voice training and retrieval | train(), apply(), getConfig() |
content-calendar.service | AI calendar planning and generation | generate(), getEntries() |
content-repurpose.service | Content transformation across formats | repurpose() |
AI Services
| Service | Responsibility | Key Functions |
|---|---|---|
ai.service | Core Gemini API integration | callGemini(), generateCaption(), rewriteCaption(), generateHashtags(), generateCTA(), translateCaption(), generateImage() |
ai-advisor.service | Performance recommendations | getRecommendations() |
trend-scanner.service | Trend detection and analysis | scan(), getTrends() |
classification.service | Post content type classification | classify(), getClassification() |
embedding.service | Vector embeddings (text-embedding-004) | generate(), search(), similarity() |
AI Usage Tracking
All AI service calls go through incrementUsage() which tracks per-workspace consumption against plan quotas. Usage data is stored in the WorkspaceUsage model and accessible via the admin dashboard.
Conversation Engine Services (ICE)
| Service | Responsibility | Key Functions |
|---|---|---|
conversation-engine.service | Orchestrates the 3-step LLM pipeline | processIncomingMessage(), listThreads(), getThread(), getThreadMessages(), sendAgentReply(), resolveThread(), reopenThread() |
conversation-brain.service | LLM response generation via Gemini | generateReply(), suggestReply() |
conversation-memory.service | 3-tier memory system (short/medium/long term) | getContext(), store(), update() |
intent-classifier.service | Message intent and sentiment detection | classify() |
escalation.service | Escalation rule evaluation and routing | evaluate(), escalate() |
context-builder.service | Builds full context for AI responses | build() |
bot-config.service | Bot toggle and configuration | toggleThreadBot(), getBotConfig(), updateBotConfig(), getInboxStats() |
Analytics Services
| Service | Responsibility | Key Functions |
|---|---|---|
analytics.service | Metrics aggregation, dashboard, time-series | getDashboard(), getTimeSeries(), getByPlatform(), getPostAnalytics() |
ab-test.service | A/B test CRUD and evaluation | create(), evaluate(), getResults() |
prediction.service | Engagement and revenue predictions | predict(), getAccuracy() |
benchmark.service | Industry and niche benchmarking | compute(), compare() |
Integration Services
| Service | Responsibility | Key Functions |
|---|---|---|
ecommerce.service | Store sync (Shopify/WooCommerce/EasyOrders) | connect(), syncProducts(), syncOrders() |
commerce-order.service | Order tracking, revenue attribution | getOrders(), attributeRevenue() |
ads.service | Meta Ads, TikTok Promote, auto-boost | createCampaign(), boost(), syncMetrics() |
competitor.service | Competitor data management | add(), getSnapshots(), getReport() |
competitor-scraper.service | Competitor metrics scraping | scrape() |
payment.service | Stripe/Paymob processing, webhooks | createSession(), handleWebhook() |
integration.service | Third-party integrations | connect(), disconnect(), sync() |
Workflow Services
| Service | Responsibility | Key Functions |
|---|---|---|
workflow-engine.service | Workflow execution engine | emitWorkflowEvent(), triggerTestRun(), executeWorkflow(), evaluateCondition(), executeAction() |
The workflow engine supports these node types and actions:
| Node Type | Purpose |
|---|---|
trigger | Event that starts the workflow |
condition | Evaluates a boolean expression |
action | Performs an operation |
| Condition Operator | Description |
|---|---|
eq | Equal to |
neq | Not equal to |
gt / gte | Greater than / greater than or equal |
lt / lte | Less than / less than or equal |
contains | String contains |
not_contains | String does not contain |
| Action Type | Description |
|---|---|
send_notification | Send a notification to workspace members |
create_draft | Create a draft post |
publish_post | Publish a post immediately |
send_reply | Send a conversation reply |
wait | Delay execution for a duration |
boost_post | Auto-boost a post via ads |
ai_repurpose_content | Repurpose content using AI |
Other Services
| Service | Responsibility | Key Functions |
|---|---|---|
audience.service | Audience graph, segmentation, tagging | getNodes(), getSegments(), tag() |
approval.service | Approval request workflows | request(), approve(), reject() |
client-report.service | Client report generation | generate(), getReports() |
Writing a New Service
Follow this pattern for consistency:
// apps/api/src/services/example.service.ts
import { prisma } from '../lib/prisma';
import type { CreateExampleInput } from '@unipulse/shared';
export const exampleService = {
async getAll(workspaceId: string) {
return prisma.example.findMany({
where: { workspaceId },
orderBy: { createdAt: 'desc' },
});
},
async getById(workspaceId: string, id: string) {
const item = await prisma.example.findFirst({
where: { id, workspaceId },
});
if (!item) throw new AppError('NOT_FOUND', 'Example not found');
return item;
},
async create(workspaceId: string, data: CreateExampleInput) {
return prisma.example.create({
data: { ...data, workspaceId },
});
},
async update(workspaceId: string, id: string, data: Partial<CreateExampleInput>) {
return prisma.example.update({
where: { id, workspaceId },
data,
});
},
async delete(workspaceId: string, id: string) {
return prisma.example.delete({
where: { id, workspaceId },
});
},
};
Always Scope by Workspace
Every query must include workspaceId to maintain multi-tenant isolation. Never return data from other workspaces.
Cross-Reference
- API Reference -- route-to-service mapping
- Queue System -- async processing via BullMQ
- AI Integration -- Gemini API details
- Conversation Engine -- ICE pipeline
- New Feature Checklist -- end-to-end feature guide