Skip to content

Overview

TendSocial's Platform Console provides a comprehensive system for managing feature access control and subscription packages through four interconnected systems:

SystemPurposeUI Path
Feature FlagsToggle features on/off, target specific segments/platform/config/features
SegmentsGroup users/companies for targeting/platform/config/segments
EntitlementsMap features to subscription plans/platform/config/entitlements
PackagesDefine 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 Granted

Segments

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

MethodEndpointDescription
GET/api/platform/segmentsList all segments
GET/api/platform/segments/:idGet single segment
POST/api/platform/segmentsCreate segment
PUT/api/platform/segments/:idUpdate segment
DELETE/api/platform/segments/:idDelete 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-companiesResolve company IDs to labels
POST/api/platform/segments/resolve-usersResolve 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

MethodEndpointDescription
GET/api/admin/config/featuresList all feature flags (with segment includes)
POST/api/admin/config/featuresCreate feature flag
PUT/api/admin/config/features/:idUpdate feature flag (isEnabled, overrides, etc.)
POST/api/admin/config/features/:id/segmentsAttach segment to flag
DELETE/api/admin/config/features/:id/segments/:segmentIdDetach 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

MethodEndpointDescription
GET/api/platform/entitlements/featuresList all entitlement features
POST/api/platform/entitlements/featuresCreate entitlement feature
GET/api/platform/entitlements/packagesList packages with entitlements
GET/api/platform/entitlements/plansGet the entitlements matrix
PUT/api/platform/entitlements/plans/:packageId/:featureIdToggle/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

MethodEndpointDescription
GET/api/platform/packagesList all packages
POST/api/platform/packagesCreate package
PUT/api/platform/packages/:idUpdate package
DELETE/api/platform/packages/:idDelete package

Platform Console UI Paths

CategoryPagePath
ConfigurationFeature Flags/platform/config/features
ConfigurationSegments/platform/config/segments
ConfigurationEntitlements/platform/config/entitlements
BillingBilling Settings/platform/billing/settings
BillingPackages/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

MethodEndpointDescription
GET/api/platform/audit-logsList audit logs (with filtering)
GET/api/platform/audit-logs/:idGet specific log details
GET/api/platform/audit-logs/settingsGet audit log retention settings
PUT/api/platform/audit-logs/settingsUpdate retention settings

Query Parameters (GET /audit-logs)

ParamTypeDescription
pagenumberPage number (default: 1)
limitnumberItems per page (default: 20, max: 100)
actionstringFilter by action (case-insensitive contains)
userIdstringFilter by user ID
companyIdstringFilter by company ID

Cascade Delete Behavior

When DeletedAlso Deleted
SegmentFeatureFlagSegment (links to flags), SegmentUser, SegmentCompany
FeatureFlagFeatureFlagSegment, FeatureFlagUserOverride
PackageConfigPlanEntitlement
EntitlementFeaturePlanEntitlement

See Also

TendSocial Documentation