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.
327 lines
11 KiB
Go
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
|
|
}
|
|
|