01c70085db
Build & Push Docker Image / build (push) Successful in 6m52s
- costs: nullable notes (fix create without notes), drop renewalDate (schema + API + UI), gate POST/PATCH/DELETE to admin + costs feature - feature middleware: admin-aware hasFeature + getEntitlements union; /auth/me and login return resolved entitlements - users: tier enum (free/premium/enterprise) in create/update/list, admin UI tier select + tier badge - tags: GET /tags/all, TagInput autocomplete in new/edit tool forms, feature suggestions on focus, query invalidation on create/update - openapi: nullable ToolUpdate urls, ToolUpdate tier fields, listAllTags - Dockerfile: push-force to drop renewal_date column
891 lines
21 KiB
YAML
891 lines
21 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"
|
|
|
|
/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]
|
|
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"
|
|
|
|
/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/{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/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"
|
|
|
|
/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
|
|
|
|
/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"
|
|
|
|
components:
|
|
schemas:
|
|
HealthStatus:
|
|
type: object
|
|
properties:
|
|
status:
|
|
type: string
|
|
required:
|
|
- status
|
|
|
|
AuthMode:
|
|
type: object
|
|
required: [mode]
|
|
properties:
|
|
mode:
|
|
type: string
|
|
enum: [oidc, local]
|
|
|
|
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]
|
|
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]
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
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
|
|
|
|
ErrorResponse:
|
|
type: object
|
|
required: [error]
|
|
properties:
|
|
error:
|
|
type: string
|