Refactor for i18n, CMS integration, and project slugs; enhance admin & analytics

Co-authored-by: dennis <dennis@konkol.net>
This commit is contained in:
Cursor Agent
2026-01-12 14:36:10 +00:00
parent 0349c686fa
commit 12245eec8e
55 changed files with 4573 additions and 753 deletions

View File

@@ -9,6 +9,8 @@ datasource db {
model Project {
id Int @id @default(autoincrement())
slug String @unique @db.VarChar(255)
defaultLocale String @default("en") @db.VarChar(10)
title String @db.VarChar(255)
description String
content String
@@ -39,6 +41,8 @@ model Project {
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
translations ProjectTranslation[]
@@index([category])
@@index([featured])
@@index([published])
@@ -47,6 +51,75 @@ model Project {
@@index([tags])
}
model ProjectTranslation {
id Int @id @default(autoincrement())
projectId Int @map("project_id")
locale String @db.VarChar(10)
title String @db.VarChar(255)
description String
content Json?
metaDescription String?
keywords String?
ogImage String? @db.VarChar(500)
schema Json?
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
@@unique([projectId, locale])
@@index([locale])
@@index([projectId])
@@map("project_translations")
}
model ContentPage {
id Int @id @default(autoincrement())
key String @unique @db.VarChar(100)
status ContentStatus @default(PUBLISHED)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
translations ContentPageTranslation[]
@@map("content_pages")
}
model ContentPageTranslation {
id Int @id @default(autoincrement())
pageId Int @map("page_id")
locale String @db.VarChar(10)
title String?
slug String? @db.VarChar(255)
content Json
metaDescription String?
keywords String?
updatedAt DateTime @updatedAt @map("updated_at")
createdAt DateTime @default(now()) @map("created_at")
page ContentPage @relation(fields: [pageId], references: [id], onDelete: Cascade)
@@unique([pageId, locale])
@@index([locale])
@@index([slug])
@@map("content_page_translations")
}
model SiteSettings {
id Int @id @default(1)
defaultLocale String @default("en") @db.VarChar(10)
locales String[] @default(["en","de"])
theme Json?
updatedAt DateTime @updatedAt @map("updated_at")
@@map("site_settings")
}
enum ContentStatus {
DRAFT
PUBLISHED
}
model PageView {
id Int @id @default(autoincrement())
projectId Int? @map("project_id")

View File

@@ -1,4 +1,5 @@
import { PrismaClient } from "@prisma/client";
import { slugify } from "../lib/slug";
const prisma = new PrismaClient();
@@ -947,10 +948,21 @@ Visit any non-existent page on the site to see the terminal in action. Or click
},
];
const usedSlugs = new Set<string>();
for (const project of projects) {
const baseSlug = slugify(project.title);
let slug = baseSlug;
let counter = 2;
while (usedSlugs.has(slug) || !slug) {
slug = `${baseSlug || "project"}-${counter++}`;
}
usedSlugs.add(slug);
await prisma.project.create({
data: {
...project,
slug,
defaultLocale: "en",
difficulty: project.difficulty as
| "BEGINNER"
| "INTERMEDIATE"