feat: bootstrap admin auth, DB schema, and encrypted API keys
- Add Prisma schema for AdminUser, Source, Article, SearchRule, SearchRun, and encrypted ApiKey storage. - Implement JWT session middleware and admin login/logout. - Add AES-256-GCM encryption helpers for API keys. - Seed default news sources and admin credentials. - Create admin shell layout and login page.
This commit is contained in:
127
prisma/schema.prisma
Normal file
127
prisma/schema.prisma
Normal file
@@ -0,0 +1,127 @@
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
}
|
||||
|
||||
datasource db {
|
||||
provider = "sqlite"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
model AdminUser {
|
||||
id String @id @default(cuid())
|
||||
username String @unique
|
||||
passwordHash String
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
}
|
||||
|
||||
model Source {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
baseUrl String?
|
||||
apiCode String @unique
|
||||
defaultBias Bias?
|
||||
credibilityScore Float @default(5)
|
||||
active Boolean @default(true)
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
articles Article[]
|
||||
}
|
||||
|
||||
model Article {
|
||||
id String @id @default(cuid())
|
||||
title String
|
||||
summary String?
|
||||
url String @unique
|
||||
sourceId String
|
||||
source Source @relation(fields: [sourceId], references: [id])
|
||||
imageUrl String?
|
||||
publishedAt DateTime?
|
||||
fetchedAt DateTime @default(now())
|
||||
primaryBias Bias
|
||||
biasConfidence Float @default(0)
|
||||
politicalTags String @default("[]")
|
||||
rankScore Float @default(0)
|
||||
credibilityScore Float @default(5)
|
||||
status ArticleStatus @default(DRAFT)
|
||||
topics String @default("[]")
|
||||
rawData String @default("{}")
|
||||
aiExplanation String?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@index([primaryBias, status])
|
||||
@@index([status, publishedAt])
|
||||
@@index([rankScore])
|
||||
}
|
||||
|
||||
model ApiKey {
|
||||
id String @id @default(cuid())
|
||||
provider ApiProvider
|
||||
encryptedKey String
|
||||
label String?
|
||||
isActive Boolean @default(true)
|
||||
lastUsedAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
@@unique([provider, isActive])
|
||||
}
|
||||
|
||||
model SearchRule {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
query String
|
||||
sources String @default("[]")
|
||||
schedule String @default("0 */6 * * *")
|
||||
maxResults Int @default(20)
|
||||
autoPublish Boolean @default(false)
|
||||
minRankScore Float @default(5)
|
||||
isActive Boolean @default(true)
|
||||
aiProvider ApiProvider @default(KIMI)
|
||||
aiModel String?
|
||||
lastRunAt DateTime?
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
runs SearchRun[]
|
||||
}
|
||||
|
||||
model SearchRun {
|
||||
id String @id @default(cuid())
|
||||
ruleId String
|
||||
rule SearchRule @relation(fields: [ruleId], references: [id], onDelete: Cascade)
|
||||
status RunStatus @default(RUNNING)
|
||||
stage String? // e.g. "FETCHING", "ANALYZING", "SAVING"
|
||||
progress String? // human-readable progress, e.g. "3 of 12"
|
||||
articlesFound Int @default(0)
|
||||
error String?
|
||||
startedAt DateTime @default(now())
|
||||
endedAt DateTime?
|
||||
}
|
||||
|
||||
enum Bias {
|
||||
LEFT
|
||||
CENTER
|
||||
RIGHT
|
||||
}
|
||||
|
||||
enum ArticleStatus {
|
||||
DRAFT
|
||||
PUBLISHED
|
||||
ARCHIVED
|
||||
}
|
||||
|
||||
enum ApiProvider {
|
||||
NEWSAPI
|
||||
GNEWS
|
||||
KIMI
|
||||
WEBSEARCH
|
||||
NEWSDATA
|
||||
BRAVE
|
||||
}
|
||||
|
||||
enum RunStatus {
|
||||
RUNNING
|
||||
SUCCESS
|
||||
ERROR
|
||||
}
|
||||
Reference in New Issue
Block a user