feat(api): add tags to resource creation

Normalize tag names before attaching them, reject names that are too short
after sanitization, and return 404 when removing tags not attached to the
resource.

Adds a per-team unique tag-name index and migrates duplicate tags onto the
kept record before creating the constraint.
This commit is contained in:
Andras Bacsai committed 2026-07-07 13:56:33 +02:00
1 parent f617e58401
commit 11b35ba3c1
12 files changed
+1466 -80

No files matched your search

+439
View File
@@ -293,6 +293,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the application.'
is_preserve_repository_enabled:
type: boolean
default: false
@@ -583,6 +587,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the application.'
is_preserve_repository_enabled:
type: boolean
default: false
@@ -873,6 +881,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the application.'
is_preserve_repository_enabled:
type: boolean
default: false
@@ -1104,6 +1116,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the application.'
type: object
responses:
'201':
@@ -1321,6 +1337,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. So if you write $ in the labels, it will be saved as $$. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the application.'
type: object
responses:
'201':
@@ -2376,6 +2396,121 @@ paths:
security:
-
bearerAuth: []
'/applications/{uuid}/tags':
get:
tags:
- Applications
summary: 'List Tags'
description: 'List tags for an application by UUID.'
operationId: list-tags-by-application-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
responses:
'200':
description: 'List of tags.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
post:
tags:
- Applications
summary: 'Create Tag'
description: 'Add tag(s) to an application by UUID.'
operationId: create-tag-by-application-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
properties:
tag_name:
type: string
description: 'The tag name (min 2 characters). Required if tag_names is not provided.'
tag_names:
type: array
items: { type: string }
description: 'Array of tag names (each min 2 characters). Required if tag_name is not provided.'
type: object
responses:
'201':
description: 'Tags added successfully.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/applications/{uuid}/tags/{tag_uuid}':
delete:
tags:
- Applications
summary: 'Delete Tag'
description: 'Remove a tag from an application by UUID.'
operationId: delete-tag-by-application-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the application.'
required: true
schema:
type: string
-
name: tag_uuid
in: path
description: 'UUID of the tag.'
required: true
schema:
type: string
responses:
'200':
description: 'Tag removed.'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/cloud-tokens:
get:
tags:
@@ -3244,6 +3379,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3339,6 +3478,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3431,6 +3574,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3526,6 +3673,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3621,6 +3772,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3725,6 +3880,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3829,6 +3988,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -3924,6 +4087,10 @@ paths:
instant_deploy:
type: boolean
description: 'Instant deploy the database'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the database.'
type: object
responses:
'200':
@@ -4636,6 +4803,121 @@ paths:
security:
-
bearerAuth: []
'/databases/{uuid}/tags':
get:
tags:
- Databases
summary: 'List Tags'
description: 'List tags for a database by UUID.'
operationId: list-tags-by-database-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the database.'
required: true
schema:
type: string
responses:
'200':
description: 'List of tags.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
post:
tags:
- Databases
summary: 'Create Tag'
description: 'Add tag(s) to a database by UUID.'
operationId: create-tag-by-database-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the database.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
properties:
tag_name:
type: string
description: 'The tag name (min 2 characters). Required if tag_names is not provided.'
tag_names:
type: array
items: { type: string }
description: 'Array of tag names (each min 2 characters). Required if tag_name is not provided.'
type: object
responses:
'201':
description: 'Tags added successfully.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/databases/{uuid}/tags/{tag_uuid}':
delete:
tags:
- Databases
summary: 'Delete Tag'
description: 'Remove a tag from a database by UUID.'
operationId: delete-tag-by-database-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the database.'
required: true
schema:
type: string
-
name: tag_uuid
in: path
description: 'UUID of the tag.'
required: true
schema:
type: string
responses:
'200':
description: 'Tag removed.'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/deployments:
get:
tags:
@@ -7008,6 +7290,10 @@ paths:
type: boolean
default: true
description: 'Escape special characters in labels. By default, $ (and other chars) is escaped. If you want to use env variables inside the labels, turn this off.'
tags:
type: array
items: { type: string }
description: 'Tags to assign to the service.'
type: object
responses:
'201':
@@ -7860,6 +8146,144 @@ paths:
security:
-
bearerAuth: []
'/services/{uuid}/tags':
get:
tags:
- Services
summary: 'List Tags'
description: 'List tags for a service by UUID.'
operationId: list-tags-by-service-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the service.'
required: true
schema:
type: string
responses:
'200':
description: 'List of tags.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
post:
tags:
- Services
summary: 'Create Tag'
description: 'Add tag(s) to a service by UUID.'
operationId: create-tag-by-service-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the service.'
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
properties:
tag_name:
type: string
description: 'The tag name (min 2 characters). Required if tag_names is not provided.'
tag_names:
type: array
items: { type: string }
description: 'Array of tag names (each min 2 characters). Required if tag_name is not provided.'
type: object
responses:
'201':
description: 'Tags added successfully.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
'422':
$ref: '#/components/responses/422'
security:
-
bearerAuth: []
'/services/{uuid}/tags/{tag_uuid}':
delete:
tags:
- Services
summary: 'Delete Tag'
description: 'Remove a tag from a service by UUID.'
operationId: delete-tag-by-service-uuid
parameters:
-
name: uuid
in: path
description: 'UUID of the service.'
required: true
schema:
type: string
-
name: tag_uuid
in: path
description: 'UUID of the tag.'
required: true
schema:
type: string
responses:
'200':
description: 'Tag removed.'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
'404':
$ref: '#/components/responses/404'
security:
-
bearerAuth: []
/tags:
get:
tags:
- Tags
summary: List
description: 'List all tags for the current team.'
operationId: list-tags
responses:
'200':
description: 'All tags for the current team.'
content:
application/json:
schema:
type: array
items:
$ref: '#/components/schemas/Tag'
'401':
$ref: '#/components/responses/401'
'400':
$ref: '#/components/responses/400'
security:
-
bearerAuth: []
/teams:
get:
tags:
@@ -8740,6 +9164,18 @@ components:
type: string
description: 'The date and time when the service was deleted.'
type: object
Tag:
description: 'Tag model'
properties:
uuid:
type: string
name:
type: string
created_at:
type: string
updated_at:
type: string
type: object
Team:
description: 'Team model'
properties:
@@ -8911,6 +9347,9 @@ tags:
-
name: Services
description: Services
-
name: Tags
description: Tags
-
name: Teams
description: Teams