feat: compare (premium), rating-history trend, hover previews
Build & Push Docker Image / build (push) Successful in 2m47s
Build & Push Docker Image / build (push) Successful in 2m47s
This commit is contained in:
@@ -145,6 +145,13 @@ export interface Tool {
|
||||
deletedBy?: string | null;
|
||||
}
|
||||
|
||||
export interface RatingHistoryItem {
|
||||
date: string;
|
||||
usefulness: number;
|
||||
usability: number;
|
||||
combined: number;
|
||||
}
|
||||
|
||||
export interface ToolWithStats {
|
||||
id: number;
|
||||
name: string;
|
||||
@@ -366,6 +373,13 @@ export const ListToolsSort = {
|
||||
recently_updated: 'recently_updated',
|
||||
} as const;
|
||||
|
||||
export type ListCompareToolsParams = {
|
||||
/**
|
||||
* Comma-separated tool ids
|
||||
*/
|
||||
ids: string;
|
||||
};
|
||||
|
||||
export type ListTrashedToolsParams = {
|
||||
search?: string;
|
||||
};
|
||||
|
||||
@@ -31,11 +31,13 @@ import type {
|
||||
GetTopToolsParams,
|
||||
HealthStatus,
|
||||
ListAuditLogsParams,
|
||||
ListCompareToolsParams,
|
||||
ListToolsParams,
|
||||
ListTrashedToolsParams,
|
||||
LocalLoginInput,
|
||||
Rating,
|
||||
RatingDistribution,
|
||||
RatingHistoryItem,
|
||||
RatingInput,
|
||||
RestoreTools200,
|
||||
Tool,
|
||||
@@ -375,6 +377,167 @@ export const useCreateTool = <TError = ErrorType<ErrorResponse>,
|
||||
return useMutation(getCreateToolMutationOptions(options));
|
||||
}
|
||||
|
||||
export const getListCompareToolsUrl = (params: ListCompareToolsParams,) => {
|
||||
const normalizedParams = new URLSearchParams();
|
||||
|
||||
Object.entries(params || {}).forEach(([key, value]) => {
|
||||
|
||||
if (value !== undefined) {
|
||||
normalizedParams.append(key, value === null ? 'null' : value.toString())
|
||||
}
|
||||
});
|
||||
|
||||
const stringifiedParams = normalizedParams.toString();
|
||||
|
||||
return stringifiedParams.length > 0 ? `/api/compare?${stringifiedParams}` : `/api/compare`
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Compare tools side by side (premium)
|
||||
*/
|
||||
export const listCompareTools = async (params: ListCompareToolsParams, options?: RequestInit): Promise<ToolWithStats[]> => {
|
||||
|
||||
return customFetch<ToolWithStats[]>(getListCompareToolsUrl(params),
|
||||
{
|
||||
...options,
|
||||
method: 'GET'
|
||||
|
||||
|
||||
}
|
||||
);}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getListCompareToolsQueryKey = (params?: ListCompareToolsParams,) => {
|
||||
return [
|
||||
`/api/compare`, ...(params ? [params] : [])
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getListCompareToolsQueryOptions = <TData = Awaited<ReturnType<typeof listCompareTools>>, TError = ErrorType<ErrorResponse>>(params: ListCompareToolsParams, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getListCompareToolsQueryKey(params);
|
||||
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof listCompareTools>>> = ({ signal }) => listCompareTools(params, { signal, ...requestOptions });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData> & { queryKey: QueryKey }
|
||||
}
|
||||
|
||||
export type ListCompareToolsQueryResult = NonNullable<Awaited<ReturnType<typeof listCompareTools>>>
|
||||
export type ListCompareToolsQueryError = ErrorType<ErrorResponse>
|
||||
|
||||
|
||||
/**
|
||||
* @summary Compare tools side by side (premium)
|
||||
*/
|
||||
|
||||
export function useListCompareTools<TData = Awaited<ReturnType<typeof listCompareTools>>, TError = ErrorType<ErrorResponse>>(
|
||||
params: ListCompareToolsParams, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof listCompareTools>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
|
||||
const queryOptions = getListCompareToolsQueryOptions(params,options)
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getGetToolRatingHistoryUrl = (id: number,) => {
|
||||
|
||||
|
||||
|
||||
|
||||
return `/api/tools/${id}/rating-history`
|
||||
}
|
||||
|
||||
/**
|
||||
* @summary Get a tool's rating history over time
|
||||
*/
|
||||
export const getToolRatingHistory = async (id: number, options?: RequestInit): Promise<RatingHistoryItem[]> => {
|
||||
|
||||
return customFetch<RatingHistoryItem[]>(getGetToolRatingHistoryUrl(id),
|
||||
{
|
||||
...options,
|
||||
method: 'GET'
|
||||
|
||||
|
||||
}
|
||||
);}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getGetToolRatingHistoryQueryKey = (id: number,) => {
|
||||
return [
|
||||
`/api/tools/${id}/rating-history`
|
||||
] as const;
|
||||
}
|
||||
|
||||
|
||||
export const getGetToolRatingHistoryQueryOptions = <TData = Awaited<ReturnType<typeof getToolRatingHistory>>, TError = ErrorType<ErrorResponse>>(id: number, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
) => {
|
||||
|
||||
const {query: queryOptions, request: requestOptions} = options ?? {};
|
||||
|
||||
const queryKey = queryOptions?.queryKey ?? getGetToolRatingHistoryQueryKey(id);
|
||||
|
||||
|
||||
|
||||
const queryFn: QueryFunction<Awaited<ReturnType<typeof getToolRatingHistory>>> = ({ signal }) => getToolRatingHistory(id, { signal, ...requestOptions });
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
return { queryKey, queryFn, enabled: !!(id), ...queryOptions} as UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData> & { queryKey: QueryKey }
|
||||
}
|
||||
|
||||
export type GetToolRatingHistoryQueryResult = NonNullable<Awaited<ReturnType<typeof getToolRatingHistory>>>
|
||||
export type GetToolRatingHistoryQueryError = ErrorType<ErrorResponse>
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get a tool's rating history over time
|
||||
*/
|
||||
|
||||
export function useGetToolRatingHistory<TData = Awaited<ReturnType<typeof getToolRatingHistory>>, TError = ErrorType<ErrorResponse>>(
|
||||
id: number, options?: { query?:UseQueryOptions<Awaited<ReturnType<typeof getToolRatingHistory>>, TError, TData>, request?: SecondParameter<typeof customFetch>}
|
||||
|
||||
): UseQueryResult<TData, TError> & { queryKey: QueryKey } {
|
||||
|
||||
const queryOptions = getGetToolRatingHistoryQueryOptions(id,options)
|
||||
|
||||
const query = useQuery(queryOptions) as UseQueryResult<TData, TError> & { queryKey: QueryKey };
|
||||
|
||||
return { ...query, queryKey: queryOptions.queryKey };
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
export const getGetToolUrl = (id: number,) => {
|
||||
|
||||
|
||||
|
||||
@@ -126,6 +126,67 @@ paths:
|
||||
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
|
||||
@@ -875,6 +936,20 @@ components:
|
||||
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]
|
||||
|
||||
@@ -85,6 +85,49 @@ export const CreateToolBody = zod.object({
|
||||
})
|
||||
|
||||
|
||||
/**
|
||||
* @summary Compare tools side by side (premium)
|
||||
*/
|
||||
export const ListCompareToolsQueryParams = zod.object({
|
||||
"ids": zod.coerce.string().describe('Comma-separated tool ids')
|
||||
})
|
||||
|
||||
export const ListCompareToolsResponseItem = zod.object({
|
||||
"id": zod.number(),
|
||||
"name": zod.string(),
|
||||
"description": zod.string(),
|
||||
"category": zod.string(),
|
||||
"websiteUrl": zod.string().nullish(),
|
||||
"iconUrl": zod.string().nullish(),
|
||||
"createdBy": 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 ListCompareToolsResponse = zod.array(ListCompareToolsResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get a tool's rating history over time
|
||||
*/
|
||||
export const GetToolRatingHistoryParams = zod.object({
|
||||
"id": zod.coerce.number()
|
||||
})
|
||||
|
||||
export const GetToolRatingHistoryResponseItem = zod.object({
|
||||
"date": zod.coerce.date(),
|
||||
"usefulness": zod.number(),
|
||||
"usability": zod.number(),
|
||||
"combined": zod.number()
|
||||
})
|
||||
export const GetToolRatingHistoryResponse = zod.array(GetToolRatingHistoryResponseItem)
|
||||
|
||||
|
||||
/**
|
||||
* @summary Get a tool by ID
|
||||
*/
|
||||
|
||||
@@ -21,12 +21,14 @@ export * from './getTopToolsMetric';
|
||||
export * from './getTopToolsParams';
|
||||
export * from './healthStatus';
|
||||
export * from './listAuditLogsParams';
|
||||
export * from './listCompareToolsParams';
|
||||
export * from './listToolsParams';
|
||||
export * from './listToolsSort';
|
||||
export * from './listTrashedToolsParams';
|
||||
export * from './localLoginInput';
|
||||
export * from './rating';
|
||||
export * from './ratingDistribution';
|
||||
export * from './ratingHistoryItem';
|
||||
export * from './ratingInput';
|
||||
export * from './restoreTools200';
|
||||
export * from './scoreBucket';
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
export type ListCompareToolsParams = {
|
||||
/**
|
||||
* Comma-separated tool ids
|
||||
*/
|
||||
ids: string;
|
||||
};
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
export interface RatingHistoryItem {
|
||||
date: Date;
|
||||
usefulness: number;
|
||||
usability: number;
|
||||
combined: number;
|
||||
}
|
||||
Reference in New Issue
Block a user