develop #83
@ -940,4 +940,251 @@ security:
|
|||||||
- bearer: []
|
- bearer: []
|
||||||
|
|
||||||
```
|
```
|
||||||
|
# Fazer login e obter token JWT
|
||||||
|
|
||||||
|
## OpenAPI Specification
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
openapi: 3.0.1
|
||||||
|
info:
|
||||||
|
title: ''
|
||||||
|
description: ''
|
||||||
|
version: 1.0.0
|
||||||
|
paths:
|
||||||
|
/auth/v1/token:
|
||||||
|
post:
|
||||||
|
summary: Fazer login e obter token JWT
|
||||||
|
deprecated: false
|
||||||
|
description: >-
|
||||||
|
Autentica o usuário e retorna um token JWT para usar em outras
|
||||||
|
requisições.
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
parameters:
|
||||||
|
- name: grant_type
|
||||||
|
in: query
|
||||||
|
description: ''
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- password
|
||||||
|
requestBody:
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginRequest'
|
||||||
|
examples: {}
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Login realizado com sucesso
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/LoginResponse'
|
||||||
|
headers: {}
|
||||||
|
x-apidog-name: OK
|
||||||
|
'400':
|
||||||
|
description: Credenciais inválidas
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: &ref_0
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
headers: {}
|
||||||
|
x-apidog-name: Bad Request
|
||||||
|
'401':
|
||||||
|
description: Email ou senha incorretos
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema: *ref_0
|
||||||
|
headers: {}
|
||||||
|
x-apidog-name: Unauthorized
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
x-apidog-folder: Authentication
|
||||||
|
x-apidog-status: released
|
||||||
|
x-run-in-apidog: https://app.apidog.com/web/project/1053378/apis/api-21940510-run
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
LoginRequest:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- email
|
||||||
|
- password
|
||||||
|
properties:
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
examples:
|
||||||
|
- usuario@exemplo.com
|
||||||
|
password:
|
||||||
|
type: string
|
||||||
|
minLength: 6
|
||||||
|
examples:
|
||||||
|
- senha123
|
||||||
|
x-apidog-orders:
|
||||||
|
- email
|
||||||
|
- password
|
||||||
|
x-apidog-ignore-properties: []
|
||||||
|
x-apidog-folder: ''
|
||||||
|
LoginResponse:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
access_token:
|
||||||
|
type: string
|
||||||
|
description: Token JWT para autenticação
|
||||||
|
examples:
|
||||||
|
- eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||||
|
token_type:
|
||||||
|
type: string
|
||||||
|
examples:
|
||||||
|
- bearer
|
||||||
|
expires_in:
|
||||||
|
type: integer
|
||||||
|
description: Tempo de expiração do token em segundos
|
||||||
|
examples:
|
||||||
|
- 3600
|
||||||
|
refresh_token:
|
||||||
|
type: string
|
||||||
|
description: Token para renovar o access_token
|
||||||
|
user:
|
||||||
|
$ref: '#/components/schemas/AuthUser'
|
||||||
|
x-apidog-orders:
|
||||||
|
- access_token
|
||||||
|
- token_type
|
||||||
|
- expires_in
|
||||||
|
- refresh_token
|
||||||
|
- user
|
||||||
|
x-apidog-ignore-properties: []
|
||||||
|
x-apidog-folder: ''
|
||||||
|
AuthUser:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
id:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
email:
|
||||||
|
type: string
|
||||||
|
format: email
|
||||||
|
email_confirmed_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
nullable: true
|
||||||
|
created_at:
|
||||||
|
type: string
|
||||||
|
format: date-time
|
||||||
|
x-apidog-orders:
|
||||||
|
- id
|
||||||
|
- email
|
||||||
|
- email_confirmed_at
|
||||||
|
- created_at
|
||||||
|
x-apidog-ignore-properties: []
|
||||||
|
x-apidog-folder: ''
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
x-apidog-orders:
|
||||||
|
- error
|
||||||
|
- message
|
||||||
|
- code
|
||||||
|
x-apidog-ignore-properties: []
|
||||||
|
x-apidog-folder: ''
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: jwt
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: Token JWT obtido no login
|
||||||
|
bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
servers:
|
||||||
|
- url: https://yuanqfswhberkoevtmfr.supabase.co
|
||||||
|
description: Prod Env
|
||||||
|
- url: ''
|
||||||
|
description: Cloud Mock
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
# Logout do usuário
|
||||||
|
|
||||||
|
## OpenAPI Specification
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
openapi: 3.0.1
|
||||||
|
info:
|
||||||
|
title: ''
|
||||||
|
description: ''
|
||||||
|
version: 1.0.0
|
||||||
|
paths:
|
||||||
|
/auth/v1/logout:
|
||||||
|
post:
|
||||||
|
summary: Logout do usuário
|
||||||
|
deprecated: false
|
||||||
|
description: Encerrar sessão do usuário
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
- Authentication
|
||||||
|
parameters: []
|
||||||
|
responses:
|
||||||
|
'204':
|
||||||
|
description: Logout realizado com sucesso
|
||||||
|
headers: {}
|
||||||
|
x-apidog-name: No Content
|
||||||
|
'401':
|
||||||
|
description: Token inválido
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/Error'
|
||||||
|
headers: {}
|
||||||
|
x-apidog-name: Unauthorized
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
x-apidog-folder: Authentication
|
||||||
|
x-apidog-status: released
|
||||||
|
x-run-in-apidog: https://app.apidog.com/web/project/1053378/apis/api-21940511-run
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
Error:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
error:
|
||||||
|
type: string
|
||||||
|
message:
|
||||||
|
type: string
|
||||||
|
code:
|
||||||
|
type: string
|
||||||
|
x-apidog-orders:
|
||||||
|
- error
|
||||||
|
- message
|
||||||
|
- code
|
||||||
|
x-apidog-ignore-properties: []
|
||||||
|
x-apidog-folder: ''
|
||||||
|
securitySchemes:
|
||||||
|
bearerAuth:
|
||||||
|
type: jwt
|
||||||
|
scheme: bearer
|
||||||
|
bearerFormat: JWT
|
||||||
|
description: Token JWT obtido no login
|
||||||
|
bearer:
|
||||||
|
type: http
|
||||||
|
scheme: bearer
|
||||||
|
servers:
|
||||||
|
- url: https://yuanqfswhberkoevtmfr.supabase.co
|
||||||
|
description: Prod Env
|
||||||
|
- url: ''
|
||||||
|
description: Cloud Mock
|
||||||
|
security:
|
||||||
|
- bearer: []
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
182
susconecta/ERRO_BACKEND_EDGE_FUNCTION.md
Normal file
182
susconecta/ERRO_BACKEND_EDGE_FUNCTION.md
Normal file
@ -0,0 +1,182 @@
|
|||||||
|
# 🚨 ERRO CRÍTICO NA EDGE FUNCTION - BACKEND
|
||||||
|
|
||||||
|
## Resumo do Problema
|
||||||
|
|
||||||
|
A Edge Function `/functions/v1/create-user` está retornando **erro 500** com mensagem:
|
||||||
|
```json
|
||||||
|
{ "error": "Failed to assign user role" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Evidências
|
||||||
|
|
||||||
|
### Console do Frontend
|
||||||
|
```
|
||||||
|
XHRPOST https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/create-user
|
||||||
|
[HTTP/3 500 1065ms]
|
||||||
|
|
||||||
|
[API ERROR] https://yuanqfswhberkoevtmfr.supabase.co/functions/v1/create-user 500
|
||||||
|
Object { error: "Failed to assign user role" }
|
||||||
|
```
|
||||||
|
|
||||||
|
### Request Enviado (CORRETO)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"email": "dipar64745@fanlvr.com",
|
||||||
|
"password": "senha789!",
|
||||||
|
"full_name": "Jonas Francisco Nascimento Bonfim",
|
||||||
|
"phone": "(79) 99649-8907",
|
||||||
|
"role": "medico"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Response Recebido (ERRO)
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"error": "Failed to assign user role"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Fluxo Atual (Correto segundo documentação)
|
||||||
|
|
||||||
|
1. ✅ Frontend cria perfil na tabela `doctors`
|
||||||
|
2. ✅ Frontend gera senha aleatória
|
||||||
|
3. ✅ Frontend chama `/functions/v1/create-user` com dados corretos
|
||||||
|
4. ❌ **Edge Function falha ao atribuir role na tabela `user_roles`**
|
||||||
|
|
||||||
|
## O Que a Edge Function DEVE Fazer
|
||||||
|
|
||||||
|
Segundo a documentação da API (`Documentação API.md`), a Edge Function `/functions/v1/create-user` deve:
|
||||||
|
|
||||||
|
1. ✅ Criar usuário no Supabase Auth (isso está funcionando)
|
||||||
|
2. ❌ **Inserir registro na tabela `user_roles`** (isso está falhando)
|
||||||
|
3. ✅ Retornar `{ success: true, user: {...} }`
|
||||||
|
|
||||||
|
## Possíveis Causas do Erro
|
||||||
|
|
||||||
|
### 1. SUPABASE_SERVICE_ROLE_KEY não configurada
|
||||||
|
A Edge Function precisa da `SUPABASE_SERVICE_ROLE_KEY` para ter permissão de:
|
||||||
|
- Inserir na tabela `user_roles`
|
||||||
|
- Fazer operações administrativas
|
||||||
|
|
||||||
|
**Como verificar:**
|
||||||
|
1. Acesse o Supabase Dashboard
|
||||||
|
2. Vá em **Edge Functions** > `create-user`
|
||||||
|
3. Verifique se a variável de ambiente `SUPABASE_SERVICE_ROLE_KEY` está configurada
|
||||||
|
4. Copie a chave de: **Settings** > **API** > **service_role key (secret)**
|
||||||
|
|
||||||
|
### 2. Tabela `user_roles` sem permissões corretas
|
||||||
|
A tabela pode estar bloqueando inserções da Edge Function.
|
||||||
|
|
||||||
|
**Como verificar:**
|
||||||
|
1. Acesse o Supabase Dashboard
|
||||||
|
2. Vá em **Database** > **user_roles**
|
||||||
|
3. Clique em **RLS Policies**
|
||||||
|
4. Verifique se existe uma policy permitindo:
|
||||||
|
- Service role pode inserir
|
||||||
|
- OU Edge Function pode inserir usando service key
|
||||||
|
|
||||||
|
**Policy esperada:**
|
||||||
|
```sql
|
||||||
|
-- Permitir que service role insira roles
|
||||||
|
CREATE POLICY "service_role_insert_user_roles"
|
||||||
|
ON user_roles FOR INSERT
|
||||||
|
TO service_role
|
||||||
|
WITH CHECK (true);
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Código da Edge Function com bug
|
||||||
|
O código da Edge Function pode ter erro de lógica ao tentar inserir na tabela.
|
||||||
|
|
||||||
|
**Onde encontrar:**
|
||||||
|
- Supabase Dashboard > **Edge Functions** > `create-user` > **Editor**
|
||||||
|
|
||||||
|
**O que verificar:**
|
||||||
|
```typescript
|
||||||
|
// A Edge Function deve ter algo assim:
|
||||||
|
const { data, error } = await supabaseAdmin
|
||||||
|
.from('user_roles')
|
||||||
|
.insert({
|
||||||
|
user_id: newUser.id,
|
||||||
|
role: role
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Erro ao inserir role:', error);
|
||||||
|
return new Response(
|
||||||
|
JSON.stringify({ error: 'Failed to assign user role' }),
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Como Testar Cada Possibilidade
|
||||||
|
|
||||||
|
### Teste 1: Verificar se service key está funcionando
|
||||||
|
Execute no SQL Editor do Supabase:
|
||||||
|
```sql
|
||||||
|
-- Teste de inserção manual
|
||||||
|
INSERT INTO user_roles (user_id, role)
|
||||||
|
VALUES ('00000000-0000-0000-0000-000000000000', 'medico');
|
||||||
|
|
||||||
|
-- Se der erro, mostrará a mensagem de permissão
|
||||||
|
```
|
||||||
|
|
||||||
|
### Teste 2: Verificar logs da Edge Function
|
||||||
|
1. Acesse **Edge Functions** > `create-user`
|
||||||
|
2. Clique em **Logs**
|
||||||
|
3. Procure por erros detalhados quando o frontend faz a chamada
|
||||||
|
|
||||||
|
### Teste 3: Verificar estrutura da tabela
|
||||||
|
```sql
|
||||||
|
-- Verificar estrutura da tabela user_roles
|
||||||
|
SELECT column_name, data_type, is_nullable
|
||||||
|
FROM information_schema.columns
|
||||||
|
WHERE table_name = 'user_roles';
|
||||||
|
```
|
||||||
|
|
||||||
|
Campos esperados:
|
||||||
|
- `id` (uuid, primary key)
|
||||||
|
- `user_id` (uuid, not null, foreign key para auth.users)
|
||||||
|
- `role` (text ou enum, not null)
|
||||||
|
- `created_at` (timestamp, default now())
|
||||||
|
|
||||||
|
## Solução Esperada do Backend
|
||||||
|
|
||||||
|
A equipe de backend precisa:
|
||||||
|
|
||||||
|
1. **URGENTE**: Configurar `SUPABASE_SERVICE_ROLE_KEY` na Edge Function
|
||||||
|
2. **URGENTE**: Adicionar RLS policy para permitir inserções via service role
|
||||||
|
3. **Recomendado**: Adicionar logs detalhados na Edge Function para debug
|
||||||
|
4. **Recomendado**: Retornar erro mais específico (ex: "Permission denied to insert into user_roles")
|
||||||
|
|
||||||
|
## Status do Frontend
|
||||||
|
|
||||||
|
✅ **O código do frontend está 100% correto e seguindo a documentação!**
|
||||||
|
|
||||||
|
Não há nada a fazer no frontend. O erro está exclusivamente no backend.
|
||||||
|
|
||||||
|
## Workaround Temporário (NÃO RECOMENDADO)
|
||||||
|
|
||||||
|
Se o backend não puder resolver urgentemente, podemos:
|
||||||
|
1. Criar usuários sem role (ou role padrão)
|
||||||
|
2. Administrador atribui roles manualmente depois
|
||||||
|
|
||||||
|
Mas isso **NÃO É RECOMENDADO** porque:
|
||||||
|
- Usuários não terão permissões corretas
|
||||||
|
- Aumenta trabalho manual
|
||||||
|
- Pode gerar problemas de segurança
|
||||||
|
|
||||||
|
## Contato
|
||||||
|
|
||||||
|
Frontend: ✅ Implementação completa e correta
|
||||||
|
Backend: ❌ Precisa corrigir Edge Function `create-user`
|
||||||
|
|
||||||
|
**Prioridade:** 🔴 CRÍTICA - Sistema não consegue criar novos usuários
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Data do erro:** 10/10/2025
|
||||||
|
**Ambiente:** https://yuanqfswhberkoevtmfr.supabase.co
|
||||||
|
**Edge Function:** `/functions/v1/create-user`
|
||||||
|
**Status Code:** 500
|
||||||
|
**Mensagem:** "Failed to assign user role"
|
||||||
158
susconecta/app/api/assign-role/route.ts
Normal file
158
susconecta/app/api/assign-role/route.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint server-side para atribuir roles aos usuários
|
||||||
|
* Usa SUPABASE_SERVICE_ROLE_KEY para realizar operações administrativas
|
||||||
|
*
|
||||||
|
* POST /api/assign-role
|
||||||
|
* Body: { user_id: string, role: string }
|
||||||
|
*/
|
||||||
|
export async function POST(request: NextRequest) {
|
||||||
|
try {
|
||||||
|
// 1. Verificar autenticação do requisitante
|
||||||
|
const authHeader = request.headers.get('authorization');
|
||||||
|
if (!authHeader) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Unauthorized', message: 'Token de autenticação não fornecido' },
|
||||||
|
{ status: 401 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Extrair dados do body
|
||||||
|
const body = await request.json();
|
||||||
|
const { user_id, role } = body;
|
||||||
|
|
||||||
|
if (!user_id || !role) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Bad Request', message: 'user_id e role são obrigatórios' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Validar role
|
||||||
|
const validRoles = ['admin', 'gestor', 'medico', 'secretaria', 'user'];
|
||||||
|
if (!validRoles.includes(role)) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Bad Request', message: `Role inválido. Valores aceitos: ${validRoles.join(', ')}` },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Obter service role key do ambiente
|
||||||
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
||||||
|
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY;
|
||||||
|
|
||||||
|
if (!supabaseUrl || !serviceRoleKey) {
|
||||||
|
console.error('❌ [ASSIGN-ROLE] SUPABASE_SERVICE_ROLE_KEY não configurada');
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Server Configuration Error',
|
||||||
|
message: 'Service role key não configurada no servidor. Entre em contato com o administrador do sistema.',
|
||||||
|
hint: 'Configure SUPABASE_SERVICE_ROLE_KEY nas variáveis de ambiente do servidor'
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Criar cliente Supabase com service role key
|
||||||
|
const supabaseAdmin = createClient(supabaseUrl, serviceRoleKey, {
|
||||||
|
auth: {
|
||||||
|
autoRefreshToken: false,
|
||||||
|
persistSession: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 6. Verificar se o usuário existe
|
||||||
|
const { data: userData, error: userError } = await supabaseAdmin.auth.admin.getUserById(user_id);
|
||||||
|
|
||||||
|
if (userError || !userData) {
|
||||||
|
console.error('❌ [ASSIGN-ROLE] Usuário não encontrado:', userError);
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'Not Found', message: 'Usuário não encontrado no sistema de autenticação' },
|
||||||
|
{ status: 404 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔐 [ASSIGN-ROLE] Atribuindo role "${role}" ao usuário ${user_id}`);
|
||||||
|
|
||||||
|
// 7. Inserir role na tabela user_roles
|
||||||
|
const { data: roleData, error: roleError } = await supabaseAdmin
|
||||||
|
.from('user_roles')
|
||||||
|
.insert({
|
||||||
|
user_id: user_id,
|
||||||
|
role: role,
|
||||||
|
created_at: new Date().toISOString(),
|
||||||
|
})
|
||||||
|
.select()
|
||||||
|
.single();
|
||||||
|
|
||||||
|
if (roleError) {
|
||||||
|
// Verificar se é erro de duplicação (usuário já tem esse role)
|
||||||
|
if (roleError.code === '23505') {
|
||||||
|
console.log(`⚠️ [ASSIGN-ROLE] Usuário já possui o role "${role}"`);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
message: `Usuário já possui o role "${role}"`,
|
||||||
|
user_id,
|
||||||
|
role,
|
||||||
|
already_exists: true
|
||||||
|
},
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.error('❌ [ASSIGN-ROLE] Erro ao inserir role:', roleError);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Database Error',
|
||||||
|
message: `Erro ao atribuir role: ${roleError.message}`,
|
||||||
|
code: roleError.code,
|
||||||
|
details: roleError.details
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`✅ [ASSIGN-ROLE] Role "${role}" atribuído com sucesso ao usuário ${user_id}`);
|
||||||
|
|
||||||
|
// 8. Retornar sucesso
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
success: true,
|
||||||
|
message: `Role "${role}" atribuído com sucesso`,
|
||||||
|
data: roleData,
|
||||||
|
user_id,
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
{ status: 200 }
|
||||||
|
);
|
||||||
|
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error('❌ [ASSIGN-ROLE] Erro inesperado:', error);
|
||||||
|
return NextResponse.json(
|
||||||
|
{
|
||||||
|
error: 'Internal Server Error',
|
||||||
|
message: 'Erro inesperado ao atribuir role',
|
||||||
|
details: error?.message || String(error)
|
||||||
|
},
|
||||||
|
{ status: 500 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Método OPTIONS para CORS (se necessário)
|
||||||
|
export async function OPTIONS(request: NextRequest) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
status: 200,
|
||||||
|
headers: {
|
||||||
|
'Access-Control-Allow-Origin': '*',
|
||||||
|
'Access-Control-Allow-Methods': 'POST, OPTIONS',
|
||||||
|
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@ -24,9 +24,7 @@ import {
|
|||||||
removerAnexoMedico,
|
removerAnexoMedico,
|
||||||
MedicoInput,
|
MedicoInput,
|
||||||
Medico,
|
Medico,
|
||||||
criarUsuario,
|
criarUsuarioMedico,
|
||||||
criarUsuarioDirectAuth,
|
|
||||||
assignRoleServerSide,
|
|
||||||
gerarSenhaAleatoria,
|
gerarSenhaAleatoria,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
;
|
;
|
||||||
@ -157,8 +155,13 @@ export function DoctorRegistrationForm({
|
|||||||
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
||||||
|
|
||||||
// Estados para o dialog de credenciais
|
// Estados para o dialog de credenciais
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [showCredentialsDialog, setShowCredentialsDialog] = useState(false);
|
||||||
const [tempCredentials, setTempCredentials] = useState<{ email: string; password: string } | null>(null);
|
const [credentials, setCredentials] = useState<{
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
userName: string;
|
||||||
|
userType: 'médico' | 'paciente';
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]);
|
const title = useMemo(() => (mode === "create" ? "Cadastro de Médico" : "Editar Médico"), [mode]);
|
||||||
|
|
||||||
@ -395,38 +398,84 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
else onOpenChange?.(false);
|
else onOpenChange?.(false);
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// --- NOVA LÓGICA DE CRIAÇÃO ---
|
// --- FLUXO DE CRIAÇÃO DE MÉDICO ---
|
||||||
|
console.log('🏥 [CRIAR MÉDICO] Iniciando processo completo...');
|
||||||
|
|
||||||
const medicoPayload = toPayload();
|
const medicoPayload = toPayload();
|
||||||
|
console.log("Enviando os dados para a API:", medicoPayload);
|
||||||
|
|
||||||
|
// 1. Cria o perfil do médico na tabela doctors
|
||||||
const savedDoctorProfile = await criarMedico(medicoPayload);
|
const savedDoctorProfile = await criarMedico(medicoPayload);
|
||||||
console.log("✅ Perfil do médico criado:", savedDoctorProfile);
|
console.log("✅ Perfil do médico criado:", savedDoctorProfile);
|
||||||
|
|
||||||
// ⚠️ IMPORTANTE: A criação de usuário de autenticação foi DESABILITADA temporariamente
|
// 2. Cria usuário no Supabase Auth (direto via /auth/v1/signup)
|
||||||
// porque a Edge Function /functions/v1/create-user está retornando erro 500 ao
|
console.log('🔐 Criando usuário de autenticação...');
|
||||||
// tentar atribuir o role "medico" ao usuário.
|
|
||||||
//
|
|
||||||
// Para habilitar novamente, o backend precisa corrigir a Edge Function ou
|
|
||||||
// configurar as permissões corretas na tabela user_roles.
|
|
||||||
//
|
|
||||||
// Por ora, apenas o perfil do médico será salvo na tabela "doctors".
|
|
||||||
// O acesso ao sistema precisa ser criado manualmente pelo administrador.
|
|
||||||
|
|
||||||
console.log("⚠️ Criação de usuário Auth desabilitada - salvando apenas perfil do médico");
|
try {
|
||||||
|
const authResponse = await criarUsuarioMedico({
|
||||||
alert(
|
email: form.email,
|
||||||
`✅ Médico cadastrado com sucesso!\n\n` +
|
full_name: form.full_name,
|
||||||
`📋 Perfil salvo na base de dados.\n\n` +
|
phone_mobile: form.celular || '',
|
||||||
`⚠️ IMPORTANTE: O acesso ao sistema (login) precisa ser criado manualmente.\n\n` +
|
});
|
||||||
`Motivo: A função de criação automática de usuários está com problema no backend.\n` +
|
|
||||||
`Entre em contato com o administrador do sistema para criar o acesso.`
|
if (authResponse.success && authResponse.user) {
|
||||||
);
|
console.log('✅ Usuário Auth criado:', authResponse.user.id);
|
||||||
|
|
||||||
// Limpa formulário e fecha
|
// 3. Exibe popup com credenciais
|
||||||
setForm(initial);
|
setCredentials({
|
||||||
setPhotoPreview(null);
|
email: authResponse.email,
|
||||||
setServerAnexos([]);
|
password: authResponse.password,
|
||||||
onSaved?.(savedDoctorProfile);
|
userName: form.full_name,
|
||||||
if (inline) onClose?.();
|
userType: 'médico',
|
||||||
else onOpenChange?.(false);
|
});
|
||||||
|
setShowCredentialsDialog(true);
|
||||||
|
|
||||||
|
// 4. Limpa formulário
|
||||||
|
setForm(initial);
|
||||||
|
setPhotoPreview(null);
|
||||||
|
setServerAnexos([]);
|
||||||
|
|
||||||
|
// 5. Notifica componente pai
|
||||||
|
onSaved?.(savedDoctorProfile);
|
||||||
|
} else {
|
||||||
|
throw new Error('Falha ao criar usuário de autenticação');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (authError: any) {
|
||||||
|
console.error('❌ Erro ao criar usuário Auth:', authError);
|
||||||
|
|
||||||
|
const errorMsg = authError?.message || String(authError);
|
||||||
|
|
||||||
|
// Mensagens específicas de erro
|
||||||
|
if (errorMsg.toLowerCase().includes('already registered') ||
|
||||||
|
errorMsg.toLowerCase().includes('already been registered') ||
|
||||||
|
errorMsg.toLowerCase().includes('já está cadastrado')) {
|
||||||
|
alert(
|
||||||
|
`⚠️ EMAIL JÁ CADASTRADO\n\n` +
|
||||||
|
`O email "${form.email}" já possui uma conta no sistema.\n\n` +
|
||||||
|
`✅ O perfil do médico "${form.full_name}" foi salvo com sucesso.\n\n` +
|
||||||
|
`❌ Porém, não foi possível criar o login porque este email já está em uso.\n\n` +
|
||||||
|
`SOLUÇÃO:\n` +
|
||||||
|
`• Use um email diferente para este médico, OU\n` +
|
||||||
|
`• Se o médico já tem conta, edite o perfil e vincule ao usuário existente`
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
alert(
|
||||||
|
`⚠️ Médico cadastrado com sucesso, mas houve um problema ao criar o acesso ao sistema.\n\n` +
|
||||||
|
`✅ Perfil do médico salvo: ${form.full_name}\n\n` +
|
||||||
|
`❌ Erro ao criar login: ${errorMsg}\n\n` +
|
||||||
|
`Por favor, entre em contato com o administrador para criar o acesso manualmente.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Limpa formulário mesmo com erro
|
||||||
|
setForm(initial);
|
||||||
|
setPhotoPreview(null);
|
||||||
|
setServerAnexos([]);
|
||||||
|
onSaved?.(savedDoctorProfile);
|
||||||
|
if (inline) onClose?.();
|
||||||
|
else onOpenChange?.(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error("❌ Erro no handleSubmit:", err);
|
console.error("❌ Erro no handleSubmit:", err);
|
||||||
@ -990,14 +1039,14 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
<div className="space-y-6">{content}</div>
|
<div className="space-y-6">{content}</div>
|
||||||
|
|
||||||
{/* Dialog de credenciais */}
|
{/* Dialog de credenciais */}
|
||||||
{tempCredentials && (
|
{credentials && (
|
||||||
<CredentialsDialog
|
<CredentialsDialog
|
||||||
open={dialogOpen}
|
open={showCredentialsDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
setDialogOpen(open);
|
setShowCredentialsDialog(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
// Quando o dialog de credenciais fecha, fecha o formulário também
|
// Quando o dialog de credenciais fecha, fecha o formulário também
|
||||||
setTempCredentials(null);
|
setCredentials(null);
|
||||||
if (inline) {
|
if (inline) {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} else {
|
} else {
|
||||||
@ -1005,10 +1054,10 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
email={tempCredentials.email}
|
email={credentials.email}
|
||||||
password={tempCredentials.password}
|
password={credentials.password}
|
||||||
userName={form.full_name}
|
userName={credentials.userName}
|
||||||
userType="médico"
|
userType={credentials.userType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@ -1029,20 +1078,20 @@ async function handleSubmit(ev: React.FormEvent) {
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* Dialog de credenciais */}
|
{/* Dialog de credenciais */}
|
||||||
{tempCredentials && (
|
{credentials && (
|
||||||
<CredentialsDialog
|
<CredentialsDialog
|
||||||
open={dialogOpen}
|
open={showCredentialsDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
setDialogOpen(open);
|
setShowCredentialsDialog(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setTempCredentials(null);
|
setCredentials(null);
|
||||||
onOpenChange?.(false);
|
onOpenChange?.(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
email={tempCredentials.email}
|
email={credentials.email}
|
||||||
password={tempCredentials.password}
|
password={credentials.password}
|
||||||
userName={form.full_name}
|
userName={credentials.userName}
|
||||||
userType="médico"
|
userType={credentials.userType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -24,9 +24,7 @@ import {
|
|||||||
listarAnexos,
|
listarAnexos,
|
||||||
removerAnexo,
|
removerAnexo,
|
||||||
buscarPacientePorId,
|
buscarPacientePorId,
|
||||||
criarUsuario,
|
criarUsuarioPaciente,
|
||||||
gerarSenhaAleatoria,
|
|
||||||
CreateUserResponse,
|
|
||||||
criarPaciente,
|
criarPaciente,
|
||||||
} from "@/lib/api";
|
} from "@/lib/api";
|
||||||
|
|
||||||
@ -106,8 +104,13 @@ export function PatientRegistrationForm({
|
|||||||
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
const [serverAnexos, setServerAnexos] = useState<any[]>([]);
|
||||||
|
|
||||||
// Estados para o dialog de credenciais
|
// Estados para o dialog de credenciais
|
||||||
const [dialogOpen, setDialogOpen] = useState(false);
|
const [showCredentialsDialog, setShowCredentialsDialog] = useState(false);
|
||||||
const [tempCredentials, setTempCredentials] = useState<{ email: string; password: string } | null>(null);
|
const [credentials, setCredentials] = useState<{
|
||||||
|
email: string;
|
||||||
|
password: string;
|
||||||
|
userName: string;
|
||||||
|
userType: 'médico' | 'paciente';
|
||||||
|
} | null>(null);
|
||||||
|
|
||||||
const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]);
|
const title = useMemo(() => (mode === "create" ? "Cadastro de Paciente" : "Editar Paciente"), [mode]);
|
||||||
|
|
||||||
@ -273,43 +276,41 @@ export function PatientRegistrationForm({
|
|||||||
console.log("✅ Perfil do paciente criado:", savedPatientProfile);
|
console.log("✅ Perfil do paciente criado:", savedPatientProfile);
|
||||||
|
|
||||||
if (form.email && form.email.includes('@')) {
|
if (form.email && form.email.includes('@')) {
|
||||||
const tempPassword = gerarSenhaAleatoria();
|
console.log("🔐 Criando usuário de autenticação (paciente)...");
|
||||||
const userInput = {
|
|
||||||
email: form.email,
|
|
||||||
password: tempPassword,
|
|
||||||
full_name: form.nome,
|
|
||||||
phone: form.telefone,
|
|
||||||
role: 'user' as const,
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log("🔐 Criando usuário de autenticação com payload:", userInput);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const userResponse = await criarUsuario(userInput);
|
const userResponse = await criarUsuarioPaciente({
|
||||||
|
email: form.email,
|
||||||
|
full_name: form.nome,
|
||||||
|
phone_mobile: form.telefone,
|
||||||
|
});
|
||||||
|
|
||||||
if (userResponse.success && userResponse.user) {
|
if (userResponse.success && userResponse.user) {
|
||||||
console.log("✅ Usuário de autenticação criado:", userResponse.user);
|
console.log("✅ Usuário de autenticação criado:", userResponse.user);
|
||||||
|
|
||||||
// Mostra credenciais (NÃO fecha o formulário ainda)
|
// Mostra credenciais no dialog usando as credenciais retornadas
|
||||||
setTempCredentials({ email: form.email, password: tempPassword });
|
setCredentials({
|
||||||
setDialogOpen(true);
|
email: userResponse.email ?? form.email,
|
||||||
|
password: userResponse.password ?? '',
|
||||||
|
userName: form.nome,
|
||||||
|
userType: 'paciente',
|
||||||
|
});
|
||||||
|
setShowCredentialsDialog(true);
|
||||||
|
|
||||||
// Limpa formulário mas NÃO fecha ainda - fechará quando o dialog de credenciais fechar
|
// Limpa formulário mas NÃO fecha ainda - fechará quando o dialog de credenciais fechar
|
||||||
setForm(initial);
|
setForm(initial);
|
||||||
setPhotoPreview(null);
|
setPhotoPreview(null);
|
||||||
setServerAnexos([]);
|
setServerAnexos([]);
|
||||||
onSaved?.(savedPatientProfile);
|
onSaved?.(savedPatientProfile);
|
||||||
// NÃO chama onClose ou onOpenChange aqui - deixa o dialog de credenciais fazer isso
|
return;
|
||||||
return;
|
|
||||||
} else {
|
} else {
|
||||||
throw new Error((userResponse as any).message || "Falhou ao criar o usuário de acesso.");
|
throw new Error((userResponse as any).message || "Falhou ao criar o usuário de acesso.");
|
||||||
}
|
}
|
||||||
} catch (userError: any) {
|
} catch (userError: any) {
|
||||||
console.error("❌ Erro ao criar usuário via função server-side:", userError);
|
console.error("❌ Erro ao criar usuário via signup:", userError);
|
||||||
|
|
||||||
// Mensagem de erro específica para email duplicado
|
// Mensagem de erro específica para email duplicado
|
||||||
const errorMsg = userError?.message || String(userError);
|
const errorMsg = userError?.message || String(userError);
|
||||||
|
|
||||||
if (errorMsg.toLowerCase().includes('already registered') ||
|
if (errorMsg.toLowerCase().includes('already registered') ||
|
||||||
errorMsg.toLowerCase().includes('já está cadastrado') ||
|
errorMsg.toLowerCase().includes('já está cadastrado') ||
|
||||||
errorMsg.toLowerCase().includes('já existe')) {
|
errorMsg.toLowerCase().includes('já existe')) {
|
||||||
@ -318,18 +319,6 @@ export function PatientRegistrationForm({
|
|||||||
`✅ O perfil do paciente foi salvo com sucesso.\n\n` +
|
`✅ O perfil do paciente foi salvo com sucesso.\n\n` +
|
||||||
`Para criar acesso ao sistema, use um email diferente ou recupere a senha do email existente.`
|
`Para criar acesso ao sistema, use um email diferente ou recupere a senha do email existente.`
|
||||||
);
|
);
|
||||||
} else if (errorMsg.toLowerCase().includes('failed to assign user role') ||
|
|
||||||
errorMsg.toLowerCase().includes('atribuir permissões')) {
|
|
||||||
alert(
|
|
||||||
`⚠️ PROBLEMA NA CONFIGURAÇÃO DO SISTEMA\n\n` +
|
|
||||||
`✅ O perfil do paciente foi salvo com sucesso.\n\n` +
|
|
||||||
`❌ Porém, houve falha ao atribuir permissões de acesso.\n\n` +
|
|
||||||
`Esse erro indica que a Edge Function do Supabase não está configurada corretamente.\n\n` +
|
|
||||||
`Entre em contato com o administrador do sistema para:\n` +
|
|
||||||
`1. Verificar se a service role key está configurada\n` +
|
|
||||||
`2. Verificar as permissões da tabela user_roles\n` +
|
|
||||||
`3. Revisar o código da Edge Function create-user`
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
alert(
|
alert(
|
||||||
`✅ Paciente cadastrado com sucesso!\n\n` +
|
`✅ Paciente cadastrado com sucesso!\n\n` +
|
||||||
@ -337,7 +326,7 @@ export function PatientRegistrationForm({
|
|||||||
`O cadastro do paciente foi salvo, mas será necessário criar o acesso manualmente.`
|
`O cadastro do paciente foi salvo, mas será necessário criar o acesso manualmente.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Limpa formulário e fecha
|
// Limpa formulário e fecha
|
||||||
setForm(initial);
|
setForm(initial);
|
||||||
setPhotoPreview(null);
|
setPhotoPreview(null);
|
||||||
@ -715,14 +704,14 @@ export function PatientRegistrationForm({
|
|||||||
<div className="space-y-6">{content}</div>
|
<div className="space-y-6">{content}</div>
|
||||||
|
|
||||||
{/* Dialog de credenciais */}
|
{/* Dialog de credenciais */}
|
||||||
{tempCredentials && (
|
{credentials && (
|
||||||
<CredentialsDialog
|
<CredentialsDialog
|
||||||
open={dialogOpen}
|
open={showCredentialsDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
setDialogOpen(open);
|
setShowCredentialsDialog(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
// Quando o dialog de credenciais fecha, fecha o formulário também
|
// Quando o dialog de credenciais fecha, fecha o formulário também
|
||||||
setTempCredentials(null);
|
setCredentials(null);
|
||||||
if (inline) {
|
if (inline) {
|
||||||
onClose?.();
|
onClose?.();
|
||||||
} else {
|
} else {
|
||||||
@ -730,10 +719,10 @@ export function PatientRegistrationForm({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
email={tempCredentials.email}
|
email={credentials.email}
|
||||||
password={tempCredentials.password}
|
password={credentials.password}
|
||||||
userName={form.nome}
|
userName={credentials.userName}
|
||||||
userType="paciente"
|
userType={credentials.userType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
@ -754,20 +743,20 @@ export function PatientRegistrationForm({
|
|||||||
</Dialog>
|
</Dialog>
|
||||||
|
|
||||||
{/* Dialog de credenciais */}
|
{/* Dialog de credenciais */}
|
||||||
{tempCredentials && (
|
{credentials && (
|
||||||
<CredentialsDialog
|
<CredentialsDialog
|
||||||
open={dialogOpen}
|
open={showCredentialsDialog}
|
||||||
onOpenChange={(open) => {
|
onOpenChange={(open) => {
|
||||||
setDialogOpen(open);
|
setShowCredentialsDialog(open);
|
||||||
if (!open) {
|
if (!open) {
|
||||||
setTempCredentials(null);
|
setCredentials(null);
|
||||||
onOpenChange?.(false);
|
onOpenChange?.(false);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
email={tempCredentials.email}
|
email={credentials.email}
|
||||||
password={tempCredentials.password}
|
password={credentials.password}
|
||||||
userName={form.nome}
|
userName={credentials.userName}
|
||||||
userType="paciente"
|
userType={credentials.userType}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user