From 9d51b121a0ad50d33d3bbde09f70311c6f989a35 Mon Sep 17 00:00:00 2001 From: final_deterrence Date: Mon, 27 Apr 2026 21:41:24 +0800 Subject: [PATCH 1/4] fixxing sast-weekly --- src/routes/weekly.ts | 59 +++++++++++++++++++++++++++++--------------- 1 file changed, 39 insertions(+), 20 deletions(-) diff --git a/src/routes/weekly.ts b/src/routes/weekly.ts index ddfd8056..b5e60675 100644 --- a/src/routes/weekly.ts +++ b/src/routes/weekly.ts @@ -17,33 +17,45 @@ import { Agent } from "https"; const router = express.Router(); const weixinSpider = async (headers: any, params: any, filename: string) => { const url = "https://mp.weixin.qq.com/cgi-bin/appmsg"; - let fcontrol: boolean = false; + const fcontrol: boolean = false; const base_directory = await utils.get_base_directory(); try { console.log("Spider Start"); const new_weekly_list: any[] = []; let i: number = 0; - const newest_weekly_date = await get_newest_weekly(); - let newest_weekly_id = await get_newest_weekly_id(); + // const newest_weekly_date = await get_newest_weekly(); + let newest_weekly_id = (await get_newest_weekly_id()) || 0; outerloop: while (!fcontrol) { params["begin"] = (i * 5).toString(); i++; await new Promise((resolve) => - setTimeout(resolve, Math.random() * 9000 + 1000), - ); // 等待 1 到 10 秒之间的随机时间 + setTimeout(resolve, Math.random() * 20000 + 15000), + ); // 等待 15 到 35 秒之间的随机时间 const response = await axios.get(url, { headers, params, httpsAgent: new Agent({ rejectUnauthorized: false }), }); const data = response.data; - if (data.base_resp.ret === 200013) { - console.log(`Frequency control, stop at ${params["begin"]}`); - fcontrol = true; - break; + if (data.base_resp && data.base_resp.ret === 200013) { + // console.log(`Frequency control, stop at ${params["begin"]}`); + // fcontrol = true; + // break; + console.error( + `触发微信反爬风控策略 (ret: 200013),于 begin=${params["begin"]} 熔断`, + ); + // 不再是简单的 fcontrol = true,而是直接抛出错误让下面 catch 捕获,写入 failed 标记 + throw new Error("Frequency control triggered by WeChat (200013)"); + } + if (!data.app_msg_list || !Array.isArray(data.app_msg_list)) { + console.error( + "响应缺少 app_msg_list,Cookie/Token可能已过期、权限不足或被滑块验证拦截:", + data, + ); + throw new Error("Invalid response or Token expired."); } - if (!data.app_msg_list || data.app_msg_list.length === 0) { - console.log("All article parsed"); + if (data.app_msg_list.length === 0) { + console.log("当前页文章列表为空,判断所有文章解析完毕。"); break; } console.log( @@ -53,8 +65,10 @@ const weixinSpider = async (headers: any, params: any, filename: string) => { new Date(data.app_msg_list[0].create_time * 1000).toLocaleString(), ); for (const item of data.app_msg_list) { - if (new Date(item.create_time * 1000) <= newest_weekly_date) - break outerloop; + // [移除限制] 允许回补老文章,不再因为遇到旧时间点就停止整个爬虫, + // 而是全量扫描,完全依靠下面的 check_weekly_exist 判断去重。 + // if (new Date(item.create_time * 1000) <= newest_weekly_date) + // break outerloop; if (item.title.includes("SAST Weekly")) { const exist: boolean = await check_weekly_exist( new Date(item.create_time * 1000), @@ -66,7 +80,7 @@ const weixinSpider = async (headers: any, params: any, filename: string) => { const new_item: WeeklyPost = { title: item.title, url: item.link, - date: new Date(item.create_time * 1000), + date: new Date(item.create_time * 1000).toISOString().split("T")[0], id: newest_weekly_id + 1, }; new_weekly_list.push(new_item); @@ -135,7 +149,7 @@ router.post("/renew", authenticate(["counselor"]), async (req, res) => { begin: "1", count: "5", query: "", - fakeid: "MzA5MjA5NjIxNg%3D%3D", + fakeid: "MzA5MjA5NjIxNg==", type: "9", }; const filename = uuid.uuid(); @@ -257,16 +271,21 @@ router.get("/cover", async (req, res) => { const getTitle = async (url: string) => { try { - const response = await fetch(url, { method: "GET" }); + const response = await fetch(url, { + method: "GET", + headers: { + "User-Agent": + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36", + }, + }); if (response.ok) { const text: string = await response.text(); - const match = text.match(/meta property="og:title" content=".*"/); + const match = text.match(/meta property="og:title" content="(.*?)"/); if (match == null) throw Error("capture failed!"); - const title = match[0].slice(34, -1); - return title; + return match[1]; } else throw Error("fetch failed!"); } catch (err: any) { - return err; + throw err; } }; From 2cd114b0c5b320aa7c0653bf15db04b0c935c35a Mon Sep 17 00:00:00 2001 From: final_deterrence Date: Mon, 27 Apr 2026 22:13:44 +0800 Subject: [PATCH 2/4] merge and yarn install --- src/hasura/share.ts | 2 +- src/routes/weekly.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/hasura/share.ts b/src/hasura/share.ts index aa879e41..122843d0 100644 --- a/src/hasura/share.ts +++ b/src/hasura/share.ts @@ -5,7 +5,7 @@ type WeeklyPost = { id: number; title: string; url: string; - date: Date; + date: string; }; export { WeeklyPost }; /** diff --git a/src/routes/weekly.ts b/src/routes/weekly.ts index b5e60675..2b2f3013 100644 --- a/src/routes/weekly.ts +++ b/src/routes/weekly.ts @@ -6,7 +6,7 @@ import * as utils from "../helpers/utils"; import * as fs from "fs/promises"; import * as uuid from "../helpers/uuid"; import { - get_newest_weekly, + // get_newest_weekly, get_newest_weekly_id, add_weekly_list, WeeklyPost, From 51fced5a01a44a29c8605373735f9efa94224d83 Mon Sep 17 00:00:00 2001 From: final_deterrence Date: Mon, 18 May 2026 21:08:50 +0800 Subject: [PATCH 3/4] sast stage change --- src/hasura/share.ts | 2 +- src/routes/weekly.ts | 79 ++++++++++++++++++++++++++++++++++++-------- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/src/hasura/share.ts b/src/hasura/share.ts index 122843d0..4f943946 100644 --- a/src/hasura/share.ts +++ b/src/hasura/share.ts @@ -724,7 +724,7 @@ export const get_newest_weekly = async (): Promise => { return new Date(date); }; -export const check_weekly_exist = async (date: Date): Promise => { +export const check_weekly_exist = async (date: string): Promise => { const check_weekly_exist_query: any = await client.request( gql` query MyQuery($targetDate: date!) { diff --git a/src/routes/weekly.ts b/src/routes/weekly.ts index 2b2f3013..75a42190 100644 --- a/src/routes/weekly.ts +++ b/src/routes/weekly.ts @@ -15,16 +15,26 @@ import { import authenticate from "../middlewares/authenticate"; import { Agent } from "https"; const router = express.Router(); -const weixinSpider = async (headers: any, params: any, filename: string) => { +const weixinSpider = async ( + headers: any, + params: any, + filename: string, + full_scan: boolean = false, + start_i: number = 0, +) => { const url = "https://mp.weixin.qq.com/cgi-bin/appmsg"; const fcontrol: boolean = false; const base_directory = await utils.get_base_directory(); try { - console.log("Spider Start"); + console.log(`Spider Start (full_scan: ${full_scan}, start_i: ${start_i})`); const new_weekly_list: any[] = []; - let i: number = 0; + let i: number = start_i; // const newest_weekly_date = await get_newest_weekly(); let newest_weekly_id = (await get_newest_weekly_id()) || 0; + + // 初始化一个计数器来统计连续命中数据库存在的文章数量 + let consecutiveExistCount = 0; + outerloop: while (!fcontrol) { params["begin"] = (i * 5).toString(); i++; @@ -71,12 +81,27 @@ const weixinSpider = async (headers: any, params: any, filename: string) => { // break outerloop; if (item.title.includes("SAST Weekly")) { const exist: boolean = await check_weekly_exist( - new Date(item.create_time * 1000), + new Date(item.create_time * 1000).toISOString().split("T")[0], ); if (exist) { + if (!full_scan) { + consecutiveExistCount++; + // 如果连续碰到 3 篇在数据库里已经存在的 Weekly 就可以放心退出了 (说明我们确实已经把残缺的洞补上了,和旧数据接轨了) + if (consecutiveExistCount >= 3) { + console.log( + "连续遇到3篇已存在的推文,判断增量更新完成,提前结束避免封禁。", + ); + break outerloop; + } + } else { + console.log(`[全量扫描模式] 已存在推文,仅跳过: ${item.title}`); + } continue; } + + // 一旦遇到“不存在”的需要插入的文章,说明前面的洞还没填完或遇到了新文章,把计数器清零 + consecutiveExistCount = 0; const new_item: WeeklyPost = { title: item.title, url: item.link, @@ -96,16 +121,29 @@ const weixinSpider = async (headers: any, params: any, filename: string) => { if ( !(await utils.checkPathExists(`${base_directory}/weixinSpiderStatus`)) ) { - await fs.mkdir(`${base_directory}/weixinSpiderStatus`); + await fs.mkdir(`${base_directory}/weixinSpiderStatus`, { + recursive: true, + }); } await fs.writeFile(`${base_directory}/weixinSpiderStatus/${filename}`, ""); console.log("Spider finished"); } catch (error) { console.error("Error fetching articles:", error); - await fs.writeFile( - `${base_directory}/weixinSpiderStatus/${filename}-failed`, - "", - ); + try { + if ( + !(await utils.checkPathExists(`${base_directory}/weixinSpiderStatus`)) + ) { + await fs.mkdir(`${base_directory}/weixinSpiderStatus`, { + recursive: true, + }); + } + await fs.writeFile( + `${base_directory}/weixinSpiderStatus/${filename}-failed`, + "", + ); + } catch (fsError) { + console.error("Error writing failure status:", fsError); + } } }; @@ -153,7 +191,9 @@ router.post("/renew", authenticate(["counselor"]), async (req, res) => { type: "9", }; const filename = uuid.uuid(); - weixinSpider(headers, params, filename); + const full_scan = req.body.full_scan === true; + const start_i = req.body.start_i || 0; + weixinSpider(headers, params, filename, full_scan, start_i); return res.status(200).json({ filename: filename }); } catch (err) { return res.status(500).send("500 Internal Server Error: " + err); @@ -322,15 +362,28 @@ router.post("/insert", authenticate(["counselor"]), async (req, res) => { } await client.request( gql` - mutation Insert_Weekly_One($id: Int, $title: String, $url: String) { - insert_weekly_one(object: { id: $id, title: $title, url: $url }) { + mutation Insert_Weekly_One( + $id: Int + $title: String + $url: String + $date: date + ) { + insert_weekly_one( + object: { id: $id, title: $title, url: $url, date: $date } + ) { id title url + date } } `, - { id: req.body.id + 1, title: title, url: req.body.url }, + { + id: req.body.id + 1, + title: title, + url: req.body.url, + date: req.body.date, + }, ); return res.status(200).send("ok"); } catch (err) { From f071fa65609ed3f59c70b8cbf7f238a60c339038 Mon Sep 17 00:00:00 2001 From: final_deterrence Date: Tue, 8 Sep 2026 00:56:38 +0800 Subject: [PATCH 4/4] honor-new type --- src/hasura/honor.ts | 137 ++++++---------- src/helpers/cos.ts | 337 ++++++++++++++++++++++---------------- src/routes/application.ts | 40 ++--- src/routes/static.ts | 33 ++++ 4 files changed, 294 insertions(+), 253 deletions(-) diff --git a/src/hasura/honor.ts b/src/hasura/honor.ts index 2c74fddb..ee705bf7 100644 --- a/src/hasura/honor.ts +++ b/src/hasura/honor.ts @@ -10,10 +10,10 @@ export const query_user_role = async (uuid: string) => { } } `, - { uuid: uuid } + { uuid: uuid }, ); return query.users_by_pk?.role ?? "anonymous"; -} +}; export const query_honor_application = async (id: string) => { const query: any = await client.request( @@ -27,122 +27,84 @@ export const query_honor_application = async (id: string) => { attachment_url year status + transcript_url } } `, - { id: id } + { id: id }, ); return query.honor_application_by_pk ?? null; -} +}; export const insert_honor_application = async ( student_uuid: string, honor: string, statement: string, attachment_url: string | undefined, - year: number + transcript_url: string | undefined, + year: number, ) => { + const object: Record = { + student_uuid: student_uuid, + honor: honor, + statement: statement, + year: year, + }; + if (attachment_url !== undefined) { + object.attachment_url = attachment_url; + } + if (transcript_url !== undefined) { + object.transcript_url = transcript_url; + } + const query: any = await client.request( gql` mutation InsertHonorApplication( - $student_uuid: uuid! - $honor: String! - $statement: String! - $attachment_url: String - $year: Int! + $object: honor_application_insert_input! ) { - insert_honor_application_one( - object: { - student_uuid: $student_uuid - honor: $honor - statement: $statement - attachment_url: $attachment_url - year: $year - } - ) { + insert_honor_application_one(object: $object) { id } } `, - { - student_uuid: student_uuid, - honor: honor, - statement: statement, - attachment_url: attachment_url, - year: year - } + { object: object }, ); return query.insert_honor_application_one?.id ?? null; -} - -export const update_honor_application_with_attachment = async ( - id: string, - honor: string, - statement: string, - attachment_url: string, -) => { - const query: any = await client.request( - gql` - mutation UpdateMentorApplication( - $id: uuid! - $honor: String! - $statement: String! - $attachment_url: String! - ) { - update_honor_application_by_pk( - pk_columns: {id: $id} - _set: { - honor: $honor - statement: $statement - attachment_url: $attachment_url - } - ) { - id - } - } - `, - { - id: id, - honor: honor, - statement: statement, - attachment_url: attachment_url - } - ); - return query.update_honor_application_by_pk?.id ?? null; -} - +}; export const update_honor_application = async ( id: string, honor: string, statement: string, + attachment_url?: string, + transcript_url?: string, ) => { + const set: Record = { + honor: honor, + statement: statement, + }; + if (attachment_url !== undefined) { + set.attachment_url = attachment_url; + } + if (transcript_url !== undefined) { + set.transcript_url = transcript_url; + } + const query: any = await client.request( gql` - mutation UpdateMentorApplication( + mutation UpdateHonorApplication( $id: uuid! - $honor: String! - $statement: String! + $set: honor_application_set_input! ) { - update_honor_application_by_pk( - pk_columns: {id: $id} - _set: { - honor: $honor - statement: $statement - } - ) { + update_honor_application_by_pk(pk_columns: { id: $id }, _set: $set) { id } } `, - { - id: id, - honor: honor, - statement: statement, - } + { id: id, set: set }, ); return query.update_honor_application_by_pk?.id ?? null; -} +}; export const delete_honor_application = async (id: string) => { const query: any = await client.request( @@ -153,24 +115,27 @@ export const delete_honor_application = async (id: string) => { } } `, - { id: id } + { id: id }, ); return query.delete_honor_application_by_pk?.id ?? null; -} +}; -export const update_honor_application_status = async (id: string, status: string) => { +export const update_honor_application_status = async ( + id: string, + status: string, +) => { const query: any = await client.request( gql` mutation UpdateMentorApplicationStatus($id: uuid!, $status: String!) { update_honor_application_by_pk( - pk_columns: {id: $id} + pk_columns: { id: $id } _set: { status: $status } ) { id } } `, - { id: id, status: status } + { id: id, status: status }, ); return query.update_honor_application_by_pk?.id ?? null; -} +}; diff --git a/src/helpers/cos.ts b/src/helpers/cos.ts index 156dfbc9..3b13101e 100644 --- a/src/helpers/cos.ts +++ b/src/helpers/cos.ts @@ -1,202 +1,249 @@ import STS from "qcloud-cos-sts"; import COS from "cos-nodejs-sdk-v5"; -import fStream from 'fs'; +import fStream from "fs"; // 获取临时密钥 -export const getSTS: any = async (action: string[], prefix: string) => { - // 配置参数 - const config = { - secretId: process.env.GROUP_SECRET_ID!, // 固定密钥 - secretKey: process.env.GROUP_SECRET_KEY!, // 固定密钥 - proxy: '', - host: 'sts.tencentcloudapi.com', - durationSeconds: 1800, // 密钥有效期 - bucket: process.env.COS_BUCKET!, // 换成你的 bucket - region: 'ap-beijing', // 换成 bucket 所在地区 - }; - const scope = [{ - action: action, - bucket: config.bucket, - region: config.region, - prefix: prefix, - }]; - const policy = STS.getPolicy(scope); - return new Promise((resolve, reject) => STS.getCredential({ +export const getSTS: any = async ( + action: string[], + prefix: string, + durationSeconds = 1800, +) => { + // 配置参数 + const config = { + secretId: process.env.GROUP_SECRET_ID!, // 固定密钥 + secretKey: process.env.GROUP_SECRET_KEY!, // 固定密钥 + proxy: "", + host: "sts.tencentcloudapi.com", + durationSeconds: Math.min(durationSeconds, 7200), // 密钥有效期 + bucket: process.env.COS_BUCKET!, // 换成你的 bucket + region: "ap-beijing", // 换成 bucket 所在地区 + }; + const scope = [ + { + action: action, + bucket: config.bucket, + region: config.region, + prefix: prefix, + }, + ]; + const policy = STS.getPolicy(scope); + return new Promise((resolve, reject) => + STS.getCredential( + { secretId: config.secretId, secretKey: config.secretKey, proxy: config.proxy, policy: policy, durationSeconds: config.durationSeconds, - }, (err, credential) => { + }, + (err, credential) => { if (err) reject(err); else resolve(credential); - })) + }, + ), + ); }; - export async function initCOS() { - const sts = await getSTS([ + const sts = await getSTS( + [ "name/cos:GetObject", "name/cos:DeleteObject", "name/cos:HeadObject", "name/cos:PutObject", - "name/cos:GetBucket" - ], "*"); - - const cos = new COS({ - getAuthorization: async (options, callback) => { - try { - if (!sts) throw (Error("Credentials invalid!")); - callback({ - TmpSecretId: sts.credentials.tmpSecretId, - TmpSecretKey: sts.credentials.tmpSecretKey, - SecurityToken: sts.credentials.sessionToken, - StartTime: sts.startTime, - ExpiredTime: sts.expiredTime, - }); - } catch (err) { - console.log(err); - } + "name/cos:GetBucket", + ], + "*", + ); + + const cos = new COS({ + getAuthorization: async (options, callback) => { + try { + if (!sts) throw Error("Credentials invalid!"); + callback({ + TmpSecretId: sts.credentials.tmpSecretId, + TmpSecretKey: sts.credentials.tmpSecretKey, + SecurityToken: sts.credentials.sessionToken, + StartTime: sts.startTime, + ExpiredTime: sts.expiredTime, + }); + } catch (err) { + console.log(err); } - }); - - return cos; - } - - export async function getConfig() { - const config = { - bucket: process.env.COS_BUCKET!, - region: 'ap-beijing', - }; - return config; - } + }, + }); + return cos; +} - export async function downloadObject(key: string, outputPath: string, cos: COS, config: any): Promise { - return new Promise((resolve, reject) => { - cos.headObject({ +export async function getConfig() { + const config = { + bucket: process.env.COS_BUCKET!, + region: "ap-beijing", + }; + return config; +} + +export async function downloadObject( + key: string, + outputPath: string, + cos: COS, + config: any, +): Promise { + return new Promise((resolve, reject) => { + cos.headObject( + { Bucket: config.bucket, Region: config.region, Key: key, - }, (err, data) => { - if (data) { - cos.getObject({ + }, + (err, data) => { + if (data) { + cos.getObject( + { Bucket: config.bucket, Region: config.region, Key: key, Output: fStream.createWriteStream(outputPath), - }, (err) => { + }, + (err) => { if (err) { reject(err); } else { resolve(true); } - }); - } else { - reject(`key: ${key} Not found.`); - } - }); - }); - }; - - - export async function uploadObject(localFilePath: string, bucketKey: string, cos: COS, config: any): Promise { - return new Promise((resolve, reject) => { - const fileStream = fStream.createReadStream(localFilePath); - fileStream.on('error', (err) => { - console.log('File Stream Error', err); - reject('Failed to read local file'); - }); - cos.putObject({ + }, + ); + } else { + reject(`key: ${key} Not found.`); + } + }, + ); + }); +} + +export async function uploadObject( + localFilePath: string, + bucketKey: string, + cos: COS, + config: any, +): Promise { + return new Promise((resolve, reject) => { + const fileStream = fStream.createReadStream(localFilePath); + fileStream.on("error", (err) => { + console.log("File Stream Error", err); + reject("Failed to read local file"); + }); + cos.putObject( + { Bucket: config.bucket, Region: config.region, Key: bucketKey, Body: fileStream, - }, (err, data) => { + }, + (err, data) => { if (err) { console.log(err); - reject('Failed to upload object to COS'); + reject("Failed to upload object to COS"); } else { if (data) { - console.debug('Upload Success'); + console.debug("Upload Success"); } // console.debug('Upload Success', data); resolve(true); } - }); - }); - }; - - - export async function deleteObject(key: string, cos: COS, config: any): Promise { - return new Promise((resolve, reject) => { - cos.deleteObject({ + }, + ); + }); +} + +export async function deleteObject( + key: string, + cos: COS, + config: any, +): Promise { + return new Promise((resolve, reject) => { + cos.deleteObject( + { Bucket: config.bucket, Region: config.region, Key: key, - }, (err, data) => { + }, + (err, data) => { if (err) { console.log(err); - reject('Failed to delete object from COS'); + reject("Failed to delete object from COS"); } else { - console.debug('Delete Success', data); + console.debug("Delete Success", data); resolve(true); } - }); - }); + }, + ); + }); +} + +export async function deleteFolder( + folderPrefix: string, + cos: COS, + config: any, +): Promise { + try { + const listParams = { + Bucket: config.bucket, + Region: config.region, + Prefix: folderPrefix, + }; + const data = await cos.getBucket(listParams); + const objects = data.Contents || []; + + const deletePromises = objects.map((obj) => + deleteObject(obj.Key, cos, config), + ); + await Promise.all(deletePromises); + + return true; + } catch (err) { + console.error("Failed to delete folder from COS:", err); + return false; } - - export async function deleteFolder(folderPrefix: string, cos: COS, config: any): Promise { - try { - const listParams = { +} + +export const listFile = ( + prefix: string, + cos: COS, + config: any, +): Promise => { + return new Promise((resolve, reject) => { + cos.getBucket( + { Bucket: config.bucket, Region: config.region, - Prefix: folderPrefix, - }; - const data = await cos.getBucket(listParams); - const objects = data.Contents || []; - - const deletePromises = objects.map(obj => - deleteObject(obj.Key, cos, config) - ); - await Promise.all(deletePromises); - - return true; - } catch (err) { - console.error("Failed to delete folder from COS:", err); - return false; - } - } - - export const listFile = (prefix: string, cos: COS, config: any): Promise => { - return new Promise((resolve, reject) => { - cos.getBucket( - { - Bucket: config.bucket, - Region: config.region, - Prefix: prefix, - }, - (err, data) => { - if (err || !data) return reject(err); - return resolve(data.Contents); - }, - ); - }); - }; - + Prefix: prefix, + }, + (err, data) => { + if (err || !data) return reject(err); + return resolve(data.Contents); + }, + ); + }); +}; - export const getAvatarUrl = (key: string, cos: COS, config: any): Promise => { - return new Promise((resolve, reject) => { - cos.getObjectUrl( - { - Bucket: config.bucket, - Region: config.region, - Key: key, - }, - (err, data) => { - if (err) return reject(err); - resolve(data.Url); - }, - ); - }); - }; +export const getAvatarUrl = ( + key: string, + cos: COS, + config: any, +): Promise => { + return new Promise((resolve, reject) => { + cos.getObjectUrl( + { + Bucket: config.bucket, + Region: config.region, + Key: key, + }, + (err, data) => { + if (err) return reject(err); + resolve(data.Url); + }, + ); + }); +}; diff --git a/src/routes/application.ts b/src/routes/application.ts index 533f0c59..d7ae864f 100644 --- a/src/routes/application.ts +++ b/src/routes/application.ts @@ -202,7 +202,10 @@ router.post("/honor/insert_one", async (req, res) => { const student_uuid: string = req.body.student_uuid; const honor: string = req.body.honor; const statement: string = req.body.statement ?? ""; - const attachment_url: string = req.body.attachment_url ?? undefined; + const attachment_url: string | undefined = + req.body.attachment_url || undefined; + const transcript_url: string | undefined = + req.body.transcript_url || undefined; if (!student_uuid || !honor) { return res.status(450).send("Error: Missing student_uuid or honor"); @@ -219,6 +222,7 @@ router.post("/honor/insert_one", async (req, res) => { honor, statement, attachment_url, + transcript_url, year, ); if (!insert_id) { @@ -236,7 +240,10 @@ router.post("/honor/update_one", async (req, res) => { const id: string = req.body.id; const honor: string = req.body.honor; const statement: string = req.body.statement ?? ""; - const attachment_url: string = req.body.attachment_url ?? undefined; + const attachment_url: string | undefined = + req.body.attachment_url || undefined; + const transcript_url: string | undefined = + req.body.transcript_url || undefined; const student_uuid: string = req.body.student_uuid; if (!id || !honor || !student_uuid) { @@ -259,26 +266,15 @@ router.post("/honor/update_one", async (req, res) => { return res.status(453).send("Error: Invalid year"); } - if (!attachment_url) { - const response = await HnrHasFunc.update_honor_application( - id, - honor, - statement, - ); - if (!response) { - return res.status(454).send("Error: Update honor application failed"); - } - } else { - const response = - await HnrHasFunc.update_honor_application_with_attachment( - id, - honor, - statement, - attachment_url, - ); - if (!response) { - return res.status(454).send("Error: Update honor application failed"); - } + const response = await HnrHasFunc.update_honor_application( + id, + honor, + statement, + attachment_url, + transcript_url, + ); + if (!response) { + return res.status(454).send("Error: Update honor application failed"); } return res.status(200).send(id); } catch (err) { diff --git a/src/routes/static.ts b/src/routes/static.ts index 6686ea1d..681f0446 100644 --- a/src/routes/static.ts +++ b/src/routes/static.ts @@ -65,6 +65,39 @@ router.get("/avatar/*", async (req, res) => { } }); +// honor application attachments +router.get( + "/honor_application/:student_uuid/:year/*", + authenticate(["student", "counselor"]), + async (req, res) => { + try { + const { student_uuid, year } = req.params; + const user = req.auth.user; + + if (!/^\d{4}$/.test(year)) { + return res.status(400).send("Invalid year"); + } + + if (user.role === "student" && user.uuid !== student_uuid) { + return res.status(401).send("当前用户没有权限访问该荣誉申请材料"); + } + + if (user.role === "student" || user.role === "counselor") { + const sts = await getSTS( + generalActions, + `honor_application/${student_uuid}/${year}/*`, + 7200, + ); + return res.status(200).send(sts); + } + + return res.status(401).send("当前用户没有权限访问该荣誉申请材料"); + } catch (err) { + return res.status(500).send(err); + } + }, +); + router.get("/chat_record/:user_uuid/member/:semester/*", async (req, res) => { try { const { user_uuid: target_uuid, semester } = req.params;