- Redesigned as standalone microservice for managing web and mobile apps - Added invite code registration system with enable/disable toggle - Service will be deployed on separate server (10.0.0.241) - Admin service at admin.parentflowapp.com - Added database schema for invite codes and usage tracking - Platform-specific code validation (web, iOS, Android) - Service-to-service authentication for secure communication - Batch code generation and export functionality - Analytics for tracking invite code effectiveness Architecture benefits: - Centralized control for all ParentFlow platforms - Independent scaling and deployment - Better security isolation for admin functions - Support for future mobile app management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
47 KiB
Admin Dashboard Implementation Plan
Created: October 3, 2025 Updated: October 6, 2025 - Separate microservice architecture Status: Planning Phase Priority: High - Multi-tenancy & Administration Estimated Effort: 6-8 weeks (120-160 hours)
📋 Executive Summary
Overview
Create a comprehensive standalone admin service for managing ParentFlow applications across multiple platforms (web, iOS, Android) with role-based access control (RBAC), user management, system monitoring, and configuration. This service will be deployed independently and manage all ParentFlow instances centrally.
Architecture Decision
The admin dashboard will be implemented as a separate microservice to:
- Manage both web and future mobile applications
- Scale independently from main application
- Provide centralized control across all platforms
- Enable separate deployment and security policies
- Support multi-tenant administration
Key Features
- Invite Code Registration System: Control user registration with invite codes
- User Management: CRUD operations, data export, anonymization
- Role-Based Access Control: Parent, Guest, Admin roles
- Multi-Platform Support: Manage web, iOS, and Android users
- Multi-Profile Support: Multiple families and account switching
- Analytics Dashboard: Cross-platform usage metrics, AI/voice analytics
- System Health Monitoring: Service status across all platforms
- LLM Configuration: Model settings, API keys, pricing
- Content Management: Legal pages, subscriptions
- Email Configuration: Mailgun settings management
Goals
- Security: Strict admin authentication and audit logging
- Scalability: Support growing user base
- Flexibility: Easy configuration updates without code changes
- Compliance: GDPR/COPPA data management tools
🏗️ Architecture Overview
Microservice Architecture
┌─────────────────────────────────────────────────────────────┐
│ ParentFlow Admin Service │
│ (Standalone Application) │
│ admin.parentflowapp.com │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Invite │ │ Users │ │ Analytics │ │
│ │ Codes │ │ Management │ │ Dashboard │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Platform │ │ System │ │ LLM │ │
│ │ Manager │ │ Health │ │ Settings │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
Service-to-Service
Communication
│
┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
┌──────────────┐ ┌──────────────┐ ┌──────────────┐
│ ParentFlow │ │ ParentFlow │ │ ParentFlow │
│ Web │ │ iOS │ │ Android │
│ (NestJS) │ │ (Swift) │ │ (Kotlin) │
├──────────────┤ ├──────────────┤ ├──────────────┤
│ Validates │ │ Validates │ │ Validates │
│ invite codes │ │ invite codes │ │ invite codes │
│ with admin │ │ with admin │ │ with admin │
│ service │ │ service │ │ service │
└──────────────┘ └──────────────┘ └──────────────┘
│ │ │
▼ ▼ ▼
┌─────────────────────────────────────────────────────────────┐
│ Shared Database Infrastructure │
│ │
│ • users (cross-platform) │
│ • invite_codes (registration control) │
│ • platform_sessions (web/ios/android tracking) │
│ • admin_audit_log (all admin actions) │
│ • system_configurations (per-platform settings) │
└─────────────────────────────────────────────────────────────┘
Admin Service Components
parentflow-admin-service/
├── src/
│ ├── modules/
│ │ ├── invite-codes/ # Invite code generation & validation
│ │ ├── user-management/ # Cross-platform user management
│ │ ├── platform-manager/ # Web/iOS/Android platform configs
│ │ ├── analytics/ # Unified analytics across platforms
│ │ ├── system-health/ # Monitor all platform services
│ │ ├── llm-config/ # AI configuration
│ │ ├── subscriptions/ # Subscription management
│ │ └── audit/ # Audit logging
│ ├── common/
│ │ ├── guards/ # Authentication guards
│ │ ├── interceptors/ # Service communication
│ │ └── decorators/ # Custom decorators
│ └── database/
│ └── migrations/ # Admin-specific migrations
├── admin-ui/ # React admin dashboard
│ ├── pages/
│ │ ├── invite-codes/ # Manage invite codes
│ │ ├── users/ # User management
│ │ ├── platforms/ # Platform-specific settings
│ │ └── analytics/ # Analytics dashboard
│ └── components/
│ └── shared/ # Shared UI components
└── docker-compose.admin.yml # Separate deployment
---
## 👥 Role-Based Access Control (RBAC)
### Role Definitions
#### 1. Parent (Default Role)
**Permissions**:
- Full access to their own families
- Create/manage children
- View all family data (activities, analytics)
- Invite guests to their families
- AI chat, voice commands
- Manage profile and settings
**Database**:
```typescript
enum UserRole {
PARENT = 'parent',
GUEST = 'guest',
ADMIN = 'admin',
}
2. Guest (Limited Access)
Permissions:
- CAN DO:
- Add activities (feeding, sleep, diaper, medication) - manual or voice
- View real-time data for assigned family
- Receive notifications
- CANNOT DO:
- View historical data (>24 hours)
- View analytics/insights
- Edit or delete activities (except their own, within 1 hour)
- Manage children profiles
- Access AI chat
- Invite other users
- Change family settings
Use Cases:
- Nanny tracking daily activities
- Grandparents helping during visits
- Babysitter logging quick updates
3. Admin (Full System Access)
Permissions:
- Access admin dashboard
- Manage all users (CRUD, anonymize, export)
- View system-wide analytics
- Configure LLM models
- Manage subscriptions and trials
- Edit legal pages
- Configure email settings
- View system health
- Audit logs access
Security:
- Admins CANNOT access user data without explicit audit trail
- All admin actions logged to
admin_audit_log - Require 2FA for admin accounts
🎟️ Invite Code Registration System
Overview
Control user registration through invite codes to limit initial app access. This feature allows controlled rollout and beta testing across all platforms (web, iOS, Android).
Features
- Generate invite codes: Batch or individual generation
- Code types: Single-use, multi-use, unlimited
- Expiration dates: Time-limited codes
- Platform restrictions: Limit codes to specific platforms
- Usage tracking: See who used which code
- Enable/Disable: Toggle registration requirement globally
Database Schema
-- Invite codes table
CREATE TABLE invite_codes (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
code VARCHAR(20) UNIQUE NOT NULL, -- e.g., "BETA-2025-X7K9"
type VARCHAR(20) NOT NULL, -- 'single', 'multi', 'unlimited'
max_uses INTEGER DEFAULT 1, -- NULL for unlimited
current_uses INTEGER DEFAULT 0,
platform_restrictions VARCHAR(20)[], -- ['web', 'ios', 'android'] or NULL for all
expires_at TIMESTAMP,
metadata JSONB, -- Custom data: { "campaign": "beta", "referrer": "influencer1" }
created_by UUID REFERENCES users(id),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_invite_codes_code ON invite_codes(code);
CREATE INDEX idx_invite_codes_active ON invite_codes(is_active) WHERE is_active = true;
-- Invite code usage tracking
CREATE TABLE invite_code_usage (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
invite_code_id UUID NOT NULL REFERENCES invite_codes(id),
used_by_user_id UUID NOT NULL REFERENCES users(id),
platform VARCHAR(20) NOT NULL, -- 'web', 'ios', 'android'
device_info JSONB,
ip_address INET,
used_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_invite_code_usage_code ON invite_code_usage(invite_code_id);
CREATE INDEX idx_invite_code_usage_user ON invite_code_usage(used_by_user_id);
-- System configuration for registration
CREATE TABLE registration_config (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
require_invite_code BOOLEAN DEFAULT false,
registration_enabled BOOLEAN DEFAULT true,
custom_message TEXT, -- Message shown when registration is disabled
whitelist_domains VARCHAR(255)[], -- Email domains that bypass invite requirement
updated_by UUID REFERENCES users(id),
updated_at TIMESTAMP DEFAULT NOW()
);
API Endpoints
Admin Endpoints
// Admin Service API
POST /api/v1/admin/invite-codes // Generate codes
GET /api/v1/admin/invite-codes // List all codes
GET /api/v1/admin/invite-codes/:id // Get code details
PATCH /api/v1/admin/invite-codes/:id // Update code
DELETE /api/v1/admin/invite-codes/:id // Deactivate code
GET /api/v1/admin/invite-codes/:id/usage // Get usage history
// Batch operations
POST /api/v1/admin/invite-codes/batch // Generate multiple codes
POST /api/v1/admin/invite-codes/export // Export codes as CSV
// Configuration
GET /api/v1/admin/registration/config // Get registration settings
PATCH /api/v1/admin/registration/config // Update settings
Application Endpoints (Web/Mobile)
// Public endpoint for all platforms
POST /api/v1/auth/validate-invite-code
{
"code": "BETA-2025-X7K9",
"platform": "ios"
}
// Response
{
"valid": true,
"message": "Code accepted",
"metadata": {
"campaign": "beta"
}
}
// Registration endpoint (modified)
POST /api/v1/auth/register
{
"email": "user@example.com",
"password": "...",
"inviteCode": "BETA-2025-X7K9", // Required if enabled
"platform": "web"
}
Admin UI Components
// pages/invite-codes/page.tsx
export default function InviteCodesPage() {
const [requireInvite, setRequireInvite] = useState(false);
return (
<div>
{/* Global toggle */}
<Card>
<h3>Registration Settings</h3>
<Switch
label="Require invite code for registration"
checked={requireInvite}
onChange={setRequireInvite}
/>
<Text>Currently: {requireInvite ? 'Invite only' : 'Open registration'}</Text>
</Card>
{/* Code generator */}
<Card>
<h3>Generate Invite Codes</h3>
<Form>
<Input label="Prefix" placeholder="BETA-2025" />
<Select label="Type">
<option value="single">Single use</option>
<option value="multi">Multi-use</option>
<option value="unlimited">Unlimited</option>
</Select>
<Input label="Max uses" type="number" />
<DatePicker label="Expires at" />
<MultiSelect label="Platform restrictions">
<option value="web">Web</option>
<option value="ios">iOS</option>
<option value="android">Android</option>
</MultiSelect>
<Input label="Quantity" type="number" defaultValue="1" />
<Button>Generate Codes</Button>
</Form>
</Card>
{/* Codes list */}
<DataTable
columns={[
{ key: 'code', label: 'Code' },
{ key: 'type', label: 'Type' },
{ key: 'uses', label: 'Uses', render: (row) => `${row.current_uses}/${row.max_uses || '∞'}` },
{ key: 'platforms', label: 'Platforms' },
{ key: 'expires_at', label: 'Expires' },
{ key: 'actions', label: 'Actions' }
]}
data={inviteCodes}
/>
</div>
);
}
Implementation in Main Application
// src/modules/auth/auth.service.ts (ParentFlow Web/API)
@Injectable()
export class AuthService {
async register(dto: RegisterDto) {
// Check if invite codes are required
const config = await this.getRegistrationConfig();
if (config.requireInviteCode) {
if (!dto.inviteCode) {
throw new BadRequestException('Invite code is required for registration');
}
// Validate with admin service
const validation = await this.adminService.validateInviteCode(
dto.inviteCode,
dto.platform || 'web'
);
if (!validation.valid) {
throw new BadRequestException(validation.message || 'Invalid invite code');
}
// Track usage
await this.adminService.recordInviteCodeUsage(
dto.inviteCode,
userId,
dto.platform
);
}
// Continue with normal registration...
}
private async getRegistrationConfig() {
// Cache this for performance
return await this.adminService.getRegistrationConfig();
}
}
Mobile Integration
// iOS - RegistrationViewController.swift
func validateInviteCode(_ code: String) async throws -> Bool {
let response = try await AdminAPI.validateInviteCode(
code: code,
platform: "ios"
)
return response.valid
}
func register() async throws {
// Check if invite code is required
if registrationConfig.requireInviteCode {
guard let inviteCode = inviteCodeTextField.text, !inviteCode.isEmpty else {
throw RegistrationError.inviteCodeRequired
}
// Validate before proceeding
let isValid = try await validateInviteCode(inviteCode)
if !isValid {
throw RegistrationError.invalidInviteCode
}
}
// Continue with registration
}
Analytics & Reporting
Track invite code effectiveness:
- Conversion rate: Codes generated vs. used
- Platform distribution: Usage by platform
- Time to use: Average time from generation to use
- Referral tracking: Which campaigns/sources are most effective
- Geographic distribution: Where users are registering from
🗄️ Database Schema Changes
New Tables
1. User Role Enhancement
-- Modify existing users table
ALTER TABLE users
ADD COLUMN global_role VARCHAR(20) DEFAULT 'parent',
ADD COLUMN is_admin BOOLEAN DEFAULT false,
ADD COLUMN admin_permissions JSONB DEFAULT '[]';
-- Add index for admin queries
CREATE INDEX idx_users_global_role ON users(global_role);
CREATE INDEX idx_users_is_admin ON users(is_admin) WHERE is_admin = true;
2. Family Membership Roles
-- Modify family_memberships table
ALTER TABLE family_memberships
ADD COLUMN family_role VARCHAR(20) DEFAULT 'parent', -- parent or guest
ADD COLUMN permissions JSONB DEFAULT '{}',
ADD COLUMN invited_by UUID REFERENCES users(id),
ADD COLUMN access_granted_at TIMESTAMP DEFAULT NOW(),
ADD COLUMN access_expires_at TIMESTAMP NULL;
-- Constraints
ALTER TABLE family_memberships
ADD CONSTRAINT valid_family_role
CHECK (family_role IN ('parent', 'guest'));
3. User Profiles (Multi-Profile Support)
CREATE TABLE user_profiles (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
profile_name VARCHAR(100) NOT NULL,
profile_type VARCHAR(20) NOT NULL, -- 'primary', 'work', 'personal'
default_family_id UUID REFERENCES families(id),
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(user_id, profile_name)
);
CREATE INDEX idx_user_profiles_user_id ON user_profiles(user_id);
4. Admin Audit Log
CREATE TABLE admin_audit_log (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
admin_user_id UUID NOT NULL REFERENCES users(id),
action VARCHAR(50) NOT NULL, -- 'user.create', 'user.delete', 'config.update'
target_entity VARCHAR(50), -- 'user', 'subscription', 'config'
target_id UUID,
changes JSONB, -- before/after values
ip_address INET,
user_agent TEXT,
created_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_admin_audit_log_admin_user ON admin_audit_log(admin_user_id);
CREATE INDEX idx_admin_audit_log_created_at ON admin_audit_log(created_at DESC);
CREATE INDEX idx_admin_audit_log_action ON admin_audit_log(action);
5. LLM Configuration
CREATE TABLE llm_config (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
provider VARCHAR(50) NOT NULL, -- 'azure', 'openai', 'anthropic'
service_type VARCHAR(50) NOT NULL, -- 'chat', 'whisper', 'embeddings'
endpoint_url TEXT NOT NULL,
api_key_encrypted TEXT NOT NULL, -- Encrypted with AES-256
model_name VARCHAR(100) NOT NULL,
deployment_name VARCHAR(100), -- For Azure
api_version VARCHAR(20),
price_per_1m_input_tokens DECIMAL(10, 4), -- e.g., 0.0015 for $1.50/1M
price_per_1m_output_tokens DECIMAL(10, 4),
max_tokens INTEGER DEFAULT 4000,
temperature DECIMAL(3, 2) DEFAULT 0.7,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
CREATE INDEX idx_llm_config_provider_service ON llm_config(provider, service_type);
6. Subscription Plans
CREATE TABLE subscription_plans (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name VARCHAR(100) NOT NULL UNIQUE,
description TEXT,
price_monthly DECIMAL(10, 2),
price_yearly DECIMAL(10, 2),
features JSONB, -- Feature flags: { "ai_queries": "unlimited", "voice_commands": 500 }
max_children INTEGER,
max_family_members INTEGER,
ai_query_limit INTEGER, -- NULL = unlimited
voice_command_limit INTEGER,
trial_period_days INTEGER DEFAULT 14,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
-- Add subscription to users
ALTER TABLE users
ADD COLUMN subscription_plan_id UUID REFERENCES subscription_plans(id),
ADD COLUMN subscription_status VARCHAR(20) DEFAULT 'trial', -- trial, active, expired, cancelled
ADD COLUMN subscription_started_at TIMESTAMP,
ADD COLUMN subscription_expires_at TIMESTAMP,
ADD COLUMN trial_ends_at TIMESTAMP;
7. Email Configuration
CREATE TABLE email_config (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
provider VARCHAR(50) DEFAULT 'mailgun',
region VARCHAR(10) DEFAULT 'US', -- US or EU
api_key_encrypted TEXT NOT NULL,
domain VARCHAR(255) NOT NULL,
sender_email VARCHAR(255) NOT NULL,
sender_name VARCHAR(255) NOT NULL,
is_active BOOLEAN DEFAULT true,
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW()
);
8. Legal Pages (CMS)
CREATE TABLE legal_pages (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
slug VARCHAR(100) NOT NULL UNIQUE, -- 'privacy', 'terms', 'eula', 'cookies'
title VARCHAR(255) NOT NULL,
content TEXT NOT NULL, -- Markdown or HTML
language VARCHAR(5) DEFAULT 'en',
version INTEGER DEFAULT 1,
is_published BOOLEAN DEFAULT false,
last_updated_by UUID REFERENCES users(id),
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
UNIQUE(slug, language)
);
🔐 Security & Permissions
Admin Guard Implementation
File: src/common/guards/admin.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { JwtAuthGuard } from './jwt-auth.guard';
@Injectable()
export class AdminGuard extends JwtAuthGuard {
async canActivate(context: ExecutionContext): Promise<boolean> {
// First check JWT authentication
const isAuthenticated = await super.canActivate(context);
if (!isAuthenticated) {
return false;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
// Check if user is admin
if (!user.isAdmin) {
throw new ForbiddenException('Admin access required');
}
return true;
}
}
Family Role Guard
File: src/common/guards/family-role.guard.ts
import { Injectable, CanActivate, ExecutionContext, ForbiddenException } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
export const RequireFamilyRole = (...roles: string[]) =>
SetMetadata('familyRoles', roles);
@Injectable()
export class FamilyRoleGuard implements CanActivate {
constructor(private reflector: Reflector, private familyService: FamilyService) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const requiredRoles = this.reflector.get<string[]>('familyRoles', context.getHandler());
if (!requiredRoles) {
return true;
}
const request = context.switchToHttp().getRequest();
const user = request.user;
const familyId = request.params.familyId || request.body.familyId;
const membership = await this.familyService.getUserFamilyRole(user.id, familyId);
if (!membership) {
throw new ForbiddenException('Not a member of this family');
}
if (!requiredRoles.includes(membership.familyRole)) {
throw new ForbiddenException(`Requires family role: ${requiredRoles.join(' or ')}`);
}
// Attach to request for use in controllers
request.familyRole = membership.familyRole;
return true;
}
}
Usage Example
@Controller('api/v1/analytics')
export class AnalyticsController {
@Get(':childId/insights')
@UseGuards(JwtAuthGuard, FamilyRoleGuard)
@RequireFamilyRole('parent') // Guests cannot access analytics
async getInsights(@Param('childId') childId: string) {
// Only parents can access
}
@Post(':childId/activities')
@UseGuards(JwtAuthGuard, FamilyRoleGuard)
@RequireFamilyRole('parent', 'guest') // Both can add activities
async createActivity(@Param('childId') childId: string) {
// Both parents and guests can add
}
}
📊 Admin Dashboard Features
1. User Management
Endpoint: GET /api/v1/admin/users
Features:
- List Users: Paginated, searchable, filterable
- User Details: Full profile, activity history, subscription
- Create User: Manual user creation (for demos, test accounts)
- Edit User: Update profile, subscription, role
- Delete User: Soft delete with anonymization option
- Anonymize User: GDPR compliance - replace PII with anonymized data
- Export User Data: Full data export in JSON/CSV
- Verify User: Manual email verification override
- Manage Subscription: Change plan, extend trial, apply discount
UI Components:
maternal-web/app/admin/users/
├── page.tsx # User list with search/filter
├── [userId]/
│ ├── page.tsx # User detail view
│ ├── edit/page.tsx # Edit user form
│ └── export/page.tsx # Data export interface
└── create/page.tsx # Create user form
Backend Module:
// src/modules/admin/user-management/user-management.service.ts
@Injectable()
export class UserManagementService {
async listUsers(filters: UserFilters, pagination: Pagination) {
// Query with filters, sorting, pagination
}
async getUserDetails(userId: string) {
// Full user details including:
// - Profile info
// - Families and roles
// - Subscription status
// - Activity stats
// - AI usage stats
}
async anonymizeUser(userId: string, adminId: string) {
// GDPR compliance
// Replace: name → "Anonymized User 12345"
// Replace: email → "anon_12345@maternal.local"
// Keep: aggregated analytics (anonymized)
// Log action in audit_log
}
async exportUserData(userId: string) {
// Full GDPR export
// - User profile
// - Children data
// - Activities
// - AI conversations
// - Photos (URLs)
return { json, csv };
}
async changeSubscription(userId: string, planId: string, adminId: string) {
// Update subscription
// Log in audit trail
}
}
2. Role Management UI
Components:
// components/admin/RoleSelector.tsx
export function RoleSelector({ userId, currentRole, onRoleChange }) {
return (
<Select value={currentRole} onChange={onRoleChange}>
<option value="parent">Parent (Full Access)</option>
<option value="guest">Guest (Limited Access)</option>
<option value="admin">Admin (System Access)</option>
</Select>
);
}
// components/admin/FamilyRoleManager.tsx
export function FamilyRoleManager({ userId, families }) {
// Show user's role in each family
// Allow changing role per family
return (
<Table>
{families.map(family => (
<TableRow key={family.id}>
<TableCell>{family.name}</TableCell>
<TableCell>
<RoleSelector
userId={userId}
familyId={family.id}
currentRole={family.userRole}
/>
</TableCell>
</TableRow>
))}
</Table>
);
}
3. Multi-Profile Support
User Stories:
Use Case 1: Multiple Families, One Account
User: Sarah (Parent)
├── Profile: "Work Family" (childcare at work)
│ └── Family: "ABC Daycare"
│ ├── Child: Emma (not hers, she's a caregiver)
│ └── Role: Guest
└── Profile: "Home Family" (her own kids)
└── Family: "Smith Family"
├── Child: Liam
├── Child: Olivia
└── Role: Parent
Use Case 2: Multiple Accounts
User: John
├── Account 1: john@personal.com (Parent in "Doe Family")
└── Account 2: john@work.com (Guest in "Work Daycare")
Implementation:
// components/layout/ProfileSwitcher.tsx
export function ProfileSwitcher() {
const { user, profiles, currentProfile, switchProfile } = useAuth();
return (
<Menu>
<MenuButton>
<Avatar src={currentProfile.avatar} />
{currentProfile.name}
</MenuButton>
<MenuList>
{profiles.map(profile => (
<MenuItem
key={profile.id}
onClick={() => switchProfile(profile.id)}
isActive={profile.id === currentProfile.id}
>
<Avatar src={profile.avatar} size="sm" />
<Box>
<Text>{profile.name}</Text>
<Text fontSize="xs" color="gray.500">
{profile.familyName} · {profile.role}
</Text>
</Box>
</MenuItem>
))}
<MenuDivider />
<MenuItem onClick={() => router.push('/add-profile')}>
+ Add Profile
</MenuItem>
</MenuList>
</Menu>
);
}
Backend API:
// POST /api/v1/profiles
async createProfile(userId: string, dto: CreateProfileDto) {
// Create new profile
// Link to family
// Set default
}
// PATCH /api/v1/profiles/:id/switch
async switchProfile(userId: string, profileId: string) {
// Update user's active profile
// Return new auth context
}
4. Analytics Dashboard
Metrics to Track:
User Analytics
- Total users (active, inactive, trial, paid)
- New users per day/week/month
- User growth rate
- Churn rate
- Average session duration
- Daily/Monthly Active Users (DAU/MAU)
Family & Children Analytics
- Total families
- Average children per family
- Average family size (members)
- Most active families
Feature Usage
- Activities logged per day (by type)
- AI queries per day
- Voice commands per day
- Photo uploads per day
- Most used features
AI/LLM Analytics
- Total AI queries (by model)
- Average tokens per query (input/output)
- Estimated costs per model
- Average response time
- Error rate
Voice Analytics
- Total voice commands
- Success rate (transcription accuracy)
- Most common voice intents
- Average processing time
UI Components:
// app/admin/analytics/page.tsx
export default function AnalyticsDashboard() {
return (
<Grid columns={3}>
<StatCard
label="Total Users"
value={stats.totalUsers}
change={stats.userGrowth}
/>
<StatCard
label="Active Families"
value={stats.activeFamilies}
/>
<StatCard
label="AI Queries Today"
value={stats.aiQueriesToday}
cost={stats.estimatedCost}
/>
<Chart
title="User Growth"
data={stats.userGrowthData}
type="line"
/>
<Chart
title="Feature Usage"
data={stats.featureUsageData}
type="bar"
/>
<Chart
title="AI Cost Breakdown"
data={stats.aiCostData}
type="pie"
/>
</Grid>
);
}
Backend:
// src/modules/admin/analytics/analytics-admin.service.ts
@Injectable()
export class AnalyticsAdminService {
async getSystemStats() {
return {
users: await this.getUserStats(),
families: await this.getFamilyStats(),
usage: await this.getUsageStats(),
ai: await this.getAIStats(),
voice: await this.getVoiceStats(),
};
}
async getAIStats() {
// Query from ai_conversations table
// Calculate token usage
// Estimate costs based on llm_config pricing
return {
totalQueries: 15234,
totalInputTokens: 2450000,
totalOutputTokens: 1850000,
estimatedCost: 5.47, // in USD
byModel: [
{ model: 'gpt-4o-mini', queries: 12000, cost: 3.20 },
{ model: 'gpt-4', queries: 3234, cost: 2.27 },
],
};
}
}
5. System Health Monitoring
Metrics:
- Service status (backend, database, redis, mongodb)
- API response times (p50, p95, p99)
- Error rates
- Database connection pool status
- Redis cache hit/miss ratio
- Queue depths (background jobs)
- Memory/CPU usage
- Disk space
Integration with Existing Health Endpoints:
// Reuse existing health endpoints
GET /health # Overall health
GET /health/liveness # Liveness probe
GET /health/readiness # Readiness probe
// New admin-specific endpoint
GET /api/v1/admin/system/health
// Response:
{
"status": "healthy",
"uptime": 345600, // seconds
"services": {
"postgres": { "status": "up", "responseTime": "15ms" },
"redis": { "status": "up", "responseTime": "2ms" },
"mongodb": { "status": "up", "responseTime": "18ms" },
"minio": { "status": "up", "responseTime": "45ms" },
"azureOpenAI": { "status": "up", "responseTime": "120ms" }
},
"metrics": {
"apiResponseTime": { "p50": 85, "p95": 250, "p99": 450 },
"errorRate": 0.02,
"requestsPerMinute": 1250,
"cacheHitRatio": 0.85
},
"resources": {
"memoryUsage": { "used": "2.1GB", "total": "4GB", "percentage": 52.5 },
"cpuUsage": 35.2,
"diskSpace": { "used": "45GB", "total": "100GB", "percentage": 45 }
}
}
UI:
// app/admin/system/health/page.tsx
export default function SystemHealthPage() {
const { data, refetch } = useQuery('/api/v1/admin/system/health', {
refetchInterval: 10000, // Refresh every 10s
});
return (
<>
<HealthStatusBadge status={data.status} />
<ServiceStatusGrid services={data.services} />
<MetricsChart metrics={data.metrics} />
<ResourceUsageCards resources={data.resources} />
</>
);
}
6. LLM Configuration Management
UI Features:
- Add/Edit LLM endpoints
- Manage API keys (encrypted storage)
- Set model names and deployments
- Configure pricing (per 1M tokens)
- Enable/disable models
- Test connections
Backend:
// src/modules/admin/llm-config/llm-config.service.ts
@Injectable()
export class LLMConfigService {
async createConfig(dto: CreateLLMConfigDto, adminId: string) {
// Encrypt API key
const encryptedKey = await this.encryptionService.encrypt(dto.apiKey);
const config = await this.llmConfigRepository.save({
...dto,
apiKeyEncrypted: encryptedKey,
});
// Audit log
await this.auditService.log({
adminUserId: adminId,
action: 'llm_config.create',
targetId: config.id,
changes: { provider: dto.provider, serviceType: dto.serviceType },
});
return config;
}
async testConnection(configId: string) {
const config = await this.llmConfigRepository.findOne(configId);
const apiKey = await this.encryptionService.decrypt(config.apiKeyEncrypted);
try {
// Test API call
const response = await axios.post(config.endpointUrl, {
messages: [{ role: 'user', content: 'Test' }],
max_tokens: 10,
}, {
headers: { 'api-key': apiKey },
});
return { success: true, latency: response.duration };
} catch (error) {
return { success: false, error: error.message };
}
}
async getEstimatedCosts() {
// Calculate monthly costs based on usage
const usage = await this.getMonthlyTokenUsage();
return usage.map(model => ({
modelName: model.name,
inputTokens: model.inputTokens,
outputTokens: model.outputTokens,
estimatedCost: (
(model.inputTokens / 1000000) * model.pricePerMInput +
(model.outputTokens / 1000000) * model.pricePerMOutput
),
}));
}
}
7. Content Management (Legal Pages)
Features:
- Create/Edit legal pages (Privacy Policy, Terms, EULA, Cookies)
- Markdown editor with preview
- Multi-language support
- Version history
- Publish/Unpublish
- SEO metadata
UI:
// app/admin/pages/[slug]/edit/page.tsx
export default function EditLegalPage({ params }) {
const [content, setContent] = useState('');
const [language, setLanguage] = useState('en');
return (
<Form>
<Input label="Title" />
<Select label="Language" value={language} onChange={setLanguage}>
<option value="en">English</option>
<option value="es">Spanish</option>
<option value="fr">French</option>
</Select>
<MarkdownEditor
value={content}
onChange={setContent}
height="600px"
/>
<MarkdownPreview content={content} />
<ButtonGroup>
<Button variant="outline">Save Draft</Button>
<Button>Publish</Button>
</ButtonGroup>
</Form>
);
}
8. Subscription Management
Features:
- Create subscription plans
- Set pricing (monthly/yearly)
- Define feature limits
- Trial period configuration
- Apply to users
- Discount codes
Data Model:
interface SubscriptionPlan {
id: string;
name: string; // 'Free', 'Pro', 'Family'
description: string;
priceMonthly: number;
priceYearly: number;
features: {
aiQueries: 'unlimited' | number;
voiceCommands: 'unlimited' | number;
maxChildren: number;
maxFamilyMembers: number;
analytics: boolean;
prioritySupport: boolean;
};
trialPeriodDays: number;
}
UI:
// app/admin/subscriptions/page.tsx
export default function SubscriptionPlansPage() {
return (
<>
<PlanList plans={plans} />
<Button onClick={openCreateDialog}>Create Plan</Button>
<CreatePlanDialog>
<Input label="Plan Name" />
<Input label="Monthly Price" type="number" prefix="$" />
<Input label="Yearly Price" type="number" prefix="$" />
<Input label="Trial Days" type="number" />
<FeatureLimits>
<Input label="Max Children" />
<Input label="AI Queries/Month" />
<Toggle label="Analytics Access" />
</FeatureLimits>
</CreatePlanDialog>
</>
);
}
9. Email Configuration
UI:
// app/admin/settings/email/page.tsx
export default function EmailSettingsPage() {
return (
<Form>
<Select label="Region">
<option value="US">US (api.mailgun.net)</option>
<option value="EU">EU (api.eu.mailgun.net)</option>
</Select>
<Input
label="API Key"
type="password"
placeholder="key-xxxxx"
/>
<Input label="Domain" placeholder="mg.maternal.com" />
<Input label="Sender Email" placeholder="noreply@maternal.com" />
<Input label="Sender Name" placeholder="Maternal App" />
<Button onClick={testEmail}>Send Test Email</Button>
<Button>Save Configuration</Button>
</Form>
);
}
Backend:
// src/modules/admin/email-config/email-config.service.ts
@Injectable()
export class EmailConfigService {
async updateConfig(dto: UpdateEmailConfigDto, adminId: string) {
const encryptedKey = await this.encryptionService.encrypt(dto.apiKey);
const config = await this.emailConfigRepository.save({
...dto,
apiKeyEncrypted: encryptedKey,
});
// Update EmailService to use new config
await this.emailService.reloadConfig();
// Audit log
await this.auditService.log({
adminUserId: adminId,
action: 'email_config.update',
changes: { region: dto.region, domain: dto.domain },
});
return config;
}
async sendTestEmail(recipientEmail: string) {
try {
await this.emailService.sendEmail({
to: recipientEmail,
subject: 'Test Email from Maternal App Admin',
text: 'If you received this, email configuration is working!',
});
return { success: true };
} catch (error) {
return { success: false, error: error.message };
}
}
}
🚀 Deployment Strategy
Admin Service Deployment
The admin service will be deployed as a separate microservice:
Infrastructure
# docker-compose.admin.yml
version: '3.8'
services:
admin-api:
build: ./parentflow-admin-service
container_name: parentflow-admin-api
ports:
- "4000:4000" # Admin API on separate port
environment:
NODE_ENV: production
DATABASE_URL: postgresql://...
JWT_SECRET: ${ADMIN_JWT_SECRET}
SERVICE_AUTH_KEY: ${SERVICE_AUTH_KEY} # For service-to-service auth
networks:
- parentflow-network
admin-ui:
build: ./parentflow-admin-service/admin-ui
container_name: parentflow-admin-ui
ports:
- "4001:3000" # Admin UI
environment:
REACT_APP_API_URL: http://admin-api:4000
networks:
- parentflow-network
networks:
parentflow-network:
external: true
Service Communication
// Service-to-service authentication
export class AdminServiceClient {
private readonly serviceKey: string;
private readonly adminApiUrl: string;
constructor() {
this.serviceKey = process.env.SERVICE_AUTH_KEY;
this.adminApiUrl = process.env.ADMIN_API_URL || 'http://localhost:4000';
}
async validateInviteCode(code: string, platform: string): Promise<ValidationResult> {
const response = await axios.post(
`${this.adminApiUrl}/api/v1/internal/validate-invite-code`,
{ code, platform },
{
headers: {
'X-Service-Auth': this.serviceKey,
'X-Service-Name': 'parentflow-web'
}
}
);
return response.data;
}
async getRegistrationConfig(): Promise<RegistrationConfig> {
// Cache this for 5 minutes
return this.cache.get('registration-config', async () => {
const response = await axios.get(
`${this.adminApiUrl}/api/v1/internal/registration-config`,
{
headers: {
'X-Service-Auth': this.serviceKey,
'X-Service-Name': 'parentflow-web'
}
}
);
return response.data;
}, 300); // 5 minutes TTL
}
}
Nginx Configuration
# admin.parentflowapp.com
server {
listen 443 ssl http2;
server_name admin.parentflowapp.com;
# SSL configuration
ssl_certificate /etc/letsencrypt/live/admin.parentflowapp.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/admin.parentflowapp.com/privkey.pem;
# IP whitelist for admin access (optional)
allow 10.0.0.0/24; # Office network
allow 192.168.1.0/24; # VPN
deny all;
# Admin UI
location / {
proxy_pass http://localhost:4001;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
}
# Admin API
location /api/ {
proxy_pass http://localhost:4000;
proxy_http_version 1.1;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
Deployment Servers
Production Environment:
├── Admin Service
│ ├── Server: 10.0.0.241 (separate from main app)
│ ├── URL: admin.parentflowapp.com
│ ├── Ports: 4000 (API), 4001 (UI)
│ └── Database: Shared with main app (read/write to admin tables)
│
├── Main Applications
│ ├── Web: 10.0.0.240:3030 → web.parentflowapp.com
│ ├── API: 10.0.0.240:3020 → api.parentflowapp.com
│ └── Mobile APIs: Will connect to api.parentflowapp.com
│
└── Shared Infrastructure
├── PostgreSQL: 10.0.0.240:5432
├── Redis: 10.0.0.240:6379
└── MongoDB: 10.0.0.240:27017
🚀 Implementation Timeline
Phase 1: Foundation (Week 1-2)
Week 1: Database & Backend Core
- Create database migrations for new tables
- Implement role enums and guards
- Create AdminGuard and FamilyRoleGuard
- Set up admin API module structure
- Implement audit logging service
Week 2: User Management
- User management endpoints (CRUD)
- Anonymization logic
- Data export functionality
- Subscription management endpoints
- Unit tests for user management
Phase 2: Admin UI (Week 3-4)
Week 3: Admin Dashboard Layout
- Admin layout component
- Navigation sidebar
- User management UI (list, detail, edit)
- Role management UI
- Multi-profile UI components
Week 4: Analytics & Monitoring
- Analytics dashboard UI
- System health monitoring UI
- Charts and visualizations
- Real-time metric updates
Phase 3: Configuration (Week 5-6)
Week 5: LLM & Email Config
- LLM configuration UI
- API key management (encrypted)
- Connection testing
- Email settings UI
- Test email functionality
Week 6: Content & Subscriptions
- Legal pages CMS
- Markdown editor integration
- Subscription plan management UI
- Trial period configuration
- Discount codes
Phase 4: Security & Testing (Week 7-8)
Week 7: Security Hardening
- 2FA for admin accounts
- Admin session timeout (15 min)
- IP whitelisting option
- Audit log viewer UI
- Security testing
Week 8: Final Testing & Documentation
- Integration tests for all admin endpoints
- E2E tests for critical admin flows
- Performance testing
- Admin user documentation
- Developer API documentation
✅ Acceptance Criteria
Security
- Only users with
isAdmin=truecan access admin dashboard - All admin actions logged to audit trail
- API keys encrypted at rest (AES-256)
- Admin sessions expire after 15 minutes of inactivity
- 2FA required for admin accounts
User Management
- Admin can create/edit/delete users
- Admin can anonymize user data (GDPR)
- Admin can export user data in JSON/CSV
- Admin can change user subscriptions
- Admin can verify user emails manually
Role Management
- Parents have full access to their families
- Guests can only add activities, no historical data
- Admins have system-wide access
- Family roles can be changed per user per family
Multi-Profile
- Users can switch between multiple families
- Users can manage multiple accounts
- Profile switcher in user menu
- Default profile saved per user
Analytics
- Admin can view user growth metrics
- Admin can view feature usage stats
- Admin can view AI/LLM costs and usage
- Admin can view voice command analytics
- Real-time metrics with 10s refresh
System Health
- Admin can view service status
- Admin can view API performance metrics
- Admin can view resource usage
- Alerts for service degradation
Configuration
- Admin can add/edit LLM models
- Admin can test LLM connections
- Admin can view estimated AI costs
- Admin can update email settings
- Admin can edit legal pages
- Admin can create subscription plans
📚 Additional Features (Future Enhancements)
Advanced Analytics
- Custom report builder
- Data export scheduler
- Cohort analysis
- Funnel visualization
- A/B testing dashboard
Automation
- Automated user onboarding emails
- Trial expiration reminders
- Inactive user re-engagement
- Subscription renewal reminders
Advanced Security
- IP whitelisting for admin access
- Admin activity anomaly detection
- Automated threat response
- SIEM integration
Support Tools
- In-app user support chat
- Impersonate user (for debugging)
- Feature flag management
- Rollback deployments
🔗 Related Documentation
- RBAC Implementation Guide (to be created)
- Audit Logging Standards (to be created)
- Data Anonymization Procedures (to be created)
- API Gateway Architecture
Last Updated: October 3, 2025
Next Review: After Phase 1 completion
Owner: Backend Team + Admin Team