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:
@@ -2,8 +2,8 @@ import { type Request, type Response, type NextFunction } from "express";
|
||||
|
||||
const TIER_FEATURES: Record<string, string[]> = {
|
||||
free: ["browse", "rate", "search"],
|
||||
premium: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash"],
|
||||
enterprise: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "sso", "audit-export", "api-access"],
|
||||
premium: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist"],
|
||||
enterprise: ["browse", "rate", "search", "similar-tools", "costs", "redundancy", "analytics-advanced", "trash", "compare", "watchlist", "sso", "audit-export", "api-access"],
|
||||
};
|
||||
|
||||
export function getEntitlements(tier: string | undefined, role: string | undefined): string[] {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Router, type IRouter } from "express";
|
||||
import { eq, desc, sql, and, not, isNull, inArray } from "drizzle-orm";
|
||||
import { eq, desc, asc, sql, and, not, isNull, inArray } from "drizzle-orm";
|
||||
import { z } from "zod";
|
||||
import { db, toolsTable, ratingsTable, toolRelationsTable } from "@workspace/db";
|
||||
import {
|
||||
@@ -232,6 +232,65 @@ router.get("/tools/:id", async (req, res): Promise<void> => {
|
||||
res.json(buildToolWithStats(tool, ratings));
|
||||
});
|
||||
|
||||
router.get("/tools/:id/rating-history", async (req, res): Promise<void> => {
|
||||
const params = GetToolParams.safeParse(req.params);
|
||||
if (!params.success) {
|
||||
res.status(400).json({ error: params.error.message });
|
||||
return;
|
||||
}
|
||||
const ratings = await db
|
||||
.select({
|
||||
date: ratingsTable.createdAt,
|
||||
usefulness: ratingsTable.usefulness,
|
||||
usability: ratingsTable.usability,
|
||||
})
|
||||
.from(ratingsTable)
|
||||
.where(eq(ratingsTable.toolId, params.data.id))
|
||||
.orderBy(asc(ratingsTable.createdAt));
|
||||
res.json(
|
||||
ratings.map((r) => ({
|
||||
date: r.date,
|
||||
usefulness: r.usefulness,
|
||||
usability: r.usability,
|
||||
combined: (r.usefulness + r.usability) / 2,
|
||||
})),
|
||||
);
|
||||
});
|
||||
|
||||
router.get("/compare", requireFeature("compare"), async (req, res): Promise<void> => {
|
||||
const ids = String(req.query.ids ?? "")
|
||||
.split(",")
|
||||
.map((s) => Number(s.trim()))
|
||||
.filter((n) => Number.isInteger(n) && n > 0);
|
||||
if (ids.length === 0) {
|
||||
res.status(400).json({ error: "Provide at least one ids value, e.g. ?ids=1,2,3" });
|
||||
return;
|
||||
}
|
||||
if (ids.length > 8) {
|
||||
res.status(400).json({ error: "Maximum of 8 tools can be compared" });
|
||||
return;
|
||||
}
|
||||
const tools = await db.select().from(toolsTable).where(and(isNull(toolsTable.deletedAt), inArray(toolsTable.id, ids)));
|
||||
if (tools.length === 0) {
|
||||
res.status(404).json({ error: "No tools found" });
|
||||
return;
|
||||
}
|
||||
const toolIds = tools.map((t) => t.id);
|
||||
const ratings = await db
|
||||
.select({ toolId: ratingsTable.toolId, usefulness: ratingsTable.usefulness, usability: ratingsTable.usability })
|
||||
.from(ratingsTable)
|
||||
.where(inArray(ratingsTable.toolId, toolIds));
|
||||
const byTool = new Map<number, { usefulness: number; usability: number }[]>();
|
||||
for (const r of ratings) {
|
||||
const list = byTool.get(r.toolId) ?? [];
|
||||
list.push({ usefulness: r.usefulness, usability: r.usability });
|
||||
byTool.set(r.toolId, list);
|
||||
}
|
||||
const byId = new Map(tools.map((t) => [t.id, t]));
|
||||
const ordered = ids.filter((id) => byId.has(id)).map((id) => buildToolWithStats(byId.get(id)!, byTool.get(id) ?? []));
|
||||
res.json(ordered);
|
||||
});
|
||||
|
||||
router.patch("/tools/:id", requireAuth, async (req, res): Promise<void> => {
|
||||
const params = UpdateToolParams.safeParse(req.params);
|
||||
if (!params.success) {
|
||||
|
||||
Reference in New Issue
Block a user