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:
cheffe01
2026-05-25 13:04:53 +00:00
parent 1de22a6979
commit 036973ea32
111 changed files with 11006 additions and 96 deletions
@@ -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
View File
@@ -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
+260 -5
View File
@@ -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
*/
+20 -3
View File
@@ -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;
+27
View File
@@ -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;
}
+20
View File
@@ -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;
}
+2 -20
View File
@@ -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";
+18
View File
@@ -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;
+19
View File
@@ -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;