const fileSystem = require("fs"); const router = require("express").Router(); const multer = require("multer"); const Jimp = require("jimp"); const dataRender = require("../models/DataRender"); const NewTheme = require("../models/mongooseSchemas/NewTheme"); /** * 頁面「投稿主題」的路由處理 */ router.get("/newtheme", (req, res) => { let user = req.user; // 如果使用者有登入 if (user) { // 根據使用者是否已經有投稿過新主題,來決定要給出訊息頁面或「主題投稿」頁面 let source = user.hasPostNewTheme ? "message_form" : "submit_theme"; dataRender.DataRender(source, req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render(source, dataObj); } }); } // 若使用者沒有登入,則重新導向到登入頁面 else { res.redirect("/login"); } }); /** * 透過Multer建立一個「上傳者」物件,將圖片暫存在專案目錄下的"temp"資料夾中。 * 最大檔案上傳量限制為1;檔案大小限制為128KB。 */ const Uploader = multer({ dest: "./temp", limits: { fileSize: 131072, files: 1 } }); /** * 檢查由客戶端傳送過來的評分資料。經檢查後無錯誤則繼續至下一個程序;若有錯誤則回送錯誤訊息。 * @param {Express.Request} req Express的Request物件。 * @param {Express.Response} res Express的Response物件。 * @param {Function} next 導向函式。 */ function NewTheme_CheckTextField(req, res, next) { // 確認使用者是否有登入。若沒有登入,則回送錯誤訊息 if (!req.user) { return res.json({isOK: false, field: "SERVER", message: "尚未登入。請登入後再執行投稿主題的動作。"}); } // 確認使用者是否已有發起過主題。若有發起過主題,則回送錯誤訊息 if (req.user.hasPostFeedback) { return res.json({isOK: false, field: "SERVER", message: "您已經投稿過主題,請在下一季時再進行投稿動作。"}); } // 檢查「主題名稱」 req.checkBody("theme") .notEmpty() .withMessage("「主題名稱」為必要填寫的欄位。請輸入您想要的主題名稱。") .isLength({max: 32, min: 1}) .withMessage("請在欄位「主題名稱」中輸入1~32字間的主題名稱。") .matches(/^[^.<>/\\]+$/) .withMessage("欄位「主題名稱」中請勿包含「.<>/\\」非法字元。"); // 檢查「敘述」 req.checkBody("narrative") .notEmpty() .withMessage("「敘述」為必要填寫的欄位。請輸入對於此主題的相關說明。") .isLength({max: 100, min: 8}) .withMessage("請在欄位「敘述」中輸入8~100字間的主題敘述。"); // 取得驗證結果 req.getValidationResult().then((result) => { let errors = result.mapped(); if (result.isEmpty()) { next(); } else { let firstErr = Object.values(errors)[0]; res.json({isOK: false, field: firstErr.param, message: firstErr.msg}); } }); } /** * 檢查由客戶端傳送過來的評分資料。經檢查後無錯誤則繼續至下一個程序;若有錯誤則回送錯誤訊息。 * @param {Express.Request} req Express的Request物件。 * @param {Express.Response} res Express的Response物件。 * @param {Function} next 導向函式。 */ function NewTheme_CheckImage(req, res, next) { let file = req.file; // 若使用者沒有上傳圖片,則回送錯誤訊息 if (!file) { return res.json({ isOK: false, message: "請選擇小於等於128KB,且類型為png或jpeg的正方形圖檔。" }); } // 若使用者上傳的圖片檔案格式不為png或jpeg,則回送錯誤訊息。 else if (file.mimetype != "image/png" && file.mimetype != "image/jpeg") { fileSystem.unlink(file.path, (err) => { if (err) console.log(err); }); return res.json({ isOK: false, message: "選擇的活動圖示之檔案格式必須為png或jpeg。" }); } // 讀取圖片,檢視其圖片是否為 1:1 比例 Jimp.read(file.path, (err, img) => { if (err) return res.json({ isOK: false, message: "伺服器內部錯誤,請稍後再嘗試。" }); // 若為正方形圖示,則繼續下一個程序 if (img.bitmap.height == img.bitmap.width) { next(); } // 若不是,則回送錯誤訊息 else { res.json({ isOK: false, field: "image", message: "選擇的活動圖示其大小比例必須為1:1。" }) } }); } /** * 儲存新的活動資料並將成功訊息回送。 * @param {Express.Request} req Express的Request物件。 * @param {Express.Response} res Express的Response物件。 */ function NewTheme_SaveDataAndResponse(req, res) { let body = req.body; // 資料主體 let file = req.file; // 檔案訊息資料 let newFileName = body.theme + (file.mimetype == "image/png" ? ".png" : ".jpg"); // 新檔案的檔案名稱 let outURLPath = "/images/newtheme/" + newFileName; // 對外的檔案路徑表示 let newFilePath = "./public" + outURLPath; // 新檔案的存擋路徑 // 建立基本資料 let data = { title: body.theme, narrative: body.narrative, image: outURLPath, sponsor: req.user.username }; // 以基本資料建立新的主題 NewTheme.createNew_NewTheme(data, (err, _id) => { // 若錯誤為「已有相同的主題名稱」,則回呼錯誤 if (NewTheme.IsError_HaveSameThemeTitle(err)) return res.json({isOK: false, field: "theme", message: "已經有其他使用者發起過相同的主題名稱。請換一個新的主題名稱。"}); // 若為伺服器內部錯誤,則將通用訊息回呼 if (err) return res.json({isOK: false, field: "SERVER", message: "伺服器內部錯誤,請稍後再嘗試。"}); // 更新檔案名稱,並轉存至目標資料夾中 fileSystem.copyFile(req.file.path, newFilePath, (err) => { if (err) return res.json({isOK: false, field: "SERVER", message: "伺服器內部錯誤,請稍後再嘗試。"}); // 刪除在暫存區的圖片檔案 fileSystem.unlink(req.file.path, (err) => { if (err) console.log(err); }); req.user.hasPostNewTheme = true; // 將「是否有投稿過新主題」設為true。 req.user.save((err) => { if (err) console.log(err); }); // 並將此使用者資料進行儲存 req.session.newThemeSuccess = true; // 標記newThemeSuccess,給轉跳頁面判斷所用 res.json({isOK: true, url: "/newtheme/successful"}); }); }); } /** * 當使用者將新的主題資料送至伺服端時的處理。 */ router.post("/newtheme", Uploader.single("image"), NewTheme_CheckTextField, NewTheme_CheckImage, NewTheme_SaveDataAndResponse); /** * 當使用者成功投稿了新主題後的轉跳頁面。 */ router.get("/newtheme/successful", (req, res) => { // 確認是否有登入,且有被標記newThemeSuccess。則傳送轉跳頁面訊息。 if (req.user && req.session.newThemeSuccess) { delete req.session.newThemeSuccess; // 將標記給去除掉 dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain; charset=utf-8"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("message_form", dataObj); } }); } else { res.redirect("/"); } }); module.exports = router;