const User = require("../models/mongooseSchemas/User"); const router = require("express").Router(); const pug = require("pug"); const passport = require("passport"); const LocalStrategy = require('passport-local').Strategy; const multer = require("multer"); const dataRender = require("../models/DataRender"); // 序列化: 在第一次驗證之後,session皆不會保留驗證訊息,因此取得user.id passport.serializeUser(function(user, done) { done(null, user._id); }); // 反序列化: 用user.id來取得資料庫中的指定資料 passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); }); /** * 定義在「登入」頁面,使用者進行登入、傳送帳號密碼時所做的驗證策略。 */ passport.use("login" ,new LocalStrategy({ usernameField: "username", passwordField: "password" }, function (username, password, done) { User.AccountComparison(username, password, (err, user) => { if (err) return done(err); if (!user) return done(null, false, {message: "使用者名稱或密碼錯誤。"}); else return done(null, user); }); })); /** * 頁面「首頁」的路由處理。 */ router.use(require("./index")); /** * 頁面「傑作藝廊」的路由處理。 */ router.use(require("./gallery")); /** * 頁面「畫作主題」的路由處理。 */ router.use(require("./theme")); /** * 頁面「繪圖創作」的路由處理。 */ router.use(require("./drawing")); /** * 頁面「意見回饋」的路由處理。 */ router.use(require("./feedback")); /** * 註冊、登入與登出的相關頁面與處理。 */ router.use(require("./gate")); /** * 頁面「展示藝廊」的路由處理。 */ router.use(require("./showcase")); /** * 頁面「投稿主題」的路由處理。. */ router.use(require("./submit_theme")); /** * 頁面「」 */ router.use(require("./vote_theme")); /* =================================== */ /** * 頁面「個人主頁」的路由處理。 */ router.get("/home/:username", (req, res) => { // 若使用者有登入,則允許觀看其他會員的個人頁面 if (req.session.passport && req.session.passport.user) { dataRender.DataRender("personal_page", req.url, req.session, (err, dataObj) => { if (!err) { res.render("personal_page", dataObj); } else if (User.IsUserNotExist(err)) { res.redirect("/homenotexist/" + req.params["username"]); } else { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } }); } // 若沒有則轉跳到登入頁面 else { res.redirect("/login"); } }); /** * 在「個人頁面」要尋找指定使用者之下,發生找不到該使用者時所跳轉的訊息頁面。 */ router.get("/homenotexist/:username", (req, res) => { dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("message_form", dataObj); } }); }); /** * 頁面「編輯個人資料」的路由處理。 */ router.get("/edit_personal_info", (req, res) => { // 若使用者有登入,則將編輯頁面傳送至客戶端 if (req.session.passport && req.session.passport.user) { // 取得相對應的插值物件 dataRender.DataRender("edit_personal_info", req.url, req.session, (err, dataObj) => { // 若發生錯誤則傳送訊息。 if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } // 若無則將頁面傳送至客戶端 else { res.render("edit_personal_info", dataObj); } }); } // 若沒有則轉跳到首頁 else { res.redirect("/"); } }); /** * 檔案上傳協助物件,SavePersonalInfo_Upload。 * 上傳的圖片會暫存在"./temp"中。 */ const SPI_Upload = multer({ dest: "./temp", limits: { fileSize: 131072, files: 1 } }); /** * 在頁面「編輯個人資料」之下,按下「儲存更變」後所傳來的資料,由此路由來處理。 * 圖像檔案資訊存放於「req.files」中,文字類訊息存放於「req.body」中。 */ router.post("/save_personal_info", SPI_Upload.single("photo"), (req, res) => { // 若沒有登入,則跳轉至首頁 if (!req.session.passport || !req.session.passport.user) return res.redirect("/"); res.setHeader("Content-Type", "application/json"); let theFile = req.file; // 若有登入,則在做檢查動作。 // 若使用者有傳送檔案 且 檔案類別不為"image/jpeg"與"image/png"的話,則回送錯誤訊息。 if (theFile && theFile.mimetype != "image/jpeg" && theFile.mimetype != "image/png") { res.end({"isOK": false, "field": "photo", "message": "類型錯誤: 上傳的檔案類型請選擇jpg或png圖檔。"}); return; } // 檢查「姓」 req.checkBody("lastName") .notEmpty() .withMessage("「姓」為必要輸入的欄位,請輸入您的大名。") .matches(/^[a-zA-Z\u2E80-\u2FDF\u3190-\u319F\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]{1,16}$/) .withMessage("「姓」欄位輸入錯誤,請輸入小於17字的英文或中文字。"); // 檢查「名」 req.checkBody("firstName") .notEmpty() .withMessage("「名」為必要輸入的欄位,請輸入您的大名。") .matches(/^([a-zA-Z\u2E80-\u2FDF\u3190-\u319F\u3400-\u4DBF\u4E00-\u9FFF\uF900-\uFAFF]{1,16})([\ ]*)([a-zA-Z]{0,16})$/) .withMessage("「名」欄位輸入錯誤,請輸入小於33字的英文或中文字。"); // 檢查「暱稱」 req.checkBody("nickname") .len({max: 16}) .withMessage("暱稱欄位的字數至多16字元。") .matches(/^([^<>\&"']{0,16})$/) .withMessage("在暱稱欄位中請勿輸入非法字元: <>&\"\'"); // 檢查「短言」 req.checkBody("motto") .len({max: 32}) .withMessage("短言欄位的字數至多32字元。") .matches(/^([^<>\&"']{0,32})$/) .withMessage("在短言欄位中請勿輸入非法字元: <>&\"\'"); // 取得檢查結果 req.getValidationResult().then((result) => { let errors = result.mapped(); // 將結果做成字典物件 let responseMsg = {isOK: result.isEmpty()}; // 宣告、初步定義回應物件 // 若檢查通過,則將資料更新至資料庫(交給資料庫Schema處理) if (responseMsg.isOK) { // 呼叫UpdatePersonalInfo,傳入使用者的_id、文字資料、檔案訊息與回呼函式 User.UpdatePersonalInfo(req.session.passport.user, req.body, theFile, (err, isOK) => { // 若有錯誤,則將錯誤設定至回應物件上,並在控制台上印出錯誤 if (err) { responseMsg.isOK = false; responseMsg.field = "SERVER"; responseMsg.message = (User.IsUserNotExist(err) ? "使用者資料對應錯誤。請重新登入再執行此操作。" : "伺服器內部錯誤,請稍後再嘗試。"); console.log(err); } // 若無錯誤,則標上 personalInfoUpdated 與轉跳頁面 else { req.session.personalInfoUpdated = true; responseMsg.redirect = "/personalinfo_updated"; } // 若無錯誤,則直接回呼 (不改變 responseMsg.isOK 的 true 值) res.send(responseMsg); }); } else { let firstErr = Object.values(errors)[0]; // 取得第一個錯誤訊息物件 responseMsg.field = firstErr.param; // 將錯誤的欄位名稱新增到回應物件的field屬性 responseMsg.message = firstErr.msg; // 將錯誤訊息新增到回應物件的message屬性 res.send(responseMsg); } }); }); /** * 在「編輯個人資料」頁面下,使用者上傳資料並成功後的跳轉訊息頁面。 */ router.get("/personalinfo_updated", (req, res) => { // 若有標記 personalInfoUpdated , if (req.session.personalInfoUpdated) { // 刪除標記,以免重複 delete req.session.personalInfoUpdated; // 取得插值資料(傳入URL,給內部判斷) dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => { // 若有錯誤,則發送錯誤訊息 if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("message_form", dataObj); } }); } // 若無標記,則直接跳轉到首頁 else { res.redirect("/"); } }); /** * 頁面「撰寫站內訊息」的路由處理 */ router.get(["/write_message", "/write_message/:username"], (req, res) => { if (req.session.passport && req.session.passport.user) { dataRender.DataRender("write_message", req.url, req.session, (err, dataObj) => { if (err) { console.log(err); res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("write_message", dataObj); } }); } else { res.redirect("/"); } }); /** * 取得由「撰寫站內訊息」頁面所傳來的訊息 */ router.post("/write_message", (req, res) => { res.setHeader("Content-Type", "application/json"); // 檢查使用者是否有登入。 若有登入,則近一步地做檢查動作 if (req.session.passport && req.session.passport.user) { // 檢查「收件者」 req.checkBody("recipient") .notEmpty() .withMessage("請選擇接收此站內信的目標使用者名稱。"); // 檢查「標題」 req.checkBody("subject") .notEmpty() .withMessage("請在「主旨」欄位上輸入1~32字間的主旨。") .matches(/^([^<>\&"'$]{1,32})$/) .withMessage("請勿在「主旨」中輸入有包含「<>&\"'$」有關的字元。"); // 檢查「內文」 req.checkBody("content") .notEmpty() .withMessage("請在「內文」中輸入您想對目標使用者傳達的訊息。") .len({min: 8, max: 500}) .withMessage("請在「內文」中輸入8~500數量的文字訊息。"); // 取得驗證結果 req.getValidationResult().then((result) => { let errors = result.mapped(); let responseMsg = { isOK: result.isEmpty() }; // 若驗證不通過,則傳送錯誤訊息 if (!responseMsg.isOK) { let firstErr = Object.values(errors)[0]; responseMsg.field = firstErr.param; responseMsg.message = firstErr.msg; res.send(responseMsg); return; } // 若驗證皆通過,則寄發信件: 將寄件者的信件寄給收件者。 User.SendSiteMail(req.session.passport.user, req.body, (err, isOK) => { if (!err) { responseMsg.url = "/send_sitemail_successfully"; } else if (User.IsUserNotExist(err)) { responseMsg.isOK = false; responseMsg.field = "SERVER"; responseMsg.message = "找不到指定的目標的收件者。"; } else { responseMsg.isOK = false; responseMsg.field = "SERVER"; responseMsg.message = err.message; } res.send(responseMsg); }); }); } // 若沒有登入,則回應錯誤。 else { res.send({isOK: false, message: "您尚未登入,請登入後再嘗試撰寫、傳送站內訊息。"}); } }); /** * 在頁面「撰寫站內訊息」下,成功處理站內信後的跳轉頁面。 */ router.get("/send_sitemail_successfully", (req, res) => { dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("message_form", dataObj); } }); }); /** * 頁面「更變密碼」的路由處理。 */ router.get("/newpw", (req, res) => { // 確認使用者是否有登入 if (req.session.passport && req.session.passport.user) { dataRender.DataRender("change_password", req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { let errMsg = req.flash("error"); if (errMsg.length > 0) { dataObj.datas.errorMessage = errMsg[0]; } res.render("change_password", dataObj); } }); } // 若沒有登入,則轉跳到首頁。 else { res.redirect("/"); } }) /** * 在頁面「更變密碼」下,傳送新、舊密碼來進行更新密碼的處理。 */ router.post("/newpw", (req, res) => { // 若使用者為登入狀態,則進一步檢查內容 if (req.session.passport && req.session.passport.user) { let body = req.body; req.checkBody("oldpw") .notEmpty() .withMessage("「舊密碼」為必要的欄位,請填寫您目前正使用的密碼。"); req.checkBody("newpw") .notEmpty() .withMessage("「新密碼」為必要的欄位,請填寫您想要的新密碼。") .not().equals(body.oldpw) .withMessage("「新密碼」請勿與「舊密碼」完全一致。請更換新密碼。") .matches(/(^[0-9a-zA-Z_?!@#+-]{5,16}$)/) .withMessage("「新密碼」欄位僅能填寫5~16的數字、英文字母或「_?!@#+-」字元。"); req.checkBody("newpw_confirm") .notEmpty() .withMessage("「確認新密碼」為必要的欄位,請填寫與「新密碼」欄位中一致的密碼。") .equals(body.newpw) .withMessage("「確認新密碼」欄位必須與「新密碼」欄位中的密碼一致。"); // 取得驗證結果 req.getValidationResult().then((result) => { // 如果結果為空,也就是沒有任何錯誤訊息的話 if (result.isEmpty()) { // 嘗試更變使用者的密碼 User.ChangePassword(req.session.passport.user, body.oldpw, body.newpw, (err, result) => { // 若有錯誤,則對錯誤做處理 if (err) { if (User.IsUserNotExist(err)){ // * 可改成轉跳到登入頁面 * req.flash("error", "使用者帳號已登出,請先重新登入。"); res.redirect("/newpw"); } // 若為其他錯誤,則告知使用者 else { console.log(err); req.flash("error", "伺服器內部錯誤,請稍後再嘗試。"); res.redirect("/newpw"); } } // 若無錯誤,則依照結果發送訊息 else { if (result) { req.session.changePW_successfuly = true; res.redirect("/newpw_success"); } else { req.flash("error", "「舊密碼」輸入錯誤。請輸入正確的、當前使用的密碼。"); res.redirect("/newpw"); } } }); } // 若不為空,表示有誤 else { let firstErr = Object.values(result.mapped())[0]; // 取得第一個錯誤 req.flash("error", firstErr.msg); // 將錯誤設定至flash中 res.redirect("/newpw"); // 轉跳到「更新密碼」頁面 } }); } // 若沒有登入,則轉跳到首頁。 (暫時) else { res.redirect("/"); } }); /** * 在頁面「更變密碼」之下,成功更改密碼之後的轉跳訊息頁面。 */ router.get("/newpw_success", (req, res) => { // 確認是否「剛剛更新完密碼」 if (req.session.changePW_successfuly) { delete req.session.changePW_successfuly; // 刪除標記,以免重複 dataRender.DataRender("message_form", req.url, req.session, (err, dataObj) => { if (err) { res.setHeader("Content-Type", "text/plain"); res.status(500); res.end("Server side error 500 : " + err); } else { res.render("message_form", dataObj); } }); } }); module.exports = router;