Admin vs Customer — Guia de Separação para Frontend¶
A FDPlay API serve duas aplicações completamente diferentes para dois tipos de usuário:
| Painel Administrativo | App de Streaming | |
|---|---|---|
| Quem usa | Admin (user_type: "admin") |
Customer (user_type: "customer") |
| Propósito | Gerenciar a plataforma | Consumir vídeos |
| Precisa de assinatura? | Nunca | Sim (para acessar vídeos) |
| Como é criado | Por outro admin (POST /users) |
Self-service (POST /customers/signup) |
| Endpoint de perfil | GET /my-user |
GET /customers/me |
| Listagem (admin vê todos) | GET /users (só admins) |
GET /customers (admin lista customers) |
| Acesso a vídeos | Total (bypass de assinatura) | Somente com subscription status: "active" |
| Login | POST /token (mesmo endpoint) |
POST /token (mesmo endpoint) |
Regra fundamental
O frontend deve tratar Admin e Customer como dois produtos distintos que compartilham apenas o login (POST /token). Após o login, o campo user_type da resposta de GET /current-user determina qual interface carregar.
Identificando o Tipo de Usuário¶
Após login, consulte GET /api/v1/current-user. O campo user_type é o discriminador:
import 'dart:convert';
import 'package:http/http.dart' as http;
Future<void> routeByUserType(String token) async {
final response = await http.get(
Uri.parse('$baseUrl/current-user'),
headers: {'Authorization': 'Bearer $token'},
);
final user = jsonDecode(response.body);
if (user['user_type'] == 'admin') {
// → Carregar Painel Administrativo
Navigator.pushReplacementNamed(context, '/admin');
} else if (user['user_type'] == 'customer') {
// → Carregar App de Streaming
Navigator.pushReplacementNamed(context, '/videos');
}
}
Segurança
O roteamento no frontend é apenas UX. O backend valida user_type em toda requisição. Customer chamando rota admin recebe 403 ADMIN_REQUIRED. Customer sem assinatura chamando /videos recebe 403 SUBSCRIPTION_REQUIRED.
Parte 1 — App de Streaming (Customer)¶
O Customer é o assinante/consumidor. Ele se cadastra, assina um plano e assiste vídeos.
Jornada do Customer¶
graph TD
A[Acessa o site] --> B{Tem conta?}
B -->|Não| C["Signup (2 passos com verificação de email)"]
C --> D[Recebe JWT]
B -->|Sim| E[Login]
E --> F{password_reset_required?}
F -->|Sim| F1{Sabe a senha atual?}
F1 -->|Sim| F2["POST /auth/change-password"]
F1 -->|Não — Legado| F3["POST /auth/forgot-password → código por email"]
F3 --> F4["POST /auth/reset-password → nova senha"]
F2 --> D
F4 --> D
F -->|Não| D
D --> G{Tem assinatura ativa?}
G -->|Não| H[Tela de planos → Assinar]
H --> G
G -->|Sim| I[Catálogo de vídeos]
I --> J[Assistir vídeo]
I --> K[Gerenciar conta]
Endpoints do Customer¶
Onboarding (Público — sem JWT)¶
| Endpoint | Método | O que faz |
|---|---|---|
/customers/signup |
POST | Cadastro em 2 passos (dados → código de email → JWT) |
/customers/check-availability |
GET | Verificar se email/CPF já está em uso |
/plans |
GET | Listar planos disponíveis com preços |
/auth/forgot-password |
POST | Solicitar reset de senha |
/auth/reset-password |
POST | Resetar senha com token do email |
/auth/resend-verification |
POST | Reenviar código de verificação de email |
/token |
POST | Login (retorna JWT) |
Área Logada (JWT obrigatório)¶
| Endpoint | Método | O que faz |
|---|---|---|
/customers/me |
GET | Ver dados do perfil |
/customers/me |
PUT | Atualizar dados pessoais (email bloqueado — usar fluxo abaixo) |
/customers/me |
DELETE | Deletar conta (com cascade de assinatura) |
/customers/me/change-email |
POST | Solicitar troca de email (envia código ao novo email) |
/customers/me/confirm-email |
POST | Confirmar troca de email (valida código) |
/auth/change-password |
POST | Alterar senha |
/current-user |
GET | Dados do usuário autenticado (retorna user_type — usar para rotear) |
/my-user |
GET | Dados do próprio perfil (alternativo) |
/my-user |
PUT | Atualizar perfil (alternativo) |
Assinatura (JWT obrigatório)¶
| Endpoint | Método | O que faz |
|---|---|---|
/customers/me/subscribe |
POST | Criar assinatura (cartão, boleto ou PIX anual) |
/customers/me/subscription |
GET | Ver status da assinatura atual |
/customers/me/subscription/renew |
PUT | Trocar método de pagamento (cancela antiga → cria nova) |
/customers/me/subscription |
DELETE | Cancelar assinatura |
Consumo de Conteúdo (JWT + assinatura ativa)¶
| Endpoint | Método | O que faz |
|---|---|---|
/videos |
GET | Listar vídeos (filtrado por tier do plano) |
/categories |
GET | Listar categorias |
/tags |
GET | Listar tags |
Vídeos exigem assinatura ativa
GET /videos valida current_subscription_id → subscription com status: "active". Sem assinatura, retorna 403 com código específico que indica a ação necessária (ver seção de erros).
Campos do Customer¶
{
"_id": "507f1f77bcf86cd799439020",
"username": "maria_silva",
"email": "maria@email.com",
"user_type": "customer",
"full_name": "Maria da Silva",
"tax_id": "12345678900",
"phone": "+5511999887766",
"address": {
"street": "Av. Paulista",
"number": "1578",
"complement": "Apto 22",
"locality": "Bela Vista",
"city": "São Paulo",
"region_code": "SP",
"country": "BRA",
"postal_code": "01310200"
},
"avatar_id": null,
"email_verified": true,
"current_subscription_id": "507f1f77bcf86cd799439030",
"created_at": "2026-01-10T14:00:00Z",
"updated_at": "2026-01-20T08:15:00Z"
}
| Campo | Tipo | Descrição |
|---|---|---|
full_name |
string | Nome completo (billing) |
tax_id |
string | null | CPF/CNPJ |
phone |
string | null | Telefone com código do país |
address |
object | null | Endereço de cobrança |
avatar_id |
string | null | ID do avatar (GridFS) |
email_verified |
boolean | Email verificado via código |
current_subscription_id |
string | null | FK para subscription ativa |
password_reset_required |
boolean | Se true, forçar troca de senha no login (ver nota abaixo) |
legacy_id |
string | null | UUID do sistema legado (migração) |
Usuários legados NÃO sabem a senha atual
Usuários migrados do sistema SQL anterior têm password_reset_required: true e senha placeholder (hash de string interna). Eles não conseguem usar POST /auth/change-password diretamente (exige senha atual). O frontend deve oferecer dois caminhos:
- Se sabe a senha atual (raro — apenas se definiu após migração):
POST /auth/change-password - Se NÃO sabe a senha (caso padrão para legados): Redirecionar para
POST /auth/forgot-password→ código por email →POST /auth/reset-password
Ambos os fluxos limpam a flag password_reset_required automaticamente.
Campos mais relevantes para o frontend
current_subscription_id: Senull→ customer não tem assinatura (mostrar CTA de assinar)email_verified: Sefalse→ signup incompleto (pedir verificação de email)password_reset_required: Setrue→ redirecionar para troca de senha obrigatória após loginaddress: Pré-preencher no formulário de assinatura se já cadastrado
Erros Específicos do Customer¶
Estes erros indicam ações concretas que o frontend deve executar:
| Código | HTTP | Ação do frontend |
|---|---|---|
PASSWORD_RESET_REQUIRED |
401 | Redirecionar para forgot-password (usuário legado sem senha) |
SUBSCRIPTION_REQUIRED |
403 | Redirecionar para página de planos |
SUBSCRIPTION_INACTIVE |
403 | Redirecionar para renovação de assinatura |
SUBSCRIPTION_EXPIRED |
403 | Redirecionar para página de planos (PIX vencido) |
PAYMENT_FAILURES_EXCEEDED |
403 | Redirecionar para troca de método de pagamento |
PLAN_TIER_INSUFFICIENT |
403 | Mostrar modal de upgrade de plano |
EMAIL_NOT_VERIFIED |
403 | Redirecionar para tela de verificação de email |
VERIFICATION_CODE_EXPIRED |
400 | Mostrar botão "Reenviar código" |
VERIFICATION_MAX_ATTEMPTS |
400 | Reenviar código automaticamente |
VERIFICATION_COOLDOWN |
429 | Mostrar timer de espera (60 segundos) |
Parte 2 — Painel Administrativo (Admin)¶
O Admin gerencia a plataforma: cria vídeos, planos, monitora assinaturas, processa reembolsos.
Jornada do Admin¶
graph TD
A[Login] --> B[Dashboard]
B --> C[Gestão de Vídeos]
C --> C1[CRUD vídeos + upload thumbnails]
C --> C2[Definir tier de acesso por vídeo]
B --> D[Gestão de Planos]
D --> D1[CRUD planos + sync Stripe/Asaas]
D --> D2[Ativar/Desativar planos]
B --> E[Gestão de Assinaturas]
E --> E1[Listar + estatísticas]
E --> E2[Cancelar / Reembolsar / Chargeback]
B --> F[Gestão de Customers]
F --> F1[Listar + editar dados]
B --> G[Conteúdo]
G --> G1[Categorias / Tags / Séries / Grupos]
B --> H[Auditoria]
H --> H1[Webhook logs + estatísticas]
Endpoints do Admin¶
Gestão de Vídeos¶
| Endpoint | Método | O que faz |
|---|---|---|
/videos |
GET | Listar todos (sem validação de assinatura — bypass) |
/videos |
POST | Criar vídeo |
/videos |
PUT | Atualizar vídeo |
/videos |
DELETE | Deletar vídeo |
Gestão de Conteúdo¶
| Endpoint | Método | O que faz |
|---|---|---|
/categories |
GET/POST/PUT/DELETE | CRUD de categorias |
/tags |
GET/POST/PUT/DELETE | CRUD de tags |
/groups |
GET/POST/PUT/DELETE | CRUD de grupos |
/seasons |
POST/PUT/DELETE | Mutações admin (GET aberto a customer com assinatura ativa) |
Gestão de Planos¶
| Endpoint | Método | O que faz |
|---|---|---|
/plans/admin |
GET | Listar todos (ativos e inativos) |
/plans |
POST | Criar plano |
/plans |
PUT | Atualizar plano |
/plans/{id}/activate |
POST | Ativar plano |
/plans/{id}/inactivate |
POST | Desativar plano |
/plans/{id}/sync-stripe |
POST | Sincronizar plano existente com Stripe |
Gestão de Usuários¶
| Endpoint | Método | O que faz |
|---|---|---|
/users |
GET | Listar admins |
/users |
POST | Criar novo admin |
/users |
PUT | Atualizar admin |
/users |
DELETE | Deletar admin |
/customers |
GET | Listar todos os customers |
/customers |
PUT | Atualizar customer (admin pode alterar email diretamente) |
/customers |
DELETE | Deletar customer (cascade de assinatura) |
Gestão de Assinaturas¶
| Endpoint | Método | O que faz |
|---|---|---|
/subscriptions |
GET | Listar todas as assinaturas |
/subscriptions/stats |
GET | Estatísticas básicas legado (MRR, status counts) |
/admin/subscriptions/dashboard |
GET | Visão 360 (totals, by_gateway, by_payment_method, alerts) |
/admin/subscriptions/insights |
GET | Análise interpretada com labels, severities, oportunidades |
/subscriptions |
PUT | Atualizar assinatura |
/subscriptions/{id}/cancel |
POST | Cancelar assinatura |
/subscriptions/{id}/refund |
POST | Processar reembolso (PIX ou recorrente) |
/subscriptions/{id}/refunds |
GET | Histórico de reembolsos e chargebacks |
/subscriptions/{id}/chargeback |
POST | Gerenciar chargeback (suspend/resolve) |
/subscriptions/{id}/payments |
GET | Histórico de pagamentos |
Quando usar cada um: -
dashboard→ tabelas, gráficos, drill-down de números brutos. -insights→ cards de saúde, alertas priorizados, recomendações com impacto $. -stats(legado) → mantido para retrocompat; novos consumos devem usar dashboard/insights.
Gestão de Clientes¶
| Endpoint | Método | O que faz |
|---|---|---|
/admin/customers/{id}/verify-email |
POST | Verificar email manualmente |
/admin/customers/{id}/toggle-active |
POST | Suspender/reativar conta |
/admin/subscriptions/{id}/extend |
POST | Estender prazo de assinatura |
/admin/subscriptions/{customer_id}/activate |
POST | Ativar assinatura manualmente (cortesia) |
/admin/customers/export |
GET | Exportar clientes segmentados (CSV/XLSX/JSON) |
Logs e Auditoria¶
| Endpoint | Método | O que faz |
|---|---|---|
/webhook-logs |
GET | Logs de webhooks |
/webhook-logs/stats |
GET | Estatísticas de webhooks |
Outras Rotas Admin¶
| Endpoint | Método | O que faz |
|---|---|---|
/archive-records |
GET/POST/PUT/DELETE | CRUD de registros de arquivo |
/upload-file/archive-records/{id} |
POST | Upload de arquivo (GridFS) |
/upload-file/archive-records/{id}/{file_id} |
PUT/DELETE | Atualizar/deletar arquivo |
/archive-records/{file_id} |
GET | Download de arquivo |
/status |
GET/POST/PUT/DELETE | CRUD de status de workflow |
/flows |
GET/POST/PUT/DELETE | CRUD de fluxos |
/types-of-registers |
GET/POST/PUT/DELETE | CRUD de tipos de registro |
/users-type |
GET/POST/PUT/DELETE | CRUD de tipos/perfis de usuário |
Campos do Admin¶
{
"_id": "507f1f77bcf86cd799439011",
"username": "admin_joao",
"email": "joao@fdplay.com",
"user_type": "admin",
"name": "João Administrador",
"avatar_id": "507f1f77bcf86cd799439012",
"created_at": "2026-01-01T00:00:00Z",
"updated_at": "2026-01-15T10:30:00Z"
}
| Campo | Tipo | Descrição |
|---|---|---|
name |
string | Nome de exibição no painel (Customer usa full_name) |
avatar_id |
string | null | ID do avatar (GridFS) |
Admin não possui campos de billing
Campos como tax_id, phone, address, current_subscription_id existem apenas no Customer. O Admin não participa do fluxo de pagamento.
Erros Específicos do Admin¶
| Código | HTTP | Quando |
|---|---|---|
ADMIN_REQUIRED |
403 | Customer tentou acessar rota admin |
REFUND_FAILED |
502 | Falha na comunicação com o gateway de pagamento ao reembolsar |
REFUND_NOT_ALLOWED |
422 | Status da subscription não permite reembolso |
CHARGEBACK_DETECTED |
409 | Chargeback ativo na assinatura |
Parte 3 — Roteamento no Frontend¶
Estrutura de Rotas Sugerida¶
PÚBLICO (sem auth)
├── / Landing page
├── /plans Página de planos
├── /signup Cadastro de customer
├── /login Login (admin ou customer)
├── /forgot-password Recuperar senha
└── /reset-password Resetar com token
APP DE STREAMING (customer)
├── /videos Catálogo de vídeos (requer assinatura)
├── /videos/:id Player
├── /account Perfil
├── /account/subscription Gerenciar assinatura
├── /account/change-email Alterar email (2 passos)
├── /account/change-password Alterar senha
└── /change-password Troca obrigatória (usuários legados)
PAINEL ADMIN
├── /admin Dashboard
├── /admin/videos CRUD de vídeos
├── /admin/plans CRUD de planos
├── /admin/customers Lista de customers
├── /admin/subscriptions Gestão de assinaturas
├── /admin/users CRUD de admins
├── /admin/categories CRUD de categorias
├── /admin/tags CRUD de tags
├── /admin/seasons CRUD de temporadas
└── /admin/webhooks Logs de webhooks
Guard de Rota¶
/// Middleware de rota — valida acesso antes de navegar.
/// Retorna a rota de destino (pode redirecionar).
String? canActivateRoute(String route, Map<String, dynamic>? user) {
// Públicas — sempre OK
const publicRoutes = ['/', '/plans', '/signup', '/login', '/forgot-password', '/reset-password'];
if (publicRoutes.contains(route)) return null; // permitido
// Sem auth → login
if (user == null) return '/login';
// /admin/* → somente admin
if (route.startsWith('/admin')) {
if (user['user_type'] != 'admin') return '/videos';
return null; // permitido
}
// /videos, /account → somente customer
if (route.startsWith('/videos') || route.startsWith('/account')) {
if (user['user_type'] == 'admin') return '/admin';
return null; // permitido
}
return null; // permitido
}
// Uso no MaterialApp (GoRouter, auto_route, ou Navigator)
void navigateTo(BuildContext context, String route, Map<String, dynamic>? user) {
final redirect = canActivateRoute(route, user);
final target = redirect ?? route;
Navigator.pushNamed(context, target);
}
Interceptor de Erros 403¶
import 'dart:convert';
import 'package:http/http.dart' as http;
/// Interceptor global para respostas 403.
/// Chamar após toda requisição autenticada.
void handleForbidden(BuildContext context, http.Response response) {
if (response.statusCode != 403) return;
final body = jsonDecode(response.body);
final code = body['error']?['code'] as String?;
switch (code) {
// Customer sem acesso a vídeos
case 'SUBSCRIPTION_REQUIRED':
case 'SUBSCRIPTION_INACTIVE':
case 'SUBSCRIPTION_EXPIRED':
Navigator.pushReplacementNamed(context, '/plans');
_showSnackBar(context, 'Assine um plano para acessar os vídeos.');
break;
// Pagamento com problema
case 'PAYMENT_FAILURES_EXCEEDED':
Navigator.pushReplacementNamed(context, '/account/subscription');
_showSnackBar(context, 'Atualize seu método de pagamento.');
break;
// Plano insuficiente
case 'PLAN_TIER_INSUFFICIENT':
_showUpgradeDialog(context);
break;
// Customer tentou acessar admin
case 'ADMIN_REQUIRED':
Navigator.pushReplacementNamed(context, '/videos');
break;
default:
_showSnackBar(context, 'Acesso negado.');
}
}
void _showSnackBar(BuildContext context, String message) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(message)),
);
}
void _showUpgradeDialog(BuildContext context) {
showDialog(
context: context,
builder: (_) => AlertDialog(
title: const Text('Upgrade necessário'),
content: const Text('Faça upgrade do seu plano para acessar este conteúdo.'),
actions: [
TextButton(
onPressed: () => Navigator.pushNamed(context, '/plans'),
child: const Text('Ver planos'),
),
],
),
);
}
Erros Comuns (Ambos os Perfis)¶
| Código | HTTP | Quando |
|---|---|---|
UNAUTHORIZED |
401 | Token ausente ou inválido |
INVALID_CREDENTIALS |
401 | Login com senha incorreta |
TOKEN_EXPIRED |
401 | Token JWT expirado |
FORBIDDEN |
403 | Sem permissão genérica |
NOT_FOUND |
404 | Recurso não encontrado |
CONFLICT |
409 | Conflito (ex: email já cadastrado) |
DUPLICATE_KEY |
422 | Chave duplicada (username, email) |
VALIDATION_ERROR |
422 | Dados inválidos no payload |
Resumo Visual¶
┌──────────────────────────────────────────────────────────────┐
│ FDPlay API │
├─────────────────────────────┬────────────────────────────────┤
│ PAINEL ADMIN (Gestão) │ APP STREAMING (Consumo) │
│ user_type: "admin" │ user_type: "customer" │
├─────────────────────────────┼────────────────────────────────┤
│ │ │
│ CRUD /videos │ GET /videos (requer assinatura)│
│ CRUD /categories │ GET /categories │
│ CRUD /tags │ GET /tags │
│ CRUD /groups │ GET /seasons (requer assinatura)│
│ CRUD /seasons │ POST /customers/me/subscribe │
│ CRUD /plans │ GET /customers/me/subscription│
│ POST /plans/{id}/sync-stripe│ PUT /.../subscription/renew │
│ │ DELETE /.../subscription │
│ CRUD /users (admins) │ │
│ GET/PUT/DELETE /customers │ GET/PUT/DELETE /customers/me │
│ │ POST /.../change-email │
│ GET /subscriptions │ POST /.../confirm-email │
│ GET /subscriptions/stats │ │
│ POST /.../cancel │ POST /auth/change-password │
│ POST /.../refund │ GET /current-user │
│ POST /.../chargeback │ GET/PUT /my-user │
│ │ │
│ GET /webhook-logs │ │
│ GET /webhook-logs/stats │ │
│ │ │
│ CRUD /archive-records │ │
│ CRUD /status │ │
│ CRUD /flows │ │
│ CRUD /types-of-registers │ │
│ CRUD /users-type │ │
├─────────────────────────────┴────────────────────────────────┤
│ PÚBLICO (Sem auth) │
├──────────────────────────────────────────────────────────────┤
│ POST /token GET /plans POST /auth/* │
│ POST /customers/signup POST /customers/check-availability │
└──────────────────────────────────────────────────────────────┘
Referências¶
- Guia do Frontend — Fluxo completo com código Dart/Flutter
- Flutter Quick Start — Integração em 5 minutos
- Autenticação — JWT, login, permissões
- Customers API — Signup, perfil, assinatura
- Admin Dashboard — Gestão de assinaturas e logs
- Videos API — CRUD e controle de acesso
- Plans API — Planos públicos e admin