Files
paliad/internal/handlers/links.go
m 460736ad1e refactor(t-paliad-092): rename Go module path patholo → paliad
F-6 from t-paliad-074 architecture audit. The Gitea repo was renamed
m/patholo → mAi/paliad → m/paliad, but go.mod still declared
`mgit.msbls.de/m/patholo` and every internal import echoed the
pre-rebrand name.

Sweep:
- go.mod: module path → mgit.msbls.de/m/paliad
- All *.go files: imports rewritten via sed
- README.md, docs/design-kanzlai-integration.md: mAi/paliad → m/paliad
- Frontend issue-reference comments (mAi/paliad#N → m/paliad#N) in
  i18n.ts, theme.ts, sidebar.ts, app.ts, Sidebar.tsx, PWAHead.tsx,
  global.css

Verified: go build/vet/test ./... clean, bun run build clean,
no remaining mgit.msbls.de/m/patholo or mAi/paliad references
outside docs that intentionally describe the rename history.
2026-04-30 16:46:31 +02:00

327 lines
11 KiB
Go

package handlers
import (
"encoding/base64"
"encoding/json"
"log"
"net/http"
"strings"
"mgit.msbls.de/m/paliad/internal/auth"
"mgit.msbls.de/m/paliad/internal/services"
)
type linkCategory struct {
ID string `json:"id"`
NameDE string `json:"nameDE"`
NameEN string `json:"nameEN"`
}
type link struct {
ID string `json:"id"`
Category string `json:"category"`
Title string `json:"title"`
URL string `json:"url"`
DescDE string `json:"descDE"`
DescEN string `json:"descEN"`
}
type linksResponse struct {
Categories []linkCategory `json:"categories"`
Links []link `json:"links"`
}
var linkCategories = []linkCategory{
{ID: "courts", NameDE: "Gerichte & Ämter", NameEN: "Courts & Offices"},
{ID: "recherche", NameDE: "Recherche", NameEN: "Research"},
{ID: "upc", NameDE: "UPC", NameEN: "UPC"},
{ID: "gesetze", NameDE: "Gesetze", NameEN: "Legislation"},
}
var curatedLinks = []link{
// Gerichte & Ämter
{
ID: "upc-cms", Category: "courts",
Title: "UPC Case Management System",
URL: "https://www.unified-patent-court.org/en/registry/case-management-system",
DescDE: "Verfahrensverwaltung des Einheitlichen Patentgerichts. Klageschriften, Schriftsätze und Verfahrensstatus.",
DescEN: "Case management of the Unified Patent Court. Statements of claim, written pleadings, and case status.",
},
{
ID: "upc-register", Category: "courts",
Title: "UPC Register",
URL: "https://www.unified-patent-court.org/en/registry",
DescDE: "Öffentliches Register des UPC. Verfahrensdokumente und Entscheidungen.",
DescEN: "Public register of the UPC. Case documents and decisions.",
},
{
ID: "epo", Category: "courts",
Title: "Europäisches Patentamt (EPO)",
URL: "https://www.epo.org",
DescDE: "Europäisches Patentamt. Patentanmeldungen, Recherchen und Prüfungsverfahren.",
DescEN: "European Patent Office. Patent applications, searches, and examination procedures.",
},
{
ID: "dpma", Category: "courts",
Title: "DPMA",
URL: "https://www.dpma.de",
DescDE: "Deutsches Patent- und Markenamt. Nationale Patente, Marken und Designs.",
DescEN: "German Patent and Trade Mark Office. National patents, trade marks, and designs.",
},
{
ID: "bpatg", Category: "courts",
Title: "Bundespatentgericht",
URL: "https://www.bundespatentgericht.de",
DescDE: "Bundespatentgericht. Nichtigkeitsverfahren und Beschwerden gegen DPMA-Entscheidungen.",
DescEN: "Federal Patent Court. Nullity proceedings and appeals against DPMA decisions.",
},
{
ID: "euipo", Category: "courts",
Title: "EUIPO",
URL: "https://euipo.europa.eu",
DescDE: "Amt der Europäischen Union für geistiges Eigentum. EU-Marken und Gemeinschaftsgeschmacksmuster.",
DescEN: "European Union Intellectual Property Office. EU trade marks and Community designs.",
},
// Recherche
{
ID: "espacenet", Category: "recherche",
Title: "Espacenet",
URL: "https://worldwide.espacenet.com",
DescDE: "Weltweite Patentdatenbank des EPO. Über 150 Mio. Patentdokumente durchsuchbar.",
DescEN: "Worldwide patent database of the EPO. Over 150 million patent documents searchable.",
},
{
ID: "dpma-register", Category: "recherche",
Title: "DPMAregister",
URL: "https://register.dpma.de",
DescDE: "Amtliches Register für deutsche Patente, Marken und Designs. Rechts- und Verfahrensstand.",
DescEN: "Official register for German patents, trade marks, and designs. Legal and procedural status.",
},
{
ID: "depatisnet", Category: "recherche",
Title: "DEPATISnet",
URL: "https://depatisnet.dpma.de",
DescDE: "Recherchesystem des DPMA. Weltweite Patentrecherche mit Klassifikationssuche.",
DescEN: "DPMA search system. Worldwide patent searches with classification search.",
},
{
ID: "google-patents", Category: "recherche",
Title: "Google Patents",
URL: "https://patents.google.com",
DescDE: "Google-Patentsuche. Volltextrecherche und maschinelle Übersetzung von Patentschriften.",
DescEN: "Google patent search. Full-text search and machine translation of patent documents.",
},
{
ID: "patentscope", Category: "recherche",
Title: "Patentscope (WIPO)",
URL: "https://patentscope.wipo.int",
DescDE: "Internationale Patentdatenbank der WIPO. PCT-Anmeldungen und nationale Sammlungen.",
DescEN: "International patent database of WIPO. PCT applications and national collections.",
},
// UPC
{
ID: "upc-rop", Category: "upc",
Title: "Rules of Procedure",
URL: "https://www.unified-patent-court.org/en/court/legal-documents/rules-of-procedure",
DescDE: "Verfahrensordnung des Einheitlichen Patentgerichts. Vollständiger Regeltext mit Anhängen.",
DescEN: "Rules of Procedure of the Unified Patent Court. Complete rule text with annexes.",
},
{
ID: "upc-fees", Category: "upc",
Title: "Schedule of Fees",
URL: "https://www.unified-patent-court.org/en/court/legal-documents/court-fees",
DescDE: "Gebührenordnung des UPC. Feste und streitwertabhängige Gerichtsgebühren.",
DescEN: "UPC fee schedule. Fixed and value-based court fees.",
},
{
ID: "upc-practice", Category: "upc",
Title: "Practice Directions",
URL: "https://www.unified-patent-court.org/en/court/legal-documents/practice-directions",
DescDE: "Praxisanweisungen des UPC. Praktische Hinweise zu Verfahrensabläufen.",
DescEN: "UPC practice directions. Practical guidance on procedural matters.",
},
{
ID: "upc-website", Category: "upc",
Title: "UPC Website",
URL: "https://www.unified-patent-court.org",
DescDE: "Offizielle Website des Einheitlichen Patentgerichts. Nachrichten, Appointments und Informationen.",
DescEN: "Official website of the Unified Patent Court. News, events, and information.",
},
{
ID: "youpc-judgments", Category: "upc",
Title: "UPC Case Law (youpc.org)",
URL: "https://youpc.org/judgments",
DescDE: "Durchsuchbare Datenbank aller UPC-Entscheidungen mit Zusammenfassungen und Themen-Tags.",
DescEN: "Searchable database of all UPC decisions with summaries and topic tags.",
},
// Gesetze
{
ID: "patg", Category: "gesetze",
Title: "PatG — Patentgesetz",
URL: "https://dejure.org/gesetze/PatG",
DescDE: "Deutsches Patentgesetz. Volltext mit Querverweisen und Rechtsprechung.",
DescEN: "German Patent Act. Full text with cross-references and case law.",
},
{
ID: "epue", Category: "gesetze",
Title: "EPÜ — Europäisches Patentübereinkommen",
URL: "https://dejure.org/gesetze/EPUe",
DescDE: "Europäisches Patentübereinkommen. Materialien und Querverweise auf dejure.org.",
DescEN: "European Patent Convention. Materials and cross-references on dejure.org.",
},
{
ID: "upca", Category: "gesetze",
Title: "UPCA — Übereinkommen über ein Einheitliches Patentgericht",
URL: "https://www.unified-patent-court.org/en/court/legal-documents/agreement",
DescDE: "Übereinkommen über ein Einheitliches Patentgericht (EPGÜ). Gründungsvertrag des UPC.",
DescEN: "Agreement on a Unified Patent Court. Founding treaty of the UPC.",
},
{
ID: "gkg", Category: "gesetze",
Title: "GKG — Gerichtskostengesetz",
URL: "https://dejure.org/gesetze/GKG",
DescDE: "Gerichtskostengesetz. Gebührentabellen und Streitwertvorschriften.",
DescEN: "Court Fees Act. Fee tables and dispute value provisions.",
},
{
ID: "rvg", Category: "gesetze",
Title: "RVG — Rechtsanwaltsvergütungsgesetz",
URL: "https://dejure.org/gesetze/RVG",
DescDE: "Rechtsanwaltsvergütungsgesetz. Anwaltsgebühren und Vergütungsverzeichnis.",
DescEN: "Lawyers' Remuneration Act. Attorney fees and remuneration schedule.",
},
{
ID: "zpo", Category: "gesetze",
Title: "ZPO — Zivilprozessordnung",
URL: "https://dejure.org/gesetze/ZPO",
DescDE: "Zivilprozessordnung. Verfahrensrechtliche Grundlage für Patentverletzungsklagen.",
DescEN: "Code of Civil Procedure. Procedural basis for patent infringement actions.",
},
}
func handleLinksPage(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, "dist/links.html")
}
func handleLinksAPI(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusOK, linksResponse{
Categories: linkCategories,
Links: curatedLinks,
})
}
type linkSuggestion struct {
Title string `json:"title"`
URL string `json:"url"`
Category string `json:"category"`
Description string `json:"description"`
}
func handleLinkSuggest(w http.ResponseWriter, r *http.Request) {
if !requireDB(w) {
return
}
var req linkSuggestion
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
if req.Title == "" || req.URL == "" || req.Category == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "title, url, and category are required"})
return
}
if err := dbSvc.link.InsertSuggestion(r.Context(), services.LinkSuggestionInput{
Title: req.Title,
URL: req.URL,
Category: req.Category,
Description: req.Description,
SuggestedBy: extractEmailFromCookie(r),
}); err != nil {
log.Printf("link suggest insert: %v", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "could not save suggestion"})
return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "ok"})
}
type linkFeedback struct {
LinkID string `json:"linkId"`
Type string `json:"type"`
Message string `json:"message"`
}
func handleLinkFeedback(w http.ResponseWriter, r *http.Request) {
if !requireDB(w) {
return
}
var req linkFeedback
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid request"})
return
}
if req.LinkID == "" || req.Type == "" {
writeJSON(w, http.StatusBadRequest, map[string]string{"error": "linkId and type are required"})
return
}
if err := dbSvc.link.InsertFeedback(r.Context(), services.LinkFeedbackInput{
LinkID: req.LinkID,
FeedbackType: req.Type,
Message: req.Message,
SubmittedBy: extractEmailFromCookie(r),
}); err != nil {
log.Printf("link feedback insert: %v", err)
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "could not save feedback"})
return
}
writeJSON(w, http.StatusCreated, map[string]string{"status": "ok"})
}
// handleSuggestionCount intentionally swallows DB errors and returns 0 so the
// admin badge never breaks page rendering — the count is informational, not
// load-bearing. When DATABASE_URL isn't set the service is nil and we return 0
// rather than 503: knowledge-platform-only deployments still serve the page.
func handleSuggestionCount(w http.ResponseWriter, r *http.Request) {
if dbSvc == nil {
writeJSON(w, http.StatusOK, map[string]int{"count": 0})
return
}
count, err := dbSvc.link.CountPendingSuggestions(r.Context())
if err != nil {
writeJSON(w, http.StatusOK, map[string]int{"count": 0})
return
}
writeJSON(w, http.StatusOK, map[string]int{"count": count})
}
// extractEmailFromCookie decodes the user's email from the session JWT.
func extractEmailFromCookie(r *http.Request) string {
c, err := r.Cookie(auth.SessionCookieName)
if err != nil || c.Value == "" {
return ""
}
value := c.Value
parts := strings.Split(value, ".")
if len(parts) != 3 {
return ""
}
payload, err := base64.RawURLEncoding.DecodeString(parts[1])
if err != nil {
return ""
}
var claims struct {
Email string `json:"email"`
}
if err := json.Unmarshal(payload, &claims); err != nil {
return ""
}
return claims.Email
}