Files
iboard/prisma/schema.prisma
T
jif2.zhang 74e1e7dd13
Docker Build and Push / build-image (push) Successful in 1m25s
feat(V0.2): 标题30字+智能拆分 / 时间标记 MM-dd / 时间词前缀 (T-TK-14~16)
- 标题上限收紧至 30 字 (服务端校验+详情编辑框); 快速添加输入框
  放宽到 200 字: 超长时在 30 字内分隔符处截断为标题, 完整原文进描述
- Task.markDate (DateTime, 迁移 20261025120000): 详情页 DatePicker
  选择/清除, 列表行与看板卡显示 MM-dd 小标签
- 快速添加时间词前缀: 今天/明天/后天/本周/下周/本月/下月 开头
  (后跟分隔符) 自动设为时间标记并移出标题; 本周=本周日, 本月=月末
- 需求/功能/数据库设计文档同步更新 T-TK-14~16 与 Task 表字段
2026-10-25 11:47:44 +08:00

492 lines
16 KiB
Plaintext

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Get a free hosted Postgres database in seconds: `npx create-db`
generator client {
provider = "prisma-client"
output = "../generated/prisma"
}
datasource db {
provider = "mysql"
}
model User {
id Int @id @default(autoincrement())
name String
email String @unique
password String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// V0.2 打卡 / 清单: 用户删除时级联清理
habits Habit[]
checkIns CheckIn[]
checkLists CheckList[]
templates CheckListTemplate[]
// V0.2 任务模块
ownedProjects Project[] @relation("ProjectOwner")
projectMembers ProjectMember[]
assignedTasks Task[] @relation("TaskAssignee")
createdTasks Task[] @relation("TaskCreator")
taskMembers TaskMember[]
}
model Article {
id Int @id @default(autoincrement())
title String
slug String @unique
content String
date String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Strategy {
id Int @id @default(autoincrement())
name String
description String
data Json
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Indicator {
id Int @id @default(autoincrement())
name String @unique
groupName String
title String @default("")
unit String @default("")
source String @default("")
frequency String @default("month") // year | quarter | month | day
hidden Boolean @default(false) // true = 数据/脚本保留, 但不在前端展示
points IndicatorPoint[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model IndicatorPoint {
id Int @id @default(autoincrement())
indicatorId Int
indicator Indicator @relation(fields: [indicatorId], references: [id], onDelete: Cascade)
period String // "2024" | "2024-Q1" | "2024-01" | "2024-01-15"
value Float
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@unique([indicatorId, period])
}
// 新闻: 每天 3 点由 mx_finance_search_news 拉取, 按影响大小排序, 累加多日供首页无限滚动
model News {
id Int @id @default(autoincrement())
title String
summary String @default("")
content String? @db.Text
source String @default("")
url String @default("")
publishedAt DateTime
impactRank Int
createdAt DateTime @default(now())
@@unique([title, publishedAt])
@@index([publishedAt])
}
// 报警: refresh 写库时比对同 (indicatorName, period) 的跨源数据, 差异超阈值记录
model Alert {
id Int @id @default(autoincrement())
indicatorName String
period String
valueA Float
valueB Float
sourceA String
sourceB String
diff Float
diffRatio Float
createdAt DateTime @default(now())
@@index([createdAt])
}
// 服务器监控: 对接 komari 公开 API
model ServerNode {
id Int @id @default(autoincrement())
uuid String @unique
name String
cpuName String @default("")
os String @default("")
region String @default("")
memTotal BigInt @default(0)
diskTotal BigInt @default(0)
cpuCores Int @default(0)
// 最新实时快照
cpuUsage Float @default(0)
ramUsed BigInt @default(0)
swapUsed BigInt @default(0)
load1 Float @default(0)
load5 Float @default(0)
load15 Float @default(0)
diskUsed BigInt @default(0)
netIn BigInt @default(0)
netOut BigInt @default(0)
uptime BigInt @default(0)
process Int @default(0)
connections Int @default(0)
status String @default("unknown") // online | offline | unknown
recordedAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// 时序历史: 每节点每分钟一条 (komari records 1 分钟粒度)
model ServerNodeHistory {
id Int @id @default(autoincrement())
uuid String
period DateTime
cpu Float @default(0)
ram BigInt @default(0)
load Float @default(0)
disk BigInt @default(0)
netIn BigInt @default(0)
netOut BigInt @default(0)
process Int @default(0)
connections Int @default(0)
createdAt DateTime @default(now())
@@unique([uuid, period])
@@index([uuid, period])
}
// 服务健康检查: 本应用自检 (健康端点 / DB / 第三方)
model ServiceCheck {
id Int @id @default(autoincrement())
name String @unique
url String
status String @default("unknown") // up | down | unknown
latencyMs Int @default(0)
message String @default("")
lastCheckAt DateTime
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Econ_SowInventory {
id Int @id @default(autoincrement())
month String @unique
inventory Int // 能繁母猪数量(万头)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model ScraperCache {
id Int @id @default(autoincrement())
scraperName String @unique // 爬虫名称,如:sow-inventory
lastDataHash String? // 上次数据指纹
lastUpdateAt DateTime // 上次更新时间
metadata Json? // 其他元数据
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
// 浙江省油价历史 (apizero price 接口每日抓取)
// 每天 04:00 cron 触发: 检查 schedule 里待定且 <= 昨天的调价日, 抓 price?province=浙江 存本表
// unique (date, province, fuel) 防重复
model OilPriceHistory {
id Int @id @default(autoincrement())
date DateTime // 调价日次日 (即数据实际抓取日)
province String // '浙江'
fuel String // '92' | '95' | '0'
price Float // 油价 (元/升, 4 位小数足够)
source String @default("apizero")
createdAt DateTime @default(now())
@@unique([date, province, fuel])
@@index([province, fuel, date])
}
// ==================== 环境监控:米家 ====================
// 米家授权状态 (DiskCache 存凭证, 本表只存状态/错误供前端展示)
model MihomeAccount {
id Int @id @default(autoincrement())
key String @unique @default("default")
userId String @default("")
status String @default("unknown") // ok | pending | expired
lastError String @default("")
updatedAt DateTime @updatedAt
}
model MihomeHome {
id Int @id @default(autoincrement())
homeId String @unique
name String
createdAt DateTime @default(now())
devices MihomeDevice[]
}
model MihomeDevice {
id Int @id @default(autoincrement())
did String @unique
homeId String
home MihomeHome @relation(fields: [homeId], references: [homeId])
model String
name String
alias String? // 用户别名, 仅本应用展示
tempSiid Int
tempPiid Int
humSiid Int
humPiid Int
battSiid Int // 电池电量属性 (炎米 3.1003 / 青萍 2.3)
battPiid Int
battery Int? // 最近一次电量 (每天读一次, 标题展示用)
batteryAt DateTime? // 电量读取时间 (当天缓存, 不频繁更新)
enabled Boolean @default(true)
lastSyncAt DateTime?
createdAt DateTime @default(now())
samples MihomeEnvSample[]
hours MihomeEnvHour[]
}
// 日档: 月视图(逐日)数据源; unique (deviceId, date) 防重
model MihomeEnvSample {
id Int @id @default(autoincrement())
deviceId Int
device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)
date DateTime // 采样日 (Asia/Shanghai 当日 00:00 存 UTC)
temperature Float? // 当日最后一次实时值 (或 vendor hour 均值)
humidity Float?
temperatureAvg Float? // 当日均值 (由小时档聚合, 或 vendor_hour)
humidityAvg Float?
temperatureMin Float? // 当日最低
temperatureMax Float? // 当日最高
humidityMin Float?
humidityMax Float?
origin String @default("live") // live | vendor_history | backfill
sampledAt DateTime?
raw Json?
createdAt DateTime @default(now())
@@unique([deviceId, date])
@@index([deviceId, date])
}
// 小时档: 当天视图(逐小时)数据源; unique (deviceId, hour) 防重
model MihomeEnvHour {
id Int @id @default(autoincrement())
deviceId Int
device MihomeDevice @relation(fields: [deviceId], references: [id], onDelete: Cascade)
hour DateTime // Asia/Shanghai 整点 (存 UTC)
temperatureAvg Float? // 该小时均值 (live 采样点, 或 vendor_hour)
humidityAvg Float?
raw Json?
createdAt DateTime @default(now())
@@unique([deviceId, hour])
@@index([deviceId, hour])
}
// ==================== 打卡 / 日历 / 清单 (V0.2) ====================
// 打卡项: 周期性目标 (每天 / 每周 / 每月 / 每年 n 次)
model Habit {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
period String @default("day") // day | week | month | year
target Int @default(1) // 周期内目标次数 n (>=1)
color String @default("#1677ff")
icon String @default("") // emoji 或图标名, 预留
note String @default("")
archived Boolean @default(false) // 归档: 不出现在今日待办, 历史保留
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
records CheckIn[]
@@index([userId, archived, sortOrder])
}
// 打卡记录: 一次打卡 = 一条; 同日多次 => seq 递增 (1..n, 超额继续累加)
model CheckIn {
id Int @id @default(autoincrement())
habitId Int
habit Habit @relation(fields: [habitId], references: [id], onDelete: Cascade)
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
date String @db.VarChar(10) // 本地日历日 "YYYY-MM-DD" (Asia/Shanghai)
seq Int @default(1) // 当日第几次
note String @default("")
source String @default("user") // user | backfill (补打)
createdAt DateTime @default(now())
@@unique([habitId, date, seq])
@@index([userId, date])
@@index([habitId, date])
}
// 检查清单: 出差 / 回老家 等场景的一次性清单
model CheckList {
id Int @id @default(autoincrement())
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
title String
scene String @default("") // 出差 | 回老家 | 自定义
note String @default("")
archived Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
items CheckListItem[]
@@index([userId, archived])
}
// 检查项: 勾选即完成
model CheckListItem {
id Int @id @default(autoincrement())
listId Int
list CheckList @relation(fields: [listId], references: [id], onDelete: Cascade)
title String
note String @default("")
done Boolean @default(false)
doneAt DateTime?
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([listId, sortOrder])
}
// 清单模板: builtin = 系统预置 (userId 为空); 否则为用户自定义
model CheckListTemplate {
id Int @id @default(autoincrement())
userId Int?
user User? @relation(fields: [userId], references: [id], onDelete: Cascade)
name String
scene String @default("")
items Json // [{ title, note }]
builtin Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
@@index([userId, builtin])
}
// ================== 任务模块 (V0.2 任务模块需求文档 T-*) ==================
// 口径见 docs/V0.2/任务模块/任务模块数据库设计文档.md
// 项目 = 隔离 + 权限单位; 看板列是实体(可增删改排序);
// 任务状态(todo/doing/done) 与 看板列 是两个正交维度, 由列的 boundStatus 决定是否联动.
model Project {
id Int @id @default(autoincrement())
name String @db.VarChar(50)
ownerId Int
owner User @relation("ProjectOwner", fields: [ownerId], references: [id], onDelete: Cascade)
isPersonal Boolean @default(false)
archived Boolean @default(false)
notes Json @default("[]") // 项目信息块 [{ title, content }] (T-PJ-13)
taskKinds Json? // 任务类型标签列表 (未配置时服务端回落默认四类)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members ProjectMember[]
columns BoardColumn[]
tasks Task[]
@@index([ownerId])
@@index([archived])
}
model ProjectMember {
id Int @id @default(autoincrement())
projectId Int
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
permission String @default("member")
createdAt DateTime @default(now())
@@unique([projectId, userId])
@@index([userId])
}
model BoardColumn {
id Int @id @default(autoincrement())
projectId Int
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
name String @db.VarChar(20)
sortOrder Int @default(0)
boundStatus String? @db.VarChar(8)
retention String @default("forever") @db.VarChar(8)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tasks Task[]
@@unique([projectId, name])
@@index([projectId, sortOrder])
}
model Task {
id Int @id @default(autoincrement())
projectId Int
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
columnId Int
column BoardColumn @relation(fields: [columnId], references: [id], onDelete: Restrict)
parentId Int?
parent Task? @relation("Subtask", fields: [parentId], references: [id], onDelete: Cascade)
children Task[] @relation("Subtask")
title String @db.VarChar(200)
description String @default("") @db.Text
kind String? @db.VarChar(20) // 任务类型标签 (项目 taskKinds 之一)
markDate DateTime? // 时间标记 (完整日期存储, 展示为 MM-dd)
status String @default("todo") @db.VarChar(8)
doneAt DateTime?
assigneeId Int?
assignee User? @relation("TaskAssignee", fields: [assigneeId], references: [id], onDelete: SetNull)
creatorId Int
creator User @relation("TaskCreator", fields: [creatorId], references: [id], onDelete: Cascade)
extras Json @default("[]")
sortOrder Int @default(0)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
members TaskMember[]
tags TaskTag[]
@@index([projectId, columnId])
@@index([projectId, status])
@@index([parentId])
@@index([projectId, doneAt])
}
model TaskMember {
id Int @id @default(autoincrement())
taskId Int
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
userId Int
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
@@unique([taskId, userId])
}
model TaskTag {
id Int @id @default(autoincrement())
taskId Int
task Task @relation(fields: [taskId], references: [id], onDelete: Cascade)
name String @db.VarChar(20)
@@index([taskId])
}