Files
bot-pontos/backend/api/openapi.yaml
2026-10-23 22:31:02 -03:00

577 lines
19 KiB
YAML

openapi: 3.1.0
info:
title: Bot de Pontos API
version: 1.0.0
description: |
API REST consumida pelo painel Next.js (ARQUITETURA.md, seção 7).
Autenticação: cookie de sessão `bp_session` (HttpOnly). Toda requisição que
altera dados exige também o header `X-CSRF-Token` com o valor do cookie
`bp_csrf` e um `Origin` igual a BP_PUBLIC_URL.
`x-min-role` indica o papel mínimo: viewer < admin < owner.
Erros seguem application/problem+json (RFC 9457).
servers:
- url: /api/v1
components:
securitySchemes:
session:
type: apiKey
in: cookie
name: bp_session
responses:
Problem:
description: Erro
content:
application/problem+json:
schema: { $ref: "#/components/schemas/Problem" }
UserDetail:
description: Usuário atualizado
content:
application/json:
schema: { $ref: "#/components/schemas/UserDetail" }
parameters:
id:
name: id
in: path
required: true
schema: { type: integer, format: int64 }
schemas:
Problem:
type: object
required: [type, title, status]
properties:
type: { type: string, example: about:blank }
title: { type: string }
status: { type: integer }
code:
type: string
description: |
Código estável: VALIDATION, NOT_FOUND, UNAUTHENTICATED, FORBIDDEN, BAD_ORIGIN, BAD_CSRF,
INVALID_LOGIN, ACCOUNT_LOCKED, CREDENTIALS_REQUIRED, CREDENTIALS_REJECTED, POINTS_UNAVAILABLE,
USER_NOT_ACTIVE, INVALID_STATE, LAST_OWNER, NOT_CONFIGURED, DB_UNAVAILABLE, INTERNAL
errors:
type: array
items:
type: object
properties:
field: { type: string }
message: { type: string }
Account:
type: object
properties:
id: { type: integer }
username: { type: string }
role: { type: string, enum: [owner, admin, viewer] }
user_id: { type: [integer, "null"] }
disabled: { type: boolean }
locked_until: { type: [string, "null"], format: date-time }
last_login_at: { type: [string, "null"], format: date-time }
created_at: { type: string, format: date-time }
User:
type: object
properties:
id: { type: integer }
telegram_chat_id: { type: integer }
display_name: { type: string }
status: { type: string, enum: [pending, active, disabled] }
is_bot_admin: { type: boolean }
notifications_enabled: { type: boolean }
paused_reason: { type: [string, "null"], enum: [manual, api_failures, admin, user_disabled, null] }
timezone: { type: string }
created_at: { type: string, format: date-time }
UserSummary:
allOf:
- $ref: "#/components/schemas/User"
- type: object
properties:
has_credential: { type: boolean }
credential_validated: { type: boolean }
consecutive_failures: { type: integer }
custom_window: { type: boolean }
QueryWindow:
type: object
required: [query_window_start, query_window_end, day_check_deadline, no_points_recheck_until]
properties:
query_window_start: { type: string, example: "11:30" }
query_window_end: { type: string, example: "19:00" }
day_check_deadline: { type: string, example: "11:45" }
no_points_recheck_until: { type: string, example: "15:00" }
Schedule:
type: object
properties:
lunch_minutes: { type: integer, minimum: 15, maximum: 180 }
daily_work_minutes: { type: integer, minimum: 60, maximum: 720 }
entrada2_lead_minutes: { type: integer, minimum: 0, maximum: 60 }
saida2_lead_minutes: { type: integer, minimum: 0, maximum: 60 }
window:
description: null = usa a janela global
oneOf:
- $ref: "#/components/schemas/QueryWindow"
- type: "null"
Day:
type: object
properties:
day: { type: string, format: date }
status: { type: string, enum: [awaiting, no_points, working, not_working, finished] }
zero_point_checks: { type: integer }
last_point_count: { type: integer }
next_check_at: { type: [string, "null"], format: date-time }
resumed_by: { type: [string, "null"] }
UserDetail:
type: object
properties:
user: { $ref: "#/components/schemas/User" }
schedule: { $ref: "#/components/schemas/Schedule" }
credential:
type: [object, "null"]
description: A senha nunca é devolvida.
properties:
api_email: { type: string }
validated: { type: boolean }
last_validated_at: { type: [string, "null"], format: date-time }
last_error: { type: [string, "null"] }
consecutive_failures: { type: integer }
last_failure_at: { type: [string, "null"], format: date-time }
updated_at: { type: string, format: date-time }
today: { $ref: "#/components/schemas/Day" }
effective_window: { $ref: "#/components/schemas/QueryWindow" }
custom_window: { type: boolean }
Today:
type: object
properties:
day: { type: string, format: date }
timezone: { type: string }
points: { type: array, items: { type: string, format: date-time } }
invalid: { type: boolean }
overtime: { type: boolean }
entrada2_at: { type: [string, "null"], format: date-time }
saida2_at: { type: [string, "null"], format: date-time }
saida2_error: { type: string }
day_state: { $ref: "#/components/schemas/Day" }
window: { $ref: "#/components/schemas/QueryWindow" }
custom_window: { type: boolean }
Notification:
type: object
properties:
id: { type: integer }
user_id: { type: integer }
user_name: { type: string }
reference_date: { type: string, format: date }
type: { type: string, enum: [entrada2, saida2] }
scheduled_at: { type: string, format: date-time }
predicted_at: { type: string, format: date-time }
status: { type: string, enum: [scheduled, sending, sent, failed, cancelled, expired] }
attempts: { type: integer }
sent_at: { type: [string, "null"], format: date-time }
error_message: { type: [string, "null"] }
AuditLog:
type: object
properties:
id: { type: integer }
occurred_at: { type: string, format: date-time }
source: { type: string, enum: [telegram, web, worker, cli] }
actor_user_id: { type: [integer, "null"] }
actor_user_name: { type: [string, "null"] }
actor_account: { type: [integer, "null"] }
actor_username: { type: [string, "null"] }
target_user_id: { type: [integer, "null"] }
target_user_name: { type: [string, "null"] }
event_type: { type: string }
success: { type: boolean }
details: { type: object }
Holiday:
type: object
properties:
day: { type: string, format: date }
name: { type: string }
source: { type: string, enum: [feriadosapi, feriados.dev, manual] }
security:
- session: []
paths:
/health/live:
get:
summary: Processo da API está de pé
security: []
responses:
"200": { description: OK }
/health/ready:
get:
summary: API pronta (Postgres respondendo)
security: []
responses:
"200": { description: OK }
"503": { $ref: "#/components/responses/Problem" }
/auth/login:
post:
summary: Login (Argon2id; 5 falhas bloqueiam por 15 min)
security: []
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, password]
properties:
username: { type: string }
password: { type: string }
responses:
"200": { description: Sessão criada; define os cookies bp_session e bp_csrf }
"401": { $ref: "#/components/responses/Problem" }
"423": { $ref: "#/components/responses/Problem" }
/auth/logout:
post:
summary: Revoga a sessão e limpa os cookies
x-min-role: viewer
responses:
"204": { description: OK }
/auth/me:
get:
summary: Conta logada
x-min-role: viewer
responses:
"200": { description: OK }
/dashboard:
get:
summary: Contadores, queda da PontoMais e fallback de feriados
x-min-role: viewer
responses:
"200": { description: OK }
/users:
get:
summary: Lista de usuários
x-min-role: viewer
parameters:
- { name: status, in: query, schema: { type: string, enum: [pending, active, disabled] } }
- { name: q, in: query, schema: { type: string }, description: Nome ou chat ID }
- { name: custom_window, in: query, schema: { type: boolean } }
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
users: { type: array, items: { $ref: "#/components/schemas/UserSummary" } }
/users/{id}:
parameters: [{ $ref: "#/components/parameters/id" }]
get:
summary: Detalhe do usuário
x-min-role: viewer
responses:
"200": { $ref: "#/components/responses/UserDetail" }
"404": { $ref: "#/components/responses/Problem" }
/users/{id}/activate:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Ativa (exige credencial validada — mesma regra do /ativarusuario)
x-min-role: admin
responses:
"200": { $ref: "#/components/responses/UserDetail" }
"409": { $ref: "#/components/responses/Problem" }
/users/{id}/disable:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Desativa e cancela os lembretes agendados
x-min-role: admin
responses:
"200": { $ref: "#/components/responses/UserDetail" }
/users/{id}/schedule:
parameters: [{ $ref: "#/components/parameters/id" }]
put:
summary: Jornada, fuso e exceção de janela (9.8, 9.14)
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema:
allOf:
- $ref: "#/components/schemas/Schedule"
- type: object
properties:
timezone: { type: string }
responses:
"200": { $ref: "#/components/responses/UserDetail" }
"422": { $ref: "#/components/responses/Problem" }
/users/{id}/credentials:
parameters: [{ $ref: "#/components/parameters/id" }]
put:
summary: Valida na PontoMais e grava cifrada; em falha a anterior é mantida
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [email, password]
properties:
email: { type: string }
password: { type: string }
responses:
"200": { $ref: "#/components/responses/UserDetail" }
"422": { $ref: "#/components/responses/Problem" }
"502": { $ref: "#/components/responses/Problem" }
/users/{id}/credentials/test:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Consulta a PontoMais com a credencial atual
x-min-role: admin
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/Today" }
/users/{id}/notifications/enable:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Liga os lembretes, zera falhas e retoma o dia se estava sem pontos
x-min-role: admin
responses:
"200": { $ref: "#/components/responses/UserDetail" }
/users/{id}/notifications/disable:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Pausa os lembretes e cancela os agendados
x-min-role: admin
responses:
"200": { $ref: "#/components/responses/UserDetail" }
/users/{id}/bot-admin:
parameters: [{ $ref: "#/components/parameters/id" }]
put:
summary: Liga ou desliga o admin do bot no Telegram
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
enabled: { type: boolean }
responses:
"200": { $ref: "#/components/responses/UserDetail" }
/users/{id}/today:
parameters: [{ $ref: "#/components/parameters/id" }]
get:
summary: Consulta ao vivo dos pontos e previsões do dia
x-min-role: viewer
responses:
"200":
description: OK
content:
application/json:
schema: { $ref: "#/components/schemas/Today" }
"502": { $ref: "#/components/responses/Problem" }
/users/{id}/today/resume:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Retoma um dia sem pontos (9.12)
x-min-role: admin
responses:
"200": { $ref: "#/components/responses/UserDetail" }
"409": { $ref: "#/components/responses/Problem" }
/notifications:
get:
summary: Lembretes (até 200)
x-min-role: viewer
parameters:
- { name: day, in: query, schema: { type: string, format: date } }
- { name: status, in: query, schema: { type: string } }
- { name: type, in: query, schema: { type: string, enum: [entrada2, saida2] } }
- { name: user_id, in: query, schema: { type: integer } }
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
notifications: { type: array, items: { $ref: "#/components/schemas/Notification" } }
/notifications/{id}/cancel:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Cancela (só scheduled)
x-min-role: admin
responses:
"200": { description: OK }
"409": { $ref: "#/components/responses/Problem" }
/notifications/{id}/resend:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Reenvio manual (só failed do dia atual, 9.5)
x-min-role: admin
responses:
"200": { description: OK }
"409": { $ref: "#/components/responses/Problem" }
/audit:
get:
summary: Auditoria, paginada por cursor (next_before)
x-min-role: viewer
parameters:
- { name: from, in: query, schema: { type: string, format: date } }
- { name: to, in: query, schema: { type: string, format: date } }
- { name: source, in: query, schema: { type: string } }
- { name: event_type, in: query, schema: { type: string } }
- { name: success, in: query, schema: { type: boolean } }
- { name: target_user_id, in: query, schema: { type: integer } }
- { name: actor_account, in: query, schema: { type: integer } }
- { name: before, in: query, schema: { type: integer } }
responses:
"200":
description: OK
content:
application/json:
schema:
type: object
properties:
logs: { type: array, items: { $ref: "#/components/schemas/AuditLog" } }
next_before: { type: [integer, "null"] }
/holidays:
get:
summary: Feriados do ano
x-min-role: viewer
parameters:
- { name: year, in: query, schema: { type: integer } }
responses:
"200": { description: OK }
post:
summary: Cadastra feriado manual
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/Holiday" }
responses:
"201": { description: Criado }
"422": { $ref: "#/components/responses/Problem" }
/holidays/sync:
post:
summary: Sincroniza o ano com a feriadosapi.com
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
year: { type: integer }
responses:
"200": { description: OK }
"503": { $ref: "#/components/responses/Problem" }
/holidays/{day}:
delete:
summary: Remove feriado manual
x-min-role: admin
parameters:
- { name: day, in: path, required: true, schema: { type: string, format: date } }
responses:
"204": { description: OK }
/settings:
get:
summary: Janela global e parâmetros do servidor
x-min-role: viewer
responses:
"200": { description: OK }
put:
summary: Altera a janela global (9.13)
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/QueryWindow" }
responses:
"200": { description: OK }
"422": { $ref: "#/components/responses/Problem" }
/settings/preview:
post:
summary: Efeito estimado de uma nova janela global
x-min-role: admin
requestBody:
required: true
content:
application/json:
schema: { $ref: "#/components/schemas/QueryWindow" }
responses:
"200": { description: OK }
/accounts:
get:
summary: Contas do painel
x-min-role: owner
responses:
"200": { description: OK }
post:
summary: Cria conta
x-min-role: owner
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [username, password, role]
properties:
username: { type: string }
password: { type: string, minLength: 12 }
role: { type: string, enum: [owner, admin, viewer] }
user_id: { type: [integer, "null"] }
responses:
"201":
description: Criada
content:
application/json:
schema: { $ref: "#/components/schemas/Account" }
/accounts/{id}:
parameters: [{ $ref: "#/components/parameters/id" }]
patch:
summary: Altera papel, desativa e/ou troca o usuário ligado (mantém pelo menos um owner ativo)
x-min-role: owner
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
role: { type: string, enum: [owner, admin, viewer] }
disabled: { type: boolean }
user_id: { type: [integer, "null"], description: "Usuário do Telegram ligado à conta; null remove o vínculo" }
responses:
"200": { description: OK }
"409": { $ref: "#/components/responses/Problem" }
/accounts/{id}/reset-password:
parameters: [{ $ref: "#/components/parameters/id" }]
post:
summary: Redefine a senha e encerra as sessões da conta
x-min-role: owner
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [password]
properties:
password: { type: string, minLength: 12 }
responses:
"204": { description: OK }