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;