Add user authentication and dynamic feature/category inputs

Implement Keycloak authentication, protected routes, and add combobox and autocomplete components for tool categories and features.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: 776963d0-f75d-42e2-a57b-cc36bdff8495
Replit-Commit-Checkpoint-Type: full_checkpoint
Replit-Commit-Event-Id: 0b145113-c016-4f54-b000-13bd3b0ba8f0
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/0683fb79-a27c-485c-9333-5f4b288c4567/776963d0-f75d-42e2-a57b-cc36bdff8495/z4uWN6A
Replit-Helium-Checkpoint-Created: true
This commit is contained in:
cheffe01
2026-05-25 13:20:15 +00:00
parent e03c51a75e
commit 7abb048edc
22 changed files with 1027 additions and 68 deletions
+16 -4
View File
@@ -1,5 +1,5 @@
import { Router, type IRouter } from "express";
import { eq, ilike, desc, sql, avg, count } from "drizzle-orm";
import { eq, ilike, desc, sql } from "drizzle-orm";
import { db, toolsTable, ratingsTable } from "@workspace/db";
import {
ListToolsQueryParams,
@@ -9,6 +9,7 @@ import {
UpdateToolBody,
DeleteToolParams,
} from "@workspace/api-zod";
import { requireAuth } from "../middleware/auth";
const router: IRouter = Router();
@@ -71,7 +72,7 @@ router.get("/tools", async (req, res): Promise<void> => {
res.json(result);
});
router.post("/tools", async (req, res): Promise<void> => {
router.post("/tools", requireAuth, async (req, res): Promise<void> => {
const parsed = CreateToolBody.safeParse(req.body);
if (!parsed.success) {
res.status(400).json({ error: parsed.error.message });
@@ -111,7 +112,7 @@ router.get("/tools/:id", async (req, res): Promise<void> => {
res.json(buildToolWithStats(tool, ratings));
});
router.patch("/tools/:id", async (req, res): Promise<void> => {
router.patch("/tools/:id", requireAuth, async (req, res): Promise<void> => {
const params = UpdateToolParams.safeParse(req.params);
if (!params.success) {
res.status(400).json({ error: params.error.message });
@@ -146,7 +147,7 @@ router.patch("/tools/:id", async (req, res): Promise<void> => {
res.json(tool);
});
router.delete("/tools/:id", async (req, res): Promise<void> => {
router.delete("/tools/:id", requireAuth, async (req, res): Promise<void> => {
const params = DeleteToolParams.safeParse(req.params);
if (!params.success) {
res.status(400).json({ error: params.error.message });
@@ -170,4 +171,15 @@ router.get("/categories", async (_req, res): Promise<void> => {
res.json(rows.map((r) => r.category));
});
router.get("/features/all", async (_req, res): Promise<void> => {
const tools = await db.select({ features: toolsTable.features }).from(toolsTable);
const featureSet = new Set<string>();
for (const t of tools) {
for (const f of t.features ?? []) {
if (f && f.trim()) featureSet.add(f.trim());
}
}
res.json([...featureSet].sort());
});
export default router;