Files
opencode 8f2fd89847
Build & Push Docker Image / build (push) Successful in 2m49s
feat(import): bulk tool import (CSV/JSON/YAML) for admins; NetBox-style help button
- Add POST /admin/tools/import with format auto-detect, CSV delimiters
  (comma/semicolon/tab), per-row validation via CreateToolBody, bulk insert,
  audit log entries per imported tool; gated by new 'tool-import' feature
  flag (premium/enterprise; admins always pass)
- Add tool-import-dialog UI (format tabs, delimiter select, textarea, file
  upload, result/error list) behind hasFeature('tool-import')
- Replace FieldHelp question marks and bare GuideHelp links with a NetBox-style
  'Hilfe/Help' outline button (HelpCircle + text) in form headers only
- Sync locales to 482 keys per language (de/en), update handbook docs
  (administration import section, index/plaene feature tables), regenerate
  API client + zod schemas, add yaml dependency
2026-08-04 23:09:11 +02:00

1445 lines
36 KiB
YAML

openapi: 3.1.0
info:
# Do not change the title, if the title changes, the import paths will be broken
title: Api
version: 0.1.0
description: ToolRate API — Tool listing and rating platform
servers:
- url: /api
description: Base API path
tags:
- name: health
description: Health operations
- name: tools
description: Tool management
- name: ratings
description: Tool ratings
- name: analytics
description: Analytics and aggregated statistics
- name: auth
description: Authentication
- name: users
description: User management (admin only)
- name: audit
description: Audit log
paths:
/healthz:
get:
operationId: healthCheck
tags: [health]
summary: Health check
description: Returns server health status
responses:
"200":
description: Healthy
content:
application/json:
schema:
$ref: "#/components/schemas/HealthStatus"
/version:
get:
operationId: getVersion
tags: [health]
summary: Build version information
description: Returns the running build version, commit SHA and build date
responses:
"200":
description: Version information
content:
application/json:
schema:
$ref: "#/components/schemas/VersionInfo"
/tools:
get:
operationId: listTools
tags: [tools]
summary: List all tools
parameters:
- name: category
in: query
required: false
schema:
type: string
- name: search
in: query
required: false
schema:
type: string
- name: sort
in: query
required: false
schema:
type: string
enum: [newest, top_rated, most_reviewed, name_asc, name_desc, recently_updated]
- name: tags
in: query
required: false
description: Comma-separated tags; tool must include all of them
schema:
type: string
- name: features
in: query
required: false
description: Comma-separated features; tool must include all of them
schema:
type: string
- name: minRating
in: query
required: false
description: Minimum average combined rating (0-5)
schema:
type: number
minimum: 0
maximum: 5
responses:
"200":
description: List of tools
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ToolWithStats"
post:
operationId: createTool
tags: [tools]
summary: Create a new tool
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ToolInput"
responses:
"201":
description: Created tool
content:
application/json:
schema:
$ref: "#/components/schemas/Tool"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/compare:
get:
operationId: listCompareTools
tags: [tools]
summary: Compare tools side by side (premium)
parameters:
- name: ids
in: query
required: true
schema:
type: string
description: Comma-separated tool ids
responses:
"200":
description: Tools in requested order
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ToolWithStats"
"401":
description: Authentication required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Premium feature required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/{id}/rating-history:
get:
operationId: getToolRatingHistory
tags: [tools]
summary: Get a tool's rating history over time
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Rating history
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/RatingHistoryItem"
"400":
description: Invalid id
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/{id}:
get:
operationId: getTool
tags: [tools]
summary: Get a tool by ID
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Tool details
content:
application/json:
schema:
$ref: "#/components/schemas/ToolWithStats"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
patch:
operationId: updateTool
tags: [tools]
summary: Update a tool
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ToolUpdate"
responses:
"200":
description: Updated tool
content:
application/json:
schema:
$ref: "#/components/schemas/Tool"
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
delete:
operationId: deleteTool
tags: [tools]
summary: Delete a tool
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
"404":
description: Not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash:
get:
operationId: listTrashedTools
tags: [tools]
summary: List trashed (soft-deleted) tools
parameters:
- name: search
in: query
required: false
schema:
type: string
responses:
"200":
description: List of trashed tools
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Tool"
"403":
description: Feature "trash" requires a higher tier
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
post:
operationId: trashTools
tags: [tools]
summary: Move tools to trash (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"200":
description: Tools trashed
content:
application/json:
schema:
type: object
properties:
trashed:
type: integer
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
delete:
operationId: deleteTrashedTools
tags: [tools]
summary: Permanently delete trashed tools (admin)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"204":
description: Deleted
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash/restore:
post:
operationId: restoreTools
tags: [tools]
summary: Restore trashed tools
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/TrashToolsInput"
responses:
"200":
description: Tools restored
content:
application/json:
schema:
type: object
properties:
restored:
type: integer
"403":
description: Feature "trash" requires a higher tier
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/trash/empty:
post:
operationId: emptyTrash
tags: [tools]
summary: Permanently delete all trashed tools (admin)
responses:
"200":
description: Trash emptied
content:
application/json:
schema:
type: object
properties:
deleted:
type: integer
"403":
description: Admin required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/tools/{id}/ratings:
get:
operationId: listToolRatings
tags: [ratings]
summary: List ratings for a tool
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: Ratings list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/Rating"
post:
operationId: createRating
tags: [ratings]
summary: Submit a rating for a tool
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/RatingInput"
responses:
"201":
description: Created rating
content:
application/json:
schema:
$ref: "#/components/schemas/Rating"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: Tool not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/analytics/summary:
get:
operationId: getAnalyticsSummary
tags: [analytics]
summary: Overall platform statistics
responses:
"200":
description: Platform-level summary stats
content:
application/json:
schema:
$ref: "#/components/schemas/AnalyticsSummary"
/analytics/top-tools:
get:
operationId: getTopTools
tags: [analytics]
summary: Top-rated tools
parameters:
- name: limit
in: query
required: false
schema:
type: integer
- name: metric
in: query
required: false
schema:
type: string
enum: [usefulness, usability, combined]
responses:
"200":
description: Top tools list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/TopToolEntry"
/analytics/by-category:
get:
operationId: getAnalyticsByCategory
tags: [analytics]
summary: Rating statistics grouped by category
responses:
"200":
description: Per-category statistics
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/CategoryStats"
/analytics/rating-distribution:
get:
operationId: getRatingDistribution
tags: [analytics]
summary: Distribution of rating scores across the platform
parameters:
- name: toolId
in: query
required: false
schema:
type: integer
responses:
"200":
description: Rating score distribution
content:
application/json:
schema:
$ref: "#/components/schemas/RatingDistribution"
/categories:
get:
operationId: listCategories
tags: [tools]
summary: List all distinct tool categories
responses:
"200":
description: Categories list
content:
application/json:
schema:
type: array
items:
type: string
/features/all:
get:
operationId: listAllFeatures
tags: [tools]
summary: List all distinct feature strings across all tools
responses:
"200":
description: All known features
content:
application/json:
schema:
type: array
items:
type: string
/tags/all:
get:
operationId: listAllTags
tags: [tools]
summary: List all distinct tag strings across all tools
responses:
"200":
description: All known tags
content:
application/json:
schema:
type: array
items:
type: string
/auth/mode:
get:
operationId: getAuthMode
tags: [auth]
summary: Get authentication mode (oidc or local)
responses:
"200":
description: Auth mode
content:
application/json:
schema:
$ref: "#/components/schemas/AuthMode"
/auth/csrf:
get:
operationId: getCsrfToken
tags: [auth]
summary: Get a CSRF token for state-changing requests
responses:
"200":
description: CSRF token
content:
application/json:
schema:
$ref: "#/components/schemas/CsrfToken"
/auth/login:
post:
operationId: localLogin
tags: [auth]
summary: Local username/password login
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/LocalLoginInput"
responses:
"200":
description: Logged in successfully
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUser"
"401":
description: Invalid credentials
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/me:
get:
operationId: getMe
tags: [auth]
summary: Get current authenticated user
responses:
"200":
description: Current user info
content:
application/json:
schema:
$ref: "#/components/schemas/AuthUser"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/me/password:
post:
operationId: changeMyPassword
tags: [auth]
summary: Change own password (local users only)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ChangePasswordInput"
responses:
"204":
description: Password changed
"400":
description: Invalid input or wrong current password
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"422":
description: OIDC user - password is managed by the identity provider
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"429":
description: Too many attempts
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/password-redirect:
get:
operationId: getPasswordRedirect
tags: [auth]
summary: Get redirect URL for managing credentials in the identity provider
responses:
"200":
description: Redirect URL (null in local mode)
content:
application/json:
schema:
$ref: "#/components/schemas/PasswordRedirect"
/auth/me/preferences:
get:
operationId: getMePreferences
tags: [auth]
summary: Get current user's browse preferences
responses:
"200":
description: User preferences
content:
application/json:
schema:
$ref: "#/components/schemas/UserPreferences"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
put:
operationId: updateMePreferences
tags: [auth]
summary: Update current user's browse preferences
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserPreferences"
responses:
"200":
description: Updated preferences
content:
application/json:
schema:
$ref: "#/components/schemas/UserPreferences"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/auth/me/watchlist:
get:
operationId: getMeWatchlist
tags: [auth]
summary: Get current user's watchlist tools (premium)
responses:
"200":
description: Watchlist tools in saved order
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/ToolWithStats"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Premium feature required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/users:
get:
operationId: listUsers
tags: [users]
summary: List all local users (admin only)
responses:
"200":
description: User list
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/User"
"401":
description: Not authenticated
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Forbidden
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
post:
operationId: createUser
tags: [users]
summary: Create a new local user (admin only)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserCreateInput"
responses:
"201":
description: Created user
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"409":
description: Username already exists
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/users/{id}:
patch:
operationId: updateUser
tags: [users]
summary: Update user role (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/UserRoleUpdate"
responses:
"200":
description: Updated user
content:
application/json:
schema:
$ref: "#/components/schemas/User"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
delete:
operationId: deleteUser
tags: [users]
summary: Delete a user (admin only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"204":
description: Deleted
/users/{id}/password:
patch:
operationId: setUserPassword
tags: [users]
summary: Set/reset a user's password (admin only, local users only)
parameters:
- name: id
in: path
required: true
schema:
type: integer
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/SetPasswordInput"
responses:
"204":
description: Password updated
"400":
description: Validation error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"404":
description: User not found
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"422":
description: OIDC user - password is managed by the identity provider
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"429":
description: Too many attempts
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
/audit-logs:
get:
operationId: listAuditLogs
tags: [audit]
summary: List audit log entries (admin only)
parameters:
- name: entityType
in: query
required: false
schema:
type: string
- name: entityId
in: query
required: false
schema:
type: integer
- name: limit
in: query
required: false
schema:
type: integer
responses:
"200":
description: Audit log entries
content:
application/json:
schema:
type: array
items:
$ref: "#/components/schemas/AuditLog"
/admin/tools/import:
post:
operationId: importTools
tags: [admin]
summary: Import tools in bulk (premium feature, admin only)
requestBody:
required: true
content:
application/json:
schema:
$ref: "#/components/schemas/ToolImportBody"
responses:
"200":
description: Import result
content:
application/json:
schema:
$ref: "#/components/schemas/ToolImportResponse"
"400":
description: Validation or parse error
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
"403":
description: Premium feature required
content:
application/json:
schema:
$ref: "#/components/schemas/ErrorResponse"
components:
schemas:
HealthStatus:
type: object
properties:
status:
type: string
required:
- status
VersionInfo:
type: object
required: [version]
properties:
version:
type: string
commitSha:
type: ["string", "null"]
buildDate:
type: ["string", "null"]
trashRetentionDays:
type: integer
AuthMode:
type: object
required: [mode]
properties:
mode:
type: string
enum: [oidc, local]
CsrfToken:
type: object
required: [token]
properties:
token:
type: string
LocalLoginInput:
type: object
required: [username, password]
properties:
username:
type: string
password:
type: string
User:
type: object
required: [id, username, role, createdAt]
properties:
id:
type: integer
username:
type: string
email:
type: ["string", "null"]
role:
type: string
enum: [admin, user]
tier:
type: string
enum: [free, premium, enterprise]
authProvider:
type: string
enum: [local, oidc]
default: local
createdAt:
type: string
format: date-time
UserCreateInput:
type: object
required: [username, password]
properties:
username:
type: string
minLength: 2
password:
type: string
minLength: 6
email:
type: string
role:
type: string
enum: [admin, user]
tier:
type: string
enum: [free, premium, enterprise]
UserRoleUpdate:
type: object
properties:
role:
type: string
enum: [admin, user]
tier:
type: string
enum: [free, premium, enterprise]
ChangePasswordInput:
type: object
required: [currentPassword, newPassword]
properties:
currentPassword:
type: string
minLength: 1
newPassword:
type: string
minLength: 6
SetPasswordInput:
type: object
required: [password]
properties:
password:
type: string
minLength: 6
PasswordRedirect:
type: object
required: [url]
properties:
url:
type: ["string", "null"]
AuditLog:
type: object
required: [id, entityType, action, userId, username, createdAt]
properties:
id:
type: integer
entityType:
type: string
entityId:
type: ["integer", "null"]
action:
type: string
userId:
type: string
username:
type: string
changes:
type: ["string", "null"]
createdAt:
type: string
format: date-time
Tool:
type: object
required: [id, name, description, category, createdAt, updatedAt]
properties:
id:
type: integer
name:
type: string
description:
type: string
category:
type: string
websiteUrl:
type: ["string", "null"]
iconUrl:
type: ["string", "null"]
createdBy:
type: ["string", "null"]
features:
type: array
items:
type: string
tags:
type: array
items:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
deletedAt:
type: ["string", "null"]
format: date-time
deletedBy:
type: ["string", "null"]
RatingHistoryItem:
type: object
required: [date, usefulness, usability, combined]
properties:
date:
type: string
format: date-time
usefulness:
type: number
usability:
type: number
combined:
type: number
ToolWithStats:
type: object
required: [id, name, description, category, createdAt, updatedAt, ratingCount, avgUsefulness, avgUsability, avgCombined]
properties:
id:
type: integer
name:
type: string
description:
type: string
category:
type: string
websiteUrl:
type: ["string", "null"]
iconUrl:
type: ["string", "null"]
createdBy:
type: ["string", "null"]
features:
type: array
items:
type: string
tags:
type: array
items:
type: string
createdAt:
type: string
format: date-time
updatedAt:
type: string
format: date-time
ratingCount:
type: integer
avgUsefulness:
type: ["number", "null"]
avgUsability:
type: ["number", "null"]
avgCombined:
type: ["number", "null"]
ToolInput:
type: object
required: [name, description, category]
properties:
name:
type: string
minLength: 1
description:
type: string
minLength: 1
category:
type: string
minLength: 1
websiteUrl:
type: string
iconUrl:
type: string
features:
type: array
items:
type: string
tags:
type: array
items:
type: string
ToolImportBody:
type: object
required: [data]
properties:
format:
type: string
enum: [auto, csv, json, yaml]
default: auto
delimiter:
type: string
enum: [auto, comma, semicolon, tab]
default: auto
data:
type: string
ToolImportResponse:
type: object
required: [imported, total, errors]
properties:
imported:
type: integer
total:
type: integer
errors:
type: array
items:
type: object
properties:
row:
type: integer
error:
type: string
ToolUpdate:
type: object
properties:
name:
type: string
minLength: 1
description:
type: string
category:
type: string
websiteUrl:
type: ["string", "null"]
iconUrl:
type: ["string", "null"]
features:
type: array
items:
type: string
tags:
type: array
items:
type: string
TrashToolsInput:
type: object
required: [ids]
properties:
ids:
type: array
minItems: 1
maxItems: 500
items:
type: integer
Rating:
type: object
required: [id, toolId, usefulness, usability, createdAt]
properties:
id:
type: integer
toolId:
type: integer
usefulness:
type: integer
minimum: 1
maximum: 5
usability:
type: integer
minimum: 1
maximum: 5
comment:
type: ["string", "null"]
reviewerName:
type: ["string", "null"]
createdAt:
type: string
format: date-time
RatingInput:
type: object
required: [usefulness, usability]
properties:
usefulness:
type: integer
minimum: 1
maximum: 5
usability:
type: integer
minimum: 1
maximum: 5
comment:
type: string
reviewerName:
type: string
AnalyticsSummary:
type: object
required: [totalTools, totalRatings, avgUsefulness, avgUsability, avgCombined, categoriesCount]
properties:
totalTools:
type: integer
totalRatings:
type: integer
avgUsefulness:
type: ["number", "null"]
avgUsability:
type: ["number", "null"]
avgCombined:
type: ["number", "null"]
categoriesCount:
type: integer
mostRatedTool:
$ref: "#/components/schemas/ToolWithStats"
TopToolEntry:
type: object
required: [tool, score, ratingCount]
properties:
tool:
$ref: "#/components/schemas/ToolWithStats"
score:
type: number
ratingCount:
type: integer
CategoryStats:
type: object
required: [category, toolCount, totalRatings, avgUsefulness, avgUsability]
properties:
category:
type: string
toolCount:
type: integer
totalRatings:
type: integer
avgUsefulness:
type: ["number", "null"]
avgUsability:
type: ["number", "null"]
RatingDistribution:
type: object
required: [usefulness, usability]
properties:
usefulness:
type: array
items:
$ref: "#/components/schemas/ScoreBucket"
usability:
type: array
items:
$ref: "#/components/schemas/ScoreBucket"
ScoreBucket:
type: object
required: [score, count]
properties:
score:
type: integer
count:
type: integer
AuthUser:
type: object
required: [sub]
properties:
sub:
type: string
email:
type: ["string", "null"]
name:
type: ["string", "null"]
preferredUsername:
type: ["string", "null"]
role:
type: string
enum: [admin, user]
tier:
type: string
enum: [free, premium, enterprise]
entitlements:
type: array
items:
type: string
isLocal:
type: boolean
UserPreferences:
type: object
properties:
view:
type: string
enum: [grid, table, rows]
density:
type: string
enum: [cozy, compact]
watchlist:
type: array
items:
type: integer
ErrorResponse:
type: object
required: [error]
properties:
error:
type: string