feat: a public notes page per release, for clients

On the releases page, «Página pública» (owners and admins) opens a
secret link to a page with the release's notes, readable without an
account, also for private projects: the notes written, or the draft
from its finished tickets while there are none (without repeating the
title the page already shows). The link can be copied, renewed (the old
one stops working) or withdrawn. Stored in Mongo (`release_note_shares`).

API: shareReleaseNotes / unshareReleaseNotes / releaseNotesShare,
publicReleaseNotes(token); GET/PUT/DELETE /api/releases/{id}/public-notes,
GET /api/shared/release-notes/{token}. Web: /notes/:token.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-20 20:57:40 +02:00
parent 3a46a236e4
commit 8ed16f133e
27 changed files with 2158 additions and 6 deletions

View File

@@ -13702,6 +13702,316 @@
]
}
},
"/api/releases/{release}/public-notes": {
"get": {
"operationId": "SharingController_releaseNotesShare",
"parameters": [
{
"name": "release",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseNotesShareDto"
}
}
}
},
"400": {
"description": "Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"401": {
"description": "Missing or rejected Pireon token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"403": {
"description": "Not a member of the project (or not allowed)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
}
},
"security": [
{
"pireon-oauth": []
},
{
"pireon": []
}
],
"summary": "A release's public notes link, if any (owners and admins)",
"tags": [
"Projects"
]
},
"put": {
"description": "The page shows the notes written, or the ones drawn from its finished tickets. ?renew=true makes a new link.",
"operationId": "SharingController_shareReleaseNotes",
"parameters": [
{
"name": "release",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
},
{
"name": "renew",
"required": true,
"in": "query",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ReleaseNotesShareDto"
}
}
}
},
"400": {
"description": "Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"401": {
"description": "Missing or rejected Pireon token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"403": {
"description": "Not a member of the project (or not allowed)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"409": {
"description": "Someone changed it at the same time: retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"422": {
"description": "Business rule (e.g. a transition the workflow forbids)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
}
},
"security": [
{
"pireon-oauth": []
},
{
"pireon": []
}
],
"summary": "Turn a release's public notes page on (owners and admins)",
"tags": [
"Projects"
]
},
"delete": {
"operationId": "SharingController_unshareReleaseNotes",
"parameters": [
{
"name": "release",
"required": true,
"in": "path",
"schema": {
"format": "uuid",
"type": "string"
}
}
],
"responses": {
"204": {
"description": ""
},
"400": {
"description": "Invalid input",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"401": {
"description": "Missing or rejected Pireon token",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"403": {
"description": "Not a member of the project (or not allowed)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"404": {
"description": "Not found",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"409": {
"description": "Someone changed it at the same time: retry",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
},
"422": {
"description": "Business rule (e.g. a transition the workflow forbids)",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ApiErrorDto"
}
}
}
}
},
"security": [
{
"pireon-oauth": []
},
{
"pireon": []
}
],
"summary": "A release's public notes link stops working",
"tags": [
"Projects"
]
}
},
"/api/shared/release-notes/{token}": {
"get": {
"operationId": "SharingController_publicReleaseNotes",
"parameters": [
{
"name": "token",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/PublicReleaseNotesDto"
}
}
}
}
},
"summary": "A release's notes, for anyone with its link",
"tags": [
"Projects"
],
"security": []
}
},
"/api/shared/dashboards/{token}": {
"get": {
"description": "Only the chosen blocks: tickets per status, the sprint under way, release forecasts, open epics and activity per day. The others are null.",
@@ -19804,6 +20114,81 @@
"blocks"
]
},
"ReleaseNotesShareDto": {
"type": "object",
"properties": {
"releaseId": {
"type": "string",
"format": "uuid"
},
"token": {
"type": "string",
"description": "The secret of the link: /notes/{token}"
},
"createdAt": {
"type": "string",
"format": "date-time"
}
},
"required": [
"releaseId",
"token",
"createdAt"
]
},
"PublicReleaseNotesDto": {
"type": "object",
"properties": {
"projectName": {
"type": "string"
},
"projectKey": {
"type": "string"
},
"releaseName": {
"type": "string"
},
"description": {
"type": "string",
"nullable": true
},
"status": {
"allOf": [
{
"$ref": "#/components/schemas/ReleaseStatus"
}
]
},
"targetDate": {
"type": "string",
"nullable": true
},
"releasedAt": {
"type": "string",
"nullable": true,
"format": "date-time"
},
"notes": {
"type": "string",
"description": "Markdown"
},
"written": {
"type": "boolean",
"description": "False: drawn from its finished tickets (none written yet)"
}
},
"required": [
"projectName",
"projectKey",
"releaseName",
"description",
"status",
"targetDate",
"releasedAt",
"notes",
"written"
]
},
"SharedFlowDto": {
"type": "object",
"properties": {