Projeto bot pontos
This commit is contained in:
576
backend/api/openapi.yaml
Normal file
576
backend/api/openapi.yaml
Normal file
@@ -0,0 +1,576 @@
|
||||
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 }
|
||||
Reference in New Issue
Block a user