Docker Build and Push / build-image (push) Successful in 1m22s
- 快速添加改 3 行 textarea, Enter 创建/Shift+Enter 换行/Esc 关闭, 焦点离开且无内容时自动收起 - 任务 ⋯ ActionSheet 点选动作后 handler.close() 收回菜单 - Project.icon (TEXT data URL, 迁移 20261025130000): 项目管理可选图片, 客户端裁为 96x96 cover JPEG (~15KB); 项目列表/最近项目/管理页图标位 显示图片, 保持原尺寸与圆角; 可恢复默认首字色块
493 lines
16 KiB
Plaintext
493 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? // 任务类型标签列表 (未配置时服务端回落默认四类)
|
|
icon String? @db.Text // 项目图标 (data URL, 客户端裁剪为 96x96 JPEG)
|
|
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])
|
|
} |