Archived
Next.js 14 + TypeScript + Tailwind + Prisma/SQLite. - LLM synthesis abstraction (OpenAI/Anthropic/Ollama) + admin settings UI - RSS ingestion pipeline (parser + cheerio content) + node-cron worker - AdSense AdUnit placements (header/in-feed/in-article/sidebar) - Sources analysis attribution, nofollow links, canonical/OG, sitemap/robots - Admin auth (ADMIN_API_KEY, timingSafeEqual, fail-closed 401) - Multi-cell lady-ga-ga marker, sitemap, robots, legal pages - Comprehensive README (setup, LLM config, AdSense, migrations, deploy)
80 lines
3.0 KiB
Plaintext
80 lines
3.0 KiB
Plaintext
// MapleBrief — database schema
|
|
// SQLite by default for zero-config local dev. To switch provider:
|
|
// 1. change `provider` in datasource below
|
|
// 2. update DATABASE_URL in .env
|
|
// 3. run `prisma migrate dev` (see README)
|
|
|
|
generator client {
|
|
provider = "prisma-client-js"
|
|
}
|
|
|
|
datasource db {
|
|
provider = "sqlite"
|
|
url = env("DATABASE_URL")
|
|
}
|
|
|
|
// A configured news source (RSS/Atom feed).
|
|
model Feed {
|
|
id String @id @default(cuid())
|
|
name String @unique
|
|
url String @unique
|
|
slug String
|
|
category String @default("news")
|
|
enabled Boolean @default(true)
|
|
lastFetchedAt DateTime?
|
|
articles Article[]
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
}
|
|
|
|
// A single synthesized news brief, built from one or more source stories.
|
|
model Article {
|
|
id String @id @default(cuid())
|
|
feedId String?
|
|
feed Feed? @relation(fields: [feedId], references: [id], onDelete: SetNull)
|
|
|
|
// Identity / dedup
|
|
guid String? @unique // feed item guid (primary dedup key)
|
|
dedupKey String // sha1 of normalized title (near-dup key)
|
|
sourceUrl String // original story URL (outbound link)
|
|
canonicalUrl String @unique // our canonical URL for this brief
|
|
siteName String // originating publisher, e.g. "CBC News"
|
|
|
|
// Source metadata
|
|
title String // original headline (kept for reference)
|
|
slug String @unique
|
|
category String @default("news") // mapped from the feed category
|
|
author String?
|
|
publishedAt DateTime?
|
|
image String? // original thumbnail URL
|
|
sources String? // JSON string[] of contributing publisher names
|
|
|
|
// Original content (kept for the "editor's notes" reference, NOT shown)
|
|
originalText String?
|
|
|
|
// Synthesized (original) output produced by the LLM
|
|
headline String? // rewritten original headline
|
|
body String? // 3 paragraphs, \n\n separated
|
|
takeaways String? // JSON string[] of key takeaways
|
|
tags String? // JSON string[]
|
|
llmProvider String? // provider that produced this brief
|
|
synthesizedAt DateTime?
|
|
status String @default("fetched") // fetched | synthesized | failed
|
|
|
|
createdAt DateTime @default(now())
|
|
updatedAt DateTime @updatedAt
|
|
|
|
@@index([category, publishedAt])
|
|
@@index([status, publishedAt])
|
|
@@index([dedupKey])
|
|
}
|
|
|
|
// Runtime configuration. The /admin/settings UI writes here; env vars act as
|
|
// fallbacks when a key is absent so the app boots with zero configuration.
|
|
model Setting {
|
|
key String @id
|
|
value String
|
|
isSecret Boolean @default(false)
|
|
updatedAt DateTime @updatedAt
|
|
}
|