docs: create Codigos/GPT-2
This commit is contained in:
997
Codigos/GPT-2.md
Normal file
997
Codigos/GPT-2.md
Normal file
@@ -0,0 +1,997 @@
|
||||
---
|
||||
title: Gpt 2
|
||||
description:
|
||||
published: true
|
||||
date: 2026-07-24T19:37:15.466Z
|
||||
tags:
|
||||
editor: markdown
|
||||
dateCreated: 2026-07-24T19:37:15.466Z
|
||||
---
|
||||
|
||||
# Guia de Estudo: Meu GPT v2 em LXC no Proxmox
|
||||
|
||||
Este documento descreve como evoluir o projeto original para uma aplicacao web multiusuario com:
|
||||
|
||||
- cadastro e login;
|
||||
- usuarios salvos em banco de dados;
|
||||
- historico de conversas;
|
||||
- upload de arquivos;
|
||||
- controle de consumo e custo por usuario;
|
||||
- painel administrativo;
|
||||
- hospedagem em um container LXC no Proxmox.
|
||||
|
||||
> Importante: a aplicacao usa a **OpenAI API**, cuja cobranca e separada da assinatura do ChatGPT. A chave da API pertence ao administrador da aplicacao e deve permanecer somente no servidor.
|
||||
|
||||
## 1. Resultado esperado
|
||||
|
||||
O usuario acessa a aplicacao pelo navegador, cria uma conta ou entra com seu login e conversa com o GPT. As conversas ficam salvas e podem ser abertas novamente. O usuario pode anexar arquivos e consultar seu consumo.
|
||||
|
||||
O administrador pode:
|
||||
|
||||
- ativar e desativar usuarios;
|
||||
- definir limite mensal individual;
|
||||
- consultar consumo por usuario;
|
||||
- consultar quantidade de tokens e custo estimado;
|
||||
- impedir novas chamadas quando o limite for atingido;
|
||||
- escolher quais modelos ficam disponiveis.
|
||||
|
||||
## 2. Arquitetura proposta
|
||||
|
||||
```text
|
||||
Navegador
|
||||
|
|
||||
| HTTPS
|
||||
v
|
||||
Nginx no LXC
|
||||
|
|
||||
v
|
||||
Next.js
|
||||
|-- telas React
|
||||
|-- login e sessoes
|
||||
|-- API de chat
|
||||
|-- API de upload
|
||||
|-- regras de limite
|
||||
|
|
||||
|----------> PostgreSQL
|
||||
| |-- usuarios
|
||||
| |-- conversas
|
||||
| |-- mensagens
|
||||
| |-- anexos
|
||||
| |-- consumo
|
||||
|
|
||||
|----------> armazenamento privado no LXC
|
||||
| |-- arquivos enviados
|
||||
|
|
||||
`----------> OpenAI Responses API
|
||||
|-- respostas do modelo
|
||||
|-- leitura de arquivos compativeis
|
||||
`-- contagem de tokens
|
||||
```
|
||||
|
||||
## 3. Tecnologias escolhidas e por que usa-las
|
||||
|
||||
### Next.js e React
|
||||
|
||||
O Next.js continua sendo a aplicacao principal. Ele permite criar as telas e as rotas de backend no mesmo projeto.
|
||||
|
||||
O React cuida das partes interativas da tela:
|
||||
|
||||
- lista de conversas;
|
||||
- envio de mensagens;
|
||||
- indicador de resposta;
|
||||
- selecao e upload de arquivos;
|
||||
- tela de consumo.
|
||||
|
||||
### TypeScript
|
||||
|
||||
O TypeScript reduz erros ao definir claramente o formato de usuarios, mensagens, anexos e registros de consumo.
|
||||
|
||||
Exemplo:
|
||||
|
||||
```ts
|
||||
type Message = {
|
||||
id: string;
|
||||
role: "USER" | "ASSISTANT";
|
||||
content: string;
|
||||
};
|
||||
```
|
||||
|
||||
### PostgreSQL
|
||||
|
||||
O PostgreSQL sera o banco de dados. Ele e adequado para dados relacionados, por exemplo:
|
||||
|
||||
- um usuario possui varias conversas;
|
||||
- uma conversa possui varias mensagens;
|
||||
- uma mensagem pode possuir varios anexos;
|
||||
- cada chamada da API gera um registro de consumo.
|
||||
|
||||
Para estudo e para uma instalacao pequena, o PostgreSQL pode rodar no mesmo LXC. Em um ambiente maior, pode ser movido para outro container.
|
||||
|
||||
### Prisma ORM
|
||||
|
||||
O Prisma faz a comunicacao entre o TypeScript e o PostgreSQL.
|
||||
|
||||
Em vez de escrever SQL para cada operacao, o projeto pode usar:
|
||||
|
||||
```ts
|
||||
const conversations = await prisma.conversation.findMany({
|
||||
where: { userId },
|
||||
orderBy: { updatedAt: "desc" },
|
||||
});
|
||||
```
|
||||
|
||||
As migrations do Prisma tambem registram as alteracoes feitas na estrutura do banco.
|
||||
|
||||
### Auth.js
|
||||
|
||||
O Auth.js sera responsavel pelo fluxo de autenticacao e pela sessao do usuario. Neste projeto:
|
||||
|
||||
- o usuario informa e-mail e senha;
|
||||
- a aplicacao procura o usuario no PostgreSQL;
|
||||
- a senha e comparada com o hash salvo;
|
||||
- uma sessao assinada identifica o usuario nas proximas requisicoes.
|
||||
|
||||
A senha nunca deve ser salva em texto puro. Use `bcryptjs` ou `argon2` para gerar o hash.
|
||||
|
||||
### Armazenamento de arquivos
|
||||
|
||||
Na primeira versao, os arquivos podem ser armazenados em uma pasta privada do LXC:
|
||||
|
||||
```text
|
||||
/var/lib/meu-gpt/uploads
|
||||
```
|
||||
|
||||
Essa pasta nao deve ficar dentro de `public/`, pois isso permitiria tentar acessar o arquivo diretamente por URL.
|
||||
|
||||
Os metadados ficam no PostgreSQL, mas o conteudo fica no disco. Mais tarde, o armazenamento pode ser trocado por MinIO ou outro servico compativel com S3.
|
||||
|
||||
### Nginx, PM2 e HTTPS
|
||||
|
||||
- **Nginx** recebe os acessos HTTPS e encaminha para o Next.js.
|
||||
- **PM2** mantem o processo do Next.js em execucao.
|
||||
- **Certbot e Let's Encrypt** fornecem o certificado HTTPS.
|
||||
|
||||
## 4. Estrutura sugerida do projeto
|
||||
|
||||
```text
|
||||
meu-gpt/
|
||||
|-- app/
|
||||
| |-- (auth)/
|
||||
| | |-- login/page.tsx
|
||||
| | `-- cadastro/page.tsx
|
||||
| |-- (app)/
|
||||
| | |-- chat/[conversationId]/page.tsx
|
||||
| | |-- configuracoes/page.tsx
|
||||
| | `-- consumo/page.tsx
|
||||
| |-- admin/
|
||||
| | |-- usuarios/page.tsx
|
||||
| | `-- consumo/page.tsx
|
||||
| `-- api/
|
||||
| |-- auth/[...nextauth]/route.ts
|
||||
| |-- register/route.ts
|
||||
| |-- conversations/route.ts
|
||||
| |-- conversations/[id]/messages/route.ts
|
||||
| |-- upload/route.ts
|
||||
| `-- admin/users/[id]/route.ts
|
||||
|-- components/
|
||||
|-- lib/
|
||||
| |-- auth.ts
|
||||
| |-- prisma.ts
|
||||
| |-- openai.ts
|
||||
| |-- permissions.ts
|
||||
| |-- pricing.ts
|
||||
| `-- storage.ts
|
||||
|-- prisma/
|
||||
| `-- schema.prisma
|
||||
|-- private-uploads/
|
||||
|-- middleware.ts
|
||||
|-- .env
|
||||
`-- package.json
|
||||
```
|
||||
|
||||
Em producao, use `/var/lib/meu-gpt/uploads` em vez de `private-uploads/`.
|
||||
|
||||
## 5. Modelo de dados
|
||||
|
||||
O modelo abaixo e um ponto de partida. Ele pode ser ajustado durante a implementacao.
|
||||
|
||||
```prisma
|
||||
enum UserRole {
|
||||
USER
|
||||
ADMIN
|
||||
}
|
||||
|
||||
enum UserStatus {
|
||||
ACTIVE
|
||||
BLOCKED
|
||||
}
|
||||
|
||||
enum MessageRole {
|
||||
USER
|
||||
ASSISTANT
|
||||
SYSTEM
|
||||
}
|
||||
|
||||
enum UsageStatus {
|
||||
SUCCESS
|
||||
ERROR
|
||||
}
|
||||
|
||||
model User {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
email String @unique
|
||||
passwordHash String
|
||||
role UserRole @default(USER)
|
||||
status UserStatus @default(ACTIVE)
|
||||
monthlyLimitUsd Decimal @default(5.00) @db.Decimal(12, 4)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
conversations Conversation[]
|
||||
usageRecords UsageRecord[]
|
||||
attachments Attachment[]
|
||||
}
|
||||
|
||||
model Conversation {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
title String @default("Nova conversa")
|
||||
model String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
archivedAt DateTime?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
messages Message[]
|
||||
usageRecords UsageRecord[]
|
||||
|
||||
@@index([userId, updatedAt])
|
||||
}
|
||||
|
||||
model Message {
|
||||
id String @id @default(cuid())
|
||||
conversationId String
|
||||
role MessageRole
|
||||
content String @db.Text
|
||||
openaiResponseId String?
|
||||
createdAt DateTime @default(now())
|
||||
conversation Conversation @relation(fields: [conversationId], references: [id], onDelete: Cascade)
|
||||
attachments Attachment[]
|
||||
|
||||
@@index([conversationId, createdAt])
|
||||
}
|
||||
|
||||
model Attachment {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
messageId String?
|
||||
originalName String
|
||||
storedName String
|
||||
mimeType String
|
||||
sizeBytes Int
|
||||
sha256 String
|
||||
openaiFileId String?
|
||||
createdAt DateTime @default(now())
|
||||
deletedAt DateTime?
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
message Message? @relation(fields: [messageId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([userId, createdAt])
|
||||
}
|
||||
|
||||
model UsageRecord {
|
||||
id String @id @default(cuid())
|
||||
userId String
|
||||
conversationId String?
|
||||
model String
|
||||
inputTokens Int @default(0)
|
||||
cachedInputTokens Int @default(0)
|
||||
outputTokens Int @default(0)
|
||||
reasoningTokens Int @default(0)
|
||||
totalTokens Int @default(0)
|
||||
costUsd Decimal @default(0) @db.Decimal(18, 8)
|
||||
status UsageStatus
|
||||
requestId String?
|
||||
createdAt DateTime @default(now())
|
||||
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
||||
conversation Conversation? @relation(fields: [conversationId], references: [id], onDelete: SetNull)
|
||||
|
||||
@@index([userId, createdAt])
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Instalacao no LXC
|
||||
|
||||
### Recursos recomendados
|
||||
|
||||
Para poucos usuarios:
|
||||
|
||||
```text
|
||||
Sistema: Debian 12 ou Ubuntu 24.04
|
||||
CPU: 2 a 4 cores
|
||||
RAM: 4 GB
|
||||
Disco: 30 GB ou mais
|
||||
Container nao privilegiado: sim
|
||||
Inicializar no boot: sim
|
||||
```
|
||||
|
||||
O tamanho do disco depende principalmente dos uploads e dos backups.
|
||||
|
||||
### Instalar os pacotes
|
||||
|
||||
```bash
|
||||
apt update
|
||||
apt upgrade -y
|
||||
apt install -y curl git nginx postgresql postgresql-contrib ca-certificates
|
||||
```
|
||||
|
||||
Instale uma versao LTS do Node.js compativel com as versoes atuais do Next.js e do Prisma escolhidas no momento da instalacao.
|
||||
|
||||
Confirme:
|
||||
|
||||
```bash
|
||||
node -v
|
||||
npm -v
|
||||
psql --version
|
||||
```
|
||||
|
||||
### Criar banco e usuario
|
||||
|
||||
Entre no PostgreSQL:
|
||||
|
||||
```bash
|
||||
sudo -u postgres psql
|
||||
```
|
||||
|
||||
Execute, substituindo a senha:
|
||||
|
||||
```sql
|
||||
CREATE USER meu_gpt_user WITH ENCRYPTED PASSWORD 'COLOQUE_UMA_SENHA_FORTE';
|
||||
CREATE DATABASE meu_gpt OWNER meu_gpt_user;
|
||||
\q
|
||||
```
|
||||
|
||||
O PostgreSQL pode continuar aceitando conexoes somente do proprio LXC.
|
||||
|
||||
### Instalar dependencias do projeto
|
||||
|
||||
Dentro da pasta do projeto:
|
||||
|
||||
```bash
|
||||
npm install openai next-auth bcryptjs zod
|
||||
npm install prisma @prisma/client
|
||||
npm install -D @types/bcryptjs
|
||||
```
|
||||
|
||||
Bibliotecas opcionais:
|
||||
|
||||
```bash
|
||||
npm install file-type
|
||||
```
|
||||
|
||||
`zod` valida os dados recebidos. `file-type` ajuda a conferir o tipo real do arquivo, em vez de confiar somente no nome informado pelo navegador.
|
||||
|
||||
## 7. Variaveis de ambiente
|
||||
|
||||
Exemplo de `.env`:
|
||||
|
||||
```env
|
||||
DATABASE_URL="postgresql://meu_gpt_user:SENHA_FORTE@127.0.0.1:5432/meu_gpt"
|
||||
|
||||
AUTH_SECRET="GERE_UMA_CHAVE_LONGA_E_ALEATORIA"
|
||||
AUTH_URL="https://gpt.seudominio.com"
|
||||
|
||||
OPENAI_API_KEY="SUA_CHAVE_DA_API"
|
||||
OPENAI_MODEL="MODELO_PERMITIDO_PELA_SUA_CONTA"
|
||||
|
||||
UPLOAD_DIR="/var/lib/meu-gpt/uploads"
|
||||
MAX_UPLOAD_BYTES="10485760"
|
||||
|
||||
DEFAULT_MONTHLY_LIMIT_USD="5.00"
|
||||
```
|
||||
|
||||
Gere o segredo da autenticacao com:
|
||||
|
||||
```bash
|
||||
openssl rand -base64 32
|
||||
```
|
||||
|
||||
Regras:
|
||||
|
||||
- nao envie `.env` para Git;
|
||||
- nunca use variavel iniciada por `NEXT_PUBLIC_` para a chave da OpenAI;
|
||||
- deixe o arquivo legivel apenas pelo usuario que executa a aplicacao;
|
||||
- troque imediatamente qualquer segredo que tenha sido exposto.
|
||||
|
||||
## 8. Cadastro e login
|
||||
|
||||
### Cadastro
|
||||
|
||||
O endpoint de cadastro deve:
|
||||
|
||||
1. validar nome, e-mail e senha com `zod`;
|
||||
2. normalizar o e-mail para letras minusculas;
|
||||
3. exigir uma senha com tamanho minimo;
|
||||
4. verificar se o e-mail ja existe;
|
||||
5. gerar o hash da senha;
|
||||
6. criar o usuario com limite mensal padrao;
|
||||
7. nunca devolver `passwordHash` na resposta.
|
||||
|
||||
Exemplo do hash:
|
||||
|
||||
```ts
|
||||
import bcrypt from "bcryptjs";
|
||||
|
||||
const passwordHash = await bcrypt.hash(password, 12);
|
||||
```
|
||||
|
||||
### Login
|
||||
|
||||
No login:
|
||||
|
||||
1. procure o usuario pelo e-mail;
|
||||
2. verifique se ele esta ativo;
|
||||
3. compare a senha informada com o hash;
|
||||
4. crie uma sessao contendo somente os identificadores necessarios;
|
||||
5. redirecione para a tela de chat.
|
||||
|
||||
```ts
|
||||
const validPassword = await bcrypt.compare(password, user.passwordHash);
|
||||
```
|
||||
|
||||
Proteja todas as rotas de chat, upload, historico e consumo. O backend deve obter o `userId` da sessao; nunca aceite um `userId` enviado pelo navegador como prova de identidade.
|
||||
|
||||
### Cadastro publico ou controlado
|
||||
|
||||
Para uma aplicacao pessoal ou empresarial, e mais seguro escolher uma destas opcoes:
|
||||
|
||||
- somente o administrador cria usuarios;
|
||||
- cadastro exige um codigo de convite;
|
||||
- cadastro publico exige verificacao de e-mail.
|
||||
|
||||
Nao deixe cadastro publico ilimitado em uma aplicacao que usa sua chave da API.
|
||||
|
||||
## 9. Historico de conversas
|
||||
|
||||
### Fluxo
|
||||
|
||||
1. O usuario cria uma conversa.
|
||||
2. A aplicacao cria um registro `Conversation`.
|
||||
3. A mensagem do usuario e salva em `Message`.
|
||||
4. O backend carrega as mensagens anteriores daquela conversa.
|
||||
5. O backend chama a OpenAI.
|
||||
6. A resposta e salva como outra `Message`.
|
||||
7. O registro de consumo e salvo.
|
||||
8. A tela atualiza a lista e o horario da conversa.
|
||||
|
||||
### Regra de propriedade
|
||||
|
||||
Toda consulta deve combinar o identificador da conversa com o usuario autenticado:
|
||||
|
||||
```ts
|
||||
const conversation = await prisma.conversation.findFirst({
|
||||
where: {
|
||||
id: conversationId,
|
||||
userId: session.user.id,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Buscar somente pelo `conversationId` criaria uma falha de seguranca: um usuario poderia tentar abrir a conversa de outro usuario alterando a URL.
|
||||
|
||||
### Contexto enviado ao modelo
|
||||
|
||||
Nao envie para a API todas as mensagens de uma conversa sem limite. Conversas grandes:
|
||||
|
||||
- aumentam o custo;
|
||||
- ficam mais lentas;
|
||||
- podem ultrapassar o limite de contexto do modelo.
|
||||
|
||||
Uma estrategia inicial e enviar as mensagens mais recentes. Depois, implemente resumo automatico das mensagens antigas e armazene esse resumo na conversa.
|
||||
|
||||
## 10. Upload de arquivos
|
||||
|
||||
### Fluxo seguro
|
||||
|
||||
1. exigir usuario autenticado;
|
||||
2. limitar o tamanho;
|
||||
3. permitir somente extensoes e tipos necessarios;
|
||||
4. conferir o tipo real do arquivo;
|
||||
5. gerar nome aleatorio para armazenamento;
|
||||
6. calcular SHA-256;
|
||||
7. salvar fora da pasta publica;
|
||||
8. registrar os metadados no banco;
|
||||
9. enviar para a OpenAI apenas quando for usado em uma mensagem;
|
||||
10. apagar arquivos temporarios e arquivos remotos quando nao forem mais necessarios.
|
||||
|
||||
Exemplo de nome fisico:
|
||||
|
||||
```text
|
||||
/var/lib/meu-gpt/uploads/usuario-id/550e8400-e29b-41d4-a716-446655440000.pdf
|
||||
```
|
||||
|
||||
O nome original fica no banco para ser exibido na interface, mas nao deve ser usado diretamente como caminho no disco.
|
||||
|
||||
### Tipos iniciais recomendados
|
||||
|
||||
Comece com poucos formatos:
|
||||
|
||||
- PDF;
|
||||
- TXT;
|
||||
- CSV;
|
||||
- DOCX, se o fluxo escolhido oferecer suporte adequado;
|
||||
- imagens PNG e JPEG, se o modelo selecionado aceitar imagens.
|
||||
|
||||
Defina um limite menor na propria aplicacao, por exemplo 10 MB, mesmo que a API aceite arquivos maiores. Isso protege o disco, a memoria e o custo.
|
||||
|
||||
### Dois usos diferentes de arquivos
|
||||
|
||||
**Arquivo anexado a uma pergunta:** o documento e enviado como entrada daquela mensagem. E a opcao mais simples para a primeira versao.
|
||||
|
||||
**Biblioteca permanente de documentos:** exige busca nos documentos, controle de permissoes, indexacao e normalmente uma estrategia de RAG ou busca em arquivos. Deve ser uma segunda fase.
|
||||
|
||||
## 11. Controle de custo por usuario
|
||||
|
||||
### Como medir
|
||||
|
||||
A resposta da OpenAI informa dados de uso, incluindo tokens de entrada, saida e total. O backend deve salvar esses valores em `UsageRecord`.
|
||||
|
||||
Exemplo conceitual:
|
||||
|
||||
```ts
|
||||
const usage = response.usage;
|
||||
|
||||
await prisma.usageRecord.create({
|
||||
data: {
|
||||
userId,
|
||||
conversationId,
|
||||
model: response.model,
|
||||
inputTokens: usage?.input_tokens ?? 0,
|
||||
cachedInputTokens: usage?.input_tokens_details?.cached_tokens ?? 0,
|
||||
outputTokens: usage?.output_tokens ?? 0,
|
||||
reasoningTokens: usage?.output_tokens_details?.reasoning_tokens ?? 0,
|
||||
totalTokens: usage?.total_tokens ?? 0,
|
||||
costUsd: calculatedCost,
|
||||
status: "SUCCESS",
|
||||
requestId: response.id,
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Os nomes exatos dos campos devem ser conferidos na versao do SDK instalada.
|
||||
|
||||
### Como calcular
|
||||
|
||||
Mantenha no backend uma tabela de precos por modelo:
|
||||
|
||||
```ts
|
||||
type ModelPrice = {
|
||||
inputUsdPerMillion: number;
|
||||
cachedInputUsdPerMillion?: number;
|
||||
outputUsdPerMillion: number;
|
||||
};
|
||||
```
|
||||
|
||||
Formula geral:
|
||||
|
||||
```text
|
||||
custo de entrada =
|
||||
tokens de entrada cobraveis / 1.000.000 x preco de entrada
|
||||
|
||||
custo de entrada em cache =
|
||||
tokens em cache / 1.000.000 x preco de cache
|
||||
|
||||
custo de saida =
|
||||
tokens de saida / 1.000.000 x preco de saida
|
||||
|
||||
custo total =
|
||||
entrada + cache + saida
|
||||
```
|
||||
|
||||
Nao grave valores monetarios usando `float`. Use `Decimal` no banco para evitar erros de arredondamento.
|
||||
|
||||
Os precos mudam. Por isso:
|
||||
|
||||
- nao espalhe precos pelo codigo;
|
||||
- mantenha uma unica tabela de configuracao;
|
||||
- registre o modelo usado em cada chamada;
|
||||
- revise os precos na pagina oficial antes de atualizar;
|
||||
- trate o valor mostrado como estimativa interna e reconcilie-o periodicamente com o painel da OpenAI.
|
||||
|
||||
### Verificacao do limite
|
||||
|
||||
Antes de chamar a API:
|
||||
|
||||
```ts
|
||||
const startOfMonth = new Date(
|
||||
new Date().getFullYear(),
|
||||
new Date().getMonth(),
|
||||
1,
|
||||
);
|
||||
|
||||
const usage = await prisma.usageRecord.aggregate({
|
||||
where: {
|
||||
userId,
|
||||
status: "SUCCESS",
|
||||
createdAt: { gte: startOfMonth },
|
||||
},
|
||||
_sum: { costUsd: true },
|
||||
});
|
||||
```
|
||||
|
||||
Se o total for maior ou igual ao limite, a API deve responder:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": "Limite mensal atingido"
|
||||
}
|
||||
```
|
||||
|
||||
Use o status HTTP `429` para indicar que o limite de uso foi atingido.
|
||||
|
||||
### Evitar que chamadas simultaneas ultrapassem o limite
|
||||
|
||||
Uma simples verificacao antes da chamada nao e suficiente quando o mesmo usuario envia varias requisicoes ao mesmo tempo.
|
||||
|
||||
Para um limite mais rigido:
|
||||
|
||||
- permita somente uma geracao ativa por usuario; ou
|
||||
- reserve um valor estimado antes da chamada em uma transacao;
|
||||
- ao terminar, substitua a reserva pelo custo real;
|
||||
- libere a reserva se a chamada falhar.
|
||||
|
||||
Tambem defina `max_output_tokens` para limitar o tamanho e o custo maximo da resposta.
|
||||
|
||||
### O painel de consumo
|
||||
|
||||
Mostre ao usuario:
|
||||
|
||||
- consumo do mes;
|
||||
- limite mensal;
|
||||
- percentual utilizado;
|
||||
- tokens de entrada e saida;
|
||||
- consumo por conversa;
|
||||
- modelo utilizado;
|
||||
- data de renovacao do limite.
|
||||
|
||||
O administrador deve visualizar os mesmos dados para todos os usuarios.
|
||||
|
||||
## 12. Fluxo da rota de chat
|
||||
|
||||
Uma rota segura segue esta ordem:
|
||||
|
||||
```text
|
||||
1. Validar a sessao
|
||||
2. Validar os dados recebidos
|
||||
3. Confirmar que a conversa pertence ao usuario
|
||||
4. Confirmar que o usuario esta ativo
|
||||
5. Conferir o limite mensal
|
||||
6. Salvar a mensagem do usuario
|
||||
7. Preparar contexto e anexos
|
||||
8. Chamar a OpenAI
|
||||
9. Salvar a resposta
|
||||
10. Salvar tokens e custo
|
||||
11. Devolver a resposta ao navegador
|
||||
```
|
||||
|
||||
Pseudoimplementacao:
|
||||
|
||||
```ts
|
||||
export async function POST(request: Request) {
|
||||
const session = await requireSession();
|
||||
const input = chatSchema.parse(await request.json());
|
||||
|
||||
const conversation = await findOwnedConversation(
|
||||
input.conversationId,
|
||||
session.user.id,
|
||||
);
|
||||
|
||||
await assertUserCanSpend(session.user.id);
|
||||
|
||||
const userMessage = await saveUserMessage(conversation.id, input);
|
||||
|
||||
try {
|
||||
const response = await createOpenAIResponse({
|
||||
conversation,
|
||||
userMessage,
|
||||
});
|
||||
|
||||
const calculatedCost = calculateCost(response.model, response.usage);
|
||||
|
||||
await saveAssistantMessageAndUsage({
|
||||
userId: session.user.id,
|
||||
conversationId: conversation.id,
|
||||
response,
|
||||
calculatedCost,
|
||||
});
|
||||
|
||||
return Response.json({ text: response.output_text });
|
||||
} catch (error) {
|
||||
await saveFailedUsage(session.user.id, conversation.id);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
No codigo real, salve a mensagem do assistente e o consumo na mesma transacao do banco.
|
||||
|
||||
## 13. Painel administrativo
|
||||
|
||||
Crie uma area acessivel somente quando:
|
||||
|
||||
```ts
|
||||
session.user.role === "ADMIN"
|
||||
```
|
||||
|
||||
Funcoes iniciais:
|
||||
|
||||
- listar usuarios;
|
||||
- bloquear e desbloquear;
|
||||
- alterar limite mensal;
|
||||
- promover um usuario a administrador;
|
||||
- consultar consumo por periodo;
|
||||
- consultar erros;
|
||||
- excluir ou anonimizar um usuario;
|
||||
- apagar anexos e conversas conforme a politica definida.
|
||||
|
||||
Toda alteracao administrativa importante deve gerar um registro de auditoria em uma futura tabela `AuditLog`.
|
||||
|
||||
## 14. Seguranca essencial
|
||||
|
||||
Antes de publicar:
|
||||
|
||||
- use HTTPS;
|
||||
- mantenha a chave da OpenAI somente no servidor;
|
||||
- use hash forte de senha;
|
||||
- aplique limite de tentativas no login;
|
||||
- aplique limite de requisicoes no chat e upload;
|
||||
- valide todos os dados com `zod`;
|
||||
- confira a propriedade de cada conversa e arquivo;
|
||||
- limite tamanho e tipo dos uploads;
|
||||
- use nomes aleatorios no disco;
|
||||
- nao renderize HTML recebido do modelo sem sanitizacao;
|
||||
- configure cookies como `HttpOnly`, `Secure` e `SameSite`;
|
||||
- bloqueie usuarios desativados em todas as APIs;
|
||||
- mantenha dependencias atualizadas;
|
||||
- faca backup criptografado;
|
||||
- nunca inclua segredos nos logs;
|
||||
- defina politica de retencao e exclusao de dados.
|
||||
|
||||
Se a aplicacao ficar acessivel somente por VPN, a superficie de ataque diminui, mas login e autorizacao continuam necessarios.
|
||||
|
||||
## 15. Preparar armazenamento no LXC
|
||||
|
||||
Exemplo:
|
||||
|
||||
```bash
|
||||
mkdir -p /var/lib/meu-gpt/uploads
|
||||
chown -R meu-gpt:meu-gpt /var/lib/meu-gpt
|
||||
chmod 750 /var/lib/meu-gpt
|
||||
chmod 750 /var/lib/meu-gpt/uploads
|
||||
```
|
||||
|
||||
O usuario Linux `meu-gpt` deve ser o mesmo que executa o processo da aplicacao.
|
||||
|
||||
Evite executar a aplicacao como `root`.
|
||||
|
||||
## 16. Migrations e primeira conta administrativa
|
||||
|
||||
Depois de configurar o Prisma:
|
||||
|
||||
```bash
|
||||
npx prisma generate
|
||||
npx prisma migrate dev --name auth_history_upload_usage
|
||||
```
|
||||
|
||||
Em producao, aplique migrations ja criadas:
|
||||
|
||||
```bash
|
||||
npx prisma migrate deploy
|
||||
```
|
||||
|
||||
Crie um script de seed para a primeira conta administrativa. A senha deve vir de uma variavel de ambiente temporaria, ser transformada em hash e nunca aparecer no repositorio.
|
||||
|
||||
## 17. Build e execucao
|
||||
|
||||
```bash
|
||||
npm run build
|
||||
pm2 start npm --name meu-gpt -- start
|
||||
pm2 save
|
||||
pm2 startup
|
||||
```
|
||||
|
||||
Use o comando exibido pelo `pm2 startup` para ativar o inicio automatico.
|
||||
|
||||
Consulte logs:
|
||||
|
||||
```bash
|
||||
pm2 logs meu-gpt
|
||||
```
|
||||
|
||||
Os logs devem conter IDs de requisicao e mensagens de erro, mas nao senhas, tokens de sessao, chave da API ou conteudo confidencial dos arquivos.
|
||||
|
||||
## 18. Nginx
|
||||
|
||||
Exemplo basico:
|
||||
|
||||
```nginx
|
||||
server {
|
||||
listen 80;
|
||||
server_name gpt.seudominio.com;
|
||||
|
||||
client_max_body_size 10M;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:3000;
|
||||
proxy_http_version 1.1;
|
||||
|
||||
proxy_set_header Host $host;
|
||||
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;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
O limite do Nginx deve ser igual ou ligeiramente maior que o limite configurado na aplicacao.
|
||||
|
||||
Depois:
|
||||
|
||||
```bash
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
certbot --nginx -d gpt.seudominio.com
|
||||
```
|
||||
|
||||
## 19. Backup
|
||||
|
||||
O backup precisa incluir:
|
||||
|
||||
- banco PostgreSQL;
|
||||
- pasta de uploads;
|
||||
- arquivos de configuracao;
|
||||
- migrations do Prisma.
|
||||
|
||||
Exemplo de backup do banco:
|
||||
|
||||
```bash
|
||||
sudo -u postgres pg_dump -Fc meu_gpt > /var/backups/meu-gpt-db.dump
|
||||
```
|
||||
|
||||
Nao basta criar o backup. Teste periodicamente a restauracao em outro banco ou container.
|
||||
|
||||
Considere snapshots do LXC como protecao adicional, mas mantenha tambem backups independentes do Proxmox.
|
||||
|
||||
## 20. Ordem recomendada de implementacao
|
||||
|
||||
### Fase 1: banco e login
|
||||
|
||||
- instalar PostgreSQL;
|
||||
- adicionar Prisma;
|
||||
- criar `User`;
|
||||
- criar cadastro controlado;
|
||||
- implementar login e logout;
|
||||
- proteger as rotas.
|
||||
|
||||
### Fase 2: historico
|
||||
|
||||
- criar `Conversation` e `Message`;
|
||||
- criar lista lateral;
|
||||
- abrir, renomear, arquivar e excluir conversa;
|
||||
- validar propriedade em todas as rotas.
|
||||
|
||||
### Fase 3: consumo
|
||||
|
||||
- criar `UsageRecord`;
|
||||
- registrar tokens retornados pela OpenAI;
|
||||
- centralizar precos;
|
||||
- exibir consumo mensal;
|
||||
- bloquear chamadas acima do limite;
|
||||
- tratar requisicoes simultaneas.
|
||||
|
||||
### Fase 4: arquivos
|
||||
|
||||
- criar `Attachment`;
|
||||
- adicionar upload privado;
|
||||
- validar arquivo;
|
||||
- anexar arquivo a uma mensagem;
|
||||
- configurar exclusao e retencao.
|
||||
|
||||
### Fase 5: administracao e operacao
|
||||
|
||||
- criar painel administrativo;
|
||||
- adicionar auditoria;
|
||||
- configurar backups;
|
||||
- configurar rate limit;
|
||||
- revisar logs e alertas;
|
||||
- testar restauracao.
|
||||
|
||||
## 21. Testes necessarios
|
||||
|
||||
### Autenticacao
|
||||
|
||||
- cadastro com e-mail duplicado;
|
||||
- senha incorreta;
|
||||
- usuario bloqueado;
|
||||
- rota privada sem sessao;
|
||||
- usuario comum tentando abrir o painel administrativo.
|
||||
|
||||
### Autorizacao
|
||||
|
||||
- usuario A tentando abrir conversa do usuario B;
|
||||
- usuario A tentando baixar anexo do usuario B;
|
||||
- alteracao manual de IDs na URL e no corpo da requisicao.
|
||||
|
||||
### Consumo
|
||||
|
||||
- chamada registra tokens;
|
||||
- custo usa o modelo correto;
|
||||
- limite bloqueia nova chamada;
|
||||
- mudanca de mes reinicia a soma;
|
||||
- duas chamadas simultaneas nao ultrapassam o limite rigido;
|
||||
- erro da OpenAI nao e cobrado incorretamente pelo sistema.
|
||||
|
||||
### Upload
|
||||
|
||||
- arquivo acima do limite;
|
||||
- extensao permitida com conteudo de outro tipo;
|
||||
- nome contendo `../`;
|
||||
- arquivo vazio;
|
||||
- arquivo de outro usuario;
|
||||
- exclusao do arquivo local e do registro correspondente.
|
||||
|
||||
## 22. Melhorias futuras
|
||||
|
||||
- verificacao de e-mail;
|
||||
- recuperacao de senha;
|
||||
- autenticacao em dois fatores;
|
||||
- login corporativo;
|
||||
- streaming da resposta;
|
||||
- busca nas conversas;
|
||||
- exportacao de conversa;
|
||||
- RAG para biblioteca de documentos;
|
||||
- MinIO para armazenamento;
|
||||
- Redis para rate limit e controle de concorrencia;
|
||||
- antivirus para uploads;
|
||||
- alertas de consumo;
|
||||
- observabilidade e auditoria;
|
||||
- separacao do PostgreSQL em outro LXC.
|
||||
|
||||
## 23. O que muda em relacao ao projeto original
|
||||
|
||||
| Parte | Projeto original | Projeto v2 |
|
||||
|---|---|---|
|
||||
| Acesso | Uma senha compartilhada | Conta individual e sessao |
|
||||
| Usuarios | Nao possui | Salvos no PostgreSQL |
|
||||
| Conversas | Somente na tela | Salvas no banco |
|
||||
| Arquivos | Nao possui | Upload privado e metadados |
|
||||
| Custos | Sem controle individual | Tokens e custo por usuario |
|
||||
| Administracao | Nao possui | Usuarios, limites e consumo |
|
||||
| Seguranca | Basica | Autenticacao, autorizacao e rate limit |
|
||||
| Backup | Nao detalhado | Banco, arquivos e restauracao |
|
||||
|
||||
## 24. Referencias para estudo
|
||||
|
||||
- [OpenAI: inicio rapido da API](https://platform.openai.com/docs/quickstart)
|
||||
- [OpenAI: referencia da Responses API](https://platform.openai.com/docs/api-reference/responses)
|
||||
- [OpenAI: arquivos](https://platform.openai.com/docs/api-reference/files)
|
||||
- [OpenAI: uso da API](https://platform.openai.com/docs/api-reference/usage)
|
||||
- [OpenAI: precos da API](https://openai.com/api/pricing/)
|
||||
- [OpenAI: controles e retencao de dados](https://platform.openai.com/docs/models/default-usage-policies-by-endpoint)
|
||||
- [Next.js: Route Handlers](https://nextjs.org/docs/app/getting-started/route-handlers)
|
||||
- [Prisma: integracao com Next.js](https://www.prisma.io/docs/guides/frameworks/nextjs)
|
||||
- [Auth.js: documentacao](https://authjs.dev/)
|
||||
- [PostgreSQL: documentacao](https://www.postgresql.org/docs/)
|
||||
|
||||
## 25. Resumo
|
||||
|
||||
A evolucao principal e transformar uma pagina de chat protegida por senha em um sistema multiusuario.
|
||||
|
||||
O Next.js continua cuidando das telas e do backend. O PostgreSQL passa a guardar usuarios, conversas, mensagens, anexos e consumo. O Prisma facilita o acesso ao banco. O Auth.js cuida do login e da sessao. Os arquivos ficam em uma pasta privada, e cada resposta da OpenAI gera um registro de tokens e custo.
|
||||
|
||||
A melhor ordem de aprendizado e:
|
||||
|
||||
```text
|
||||
login -> historico -> consumo -> upload -> painel administrativo
|
||||
```
|
||||
|
||||
Essa ordem permite testar cada parte antes de adicionar a proxima e reduz a dificuldade de encontrar erros.
|
||||
Reference in New Issue
Block a user