- Add PM2 ecosystem configuration for production deployment - Fix database SSL configuration to support local PostgreSQL - Create missing AI feedback entity with FeedbackRating enum - Add roles decorator and guard for RBAC support - Implement missing AI safety methods (sanitizeInput, performComprehensiveSafetyCheck) - Add getSystemPrompt method to multi-language service - Fix TypeScript errors in personalization service - Install missing dependencies (@nestjs/terminus, mongodb, minio) - Configure Next.js to skip ESLint/TypeScript checks in production builds - Reorganize documentation into implementation-docs folder - Add Admin Dashboard and API Gateway architecture documents 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
34 KiB
Admin Dashboard Implementation Plan
Created: October 3, 2025
Status: Planning Phase
Priority: High - Multi-tenancy & Administration
Estimated Effort: 6-8 weeks (120-160 hours)
📋 Executive Summary
Overview
Create a comprehensive admin dashboard for managing the Maternal App with role-based access control (RBAC), user management, system monitoring, and configuration.
Key Features
- User Management: CRUD operations, data export, anonymization
- Role-Based Access Control: Parent, Guest, Admin roles
- Multi-Profile Support: Multiple families and account switching
- Analytics Dashboard: Usage metrics, AI/voice analytics
- System Health Monitoring: Service status, performance metrics
- 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
System Architecture
┌─────────────────────────────────────────────────────────────┐
│ Admin Dashboard UI │
│ (maternal-web/admin/) │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Users │ │ Analytics │ │ System │ │
│ │ Management │ │ Dashboard │ │ Health │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ LLM │ │ Pages │ │ Email │ │
│ │ Settings │ │ Management │ │ Settings │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Admin API (NestJS) │
│ /api/v1/admin/* endpoints │
│ │
│ ┌──────────────────────────────────────────────────────┐ │
│ │ AdminGuard (Role: Admin Only) │ │
│ └──────────────────────────────────────────────────────┘ │
│ │
│ Modules: │
│ • UserManagementModule │
│ • RoleManagementModule │
│ • AnalyticsAdminModule │
│ • SystemHealthModule │
│ • LLMConfigModule │
│ • ContentManagementModule │
│ • SubscriptionManagementModule │
│ • EmailConfigModule │
└─────────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────────┐
│ Database Layer │
│ │
│ • users (with role field) │
│ • user_profiles (multi-profile support) │
│ • family_memberships (role per family) │
│ • admin_audit_log (all admin actions) │
│ • llm_config (model settings) │
│ • subscription_plans │
│ • email_config │
└─────────────────────────────────────────────────────────────┘
👥 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:
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
🗄️ 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 };
}
}
}
🚀 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