Files
JMuseum/routes/main.js
2018-02-26 14:09:18 +08:00

495 lines
18 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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;