Add API endpoints and frontend components for tool management and analytics
Implement CRUD operations for tools and ratings, introduce analytics endpoints, and develop frontend components for displaying tools, ratings, and analytics data. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 776963d0-f75d-42e2-a57b-cc36bdff8495 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: feaa4ce1-5aed-4cc0-bcea-47855b615b48 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0683fb79-a27c-485c-9333-5f4b288c4567/776963d0-f75d-42e2-a57b-cc36bdff8495/rx9K7bW Replit-Helium-Checkpoint-Created: true
This commit is contained in:
@@ -1,10 +1,177 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* API specification
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
export interface HealthStatus {
|
||||
status: string;
|
||||
}
|
||||
|
||||
export interface Tool {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
/** @nullable */
|
||||
websiteUrl?: string | null;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
}
|
||||
|
||||
export interface ToolWithStats {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
/** @nullable */
|
||||
websiteUrl?: string | null;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
createdAt: string;
|
||||
updatedAt: string;
|
||||
ratingCount: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
/** @nullable */
|
||||
avgCombined: number | null;
|
||||
}
|
||||
|
||||
export interface ToolInput {
|
||||
/** @minLength 1 */
|
||||
name: string;
|
||||
/** @minLength 1 */
|
||||
description: string;
|
||||
/** @minLength 1 */
|
||||
category: string;
|
||||
websiteUrl?: string;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface ToolUpdate {
|
||||
/** @minLength 1 */
|
||||
name?: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
websiteUrl?: string;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface Rating {
|
||||
id: number;
|
||||
toolId: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usefulness: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usability: number;
|
||||
/** @nullable */
|
||||
comment?: string | null;
|
||||
/** @nullable */
|
||||
reviewerName?: string | null;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface RatingInput {
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usefulness: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usability: number;
|
||||
comment?: string;
|
||||
reviewerName?: string;
|
||||
}
|
||||
|
||||
export interface AnalyticsSummary {
|
||||
totalTools: number;
|
||||
totalRatings: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
/** @nullable */
|
||||
avgCombined: number | null;
|
||||
categoriesCount: number;
|
||||
mostRatedTool?: ToolWithStats;
|
||||
}
|
||||
|
||||
export interface TopToolEntry {
|
||||
tool: ToolWithStats;
|
||||
score: number;
|
||||
ratingCount: number;
|
||||
}
|
||||
|
||||
export interface CategoryStats {
|
||||
category: string;
|
||||
toolCount: number;
|
||||
totalRatings: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
}
|
||||
|
||||
export interface ScoreBucket {
|
||||
score: number;
|
||||
count: number;
|
||||
}
|
||||
|
||||
export interface RatingDistribution {
|
||||
usefulness: ScoreBucket[];
|
||||
usability: ScoreBucket[];
|
||||
}
|
||||
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
|
||||
export type ListToolsParams = {
|
||||
category?: string;
|
||||
search?: string;
|
||||
sort?: ListToolsSort;
|
||||
};
|
||||
|
||||
export type ListToolsSort = typeof ListToolsSort[keyof typeof ListToolsSort];
|
||||
|
||||
|
||||
export const ListToolsSort = {
|
||||
newest: 'newest',
|
||||
top_rated: 'top_rated',
|
||||
most_reviewed: 'most_reviewed',
|
||||
} as const;
|
||||
|
||||
export type GetTopToolsParams = {
|
||||
limit?: number;
|
||||
metric?: GetTopToolsMetric;
|
||||
};
|
||||
|
||||
export type GetTopToolsMetric = typeof GetTopToolsMetric[keyof typeof GetTopToolsMetric];
|
||||
|
||||
|
||||
export const GetTopToolsMetric = {
|
||||
usefulness: 'usefulness',
|
||||
usability: 'usability',
|
||||
combined: 'combined',
|
||||
} as const;
|
||||
|
||||
export type GetRatingDistributionParams = {
|
||||
toolId?: number;
|
||||
};
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
+506
-1
@@ -3,13 +3,19 @@ info:
|
||||
# Do not change the title, if the title changes, the import paths will be broken
|
||||
title: Api
|
||||
version: 0.1.0
|
||||
description: API specification
|
||||
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
|
||||
paths:
|
||||
/healthz:
|
||||
get:
|
||||
@@ -24,6 +30,280 @@ paths:
|
||||
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
|
||||
|
||||
components:
|
||||
schemas:
|
||||
HealthStatus:
|
||||
@@ -34,3 +314,228 @@ components:
|
||||
required:
|
||||
- status
|
||||
|
||||
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"]
|
||||
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"]
|
||||
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
|
||||
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
|
||||
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
|
||||
|
||||
ErrorResponse:
|
||||
type: object
|
||||
required: [error]
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
|
||||
@@ -1,16 +1,271 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* API specification
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import * as zod from "zod";
|
||||
import * as zod from 'zod';
|
||||
|
||||
|
||||
/**
|
||||
* Returns server health status
|
||||
* @summary Health check
|
||||
*/
|
||||
export const HealthCheckResponse = zod.object({
|
||||
status: zod.string(),
|
||||
});
|
||||
"status": zod.string()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary List all tools
|
||||
*/
|
||||
export const ListToolsQueryParams = zod.object({
|
||||
"category": zod.coerce.string().optional(),
|
||||
"search": zod.coerce.string().optional(),
|
||||
"sort": zod.enum(['newest', 'top_rated', 'most_reviewed']).optional()
|
||||
})
|
||||
|
||||
export const ListToolsResponseItem = zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date(),
|
||||
"ratingCount": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable()
|
||||
})
|
||||
export const ListToolsResponse = zod.array(ListToolsResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Create a new tool
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const CreateToolBody = zod.object({
|
||||
"name": zod.string().min(1),
|
||||
"description": zod.string().min(1),
|
||||
"category": zod.string().min(1),
|
||||
"websiteUrl": zod.string().optional(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get a tool by ID
|
||||
*/
|
||||
export const GetToolParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
export const GetToolResponse = zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date(),
|
||||
"ratingCount": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Update a tool
|
||||
*/
|
||||
export const UpdateToolParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
export const UpdateToolBody = zod.object({
|
||||
"name": zod.string().min(1).optional(),
|
||||
"description": zod.string().optional(),
|
||||
"category": zod.string().optional(),
|
||||
"websiteUrl": zod.string().optional(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional()
|
||||
})
|
||||
|
||||
export const UpdateToolResponse = zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Delete a tool
|
||||
*/
|
||||
export const DeleteToolParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary List ratings for a tool
|
||||
*/
|
||||
export const ListToolRatingsParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
export const listToolRatingsResponseUsefulnessMax = 5;
|
||||
|
||||
export const listToolRatingsResponseUsabilityMax = 5;
|
||||
|
||||
|
||||
|
||||
export const ListToolRatingsResponseItem = zod.object({
|
||||
"id": zod.number(),
|
||||
"toolId": zod.number(),
|
||||
"usefulness": zod.number().min(1).max(listToolRatingsResponseUsefulnessMax),
|
||||
"usability": zod.number().min(1).max(listToolRatingsResponseUsabilityMax),
|
||||
"comment": zod.string().nullish(),
|
||||
"reviewerName": zod.string().nullish(),
|
||||
"createdAt": zod.coerce.date()
|
||||
})
|
||||
export const ListToolRatingsResponse = zod.array(ListToolRatingsResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Submit a rating for a tool
|
||||
*/
|
||||
export const CreateRatingParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
export const createRatingBodyUsefulnessMax = 5;
|
||||
|
||||
export const createRatingBodyUsabilityMax = 5;
|
||||
|
||||
|
||||
|
||||
export const CreateRatingBody = zod.object({
|
||||
"usefulness": zod.number().min(1).max(createRatingBodyUsefulnessMax),
|
||||
"usability": zod.number().min(1).max(createRatingBodyUsabilityMax),
|
||||
"comment": zod.string().optional(),
|
||||
"reviewerName": zod.string().optional()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Overall platform statistics
|
||||
*/
|
||||
export const GetAnalyticsSummaryResponse = zod.object({
|
||||
"totalTools": zod.number(),
|
||||
"totalRatings": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable(),
|
||||
"categoriesCount": zod.number(),
|
||||
"mostRatedTool": zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date(),
|
||||
"ratingCount": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable()
|
||||
}).optional()
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Top-rated tools
|
||||
*/
|
||||
export const GetTopToolsQueryParams = zod.object({
|
||||
"limit": zod.coerce.number().optional(),
|
||||
"metric": zod.enum(['usefulness', 'usability', 'combined']).optional()
|
||||
})
|
||||
|
||||
export const GetTopToolsResponseItem = zod.object({
|
||||
"tool": zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"features": zod.array(zod.string()).optional(),
|
||||
"tags": zod.array(zod.string()).optional(),
|
||||
"createdAt": zod.coerce.date(),
|
||||
"updatedAt": zod.coerce.date(),
|
||||
"ratingCount": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable(),
|
||||
"avgCombined": zod.number().nullable()
|
||||
}),
|
||||
"score": zod.number(),
|
||||
"ratingCount": zod.number()
|
||||
})
|
||||
export const GetTopToolsResponse = zod.array(GetTopToolsResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Rating statistics grouped by category
|
||||
*/
|
||||
export const GetAnalyticsByCategoryResponseItem = zod.object({
|
||||
"category": zod.string(),
|
||||
"toolCount": zod.number(),
|
||||
"totalRatings": zod.number(),
|
||||
"avgUsefulness": zod.number().nullable(),
|
||||
"avgUsability": zod.number().nullable()
|
||||
})
|
||||
export const GetAnalyticsByCategoryResponse = zod.array(GetAnalyticsByCategoryResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Distribution of rating scores across the platform
|
||||
*/
|
||||
export const GetRatingDistributionQueryParams = zod.object({
|
||||
"toolId": zod.coerce.number().optional()
|
||||
})
|
||||
|
||||
export const GetRatingDistributionResponse = zod.object({
|
||||
"usefulness": zod.array(zod.object({
|
||||
"score": zod.number(),
|
||||
"count": zod.number()
|
||||
})),
|
||||
"usability": zod.array(zod.object({
|
||||
"score": zod.number(),
|
||||
"count": zod.number()
|
||||
}))
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary List all distinct tool categories
|
||||
*/
|
||||
export const ListCategoriesResponseItem = zod.string()
|
||||
export const ListCategoriesResponse = zod.array(ListCategoriesResponseItem)
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ToolWithStats } from './toolWithStats';
|
||||
|
||||
export interface AnalyticsSummary {
|
||||
totalTools: number;
|
||||
totalRatings: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
/** @nullable */
|
||||
avgCombined: number | null;
|
||||
categoriesCount: number;
|
||||
mostRatedTool?: ToolWithStats;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface CategoryStats {
|
||||
category: string;
|
||||
toolCount: number;
|
||||
totalRatings: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ErrorResponse {
|
||||
error: string;
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type GetRatingDistributionParams = {
|
||||
toolId?: number;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type GetTopToolsMetric = typeof GetTopToolsMetric[keyof typeof GetTopToolsMetric];
|
||||
|
||||
|
||||
export const GetTopToolsMetric = {
|
||||
usefulness: 'usefulness',
|
||||
usability: 'usability',
|
||||
combined: 'combined',
|
||||
} as const;
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { GetTopToolsMetric } from './getTopToolsMetric';
|
||||
|
||||
export type GetTopToolsParams = {
|
||||
limit?: number;
|
||||
metric?: GetTopToolsMetric;
|
||||
};
|
||||
@@ -1,8 +1,8 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* API specification
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
|
||||
@@ -1,9 +1,26 @@
|
||||
/**
|
||||
* Generated by orval v8.5.3 🍺
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* API specification
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export * from "./healthStatus";
|
||||
export * from './analyticsSummary';
|
||||
export * from './categoryStats';
|
||||
export * from './errorResponse';
|
||||
export * from './getRatingDistributionParams';
|
||||
export * from './getTopToolsMetric';
|
||||
export * from './getTopToolsParams';
|
||||
export * from './healthStatus';
|
||||
export * from './listToolsParams';
|
||||
export * from './listToolsSort';
|
||||
export * from './rating';
|
||||
export * from './ratingDistribution';
|
||||
export * from './ratingInput';
|
||||
export * from './scoreBucket';
|
||||
export * from './tool';
|
||||
export * from './toolInput';
|
||||
export * from './toolUpdate';
|
||||
export * from './toolWithStats';
|
||||
export * from './topToolEntry';
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ListToolsSort } from './listToolsSort';
|
||||
|
||||
export type ListToolsParams = {
|
||||
category?: string;
|
||||
search?: string;
|
||||
sort?: ListToolsSort;
|
||||
};
|
||||
@@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export type ListToolsSort = typeof ListToolsSort[keyof typeof ListToolsSort];
|
||||
|
||||
|
||||
export const ListToolsSort = {
|
||||
newest: 'newest',
|
||||
top_rated: 'top_rated',
|
||||
most_reviewed: 'most_reviewed',
|
||||
} as const;
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface Rating {
|
||||
id: number;
|
||||
toolId: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usefulness: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usability: number;
|
||||
/** @nullable */
|
||||
comment?: string | null;
|
||||
/** @nullable */
|
||||
reviewerName?: string | null;
|
||||
createdAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ScoreBucket } from './scoreBucket';
|
||||
|
||||
export interface RatingDistribution {
|
||||
usefulness: ScoreBucket[];
|
||||
usability: ScoreBucket[];
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface RatingInput {
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usefulness: number;
|
||||
/**
|
||||
* @minimum 1
|
||||
* @maximum 5
|
||||
*/
|
||||
usability: number;
|
||||
comment?: string;
|
||||
reviewerName?: string;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ScoreBucket {
|
||||
score: number;
|
||||
count: number;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface Tool {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
/** @nullable */
|
||||
websiteUrl?: string | null;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ToolInput {
|
||||
/** @minLength 1 */
|
||||
name: string;
|
||||
/** @minLength 1 */
|
||||
description: string;
|
||||
/** @minLength 1 */
|
||||
category: string;
|
||||
websiteUrl?: string;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ToolUpdate {
|
||||
/** @minLength 1 */
|
||||
name?: string;
|
||||
description?: string;
|
||||
category?: string;
|
||||
websiteUrl?: string;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
|
||||
export interface ToolWithStats {
|
||||
id: number;
|
||||
name: string;
|
||||
description: string;
|
||||
category: string;
|
||||
/** @nullable */
|
||||
websiteUrl?: string | null;
|
||||
features?: string[];
|
||||
tags?: string[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
ratingCount: number;
|
||||
/** @nullable */
|
||||
avgUsefulness: number | null;
|
||||
/** @nullable */
|
||||
avgUsability: number | null;
|
||||
/** @nullable */
|
||||
avgCombined: number | null;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/**
|
||||
* Generated by orval v8.9.1 🍺
|
||||
* Do not edit manually.
|
||||
* Api
|
||||
* ToolRate API — Tool listing and rating platform
|
||||
* OpenAPI spec version: 0.1.0
|
||||
*/
|
||||
import type { ToolWithStats } from './toolWithStats';
|
||||
|
||||
export interface TopToolEntry {
|
||||
tool: ToolWithStats;
|
||||
score: number;
|
||||
ratingCount: number;
|
||||
}
|
||||
@@ -1,20 +1,2 @@
|
||||
// Export your models here. Add one export per file
|
||||
// export * from "./posts";
|
||||
//
|
||||
// Each model/table should ideally be split into different files.
|
||||
// Each model/table should define a Drizzle table, insert schema, and types:
|
||||
//
|
||||
// import { pgTable, text, serial } from "drizzle-orm/pg-core";
|
||||
// import { createInsertSchema } from "drizzle-zod";
|
||||
// import { z } from "zod/v4";
|
||||
//
|
||||
// export const postsTable = pgTable("posts", {
|
||||
// id: serial("id").primaryKey(),
|
||||
// title: text("title").notNull(),
|
||||
// });
|
||||
//
|
||||
// export const insertPostSchema = createInsertSchema(postsTable).omit({ id: true });
|
||||
// export type InsertPost = z.infer<typeof insertPostSchema>;
|
||||
// export type Post = typeof postsTable.$inferSelect;
|
||||
|
||||
export {}
|
||||
export * from "./tools";
|
||||
export * from "./ratings";
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import { pgTable, text, serial, integer, timestamp } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod/v4";
|
||||
import { toolsTable } from "./tools";
|
||||
|
||||
export const ratingsTable = pgTable("ratings", {
|
||||
id: serial("id").primaryKey(),
|
||||
toolId: integer("tool_id").notNull().references(() => toolsTable.id, { onDelete: "cascade" }),
|
||||
usefulness: integer("usefulness").notNull(),
|
||||
usability: integer("usability").notNull(),
|
||||
comment: text("comment"),
|
||||
reviewerName: text("reviewer_name"),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
});
|
||||
|
||||
export const insertRatingSchema = createInsertSchema(ratingsTable).omit({ id: true, createdAt: true });
|
||||
export type InsertRating = z.infer<typeof insertRatingSchema>;
|
||||
export type Rating = typeof ratingsTable.$inferSelect;
|
||||
@@ -0,0 +1,19 @@
|
||||
import { pgTable, text, serial, timestamp } from "drizzle-orm/pg-core";
|
||||
import { createInsertSchema } from "drizzle-zod";
|
||||
import { z } from "zod/v4";
|
||||
|
||||
export const toolsTable = pgTable("tools", {
|
||||
id: serial("id").primaryKey(),
|
||||
name: text("name").notNull(),
|
||||
description: text("description").notNull(),
|
||||
category: text("category").notNull(),
|
||||
websiteUrl: text("website_url"),
|
||||
features: text("features").array().notNull().default([]),
|
||||
tags: text("tags").array().notNull().default([]),
|
||||
createdAt: timestamp("created_at", { withTimezone: true }).notNull().defaultNow(),
|
||||
updatedAt: timestamp("updated_at", { withTimezone: true }).notNull().defaultNow().$onUpdate(() => new Date()),
|
||||
});
|
||||
|
||||
export const insertToolSchema = createInsertSchema(toolsTable).omit({ id: true, createdAt: true, updatedAt: true });
|
||||
export type InsertTool = z.infer<typeof insertToolSchema>;
|
||||
export type Tool = typeof toolsTable.$inferSelect;
|
||||
Reference in New Issue
Block a user