Overview
TendSocial's Platform Console provides a comprehensive system for managing feature access control and subscription packages through four interconnected systems:
| System | Purpose | UI Path |
|---|---|---|
| Feature Flags | Toggle features on/off, target specific segments | /platform/config/features |
| Segments | Group users/companies for targeting | /platform/config/segments |
| Entitlements | Map features to subscription plans | /platform/config/entitlements |
| Packages | Define subscription tiers and pricing | /platform/billing/packages |
How They Work Together
User requests feature X
│
▼
┌──────────────────────────┐
│ 1. Check Feature Flag │ Is flag enabled? Is user in targeted segment?
└───────────┬──────────────┘
│ (if flag passes or no flag exists)
▼
┌──────────────────────────┐
│ 2. Check Entitlements │ Does user's plan include this feature?
└───────────┬──────────────┘
│ (if entitled)
▼
✅ Access GrantedSegments
Segments are reusable groups for targeting features.
Prisma Model
prisma
model Segment {
id String @id @default(cuid())
name String @unique
description String?
// Inclusion (stored as arrays)
userIds String[] @default([])
companyIds String[] @default([])
// Exclusion
excludeUserIds String[] @default([])
excludeCompanyIds String[] @default([])
// Attribute Rules (JSON for flexible matching)
rules Json @default("[]")
ruleOperator String @default("OR") // AND | OR
isActive Boolean @default(true)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
featureFlags FeatureFlagSegment[]
}API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/platform/segments | List all segments |
| GET | /api/platform/segments/:id | Get single segment |
| POST | /api/platform/segments | Create segment |
| PUT | /api/platform/segments/:id | Update segment |
| DELETE | /api/platform/segments/:id | Delete segment (cascade deletes flag associations) |
| GET | /api/platform/segments/search-companies?q=&limit= | Search companies for multi-select |
| GET | /api/platform/segments/search-users?q=&limit= | Search users for multi-select |
| POST | /api/platform/segments/resolve-companies | Resolve company IDs to labels |
| POST | /api/platform/segments/resolve-users | Resolve user IDs to labels |
Segment Deactivation Behavior
- Deactivated segments (
isActive: false) are ignored during feature access evaluation - When re-enabled, all existing associations (feature flags, users, companies) become active again
- Deactivation is a soft pause, not a removal
Feature Flags
Feature flags control rollout of features - great for beta testing and kill switches.
Prisma Model
prisma
model FeatureFlag {
id String @id // Code-defined ID (e.g., 'feedback_widget')
name String
description String?
type String @default("boolean")
isEnabled Boolean @default(false) // Kill switch
// Targeting
segments FeatureFlagSegment[] // Via join table
companyOverrides String[] @default([]) // Specific company IDs
userOverrides String[] @default([]) // Specific user IDs or emails
defaultValue Json?
status String @default("development") // development, active, deprecated
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model FeatureFlagSegment {
id String @id @default(cuid())
featureFlagId String
segmentId String
featureFlag FeatureFlag @relation(fields: [featureFlagId], references: [id], onDelete: Cascade)
segment Segment @relation(fields: [segmentId], references: [id], onDelete: Cascade)
@@unique([featureFlagId, segmentId])
}API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/admin/config/features | List all feature flags (with segment includes) |
| POST | /api/admin/config/features | Create feature flag |
| PUT | /api/admin/config/features/:id | Update feature flag (isEnabled, overrides, etc.) |
| POST | /api/admin/config/features/:id/segments | Attach segment to flag |
| DELETE | /api/admin/config/features/:id/segments/:segmentId | Detach segment |
Checking Feature Access in Code
typescript
import { featureAccessService } from '../services/featureAccessService.js';
// Check if user has access (evaluates: Feature Flag -> Segments -> Entitlements)
const hasAccess = await featureAccessService.hasAccess('my_feature_flag', {
userId,
companyId,
email: 'user@company.com',
tier: 'professional'
});
// Helper from lib/featureFlags.ts
import { isFeatureEnabled, isFeatureEnabledForCompany } from '../services/featureFlags.service.js';
const enabled = await isFeatureEnabled('my_feature_flag', { companyId });
const companyEnabled = await isFeatureEnabledForCompany('my_feature_flag', companyId);Entitlements
Entitlements define which features each subscription plan includes.
Prisma Models
prisma
model EntitlementFeature {
id String @id // Code-defined: "ai_generation"
name String
description String?
type String // "boolean" | "number"
category String?
defaultEnabled Boolean @default(false)
defaultValue Int?
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
planEntitlements PlanEntitlement[]
}
model PlanEntitlement {
id String @id @default(cuid())
packageId String // References PackageConfig
featureId String // References EntitlementFeature
enabled Boolean @default(false)
value Int @default(-1) // -1 = unlimited
package PackageConfig @relation(fields: [packageId], references: [id], onDelete: Cascade)
feature EntitlementFeature @relation(fields: [featureId], references: [id], onDelete: Cascade)
@@unique([packageId, featureId])
}API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/platform/entitlements/features | List all entitlement features |
| POST | /api/platform/entitlements/features | Create entitlement feature |
| GET | /api/platform/entitlements/packages | List packages with entitlements |
| GET | /api/platform/entitlements/plans | Get the entitlements matrix |
| PUT | /api/platform/entitlements/plans/:packageId/:featureId | Toggle/set entitlement |
Checking Entitlements in Code
typescript
import { FeatureAccessService } from '../services/featureAccessService.js';
// Check if company's plan includes a feature (boolean entitlements)
const hasEntitlement = await FeatureAccessService.checkEntitlement(
'advanced_analytics',
companyId
);
// For AI usage with numeric limits, use EntitlementsService
import { entitlementsService } from '../services/entitlements.service.js';
// Check if action is allowed and get remaining usage
const result = await entitlementsService.checkAIAction(companyId, 'WEBSITE_ANALYSIS');
// { allowed: true, remaining: 5, limit: 10, used: 5 }
// Get complete usage summary for dashboard
const summary = await entitlementsService.getUsageSummary(companyId);
// { websiteAnalysis: { used: 5, limit: 10 }, ... }Packages
Packages define subscription tiers with pricing and feature limits.
Prisma Model
prisma
model PackageConfig {
id String @id @default(uuid())
name String @unique // "Starter", "Professional", etc.
description String?
price Decimal @default(0.00)
interval String @default("month") // month, year
currency String @default("USD")
// Limits
maxUsers Int @default(1)
maxAiPosts Int @default(50) // -1 for unlimited
displayFeatures String[] // UI display list
// Billing Integration
variantId String? // LemonSqueezy Variant ID
// Sales
salePrice Decimal?
saleStartsAt DateTime?
saleEndsAt DateTime?
// Status
isActive Boolean @default(true)
isPopular Boolean @default(false)
entitlements PlanEntitlement[]
}API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/platform/packages | List all packages |
| POST | /api/platform/packages | Create package |
| PUT | /api/platform/packages/:id | Update package |
| DELETE | /api/platform/packages/:id | Delete package |
Platform Console UI Paths
| Category | Page | Path |
|---|---|---|
| Configuration | Feature Flags | /platform/config/features |
| Configuration | Segments | /platform/config/segments |
| Configuration | Entitlements | /platform/config/entitlements |
| Billing | Billing Settings | /platform/billing/settings |
| Billing | Packages | /platform/billing/packages |
Audit Logging
All changes to feature flags, segments, entitlements, and packages are logged via auditService.log():
typescript
await auditService.log({
userId: request.user.userId,
action: 'CREATE' | 'UPDATE' | 'DELETE' | 'ATTACH_SEGMENT' | 'DETACH_SEGMENT',
entity: 'FeatureFlag' | 'Segment' | 'EntitlementFeature' | 'PlanEntitlement' | 'PackageConfig',
entityId: id,
details: { ... },
ipAddress: request.ip,
userAgent: request.headers['user-agent']
});Audit Log Viewer UI
View audit logs at /platform/reporting/audit-logs.
Features:
- Table View: Lists all audit events with Action, Resource, Target, User, and Timestamp columns
- Detail Panel: Click any log entry to view full metadata and JSON details
- Filtering: Toggle filter bar to search by Action (e.g., "LOGIN", "CREATE") and User ID
- Pagination: Navigate through large datasets
- Retention Settings: Configure how long logs are retained (30 days to 1 year)
API Endpoints
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/platform/audit-logs | List audit logs (with filtering) |
| GET | /api/platform/audit-logs/:id | Get specific log details |
| GET | /api/platform/audit-logs/settings | Get audit log retention settings |
| PUT | /api/platform/audit-logs/settings | Update retention settings |
Query Parameters (GET /audit-logs)
| Param | Type | Description |
|---|---|---|
page | number | Page number (default: 1) |
limit | number | Items per page (default: 20, max: 100) |
action | string | Filter by action (case-insensitive contains) |
userId | string | Filter by user ID |
companyId | string | Filter by company ID |
Cascade Delete Behavior
| When Deleted | Also Deleted |
|---|---|
| Segment | FeatureFlagSegment (links to flags), SegmentUser, SegmentCompany |
| FeatureFlag | FeatureFlagSegment, FeatureFlagUserOverride |
| PackageConfig | PlanEntitlement |
| EntitlementFeature | PlanEntitlement |