Added Wiki
This commit is contained in:
203
backend/src/routes/wiki.ts
Normal file
203
backend/src/routes/wiki.ts
Normal file
@@ -0,0 +1,203 @@
|
||||
import { FastifyInstance } from "fastify"
|
||||
import { and, eq, isNull, asc, desc } from "drizzle-orm"
|
||||
import { wikiPages, authUsers } from "../../db/schema/"
|
||||
|
||||
// Types für Request Body & Query (statt Zod)
|
||||
interface WikiTreeQuery {
|
||||
entityType?: string
|
||||
entityId?: number // Kommt als string oder number an, je nach parser
|
||||
entityUuid?: string
|
||||
}
|
||||
|
||||
interface WikiCreateBody {
|
||||
title: string
|
||||
parentId?: string // UUID
|
||||
isFolder?: boolean
|
||||
entityType?: string
|
||||
entityId?: number
|
||||
entityUuid?: string
|
||||
}
|
||||
|
||||
interface WikiUpdateBody {
|
||||
title?: string
|
||||
content?: any // Das Tiptap JSON Object
|
||||
parentId?: string | null
|
||||
sortOrder?: number
|
||||
isFolder?: boolean
|
||||
}
|
||||
|
||||
export default async function wikiRoutes (server: FastifyInstance) {
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 1. GET /wiki/tree
|
||||
// Lädt die Struktur für die Sidebar (OHNE Content -> Schnell)
|
||||
// ---------------------------------------------------------
|
||||
server.get<{ Querystring: WikiTreeQuery }>("/wiki/tree", async (req, reply) => {
|
||||
const user = req.user
|
||||
const { entityType, entityId, entityUuid } = req.query
|
||||
|
||||
// Basis-Filter: Tenant Sicherheit
|
||||
const filters = [eq(wikiPages.tenantId, user.tenant_id)]
|
||||
|
||||
// Logik: Laden wir "Allgemein" oder "Entitäts-Spezifisch"?
|
||||
if (entityType) {
|
||||
// Spezifisch (z.B. Kunde)
|
||||
filters.push(eq(wikiPages.entityType, entityType))
|
||||
|
||||
if (entityId) {
|
||||
filters.push(eq(wikiPages.entityId, Number(entityId)))
|
||||
} else if (entityUuid) {
|
||||
filters.push(eq(wikiPages.entityUuid, entityUuid))
|
||||
}
|
||||
} else {
|
||||
// Allgemein (alles wo entityType NULL ist)
|
||||
filters.push(isNull(wikiPages.entityType))
|
||||
}
|
||||
|
||||
// Performance: Nur Metadaten laden, kein riesiges JSON-Content
|
||||
const pages = await server.db
|
||||
.select({
|
||||
id: wikiPages.id,
|
||||
parentId: wikiPages.parentId,
|
||||
title: wikiPages.title,
|
||||
isFolder: wikiPages.isFolder,
|
||||
sortOrder: wikiPages.sortOrder,
|
||||
entityType: wikiPages.entityType,
|
||||
updatedAt: wikiPages.updatedAt,
|
||||
})
|
||||
.from(wikiPages)
|
||||
.where(and(...filters))
|
||||
.orderBy(asc(wikiPages.sortOrder), asc(wikiPages.title))
|
||||
|
||||
return pages
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 2. GET /wiki/:id
|
||||
// Lädt EINEN Eintrag komplett MIT Content (für den Editor)
|
||||
// ---------------------------------------------------------
|
||||
server.get<{ Params: { id: string } }>("/wiki/:id", async (req, reply) => {
|
||||
const user = req.user
|
||||
const { id } = req.params
|
||||
|
||||
// Drizzle Query API nutzen für Relation (optional Author laden)
|
||||
const page = await server.db.query.wikiPages.findFirst({
|
||||
where: and(
|
||||
eq(wikiPages.id, id),
|
||||
eq(wikiPages.tenantId, user.tenant_id)
|
||||
),
|
||||
with: {
|
||||
author: {
|
||||
columns: {
|
||||
id: true,
|
||||
// name: true, // Falls im authUsers schema vorhanden
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
if (!page) {
|
||||
return reply.code(404).send({ error: "Page not found" })
|
||||
}
|
||||
|
||||
return page
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 3. POST /wiki
|
||||
// Erstellt neuen Eintrag
|
||||
// ---------------------------------------------------------
|
||||
server.post<{ Body: WikiCreateBody }>("/wiki", async (req, reply) => {
|
||||
const user = req.user
|
||||
const body = req.body
|
||||
|
||||
// Simple Validierung
|
||||
if (!body.title) return reply.code(400).send({ error: "Title required" })
|
||||
|
||||
// Split Logik für Polymorphie
|
||||
const hasEntity = !!body.entityType
|
||||
|
||||
const [newPage] = await server.db
|
||||
.insert(wikiPages)
|
||||
.values({
|
||||
tenantId: user.tenant_id,
|
||||
title: body.title,
|
||||
parentId: body.parentId || null, // undefined abfangen
|
||||
isFolder: body.isFolder ?? false,
|
||||
|
||||
// Polymorphe Zuweisung
|
||||
entityType: hasEntity ? body.entityType : null,
|
||||
entityId: hasEntity && body.entityId ? body.entityId : null,
|
||||
entityUuid: hasEntity && body.entityUuid ? body.entityUuid : null,
|
||||
//@ts-ignore
|
||||
createdBy: user.id,
|
||||
//@ts-ignore
|
||||
updatedBy: user.id
|
||||
})
|
||||
.returning()
|
||||
|
||||
return newPage
|
||||
})
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 4. PATCH /wiki/:id
|
||||
// Universal-Update (Inhalt, Titel, Verschieben, Sortieren)
|
||||
// ---------------------------------------------------------
|
||||
server.patch<{ Params: { id: string }; Body: WikiUpdateBody }>(
|
||||
"/wiki/:id",
|
||||
async (req, reply) => {
|
||||
const user = req.user
|
||||
const { id } = req.params
|
||||
const body = req.body
|
||||
|
||||
// 1. Prüfen ob Eintrag existiert & Tenant gehört
|
||||
const existing = await server.db.query.wikiPages.findFirst({
|
||||
where: and(eq(wikiPages.id, id), eq(wikiPages.tenantId, user.tenant_id)),
|
||||
columns: { id: true }
|
||||
})
|
||||
|
||||
if(!existing) return reply.code(404).send({ error: "Not found" })
|
||||
|
||||
// 2. Update durchführen
|
||||
const [updatedPage] = await server.db
|
||||
.update(wikiPages)
|
||||
.set({
|
||||
title: body.title, // update title if defined
|
||||
content: body.content, // update content if defined
|
||||
parentId: body.parentId, // update parent (move) if defined
|
||||
sortOrder: body.sortOrder,
|
||||
isFolder: body.isFolder,
|
||||
updatedAt: new Date(),
|
||||
//@ts-ignore
|
||||
updatedBy: user.id
|
||||
})
|
||||
.where(eq(wikiPages.id, id))
|
||||
.returning()
|
||||
|
||||
return updatedPage
|
||||
}
|
||||
)
|
||||
|
||||
// ---------------------------------------------------------
|
||||
// 5. DELETE /wiki/:id
|
||||
// Löscht Eintrag (DB Cascade erledigt die Kinder)
|
||||
// ---------------------------------------------------------
|
||||
server.delete<{ Params: { id: string } }>("/wiki/:id", async (req, reply) => {
|
||||
const user = req.user
|
||||
const { id } = req.params
|
||||
|
||||
const result = await server.db
|
||||
.delete(wikiPages)
|
||||
.where(and(
|
||||
eq(wikiPages.id, id),
|
||||
eq(wikiPages.tenantId, user.tenant_id) // WICHTIG: Security
|
||||
))
|
||||
.returning({ id: wikiPages.id })
|
||||
|
||||
if (result.length === 0) {
|
||||
return reply.code(404).send({ error: "Not found or not authorized" })
|
||||
}
|
||||
|
||||
return { success: true, deletedId: result[0].id }
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user