= {
"arrow-right": ArrowRight,
award: Award,
"book-open": BookOpen,
+ bug: Bug,
"bar-chart-2": ChartNoAxesColumn,
check: Check,
"chevron-down": ChevronDown,
diff --git a/client/src/utils/type.ts b/client/src/utils/type.ts
index 0813ff4..7d0dce3 100644
--- a/client/src/utils/type.ts
+++ b/client/src/utils/type.ts
@@ -167,6 +167,8 @@ export interface SubmissionAll {
view_count: number
created: Date
modified: Date
+ // 交的时候写的反思,只有老师和作者本人拿得到
+ reflection?: string | null
}
export interface UserTag {
diff --git a/server/src/db.ts b/server/src/db.ts
index bb207e7..fa6dff2 100644
--- a/server/src/db.ts
+++ b/server/src/db.ts
@@ -51,6 +51,9 @@ CREATE INDEX IF NOT EXISTS "prompt_message_prompt_level_e461b596" ON "prompt_mes
CREATE INDEX IF NOT EXISTS "prompt_message_chain" ON "prompt_message" ("conversation_id", "created", "id", "role", "source", "prompt_level");
CREATE INDEX IF NOT EXISTS "prompt_message_submission" ON "prompt_message" ("submission_id", "conversation_id", "created", "source") WHERE "submission_id" IS NOT NULL;
+-- 学生交作品时写的一句反思(最满意的改动和理由);可以不写。删提交时跟着删
+CREATE TABLE IF NOT EXISTS "submission_reflection" ("submission_id" char(32) NOT NULL PRIMARY KEY REFERENCES "submission_submission" ("id") ON DELETE CASCADE, "content" text NOT NULL, "modified" datetime NOT NULL);
+
-- 登录用 JWT:access token 不落库,refresh token 只存 sha256
DROP TABLE IF EXISTS "auth_session";
CREATE TABLE IF NOT EXISTS "auth_refresh_token" ("token_hash" text NOT NULL PRIMARY KEY, "user_id" bigint NOT NULL REFERENCES "account_user" ("id") ON DELETE CASCADE, "expire_at" datetime NOT NULL);
diff --git a/server/src/routes/submission.ts b/server/src/routes/submission.ts
index a55f91c..159ab12 100644
--- a/server/src/routes/submission.ts
+++ b/server/src/routes/submission.ts
@@ -120,6 +120,7 @@ router.post("/", async (c) => {
js: z.string().nullable().optional(),
prompt: z.string().nullable().optional(),
message_id: z.number().int().nullable().optional(), // 关联的 assistant message
+ reflection: z.string().max(1000).nullable().optional(), // 交的时候写的一句反思,可以不写
}),
)
const task = orNotFound(
@@ -131,6 +132,8 @@ router.post("/", async (c) => {
const code = { html: p.html ?? null, css: p.css ?? null, js: p.js ?? null }
+ const reflection = p.reflection?.trim() || null
+
const { submissionId, newUserMsgId } = tx(() => {
let manualAsstMsgId: number | null = null
let linkedMsg: MessageRow | null = null
@@ -203,6 +206,15 @@ router.post("/", async (c) => {
return { submissionId: id, newUserMsgId }
})
+ // 同一版再交时没写就留着上次写的
+ if (reflection) {
+ run(
+ `INSERT INTO submission_reflection (submission_id, content, modified) VALUES ($id, $content, $ts)
+ ON CONFLICT (submission_id) DO UPDATE SET content = excluded.content, modified = excluded.modified`,
+ { id: submissionId, content: reflection, ts: now() },
+ )
+ }
+
// 只对新增的提示词做层级分类
if (
newUserMsgId !== null &&
@@ -540,7 +552,13 @@ router.get("/:id", (c) => {
const id = uuidParam(c, "id")
requireView(user, id)
const s = orNotFound(get(`${SUBMISSION_SELECT} ${SUBMISSION_FROM} WHERE s.id = $id`, { me: user.id, id }))
- const out = submissionOut(s)
+ // 反思只给老师和作者本人看
+ const reflection =
+ isStaff(user) || s.user_id === user.id
+ ? (get<{ content: string }>(`SELECT content FROM submission_reflection WHERE submission_id = $id`, { id })
+ ?.content ?? null)
+ : null
+ const out = { ...submissionOut(s), reflection }
// 和同学作品一样:学生给同学打分之前不告诉作者和得分(作品展里的本来就署名)
if (!isStaff(user) && s.user_id !== user.id && !out.my_score && !inShowcase(id)) {
return c.json({ ...out, userid: null, username: null, score: null })
diff --git a/server/tests/api.test.ts b/server/tests/api.test.ts
index a76abeb..442df1e 100644
--- a/server/tests/api.test.ts
+++ b/server/tests/api.test.ts
@@ -276,6 +276,34 @@ describe("提交与删除", () => {
expect(all(`SELECT COUNT(*) AS n FROM submission_submission WHERE id != $id`, { id: otherSub })).toEqual([{ n: 1 }])
})
+ test("交的时候写的反思:再交没写就保留,只有老师和作者能看,删提交时一起删", async () => {
+ makeUser("stu")
+ makeUser("mate")
+ makeUser("teacher", "admin")
+ const task = makeTask("T")
+ const stu = await client("stu")
+ const edit = await stu("POST", "/api/prompt/versions", { task_id: task, html: "1", css: "", js: "" })
+ const sub = await stu("POST", "/api/submission/", {
+ task_id: task,
+ html: "
1",
+ message_id: edit.json.message_id,
+ reflection: " 按钮改成三个,因为五个太乱 ",
+ })
+ const id = sub.json.id
+ await stu("POST", "/api/submission/", { task_id: task, html: "
1!", message_id: edit.json.message_id, reflection: "" })
+ expect((await stu("GET", `/api/submission/${id}`)).json.reflection).toBe("按钮改成三个,因为五个太乱")
+ expect((await (await client("teacher"))("GET", `/api/submission/${id}`)).json.reflection).toBe("按钮改成三个,因为五个太乱")
+
+ const mate = await client("mate")
+ await mate("POST", "/api/submission/", { task_id: task, html: "
m" })
+ const peer = await mate("GET", `/api/submission/${id}`)
+ expect(peer.status).toBe(200)
+ expect(peer.json.reflection).toBeNull()
+
+ expect((await stu("DELETE", `/api/submission/${id}`)).status).toBe(200)
+ expect(all(`SELECT * FROM submission_reflection`)).toEqual([])
+ })
+
test("被评分的提交只有超管能删", async () => {
const author = makeUser("author")
const other = makeUser("other")