4322383ed8
Deploy to LXC / deploy (push) Failing after 4s
- wiki_categories, wiki_pages, wiki_tags, wiki_page_tags tables - /wiki overview with category-grouped pages, tag sidebar, search - /wiki/new create page with Markdown editor and live preview - /wiki/[slug] view page with rendered Markdown, tags, edit/delete - /wiki/[slug]/edit with pre-populated editor - Tag input with existing tag suggestions (click to add) - Category management (add/delete) in sidebar - Tailwind Typography plugin for prose styling - marked package for Markdown rendering - Sidebar nav item with book icon Run db:push on server to create wiki tables. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
133 lines
3.9 KiB
TypeScript
133 lines
3.9 KiB
TypeScript
import type { PageServerLoad, Actions } from './$types';
|
|
import { db } from '$lib/server/db/index.js';
|
|
import { wikiPages, wikiCategories, wikiTags, wikiPageTags } from '$lib/server/db/schema.js';
|
|
import { eq, ilike, or, desc, sql, count } from 'drizzle-orm';
|
|
import { fail } from '@sveltejs/kit';
|
|
import { slugify } from '$lib/utils/slug.js';
|
|
|
|
export const load: PageServerLoad = async ({ url }) => {
|
|
const search = url.searchParams.get('q');
|
|
const tagFilter = url.searchParams.get('tag');
|
|
|
|
// Categories
|
|
const categories = await db
|
|
.select()
|
|
.from(wikiCategories)
|
|
.orderBy(wikiCategories.sortOrder, wikiCategories.name);
|
|
|
|
// Pages query
|
|
let pagesQuery;
|
|
if (tagFilter) {
|
|
// Filter by tag
|
|
const [tag] = await db.select().from(wikiTags).where(eq(wikiTags.name, tagFilter));
|
|
if (tag) {
|
|
const taggedPageIds = await db
|
|
.select({ pageId: wikiPageTags.pageId })
|
|
.from(wikiPageTags)
|
|
.where(eq(wikiPageTags.tagId, tag.id));
|
|
const ids = taggedPageIds.map((r) => r.pageId);
|
|
|
|
if (ids.length > 0) {
|
|
pagesQuery = db
|
|
.select({
|
|
id: wikiPages.id,
|
|
title: wikiPages.title,
|
|
slug: wikiPages.slug,
|
|
categoryId: wikiPages.categoryId,
|
|
categoryName: wikiCategories.name,
|
|
updatedAt: wikiPages.updatedAt,
|
|
updatedBy: wikiPages.updatedBy
|
|
})
|
|
.from(wikiPages)
|
|
.leftJoin(wikiCategories, eq(wikiPages.categoryId, wikiCategories.id))
|
|
.where(sql`${wikiPages.id} IN ${ids}`)
|
|
.orderBy(desc(wikiPages.updatedAt));
|
|
}
|
|
}
|
|
} else if (search) {
|
|
pagesQuery = db
|
|
.select({
|
|
id: wikiPages.id,
|
|
title: wikiPages.title,
|
|
slug: wikiPages.slug,
|
|
categoryId: wikiPages.categoryId,
|
|
categoryName: wikiCategories.name,
|
|
updatedAt: wikiPages.updatedAt,
|
|
updatedBy: wikiPages.updatedBy
|
|
})
|
|
.from(wikiPages)
|
|
.leftJoin(wikiCategories, eq(wikiPages.categoryId, wikiCategories.id))
|
|
.where(or(ilike(wikiPages.title, `%${search}%`), ilike(wikiPages.content, `%${search}%`)))
|
|
.orderBy(desc(wikiPages.updatedAt));
|
|
} else {
|
|
pagesQuery = db
|
|
.select({
|
|
id: wikiPages.id,
|
|
title: wikiPages.title,
|
|
slug: wikiPages.slug,
|
|
categoryId: wikiPages.categoryId,
|
|
categoryName: wikiCategories.name,
|
|
updatedAt: wikiPages.updatedAt,
|
|
updatedBy: wikiPages.updatedBy
|
|
})
|
|
.from(wikiPages)
|
|
.leftJoin(wikiCategories, eq(wikiPages.categoryId, wikiCategories.id))
|
|
.orderBy(desc(wikiPages.updatedAt));
|
|
}
|
|
|
|
const pages = pagesQuery ? await pagesQuery : [];
|
|
|
|
// Get tags for each page
|
|
const pageIds = pages.map((p) => p.id);
|
|
let tagsByPage: Record<string, string[]> = {};
|
|
if (pageIds.length > 0) {
|
|
const pageTags = await db
|
|
.select({
|
|
pageId: wikiPageTags.pageId,
|
|
tagName: wikiTags.name
|
|
})
|
|
.from(wikiPageTags)
|
|
.innerJoin(wikiTags, eq(wikiPageTags.tagId, wikiTags.id))
|
|
.where(sql`${wikiPageTags.pageId} IN ${pageIds}`);
|
|
|
|
for (const pt of pageTags) {
|
|
if (!tagsByPage[pt.pageId]) tagsByPage[pt.pageId] = [];
|
|
tagsByPage[pt.pageId].push(pt.tagName);
|
|
}
|
|
}
|
|
|
|
// All tags with counts
|
|
const allTags = await db
|
|
.select({ name: wikiTags.name, count: count() })
|
|
.from(wikiTags)
|
|
.innerJoin(wikiPageTags, eq(wikiTags.id, wikiPageTags.tagId))
|
|
.groupBy(wikiTags.name)
|
|
.orderBy(wikiTags.name);
|
|
|
|
return {
|
|
categories,
|
|
pages: pages.map((p) => ({ ...p, tags: tagsByPage[p.id] ?? [] })),
|
|
allTags,
|
|
filters: { search, tag: tagFilter }
|
|
};
|
|
};
|
|
|
|
export const actions: Actions = {
|
|
createCategory: async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const name = (formData.get('name') as string)?.trim();
|
|
if (!name) return fail(400, { error: 'Category name is required' });
|
|
|
|
const slug = slugify(name);
|
|
await db.insert(wikiCategories).values({ name, slug });
|
|
return { categoryCreated: true };
|
|
},
|
|
|
|
deleteCategory: async ({ request }) => {
|
|
const formData = await request.formData();
|
|
const id = formData.get('id') as string;
|
|
await db.delete(wikiCategories).where(eq(wikiCategories.id, id));
|
|
return { categoryDeleted: true };
|
|
}
|
|
};
|