原来只有 `apps/web` 在 Prettier 下(配置在 `apps/web/.prettierrc.toml`、脚本在 web 的 package.json),后端和契约从来没格式化过 —— 手写在 100 列上下,`db/schema.ts` 还是 drizzle-kit pull 留下的 tab 缩进。两套口径分叉久了,跨端改一处就得记着「这边 什么风格」。 - 配置搬到根目录 `.prettierrc.toml`,内容不变(`semi=false`,其余全默认, printWidth 80 —— 和前端已有的格式一致,不另立一套宽度); - 脚本统一成根目录 `bun run fmt`,覆盖 `apps/*/src`、`apps/web/tests` 和两个构建 配置;web 自己那份 `fmt` 和重复的 prettier 依赖删掉; - `.prettierignore` 挡掉两类不该碰的:drizzle-kit 生成的 `src/db/meta/` 结构快照 (它是 db:generate 的比对输入,只该由 drizzle-kit 写)、unplugin 每次 dev 都会 重写的 `auto-imports.d.ts` / `components.d.ts`; - 全量跑了一遍。纯格式,无行为改动:api typecheck / check:routes / check:ast、 前端 type-check 全过,起 api 打了接口确认正常。前端这 39 个文件的小改动是 prettier 版本漂移(类型断言的换行口径变了),不是新配置带来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e600fd24cf
commit
ed56a209ea
122 files changed
+10557
-4843
No files matched your search
@@ -21,7 +21,13 @@ function excerpt(analysis: string | null) {
|
||||
return text.length <= 120 ? text : `${text.slice(0, 120)}…`
|
||||
}
|
||||
|
||||
function listItem(row: { id: number; username: string; createTime: string; analysis: string; isPinned: boolean }) {
|
||||
function listItem(row: {
|
||||
id: number
|
||||
username: string
|
||||
createTime: string
|
||||
analysis: string
|
||||
isPinned: boolean
|
||||
}) {
|
||||
return {
|
||||
id: row.id,
|
||||
username: row.username,
|
||||
@@ -41,7 +47,9 @@ const listColumns = {
|
||||
|
||||
adminAiRoutes.get("/ai/reports", requireTeacher, async (c) => {
|
||||
const username = c.req.query("username")?.trim()
|
||||
const where = username ? ilike(schema.user.username, `%${username}%`) : undefined
|
||||
const where = username
|
||||
? ilike(schema.user.username, `%${username}%`)
|
||||
: undefined
|
||||
|
||||
// 置顶列表不分页:它是「每个学生最新钉住的那份」,数量等于学生数,前端一次性拿走。
|
||||
// 但**形状必须和分页那支一样**:同一个 URL 返回两种形状,调用方没法照着一个类型写。
|
||||
@@ -49,7 +57,9 @@ adminAiRoutes.get("/ai/reports", requireTeacher, async (c) => {
|
||||
// 读的是 res.results,于是拿到 undefined,`pinnedReports.length` 在渲染时抛
|
||||
// 「Cannot read properties of undefined」——空库也照抛,这个页面每次打开都白屏。
|
||||
if (c.req.query("pinnedOnly") === "true") {
|
||||
const rows = await db.select(listColumns).from(schema.aiAnalysis)
|
||||
const rows = await db
|
||||
.select(listColumns)
|
||||
.from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id))
|
||||
.where(and(eq(schema.aiAnalysis.isPinned, true), where))
|
||||
.orderBy(desc(schema.aiAnalysis.createTime))
|
||||
@@ -62,11 +72,19 @@ adminAiRoutes.get("/ai/reports", requireTeacher, async (c) => {
|
||||
const limit = queryInteger(c.req.query("limit"), 10, { min: 1, max: 250 })
|
||||
const offset = queryInteger(c.req.query("offset"), 0, { min: 0 })
|
||||
const [totalRows, rows] = await Promise.all([
|
||||
db.select({ value: count() }).from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id)).where(where),
|
||||
db.select(listColumns).from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id)).where(where)
|
||||
.orderBy(desc(schema.aiAnalysis.createTime)).limit(limit).offset(offset),
|
||||
db
|
||||
.select({ value: count() })
|
||||
.from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id))
|
||||
.where(where),
|
||||
db
|
||||
.select(listColumns)
|
||||
.from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id))
|
||||
.where(where)
|
||||
.orderBy(desc(schema.aiAnalysis.createTime))
|
||||
.limit(limit)
|
||||
.offset(offset),
|
||||
])
|
||||
return success(c, {
|
||||
results: rows.map(listItem),
|
||||
@@ -75,15 +93,20 @@ adminAiRoutes.get("/ai/reports", requireTeacher, async (c) => {
|
||||
})
|
||||
|
||||
adminAiRoutes.get("/ai/reports/:id", requireTeacher, async (c) => {
|
||||
const [row] = await db.select({
|
||||
id: schema.aiAnalysis.id,
|
||||
username: schema.user.username,
|
||||
className: schema.user.className,
|
||||
createTime: schema.aiAnalysis.createTime,
|
||||
analysis: schema.aiAnalysis.analysis,
|
||||
}).from(schema.aiAnalysis)
|
||||
const [row] = await db
|
||||
.select({
|
||||
id: schema.aiAnalysis.id,
|
||||
username: schema.user.username,
|
||||
className: schema.user.className,
|
||||
createTime: schema.aiAnalysis.createTime,
|
||||
analysis: schema.aiAnalysis.analysis,
|
||||
})
|
||||
.from(schema.aiAnalysis)
|
||||
.innerJoin(schema.user, eq(schema.aiAnalysis.userId, schema.user.id))
|
||||
.where(eq(schema.aiAnalysis.id, queryInteger(c.req.param("id"), 0, { min: 1 }))).limit(1)
|
||||
.where(
|
||||
eq(schema.aiAnalysis.id, queryInteger(c.req.param("id"), 0, { min: 1 })),
|
||||
)
|
||||
.limit(1)
|
||||
if (!row) return failure(c, 404, "report-not-found", "AIAnalysis not found")
|
||||
// data / systemPrompt / userPrompt 一律不下发:里面是喂给模型的原始学情数据与提示词
|
||||
return success(c, row satisfies AdminAiReport)
|
||||
@@ -91,18 +114,36 @@ adminAiRoutes.get("/ai/reports/:id", requireTeacher, async (c) => {
|
||||
|
||||
adminAiRoutes.post("/ai/reports/:id/pin", requireTeacher, async (c) => {
|
||||
const id = queryInteger(c.req.param("id"), 0, { min: 1 })
|
||||
const [report] = await db.select({ id: schema.aiAnalysis.id, userId: schema.aiAnalysis.userId, isPinned: schema.aiAnalysis.isPinned })
|
||||
.from(schema.aiAnalysis).where(eq(schema.aiAnalysis.id, id)).limit(1)
|
||||
if (!report) return failure(c, 404, "report-not-found", "AIAnalysis not found")
|
||||
const [report] = await db
|
||||
.select({
|
||||
id: schema.aiAnalysis.id,
|
||||
userId: schema.aiAnalysis.userId,
|
||||
isPinned: schema.aiAnalysis.isPinned,
|
||||
})
|
||||
.from(schema.aiAnalysis)
|
||||
.where(eq(schema.aiAnalysis.id, id))
|
||||
.limit(1)
|
||||
if (!report)
|
||||
return failure(c, 404, "report-not-found", "AIAnalysis not found")
|
||||
|
||||
// 切换语义,与旧后端一致:已置顶则取消;未置顶则先把该学生其它置顶清掉,保证每人至多一份
|
||||
const next = !report.isPinned
|
||||
await db.transaction(async (tx) => {
|
||||
if (next) {
|
||||
await tx.update(schema.aiAnalysis).set({ isPinned: false })
|
||||
.where(and(eq(schema.aiAnalysis.userId, report.userId), eq(schema.aiAnalysis.isPinned, true)))
|
||||
await tx
|
||||
.update(schema.aiAnalysis)
|
||||
.set({ isPinned: false })
|
||||
.where(
|
||||
and(
|
||||
eq(schema.aiAnalysis.userId, report.userId),
|
||||
eq(schema.aiAnalysis.isPinned, true),
|
||||
),
|
||||
)
|
||||
}
|
||||
await tx.update(schema.aiAnalysis).set({ isPinned: next }).where(eq(schema.aiAnalysis.id, id))
|
||||
await tx
|
||||
.update(schema.aiAnalysis)
|
||||
.set({ isPinned: next })
|
||||
.where(eq(schema.aiAnalysis.id, id))
|
||||
})
|
||||
return success(c, { isPinned: next } satisfies ToggleAiReportPinResponse)
|
||||
})
|
||||
Reference in new issue
Block a user