Public projects:
- Project.isPublic (create/update, owners and admins). Anyone can read a
public project without a token: project, board, tickets, comments,
history, releases, activity, tracking and statistics, over GraphQL and
REST. Writes stay for members (401 anonymous, 403 outsiders).
- @OptionalAuth() / @ApiOptionalPireonAuth(): anonymous callers allowed,
invalid tokens still rejected. ReadAccess lets anonymous viewers read
public projects only; linked tickets are filtered by visibility.
- publicProjects (GraphQL) and GET /api/explore/projects (REST).
- Web: «Explorar» page, project pages open without a session (read-only
notice, sign-in hints), public badge, visibility switch in settings,
no realtime subscriptions without a session.
Project deletion:
- Owner only, confirming the key (deleteProject / DELETE
/api/projects/{project}?confirm=KEY). The key is freed; the read model
drops the project and members are notified (web goes home).
Sagas (run by the EventsBridge after projections, durable and ordered):
- ProjectDeletionSaga: project deleted → delete its tickets and releases;
ticket deleted → purge its comments and drop links from other tickets.
- ReleaseArchivedSaga: unfinished tickets leave an archived release.
- UserDeactivatedSaga: the Pireon webhook records UserDeactivatedEvent in
the event store (EventRecorder); the tracker unassigns that user's open
tickets as the system actor.
- Internal idempotent commands; domain refusals are logged and skipped,
concurrency conflicts retried. Affected streams come from the event store
(TrackerStreamsRepository).
Tests: domain lifecycle specs, saga specs run like the bridge, facade
tests, e2e for public access, cascade deletion, archived releases and
deactivation webhook. Docs, READMEs, schema.gql/openapi.json and Word/PDF
updated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
439 lines
25 KiB
Markdown
439 lines
25 KiB
Markdown
# 3. API
|
|
|
|
API GraphQL en `/graphql` (HTTP para queries y mutations, WebSocket `graphql-ws` para subscriptions) más unos pocos
|
|
endpoints REST bajo `/api`. El esquema completo se genera en `apps/api/schema.gql`.
|
|
|
|
## 3.1 Modelo de dominio (contexto `tracker`)
|
|
|
|
```mermaid
|
|
classDiagram
|
|
class Project {
|
|
key: "TS"
|
|
name
|
|
members: (userId, role)[]
|
|
settings: estados · transiciones · tipos · áreas
|
|
addMember() changeMemberRole() removeMember() updateDetails() updateSettings()
|
|
}
|
|
class Issue {
|
|
key: "TS-42"
|
|
type · area · priority · status
|
|
assigneeId · releaseId · labels · figmaUrl
|
|
links: (type, target)[]
|
|
changeStatus() assign() linkTo() unlink() setRelease() updateDetails()
|
|
}
|
|
class Comment {
|
|
body · authorId
|
|
edit() delete()
|
|
}
|
|
class Release {
|
|
name · status · targetDate
|
|
update() ship() archive()
|
|
}
|
|
Project "1" --> "*" Issue
|
|
Issue "1" --> "*" Comment
|
|
Project "1" --> "*" Release
|
|
Release "0..1" <-- "*" Issue
|
|
Issue "*" --> "*" Issue : links
|
|
```
|
|
|
|
### Roles por proyecto
|
|
|
|
| Rol | Ver | Crear/modificar tickets, comentar | Gestionar miembros y releases |
|
|
| -------------------------------- | --- | --------------------------------- | -------------------------------------------- |
|
|
| `OWNER` (quien crea el proyecto) | ✅ | ✅ | ✅ (no se le puede quitar ni cambiar el rol) |
|
|
| `ADMIN` | ✅ | ✅ | ✅ |
|
|
| `MEMBER` | ✅ | ✅ | ❌ |
|
|
| `VIEWER` | ✅ | ❌ | ❌ |
|
|
|
|
Cualquiera puede salir de un proyecto (salvo el propietario). Los no miembros reciben _not found_, para no revelar que
|
|
el proyecto existe.
|
|
|
|
### Proyectos públicos
|
|
|
|
Con `isPublic` (al crear, o después con `updateProject` / `PATCH /api/projects/{project}`, propietario o admin),
|
|
**cualquiera puede leer** el proyecto, también sin token: el proyecto y sus miembros, tablero, tickets, comentarios,
|
|
historial, releases (progreso y burnup), actividad, seguimiento y estadísticas. Escribir sigue siendo cosa de los
|
|
miembros según su rol (sin token: 401; con token pero sin ser miembro: 403). Un usuario con sesión que no es miembro
|
|
lee igual que un anónimo. Las subscriptions exigen sesión; con ella, también se pueden seguir proyectos públicos.
|
|
|
|
`publicProjects` (GraphQL) y `GET /api/explore/projects` (REST, sin token) los listan. Al volver a privado, quien no es
|
|
miembro vuelve a recibir _not found_. Los enlaces a tickets de otros proyectos solo se muestran si el visitante puede
|
|
leer esos proyectos.
|
|
|
|
### Borrar un proyecto
|
|
|
|
Solo el **propietario**, escribiendo la clave del proyecto para confirmar (`deleteProject(projectId, confirmKey)` o
|
|
`DELETE /api/projects/{project}?confirm=CLAVE`). El proyecto desaparece al momento y, a continuación, una saga borra
|
|
sus tickets (y sus comentarios), sus releases y los enlaces que tickets de otros proyectos tenían hacia los suyos (ver
|
|
[Arquitectura](02-arquitectura.md)). La clave queda libre para un proyecto nuevo. Los eventos se quedan en el event
|
|
store como auditoría, pero desde la API no se puede deshacer.
|
|
|
|
### Configuración por proyecto
|
|
|
|
Cada proyecto tiene sus **estados**, **transiciones**, **tipos de ticket** y **áreas** (`Project.settings`). Todos
|
|
empiezan con los valores por defecto de abajo, y el propietario o un admin los cambia con `updateProjectSettings` (o
|
|
`PUT /api/projects/{project}/settings`); el cambio es un evento más (`ProjectSettingsChangedEvent`).
|
|
|
|
| Valor | Campos | Por defecto |
|
|
| ------ | -------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
| Estado | `id`, `name`, `category`, `color`, `requiresReason`, `archived` (el orden es el del tablero) | `BACKLOG`, `TODO`, `IN_PROGRESS`, `IN_REVIEW`, `READY_FOR_QA`, `IN_QA`, `QA_FAILED`, `DONE`, `CANCELLED` |
|
|
| Tipo | `id`, `name`, `icon`, `color`, `archived` | `BUG`, `FEATURE`, `UI_CHANGE`, `TASK`, `QA` |
|
|
| Área | `id`, `name`, `archived` | `FRONTEND`, `BACKEND`, `MOBILE`, `QA`, `DESIGN`, `DEVOPS` |
|
|
|
|
La **categoría** de un estado dice qué significa, se llame como se llame: `TODO` (pendiente), `IN_PROGRESS`, `QA`,
|
|
`DONE` y `CANCELLED`. Releases («todo terminado» = `DONE` o `CANCELLED`), «Mi trabajo», el seguimiento y las
|
|
estadísticas trabajan con categorías, así que siguen funcionando con estados propios. Los tickets guardan el id del
|
|
estado y, en el read model, su categoría (`Issue.statusCategory`).
|
|
|
|
Reglas:
|
|
|
|
- **No se borra lo que ya existe: se archiva.** Tickets, historial y estadísticas siguen apuntando a ese id. Un valor
|
|
archivado no se ofrece para tickets nuevos ni recibe transiciones; los tickets que ya lo tienen lo conservan.
|
|
- Los ids son estables (`A-Z`, `0-9`, `_`, hasta 30, empezando por letra); los nombres, de 1 a 40 caracteres, se
|
|
pueden cambiar cuando se quiera.
|
|
- Hace falta al menos un estado activo de categoría `TODO` (los tickets nuevos entran en el primero), uno `DONE` y un
|
|
tipo activo.
|
|
- Las transiciones solo pueden apuntar a estados que existen y nunca de un estado a sí mismo.
|
|
- Colores: `slate`, `stone`, `violet`, `blue`, `sky`, `teal`, `green`, `amber`, `orange`, `red`, `pink`. Iconos:
|
|
`bug`, `feature`, `palette`, `task`, `qa`, `flag`, `star-fill`, `rocket`, `document`, `light-bulb`, `key`, `support`.
|
|
|
|
Los proyectos creados antes de que existiera la configuración usan los valores por defecto (sus eventos no la
|
|
llevan). Al arrancar, la API rellena la categoría de los tickets del read model que no la tienen.
|
|
|
|
### Workflow por defecto
|
|
|
|
```mermaid
|
|
stateDiagram-v2
|
|
[*] --> BACKLOG
|
|
BACKLOG --> TODO
|
|
BACKLOG --> IN_PROGRESS
|
|
BACKLOG --> CANCELLED
|
|
TODO --> BACKLOG
|
|
TODO --> IN_PROGRESS
|
|
TODO --> CANCELLED
|
|
IN_PROGRESS --> TODO
|
|
IN_PROGRESS --> IN_REVIEW
|
|
IN_PROGRESS --> READY_FOR_QA
|
|
IN_PROGRESS --> CANCELLED
|
|
IN_REVIEW --> IN_PROGRESS
|
|
IN_REVIEW --> READY_FOR_QA
|
|
IN_REVIEW --> DONE
|
|
READY_FOR_QA --> IN_QA
|
|
READY_FOR_QA --> IN_PROGRESS
|
|
IN_QA --> DONE
|
|
IN_QA --> QA_FAILED
|
|
QA_FAILED --> IN_PROGRESS
|
|
QA_FAILED --> TODO
|
|
DONE --> TODO
|
|
DONE --> IN_PROGRESS
|
|
CANCELLED --> BACKLOG
|
|
```
|
|
|
|
- `QA_FAILED` y `CANCELLED` exigen un **motivo** (`requiresReason`), que queda en el historial.
|
|
- No se puede pasar a `DONE` saltándose QA salvo desde `IN_REVIEW` (cambios que no requieren QA).
|
|
- `Issue.allowedTransitions` devuelve los estados activos a los que puede pasar el ticket según el workflow de su
|
|
proyecto.
|
|
|
|
### Otras reglas
|
|
|
|
- **Proyecto**: clave de 2 a 10 caracteres alfanuméricos que empieza por letra y es única (los tickets se numeran
|
|
`CLAVE-1`, `CLAVE-2`…).
|
|
- **Asignación**: solo a miembros del proyecto. Para añadir miembros, el usuario debe existir y estar activo en Pireon.
|
|
- **Relaciones**: nunca consigo mismo ni duplicadas; pueden cruzar proyectos si el actor puede ver el destino. Se
|
|
muestran en los dos tickets (`OUTWARD` en el origen, `INWARD` en el destino).
|
|
- **Releases**: solo las `PLANNED` aceptan o pierden tickets; publicar con tickets sin terminar exige `force: true`.
|
|
- **Comentarios**: solo el autor edita; borra el autor o un admin del proyecto.
|
|
- **Cambios de UI**: `figmaUrl` debe ser un enlace `https://…figma.com/…`.
|
|
|
|
### Eventos de dominio
|
|
|
|
Todo cambio es un evento guardado en el event store (stream = id del agregado). Llevan `actorId`, `occurredAt`,
|
|
`aggregateId`, `projectId` y `aggregateVersion` (la posición en el stream).
|
|
|
|
| Agregado | Eventos |
|
|
| -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Project | `ProjectCreatedEvent`, `ProjectUpdatedEvent`, `ProjectSettingsChangedEvent`, `ProjectMemberAddedEvent`, `ProjectMemberRoleChangedEvent`, `ProjectMemberRemovedEvent`, `ProjectDeletedEvent` |
|
|
| Issue | `IssueCreatedEvent`, `IssueUpdatedEvent`, `IssueStatusChangedEvent`, `IssueAssignedEvent`, `IssueLinkedEvent`, `IssueUnlinkedEvent`, `IssueReleaseChangedEvent`, `IssueDeletedEvent` |
|
|
| Comment | `CommentAddedEvent`, `CommentEditedEvent`, `CommentDeletedEvent` |
|
|
| Release | `ReleaseCreatedEvent`, `ReleaseUpdatedEvent`, `ReleaseShippedEvent`, `ReleaseArchivedEvent`, `ReleaseDeletedEvent` |
|
|
|
|
`IssueStatusChangedEvent` lleva además `meta` con el nombre y la categoría de los dos estados en el momento del
|
|
cambio, para que el historial y las estadísticas no cambien si luego se renombra o recategoriza un estado.
|
|
|
|
### Estadísticas
|
|
|
|
Una proyección más (`issue_timeline` en MongoDB) guarda cada paso de cada ticket: a qué estado pasó, con qué categoría,
|
|
quién lo tenía asignado, y cuándo entró o salió de una release. Las estadísticas se calculan sobre ella con funciones
|
|
puras (`application/src/lib/analytics`):
|
|
|
|
| Métrica | Definición |
|
|
| --------------------- | ----------------------------------------------------------------------------------------------------------------- |
|
|
| Creados vs. resueltos | Por semana (lunes, UTC): tickets creados y tickets que entraron en una categoría `DONE` |
|
|
| Flujo acumulado | Tickets en cada estado al final de cada día |
|
|
| Tiempo de ciclo | Del primer paso a `IN_PROGRESS` o `QA` hasta `DONE`; media y percentil 85 |
|
|
| Tiempo de entrega | De la creación hasta `DONE`; media y percentil 85 |
|
|
| Throughput | Tickets resueltos por semana y por la persona que los tenía asignados |
|
|
| QA | Salidas de un estado `QA`: a `DONE` cuenta como superado; a cualquier otro sitio salvo `CANCELLED`, como fallido |
|
|
| Burnup de una release | Por día, desde que se creó hasta que se publica (o hoy, máx. 180 días): tickets en ella (sin cancelados) y hechos |
|
|
|
|
Los tickets anteriores a esta proyección solo tienen su creación y su estado actual; para tener su historia completa,
|
|
reconstruye el read model (ver [Despliegue](07-despliegue.md)).
|
|
|
|
## 3.2 GraphQL
|
|
|
|
Las operaciones requieren `Authorization: Bearer <token de Pireon>` (en subscriptions, en el `connection_init`), salvo
|
|
las lecturas de proyectos públicos (`project`, `issue`, `issues`, `release`, `activity`, `publicProjects` y sus campos),
|
|
que funcionan también sin token.
|
|
|
|
### Queries
|
|
|
|
| Query | Devuelve |
|
|
| ------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| `me` | El usuario autenticado, con sus roles en Pireon |
|
|
| `users(search, limit)` | Usuarios de Pireon (para asignar o añadir miembros) |
|
|
| `user(id)` | Un usuario |
|
|
| `myProjects` | Proyectos de los que soy miembro |
|
|
| `myIssues(assignedToMe, statuses, categories, limit, offset)` | «Mi trabajo»: tickets de todos mis proyectos, asignados a mí (por defecto) o de cualquiera (p. ej. `categories: [QA]`) |
|
|
| `upcomingReleases(limit)` | Releases planificadas de mis proyectos, por fecha objetivo |
|
|
| `publicProjects` | Proyectos públicos (sin token) |
|
|
| `project(idOrKey)` | Un proyecto (mío, o público) con su `settings`. Campos calculados: `board(releaseId, assigneeId, area, perColumn)`, `stats`, `analytics(days)`, `releases` |
|
|
| `issues(filter, limit, offset)` | Búsqueda de tickets (estado, tipo, área, prioridad, persona, release, etiquetas, texto) |
|
|
| `issue(idOrKey)` | Un ticket. Campos calculados: `allowedTransitions`, `linkedIssues`, `comments`, `history` |
|
|
| `release(id)` | Una release. Campos calculados: `progress`, `burnup`, `issues` |
|
|
| `activity(projectId, issueId?, limit, before?)` | Feed de actividad, del más reciente al más antiguo |
|
|
|
|
### Mutations
|
|
|
|
Todas devuelven `CommandResult { id, key, version }`.
|
|
|
|
| Ámbito | Mutations |
|
|
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
| Proyectos | `createProject`, `updateProject`, `updateProjectSettings`, `deleteProject(confirmKey)`, `addProjectMember`, `changeProjectMemberRole`, `removeProjectMember` |
|
|
| Tickets | `createIssue`, `updateIssue`, `changeIssueStatus`, `assignIssue`, `linkIssues`, `unlinkIssues`, `setIssueRelease` |
|
|
| Comentarios | `addComment`, `editComment`, `deleteComment` |
|
|
| Releases | `createRelease`, `updateRelease`, `publishRelease(force)`, `archiveRelease` |
|
|
|
|
### Subscriptions
|
|
|
|
| Subscription | Se emite cuando |
|
|
| --------------------------- | ---------------------------------------------------------------------------------------------------------- |
|
|
| `issueChanged(projectId)` | Se crea o cambia cualquier ticket del proyecto (incluye el ticket completo y `action` = nombre del evento) |
|
|
| `commentChanged(issueId)` | Se añade, edita o borra un comentario (`comment` es `null` al borrar) |
|
|
| `activityAdded(projectId)` | Hay una nueva entrada en el feed |
|
|
| `releaseChanged(projectId)` | Cambia una release |
|
|
| `projectChanged(projectId)` | Cambian los datos, la configuración o los miembros del proyecto |
|
|
| `myProjectsChanged` | Me añaden a un proyecto o me quitan de uno |
|
|
|
|
Solo los miembros pueden suscribirse a un proyecto; las conexiones sin token válido se cierran con el código 4403.
|
|
|
|
### Ejemplos
|
|
|
|
Estados, tipos y áreas son texto (ids de la configuración del proyecto), no enums: van entre comillas.
|
|
|
|
```graphql
|
|
mutation ($settings: ProjectSettingsInput!) {
|
|
# La configuración entera (la que devuelve project.settings, cambiada).
|
|
updateProjectSettings(projectId: "…", settings: $settings) {
|
|
version
|
|
}
|
|
}
|
|
|
|
mutation {
|
|
createProject(input: { key: "SHOP", name: "Checkout revamp" }) {
|
|
id
|
|
key
|
|
version
|
|
}
|
|
}
|
|
|
|
mutation {
|
|
createIssue(input: { projectId: "…", type: "BUG", title: "El botón de pagar no responde en Safari", area: "FRONTEND", priority: CRITICAL, assigneeId: "102", labels: ["safari"] }) {
|
|
id
|
|
key
|
|
version
|
|
}
|
|
}
|
|
|
|
mutation {
|
|
changeIssueStatus(input: { issueId: "…", status: "QA_FAILED", reason: "Sigue fallando en Safari 17" }) {
|
|
version
|
|
}
|
|
}
|
|
|
|
mutation {
|
|
linkIssues(input: { issueId: "<FE>", type: DEPENDS_ON, targetIssueId: "<BE>" }) {
|
|
version
|
|
}
|
|
}
|
|
|
|
query {
|
|
project(idOrKey: "SHOP") {
|
|
stats {
|
|
total
|
|
open
|
|
byStatus {
|
|
key
|
|
count
|
|
}
|
|
byAssignee {
|
|
user {
|
|
name
|
|
}
|
|
count
|
|
}
|
|
}
|
|
board {
|
|
status
|
|
total
|
|
issues {
|
|
key
|
|
title
|
|
assignee {
|
|
name
|
|
}
|
|
}
|
|
}
|
|
releases {
|
|
name
|
|
status
|
|
progress {
|
|
percent
|
|
}
|
|
}
|
|
analytics(days: 90) {
|
|
createdVsResolved {
|
|
week
|
|
created
|
|
resolved
|
|
}
|
|
cycleTime {
|
|
averageDays
|
|
p85Days
|
|
}
|
|
qa {
|
|
failureRate
|
|
}
|
|
}
|
|
}
|
|
release(id: "…") {
|
|
burnup {
|
|
dates
|
|
scope
|
|
done
|
|
}
|
|
}
|
|
issue(idOrKey: "SHOP-1") {
|
|
status
|
|
allowedTransitions
|
|
links {
|
|
type
|
|
direction
|
|
issueKey
|
|
}
|
|
comments {
|
|
items {
|
|
author {
|
|
name
|
|
}
|
|
body
|
|
}
|
|
}
|
|
history {
|
|
summary
|
|
occurredAt
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
```ts
|
|
import { createClient } from 'graphql-ws';
|
|
|
|
const client = createClient({
|
|
url: 'ws://localhost:3000/graphql',
|
|
connectionParams: { authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
client.subscribe(
|
|
{
|
|
query: 'subscription($p: ID!) { issueChanged(projectId: $p) { action version issue { key status } } }',
|
|
variables: { p: projectId },
|
|
},
|
|
{ next: console.log, error: console.error, complete: () => {} },
|
|
);
|
|
```
|
|
|
|
### Errores
|
|
|
|
Los errores de dominio llegan como errores GraphQL con `extensions.code`:
|
|
|
|
| Código | Significado |
|
|
| ----------------- | -------------------------------------------------------------- |
|
|
| `UNAUTHENTICATED` | Falta el token o Pireon lo rechaza |
|
|
| `NOT_FOUND` | No existe o no eres miembro |
|
|
| `FORBIDDEN` | No tienes permiso (p. ej. un `VIEWER` modificando) |
|
|
| `VALIDATION` | Datos inválidos (clave de proyecto, URL de Figma…) |
|
|
| `BUSINESS_RULE` | Regla de negocio (transición no permitida, release bloqueada…) |
|
|
| `CONFLICT` | Otro cambio llegó antes (concurrencia optimista): reintenta |
|
|
|
|
## 3.3 REST con OpenAPI (para otras apps)
|
|
|
|
Los mismos casos de uso que GraphQL, como API REST documentada con OpenAPI 3.0, para que otras apps de la plataforma
|
|
la consuman o generen un cliente. Son otro adaptador de entrada (`presenters/http`) que llama a los mismos
|
|
`*.service.ts`: misma validación, permisos y errores.
|
|
|
|
| | |
|
|
| ----------------- | ----------------------------------------------------------------------------------- |
|
|
| Swagger UI | `/api/docs` (con «Authorize» contra Pireon) |
|
|
| Documento OpenAPI | `/api/docs-json` (y `/api/docs-yaml`) |
|
|
| Fichero exportado | `apps/api/openapi.json`: lo reescribe `nx serve api` al arrancar, como `schema.gql` |
|
|
|
|
### Autenticación: siempre a través de Pireon
|
|
|
|
Cualquier app o usuario que la consuma pasa por el OAuth de Pireon: todas las rutas, salvo `health`, `auth`, el
|
|
webhook, `explore` y las lecturas de proyectos públicos, exigen `Authorization: Bearer <token de Pireon>` (el
|
|
`AuthGuard` global, igual que en GraphQL). En el documento OpenAPI, las rutas donde el token es opcional lo indican con
|
|
un requisito de seguridad vacío (`{}`). Declara dos esquemas:
|
|
|
|
- **`pireon-oauth`** (OAuth2): _Authorization Code + PKCE_ contra `oauth.pireon.dev/oauth/authorize` y, si
|
|
`PIREON_PASSWORD_GRANT=true`, _password_. El intercambio de tokens va por `POST /api/auth/token` (Pireon no tiene
|
|
CORS en `/oauth/token` y el `client_secret` se queda en el servidor). Para usar «Authorize» en Swagger UI, registra
|
|
`<api>/api/docs/oauth2-redirect.html` como redirect URI del cliente en Pireon.
|
|
- **`pireon`** (Bearer): para una app que ya tiene un token de Pireon, por ejemplo con su propio cliente OAuth.
|
|
|
|
Lo que cada usuario puede ver o cambiar depende de su rol en cada proyecto, como en la web.
|
|
|
|
### Recursos
|
|
|
|
Proyectos y tickets se pueden indicar por **id o por clave** (`WEB`, `WEB-12`).
|
|
|
|
| Recurso | Rutas |
|
|
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
| Usuarios | `GET /api/users/me`, `GET /api/users?search=`, `GET /api/users/{id}` |
|
|
| Proyectos | `GET/POST /api/projects`, `GET/PATCH/DELETE /api/projects/{project}` (`DELETE …?confirm=CLAVE`), `PUT …/settings`, `…/board`, `…/stats`, `…/analytics?days=`, `…/activity`, `…/members` (POST, PATCH, DELETE) |
|
|
| Tickets | `GET /api/me/issues` (mi trabajo), `GET/POST /api/projects/{project}/issues`, `GET/PATCH /api/issues/{issue}`, `PUT …/status`, `PUT …/assignee`, `PUT …/release`, `POST/DELETE …/links`, `GET/POST …/comments`, `GET …/history` |
|
|
| Comentarios | `PATCH/DELETE /api/comments/{commentId}` |
|
|
| Releases | `GET /api/me/releases` (próximas), `GET/POST /api/projects/{project}/releases`, `GET/PATCH /api/releases/{id}`, `…/progress`, `…/burnup`, `…/issues`, `POST …/publish`, `POST …/archive` |
|
|
| Explorar | `GET /api/explore/projects` (proyectos públicos, sin token) |
|
|
| Sistema | `GET /api/health`, `GET /api/auth/config`, `POST /api/auth/token`, `POST /api/webhooks/pireon` |
|
|
|
|
Los filtros de tickets aceptan `status` (ids del proyecto) y `category` (`?category=QA`), repetidos o separados por
|
|
comas. Las escrituras responden `{ id, key?, version }` en cuanto el evento se guarda; las lecturas salen del read model, que
|
|
se pone al día en milisegundos (un `GET` justo después puede devolver la versión anterior, o 404 tras crear). Para
|
|
tiempo real, las subscriptions de GraphQL.
|
|
|
|
### Errores
|
|
|
|
`{ statusCode, code?, message }`: `400` entrada no válida (`message` es una lista), `401` sin token o token rechazado
|
|
por Pireon, `403`/`404` sin acceso, `409` cambio concurrente (reintentar) y `422` regla de negocio (p. ej. una
|
|
transición que el workflow no permite).
|
|
|
|
### Generar un cliente en otra app
|
|
|
|
```sh
|
|
npx openapi-typescript apps/api/openapi.json -o src/task-stream-api.d.ts # tipos
|
|
# o un cliente completo: openapi-generator, orval, ng-openapi-gen…
|
|
```
|
|
|
|
Ver también [Autenticación](05-autenticacion.md).
|