diff --git a/artifacts/toolrate/src/components/guide-help.tsx b/artifacts/toolrate/src/components/guide-help.tsx new file mode 100644 index 0000000..788c6d8 --- /dev/null +++ b/artifacts/toolrate/src/components/guide-help.tsx @@ -0,0 +1,29 @@ +import { BookOpen } from "lucide-react"; +import { Link } from "wouter"; +import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; + +export function GuideHelp({ + guide, + label, +}: { + guide: string; + label: string; +}) { + return ( + + + + + + + {label} — Anleitung in der Dokumentation + + ); +} diff --git a/artifacts/toolrate/src/pages/analytics.tsx b/artifacts/toolrate/src/pages/analytics.tsx index 25123c6..884de54 100644 --- a/artifacts/toolrate/src/pages/analytics.tsx +++ b/artifacts/toolrate/src/pages/analytics.tsx @@ -6,6 +6,7 @@ import { GetTopToolsMetric } from "@workspace/api-client-react"; import { Layout } from "@/components/layout"; +import { GuideHelp } from "@/components/guide-help"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -48,7 +49,10 @@ export default function Analytics() {
-

Platform Analytics

+

+ Platform Analytics + +

Macro-level insights into tool performance and community engagement.

diff --git a/artifacts/toolrate/src/pages/compare.tsx b/artifacts/toolrate/src/pages/compare.tsx index e359b70..36f671c 100644 --- a/artifacts/toolrate/src/pages/compare.tsx +++ b/artifacts/toolrate/src/pages/compare.tsx @@ -8,6 +8,7 @@ import { Link, useSearch } from "wouter"; import { Layout } from "@/components/layout"; import { useAuth } from "@/hooks/use-auth"; import { RatingStars } from "@/components/rating-stars"; +import { GuideHelp } from "@/components/guide-help"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; @@ -101,7 +102,10 @@ export default function Compare() {
-

{t("compare.title")}

+

+ {t("compare.title")} + +

{t("compare.subtitle", { count: list.length })}

diff --git a/artifacts/toolrate/src/pages/docs.tsx b/artifacts/toolrate/src/pages/docs.tsx index c16ec5c..4c622e2 100644 --- a/artifacts/toolrate/src/pages/docs.tsx +++ b/artifacts/toolrate/src/pages/docs.tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo, useState, createContext, useContext } from "react"; import { Link, useLocation } from "wouter"; import { Marked } from "marked"; import DOMPurify from "dompurify"; @@ -42,6 +42,21 @@ import { const DOCS_BASE = `${import.meta.env.BASE_URL.replace(/\/$/, "")}/docs`; const REPO_URL = "https://git.kubebase.de/admin/tool-evaluator"; +// Active docs version (null = current docs). Shared via context so every +// sub-view builds versioned links and file paths. +const DocsVersionContext = createContext(null); +const useDocsVersion = () => useContext(DocsVersionContext); + +// Full in-app URL for a docs path, prefixed with the active version. +function docsHref(version: string | null, path: string) { + return version === null ? `/docs/${path}` : `/docs/${version}/${path}`; +} + +// Static file path under /docs, prefixed with the versioned snapshot dir. +function docsFile(version: string | null, relPath: string) { + return version === null ? relPath : `versions/${version}/${relPath}`; +} + // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- @@ -88,6 +103,7 @@ type ReleaseDoc = { title: string; date: string | null; hasReference: boolean; + hasHandbook: boolean; }; type HandbookPage = { slug: string; file: string; title: string; order: number }; @@ -230,8 +246,11 @@ function isLinkableType(t: FieldType): boolean { } function resolveTypeHref(t: FieldType): string | null { - if (t.kind === "ref") return `/docs/reference/schemas/${t.value}`; - if (t.kind === "array" && /^[A-Z]/.test(t.value)) return `/docs/reference/schemas/${t.value}`; + const version = useDocsVersion(); + const href = (v: string | null) => + v === null ? `/docs/reference/schemas/${t.value}` : `/docs/${v}/reference/schemas/${t.value}`; + if (t.kind === "ref") return href(version); + if (t.kind === "array" && /^[A-Z]/.test(t.value)) return href(version); return null; } @@ -328,14 +347,14 @@ function DocsNav({ const groups: { label: string; icon: LucideIcon; items: { href: string; label: string; active: boolean }[] }[] = []; - if (handbook && handbook.length > 0 && version === null) { + if (handbook && handbook.length > 0) { groups.push({ label: t("docs.guides"), icon: BookOpen, items: handbook.map((p) => ({ - href: `/docs/handbook/${p.slug}`, + href: docsHref(version, `handbook/${p.slug}`), label: p.title, - active: navLink(`/docs/handbook/${p.slug}`), + active: navLink(docsHref(version, `handbook/${p.slug}`)), })), }); } @@ -345,18 +364,18 @@ function DocsNav({ label: t("docs.endpoints"), icon: Server, items: reference.tags.map((tag) => ({ - href: `/docs/reference/endpoints/${tag.name}`, + href: docsHref(version, `reference/endpoints/${tag.name}`), label: tag.name, - active: navLink(`/docs/reference/endpoints/${tag.name}`), + active: navLink(docsHref(version, `reference/endpoints/${tag.name}`)), })), }); groups.push({ label: t("docs.schemas"), icon: Library, items: reference.schemas.map((s) => ({ - href: `/docs/reference/schemas/${s.name}`, + href: docsHref(version, `reference/schemas/${s.name}`), label: s.name, - active: navLink(`/docs/reference/schemas/${s.name}`), + active: navLink(docsHref(version, `reference/schemas/${s.name}`)), })), }); } @@ -365,9 +384,7 @@ function DocsNav({ label: t("docs.releases"), icon: Tag, items: versions.map((v) => ({ - href: v.version === (version ?? versions[0]?.version) && version !== null - ? `/docs/releases/${v.version}` - : `/docs/releases/${v.version}`, + href: `/docs/releases/${v.version}`, label: v.version, active: navLink(`/docs/releases/${v.version}`), })), @@ -708,7 +725,7 @@ function ReleaseNoteView({ version }: { version: string }) { {version} ; + return ; } // --------------------------------------------------------------------------- @@ -803,9 +821,11 @@ export default function Docs() { }); const { data: releases, error: releasesError } = useJson(`${DOCS_BASE}/index.json`); - const { data: handbook, error: handbookError } = useJson( - version === null ? `${DOCS_BASE}/handbook/index.json` : null, - ); + + const activeRelease = releases?.find((r) => r.version === version) ?? null; + const handbookAvailable = version === null || activeRelease?.hasHandbook; + const handbookUrl = handbookAvailable ? `${DOCS_BASE}/${docsFile(version, "handbook/index.json")}` : null; + const { data: handbook, error: handbookError } = useJson(handbookUrl); const isCurrentVersion = version === null || @@ -814,7 +834,7 @@ export default function Docs() { const refUrl = version === null ? `${DOCS_BASE}/reference.json` - : releases?.find((r) => r.version === version)?.hasReference + : activeRelease?.hasReference ? `${DOCS_BASE}/versions/${version}.json` : null; @@ -832,20 +852,13 @@ export default function Docs() { return null; }, [releases, versionInfo]); - // Redirect old-style /docs/vX.Y.Z to /docs/releases/vX.Y.Z - useEffect(() => { - if (version !== null && path.length === 0) { - setLocation(`/docs/releases/${version}`, { replace: true }); - } - }, [version, path, setLocation]); - const handleVersionChange = (v: string | null) => { setSearchQuery(""); if (v === null || v === currentVersion) { setLocation("/docs"); return; } - setLocation(`/docs/releases/${v}`); + setLocation(`/docs/${v}`); }; // ---- route resolution ---- @@ -864,7 +877,12 @@ export default function Docs() { let content: React.ReactNode = null; if (version !== null && path.length === 0) { - content = ; + content = + handbook && handbook.length > 0 ? ( + + ) : ( + + ); } else if (section === "home") { content = handbook && handbook.length > 0 ? ( @@ -913,7 +931,7 @@ export default function Docs() { {reference.tags.map((tg) => ( {tg.name} @@ -930,7 +948,7 @@ export default function Docs() { {reference.schemas.map((s) => ( {s.name} @@ -955,75 +973,77 @@ export default function Docs() { const showSearch = version === null && section !== "releases"; return ( -
-
-
-
- - - - - - {t("docs.nav")} + +
+
+
+
+ + + + + + {t("docs.nav")} + + + + + + toolr + + + {t("docs.backToApp")} + + +
+
+ +
+
+
+ +
+ + + {showSearch && setSearchQuery("")} />} + +
+ +
{content}
-
- -
-
-
- -
- - - {showSearch && setSearchQuery("")} />} - -
- -
{content}
-
+ ); } diff --git a/artifacts/toolrate/src/pages/tool-detail.tsx b/artifacts/toolrate/src/pages/tool-detail.tsx index 00dc42d..f3448b5 100644 --- a/artifacts/toolrate/src/pages/tool-detail.tsx +++ b/artifacts/toolrate/src/pages/tool-detail.tsx @@ -61,6 +61,7 @@ import { import { customFetch } from "@workspace/api-client-react"; import { recordRecentTool } from "@/lib/recent-tools"; import { FieldHelp } from "@/components/field-help"; +import { GuideHelp } from "@/components/guide-help"; const ratingSchema = z.object({ usefulness: z.number().min(1).max(5), @@ -615,7 +616,10 @@ export default function ToolDetail() { { if (!o) setCostDialogOpen(false); }}> - {editCost ? t("detail.edit") + " " + t("detail.costs") : t("detail.addCost")} + + {editCost ? t("detail.edit") + " " + t("detail.costs") : t("detail.addCost")} + + Manage license cost information for this tool.
@@ -765,7 +769,10 @@ export default function ToolDetail() { {isReviewFormOpen && ( - {t("detail.addReview")} + + {t("detail.addReview")} + + Share your experience with {tool.name} diff --git a/artifacts/toolrate/src/pages/tool-edit.tsx b/artifacts/toolrate/src/pages/tool-edit.tsx index 5549d1c..1069298 100644 --- a/artifacts/toolrate/src/pages/tool-edit.tsx +++ b/artifacts/toolrate/src/pages/tool-edit.tsx @@ -31,6 +31,7 @@ import { FeatureInput } from "@/components/feature-input"; import { TagInput } from "@/components/tag-input"; import { useAuth } from "@/hooks/use-auth"; import { FieldHelp } from "@/components/field-help"; +import { GuideHelp } from "@/components/guide-help"; const toolSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), @@ -160,6 +161,7 @@ export default function ToolEdit() { Tool Details + Modify the tool information below. diff --git a/artifacts/toolrate/src/pages/tool-new.tsx b/artifacts/toolrate/src/pages/tool-new.tsx index 12db671..65c15cd 100644 --- a/artifacts/toolrate/src/pages/tool-new.tsx +++ b/artifacts/toolrate/src/pages/tool-new.tsx @@ -19,6 +19,7 @@ import { FeatureInput } from "@/components/feature-input"; import { TagInput } from "@/components/tag-input"; import { useAuth } from "@/hooks/use-auth"; import { FieldHelp } from "@/components/field-help"; +import { GuideHelp } from "@/components/guide-help"; const toolSchema = z.object({ name: z.string().min(2, "Name must be at least 2 characters"), @@ -123,6 +124,7 @@ export default function ToolNew() { Tool Details + Provide the basic information about the tool. diff --git a/artifacts/toolrate/src/pages/watchlist.tsx b/artifacts/toolrate/src/pages/watchlist.tsx index 475e947..ca52835 100644 --- a/artifacts/toolrate/src/pages/watchlist.tsx +++ b/artifacts/toolrate/src/pages/watchlist.tsx @@ -6,6 +6,7 @@ import { useAuth } from "@/hooks/use-auth"; import { useWatchlist } from "@/hooks/use-watchlist"; import { Layout } from "@/components/layout"; import { ToolCard } from "@/components/tool-card"; +import { GuideHelp } from "@/components/guide-help"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { ShieldAlert, Bookmark } from "lucide-react"; @@ -42,7 +43,10 @@ export default function Watchlist() {
-

{t("watchlist.title")}

+

+ {t("watchlist.title")} + +

{t("watchlist.subtitle")}

diff --git a/docs/releases/TEMPLATE.md b/docs/releases/TEMPLATE.md index 28f1c5e..b5014d6 100644 --- a/docs/releases/TEMPLATE.md +++ b/docs/releases/TEMPLATE.md @@ -5,7 +5,7 @@ > nicht zutreffen entfernen. Die Seite wird unter `/docs/vX.Y.Z` in der App > angezeigt. -**Datum:** YYYY-MM-DD · **Tag:** [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/tags/vX.Y.Z) +**Datum:** YYYY-MM-DD · **Tag:** [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/vX.Y.Z) ## Neue Features @@ -32,4 +32,4 @@ ## Links - Commit: [``](https://git.kubebase.de/admin/tool-evaluator/commit/) -- Tag: [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/tags/vX.Y.Z) +- Tag: [`vX.Y.Z`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/vX.Y.Z) diff --git a/docs/releases/v0.6.0.md b/docs/releases/v0.6.0.md index 38758c7..6e254c0 100644 --- a/docs/releases/v0.6.0.md +++ b/docs/releases/v0.6.0.md @@ -1,6 +1,6 @@ # v0.6.0 — Release Notes -**Datum:** 2026-08-03 · **Tag:** [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.6.0) +**Datum:** 2026-08-03 · **Tag:** [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0) ## Neue Features @@ -34,4 +34,4 @@ ## Links - Commit: [`2f66fff`](https://git.kubebase.de/admin/tool-evaluator/commit/2f66fff) -- Tag: [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.6.0) +- Tag: [`v0.6.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.6.0) diff --git a/docs/releases/v0.7.0.md b/docs/releases/v0.7.0.md index ae5f12e..daf5078 100644 --- a/docs/releases/v0.7.0.md +++ b/docs/releases/v0.7.0.md @@ -1,6 +1,6 @@ # v0.7.0 — Release Notes -**Datum:** 2026-08-03 · **Tag:** [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.7.0) +**Datum:** 2026-08-03 · **Tag:** [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0) ## Neue Features @@ -32,4 +32,4 @@ ## Links - Commit: [`520f917`](https://git.kubebase.de/admin/tool-evaluator/commit/520f917) -- Tag: [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.7.0) +- Tag: [`v0.7.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.7.0) diff --git a/docs/releases/v0.8.0.md b/docs/releases/v0.8.0.md index 06beddd..a38d36f 100644 --- a/docs/releases/v0.8.0.md +++ b/docs/releases/v0.8.0.md @@ -1,6 +1,6 @@ # v0.8.0 — Release Notes -**Datum:** 2026-08-03 · **Tag:** [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.0) +**Datum:** 2026-08-03 · **Tag:** [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0) ## Neue Features @@ -43,4 +43,4 @@ ## Links - Commit: [`6c92b63`](https://git.kubebase.de/admin/tool-evaluator/commit/6c92b63) -- Tag: [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.0) +- Tag: [`v0.8.0`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.0) diff --git a/docs/releases/v0.8.1.md b/docs/releases/v0.8.1.md index afe4e4f..fe5622a 100644 --- a/docs/releases/v0.8.1.md +++ b/docs/releases/v0.8.1.md @@ -1,6 +1,6 @@ # v0.8.1 — Release Notes -**Datum:** 2026-08-03 · **Tag:** [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.1) +**Datum:** 2026-08-03 · **Tag:** [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1) ## Neue Features @@ -44,4 +44,4 @@ ## Links - Commit: [`fdf2d74`](https://git.kubebase.de/admin/tool-evaluator/commit/fdf2d74) -- Tag: [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.1) +- Tag: [`v0.8.1`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.1) diff --git a/docs/releases/v0.8.1/handbook/administration.md b/docs/releases/v0.8.1/handbook/administration.md new file mode 100644 index 0000000..585e0ce --- /dev/null +++ b/docs/releases/v0.8.1/handbook/administration.md @@ -0,0 +1,61 @@ + +# Administration + +Der Bereich **Admin** (`/admin`) ist ausschließlich für Admins zugänglich. +Ohne Admin-Rolle erscheint eine Zugriffsverweigerung. + +> Oben rechts führt die Schaltfläche **Redundanz-Dashboard** zur automatischen +> Doppelungs-Erkennung (siehe [Redundanz](/docs/handbook/redundanz)). + +## Tab „Nutzer" + +Verwaltung der lokalen Konten. + +- **Nutzer hinzufügen:** Benutzername (Pflicht), Passwort (mind. 6 Zeichen), + E-Mail (optional), **Rolle** (User/Admin), **Tarif** (Free/Premium/Enterprise). +- **Nutzer bearbeiten:** Rolle, Tarif und (für lokale Konten) ein neues Passwort + setzen. Für OIDC-Konten wird die Passwortverwaltung im Identitätsanbieter + (z. B. Keycloak) angeboten. +- **Nutzer löschen:** Entfernt das Konto endgültig (nicht für das eigene Konto). + +API-Referenz: +[`POST /users`](/docs/reference/endpoints/users#createUser), +[`PATCH /users/{id}`](/docs/reference/endpoints/users#updateUser), +[`DELETE /users/{id}`](/docs/reference/endpoints/users#deleteUser). + +## Tab „Tools" + +Zentraler Zugriff auf den Tool-Katalog. + +- **Suchen** nach Tools. +- Tools einzeln ansehen, bearbeiten oder in den Papierkorb verschieben. +- **Massenaktion:** mehrere Tools auswählen und in den Papierkorb verschieben + (Bestätigungsdialog; soft gelöschte Tools sind aus allen öffentlichen Ansichten + entfernt und können wiederhergestellt oder endgültig gelöscht werden). + +## Tab „Audit-Log" + +Chronologisches Protokoll aller Anlage-, Änderungs- und Löschvorgänge +(max. 100 Einträge): Aktion, Entität + ID, Zeitstempel, ausführende Person und +geänderte Felder. + +API-Referenz: [`GET /audit-logs`](/docs/reference/endpoints/audit#listAuditLogs). + +## Tab „System" + +Versionsinformationen der laufenden Instanz: + +- **Version** (z. B. `v0.8.1`), +- **Commit** (7-stelliger SHA, verlinkt zum Repository), +- **Build-Datum**, +- **Papierkorb-Aufbewahrung** („N Tage" oder „Für immer"). + +## Tool-Verknüpfungen (Admin) + +Auf der Detailseite eines Tools kannst du als Admin **Verknüpfungen** +(eigene/„manual" sowie automatisch erkannte) verwalten: + +- **Tool verknüpfen:** Dialog mit Tool-ID, **Beziehungstyp** + (Ähnlich / Ersetzt / Abgelöst durch) und optionalen Notizen. +- Beziehungstypen werden als Badges auf der Detailseite angezeigt. +- Manuelle Verknüpfungen lassen sich per Papierkorb-Icon wieder entfernen. diff --git a/docs/releases/v0.8.1/handbook/analytics.md b/docs/releases/v0.8.1/handbook/analytics.md new file mode 100644 index 0000000..3cfacd8 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/analytics.md @@ -0,0 +1,29 @@ + +# Analytics + +Der Bereich **Analytics** (`/analytics`) ist ein öffentliches Dashboard mit +Kennzahlen und Diagrammen auf Basis aller Tools und Bewertungen. + +## Kennzahlen (KPI-Karten) + +- **Anzahl Tools** — wie viele Tools sind im Katalog erfasst. +- **Anzahl Bewertungen** — wie viele Bewertungen wurden insgesamt abgegeben. +- **Aktive Kategorien** — wie viele Kategorien existieren. +- **Durchschnittliche Bewertung** — globaler kombinierter Wert. + +## Diagramme + +| Diagramm | Inhalt | +| --- | --- | +| **Top 8 Tools** | Balkendiagramm der Tools mit der höchsten kombinierten Punktzahl (0–5) | +| **Tools je Kategorie** | Radar-Diagramm der Tool-Anzahl pro Kategorie | +| **Punkteverteilung** | Zwei horizontale Balken-Diagramme (Nützlichkeit & Bedienbarkeit) pro Stern | + +Die Diagramme sind interaktiv (Tooltips beim Überfahren). + +## API + +- [`GET /analytics/summary`](/docs/reference/endpoints/analytics#getAnalyticsSummary) +- [`GET /analytics/top-tools`](/docs/reference/endpoints/analytics#getTopTools) +- [`GET /analytics/by-category`](/docs/reference/endpoints/analytics#getAnalyticsByCategory) +- [`GET /analytics/rating-distribution`](/docs/reference/endpoints/analytics#getRatingDistribution) diff --git a/docs/releases/v0.8.1/handbook/bewerten.md b/docs/releases/v0.8.1/handbook/bewerten.md new file mode 100644 index 0000000..b4d054e --- /dev/null +++ b/docs/releases/v0.8.1/handbook/bewerten.md @@ -0,0 +1,39 @@ + +# Bewerten + +Auf der Detailseite eines Tools kannst du deine Erfahrung teilen. Klicke auf +**Bewertung abgeben** (erfordert ein Konto). + +## Formularfelder + +| Feld | Pflicht | Hinweise | +| --- | --- | --- | +| **Nützlichkeit** | Ja | 1–5 Sterne | +| **Bedienbarkeit** | Ja | 1–5 Sterne | +| **Kommentar** | Nein | Freitext | +| **Name** | Nein | Standard „Anonym" | + +Neben den Feldern führt das **?‑Icon** direkt zur zugehörigen Feldbeschreibung +in der [Datenmodell-Referenz](/docs/reference/schemas/ratinginput). + +## Was passiert nach dem Abgeben? + +- Deine Bewertung wird sofort gespeichert und erscheint in der + **Bewertungsliste** der Detailseite. +- Die **Durchschnittswerte** (Nützlichkeit, Bedienbarkeit, Kombiniert) und die + **Punkteverteilung** werden aktualisiert. +- Die **Statistiken** im Bereich [Analytics](/docs/handbook/analytics) werden + neu berechnet. + +## Statistik-Bereiche auf der Detailseite + +- **Bewertungsübersicht:** Nützlichkeit & Bedienbarkeit als Durchschnitt mit + Fortschrittsbalken. +- **Punkteverteilung:** Anzahl der Bewertungen pro Stern (1★–5★). +- **Verlauf:** Linienchart der kombinierten/Teilwerte über die Zeit + (erst ab mehreren Bewertungen sichtbar). + +## API + +- [`POST /tools/{id}/ratings`](/docs/reference/endpoints/ratings#createRating) — Bewertung abgeben +- [`GET /tools/{id}/ratings`](/docs/reference/endpoints/ratings#listToolRatings) — Bewertungen eines Tools diff --git a/docs/releases/v0.8.1/handbook/datenmodell.md b/docs/releases/v0.8.1/handbook/datenmodell.md new file mode 100644 index 0000000..618e160 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/datenmodell.md @@ -0,0 +1,70 @@ + +# Datenmodell + +Dieses Kapitel erklärt die zentralen Datenobjekte von toolr auf Ebene der +Anwendung. Die vollständige, automatisch generierte Referenz aller Felder, +Typen und Constraints findest du in der +[API-Referenz](/docs/reference/schemas/tool). + +## Tool + +Das Herzstück: ein im Katalog erfasstes Werkzeug. + +| Eigenschaft | Beschreibung | +| --- | --- | +| `id` | Eindeutige Kennung | +| `name` | Anzeigename | +| `description` | Beschreibung (Was macht das Tool?) | +| `category` | Kategorie-Zuordnung | +| `websiteUrl` | Offizielle Website (optional) | +| `iconUrl` | Logo-/Icon-URL (optional) | +| `features` | Liste von Fähigkeiten | +| `tags` | Liste von Schlagwörtern | +| `createdAt` / `updatedAt` | Zeitstempel | +| `createdBy` | Erstellende Person | +| `deletedAt` / `deletedBy` | Soft-Löschung (Papierkorb) | + +Eingabe-Formulare verwenden die abgeleiteten Schemas +[`ToolInput`](/docs/reference/schemas/toolinput) und +[`ToolUpdate`](/docs/reference/schemas/toolupdate). +Aggregierte Ansichten liefert [`ToolWithStats`](/docs/reference/schemas/toolwithstats) +(z. B. mit Durchschnittsbewertung). + +## Rating (Bewertung) + +Eine einzelne Bewertung zu einem Tool: + +- `usefulness` (Nützlichkeit, 1–5) und `usability` (Bedienbarkeit, 1–5) +- optional `comment` und ein Anzeigename (`reviewerName`) +- Zeitstempel + +Eingabe-Schema: [`RatingInput`](/docs/reference/schemas/ratinginput). + +## User & Auth + +- [`User`](/docs/reference/schemas/user) — Benutzerkonto mit Rolle (User/Admin) + und Tarif (Free/Premium/Enterprise). +- [`AuthUser`](/docs/reference/schemas/authuser) — das angemeldete Profil + inklusive `entitlements` (verfügbare Features). +- [`UserPreferences`](/docs/reference/schemas/userpreferences) — Ansichts- und + Dichte-Präferenzen sowie die `watchlist` (Liste von Tool-IDs). + +## Analytics + +Die Statistik-Endpunkte liefern aggregierte Daten: + +- [`AnalyticsSummary`](/docs/reference/schemas/analyticssummary) — globale + Kennzahlen (Anzahl Tools/Bewertungen, Kategorien, Durchschnitt). +- [`TopToolEntry`](/docs/reference/schemas/TopToolEntry) — ein Eintrag der + Top-Tools.- [`CategoryStats`](/docs/reference/schemas/categorystats) — Tool-Anzahl je + Kategorie. +- [`RatingDistribution`](/docs/reference/schemas/ratingdistribution) — + Punkteverteilung (Nützlichkeit & Bedienbarkeit). +- [`ScoreBucket`](/docs/reference/schemas/scorebucket) — ein Werte-Bucket. + +## Weitere + +- [`VersionInfo`](/docs/reference/schemas/versioninfo) — Version, Commit-SHA, + Build-Datum und Papierkorb-Aufbewahrung der laufenden Instanz. +- [`AuditLog`](/docs/reference/schemas/auditlog) — ein Protokolleintrag + (Aktion, Entität, Zeitstempel, Akteur, Änderungen). diff --git a/docs/releases/v0.8.1/handbook/getting-started.md b/docs/releases/v0.8.1/handbook/getting-started.md new file mode 100644 index 0000000..0fd7820 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/getting-started.md @@ -0,0 +1,53 @@ + +# Erste Schritte + +Diese Seite führt dich durch die wichtigsten Abläufe in toolr — vom ersten +Besuch bis zum Anlegen und Bewerten eines Tools. + +## 1. Anmelden + +Die meisten Aktionen (Tool anlegen, bewerten, Watchlist, Vergleichen) erfordern +ein Konto. Klicke unten links auf **Anmelden**. Je nach Konfiguration der +Instanz hast du zwei Möglichkeiten: + +- **Lokale Konten:** Benutzername + Passwort. Der Zugang wird von einem Admin + angelegt (siehe [Administration](/docs/handbook/administration)). +- **OIDC (SSO):** Anmelden mit dem konfigurierten Identitätsanbieter (z. B. + Keycloak). + +Welcher Modus aktiv ist, steht im Endpunkt +[`GET /auth/mode`](/docs/reference/endpoints/auth#getAuthMode). Details findest +du im Abschnitt [Anmelden & Konto](/docs/handbook/konto). + +## 2. Tools finden + +Öffne den Bereich **Tools durchsuchen**: + +- **Suchen** — Volltextsuche über Name & Beschreibung (Tastenkürzel `/`). +- **Filtern** — nach Kategorie, Tags, Features und Mindestbewertung + (`minRating`). +- **Sortieren** — nach Aktualität, Top-Bewertung, meistbewertet, Name + (auf-/absteigend) oder letztem Update. + +Alle Optionen im Detail: [Tools finden & durchsuchen](/docs/handbook/tools-finden). + +## 3. Tool anlegen + +Gehe auf **Tool hinzufügen** und fülle das Formular aus. Details zu jedem Feld +findest du im Abschnitt [Tool anlegen](/docs/handbook/tool-anlegen) und in der +[Feld-Referenz](/docs/reference/schemas/toolinput). + +## 4. Bewerten + +Auf der Detailseite eines Tools kannst du **Nützlichkeit** und **Bedienbarkeit** +(jeweils 1–5) vergeben und optional einen Kommentar hinterlassen. Deine +Bewertung fließt sofort in die Statistiken ein. +Siehe [Bewerten](/docs/handbook/bewerten). + +## 5. Weiterführend + +- [Tools vergleichen](/docs/handbook/vergleichen) +- [Watchlist](/docs/handbook/watchlist) +- [Analytics](/docs/handbook/analytics) +- [Pläne & Berechtigungen](/docs/handbook/plaene) +- [Administration](/docs/handbook/administration) diff --git a/docs/releases/v0.8.1/handbook/index.json b/docs/releases/v0.8.1/handbook/index.json new file mode 100644 index 0000000..9379656 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/index.json @@ -0,0 +1,104 @@ +[ + { + "slug": "index", + "file": "index.md", + "title": "Überblick", + "order": 1 + }, + { + "slug": "getting-started", + "file": "getting-started.md", + "title": "Erste Schritte", + "order": 2 + }, + { + "slug": "konto", + "file": "konto.md", + "title": "Anmelden & Konto", + "order": 3 + }, + { + "slug": "tools-finden", + "file": "tools-finden.md", + "title": "Tools finden & durchsuchen", + "order": 4 + }, + { + "slug": "tool-anlegen", + "file": "tool-anlegen.md", + "title": "Tool anlegen", + "order": 5 + }, + { + "slug": "tool-bearbeiten", + "file": "tool-bearbeiten.md", + "title": "Tool bearbeiten & löschen", + "order": 6 + }, + { + "slug": "bewerten", + "file": "bewerten.md", + "title": "Bewerten", + "order": 7 + }, + { + "slug": "watchlist", + "file": "watchlist.md", + "title": "Watchlist", + "order": 8 + }, + { + "slug": "vergleichen", + "file": "vergleichen.md", + "title": "Vergleichen", + "order": 9 + }, + { + "slug": "analytics", + "file": "analytics.md", + "title": "Analytics", + "order": 10 + }, + { + "slug": "plaene", + "file": "plaene.md", + "title": "Pläne & Berechtigungen", + "order": 11 + }, + { + "slug": "kosten", + "file": "kosten.md", + "title": "Kosten erfassen", + "order": 12 + }, + { + "slug": "administration", + "file": "administration.md", + "title": "Administration", + "order": 13 + }, + { + "slug": "redundanz", + "file": "redundanz.md", + "title": "Redundanz-Dashboard", + "order": 14 + }, + { + "slug": "papierkorb", + "file": "papierkorb.md", + "title": "Papierkorb", + "order": 15 + }, + { + "slug": "tastatur", + "file": "tastatur.md", + "title": "Tastenkürzel & Kommandopalette", + "order": 16 + }, + { + "slug": "datenmodell", + "file": "datenmodell.md", + "title": "Datenmodell", + "order": 17 + } +] \ No newline at end of file diff --git a/docs/releases/v0.8.1/handbook/index.md b/docs/releases/v0.8.1/handbook/index.md new file mode 100644 index 0000000..f527a6e --- /dev/null +++ b/docs/releases/v0.8.1/handbook/index.md @@ -0,0 +1,52 @@ + +# Willkommen bei toolr + +toolr ist eine Plattform zum **Entdecken, Bewerten und Vergleichen von +Entwicklungstools**. Nutzer:innen pflegen einen gemeinsamen Katalog von Tools, +vergeben Bewertungen (Nützlichkeit & Bedienbarkeit) und nutzen Statistiken, um +die richtige Wahl zu treffen. + +## Was kannst du mit toolr tun? + +| Funktion | Beschreibung | Sichtbarkeit | +| --- | --- | --- | +| **Tools durchsuchen** | Katalog filtern, sortieren und durchsuchen | Alle | +| **Tool anlegen** | Neues Tool mit Beschreibung, Kategorie, Features & Tags eintragen | Angemeldet | +| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller:in oder Admin) | Angemeldet | +| **Bewerten** | Nützlichkeit & Bedienbarkeit (1–5) plus Kommentar vergeben | Angemeldet | +| **Watchlist** | Tools als Favoriten speichern | Premium | +| **Vergleichen** | Tools nebeneinander gegenüberstellen | Premium | +| **Kosten erfassen** | Lizenz- und Kostenmodelle je Tool eintragen | Premium | +| **Analytics** | Statistiken, Top-Tools, Verteilungen | Alle | +| **Papierkorb** | Soft-gelöschte Tools wiederherstellen oder endgültig löschen | Premium | +| **Admin** | Nutzerverwaltung, Audit-Log, Systeminformationen | Admin | +| **Redundanz** | Automatische Doppelungs-Erkennung | Admin | + +## Wie diese Doku aufgebaut ist + +- **User Guide** (diese Seiten): Schritt-für-Schritt-Anleitungen für alle + Funktionen — von den [Ersten Schritten](/docs/handbook/getting-started) bis + zur [Administration](/docs/handbook/administration). +- **API-Referenz**: automatisch aus der OpenAPI-Spezifikation generiert — alle + [Endpunkte](/docs/reference/endpoints/tools) und + [Datenfelder](/docs/reference/schemas/toolinput) der aktuellen Version. +- **Release-Notes**: Was ist in welcher [Version](/docs/releases/v0.8.1) neu. + +## Der Einstieg + +Der schnellste Weg: + +1. **Anmelden** — ohne Konto kannst du nur stöbern + (siehe [Erste Schritte](/docs/handbook/getting-started#1-anmelden)). +2. **Tools finden** — Suche, Filter und Sortierung im Bereich + [Tools durchsuchen](/docs/handbook/tools-finden). +3. **Tool anlegen** — über „Tool hinzufügen" + ([Anleitung](/docs/handbook/tool-anlegen)). +4. **Bewerten** — auf der Detailseite eines Tools + ([Anleitung](/docs/handbook/bewerten)). + +## Kontakt & Quellcode + +Der Quellcode liegt unter +[git.kubebase.de/admin/tool-evaluator](https://git.kubebase.de/admin/tool-evaluator) — +über das Repository-Icon oben rechts erreichst du ihn jederzeit. diff --git a/docs/releases/v0.8.1/handbook/konto.md b/docs/releases/v0.8.1/handbook/konto.md new file mode 100644 index 0000000..40aae3d --- /dev/null +++ b/docs/releases/v0.8.1/handbook/konto.md @@ -0,0 +1,53 @@ + +# Anmelden & Konto + +## Anmelden + +Klicke unten links in der Seitenleiste auf **Anmelden**. Je nach Konfiguration +der Instanz: + +- **Lokale Konten:** Benutzername und Passwort eingeben. Die Konten werden von + einem Admin angelegt (siehe [Administration](/docs/handbook/administration)). +- **OIDC (SSO):** Du wirst an den konfigurierten Identitätsanbieter + weitergeleitet und meldest dich dort an. + +Der aktive Modus steht im Endpunkt +[`GET /auth/mode`](/docs/reference/endpoints/auth#getAuthMode). + +> Die Login-Seite erreichst du direkt unter `/login`. Nach erfolgreicher +> Anmeldung wirst du zur ursprünglich aufgerufenen Seite zurückgeleitet. + +## Benutzerprofil + +Dein Profil (Avatar, Name, E-Mail, Tarif) siehst du unten links im +Benutzermenü. Dort stehen dir folgende Aktionen zur Verfügung: + +- **Watchlist** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif). +- **Papierkorb** — wiederherstellbare, gelöschte Tools (Premium/Enterprise). +- **Passwort ändern** — für lokale Konten direkt in toolr; für OIDC-Konten wird + die Passwortverwaltung im Identitätsanbieter angeboten. +- **Abmelden** — beendet deine Sitzung. + +## Passwort ändern (lokales Konto) + +1. Öffne das Benutzermenü unten links. +2. Wähle **Passwort ändern**. +3. Gib das **aktuelle** sowie ein **neues** Passwort ein (mind. 6 Zeichen) und + bestätige es. +4. Speichern — das Passwort wird sofort übernommen. + +API-Referenz: [`POST /auth/me/password`](/docs/reference/endpoints/auth#changeMyPassword). + +## Anzeigeeinstellungen + +Über die Schaltflächen oben rechts kannst du: + +- **Sprache** wechseln (Deutsch / Englisch), +- **Theme** umschalten (Hell / Dunkel / System), +- die **Listenansicht** und **Dichte** im Bereich Tools durchsuchen anpassen + (siehe [Tools finden & durchsuchen](/docs/handbook/tools-finden)). + +Deine Präferenzen (inkl. Watchlist) werden im Endpunkt +[`GET /auth/me/preferences`](/docs/reference/endpoints/auth#getMePreferences) +gespeichert und über [`PUT /auth/me/preferences`](/docs/reference/endpoints/auth#updateMePreferences) +aktualisiert. diff --git a/docs/releases/v0.8.1/handbook/kosten.md b/docs/releases/v0.8.1/handbook/kosten.md new file mode 100644 index 0000000..8904047 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/kosten.md @@ -0,0 +1,35 @@ + +# Kosten erfassen + +Auf der Detailseite eines Tools kannst du Kosten- und Lizenzmodelle eintragen, +damit die Gesamtkosten je Tool transparent werden. + +> Kosten ist ein **Premium-Feature** (`costs`, Premium/Enterprise). Admins +> haben immer Zugriff. + +## Kosten hinzufügen + +Klicke auf **Kosten hinzufügen** im Kosten-Bereich der Detailseite und fülle +das Formular aus: + +| Feld | Hinweise | +| --- | --- | +| **Lizenztyp** | Free / Subscription / One-Time / Usage-Based | +| **Abrechnungszeitraum** | Nur für „Subscription": Monatlich / Quartalsweise / Jährlich | +| **Kosten** | Betrag als Zahl | +| **Währung** | EUR / USD / GBP / CHF | +| **Notizen** | Optionaler Freitext | + +Speichern legt den Eintrag an. Jeder Kosten-Eintrag wird als Karte mit +Lizenz-Badge, Abrechnungszeitraum, Betrag (`Betrag Währung` bzw. „Free") und +Notizen angezeigt. + +## Kosten bearbeiten & löschen + +Beim Überfahren einer Kosten-Karte erscheinen die Aktionen **Bearbeiten** +(Bleistift) und **Löschen** (Papierkorb). + +## API + +Die Kosten-Daten werden über die Tool-Endpunkte verwaltet +(siehe [API-Referenz](/docs/reference/endpoints/tools)). diff --git a/docs/releases/v0.8.1/handbook/papierkorb.md b/docs/releases/v0.8.1/handbook/papierkorb.md new file mode 100644 index 0000000..3179c74 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/papierkorb.md @@ -0,0 +1,41 @@ + +# Papierkorb + +Der **Papierkorb** (`/trash`) enthält soft gelöschte Tools. Mit Papierkorb-Zugang +können sie wiederhergestellt werden; endgültiges Löschen ist Admins vorbehalten. + +> Der Papierkorb ist ein **Premium-Feature** (`trash`, Premium/Enterprise). +> Admins haben immer Zugriff. + +## Zugang + +Der Papierkorb ist über das Benutzermenü oder die Seitenleiste erreichbar. +Ohne `trash`-Berechtigung erscheint ein Hinweis auf den Tarifwechsel. + +## Wiederherstellen + +- Markiere ein oder mehrere Tools (Checkboxen). +- Klicke auf **Wiederherstellen (N)** — die Tools erscheinen wieder in allen + öffentlichen Ansichten. + +> Wiederherstellen steht jeder Person mit Papierkorb-Zugang zur Verfügung. + +## Endgültig löschen (nur Admin) + +- **Löschen (N)** entfernt die ausgewählten Tools **endgültig** — inklusive + aller Bewertungen, Kosten und Verknüpfungen. Das kann nicht rückgängig + gemacht werden. +- **Papierkorb leeren** entfernt alle soft gelöschten Tools endgültig. + +## Tabelle + +Der Papierkorb listet: Name, Kategorie, **Gelöscht am** (`tt.MM.jjjj HH:mm`), +**Gelöscht von** sowie Aktionen (Wiederherstellen; Löschen nur Admin). Die Suche +filtert nach Namen. + +## API + +- [`GET /tools/trash`](/docs/reference/endpoints/tools#listTrashedTools) — Liste +- [`POST /tools/trash/restore`](/docs/reference/endpoints/tools#restoreTools) — Wiederherstellen +- [`DELETE /tools/trash`](/docs/reference/endpoints/tools#deleteTrashedTools) — Endgültig löschen (Admin) +- [`POST /tools/trash/empty`](/docs/reference/endpoints/tools#emptyTrash) — Papierkorb leeren (Admin) diff --git a/docs/releases/v0.8.1/handbook/plaene.md b/docs/releases/v0.8.1/handbook/plaene.md new file mode 100644 index 0000000..d4546b7 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/plaene.md @@ -0,0 +1,41 @@ + +# Pläne & Berechtigungen + +toolr unterscheidet **Tarife** (Tier) und **Rollen**. Admins umgehen alle +Feature-Beschränkungen. + +## Tarife + +| Tarif | Beschreibung | +| --- | --- | +| **Free** | Grundfunktionen: suchen, filtern, ansehen, Analytics | +| **Premium** | Zusätzlich Watchlist, Vergleichen, Papierkorb, Kosten | +| **Enterprise** | Alle Premium-Features + erweiterter Support | + +### Feature-Berechtigungen + +Premium/Enterprise schalten folgende Features frei: + +| Feature | Funktion | Mehr erfahren | +| --- | --- | --- | +| `compare` | Tools vergleichen | [Vergleichen](/docs/handbook/vergleichen) | +| `watchlist` | Favoritenliste | [Watchlist](/docs/handbook/watchlist) | +| `trash` | Papierkorb (soft gelöschte Tools) | [Papierkorb](/docs/handbook/papierkorb) | +| `costs` | Kosten-/Lizenzmodelle erfassen | [Kosten erfassen](/docs/handbook/kosten) | + +Fehlt dir ein Feature, zeigt die App einen **Upgrade-Hinweis** mit Link zur +Tarifverwaltung. + +## Rollen + +| Rolle | Berechtigungen | +| --- | --- | +| **User** | Standard-Konto: Tools anlegen/bewerten, eigene Tools bearbeiten | +| **Admin** | Alle User-Rechte + Verwaltung, Audit-Log, Redundanz, Papierkorb leeren, Tool-Verknüpfungen | + +Admins passieren **alle** Feature-Checks — auch ohne Premium-Tarif. + +## Tarif-/Rollenverwaltung + +Die Zuordnung von Rolle und Tarif wird durch Admins im Bereich +[Administration](/docs/handbook/administration) (Tab „Nutzer") verwaltet. diff --git a/docs/releases/v0.8.1/handbook/redundanz.md b/docs/releases/v0.8.1/handbook/redundanz.md new file mode 100644 index 0000000..7521ffe --- /dev/null +++ b/docs/releases/v0.8.1/handbook/redundanz.md @@ -0,0 +1,38 @@ + +# Redundanz-Dashboard + +Das **Redundanz-Dashboard** (`/admin/redundancy`) ist ein Admin-Werkzeug zur +automatischen Erkennung doppelter oder stark überlappender Tools — jeweils +pro Kategorie — inklusive Kosten- und Bewertungsvergleich. + +> Der Zugriff ist ausschließlich Admins vorbehalten (die API ist +> admin-geschützt). + +## Aufbau + +- **Pro Kategorie** wird eine Gruppe angezeigt: Name der Kategorie, + Anzahl Tools und Vergleiche sowie ggf. die **gesamten monatlichen Kosten** + (z. B. `€X.XX/mo gesamt`). +- Jedes Tool wird als Karte dargestellt: Name, monatliche Kosten, Anzahl der + Bewertungen, kombinierte Bewertung, Lizenz-Badges und Feature-Anzahl. + +## Vergleiche & Empfehlungen + +Für jedes Tool-Paar erscheint: + +- Tool A vs. Tool B, jeweils mit Bewertung (`X.X ★`) und monatlichen Kosten. +- **Überlappung** in Prozent (Fortschrittsbalken in der Mitte). +- Eine **Empfehlung** mit Konfidenz-Farbe: + - **hoch** (grün), **mittel** (gelb), **niedrig** (grau) +- Das empfohlene, bessere Tool wird mit „Daumen hoch" markiert und begründet. + +## Manuelle Bewertung + +Du kannst ein Paar manuell bewerten: Klicke auf Tool A oder Tool B, um +festzuhalten, welches besser ist. Die Auswahl wird gespeichert und die +Darstellung aktualisiert. + +## API + +- [`GET /api/admin/redundancy`](#) — Daten laden (admin-geschützt) +- [`POST /api/admin/redundancy/evaluate`](#) — manuelle Bewertung speichern diff --git a/docs/releases/v0.8.1/handbook/tastatur.md b/docs/releases/v0.8.1/handbook/tastatur.md new file mode 100644 index 0000000..7eb6f17 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/tastatur.md @@ -0,0 +1,36 @@ + +# Tastenkürzel & Kommandopalette + +## Kommandopalette + +Die Kommandopalette ist die zentrale Schnellnavigation: + +- Öffnen mit **`⌘K`** (macOS) bzw. **`Ctrl+K`** (Windows/Linux). +- Alternativ über die Suchleiste oben rechts („Tools suchen… ⌘K") oder das + Such-Icon auf Mobilgeräten. + +### Leerer Zustand + +Ohne Eingabe zeigt die Palette: + +- **Zuletzt angesehen** — die letzten 5 Tools, die du besucht hast. +- **Navigation** — Tools durchsuchen, Tool hinzufügen, Analytics sowie + (abhängig von Berechtigungen) Watchlist, Papierkorb und Admin. + +### Suche + +Tippe, um live nach Tools zu suchen (max. 10 Ergebnisse, inkl. Bewertung +`X.X★`). + +## Tastenkürzel im Überblick + +| Kürzel | Aktion | +| --- | --- | +| `⌘K` / `Ctrl+K` | Kommandopalette öffnen | +| `/` | Suche im Bereich „Tools durchsuchen" fokussieren | + +## Weitere Hinweise + +- **Zuletzt angesehen** wird lokal im Browser gespeichert (max. 5 Einträge). +- Die Seitenleiste (linke Navigation) ist auf Desktop einklappbar; der + Breadcrumb oben zeigt deinen aktuellen Ort. diff --git a/docs/releases/v0.8.1/handbook/tool-anlegen.md b/docs/releases/v0.8.1/handbook/tool-anlegen.md new file mode 100644 index 0000000..db93745 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/tool-anlegen.md @@ -0,0 +1,46 @@ + +# Tool anlegen + +Um ein neues Tool zum Katalog hinzuzufügen, klicke auf **Tool hinzufügen** +(`/tools/new`). Das Anlegen erfordert ein Konto — ohne Anmeldung erscheint ein +Hinweis mit Login-Button. + +## Formularfelder + +| Feld | Pflicht | Hinweise | +| --- | --- | --- | +| **Name** | Ja | Mind. 2 Zeichen | +| **Kategorie** | Ja | Auswahlliste; neue Kategorien lassen sich direkt anlegen | +| **Website URL** | Nein | Gültige URL (z. B. `https://...`) | +| **Icon / Logo URL** | Nein | Gültige URL; Vorschau wird live angezeigt | +| **Beschreibung** | Ja | Mind. 10 Zeichen; beschreibe, was das Tool tut | +| **Features** | Nein | Dynamische Liste mit Autovervollständigung (max. 6) | +| **Tags** | Nein | Dynamische Liste mit Autovervollständigung | + +Neben jedem Feld führt das **?‑Icon** direkt zur zugehörigen Feldbeschreibung +in der [Datenmodell-Referenz](/docs/reference/schemas/toolinput). + +### Kategorie + +- Tippe, um nach bestehenden Kategorien zu suchen. +- Wähle **+ Erstelle „..."**, um eine neue Kategorie anzulegen. + +### Features & Tags + +- **Feature hinzufügen** / **Tag hinzufügen** hängt eine neue Zeile an. +- Die Eingabefelder schlagen bestehende Features/Tags vor + (Autovervollständigung, max. 6 Vorschläge). +- Mit dem **×**‑Button entfernst du einzelne Zeilen. +- Features und Tags helfen beim Filtern und Wiederfinden. + +## Speichern + +Klicke auf **Tool hinzufügen**. Nach erfolgreicher Anlage wirst du auf die +Detailseite des neuen Tools weitergeleitet. + +## API + +- [`POST /tools`](/docs/reference/endpoints/tools#createTool) — Tool anlegen +- [`GET /categories`](/docs/reference/endpoints/tools#listCategories) — Kategorien +- [`GET /features/all`](/docs/reference/endpoints/tools#listAllFeatures) — Features +- [`GET /tags/all`](/docs/reference/endpoints/tools#listAllTags) — Tags diff --git a/docs/releases/v0.8.1/handbook/tool-bearbeiten.md b/docs/releases/v0.8.1/handbook/tool-bearbeiten.md new file mode 100644 index 0000000..7f2fd3f --- /dev/null +++ b/docs/releases/v0.8.1/handbook/tool-bearbeiten.md @@ -0,0 +1,33 @@ + +# Tool bearbeiten & löschen + +## Bearbeiten + +Auf der Detailseite eines Tools findest du die Schaltfläche **Bearbeiten** +(nur für die Person, die das Tool angelegt hat, sowie für Admins). + +Die Bearbeitungsseite (`/tools/:id/edit`) enthält dieselben Felder wie beim +Anlegen (Name, Kategorie, Website/Icon-URL, Beschreibung, Features, Tags) — +bereits mit den aktuellen Werten befüllt. + +- **Speichern** übernimmt die Änderungen. +- **Abbrechen** führt zurück zur Detailseite. + +API-Referenz: [`PATCH /tools/{id}`](/docs/reference/endpoints/tools#updateTool). + +## Löschen + +Über **Löschen** auf der Detailseite wird das Tool entfernt. Das Verhalten +hängt von deinem Tarif ab: + +- **Mit Papierkorb-Zugang** (Premium/Enterprise oder Admin): Das Tool wird + **soft gelöscht** — es verschwindet aus allen öffentlichen Ansichten, kann + aber im [Papierkorb](/docs/handbook/papierkorb) wiederhergestellt oder + endgültig gelöscht werden. +- **Ohne Papierkorb-Zugang:** Das Tool wird **endgültig** gelöscht und kann + nicht wiederhergestellt werden. + +Die Löschung ist nur für die Person, die das Tool angelegt hat, sowie für +Admins möglich. + +API-Referenz: [`DELETE /tools/{id}`](/docs/reference/endpoints/tools#deleteTool). diff --git a/docs/releases/v0.8.1/handbook/tools-finden.md b/docs/releases/v0.8.1/handbook/tools-finden.md new file mode 100644 index 0000000..c976515 --- /dev/null +++ b/docs/releases/v0.8.1/handbook/tools-finden.md @@ -0,0 +1,67 @@ + +# Tools finden & durchsuchen + +Der Bereich **Tools durchsuchen** (`/tools`) ist der Einstieg in den Katalog. +Hier kombinierst du Suche, Filter und Sortierung, um genau die Tools zu finden, +die dich interessieren. + +## Suche + +- Die **Suchleiste** durchsucht Name und Beschreibung (Volltext). +- Tastenkürzel: Drücke **`/`**, um die Suche zu fokussieren. +- Die Eingabe ist deaktiviert (Debounce), damit bei jedem Tastendruck sofort + nachgefiltert wird. + +## Filtern + +Über die Schaltfläche **Filter** (mit Badge für die Anzahl aktiver Filter) +öffnest du den Filter-Popover mit: + +- **Tags** — Auswahl über Checkboxen (scrollbare Liste). +- **Features** — Auswahl über Checkboxen. +- **Mindestbewertung** — Schieberegler von 0 bis 5 (Schritte von 0,5); zeigt + z. B. „3.0+" an. + +Aktive Filter erscheinen als **entfernbare Chips** über der Ergebnisliste. +Mit **Filter zurücksetzen** bzw. **Alle entfernen** räumst du sie wieder auf. + +## Sortieren + +Über das Dropdown **Sortieren** stehen folgende Optionen zur Verfügung: + +| Sortierung | Beschreibung | +| --- | --- | +| Neueste | Neue Tools zuerst | +| Top bewertet | Nach kombinierter Bewertung | +| Meistbewertet | Nach Anzahl der Bewertungen | +| Name (A–Z) | Alphabetisch aufsteigend | +| Name (Z–A) | Alphabetisch absteigend | +| Zuletzt aktualisiert | Nach letztem Update | + +## Ansicht & Dichte + +- **Ansicht wechseln:** Raster / Tabelle / Zeilen. +- **Dichte:** gemütlich / kompakt (Schieberegler). + +Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer:innen +zusätzlich serverseitig in den Präferenzen. Ansicht, Dichte, Suche, Filter und +Sortierung werden dabei in die URL übernommen, sodass du Ergebnisse teilen +kannst. + +## Tabellenansicht + +In der Tabellenansicht sind die Spalten **Tool**, **Bewertung** und **Anzahl +Bewertungen** sortierbar. Beim Überfahren einer Zeile erscheint eine Vorschau +mit Bewertungsdetails, Tags und Mini-Balken. + +## Auswählen für Vergleich & Watchlist + +- Auf jeder Karte/Zeile findest du ein **Vergleichs-Icon**, mit dem du Tools zur + [Vergleichsleiste](/docs/handbook/vergleichen) hinzufügst. +- Das **Lesezeichen-Icon** speichert Tools in deiner + [Watchlist](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif). + +## API + +Alle Such-, Filter- und Sortierparameter entsprechen den Query-Parametern von +[`GET /tools`](/docs/reference/endpoints/tools#listTools). diff --git a/docs/releases/v0.8.1/handbook/vergleichen.md b/docs/releases/v0.8.1/handbook/vergleichen.md new file mode 100644 index 0000000..ddcf0de --- /dev/null +++ b/docs/releases/v0.8.1/handbook/vergleichen.md @@ -0,0 +1,42 @@ + +# Vergleichen + +Mit der Vergleichsfunktion stellst du mehrere Tools **nebeneinander** gegenüber — +ideal, um eine fundierte Entscheidung zu treffen. + +> Vergleichen ist ein **Premium-Feature** (Premium/Enterprise) und steht Admins +> immer zur Verfügung. + +## Tools auswählen + +1. Im Bereich **Tools durchsuchen** klickst du auf jeder Karte/Zeile auf das + **Vergleichs-Icon** (Waage). +2. Unten erscheint die **Vergleichsleiste** mit den ausgewählten Tools als + Chips. Du kannst einzelne Tools entfernen (×) oder die Auswahl leeren. +3. Klicke auf **Vergleichen (N)**, um zur Vergleichsansicht zu gelangen. + +> Ohne Premium-Tarif ist der Button gesperrt (Schloss-Icon). Über den +> Dialog gelangst du zum Tarifwechsel +> (siehe [Pläne & Berechtigungen](/docs/handbook/plaene)). + +## Die Vergleichsansicht + +Die Ansicht zeigt eine Tabelle mit einer Spalte pro Tool. Zeilen: + +| Zeile | Inhalt | +| --- | --- | +| **Bewertung** | Sterne + Wert (z. B. `4.2/5`) | +| **Nützlichkeit** | Wert (X.X/5) | +| **Bedienbarkeit** | Wert (X.X/5) | +| **Anzahl Bewertungen** | Anzahl | +| **Beschreibung** | Text | +| **Features** | Badges | +| **Tags** | Badges | +| **Zuletzt aktualisiert** | Datum | + +Der **beste Wert** pro Zeile wird hervorgehoben (mit Trophäen-Icon). + +## API + +Die Vergleichsansicht liest die Daten über +[`GET /compare`](/docs/reference/endpoints/tools#listCompareTools). diff --git a/docs/releases/v0.8.1/handbook/watchlist.md b/docs/releases/v0.8.1/handbook/watchlist.md new file mode 100644 index 0000000..783da0c --- /dev/null +++ b/docs/releases/v0.8.1/handbook/watchlist.md @@ -0,0 +1,34 @@ + +# Watchlist + +Die **Watchlist** ist eine persönliche Favoritenliste. Tools darin kannst du +jederzeit per Klick wieder aufrufen und vergleichen. + +> Die Watchlist ist ein **Premium-Feature** (Premium/Enterprise) und steht +> Admins immer zur Verfügung. + +## Voraussetzung + +Du benötigst einen Tarif mit `watchlist`-Berechtigung. Fehlt diese, erscheint +beim Lesezeichen ein Hinweis auf den Tarifwechsel +(siehe [Pläne & Berechtigungen](/docs/handbook/plaene)). + +## Tool speichern + +- Auf jeder Karte/Zeile im Bereich **Tools durchsuchen** findest du das + **Lesezeichen-Icon**. +- Ein Klick speichert das Tool in deiner Watchlist — das Icon wird gefüllt. +- Ein erneuter Klick entfernt es wieder. + +## Watchlist ansehen + +Öffne die Watchlist über das Benutzermenü oder die Seitenleiste. Sie zeigt alle +gespeicherten Tools als Karten. Das gefüllte Lesezeichen auf einer Karte +entfernt das Tool aus der Liste. + +## Wo wird die Watchlist gespeichert? + +Die Watchlist ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**. +Damit ist sie geräteübergreifend mit deinem Konto verbunden. + +API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeWatchlist). diff --git a/docs/releases/v0.8.1/reference.json b/docs/releases/v0.8.1/reference.json new file mode 100644 index 0000000..a3adfd9 --- /dev/null +++ b/docs/releases/v0.8.1/reference.json @@ -0,0 +1,2782 @@ +{ + "tags": [ + { + "name": "health", + "description": "Health operations", + "endpoints": [ + { + "operationId": "healthCheck", + "method": "GET", + "path": "/healthz", + "summary": "Health check", + "description": "Returns server health status", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Healthy", + "schema": { + "kind": "ref", + "value": "HealthStatus" + } + } + ] + }, + { + "operationId": "getVersion", + "method": "GET", + "path": "/version", + "summary": "Build version information", + "description": "Returns the running build version, commit SHA and build date", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Version information", + "schema": { + "kind": "ref", + "value": "VersionInfo" + } + } + ] + } + ] + }, + { + "name": "tools", + "description": "Tool management", + "endpoints": [ + { + "operationId": "listTools", + "method": "GET", + "path": "/tools", + "summary": "List all tools", + "description": "", + "parameters": [ + { + "name": "category", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "search", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "sort", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "newest, top_rated, most_reviewed, name_asc, name_desc, recently_updated" + }, + { + "name": "tags", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated tags; tool must include all of them", + "constraints": "" + }, + { + "name": "features", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated features; tool must include all of them", + "constraints": "" + }, + { + "name": "minRating", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "number" + }, + "description": "Minimum average combined rating (0-5)", + "constraints": "0–5" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "List of tools", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + } + ] + }, + { + "operationId": "createTool", + "method": "POST", + "path": "/tools", + "summary": "Create a new tool", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ToolInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created tool", + "schema": { + "kind": "ref", + "value": "Tool" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listCompareTools", + "method": "GET", + "path": "/compare", + "summary": "Compare tools side by side (premium)", + "description": "", + "parameters": [ + { + "name": "ids", + "in": "query", + "required": true, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated tool ids", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Tools in requested order", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + }, + { + "status": "401", + "description": "Authentication required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Premium feature required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getToolRatingHistory", + "method": "GET", + "path": "/tools/{id}/rating-history", + "summary": "Get a tool's rating history over time", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Rating history", + "schema": { + "kind": "array", + "value": "RatingHistoryItem" + } + }, + { + "status": "400", + "description": "Invalid id", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getTool", + "method": "GET", + "path": "/tools/{id}", + "summary": "Get a tool by ID", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Tool details", + "schema": { + "kind": "ref", + "value": "ToolWithStats" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateTool", + "method": "PATCH", + "path": "/tools/{id}", + "summary": "Update a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ToolUpdate" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated tool", + "schema": { + "kind": "ref", + "value": "Tool" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteTool", + "method": "DELETE", + "path": "/tools/{id}", + "summary": "Delete a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listTrashedTools", + "method": "GET", + "path": "/tools/trash", + "summary": "List trashed (soft-deleted) tools", + "description": "", + "parameters": [ + { + "name": "search", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "List of trashed tools", + "schema": { + "kind": "array", + "value": "Tool" + } + }, + { + "status": "403", + "description": "Feature \"trash\" requires a higher tier", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "trashTools", + "method": "POST", + "path": "/tools/trash", + "summary": "Move tools to trash (admin)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Tools trashed", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteTrashedTools", + "method": "DELETE", + "path": "/tools/trash", + "summary": "Permanently delete trashed tools (admin)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "restoreTools", + "method": "POST", + "path": "/tools/trash/restore", + "summary": "Restore trashed tools", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Tools restored", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Feature \"trash\" requires a higher tier", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "emptyTrash", + "method": "POST", + "path": "/tools/trash/empty", + "summary": "Permanently delete all trashed tools (admin)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Trash emptied", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listCategories", + "method": "GET", + "path": "/categories", + "summary": "List all distinct tool categories", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Categories list", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + }, + { + "operationId": "listAllFeatures", + "method": "GET", + "path": "/features/all", + "summary": "List all distinct feature strings across all tools", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "All known features", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + }, + { + "operationId": "listAllTags", + "method": "GET", + "path": "/tags/all", + "summary": "List all distinct tag strings across all tools", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "All known tags", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + } + ] + }, + { + "name": "ratings", + "description": "Tool ratings", + "endpoints": [ + { + "operationId": "listToolRatings", + "method": "GET", + "path": "/tools/{id}/ratings", + "summary": "List ratings for a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Ratings list", + "schema": { + "kind": "array", + "value": "Rating" + } + } + ] + }, + { + "operationId": "createRating", + "method": "POST", + "path": "/tools/{id}/ratings", + "summary": "Submit a rating for a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "RatingInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created rating", + "schema": { + "kind": "ref", + "value": "Rating" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "404", + "description": "Tool not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "analytics", + "description": "Analytics and aggregated statistics", + "endpoints": [ + { + "operationId": "getAnalyticsSummary", + "method": "GET", + "path": "/analytics/summary", + "summary": "Overall platform statistics", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Platform-level summary stats", + "schema": { + "kind": "ref", + "value": "AnalyticsSummary" + } + } + ] + }, + { + "operationId": "getTopTools", + "method": "GET", + "path": "/analytics/top-tools", + "summary": "Top-rated tools", + "description": "", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + }, + { + "name": "metric", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "usefulness, usability, combined" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Top tools list", + "schema": { + "kind": "array", + "value": "TopToolEntry" + } + } + ] + }, + { + "operationId": "getAnalyticsByCategory", + "method": "GET", + "path": "/analytics/by-category", + "summary": "Rating statistics grouped by category", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Per-category statistics", + "schema": { + "kind": "array", + "value": "CategoryStats" + } + } + ] + }, + { + "operationId": "getRatingDistribution", + "method": "GET", + "path": "/analytics/rating-distribution", + "summary": "Distribution of rating scores across the platform", + "description": "", + "parameters": [ + { + "name": "toolId", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Rating score distribution", + "schema": { + "kind": "ref", + "value": "RatingDistribution" + } + } + ] + } + ] + }, + { + "name": "auth", + "description": "Authentication", + "endpoints": [ + { + "operationId": "getAuthMode", + "method": "GET", + "path": "/auth/mode", + "summary": "Get authentication mode (oidc or local)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Auth mode", + "schema": { + "kind": "ref", + "value": "AuthMode" + } + } + ] + }, + { + "operationId": "getCsrfToken", + "method": "GET", + "path": "/auth/csrf", + "summary": "Get a CSRF token for state-changing requests", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "CSRF token", + "schema": { + "kind": "ref", + "value": "CsrfToken" + } + } + ] + }, + { + "operationId": "localLogin", + "method": "POST", + "path": "/auth/login", + "summary": "Local username/password login", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "LocalLoginInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Logged in successfully", + "schema": { + "kind": "ref", + "value": "AuthUser" + } + }, + { + "status": "401", + "description": "Invalid credentials", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getMe", + "method": "GET", + "path": "/auth/me", + "summary": "Get current authenticated user", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Current user info", + "schema": { + "kind": "ref", + "value": "AuthUser" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "changeMyPassword", + "method": "POST", + "path": "/auth/me/password", + "summary": "Change own password (local users only)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ChangePasswordInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Password changed", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "400", + "description": "Invalid input or wrong current password", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "422", + "description": "OIDC user - password is managed by the identity provider", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "429", + "description": "Too many attempts", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getPasswordRedirect", + "method": "GET", + "path": "/auth/password-redirect", + "summary": "Get redirect URL for managing credentials in the identity provider", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Redirect URL (null in local mode)", + "schema": { + "kind": "ref", + "value": "PasswordRedirect" + } + } + ] + }, + { + "operationId": "getMePreferences", + "method": "GET", + "path": "/auth/me/preferences", + "summary": "Get current user's browse preferences", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "User preferences", + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateMePreferences", + "method": "PUT", + "path": "/auth/me/preferences", + "summary": "Update current user's browse preferences", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated preferences", + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getMeWatchlist", + "method": "GET", + "path": "/auth/me/watchlist", + "summary": "Get current user's watchlist tools (premium)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Watchlist tools in saved order", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Premium feature required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "users", + "description": "User management (admin only)", + "endpoints": [ + { + "operationId": "listUsers", + "method": "GET", + "path": "/users", + "summary": "List all local users (admin only)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "User list", + "schema": { + "kind": "array", + "value": "User" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Forbidden", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "createUser", + "method": "POST", + "path": "/users", + "summary": "Create a new local user (admin only)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserCreateInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created user", + "schema": { + "kind": "ref", + "value": "User" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "409", + "description": "Username already exists", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateUser", + "method": "PATCH", + "path": "/users/{id}", + "summary": "Update user role (admin only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserRoleUpdate" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated user", + "schema": { + "kind": "ref", + "value": "User" + } + }, + { + "status": "404", + "description": "User not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteUser", + "method": "DELETE", + "path": "/users/{id}", + "summary": "Delete a user (admin only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + } + ] + }, + { + "operationId": "setUserPassword", + "method": "PATCH", + "path": "/users/{id}/password", + "summary": "Set/reset a user's password (admin only, local users only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "SetPasswordInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Password updated", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "404", + "description": "User not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "422", + "description": "OIDC user - password is managed by the identity provider", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "429", + "description": "Too many attempts", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "audit", + "description": "Audit log", + "endpoints": [ + { + "operationId": "listAuditLogs", + "method": "GET", + "path": "/audit-logs", + "summary": "List audit log entries (admin only)", + "description": "", + "parameters": [ + { + "name": "entityType", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "entityId", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Audit log entries", + "schema": { + "kind": "array", + "value": "AuditLog" + } + } + ] + } + ] + } + ], + "schemas": [ + { + "name": "HealthStatus", + "description": "", + "fields": [ + { + "name": "status", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "VersionInfo", + "description": "", + "fields": [ + { + "name": "version", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "commitSha", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "buildDate", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "trashRetentionDays", + "type": { + "kind": "type", + "value": "integer" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuthMode", + "description": "", + "fields": [ + { + "name": "mode", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "oidc, local" + } + ] + }, + { + "name": "CsrfToken", + "description": "", + "fields": [ + { + "name": "token", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "LocalLoginInput", + "description": "", + "fields": [ + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "User", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + }, + { + "name": "authProvider", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "local, oidc" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "UserCreateInput", + "description": "", + "fields": [ + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 2 chars" + }, + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + } + ] + }, + { + "name": "UserRoleUpdate", + "description": "", + "fields": [ + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + } + ] + }, + { + "name": "ChangePasswordInput", + "description": "", + "fields": [ + { + "name": "currentPassword", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "newPassword", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + } + ] + }, + { + "name": "SetPasswordInput", + "description": "", + "fields": [ + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + } + ] + }, + { + "name": "PasswordRedirect", + "description": "", + "fields": [ + { + "name": "url", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuditLog", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "entityType", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "entityId", + "type": { + "kind": "type", + "value": "integer | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "action", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "userId", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "changes", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "Tool", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "updatedAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "deletedAt", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "date-time" + }, + { + "name": "deletedBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "RatingHistoryItem", + "description": "", + "fields": [ + { + "name": "date", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "combined", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolWithStats", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "updatedAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "ratingCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgCombined", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolInput", + "description": "", + "fields": [ + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolUpdate", + "description": "", + "fields": [ + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "TrashToolsInput", + "description": "", + "fields": [ + { + "name": "ids", + "type": { + "kind": "array", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "min 1 items, max 500 items" + } + ] + }, + { + "name": "Rating", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "toolId", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "comment", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "reviewerName", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "RatingInput", + "description": "", + "fields": [ + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "comment", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "reviewerName", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AnalyticsSummary", + "description": "", + "fields": [ + { + "name": "totalTools", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "totalRatings", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgCombined", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "categoriesCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "mostRatedTool", + "type": { + "kind": "ref", + "value": "ToolWithStats" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "TopToolEntry", + "description": "", + "fields": [ + { + "name": "tool", + "type": { + "kind": "ref", + "value": "ToolWithStats" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "score", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "ratingCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "CategoryStats", + "description": "", + "fields": [ + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "toolCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "totalRatings", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "RatingDistribution", + "description": "", + "fields": [ + { + "name": "usefulness", + "type": { + "kind": "array", + "value": "ScoreBucket" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usability", + "type": { + "kind": "array", + "value": "ScoreBucket" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ScoreBucket", + "description": "", + "fields": [ + { + "name": "score", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "count", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuthUser", + "description": "", + "fields": [ + { + "name": "sub", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "preferredUsername", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + }, + { + "name": "entitlements", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "isLocal", + "type": { + "kind": "type", + "value": "boolean" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "UserPreferences", + "description": "", + "fields": [ + { + "name": "view", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "grid, table, rows" + }, + { + "name": "density", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "cozy, compact" + }, + { + "name": "watchlist", + "type": { + "kind": "array", + "value": "integer" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ErrorResponse", + "description": "", + "fields": [ + { + "name": "error", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/docs/releases/v0.8.2.md b/docs/releases/v0.8.2.md index ec50e46..4613159 100644 --- a/docs/releases/v0.8.2.md +++ b/docs/releases/v0.8.2.md @@ -1,6 +1,6 @@ # v0.8.2 — Release Notes -**Datum:** 2026-08-04 · **Tag:** [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.2) +**Datum:** 2026-08-04 · **Tag:** [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2) ## Fixes & Verbesserungen @@ -33,4 +33,4 @@ ## Links - Commit: [`33cf261`](https://git.kubebase.de/admin/tool-evaluator/commit/33cf261) -- Tag: [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/tags/v0.8.2) +- Tag: [`v0.8.2`](https://git.kubebase.de/admin/tool-evaluator/releases/tag/v0.8.2) diff --git a/docs/releases/v0.8.2/handbook/administration.md b/docs/releases/v0.8.2/handbook/administration.md new file mode 100644 index 0000000..585e0ce --- /dev/null +++ b/docs/releases/v0.8.2/handbook/administration.md @@ -0,0 +1,61 @@ + +# Administration + +Der Bereich **Admin** (`/admin`) ist ausschließlich für Admins zugänglich. +Ohne Admin-Rolle erscheint eine Zugriffsverweigerung. + +> Oben rechts führt die Schaltfläche **Redundanz-Dashboard** zur automatischen +> Doppelungs-Erkennung (siehe [Redundanz](/docs/handbook/redundanz)). + +## Tab „Nutzer" + +Verwaltung der lokalen Konten. + +- **Nutzer hinzufügen:** Benutzername (Pflicht), Passwort (mind. 6 Zeichen), + E-Mail (optional), **Rolle** (User/Admin), **Tarif** (Free/Premium/Enterprise). +- **Nutzer bearbeiten:** Rolle, Tarif und (für lokale Konten) ein neues Passwort + setzen. Für OIDC-Konten wird die Passwortverwaltung im Identitätsanbieter + (z. B. Keycloak) angeboten. +- **Nutzer löschen:** Entfernt das Konto endgültig (nicht für das eigene Konto). + +API-Referenz: +[`POST /users`](/docs/reference/endpoints/users#createUser), +[`PATCH /users/{id}`](/docs/reference/endpoints/users#updateUser), +[`DELETE /users/{id}`](/docs/reference/endpoints/users#deleteUser). + +## Tab „Tools" + +Zentraler Zugriff auf den Tool-Katalog. + +- **Suchen** nach Tools. +- Tools einzeln ansehen, bearbeiten oder in den Papierkorb verschieben. +- **Massenaktion:** mehrere Tools auswählen und in den Papierkorb verschieben + (Bestätigungsdialog; soft gelöschte Tools sind aus allen öffentlichen Ansichten + entfernt und können wiederhergestellt oder endgültig gelöscht werden). + +## Tab „Audit-Log" + +Chronologisches Protokoll aller Anlage-, Änderungs- und Löschvorgänge +(max. 100 Einträge): Aktion, Entität + ID, Zeitstempel, ausführende Person und +geänderte Felder. + +API-Referenz: [`GET /audit-logs`](/docs/reference/endpoints/audit#listAuditLogs). + +## Tab „System" + +Versionsinformationen der laufenden Instanz: + +- **Version** (z. B. `v0.8.1`), +- **Commit** (7-stelliger SHA, verlinkt zum Repository), +- **Build-Datum**, +- **Papierkorb-Aufbewahrung** („N Tage" oder „Für immer"). + +## Tool-Verknüpfungen (Admin) + +Auf der Detailseite eines Tools kannst du als Admin **Verknüpfungen** +(eigene/„manual" sowie automatisch erkannte) verwalten: + +- **Tool verknüpfen:** Dialog mit Tool-ID, **Beziehungstyp** + (Ähnlich / Ersetzt / Abgelöst durch) und optionalen Notizen. +- Beziehungstypen werden als Badges auf der Detailseite angezeigt. +- Manuelle Verknüpfungen lassen sich per Papierkorb-Icon wieder entfernen. diff --git a/docs/releases/v0.8.2/handbook/analytics.md b/docs/releases/v0.8.2/handbook/analytics.md new file mode 100644 index 0000000..3cfacd8 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/analytics.md @@ -0,0 +1,29 @@ + +# Analytics + +Der Bereich **Analytics** (`/analytics`) ist ein öffentliches Dashboard mit +Kennzahlen und Diagrammen auf Basis aller Tools und Bewertungen. + +## Kennzahlen (KPI-Karten) + +- **Anzahl Tools** — wie viele Tools sind im Katalog erfasst. +- **Anzahl Bewertungen** — wie viele Bewertungen wurden insgesamt abgegeben. +- **Aktive Kategorien** — wie viele Kategorien existieren. +- **Durchschnittliche Bewertung** — globaler kombinierter Wert. + +## Diagramme + +| Diagramm | Inhalt | +| --- | --- | +| **Top 8 Tools** | Balkendiagramm der Tools mit der höchsten kombinierten Punktzahl (0–5) | +| **Tools je Kategorie** | Radar-Diagramm der Tool-Anzahl pro Kategorie | +| **Punkteverteilung** | Zwei horizontale Balken-Diagramme (Nützlichkeit & Bedienbarkeit) pro Stern | + +Die Diagramme sind interaktiv (Tooltips beim Überfahren). + +## API + +- [`GET /analytics/summary`](/docs/reference/endpoints/analytics#getAnalyticsSummary) +- [`GET /analytics/top-tools`](/docs/reference/endpoints/analytics#getTopTools) +- [`GET /analytics/by-category`](/docs/reference/endpoints/analytics#getAnalyticsByCategory) +- [`GET /analytics/rating-distribution`](/docs/reference/endpoints/analytics#getRatingDistribution) diff --git a/docs/releases/v0.8.2/handbook/bewerten.md b/docs/releases/v0.8.2/handbook/bewerten.md new file mode 100644 index 0000000..b4d054e --- /dev/null +++ b/docs/releases/v0.8.2/handbook/bewerten.md @@ -0,0 +1,39 @@ + +# Bewerten + +Auf der Detailseite eines Tools kannst du deine Erfahrung teilen. Klicke auf +**Bewertung abgeben** (erfordert ein Konto). + +## Formularfelder + +| Feld | Pflicht | Hinweise | +| --- | --- | --- | +| **Nützlichkeit** | Ja | 1–5 Sterne | +| **Bedienbarkeit** | Ja | 1–5 Sterne | +| **Kommentar** | Nein | Freitext | +| **Name** | Nein | Standard „Anonym" | + +Neben den Feldern führt das **?‑Icon** direkt zur zugehörigen Feldbeschreibung +in der [Datenmodell-Referenz](/docs/reference/schemas/ratinginput). + +## Was passiert nach dem Abgeben? + +- Deine Bewertung wird sofort gespeichert und erscheint in der + **Bewertungsliste** der Detailseite. +- Die **Durchschnittswerte** (Nützlichkeit, Bedienbarkeit, Kombiniert) und die + **Punkteverteilung** werden aktualisiert. +- Die **Statistiken** im Bereich [Analytics](/docs/handbook/analytics) werden + neu berechnet. + +## Statistik-Bereiche auf der Detailseite + +- **Bewertungsübersicht:** Nützlichkeit & Bedienbarkeit als Durchschnitt mit + Fortschrittsbalken. +- **Punkteverteilung:** Anzahl der Bewertungen pro Stern (1★–5★). +- **Verlauf:** Linienchart der kombinierten/Teilwerte über die Zeit + (erst ab mehreren Bewertungen sichtbar). + +## API + +- [`POST /tools/{id}/ratings`](/docs/reference/endpoints/ratings#createRating) — Bewertung abgeben +- [`GET /tools/{id}/ratings`](/docs/reference/endpoints/ratings#listToolRatings) — Bewertungen eines Tools diff --git a/docs/releases/v0.8.2/handbook/datenmodell.md b/docs/releases/v0.8.2/handbook/datenmodell.md new file mode 100644 index 0000000..618e160 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/datenmodell.md @@ -0,0 +1,70 @@ + +# Datenmodell + +Dieses Kapitel erklärt die zentralen Datenobjekte von toolr auf Ebene der +Anwendung. Die vollständige, automatisch generierte Referenz aller Felder, +Typen und Constraints findest du in der +[API-Referenz](/docs/reference/schemas/tool). + +## Tool + +Das Herzstück: ein im Katalog erfasstes Werkzeug. + +| Eigenschaft | Beschreibung | +| --- | --- | +| `id` | Eindeutige Kennung | +| `name` | Anzeigename | +| `description` | Beschreibung (Was macht das Tool?) | +| `category` | Kategorie-Zuordnung | +| `websiteUrl` | Offizielle Website (optional) | +| `iconUrl` | Logo-/Icon-URL (optional) | +| `features` | Liste von Fähigkeiten | +| `tags` | Liste von Schlagwörtern | +| `createdAt` / `updatedAt` | Zeitstempel | +| `createdBy` | Erstellende Person | +| `deletedAt` / `deletedBy` | Soft-Löschung (Papierkorb) | + +Eingabe-Formulare verwenden die abgeleiteten Schemas +[`ToolInput`](/docs/reference/schemas/toolinput) und +[`ToolUpdate`](/docs/reference/schemas/toolupdate). +Aggregierte Ansichten liefert [`ToolWithStats`](/docs/reference/schemas/toolwithstats) +(z. B. mit Durchschnittsbewertung). + +## Rating (Bewertung) + +Eine einzelne Bewertung zu einem Tool: + +- `usefulness` (Nützlichkeit, 1–5) und `usability` (Bedienbarkeit, 1–5) +- optional `comment` und ein Anzeigename (`reviewerName`) +- Zeitstempel + +Eingabe-Schema: [`RatingInput`](/docs/reference/schemas/ratinginput). + +## User & Auth + +- [`User`](/docs/reference/schemas/user) — Benutzerkonto mit Rolle (User/Admin) + und Tarif (Free/Premium/Enterprise). +- [`AuthUser`](/docs/reference/schemas/authuser) — das angemeldete Profil + inklusive `entitlements` (verfügbare Features). +- [`UserPreferences`](/docs/reference/schemas/userpreferences) — Ansichts- und + Dichte-Präferenzen sowie die `watchlist` (Liste von Tool-IDs). + +## Analytics + +Die Statistik-Endpunkte liefern aggregierte Daten: + +- [`AnalyticsSummary`](/docs/reference/schemas/analyticssummary) — globale + Kennzahlen (Anzahl Tools/Bewertungen, Kategorien, Durchschnitt). +- [`TopToolEntry`](/docs/reference/schemas/TopToolEntry) — ein Eintrag der + Top-Tools.- [`CategoryStats`](/docs/reference/schemas/categorystats) — Tool-Anzahl je + Kategorie. +- [`RatingDistribution`](/docs/reference/schemas/ratingdistribution) — + Punkteverteilung (Nützlichkeit & Bedienbarkeit). +- [`ScoreBucket`](/docs/reference/schemas/scorebucket) — ein Werte-Bucket. + +## Weitere + +- [`VersionInfo`](/docs/reference/schemas/versioninfo) — Version, Commit-SHA, + Build-Datum und Papierkorb-Aufbewahrung der laufenden Instanz. +- [`AuditLog`](/docs/reference/schemas/auditlog) — ein Protokolleintrag + (Aktion, Entität, Zeitstempel, Akteur, Änderungen). diff --git a/docs/releases/v0.8.2/handbook/getting-started.md b/docs/releases/v0.8.2/handbook/getting-started.md new file mode 100644 index 0000000..0fd7820 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/getting-started.md @@ -0,0 +1,53 @@ + +# Erste Schritte + +Diese Seite führt dich durch die wichtigsten Abläufe in toolr — vom ersten +Besuch bis zum Anlegen und Bewerten eines Tools. + +## 1. Anmelden + +Die meisten Aktionen (Tool anlegen, bewerten, Watchlist, Vergleichen) erfordern +ein Konto. Klicke unten links auf **Anmelden**. Je nach Konfiguration der +Instanz hast du zwei Möglichkeiten: + +- **Lokale Konten:** Benutzername + Passwort. Der Zugang wird von einem Admin + angelegt (siehe [Administration](/docs/handbook/administration)). +- **OIDC (SSO):** Anmelden mit dem konfigurierten Identitätsanbieter (z. B. + Keycloak). + +Welcher Modus aktiv ist, steht im Endpunkt +[`GET /auth/mode`](/docs/reference/endpoints/auth#getAuthMode). Details findest +du im Abschnitt [Anmelden & Konto](/docs/handbook/konto). + +## 2. Tools finden + +Öffne den Bereich **Tools durchsuchen**: + +- **Suchen** — Volltextsuche über Name & Beschreibung (Tastenkürzel `/`). +- **Filtern** — nach Kategorie, Tags, Features und Mindestbewertung + (`minRating`). +- **Sortieren** — nach Aktualität, Top-Bewertung, meistbewertet, Name + (auf-/absteigend) oder letztem Update. + +Alle Optionen im Detail: [Tools finden & durchsuchen](/docs/handbook/tools-finden). + +## 3. Tool anlegen + +Gehe auf **Tool hinzufügen** und fülle das Formular aus. Details zu jedem Feld +findest du im Abschnitt [Tool anlegen](/docs/handbook/tool-anlegen) und in der +[Feld-Referenz](/docs/reference/schemas/toolinput). + +## 4. Bewerten + +Auf der Detailseite eines Tools kannst du **Nützlichkeit** und **Bedienbarkeit** +(jeweils 1–5) vergeben und optional einen Kommentar hinterlassen. Deine +Bewertung fließt sofort in die Statistiken ein. +Siehe [Bewerten](/docs/handbook/bewerten). + +## 5. Weiterführend + +- [Tools vergleichen](/docs/handbook/vergleichen) +- [Watchlist](/docs/handbook/watchlist) +- [Analytics](/docs/handbook/analytics) +- [Pläne & Berechtigungen](/docs/handbook/plaene) +- [Administration](/docs/handbook/administration) diff --git a/docs/releases/v0.8.2/handbook/index.json b/docs/releases/v0.8.2/handbook/index.json new file mode 100644 index 0000000..9379656 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/index.json @@ -0,0 +1,104 @@ +[ + { + "slug": "index", + "file": "index.md", + "title": "Überblick", + "order": 1 + }, + { + "slug": "getting-started", + "file": "getting-started.md", + "title": "Erste Schritte", + "order": 2 + }, + { + "slug": "konto", + "file": "konto.md", + "title": "Anmelden & Konto", + "order": 3 + }, + { + "slug": "tools-finden", + "file": "tools-finden.md", + "title": "Tools finden & durchsuchen", + "order": 4 + }, + { + "slug": "tool-anlegen", + "file": "tool-anlegen.md", + "title": "Tool anlegen", + "order": 5 + }, + { + "slug": "tool-bearbeiten", + "file": "tool-bearbeiten.md", + "title": "Tool bearbeiten & löschen", + "order": 6 + }, + { + "slug": "bewerten", + "file": "bewerten.md", + "title": "Bewerten", + "order": 7 + }, + { + "slug": "watchlist", + "file": "watchlist.md", + "title": "Watchlist", + "order": 8 + }, + { + "slug": "vergleichen", + "file": "vergleichen.md", + "title": "Vergleichen", + "order": 9 + }, + { + "slug": "analytics", + "file": "analytics.md", + "title": "Analytics", + "order": 10 + }, + { + "slug": "plaene", + "file": "plaene.md", + "title": "Pläne & Berechtigungen", + "order": 11 + }, + { + "slug": "kosten", + "file": "kosten.md", + "title": "Kosten erfassen", + "order": 12 + }, + { + "slug": "administration", + "file": "administration.md", + "title": "Administration", + "order": 13 + }, + { + "slug": "redundanz", + "file": "redundanz.md", + "title": "Redundanz-Dashboard", + "order": 14 + }, + { + "slug": "papierkorb", + "file": "papierkorb.md", + "title": "Papierkorb", + "order": 15 + }, + { + "slug": "tastatur", + "file": "tastatur.md", + "title": "Tastenkürzel & Kommandopalette", + "order": 16 + }, + { + "slug": "datenmodell", + "file": "datenmodell.md", + "title": "Datenmodell", + "order": 17 + } +] \ No newline at end of file diff --git a/docs/releases/v0.8.2/handbook/index.md b/docs/releases/v0.8.2/handbook/index.md new file mode 100644 index 0000000..f527a6e --- /dev/null +++ b/docs/releases/v0.8.2/handbook/index.md @@ -0,0 +1,52 @@ + +# Willkommen bei toolr + +toolr ist eine Plattform zum **Entdecken, Bewerten und Vergleichen von +Entwicklungstools**. Nutzer:innen pflegen einen gemeinsamen Katalog von Tools, +vergeben Bewertungen (Nützlichkeit & Bedienbarkeit) und nutzen Statistiken, um +die richtige Wahl zu treffen. + +## Was kannst du mit toolr tun? + +| Funktion | Beschreibung | Sichtbarkeit | +| --- | --- | --- | +| **Tools durchsuchen** | Katalog filtern, sortieren und durchsuchen | Alle | +| **Tool anlegen** | Neues Tool mit Beschreibung, Kategorie, Features & Tags eintragen | Angemeldet | +| **Tool bearbeiten/löschen** | Eigene Tools pflegen (Ersteller:in oder Admin) | Angemeldet | +| **Bewerten** | Nützlichkeit & Bedienbarkeit (1–5) plus Kommentar vergeben | Angemeldet | +| **Watchlist** | Tools als Favoriten speichern | Premium | +| **Vergleichen** | Tools nebeneinander gegenüberstellen | Premium | +| **Kosten erfassen** | Lizenz- und Kostenmodelle je Tool eintragen | Premium | +| **Analytics** | Statistiken, Top-Tools, Verteilungen | Alle | +| **Papierkorb** | Soft-gelöschte Tools wiederherstellen oder endgültig löschen | Premium | +| **Admin** | Nutzerverwaltung, Audit-Log, Systeminformationen | Admin | +| **Redundanz** | Automatische Doppelungs-Erkennung | Admin | + +## Wie diese Doku aufgebaut ist + +- **User Guide** (diese Seiten): Schritt-für-Schritt-Anleitungen für alle + Funktionen — von den [Ersten Schritten](/docs/handbook/getting-started) bis + zur [Administration](/docs/handbook/administration). +- **API-Referenz**: automatisch aus der OpenAPI-Spezifikation generiert — alle + [Endpunkte](/docs/reference/endpoints/tools) und + [Datenfelder](/docs/reference/schemas/toolinput) der aktuellen Version. +- **Release-Notes**: Was ist in welcher [Version](/docs/releases/v0.8.1) neu. + +## Der Einstieg + +Der schnellste Weg: + +1. **Anmelden** — ohne Konto kannst du nur stöbern + (siehe [Erste Schritte](/docs/handbook/getting-started#1-anmelden)). +2. **Tools finden** — Suche, Filter und Sortierung im Bereich + [Tools durchsuchen](/docs/handbook/tools-finden). +3. **Tool anlegen** — über „Tool hinzufügen" + ([Anleitung](/docs/handbook/tool-anlegen)). +4. **Bewerten** — auf der Detailseite eines Tools + ([Anleitung](/docs/handbook/bewerten)). + +## Kontakt & Quellcode + +Der Quellcode liegt unter +[git.kubebase.de/admin/tool-evaluator](https://git.kubebase.de/admin/tool-evaluator) — +über das Repository-Icon oben rechts erreichst du ihn jederzeit. diff --git a/docs/releases/v0.8.2/handbook/konto.md b/docs/releases/v0.8.2/handbook/konto.md new file mode 100644 index 0000000..40aae3d --- /dev/null +++ b/docs/releases/v0.8.2/handbook/konto.md @@ -0,0 +1,53 @@ + +# Anmelden & Konto + +## Anmelden + +Klicke unten links in der Seitenleiste auf **Anmelden**. Je nach Konfiguration +der Instanz: + +- **Lokale Konten:** Benutzername und Passwort eingeben. Die Konten werden von + einem Admin angelegt (siehe [Administration](/docs/handbook/administration)). +- **OIDC (SSO):** Du wirst an den konfigurierten Identitätsanbieter + weitergeleitet und meldest dich dort an. + +Der aktive Modus steht im Endpunkt +[`GET /auth/mode`](/docs/reference/endpoints/auth#getAuthMode). + +> Die Login-Seite erreichst du direkt unter `/login`. Nach erfolgreicher +> Anmeldung wirst du zur ursprünglich aufgerufenen Seite zurückgeleitet. + +## Benutzerprofil + +Dein Profil (Avatar, Name, E-Mail, Tarif) siehst du unten links im +Benutzermenü. Dort stehen dir folgende Aktionen zur Verfügung: + +- **Watchlist** — deine gespeicherten Tools (nur mit dem entsprechenden Tarif). +- **Papierkorb** — wiederherstellbare, gelöschte Tools (Premium/Enterprise). +- **Passwort ändern** — für lokale Konten direkt in toolr; für OIDC-Konten wird + die Passwortverwaltung im Identitätsanbieter angeboten. +- **Abmelden** — beendet deine Sitzung. + +## Passwort ändern (lokales Konto) + +1. Öffne das Benutzermenü unten links. +2. Wähle **Passwort ändern**. +3. Gib das **aktuelle** sowie ein **neues** Passwort ein (mind. 6 Zeichen) und + bestätige es. +4. Speichern — das Passwort wird sofort übernommen. + +API-Referenz: [`POST /auth/me/password`](/docs/reference/endpoints/auth#changeMyPassword). + +## Anzeigeeinstellungen + +Über die Schaltflächen oben rechts kannst du: + +- **Sprache** wechseln (Deutsch / Englisch), +- **Theme** umschalten (Hell / Dunkel / System), +- die **Listenansicht** und **Dichte** im Bereich Tools durchsuchen anpassen + (siehe [Tools finden & durchsuchen](/docs/handbook/tools-finden)). + +Deine Präferenzen (inkl. Watchlist) werden im Endpunkt +[`GET /auth/me/preferences`](/docs/reference/endpoints/auth#getMePreferences) +gespeichert und über [`PUT /auth/me/preferences`](/docs/reference/endpoints/auth#updateMePreferences) +aktualisiert. diff --git a/docs/releases/v0.8.2/handbook/kosten.md b/docs/releases/v0.8.2/handbook/kosten.md new file mode 100644 index 0000000..8904047 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/kosten.md @@ -0,0 +1,35 @@ + +# Kosten erfassen + +Auf der Detailseite eines Tools kannst du Kosten- und Lizenzmodelle eintragen, +damit die Gesamtkosten je Tool transparent werden. + +> Kosten ist ein **Premium-Feature** (`costs`, Premium/Enterprise). Admins +> haben immer Zugriff. + +## Kosten hinzufügen + +Klicke auf **Kosten hinzufügen** im Kosten-Bereich der Detailseite und fülle +das Formular aus: + +| Feld | Hinweise | +| --- | --- | +| **Lizenztyp** | Free / Subscription / One-Time / Usage-Based | +| **Abrechnungszeitraum** | Nur für „Subscription": Monatlich / Quartalsweise / Jährlich | +| **Kosten** | Betrag als Zahl | +| **Währung** | EUR / USD / GBP / CHF | +| **Notizen** | Optionaler Freitext | + +Speichern legt den Eintrag an. Jeder Kosten-Eintrag wird als Karte mit +Lizenz-Badge, Abrechnungszeitraum, Betrag (`Betrag Währung` bzw. „Free") und +Notizen angezeigt. + +## Kosten bearbeiten & löschen + +Beim Überfahren einer Kosten-Karte erscheinen die Aktionen **Bearbeiten** +(Bleistift) und **Löschen** (Papierkorb). + +## API + +Die Kosten-Daten werden über die Tool-Endpunkte verwaltet +(siehe [API-Referenz](/docs/reference/endpoints/tools)). diff --git a/docs/releases/v0.8.2/handbook/papierkorb.md b/docs/releases/v0.8.2/handbook/papierkorb.md new file mode 100644 index 0000000..3179c74 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/papierkorb.md @@ -0,0 +1,41 @@ + +# Papierkorb + +Der **Papierkorb** (`/trash`) enthält soft gelöschte Tools. Mit Papierkorb-Zugang +können sie wiederhergestellt werden; endgültiges Löschen ist Admins vorbehalten. + +> Der Papierkorb ist ein **Premium-Feature** (`trash`, Premium/Enterprise). +> Admins haben immer Zugriff. + +## Zugang + +Der Papierkorb ist über das Benutzermenü oder die Seitenleiste erreichbar. +Ohne `trash`-Berechtigung erscheint ein Hinweis auf den Tarifwechsel. + +## Wiederherstellen + +- Markiere ein oder mehrere Tools (Checkboxen). +- Klicke auf **Wiederherstellen (N)** — die Tools erscheinen wieder in allen + öffentlichen Ansichten. + +> Wiederherstellen steht jeder Person mit Papierkorb-Zugang zur Verfügung. + +## Endgültig löschen (nur Admin) + +- **Löschen (N)** entfernt die ausgewählten Tools **endgültig** — inklusive + aller Bewertungen, Kosten und Verknüpfungen. Das kann nicht rückgängig + gemacht werden. +- **Papierkorb leeren** entfernt alle soft gelöschten Tools endgültig. + +## Tabelle + +Der Papierkorb listet: Name, Kategorie, **Gelöscht am** (`tt.MM.jjjj HH:mm`), +**Gelöscht von** sowie Aktionen (Wiederherstellen; Löschen nur Admin). Die Suche +filtert nach Namen. + +## API + +- [`GET /tools/trash`](/docs/reference/endpoints/tools#listTrashedTools) — Liste +- [`POST /tools/trash/restore`](/docs/reference/endpoints/tools#restoreTools) — Wiederherstellen +- [`DELETE /tools/trash`](/docs/reference/endpoints/tools#deleteTrashedTools) — Endgültig löschen (Admin) +- [`POST /tools/trash/empty`](/docs/reference/endpoints/tools#emptyTrash) — Papierkorb leeren (Admin) diff --git a/docs/releases/v0.8.2/handbook/plaene.md b/docs/releases/v0.8.2/handbook/plaene.md new file mode 100644 index 0000000..d4546b7 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/plaene.md @@ -0,0 +1,41 @@ + +# Pläne & Berechtigungen + +toolr unterscheidet **Tarife** (Tier) und **Rollen**. Admins umgehen alle +Feature-Beschränkungen. + +## Tarife + +| Tarif | Beschreibung | +| --- | --- | +| **Free** | Grundfunktionen: suchen, filtern, ansehen, Analytics | +| **Premium** | Zusätzlich Watchlist, Vergleichen, Papierkorb, Kosten | +| **Enterprise** | Alle Premium-Features + erweiterter Support | + +### Feature-Berechtigungen + +Premium/Enterprise schalten folgende Features frei: + +| Feature | Funktion | Mehr erfahren | +| --- | --- | --- | +| `compare` | Tools vergleichen | [Vergleichen](/docs/handbook/vergleichen) | +| `watchlist` | Favoritenliste | [Watchlist](/docs/handbook/watchlist) | +| `trash` | Papierkorb (soft gelöschte Tools) | [Papierkorb](/docs/handbook/papierkorb) | +| `costs` | Kosten-/Lizenzmodelle erfassen | [Kosten erfassen](/docs/handbook/kosten) | + +Fehlt dir ein Feature, zeigt die App einen **Upgrade-Hinweis** mit Link zur +Tarifverwaltung. + +## Rollen + +| Rolle | Berechtigungen | +| --- | --- | +| **User** | Standard-Konto: Tools anlegen/bewerten, eigene Tools bearbeiten | +| **Admin** | Alle User-Rechte + Verwaltung, Audit-Log, Redundanz, Papierkorb leeren, Tool-Verknüpfungen | + +Admins passieren **alle** Feature-Checks — auch ohne Premium-Tarif. + +## Tarif-/Rollenverwaltung + +Die Zuordnung von Rolle und Tarif wird durch Admins im Bereich +[Administration](/docs/handbook/administration) (Tab „Nutzer") verwaltet. diff --git a/docs/releases/v0.8.2/handbook/redundanz.md b/docs/releases/v0.8.2/handbook/redundanz.md new file mode 100644 index 0000000..7521ffe --- /dev/null +++ b/docs/releases/v0.8.2/handbook/redundanz.md @@ -0,0 +1,38 @@ + +# Redundanz-Dashboard + +Das **Redundanz-Dashboard** (`/admin/redundancy`) ist ein Admin-Werkzeug zur +automatischen Erkennung doppelter oder stark überlappender Tools — jeweils +pro Kategorie — inklusive Kosten- und Bewertungsvergleich. + +> Der Zugriff ist ausschließlich Admins vorbehalten (die API ist +> admin-geschützt). + +## Aufbau + +- **Pro Kategorie** wird eine Gruppe angezeigt: Name der Kategorie, + Anzahl Tools und Vergleiche sowie ggf. die **gesamten monatlichen Kosten** + (z. B. `€X.XX/mo gesamt`). +- Jedes Tool wird als Karte dargestellt: Name, monatliche Kosten, Anzahl der + Bewertungen, kombinierte Bewertung, Lizenz-Badges und Feature-Anzahl. + +## Vergleiche & Empfehlungen + +Für jedes Tool-Paar erscheint: + +- Tool A vs. Tool B, jeweils mit Bewertung (`X.X ★`) und monatlichen Kosten. +- **Überlappung** in Prozent (Fortschrittsbalken in der Mitte). +- Eine **Empfehlung** mit Konfidenz-Farbe: + - **hoch** (grün), **mittel** (gelb), **niedrig** (grau) +- Das empfohlene, bessere Tool wird mit „Daumen hoch" markiert und begründet. + +## Manuelle Bewertung + +Du kannst ein Paar manuell bewerten: Klicke auf Tool A oder Tool B, um +festzuhalten, welches besser ist. Die Auswahl wird gespeichert und die +Darstellung aktualisiert. + +## API + +- [`GET /api/admin/redundancy`](#) — Daten laden (admin-geschützt) +- [`POST /api/admin/redundancy/evaluate`](#) — manuelle Bewertung speichern diff --git a/docs/releases/v0.8.2/handbook/tastatur.md b/docs/releases/v0.8.2/handbook/tastatur.md new file mode 100644 index 0000000..7eb6f17 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/tastatur.md @@ -0,0 +1,36 @@ + +# Tastenkürzel & Kommandopalette + +## Kommandopalette + +Die Kommandopalette ist die zentrale Schnellnavigation: + +- Öffnen mit **`⌘K`** (macOS) bzw. **`Ctrl+K`** (Windows/Linux). +- Alternativ über die Suchleiste oben rechts („Tools suchen… ⌘K") oder das + Such-Icon auf Mobilgeräten. + +### Leerer Zustand + +Ohne Eingabe zeigt die Palette: + +- **Zuletzt angesehen** — die letzten 5 Tools, die du besucht hast. +- **Navigation** — Tools durchsuchen, Tool hinzufügen, Analytics sowie + (abhängig von Berechtigungen) Watchlist, Papierkorb und Admin. + +### Suche + +Tippe, um live nach Tools zu suchen (max. 10 Ergebnisse, inkl. Bewertung +`X.X★`). + +## Tastenkürzel im Überblick + +| Kürzel | Aktion | +| --- | --- | +| `⌘K` / `Ctrl+K` | Kommandopalette öffnen | +| `/` | Suche im Bereich „Tools durchsuchen" fokussieren | + +## Weitere Hinweise + +- **Zuletzt angesehen** wird lokal im Browser gespeichert (max. 5 Einträge). +- Die Seitenleiste (linke Navigation) ist auf Desktop einklappbar; der + Breadcrumb oben zeigt deinen aktuellen Ort. diff --git a/docs/releases/v0.8.2/handbook/tool-anlegen.md b/docs/releases/v0.8.2/handbook/tool-anlegen.md new file mode 100644 index 0000000..db93745 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/tool-anlegen.md @@ -0,0 +1,46 @@ + +# Tool anlegen + +Um ein neues Tool zum Katalog hinzuzufügen, klicke auf **Tool hinzufügen** +(`/tools/new`). Das Anlegen erfordert ein Konto — ohne Anmeldung erscheint ein +Hinweis mit Login-Button. + +## Formularfelder + +| Feld | Pflicht | Hinweise | +| --- | --- | --- | +| **Name** | Ja | Mind. 2 Zeichen | +| **Kategorie** | Ja | Auswahlliste; neue Kategorien lassen sich direkt anlegen | +| **Website URL** | Nein | Gültige URL (z. B. `https://...`) | +| **Icon / Logo URL** | Nein | Gültige URL; Vorschau wird live angezeigt | +| **Beschreibung** | Ja | Mind. 10 Zeichen; beschreibe, was das Tool tut | +| **Features** | Nein | Dynamische Liste mit Autovervollständigung (max. 6) | +| **Tags** | Nein | Dynamische Liste mit Autovervollständigung | + +Neben jedem Feld führt das **?‑Icon** direkt zur zugehörigen Feldbeschreibung +in der [Datenmodell-Referenz](/docs/reference/schemas/toolinput). + +### Kategorie + +- Tippe, um nach bestehenden Kategorien zu suchen. +- Wähle **+ Erstelle „..."**, um eine neue Kategorie anzulegen. + +### Features & Tags + +- **Feature hinzufügen** / **Tag hinzufügen** hängt eine neue Zeile an. +- Die Eingabefelder schlagen bestehende Features/Tags vor + (Autovervollständigung, max. 6 Vorschläge). +- Mit dem **×**‑Button entfernst du einzelne Zeilen. +- Features und Tags helfen beim Filtern und Wiederfinden. + +## Speichern + +Klicke auf **Tool hinzufügen**. Nach erfolgreicher Anlage wirst du auf die +Detailseite des neuen Tools weitergeleitet. + +## API + +- [`POST /tools`](/docs/reference/endpoints/tools#createTool) — Tool anlegen +- [`GET /categories`](/docs/reference/endpoints/tools#listCategories) — Kategorien +- [`GET /features/all`](/docs/reference/endpoints/tools#listAllFeatures) — Features +- [`GET /tags/all`](/docs/reference/endpoints/tools#listAllTags) — Tags diff --git a/docs/releases/v0.8.2/handbook/tool-bearbeiten.md b/docs/releases/v0.8.2/handbook/tool-bearbeiten.md new file mode 100644 index 0000000..7f2fd3f --- /dev/null +++ b/docs/releases/v0.8.2/handbook/tool-bearbeiten.md @@ -0,0 +1,33 @@ + +# Tool bearbeiten & löschen + +## Bearbeiten + +Auf der Detailseite eines Tools findest du die Schaltfläche **Bearbeiten** +(nur für die Person, die das Tool angelegt hat, sowie für Admins). + +Die Bearbeitungsseite (`/tools/:id/edit`) enthält dieselben Felder wie beim +Anlegen (Name, Kategorie, Website/Icon-URL, Beschreibung, Features, Tags) — +bereits mit den aktuellen Werten befüllt. + +- **Speichern** übernimmt die Änderungen. +- **Abbrechen** führt zurück zur Detailseite. + +API-Referenz: [`PATCH /tools/{id}`](/docs/reference/endpoints/tools#updateTool). + +## Löschen + +Über **Löschen** auf der Detailseite wird das Tool entfernt. Das Verhalten +hängt von deinem Tarif ab: + +- **Mit Papierkorb-Zugang** (Premium/Enterprise oder Admin): Das Tool wird + **soft gelöscht** — es verschwindet aus allen öffentlichen Ansichten, kann + aber im [Papierkorb](/docs/handbook/papierkorb) wiederhergestellt oder + endgültig gelöscht werden. +- **Ohne Papierkorb-Zugang:** Das Tool wird **endgültig** gelöscht und kann + nicht wiederhergestellt werden. + +Die Löschung ist nur für die Person, die das Tool angelegt hat, sowie für +Admins möglich. + +API-Referenz: [`DELETE /tools/{id}`](/docs/reference/endpoints/tools#deleteTool). diff --git a/docs/releases/v0.8.2/handbook/tools-finden.md b/docs/releases/v0.8.2/handbook/tools-finden.md new file mode 100644 index 0000000..c976515 --- /dev/null +++ b/docs/releases/v0.8.2/handbook/tools-finden.md @@ -0,0 +1,67 @@ + +# Tools finden & durchsuchen + +Der Bereich **Tools durchsuchen** (`/tools`) ist der Einstieg in den Katalog. +Hier kombinierst du Suche, Filter und Sortierung, um genau die Tools zu finden, +die dich interessieren. + +## Suche + +- Die **Suchleiste** durchsucht Name und Beschreibung (Volltext). +- Tastenkürzel: Drücke **`/`**, um die Suche zu fokussieren. +- Die Eingabe ist deaktiviert (Debounce), damit bei jedem Tastendruck sofort + nachgefiltert wird. + +## Filtern + +Über die Schaltfläche **Filter** (mit Badge für die Anzahl aktiver Filter) +öffnest du den Filter-Popover mit: + +- **Tags** — Auswahl über Checkboxen (scrollbare Liste). +- **Features** — Auswahl über Checkboxen. +- **Mindestbewertung** — Schieberegler von 0 bis 5 (Schritte von 0,5); zeigt + z. B. „3.0+" an. + +Aktive Filter erscheinen als **entfernbare Chips** über der Ergebnisliste. +Mit **Filter zurücksetzen** bzw. **Alle entfernen** räumst du sie wieder auf. + +## Sortieren + +Über das Dropdown **Sortieren** stehen folgende Optionen zur Verfügung: + +| Sortierung | Beschreibung | +| --- | --- | +| Neueste | Neue Tools zuerst | +| Top bewertet | Nach kombinierter Bewertung | +| Meistbewertet | Nach Anzahl der Bewertungen | +| Name (A–Z) | Alphabetisch aufsteigend | +| Name (Z–A) | Alphabetisch absteigend | +| Zuletzt aktualisiert | Nach letztem Update | + +## Ansicht & Dichte + +- **Ansicht wechseln:** Raster / Tabelle / Zeilen. +- **Dichte:** gemütlich / kompakt (Schieberegler). + +Deine Auswahl wird gespeichert — lokal im Browser und für angemeldete Nutzer:innen +zusätzlich serverseitig in den Präferenzen. Ansicht, Dichte, Suche, Filter und +Sortierung werden dabei in die URL übernommen, sodass du Ergebnisse teilen +kannst. + +## Tabellenansicht + +In der Tabellenansicht sind die Spalten **Tool**, **Bewertung** und **Anzahl +Bewertungen** sortierbar. Beim Überfahren einer Zeile erscheint eine Vorschau +mit Bewertungsdetails, Tags und Mini-Balken. + +## Auswählen für Vergleich & Watchlist + +- Auf jeder Karte/Zeile findest du ein **Vergleichs-Icon**, mit dem du Tools zur + [Vergleichsleiste](/docs/handbook/vergleichen) hinzufügst. +- Das **Lesezeichen-Icon** speichert Tools in deiner + [Watchlist](/docs/handbook/watchlist) (nur mit dem entsprechenden Tarif). + +## API + +Alle Such-, Filter- und Sortierparameter entsprechen den Query-Parametern von +[`GET /tools`](/docs/reference/endpoints/tools#listTools). diff --git a/docs/releases/v0.8.2/handbook/vergleichen.md b/docs/releases/v0.8.2/handbook/vergleichen.md new file mode 100644 index 0000000..ddcf0de --- /dev/null +++ b/docs/releases/v0.8.2/handbook/vergleichen.md @@ -0,0 +1,42 @@ + +# Vergleichen + +Mit der Vergleichsfunktion stellst du mehrere Tools **nebeneinander** gegenüber — +ideal, um eine fundierte Entscheidung zu treffen. + +> Vergleichen ist ein **Premium-Feature** (Premium/Enterprise) und steht Admins +> immer zur Verfügung. + +## Tools auswählen + +1. Im Bereich **Tools durchsuchen** klickst du auf jeder Karte/Zeile auf das + **Vergleichs-Icon** (Waage). +2. Unten erscheint die **Vergleichsleiste** mit den ausgewählten Tools als + Chips. Du kannst einzelne Tools entfernen (×) oder die Auswahl leeren. +3. Klicke auf **Vergleichen (N)**, um zur Vergleichsansicht zu gelangen. + +> Ohne Premium-Tarif ist der Button gesperrt (Schloss-Icon). Über den +> Dialog gelangst du zum Tarifwechsel +> (siehe [Pläne & Berechtigungen](/docs/handbook/plaene)). + +## Die Vergleichsansicht + +Die Ansicht zeigt eine Tabelle mit einer Spalte pro Tool. Zeilen: + +| Zeile | Inhalt | +| --- | --- | +| **Bewertung** | Sterne + Wert (z. B. `4.2/5`) | +| **Nützlichkeit** | Wert (X.X/5) | +| **Bedienbarkeit** | Wert (X.X/5) | +| **Anzahl Bewertungen** | Anzahl | +| **Beschreibung** | Text | +| **Features** | Badges | +| **Tags** | Badges | +| **Zuletzt aktualisiert** | Datum | + +Der **beste Wert** pro Zeile wird hervorgehoben (mit Trophäen-Icon). + +## API + +Die Vergleichsansicht liest die Daten über +[`GET /compare`](/docs/reference/endpoints/tools#listCompareTools). diff --git a/docs/releases/v0.8.2/handbook/watchlist.md b/docs/releases/v0.8.2/handbook/watchlist.md new file mode 100644 index 0000000..783da0c --- /dev/null +++ b/docs/releases/v0.8.2/handbook/watchlist.md @@ -0,0 +1,34 @@ + +# Watchlist + +Die **Watchlist** ist eine persönliche Favoritenliste. Tools darin kannst du +jederzeit per Klick wieder aufrufen und vergleichen. + +> Die Watchlist ist ein **Premium-Feature** (Premium/Enterprise) und steht +> Admins immer zur Verfügung. + +## Voraussetzung + +Du benötigst einen Tarif mit `watchlist`-Berechtigung. Fehlt diese, erscheint +beim Lesezeichen ein Hinweis auf den Tarifwechsel +(siehe [Pläne & Berechtigungen](/docs/handbook/plaene)). + +## Tool speichern + +- Auf jeder Karte/Zeile im Bereich **Tools durchsuchen** findest du das + **Lesezeichen-Icon**. +- Ein Klick speichert das Tool in deiner Watchlist — das Icon wird gefüllt. +- Ein erneuter Klick entfernt es wieder. + +## Watchlist ansehen + +Öffne die Watchlist über das Benutzermenü oder die Seitenleiste. Sie zeigt alle +gespeicherten Tools als Karten. Das gefüllte Lesezeichen auf einer Karte +entfernt das Tool aus der Liste. + +## Wo wird die Watchlist gespeichert? + +Die Watchlist ist eine Liste von Tool-IDs in deinen **Benutzerpräferenzen**. +Damit ist sie geräteübergreifend mit deinem Konto verbunden. + +API-Referenz: [`GET /auth/me/watchlist`](/docs/reference/endpoints/auth#getMeWatchlist). diff --git a/docs/releases/v0.8.2/reference.json b/docs/releases/v0.8.2/reference.json new file mode 100644 index 0000000..a3adfd9 --- /dev/null +++ b/docs/releases/v0.8.2/reference.json @@ -0,0 +1,2782 @@ +{ + "tags": [ + { + "name": "health", + "description": "Health operations", + "endpoints": [ + { + "operationId": "healthCheck", + "method": "GET", + "path": "/healthz", + "summary": "Health check", + "description": "Returns server health status", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Healthy", + "schema": { + "kind": "ref", + "value": "HealthStatus" + } + } + ] + }, + { + "operationId": "getVersion", + "method": "GET", + "path": "/version", + "summary": "Build version information", + "description": "Returns the running build version, commit SHA and build date", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Version information", + "schema": { + "kind": "ref", + "value": "VersionInfo" + } + } + ] + } + ] + }, + { + "name": "tools", + "description": "Tool management", + "endpoints": [ + { + "operationId": "listTools", + "method": "GET", + "path": "/tools", + "summary": "List all tools", + "description": "", + "parameters": [ + { + "name": "category", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "search", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "sort", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "newest, top_rated, most_reviewed, name_asc, name_desc, recently_updated" + }, + { + "name": "tags", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated tags; tool must include all of them", + "constraints": "" + }, + { + "name": "features", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated features; tool must include all of them", + "constraints": "" + }, + { + "name": "minRating", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "number" + }, + "description": "Minimum average combined rating (0-5)", + "constraints": "0–5" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "List of tools", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + } + ] + }, + { + "operationId": "createTool", + "method": "POST", + "path": "/tools", + "summary": "Create a new tool", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ToolInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created tool", + "schema": { + "kind": "ref", + "value": "Tool" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listCompareTools", + "method": "GET", + "path": "/compare", + "summary": "Compare tools side by side (premium)", + "description": "", + "parameters": [ + { + "name": "ids", + "in": "query", + "required": true, + "type": { + "kind": "type", + "value": "string" + }, + "description": "Comma-separated tool ids", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Tools in requested order", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + }, + { + "status": "401", + "description": "Authentication required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Premium feature required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getToolRatingHistory", + "method": "GET", + "path": "/tools/{id}/rating-history", + "summary": "Get a tool's rating history over time", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Rating history", + "schema": { + "kind": "array", + "value": "RatingHistoryItem" + } + }, + { + "status": "400", + "description": "Invalid id", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getTool", + "method": "GET", + "path": "/tools/{id}", + "summary": "Get a tool by ID", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Tool details", + "schema": { + "kind": "ref", + "value": "ToolWithStats" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateTool", + "method": "PATCH", + "path": "/tools/{id}", + "summary": "Update a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ToolUpdate" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated tool", + "schema": { + "kind": "ref", + "value": "Tool" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteTool", + "method": "DELETE", + "path": "/tools/{id}", + "summary": "Delete a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "404", + "description": "Not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listTrashedTools", + "method": "GET", + "path": "/tools/trash", + "summary": "List trashed (soft-deleted) tools", + "description": "", + "parameters": [ + { + "name": "search", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "List of trashed tools", + "schema": { + "kind": "array", + "value": "Tool" + } + }, + { + "status": "403", + "description": "Feature \"trash\" requires a higher tier", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "trashTools", + "method": "POST", + "path": "/tools/trash", + "summary": "Move tools to trash (admin)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Tools trashed", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteTrashedTools", + "method": "DELETE", + "path": "/tools/trash", + "summary": "Permanently delete trashed tools (admin)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "restoreTools", + "method": "POST", + "path": "/tools/trash/restore", + "summary": "Restore trashed tools", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "TrashToolsInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Tools restored", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Feature \"trash\" requires a higher tier", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "emptyTrash", + "method": "POST", + "path": "/tools/trash/empty", + "summary": "Permanently delete all trashed tools (admin)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Trash emptied", + "schema": { + "kind": "type", + "value": "object" + } + }, + { + "status": "403", + "description": "Admin required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "listCategories", + "method": "GET", + "path": "/categories", + "summary": "List all distinct tool categories", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Categories list", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + }, + { + "operationId": "listAllFeatures", + "method": "GET", + "path": "/features/all", + "summary": "List all distinct feature strings across all tools", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "All known features", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + }, + { + "operationId": "listAllTags", + "method": "GET", + "path": "/tags/all", + "summary": "List all distinct tag strings across all tools", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "All known tags", + "schema": { + "kind": "array", + "value": "string" + } + } + ] + } + ] + }, + { + "name": "ratings", + "description": "Tool ratings", + "endpoints": [ + { + "operationId": "listToolRatings", + "method": "GET", + "path": "/tools/{id}/ratings", + "summary": "List ratings for a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Ratings list", + "schema": { + "kind": "array", + "value": "Rating" + } + } + ] + }, + { + "operationId": "createRating", + "method": "POST", + "path": "/tools/{id}/ratings", + "summary": "Submit a rating for a tool", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "RatingInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created rating", + "schema": { + "kind": "ref", + "value": "Rating" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "404", + "description": "Tool not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "analytics", + "description": "Analytics and aggregated statistics", + "endpoints": [ + { + "operationId": "getAnalyticsSummary", + "method": "GET", + "path": "/analytics/summary", + "summary": "Overall platform statistics", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Platform-level summary stats", + "schema": { + "kind": "ref", + "value": "AnalyticsSummary" + } + } + ] + }, + { + "operationId": "getTopTools", + "method": "GET", + "path": "/analytics/top-tools", + "summary": "Top-rated tools", + "description": "", + "parameters": [ + { + "name": "limit", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + }, + { + "name": "metric", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "usefulness, usability, combined" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Top tools list", + "schema": { + "kind": "array", + "value": "TopToolEntry" + } + } + ] + }, + { + "operationId": "getAnalyticsByCategory", + "method": "GET", + "path": "/analytics/by-category", + "summary": "Rating statistics grouped by category", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Per-category statistics", + "schema": { + "kind": "array", + "value": "CategoryStats" + } + } + ] + }, + { + "operationId": "getRatingDistribution", + "method": "GET", + "path": "/analytics/rating-distribution", + "summary": "Distribution of rating scores across the platform", + "description": "", + "parameters": [ + { + "name": "toolId", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Rating score distribution", + "schema": { + "kind": "ref", + "value": "RatingDistribution" + } + } + ] + } + ] + }, + { + "name": "auth", + "description": "Authentication", + "endpoints": [ + { + "operationId": "getAuthMode", + "method": "GET", + "path": "/auth/mode", + "summary": "Get authentication mode (oidc or local)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Auth mode", + "schema": { + "kind": "ref", + "value": "AuthMode" + } + } + ] + }, + { + "operationId": "getCsrfToken", + "method": "GET", + "path": "/auth/csrf", + "summary": "Get a CSRF token for state-changing requests", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "CSRF token", + "schema": { + "kind": "ref", + "value": "CsrfToken" + } + } + ] + }, + { + "operationId": "localLogin", + "method": "POST", + "path": "/auth/login", + "summary": "Local username/password login", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "LocalLoginInput" + } + }, + "responses": [ + { + "status": "200", + "description": "Logged in successfully", + "schema": { + "kind": "ref", + "value": "AuthUser" + } + }, + { + "status": "401", + "description": "Invalid credentials", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getMe", + "method": "GET", + "path": "/auth/me", + "summary": "Get current authenticated user", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Current user info", + "schema": { + "kind": "ref", + "value": "AuthUser" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "changeMyPassword", + "method": "POST", + "path": "/auth/me/password", + "summary": "Change own password (local users only)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "ChangePasswordInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Password changed", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "400", + "description": "Invalid input or wrong current password", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "422", + "description": "OIDC user - password is managed by the identity provider", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "429", + "description": "Too many attempts", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getPasswordRedirect", + "method": "GET", + "path": "/auth/password-redirect", + "summary": "Get redirect URL for managing credentials in the identity provider", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Redirect URL (null in local mode)", + "schema": { + "kind": "ref", + "value": "PasswordRedirect" + } + } + ] + }, + { + "operationId": "getMePreferences", + "method": "GET", + "path": "/auth/me/preferences", + "summary": "Get current user's browse preferences", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "User preferences", + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateMePreferences", + "method": "PUT", + "path": "/auth/me/preferences", + "summary": "Update current user's browse preferences", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated preferences", + "schema": { + "kind": "ref", + "value": "UserPreferences" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "getMeWatchlist", + "method": "GET", + "path": "/auth/me/watchlist", + "summary": "Get current user's watchlist tools (premium)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Watchlist tools in saved order", + "schema": { + "kind": "array", + "value": "ToolWithStats" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Premium feature required", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "users", + "description": "User management (admin only)", + "endpoints": [ + { + "operationId": "listUsers", + "method": "GET", + "path": "/users", + "summary": "List all local users (admin only)", + "description": "", + "parameters": [], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "User list", + "schema": { + "kind": "array", + "value": "User" + } + }, + { + "status": "401", + "description": "Not authenticated", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "403", + "description": "Forbidden", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "createUser", + "method": "POST", + "path": "/users", + "summary": "Create a new local user (admin only)", + "description": "", + "parameters": [], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserCreateInput" + } + }, + "responses": [ + { + "status": "201", + "description": "Created user", + "schema": { + "kind": "ref", + "value": "User" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "409", + "description": "Username already exists", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "updateUser", + "method": "PATCH", + "path": "/users/{id}", + "summary": "Update user role (admin only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "UserRoleUpdate" + } + }, + "responses": [ + { + "status": "200", + "description": "Updated user", + "schema": { + "kind": "ref", + "value": "User" + } + }, + { + "status": "404", + "description": "User not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + }, + { + "operationId": "deleteUser", + "method": "DELETE", + "path": "/users/{id}", + "summary": "Delete a user (admin only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "204", + "description": "Deleted", + "schema": { + "kind": "type", + "value": "any" + } + } + ] + }, + { + "operationId": "setUserPassword", + "method": "PATCH", + "path": "/users/{id}/password", + "summary": "Set/reset a user's password (admin only, local users only)", + "description": "", + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": { + "required": true, + "schema": { + "kind": "ref", + "value": "SetPasswordInput" + } + }, + "responses": [ + { + "status": "204", + "description": "Password updated", + "schema": { + "kind": "type", + "value": "any" + } + }, + { + "status": "400", + "description": "Validation error", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "404", + "description": "User not found", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "422", + "description": "OIDC user - password is managed by the identity provider", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + }, + { + "status": "429", + "description": "Too many attempts", + "schema": { + "kind": "ref", + "value": "ErrorResponse" + } + } + ] + } + ] + }, + { + "name": "audit", + "description": "Audit log", + "endpoints": [ + { + "operationId": "listAuditLogs", + "method": "GET", + "path": "/audit-logs", + "summary": "List audit log entries (admin only)", + "description": "", + "parameters": [ + { + "name": "entityType", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "string" + }, + "description": "", + "constraints": "" + }, + { + "name": "entityId", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + }, + { + "name": "limit", + "in": "query", + "required": false, + "type": { + "kind": "type", + "value": "integer" + }, + "description": "", + "constraints": "" + } + ], + "requestBody": null, + "responses": [ + { + "status": "200", + "description": "Audit log entries", + "schema": { + "kind": "array", + "value": "AuditLog" + } + } + ] + } + ] + } + ], + "schemas": [ + { + "name": "HealthStatus", + "description": "", + "fields": [ + { + "name": "status", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "VersionInfo", + "description": "", + "fields": [ + { + "name": "version", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "commitSha", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "buildDate", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "trashRetentionDays", + "type": { + "kind": "type", + "value": "integer" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuthMode", + "description": "", + "fields": [ + { + "name": "mode", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "oidc, local" + } + ] + }, + { + "name": "CsrfToken", + "description": "", + "fields": [ + { + "name": "token", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "LocalLoginInput", + "description": "", + "fields": [ + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "User", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + }, + { + "name": "authProvider", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "local, oidc" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "UserCreateInput", + "description": "", + "fields": [ + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 2 chars" + }, + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + } + ] + }, + { + "name": "UserRoleUpdate", + "description": "", + "fields": [ + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + } + ] + }, + { + "name": "ChangePasswordInput", + "description": "", + "fields": [ + { + "name": "currentPassword", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "newPassword", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + } + ] + }, + { + "name": "SetPasswordInput", + "description": "", + "fields": [ + { + "name": "password", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 6 chars" + } + ] + }, + { + "name": "PasswordRedirect", + "description": "", + "fields": [ + { + "name": "url", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuditLog", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "entityType", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "entityId", + "type": { + "kind": "type", + "value": "integer | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "action", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "userId", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "username", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "changes", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "Tool", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "updatedAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "deletedAt", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "date-time" + }, + { + "name": "deletedBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "RatingHistoryItem", + "description": "", + "fields": [ + { + "name": "date", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "combined", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolWithStats", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdBy", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "updatedAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + }, + { + "name": "ratingCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgCombined", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolInput", + "description": "", + "fields": [ + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ToolUpdate", + "description": "", + "fields": [ + { + "name": "name", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "min 1 chars" + }, + { + "name": "description", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "websiteUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "iconUrl", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "features", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "tags", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "TrashToolsInput", + "description": "", + "fields": [ + { + "name": "ids", + "type": { + "kind": "array", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "min 1 items, max 500 items" + } + ] + }, + { + "name": "Rating", + "description": "", + "fields": [ + { + "name": "id", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "toolId", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "comment", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "reviewerName", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "createdAt", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "date-time" + } + ] + }, + { + "name": "RatingInput", + "description": "", + "fields": [ + { + "name": "usefulness", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "usability", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "1–5" + }, + { + "name": "comment", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "reviewerName", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AnalyticsSummary", + "description": "", + "fields": [ + { + "name": "totalTools", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "totalRatings", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgCombined", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "categoriesCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "mostRatedTool", + "type": { + "kind": "ref", + "value": "ToolWithStats" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "TopToolEntry", + "description": "", + "fields": [ + { + "name": "tool", + "type": { + "kind": "ref", + "value": "ToolWithStats" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "score", + "type": { + "kind": "type", + "value": "number" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "ratingCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "CategoryStats", + "description": "", + "fields": [ + { + "name": "category", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "toolCount", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "totalRatings", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsefulness", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "avgUsability", + "type": { + "kind": "type", + "value": "number | null" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "RatingDistribution", + "description": "", + "fields": [ + { + "name": "usefulness", + "type": { + "kind": "array", + "value": "ScoreBucket" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "usability", + "type": { + "kind": "array", + "value": "ScoreBucket" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ScoreBucket", + "description": "", + "fields": [ + { + "name": "score", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "count", + "type": { + "kind": "type", + "value": "integer" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "AuthUser", + "description": "", + "fields": [ + { + "name": "sub", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + }, + { + "name": "email", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "name", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "preferredUsername", + "type": { + "kind": "type", + "value": "string | null" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "role", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "admin, user" + }, + { + "name": "tier", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "free, premium, enterprise" + }, + { + "name": "entitlements", + "type": { + "kind": "array", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "" + }, + { + "name": "isLocal", + "type": { + "kind": "type", + "value": "boolean" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "UserPreferences", + "description": "", + "fields": [ + { + "name": "view", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "grid, table, rows" + }, + { + "name": "density", + "type": { + "kind": "type", + "value": "string" + }, + "required": false, + "description": "", + "constraints": "cozy, compact" + }, + { + "name": "watchlist", + "type": { + "kind": "array", + "value": "integer" + }, + "required": false, + "description": "", + "constraints": "" + } + ] + }, + { + "name": "ErrorResponse", + "description": "", + "fields": [ + { + "name": "error", + "type": { + "kind": "type", + "value": "string" + }, + "required": true, + "description": "", + "constraints": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/scripts/src/generate-docs.mjs b/scripts/src/generate-docs.mjs index 65cf65b..758cf8b 100644 --- a/scripts/src/generate-docs.mjs +++ b/scripts/src/generate-docs.mjs @@ -196,7 +196,7 @@ function slugify(name) { return name.replace(/\.md$/, "").toLowerCase().replace(/[^a-z0-9-]+/g, "-"); } -async function buildHandbookIndex() { +async function readHandbookPages() { let files; try { files = (await readdir(handbookDir)).filter(isHandbookFile); @@ -213,13 +213,35 @@ async function buildHandbookIndex() { file, title: frontmatter.title ?? extractTitle(body) ?? basename(file), order: typeof frontmatter.order === "number" ? frontmatter.order : 999, + body, }); - await writeFile(join(targetDir, "handbook", file), body); } pages.sort((a, b) => a.order - b.order || a.title.localeCompare(b.title)); return pages; } +async function writeHandbookTo(dir) { + const pages = await readHandbookPages(); + await mkdir(dir, { recursive: true }); + for (const page of pages) { + await writeFile(join(dir, page.file), page.body); + } + await writeFile( + join(dir, "index.json"), + JSON.stringify( + pages.map(({ slug, file, title, order }) => ({ slug, file, title, order })), + null, + 2, + ), + ); + return pages; +} + +async function buildHandbookIndex() { + const pages = await writeHandbookTo(join(targetDir, "handbook")); + return pages; +} + // --------------------------------------------------------------------------- // Search index // --------------------------------------------------------------------------- @@ -295,7 +317,8 @@ async function writeSnapshot(version) { const dir = join(releasesDir, version); await mkdir(dir, { recursive: true }); await writeFile(join(dir, "reference.json"), JSON.stringify(reference, null, 2)); - console.log(`[generate-docs] snapshot ${version} -> ${join(dir, "reference.json")}`); + await writeHandbookTo(join(dir, "handbook")); + console.log(`[generate-docs] snapshot ${version} -> ${dir}/ (reference.json + handbook)`); } // --------------------------------------------------------------------------- @@ -343,27 +366,48 @@ async function main() { JSON.stringify(handbookPages, null, 2), ); - // 3. Release notes + versioned reference snapshots + // 3. Release notes + versioned docs snapshots (last 7 versions) const versions = []; - for (const name of releaseNames) { + for (const name of releaseNames.slice(0, 7)) { const version = parseVersion(name); const content = await readFile(join(releasesDir, name), "utf8"); await copyFile(join(releasesDir, name), join(targetDir, "releases", name)); - const hasSnapshot = await stat(join(releasesDir, version, "reference.json")) + const snapDir = join(releasesDir, version); + const hasReference = await stat(join(snapDir, "reference.json")) .then(() => true) .catch(() => false); - if (hasSnapshot) { + const hasHandbook = await stat(join(snapDir, "handbook", "index.json")) + .then(() => true) + .catch(() => false); + if (hasReference) { await copyFile( - join(releasesDir, version, "reference.json"), + join(snapDir, "reference.json"), join(targetDir, "versions", `${version}.json`), ); } + if (hasHandbook) { + await mkdir(join(targetDir, "versions", version, "handbook"), { recursive: true }); + await copyFile( + join(snapDir, "handbook", "index.json"), + join(targetDir, "versions", version, "handbook", "index.json"), + ); + const pages = JSON.parse( + await readFile(join(snapDir, "handbook", "index.json"), "utf8"), + ); + for (const page of pages) { + await copyFile( + join(snapDir, "handbook", page.file), + join(targetDir, "versions", version, "handbook", page.file), + ); + } + } versions.push({ version, file: `releases/${name}`, title: extractTitle(content) ?? version, date: extractDate(content) ?? null, - hasReference: hasSnapshot, + hasReference, + hasHandbook, }); }