Upload All

This commit is contained in:
Alan
2018-02-26 14:09:18 +08:00
parent 42d3a3fc46
commit 46257f08b0
1024 changed files with 204324 additions and 0 deletions

68
models/DataRender.js Normal file
View File

@ -0,0 +1,68 @@
const User = require("./mongooseSchemas/User"); // 引入「使用者 (User)」資料庫表。
const ServerStatus = require("../ServerStatus");
/**
* 插值來源對應表。可以迅速的比對使用者要求的頁面需要什麼樣的插值。
*/
const renderList = {
"index": require("./renderModels/index"),
"gallery": require("./renderModels/gallery"),
"theme": require("./renderModels/theme"),
"feedback": require("./renderModels/feedback"),
"login": require("./renderModels/login"),
"signup": require("./renderModels/signup"),
"message_form": require("./renderModels/message_form"),
"personal_page": require("./renderModels/personal_page"),
"edit_personal_info": require("./renderModels/edit_personal_info"),
"write_message": require("./renderModels/write_message"),
"change_password": require("./renderModels/change_password"),
"drawing": require("./renderModels/drawing"),
"showcase": require("./renderModels/showcase"),
"submit_theme": require("./renderModels/submit_theme"),
"vote_theme": require("./renderModels/vote_theme")
};
/** 
* 插值資料給予者。
* 依照使用者所給的路由位置,此函式就會回傳相對應的插值物件。
* @param {string} source 頁面的模板來源名稱(Pug Template)。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express 的 Session物件。
* @param {CallbackFunction} callback 回呼函式。傳回錯誤訊息與插值資料。
*/
function DataRender(source, route, session, callback) {
// 檢查Session中是否有passport欄位若有則嘗試取得user(資料庫中的_id)若無則設為null。
let _id = session.passport ? session.passport.user : null;
// 建構 dataObject 物件
let dataObject = {
source: source,
extendedStyle: source + "_extended.css",
title: route,
hasLogin: (_id !== undefined && _id !== null),
username: null,
notices: null,
datas: {},
haveSubmitThemeEvent: ServerStatus.status.submitThemeEvent,
haveVoteThemeEvent: ServerStatus.status.voteThemeEvent
};
// 嘗試取得資料來設定與目標使用者有關的基本資料(Username與通知數)
User.SetBasicInformation(_id, dataObject, function (err, dataObject) {
// 若該路由有在清單中則透過指定的插值方法來為dataObject加入其所需要的資料
if (source in renderList) {
// 透過source來取得該頁面的插值資料也就是設定 dataObject.datas 內容。
renderList[source].Render(dataObject, route, session, function (err, isSuccess) {
if (err)
callback(err, null);
else
callback(null, dataObject);
});
}
// 若不包含在清單中的,則視為例外
else {
callback(new Error("找不到對應的插值物件或Pug模板。"), null);
}
});
}
module.exports.DataRender = DataRender;

98
models/DataRender.ts Normal file
View File

@ -0,0 +1,98 @@
const User = require("./mongooseSchemas/User"); // 引入「使用者 (User)」資料庫表。
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* Standard data render layout
*/
interface BasicLayout {
/** 紀錄使用者目前在哪個頁面上。 */
source : string,
/** 延伸的CSS檔案名稱。 */
extendedStyle : string,
/** 網頁的標題名稱。 */
title : string,
/** 紀錄使用者是否登入網站。 */
hasLogin : boolean,
/** 使用者的名稱。 */
username : string,
/** 使用者的通知數。 */
notices : number,
/** 放置主要資料的物件。 */
datas : any,
/** 是否有「投稿主題」的活動、頁面在進行。 */
haveSubmitThemeEvent : boolean,
/** 是否有「投票主題」的活動、頁面在進行。 */
haveVoteThemeEvent : boolean
}
/**
* 插值來源對應表。可以迅速的比對使用者要求的頁面需要什麼樣的插值。
*/
const renderList =
{
"index" : require("./renderModels/index"),
"gallery" : require("./renderModels/gallery"),
"theme": require("./renderModels/theme"),
"feedback": require("./renderModels/feedback"),
"login": require("./renderModels/login"),
"signup": require("./renderModels/signup")
};
/**
* 轉跳頁面、訊息頁面所用的插值比起renderList中的差值這有較大的靈活性。
*/
const messageRender = require("./renderModels/message_form");
/** 
* 插值資料給予者。
* 依照使用者所給的路由位置,此函式就會回傳相對應的插值物件。
* @param {string} source 頁面的模板來源名稱(Pug Template)。
* @param {string} route 路由路徑。
* @param {any} session Express 的 Session物件。
* @param {CallbackFunction} callback 回呼函式。傳回錯誤訊息與插值資料。
*/
function DataRender(source: string, route : string, session : any, callback : CallbackFunction) : void
{
// 檢查Session中是否有passport欄位若有則嘗試取得user(資料庫中的_id)若無則設為null。
let _id = session.passport ? session.passport.user : null;
// 建構 dataObject 物件
let dataObject : BasicLayout = {
source : source,
extendedStyle : source + "_extended.css",
title : route,
hasLogin : (_id !== undefined && _id !== null),
username : null,
notices : null,
datas : {},
haveSubmitThemeEvent : false,
haveVoteThemeEvent : false
};
// 嘗試取得資料來設定與目標使用者有關的基本資料(Username與通知數)
User.SetBasicInformation(_id, dataObject, (err, dataObject) => {
// 透過route來取得該頁面的插值資料也就是設定 dataObject.datas 內容。
renderList[source].Render(dataObject, (err, isSuccess) => {
if (err)
callback(err, null);
else
callback(null, dataObject);
});
});
}
module.exports.DataRender = DataRender;

57
models/ResourceManager.js Normal file
View File

@ -0,0 +1,57 @@
const User = require("./mongooseSchemas/User");
const Painting = require("./mongooseSchemas/Painting");
const router = require("express").Router();
let sourceDir;
/**
* 用於初始化的函式。
* @param {string} sourceDirectory 被管理的資源的根目錄。
*/
function Initialize(sourceDirectory) {
sourceDir = sourceDirectory;
return router;
}
/**
* 檢查使用者是否登入。如果已登入則進行下一步反之則回送403狀態碼。
* @param {Express.Request} req Express的Request物件。
* @param {Express.Response} res Express的Response物件。
* @param {Function} next 導向函式。
*/
function CheckUserLogin(req, res, next) {
if (req.session.passport && req.session.passport.user) {
next();
}
else {
res.state(403).send("Forbidden");
}
}
/**
* 嘗試取得指定的圖畫影像。經過登入檢查之後,還要再檢查該項圖畫是否存在或屬於使用者。
*/
router.get("/db/paintings/:painting_file", (req, res) => {
let user_id = (req.session.passport && req.session.passport.user) ? req.session.passport.user : null;
let fileName = req.params.painting_file; // 取得URL參數中的檔案名稱
let painting_id = fileName.substr(0, fileName.length - 4); // 去掉後面的".png"
// 檢查使用者是否有足夠的權限訪問此畫作
Painting.CheckViewAuthority(painting_id, user_id, (err, result) => {
if (err) {
res.state(500).send("Internal Server Error");
return;
}
// 若權限足夠則將圖畫檔案送至客戶端若無則回送403。
if (result) {
res.status(200).sendFile(sourceDir + "/paintings/" + fileName);
}
else {
res.status(403).send("Forbidden");
}
});
});
module.exports = Initialize;

1027
models/ServerControl.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,28 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let CommentSchema = Schema({
username : String,
photo : String,
comment : String,
time : {type : Date, default : new Date()},
fromActivity : {type : Boolean, default : false}
});
CommentSchema.statics.createNewComment = function (data, callback) {
let newComment = this({
username: data.username,
photo: data.photo,
comment: data.comment,
time: new Date(),
fromActivity: data.fromActivity
});
newComment.save((err, comment) => {
if (err)
callback(err, null);
else
callback(null, comment._id);
});
}
module.exports = mongoose.model("Comment", CommentSchema);

View File

@ -0,0 +1,73 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const User = require("./User");
const Error_HaveSameThemeTitle = new Error("已經存在相同的主題名稱。");
const Error_ExistSameSponser = new Error("使用者已經在先前發起過一次主題了。");
let NewTheme = Schema({
title: String,
narrative: String,
image: String, // Image URL
sponsor: String,
votes: [String],
createdTime: { type: Date, default: Date.now }
});
/**
* 外部傳進來的新主題的必要資料項。
* @typedef NewThemeDatas
* @prop {string} title 新主題的主題名稱。
* @prop {string} narrative 新主題的主題敘述。
* @prop {string} image 新主題的圖示的連結位置。
* @prop {string} sponsor 此新主題的發起人之使用者名稱。
*/
/**
* 建立一個新的「新主題」資料。
* @param {NewThemeDatas} datas 原初的資料集合。
* @param {CallbackFunction} callback 回呼函式。成功時回呼_id。
*/
NewTheme.statics.createNew_NewTheme = function(datas, callback) {
// 先檢查是否已有存在了相同的主題名稱 或 發起人在先前已經有發起過一次了
this.findOne({title: datas.title}, (err, docs) => {
if (err) return callback(err, null);
if (docs) return callback(Error_HaveSameThemeTitle, null); // 若使用者發起的主題名稱,別人已經發起過,則回呼錯誤
// 建立新的主題名稱資料
let new_NewTheme = this({
title: datas.title,
narrative: datas.narrative,
image: datas.image,
sponsor: datas.sponsor,
votes: [],
createdTime: new Date()
});
// 將其儲存後並回呼
new_NewTheme.save((err) => {
if (err) return callback(err, null);
callback(null, new_NewTheme._id);
});
});
}
/**
* 檢查是否為「已經存在相同的主題名稱」之錯誤。
* @param {Error} error 欲進行檢查的錯誤。
* @return {boolean} 檢查之結果。
*/
NewTheme.statics.IsError_HaveSameThemeTitle = function (error) {
return error === Error_HaveSameThemeTitle;
}
/**
* 檢查是否為「已經存在相同的主題名稱」之錯誤。
* @param {Error} error 欲進行檢查的錯誤。
* @return {boolean} 檢查之結果。
*/
NewTheme.statics.IsError_ExistSameSponser = function (error) {
return error === Error_ExistSameSponser;
}
module.exports = mongoose.model("NewTheme", NewTheme);

View File

@ -0,0 +1,392 @@
const mongoose = require("mongoose");
const Jimp = require("jimp");
const uuid = require("uuid/v4");
const Participation = require("./Participation");
const Rating = require("./Rating");
const Comment = require("./Comment");
let User;
const Schema = mongoose.Schema;
const Error_PaintingNotExist = new Error("尋找的目標畫作不存在。");
const Error_PaintingIsLocked = new Error("此畫作已被鎖定,無法對其做任何修改。");
const Error_PaintingHasFinished = new Error("此畫作已經完成,無法再做一次完成的動作。");
let PaintingSchema = Schema({
id: String,
links: String,
name: String,
description: String,
artist: String,
createdTime: {type : Date, default : Date.now },
lastModified: {type : Date, default : Date.now },
tags: [{type : String}],
activity: {type : Schema.Types.ObjectId, ref: "Participation"},
viewAuthority: Number,
totalScore: Number,
ratings: [{type : Schema.Types.ObjectId, ref: "Rating"}],
comments: [{type : Schema.Types.ObjectId, ref: "Comment"}],
isFinished: Boolean,
isLocked: Boolean
});
/**
* 交叉引入由User來呼叫。
*/
PaintingSchema.statics.crossInitByUser = function () {
User = Object.freeze(require("./User"));
}
/**
* 建立新畫作的基本必要資料。
* @typedef NewPaintingData
* @prop {string} name 作品名稱。
* @prop {string} description 作品的敘述。
* @prop {string} artist 畫作的作者。即使用者名稱。
* @prop {string[]} tags 此畫作的標籤
* @prop {number} viewAuthority 作品的訪問權限。0=公開1=半公開2=私人。
* @prop {boolean} isFinished 作品是否完成。
*/
/**
* 建立一個新的畫作。
* @param {NewPaintingData} data 用於建立新畫作的基本資料。
* @param {CallbackFunction} callback 回呼函式。成功時回呼畫作的_id。
*/
PaintingSchema.statics.createNewPainting = function (data, callback) {
let createdDate = new Date();
let paintingUUID = uuid();
let newPainting = this({
id : paintingUUID,
links : "/db/paintings/" + paintingUUID + ".png",
name : data.name,
description : data.description,
artist : data.artist,
createdTime : createdDate,
lastModified : createdDate,
tags : data.tags,
activity : null,
viewAuthority : data.viewAuthority,
totalScore : 0,
ratings : [],
comments : [],
isFinished : data.isFinished,
isLocked : false
});
newPainting.save((err, painting) => {
if (err)
callback(err, null);
else
callback(null, {_id: painting._id, id: paintingUUID, lastModified: createdDate});
});
};
/**
* 一個包含所有要更新的圖畫資訊的物件。
* @typedef NewPaintingInfo
* @prop {string} name 作品名稱
* @prop {string} description 作品的敘述。
* @prop {string[]} taglist 標籤清單。
* @prop {number} view_authority 作品的訪問權限。
* @prop {boolean} isFinished 作品是否完成。
*/
/**
* 透過傳入的圖畫id尋找目標的圖畫資料以new_info來更新其資訊。
* @param {NewPaintingInfo} new_info 要更新目標圖畫資訊的物件。
* @param {string} id 目標圖畫資料的ID。
* @param {Jimp.Jimp} image Jimp影像物件。用來更新圖畫資料。
* @param {CallbackFunction} callback 回呼函式。若成功則回呼圖畫的_id與最後更新時間其餘皆為錯誤。
*/
PaintingSchema.statics.UpdateInfoById = function (new_info, id, image, callback) {
// 以圖畫id來取得目標資料
this.findOne({"id": id}, (err, paintingDocs) => {
if (err) return callback(err, null);
if (!paintingDocs) return callback(Error_PaintingNotExist, null); // 若找不到畫作資料,則回呼錯誤
if (paintingDocs.isLocked) return callback(Error_PaintingIsLocked, null); // 若目標畫作為「鎖定」狀態,則回乎錯誤
let isFinished_before = paintingDocs.isFinished; // 取得更改之前的isFinished的狀態
let newLastModified = new Date(); // 更新「最後修改日期」的時間
// 若目前作品已完成 且 使用者還想再次「完成」此作品,則回報錯誤
if (isFinished_before && new_info.isFinished) return callback(Error_PaintingHasFinished, null);
// 更新資料
paintingDocs.name = new_info.name;
paintingDocs.description = new_info.description;
paintingDocs.tags = new_info.taglist;
paintingDocs.viewAuthority = new_info.view_authority;
// 「最後修改日期」是隨著圖畫內容是否更動而變的
if (!isFinished_before)
paintingDocs.lastModified = newLastModified;
// 一但完成作品之後,就不會再回到「沒有完成」的狀態了。
paintingDocs.isFinished = isFinished_before || new_info.isFinished;
// 將圖畫資料儲存後並回呼
paintingDocs.save((err) => {
if (err) return callback(err, null);
/**
* 幾本上針對「作品完成」狀態與動作有四種情況:
* 儲存前 儲存動作
* 1. 未完成 不完成 : 作品未完成情況下,執行一般的「儲存」動作。
* 2. 未完成  完成 : 作品未完成情況下,執行「完成圖畫」的動作。
* 3. 已完成 不完成 : 作品已完成情況下,執行一般的「儲存」動作。也就是不更改畫作影像,僅變更畫作的相關資訊。動作完成後,圖畫仍為「完成」狀態。
* 4. 已完成  完成 : 作品已完成情況下,執行「完成圖畫」的動作。這是不被允許的,一個作品完成之後就不能再「完成」第二次。
* 在上頭的程式碼 "if (isFinished_before && new_info.isFinished) ..." 中已將第4種情況剔除
* 因此以下拿 paintingDocs.isFinished 來判斷是否更新圖畫影像。
*/
if (!isFinished_before) {
// 將新的影像複寫在舊的影像之上
image.write("./db/paintings/" + paintingDocs.id + ".png", (err) => {
if (err) return callback(err, null);
callback(null, {_id: paintingDocs._id, lastModified: newLastModified});
});
}
else {
callback(null, {_id: paintingDocs._id, lastModified: newLastModified});
}
});
});
}
/**
* 確認使用者(user_id)對於畫作(paintingId)的訪問權限是否足夠。
* 若訪問權限足夠回呼圖畫id反之則回呼false。
* @param {string} paintingId 畫作的Id。
* @param {string} user_id 使用者的_id。
* @param {CallbackFunction} callback 回呼函式。
*/
PaintingSchema.statics.CheckViewAuthority = function (paintingId, user_id, callback) {
this.findOne({"id": paintingId})
.select("id artist link viewAuthority")
.exec((err, paintingDocs) => {
if (err) return callback(err, null);
if (!paintingDocs) return callback(null, false); // 若尋找的圖畫Id不存在則回乎false。
// 當訪問權限為「公開」時,則直接回呼
if (paintingDocs.viewAuthority == 0) {
callback(null, paintingId);
}
// 當訪問權限為「半公開」或「私人」時,且使用者有登入時,則近一步判斷。
else if (user_id){
// 取得圖畫作者的資料,判斷使用者是否正為圖畫作者 或 圖畫作者的好友。
User.findOne({"username": paintingDocs.artist}, "_id friendList", (err, artistDocs) => {
if (err) return callback(err, null);
let isArtist = user_id == artistDocs._id; // 使用者是否為圖畫作者
let isFriend = artistDocs.IsUsersFriend(user_id); // 使用者是否為圖畫作者的好友
let check = isArtist || (paintingDocs.viewAuthority == 1 && isFriend); // 只要是圖畫作者 或 圖畫作者的朋友且訪問權限為「半公開」,則可以瀏覽此圖畫。
let result = check ? paintingId : false; // 依 check 取得結果
callback(null, result);
});
}
// 若圖畫訪問權限不為「公開」且使用者又沒登入則回呼false。
else {
callback(null, false);
}
}
);
}
/**
* 透過畫作Id取得在「繪圖創作」頁面上所需要的畫作資料。
* @param {string} paintingId 指定的畫作Id。
* @param {CallbackFunction} 回呼函式。若成功找到則將畫作資料回呼若失敗則為null。
*/
PaintingSchema.statics.GetDrawingPageInfoById = function (paintingId, callback) {
this.findOne({ "id": paintingId })
.populate({ path: "activity", select: { "nthSeason": 1, "themeName": 1 } })
.exec((err, paintingDocs) => {
if (err) return callback(err, null);
if (!paintingDocs) return callback(null, null);
paintingDocs.paintingName = paintingDocs.name; // 差值需求表定義之中與Painting資料表唯一不一樣的地方。 paintingName: paintingDocs.name
callback(null, paintingDocs);
}
);
}
/**
* 判斷錯誤是否為「指定的畫作不存在」。
* @return {boolean} 檢查的結果。
*/
PaintingSchema.statics.IsError_PaintingNotExist = function (error) {
return error == Error_PaintingNotExist;
}
/**
* 判斷錯誤是否為「畫作已被鎖定無法更改」。
* @return {boolean} 檢查結果。
*/
PaintingSchema.statics.IsError_PaintingIsLocked = function (error) {
return error == Error_PaintingIsLocked;
}
/**
* 判斷錯誤是否為「畫作已完成無法做二次完成動作」
* @return {boolean} 檢查結果。
*/
PaintingSchema.statics.IsError_PaintingHasFinished = function (error) {
return error == Error_PaintingHasFinished;
}
/**
* 取得「找不到指定畫作」的錯誤。
* @return {Error} 錯誤 Error_PaintingNotExist 。
*/
PaintingSchema.statics.GetError_PaintingNotExist = function () {
return Error_PaintingNotExist;
}
/**
* 移除所有與圖畫有關聯的「留言(Comment)」、「評分(Rating)」與「參與活動(Participation)」
* @param {CallbackFunction} callback 回呼函式。
*/
PaintingSchema.methods.RemoveAllReferenceInfo = function (callback) {
Participation.remove({"_id": this.activity}, (err) => {
if (err) return callback(err, null);
Rating.remove({"_id": { $in: this.ratings } }, (err) => {
if (err) return callback(err, null);
Comment.remove({"_id": { $in: this.comments }}, (err) => {
if (err) return callback(err, null);
callback(null, true);
});
});
});
}
/**
* 尋找使用者(Username)對這幅畫作的評分。若沒有則回傳0。
* 注意畫作資料必須要對ratings欄位做Populate之後此函式執行才會有正確的結果。
* @param {string} username 使用者名稱。
* @return {number} 目標使用者的評分分數。
*/
PaintingSchema.methods.FindRatingScoreByUsername = function (username) {
for (let rating of this.ratings) {
if (rating.username == username)
return rating.score;
}
return 0;
}
/**
* 透過畫作Id尋找指定的畫作資料將留言新增到其中。
* @param {string} paintingId 指定的畫作Id。
* @param {string} username 留言的使用者。
* @param {string} userPhotoURL 留言使用者的個人圖像。
* @param {string} comment 留言內容。
* @param {CallbackFunction} callback 回呼函式。
*/
PaintingSchema.statics.PushNewComment = function (paintingId, username, userPhotoURL, comment, callback) {
// 尋找目標畫作
this.findOne({"id": paintingId}, "comments", (err, paintingDocs) => {
if (err) return callback(err, null);
if (!paintingDocs) return callback(Error_PaintingNotExist, null);
// 定義留言資料
let newComment = {
username: username,
photo: userPhotoURL,
comment: comment,
time: new Date()
};
// 新增留言
Comment.createNewComment(newComment, (err, _id) => {
if (err) return callback(err, null);
// 將該項留言連結至目標畫作
paintingDocs.comments.push(_id);
paintingDocs.save((err) => {
if (err)
callback(err, null);
else
callback(null, _id);
});
});
});
}
/**
* 以使用者名稱(username)來尋找對應的評分(Rating)。
* 注意,必須要先連結(Populate)過ratings欄位之後才能使用此函式。
* @param {string} username 目標使用者名稱。
* @return {Rating?} 評分資料。
*/
PaintingSchema.methods.FindRatingByUsername = function (username) {
for (let docs of this.ratings) {
if (docs.username == username)
return docs;
}
return null;
}
/**
* 更新totalScore欄位。注意必須要先連結(Populate)過ratings欄位之後才能使用此函式。
*/
PaintingSchema.methods.UpdateTotalScore = function () {
let sum = 0;
for (let docs of this.ratings) {
sum += docs.score;
}
this.totalScore = sum / this.ratings.length;
}
/**
* 透過畫作Id尋找指定的畫作將評分分數更新到其上。
* @param {string} paintingId 指定的畫作Id。
* @param {string} username 評分的使用者名稱。
* @param {number} score 評分分數。
* @param {CallbackFunction} callback 回呼函式。
*/
PaintingSchema.statics.UpdateRatingById = function (paintingId, username, score, callback) {
// 以圖畫Id尋找指定的畫作資料
this.findOne({"id": paintingId})
.select("ratings totalScore")
.populate("ratings")
.exec((err, paintingDocs) => {
if (err) return callback(err, null);
if (!paintingDocs) return callback(Error_PaintingNotExist, null);
let datas = { username: username, score: score }; // Rating建立新資料
// 檢查使用者在之前是否已有做過評分
let ratingDocs = paintingDocs.FindRatingByUsername(username);
// 若在先前已有評分過,則
if (ratingDocs) {
ratingDocs.score = score; // 更新評分分數
paintingDocs.UpdateTotalScore(); // 更新totalScore欄位
// 儲存評分資料
ratingDocs.save((err) => {
if (err) return callback(err, null);
// 儲存畫作資料
paintingDocs.save((err) => {
if (err) return callback(err, null);
callback(null, paintingDocs._id);
});
});
}
else {
// 創立一個新的評分
Rating.createNewRating(datas, (err, ratingDocs) => {
if (err) return callback(err, null);
paintingDocs.ratings.push(ratingDocs); // 將新的評分加入
paintingDocs.UpdateTotalScore(); // 更新總評分
// 儲存畫作資料
paintingDocs.save((err) => {
if (err) return callback(err, null);
callback(null, paintingDocs._id);
});
});
}
}
);
}
module.exports = mongoose.model("Painting", PaintingSchema);

View File

@ -0,0 +1,65 @@
const mongoose = require("mongoose")
const Schema = mongoose.Schema;
/**
* 回呼函式,表示是否成功,並回傳物件。
* @callback CallbackFunction
* @param {Object} err 表示是否失敗。
* @param {Object} obj 表示回呼的物件。
*/
/**
* 精選集的模板定義。
* @property {String} relation 表示此精選集的所屬。如首頁的精選集、藝廊的精選輯。
* @property {Schema.Types.ObjectId[]} paintings 表示此精選集所有連接的圖畫。
*/
let PaintingSpotlightSchema = Schema({
relation : {type : String, required : true},
paintings : [{type : Schema.Types.ObjectId, ref : "Painting"}]
});
/**
* 建立一個新的精選集。
* @param {Object} data 包含要設定精選輯的資料。
* @param {function} callback 回呼函式。
*/
PaintingSpotlightSchema.statics.createNewPaintingSpotlight = function (data, callback) {
let newPaintingSpotlight = { relation : data.relation, paintings : data.paintings };
newPaintingSpotlight.save((err, paintingSpotlight) => {
if (err)
callback(err, null);
else
callback(null, paintingSpotlight._id);
});
};
/**
* 指定一個精選集,將新畫作加入進去。
* @param {string} collName 要加入新畫作的精選集。
* @param {objectId} painting 要加入的新畫作的Id。
* @param {function} callback 回呼函式(Error, IsSuccess)。
*/
PaintingSpotlightSchema.statics.addNewPaintingToCollection = function (collName, painting, callback) {
this.findOne({relation : collName}, (err, obj) => {
if (err) {
callback(err, null);
return;
}
obj.paintings.push(painting);
obj.save((err, RObj) => {
callback(err, RObj != null && RObj != undefined);
});
});
};
/**
* 取得畫作展示所要的資訊
* @param {string} collName 精選集的名稱
* @param {function} callback 回呼函式(Error, IsSuccess)。
*/
PaintingSpotlightSchema.statics.GetCarouselInfo = function (collName, callback) {
let query = {path : "paintings", select : {links : 1, name : 1, description : 1, artist : 1}};
this.findOne({"relation" : collName}).populate(query).exec(callback);
};
module.exports = mongoose.model("PaintingSpotlight", PaintingSpotlightSchema);

View File

@ -0,0 +1,35 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let ParticipantInfoSchema = Schema({
links: String,
rank: Number,
artist: String,
paintingName: { type: String, minlength: 1 },
description: { type: String, default: ""},
totalScore: { type: Number, default: 0},
ratings: [{type: Schema.Types.ObjectId, ref: "Rating"}],
comment: [{type: Schema.Types.ObjectId, ref: "Comment"}],
postTime : { type: Date, default: Date.now }
});
ParticipantInfoSchema.statics.createNewParticipantInfo = function (data, callback) {
let newPartInfo = this({
links : data.links,
rank : data.rank,
artist : data.artist,
paintingName : data.paintingName,
description : data.description,
totalScore : data.totalScore,
ratings : data.ratings,
postTime : data.postTime
});
newPartInfo.save((err, partInfo) => {
if (err)
callback(err, null);
else
callback(null, partInfo._id);
});
};
module.exports = mongoose.model("ParticipantInfo", ParticipantInfoSchema);

View File

@ -0,0 +1,26 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let ParticipationSchema = Schema({
nthSeason : Number,
themeName : String,
activityRank : Number,
postTime : { type: Date, default: Date.now }
});
ParticipationSchema.statics.createNewParticipation = function (data, callback) {
let newParticipation = this({
nthSeason : data.nthSeason,
themeName : data.themeName,
activityRank : data.activityRank,
postTime : data.postTime
});
newParticipation.save((err, obj) => {
if (err)
callback(err, null);
else
callback(null, obj._id);
});
};
module.exports = mongoose.model("Participation", ParticipationSchema);

View File

@ -0,0 +1,22 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let RatingSchema = Schema({
username : String,
score : {type : Number, min: 1, max: 5}
});
RatingSchema.statics.createNewRating = function(data, callback) {
let newRating = this({
username : data.username,
score : data.score
});
newRating.save((err, rating) => {
if (err)
callback(err, null);
else
callback(null, rating);
});
}
module.exports = mongoose.model("Rating", RatingSchema);

View File

@ -0,0 +1,169 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ServerStatus = require("../../ServerStatus");
const Theme = require("./Theme");
const NewTheme = require("./NewTheme");
/**
* 回呼函式。
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 傳回的物件。
*/
/**
* 「季」的模板定義。
* @prop {number} nth 表示目前的資料是第nth季
* @prop {Schema.Types.ObjectId[]} themes 這一季的所有主題。
* @prop {Date} startTime 這一季的開始時間。
* @prop {Date} endTime 這一季的結束時間。
*/
let SeasonSchema = Schema({
nth: Number,
themes: [{type : Schema.Types.ObjectId, ref : "Theme"}],
startTime: {type : Date, default : Date.now},
endTime: Date
});
/**
* 新一季的必要的基本資料。
* @typedef NewSeasonData
* @prop {number} nth 紀錄此資料為第nth季。
* @prop {ObjectId[]} themes 這一季所有的活動。以ObjectId做關聯。
* @prop {Date} startTime 開始這一季時的時間。
*/
/**
* 建立一個新的「季」資料。
* @param {Object} data 欲儲存的原始資料。
* @param {function} callback 回呼函式(Error, ObjectId)。
*/
SeasonSchema.statics.createNewSeason = function (data, callback) {
let newSeason = this({
nth: data.nth,
themes: data.themes,
startTime: data.startTime,
endTime: null
});
newSeason.save((err, season) => {
if (err) return callback(err, null);
callback(null, season._id);
});
}
/**
* 取得「傑作藝廊」頁面所需的資料。
* 注意: 要取用相關資料時必須先將Theme與ParticipantInfo資料表引入。
* @param {CallbackFunction} callback 回呼函式。含有錯誤訊息物件或資料物件。
*/
SeasonSchema.statics.GetGalleryNeedInfo = function (callback) {
// 取得一季之中所有的「主題 (themes)」與「季次 (nth)」
// 所有主題中的「編號 (order)」、「標題 (title)」與「參加作品 (participants)」
// 參加作品中的「排行 (rank)」、「作者 (artist)」、「作品敘述 (description)」與「投稿時間 (postTime)」
let curNthSeason = ServerStatus.status.currentSeason;
let populateQuery = {
path : "themes",
select : {"order": 1, "title": 1, "participants": 1},
populate : {
path : "participants",
select : {"rank": 1, "artist": 1, "description": 1, "postTime": 1}
}
};
// {"nth": {$lt: curNthSeason}} 中的 $lt: curNthSeason 表示不要選取到最新一季。
this.find({"nth": {$lt: curNthSeason}}).sort({nth: -1}).select("nth themes").populate(populateQuery).exec(callback);
}
/**
* 取得在「畫作主題」頁面下所需要的插值資料。
* @param {CallbackFunction} callback 回呼函式。含有錯誤訊息物件或資料物件。
*/
SeasonSchema.statics.GetThemePageNeedInfo = function (callback) {
let result = {currentSeason: null, lastSeason: null};
let currentSeasonPopulate = {path: "themes", select: {"order": 1, "title": 1, "narrative": 1, "imageURL": 1, "originator": 1, "participentCount": 1, "views": 1, "commentCount": 1}};
let lastSeasonPopulate = {path: "themes", select: {"order": 1, "title": 1, "originator": 1}};
let currentNthSeason = ServerStatus.status.currentSeason;
// 以nth尋找最新的一季在選擇其中的「nth」與「themes」欄位
// 並對「themes」展開選擇其中的指定欄位然後執行以上動作。
this.findOne({"nth": currentNthSeason})
.select("nth themes")
.populate(currentSeasonPopulate)
.exec((err, curSeason) => {
if (err) {
callback(err, null);
return;
}
result.currentSeason = curSeason;
// 接著尋找上一季指定「nth」、「endTime」與「themes」欄位
// 並對「themes」展開選擇其中的指定欄位然後執行以上動作。
this.findOne({"nth": currentNthSeason - 1})
.select("nth endTime themes")
.populate(lastSeasonPopulate)
.exec((err, lastSeason) => {
if (err) {
callback(err, null);
return;
}
result.lastSeason = lastSeason;
callback(null, result);
return;
});
});
}
/**
* 將畫作主題活動推進新的一季。
* 注意ServerStatus.status中的currentSeason必須要先推進到新的一季否則會相衝。
* @param {CallbackFunction} callback 回呼函式。
*/
SeasonSchema.statics.PushNewSeason = function (callback) {
let promotionCount = ServerStatus.status.promotionCount; // 取得要選取多少個候選主題至正規主題中
let curNthSeason = ServerStatus.status.currentSeason;
let Season = this;
// 首先先更新上一季的結束時間
Season.update({ nth: curNthSeason - 1 }, { $set: { "endTime": new Date() } }, (err, seasonDocs) => {
if(err) return callback(err, "\n更新前一季的時間時\n");
});
// 做統合取NewTheme中所有的欄位並建立一個 "voteCount" 欄位來記錄 votes 中有有多少個項目
// 然後再先後以投票數量(voteCount)與投稿時間(createdTime)來做排序並選取前N個(promotionCount)作為新一季的新主題。
NewTheme.aggregate(
[
{ "$project": {
title: 1,
narrative: 1,
image: 1,
sponsor: 1,
votes: 1,
createdTime: 1,
voteCount: { "$size": "$votes" }
}},
{ "$sort": { "voteCount": -1 } },
{ "$sort": { "createdTime": 1 } },
{ "$limit": promotionCount }
],
function (err, docsList) {
if (err) return callback(err, "\n取得指定候選主題時發生了錯誤。請確認是否有連接MongoDB並重新嘗試。\n");
// 將指定的候選主題轉換到正規的主題(Theme)中。其中處理完成後回呼_id清單。
Theme.TransferFromNewTheme(docsList, (err, _idList) => {
if (err) return callback(err, "\n將候選主題轉換至正規主題時發生了錯誤。請確認是否有連接MongoDB並重新嘗試。\n");
// 建立基本新的一季(Season)的資料
let data = {
nth: ServerStatus.status.currentSeason,
themes: _idList,
startTime: new Date()
};
// 利用data建立最新一季的資料
Season.createNewSeason(data, (err, _id) => {
if (err) return callback(err, "\n建立新一季的活動資料時發生了錯誤。請確認是否有連接MongoDB並重新嘗試。\n");
// 將所有的候選主題都清空
NewTheme.remove({}, (err) => {
if (err) return callback(err, "\n在清空NewTheme中所有資料時發生了錯誤。請改用手動的方式刪除或檢查是否有連接MongoDB。\n");
callback(null, true);
});
});
});
}
)
}
module.exports = mongoose.model("Season", SeasonSchema);

View File

@ -0,0 +1,28 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let ServerMessageSchema = Schema({
title: String,
content: String
});
/**
* @typedef NewServerMessageData
* @param {string} title 訊息主題。
* @param {string} content 訊息內容。
*/
/**
* 建立一個新的伺服訊息資料。
* @param {NewServerMessageData} data 建立一個新的伺服訊息資料的必要資料。
* @param {CallbackFunction} callback 回呼函式。
*/
ServerMessageSchema.statics.createNewServerMessage = function (data, callback) {
let newServMsg = this({ title: data.title, content: data.content });
newServMsg.save((err) => {
if (err) return callback(err, null);
callback(null, newServMsg._id);
});
}
module.exports = mongoose.model("ServerMessage", ServerMessageSchema);

View File

@ -0,0 +1,37 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
/**
* 站內信件。一封信件中內有基本的主旨、內容、寄件者等等資訊。
* @prop {String} title 主旨。
* @prop {String} content 內文。
* @prop {String} sender 寄件者。
* @prop {String} isPrivate 是否為私人信件。若為是,表示只有收件者看得到;反之則否。
* @prop {Date} sendTime 寄件時間。
*/
let SiteMailSchema = Schema({
title: String,
content: String,
sender: String,
isPrivate: Boolean,
sendTime: {type: Date, default: new Date()}
});
// 建立一個新的站內信件。回傳站內信的_id。
SiteMailSchema.statics.createNewSiteMail = function (data, callback) {
let newSiteMail = this({
title: data.title,
content: data.content,
sender: data.sender,
isPrivate: data.isPrivate,
sendTime: data.sendTime
});
newSiteMail.save((err, siteMail) => {
if (err)
callback(err, null);
else
callback(null, siteMail._id);
});
}
module.exports = mongoose.model("SiteMail", SiteMailSchema);

View File

@ -0,0 +1,46 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
let SiteMessageSchema = Schema({
from : { type : String, enum : ["server", "personal"] },
title : { type : String, default : "Title" },
content : { type : String, default : "" },
refId : { type : Schema.Types.ObjectId, ref : "ServerMessage" },
id : { type : Number },
isSeen : { type : Boolean, default : false },
isPrivate : { type : Boolean, default : false }
});
/**
* @typedef NewSiteMessageData
* @prop {string} from 表示來自「伺服公告」(“server”) 或是「個人訊息」(“personal”)。
* @prop {string} title 訊息標題。
* @prop {string?} content 訊息內容 (可為HTML格式。當 from 為 “personal”時此項才會有內容)。
* @prop {string?} refId 訊息參考 (參考NoticeBoard中的訊息當 from 為 “server” 時此項才會有內容)。
* @prop {number} id 訊息編號。
* @prop {boolean} isPrivate 是否為私人訊息。
*/
/**
* 建立一個主頁訊息資料。
* @param {NewSiteMessageData} data 建立一個新的主頁訊息所需的基本資料。
* @param {CallbackFunction} callback 回呼函式。
*/
SiteMessageSchema.statics.createNewSiteMessage = function (data, callback) {
let newSiteMsg = this({
from : data.from,
title : data.title,
content : data.content,
refId : data.refId,
id : data.id,
isSeen : false,
isPrivate : data.isPrivate
});
newSiteMsg.save((err, siteMsg) => {
if (err)
callback(err, null);
else
callback(err, siteMsg._id);
});
};
module.exports = mongoose.model("SiteMessage", SiteMessageSchema);

View File

@ -0,0 +1,89 @@
const fileSystem = require("fs");
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ServerStatus = require("../../ServerStatus");
// 建立「主題」樣板
let ThemeSchema = Schema({
order : Number,
title : String,
narrative : String,
image : String, // URL
originator : String,
participants : [{ type : Schema.Types.ObjectId, ref : "ParticipantInfo" }],
views : Number,
commentCount : Number
});
// 透過參數data來建立「主題」資料於表中。
ThemeSchema.statics.createNewTheme = function (data, callback) {
let newTheme = this({
order : data.order,
title : data.title,
narrative : data.narrative,
image : data.image, // URL
originator : data.originator,
participants : data.participants,
views : data.views,
commentCount : data.commentCount
});
newTheme.save((err, theme) => {
if (err)
callback(err, null);
else
callback(null, theme._id);
});
}
/**
* 轉移完成後回呼的函式定義。
* @callback TransferCallback
* @param {Error?} error 錯誤訊息物件。
* @param {ObjectId[]?} docs_id 轉換後的所有主題對應的_id。
*/
/**
* 自候選主題(NewTheme)轉移到主題(Theme)。當活動狀態要推向新的一季時所會用到的函式。
* @param {NewTheme[]} newThemeDocs 要轉移的候選主題。
* @param {TransferCallback} callback 回呼函式。
*/
ThemeSchema.statics.TransferFromNewTheme = function (newThemeDocs, callback) {
let projectRoot = global.__dirname; // 此專案的根目錄
let curNthSeason = ServerStatus.status.currentSeason; // 取得目前最新一季的季數
let newThemeDatas = []; // 用來新增Theme資料的集合清單
// 為每一個選中的候選主題建立Theme資料
newThemeDatas.forEach((docs, index) => {
let routePath = docs.image.split("/"); // 以字元"/"分解字串,取得路徑
let fileName = routePath[routePath.length - 1]; // 取得檔案名稱
let URLFilePath = "/images/seasons/" + curNthSeason + "/" + fileName; // 找資源用的URL檔案路徑
let newFilePath = "/public" + URLFilePath; // 儲存用的相對檔案路徑
// 將建立主題的基本資料加入至newThemeDatas中
newThemeDatas.push({
order: index,
title: docs.title,
narrative: docs.narrative,
image: "/images/seasons/" + curNthSeason + "/" + fileName,
originator: docs.sponsor,
participants: [],
views: 0,
commentCount: 0
});
// 將檔案移動至新的資料夾中。
// 從位置 "/public/images/newtheme" 轉移到 "/public/images/seasons/[currentNthSeason]" 之下
// 其中檔案名稱不變。
fileSystem.rename(projectRoot + "/public" + docs.image, projectRoot + newFilePath, (err) => {
console.log("「轉移候選主題」: 在移動檔案\"%s\"至\"%s\"時發生了錯誤。", "/public" + docs.image, newFilePath);
});
});
// 將所有選取的候選主題資料插入、新增到Theme之中並且回呼這些資料的_id組成ObjectId清單。
this.insertMany(newThemeDatas, (err, docsList) => {
if (err) return callback(err, null);
callback(null, docsList.map(docs => docs._id));
});
}
module.exports = mongoose.model("Theme", ThemeSchema);

View File

@ -0,0 +1,624 @@
const fileSystem = require("fs");
const bcrypt = require("bcryptjs");
const mongoose = require("mongoose");
const SiteMail = require("../mongooseSchemas/SiteMail");
const Painting = require("./Painting");
const Schema = mongoose.Schema;
const saltNumber = 8; // 在進行雜湊之前將要被雜湊的資料「加鹽」其中長度固定為8。
const Error_ExistSameUsername = new Error("已有存在相同使用者名稱。");
const Error_ExistSameEmail = new Error("已有存在相同電子郵件。");
const Error_UserNotExist = new Error("目標使用者不存在。");
const Error_IlligelPhotoImageFormat = new Error("錯誤的影像檔案格式。");
/**
* 定義在「使用者」資料表中,儲存「個人資訊」的子資料表。
* @prop {String} email 使用者的電子郵件信箱。
* @prop {String} lastName 使用者的姓。
* @prop {String} firstName 使用者的名。
* @prop {String} nickName 使用者的暱稱。
* @prop {String} motto 使用者的個人短語。
* @prop {String} photo 使用者的照片。儲存形式為URL。
*/
let PersonalInfo = Schema({
email : String,
lastName : String,
firstName : String,
nickname : String,
motto : String,
photo : String, // URL
});
/**
* 定義在「使用者」資料表中,儲存「主頁訊息」的子資料表。
* @prop {Boolean} isServerMessage 是否為伺服廣播訊息。
* @prop {String?} title 訊息標題。在isServerMessage = true時此項為空。
* @prop {String?} content 訊息內容。在isServerMessage = true時此項為空
* @prop {ObjectId} refId 訊息參考_id。當isServerMessage = false時此項會連接到一個假的伺服訊息。
* @prop {Date} postTime 輸出此訊息時的時間日期。
* @prop {Boolean} isSeen 此訊息是否已被使用者讀過了。
* @prop {Boolean} isPrivate 訊息是否僅能被使用者看見。
*/
let SiteMessage = Schema({
isServerMessage: Boolean,
title: String,
content: String,
refId: {type: Schema.Types.ObjectId, ref: "ServerMessage"},
postTime: {type: Schema.Types.Date, default: Date.now },
isSeen: {type: Schema.Types.Boolean, default: false},
isPrivate: Boolean
});
/**
* 定義「使用者」資料表。
* @prop {String} username 使用者的名稱。
* @prop {String} password 使用者的密碼。以雜湊的方式來儲存。
* @prop {PersonalInfo} personalInfo 使用者的個人資料。
* @prop {String[]} tags 使用者所定義的標籤。
* @prop {ObjectId[] -> Painting} paintings 儲存使用者的作畫資料。以連結的方式儲存。
* @prop {SiteMessage[]} siteMsg 站內訊息。以連結的方式儲存。
* @prop {ObjectId[] -> SiteMail} siteMail 站內信。以連結的方式儲存。
* @prop {Number} notices 通知數。表示使用者未讀的站內訊息數量。
* @prop {ObjectId[] -> User} friendList 好友清單。以連結的方式儲存。
* @prop {Boolean} autoSaveEnable 選項。是否在作畫的時候自動儲存。
* @prop {Boolean} hasPostFeedback 表示此使用者是否有在這個月內回饋。
* @prop {Boolean} hasPostNewTheme 表示使用者是否有投稿過新主題。
* @prop {Boolean} hasVotedNewTheme 表示使用者是否有為新主題投過票。
*/
let UserSchema = Schema({
id : String,
username : String,
password : String,
personalInfo : PersonalInfo,
tags : [{type: String}],
paintings : [{type : Schema.Types.ObjectId, ref : "Painting"}],
siteMsg : [{type: SiteMessage}],
siteMail : [{type : Schema.Types.ObjectId, ref : "SiteMail"}],
notices : Number,
friendList : [{type : Schema.Types.ObjectId, ref : "User"}],
autoSaveEnable : Boolean,
hasPostFeedback : Boolean,
hasPostNewTheme : Boolean,
hasVotedNewTheme : Boolean
});
/**
* @typedef NewUserDataSet
* @prop {String} lastName  新使用者的「姓」字串資料。
* @prop {String} firstName 新使用者的「名」字串資料。
* @prop {String} email 新使用者的「Email」字串資料。
* @prop {String} username 新使用者的「使用者名稱」字串資料。
* @prop {String} password 新使用者的「密碼」字串資料。
* @prop {String?} confirmPassword 新使用者的「確認密碼」字串資料。
*/
/**
* 以輸入的資料,建立新的使用者資料。
* @param {NewUserDataSet} data 紀錄要新增使用者的來源資料.
* @param {CallbackFunction} 回呼函式。決定資料儲存是否成功或發生錯誤。
*/
UserSchema.statics.createNewUser = function (data, callback) {
let _User = this;
// 檢查輸入的使用者名稱與信箱是否與現存使用者的相衝
this.findOne({ $or: [{"username": data.username}, {"personalInfo.email": data.email}]})
.exec((err, user) => {
if (err) { // 如果發生錯誤,則回傳錯誤訊息
callback(err, null);
return;
} // 若有找到相符的信箱或名稱,則回呼錯誤訊息
else if (user) {
if (user.username == data.username) {
callback(Error_ExistSameUsername, null);
}
else {
callback(Error_ExistSameEmail, null);
}
return;
}
// 若欲新增的使用者不存在,則可以新增。首先,先對密碼做加密動作。
bcrypt.hash(data.password, saltNumber, (err, hash) => {
// 若密碼雜湊過程有錯誤,則直接回呼。
if (err) {
callback(err, null);
return;
}
// 以自身(模組)建立一個新的資料
let newUser = _User({
username : data.username,
password : hash,
personalInfo : {
email : data.email,
lastName : data.lastName,
firstName : data.firstName,
nickname : "",
motto : "",
photo : "/sample/Example.png"
},
tags : [],
paintings : [],
siteMsg : [],
siteMail : [],
notices : 0,
friendList : [],
autoSaveEnable : true,
hasPostFeedback : false,
hasPostNewTheme: false,
hasVotedNewTheme: false
});
// 將新建立的使用者資料儲存。並回呼結果。
newUser.save(callback);
});
}
);
};
/**
* 判斷「相同使用者名稱」錯誤物件。
* @param {Error} error 要判斷的錯誤物件。
* @return {Boolean} 回傳布林值,判斷錯誤是否為「相同使用者名稱或信箱」。
*/
UserSchema.statics.IsExistSameUsername = function (error) {
return error === Error_ExistSameUsername;
}
/**
* 判斷「相同信箱」錯誤物件。
* @param {Error} error 要判斷的錯誤物件。
* @return {Boolean} 回傳布林值,判斷錯誤是否為「相同使用者名稱或信箱」。
*/
UserSchema.statics.IsExistSameEmail = function (error) {
return error === Error_ExistSameEmail;
}
/**
* 判斷「使用者不存在」錯誤物件。
* @param {Error} error 要判斷的錯誤訊息物件。
* @return {Boolean} 回傳布林值,判斷錯誤是否為「目標使用者不存在。」。
*/
UserSchema.statics.IsUserNotExist = function (error) {
return error === Error_UserNotExist;
}
/**
* 取得「使用者不存在」錯誤物件。
* @return {Error} 回傳「使用者不存在」錯誤物件。
*/
UserSchema.statics.Error_UserNotExist = function () {
return Error_UserNotExist;
};
/**
* 比對登入的帳號與密碼。
* @param {String} username 使用者名稱。
* @param {String} password 要進行比對的密碼。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.AccountComparison = function (username, password, callback) {
// 尋找指定的使用者帳號是否存在
this.findOne({"username" : username})
.exec((err, user) => {
if (err) {
callback(err, null);
return;
}
if (!user) {
callback(null, false);
return;
}
// 比對輸入的帳號與儲存於資料庫中的密碼雜湊
bcrypt.compare(password, user.password, (err, result) => {
if (result)
callback(err, user);
else
callback(err, false);
});
}
);
}
/**
* 以傳入的資料庫識別ID來尋找指定使用者資料將需要的基本訊息(使用者名稱、通知數)設定至標準插值物件上。
* @param {String?} user_Id 為資料庫中的識別Id用來尋找使用者資料所用。若此項不存在則直接回呼。
* @param {BasicLayout} dataObject 基本插值物件。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.SetBasicInformation = function (user_Id, dataObject, callback) {
// 若傳入的 user_Id 不為空,則嘗試尋找該目標使用者並取得需要的基本插值資料
if (user_Id) {
// 以 user_Id 取得目標使用者資料。
this.findOne({_id: user_Id})
.exec((err, user) => {
if (err) {
callback(err, null);
return;
}
if (!user) {
callback(Error_UserNotExist, null);
return;
}
// 若資料庫存取無錯誤、有找到目標使用者,則將使用者名稱與通知數的資訊,加入到基本插值物件中。
dataObject.username = user.username;
dataObject.notices = user.notices;
callback(null, dataObject);
}
);
}
// 如果 user_Id 為空的話,則直接回呼。
else {
callback(null, dataObject);
}
}
/**
* 嘗試以傳入的_id來去尋找目標使用者資料若目標資料中的使用者名稱與參數username一樣則回呼true否則false。
* @param {string} user_Id 目標要尋找的使用者的_id。
* @param {string} username 要比對的使用者名稱。
* @param {CallbackFunction} callback 回呼函式。回傳錯誤訊息或結果。
*/
UserSchema.statics.CheckUsernameBy_Id = function (user_Id, username, callback) {
// 以 user_Id 來尋找目標使用者。
this.findOne({"_id": user_Id})
.exec((err, user) => {
// 若資料庫有發生錯誤則直接將錯誤回呼。
if (err) return callback(err, null);
// 若使用者存在,則回呼比較結果。
if (user) {
return callback(null, user.username == username);
}
// 若不存在,則回呼「使用者不存在」錯誤。
else {
return callback(Error_UserNotExist, null);
}
}
);
}
/**
* 更新個人資料。
* 更新完資料若無問題則回呼callback(null, true)若有錯誤則回呼callback(err, false)。
* @param {string} user_id 對應至使用者資料的 ObjectId 字串。
* @param {PersonalInfo} textDatas 存放姓、名、暱稱、短言的物件。
* @param {Multer.FileInfo?} photoInfo 使用者傳送至伺服端的初始影像檔案。若此項為null則不更新圖像資料。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.UpdatePersonalInfo = function (user_id, textDatas, photoInfo, callback) {
// 先尋找目標使用者的資料
this.findOne({ "_id": user_id })
.exec((err, userDocs) => {
// 若資料庫尋找時出現錯誤,將其錯誤資料回呼
if (err) return callback(err, null);
// 若找不到使用者時,將「使用者不存在」回呼。
if (!userDocs) return callback(Error_UserNotExist, null);
// 更新文字部分的個人資料
userDocs.personalInfo.lastName = textDatas.lastName;
userDocs.personalInfo.firstName = textDatas.firstName;
userDocs.personalInfo.nickname = textDatas.nickname;
userDocs.personalInfo.motto = textDatas.motto;
// 若有圖像資訊,表示使用者上傳了新的圖像,需要對此資料項更新
if (photoInfo) {
let fileName = userDocs.username + (photoInfo.mimetype == "image/jpeg" ? ".jpg" : ".png"); // 定義新圖檔名稱
let publicPath = "/images/user_photos/" + fileName; // 外部、瀏覽器端可看得到的路徑
let dstFilePath = "public" + publicPath; // 複製檔案的目的路徑
// 將暫存的圖片檔案複製到指定的位置
fileSystem.copyFile(photoInfo.path, dstFilePath, (err) => {
// 若發生錯誤,則將錯誤回呼
if (err) return callback(err, null);
// 更新個人頭像路徑
userDocs.personalInfo.photo = publicPath;
// 將暫存的圖片刪除
fileSystem.unlink(photoInfo.path, (err) => { if (err) console.log(err); });
// 儲存更變後的個人資料
userDocs.save((err) => {
// 若發生錯誤,則將錯誤回呼
if (err) return callback(err, null);
// 若無,則回呼 callback(null, true) 以表示完成
callback(null, true);
});
});
}
// 若無,則直接儲存資料
else {
// 儲存更變後的個人資料
userDocs.save((err) => {
// 若發生錯誤,則將錯誤回呼
if (err) return callback(err, null);
// 若無,則回呼 callback(null, true) 以表示完成
callback(null, true);
});
}
})
;
}
/**
* 傳入使用者名稱,檢查該名使用者是否存在於資料庫中。
* 若存在,則回呼 true ;若否,則回呼 false。
* @param {string} username 欲查詢的使用者名稱。
* @param {CallbackFucntion} callback 回呼函式。
*/
UserSchema.statics.CheckUserIsExistByUsername = function (username, callback) {
this.findOne({"username": username})
.exec((err, docs) => {
if (err) return callback(err, null);
callback(null, docs !== null && docs !== undefined);
}
);
}
/**
* @typedef {Object} MailData 信件資料。
* @prop {string} recipient 收件者的使用者名稱。
* @prop {string} subject 信件的主旨。
* @prop {string} content 信件的內容。
* @prop {Boolean} isPrivate 表示此信件是否為「私人觀看」的。
*/
/**
* 使用者sender傳送站內訊息給目標使用者。
* @param {string} sender 寄件者的使用者_id。
* @param {MailData} mailInfo 信件內容。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.SendSiteMail = function (sender_id, mailInfo, callback) {
// 檢測目標收件者是否存在
this.findOne({"username": mailInfo.recipient}).exec((err, recipientDocs) => {
if (err) return callback(err, null); // 若資料庫有錯誤,則直接回呼
if (!recipientDocs) return callback(Error_UserNotExist, null); // 若找不到收件者,則回呼「目標使用者不存在。」錯誤。
// 尋找寄件者的使用者名稱
this.findOne({"_id": sender_id}).select("username").exec((err, senderDocs) => {
if (err) return callback(err, null); // 若資料庫有錯誤,則直接回呼
// 建立SiteMail資料
let data = {
title: mailInfo.subject,
content: mailInfo.content,
sender: senderDocs.username,
isPrivate: mailInfo.isPrivate,
sendTime: new Date()
}
// 建立站內信並將其站內信連接到收件者的siteMail中
SiteMail.createNewSiteMail(data, (err, _id) => {
if (err) return callback(err, null); // 若資料庫有錯誤,則直接回呼
recipientDocs.siteMail.push(_id); // 將新增的站內信的_id加入到收件者的siteMail中。
// 也許多新增一下站內訊息?
// 儲存更動結果
recipientDocs.save((err) => {
if (err) return callback(err, null); // 若資料庫有錯誤,則直接回呼
callback(null, true);
});
});
});
});
}
/**
* 更變指定使用者的密碼。先驗證舊有密碼,若成功則更新密碼。
* @param {string} _id 要更改使用者密碼的目標使用者的_id。
* @param {string} old_password 使用者輸入的舊密碼。
* @param {string} new_password 使用者輸入的新密碼。
* @param {CallbackFunction} callback 回呼函式。若輸入的舊密碼與新密碼不相符則回呼false若相符且成功更改則回呼true。
*/
UserSchema.statics.ChangePassword = function (_id, old_password, new_password, callback) {
// 以_id尋找指定的使用者資料
this.findOne({"_id": _id}).exec((err, userDocs) => {
if (err) return callback(err, null);
if (!userDocs) return callback(Error_UserNotExist, null);
// 比對舊密碼若result = true表示正確則繼續更新密碼動作反之則回呼false。
bcrypt.compare(old_password ,userDocs.password, (err, result) => {
if (err) return callback(err, null);
if (!result) return callback(null, false);
// 對新密碼做雜湊演算,取得雜湊後的密碼
bcrypt.hash(new_password, saltNumber, (err, hashedPW) => {
userDocs.password = hashedPW; // 更新密碼
// 將更動過後的使用者資料儲存
userDocs.save((err) => {
if (err) return callback(err, null);
// 回呼 true表示成功
callback(null, true);
});
})
});
});
}
/**
* 確認傳入的標籤清單中所有的標籤,是否皆在使用者定義的標籤清單之中。
* @param {string} user_id 目標使用者的_id。
* @param {string[]} tagsList 要進行檢查的目標標籤清單。
* @param {CallbackFunction} callback 回呼函式。若驗證成功則回呼true反之則false。
*/
UserSchema.statics.IsInUsersTagsList = function (user_id, tagsList, callback) {
this.findOne({"_id": user_id}).exec((err, userDocs) => {
if (err) return callback(err, null);
if (!userDocs) return callback(Error_UserNotExist, null);
if (tagsList === null || tagsList == undefined) return callback(null, false);
// 檢查tagsList中所有的標籤是否皆在使用者定義的標籤之中。
for (let tag of tagsList) {
// 若其中一個標前不在使用者的定義之中時則回呼false。
if (userDocs.tags.indexOf(tag) < 0)
return callback(null, false);
}
// 經檢查後若皆在定義之中則回呼true。
callback(null, true);
});
}
/**
* 確認傳入的標籤清單中所有的標籤,是否接載使用者定義的標籤清單中。
* @param {string[]} tagsList 要進行檢查的目標標籤清單。
* @return {boolean} 是否皆在使用者定義的標籤清單中。
*/
UserSchema.methods.IsInTagsList = function (tagsList) {
let tagsByUser = this.tags;
// 檢查tagsList中所有的標籤是否皆在使用者定義的標籤之中。
for (let tag of tagsList) {
// 若其中一個標前不在使用者的定義之中時則回呼false。
if (tagsByUser.indexOf(tag) < 0)
return false;
}
return true;
}
/**
* 確認這個畫作是否為此使用者所擁有。
* @param {string} paintingId 欲檢查的畫作的id (為UUID)。
* @return {boolean} 代表檢查的畫作是否為使用者擁有。
*/
UserSchema.methods.IsPaintingsOwner = function (paintingId) {
let usersPaintings = this.paintings;
for (let painting of usersPaintings) {
if (painting.id === paintingId)
return true;
}
return false;
}
/**
* 嘗試以畫作Id取得使用者的畫作資料。
*
* @param {string} 畫作Id。
* @return {Painting?} 目標畫作的資料。若找不到畫作則回傳null。
*/
UserSchema.methods.GetPaintingById = function (paintingId) {
for (let painting of this.paintings) {
if (painting.id == paintingId)
return painting;
}
return null;
}
/**
* 檢查圖畫ID(paintingId)是否為目標使用者(user_id)所擁有。
* 若擁有則回呼paintingId若否則回呼false。
* @param {string} paintingId 要檢查的圖畫的Id。
* @param {string} user_id 目標使用者的_id。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.CheckPaintingId = function (paintingId, user_id, callback) {
this.findOne({"_id": user_id})
.populate({ path: "paintings", select: { "id": 1 } })
.exec((err, userDocs) => {
if (err) return callback(err, null);
if (userDocs.IsPaintingsOwner(paintingId)) {
callback(null, paintingId);
}
else {
callback(null, false);
}
}
);
}
/**
* 查詢目標使用者(user_id)是否為此使用者的好友。
* @param {string} user_id 目標使用者的_id。
* @return {boolean} 是否為使用者的好友。
*/
UserSchema.methods.IsUsersFriend = function (user_id) {
let list = this.friendList;
for (let _id of list) {
if (_id.equals(user_id))
return true;
}
return false;
}
/**
* 對所有的使用者做訊息廣播。
* @param {string} servMsg_id 目標的伺服器訊息的_id
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.BroadcastServerMessage = function (servMsg_id, callback) {
// 建立站內資料
let newSiteMsg = {
isServerMessage: true,
refId: servMsg_id,
postTime: new Date(),
isSeen: false,
isPrivate: false
};
// 取得每一個使用者的資料並選取其中的notices與siteMsg欄位
this.find({}, "notices siteMsg", (err, userDocs) => {
if (err) return callback(err, null);
// 循每一位使用者,將新的站內訊息資料加入到使用者資料中
let index = -1, length = userDocs.length;
function SaveUserDocs(err) {
if (err) return callback(err, null);
index += 1;
// 若尚未儲存完畢,也就是還未到最後一個時,則繼續儲存
if (index < length) {
userDocs[index].notices += 1;
userDocs[index].siteMsg.push(newSiteMsg);
userDocs[index].save(SaveUserDocs);
}
// 若已完成則回呼
else {
callback(null, true);
}
}
// 回呼式地做更改、儲存的動作。
SaveUserDocs(false);
});
}
/**
* 以指定的標題、內容、隱私設定來獨立新增一個站內訊息。
* @param {string} title 訊息的標題。
* @param {string} content 訊息的內容。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.methods.AddNewSiteMessage = function (title, content, isPrivate) {
this.siteMsg.push({
isServerMessage: false,
title: title,
content: content,
refId: null,
postTime: new Date(),
isSeen: false,
isPrivate: isPrivate
});
}
/**
* 將所有使用者的「hasPostNewTheme」欄位設定為false。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.Refresh_HasPostNewTheme = function (callback) {
// 嘗試將所有使用者資料中的 hasPostNewTheme 欄位更新成 false。
this.updateMany({}, { $set: { "hasPostNewTheme": false } }, callback);
}
/**
* 將所也使用者的「hasVotedNewTheme」欄位設定為false。
* @param {CallbackFunction} callback 回呼函式。
*/
UserSchema.statics.Refresh_HasVotedNewTheme = function (callback) {
// 嘗試將所有使用者資料中的 hasVotedNewTheme 欄位更新成 false。
this.updateMany({}, { $set: { "hasVotedNewTheme": false } }, callback);
}
module.exports = mongoose.model("User", UserSchema);
// 交叉引入下替Painting引入User。
Painting.crossInitByUser();

View File

@ -0,0 +1,16 @@
const User = require("../mongooseSchemas/User");
/**
* 頁面「更變密碼」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function ChangePasswordRender(renderData, route, session, callback) {
// 頁面「更變密碼」不需要任何插值。
callback(null, true);
}
module.exports.Render = ChangePasswordRender;

View File

@ -0,0 +1,72 @@
const User = require("../mongooseSchemas/User");
const Painting = require("../mongooseSchemas/Painting");
const Participation = require("../mongooseSchemas/Participation");
/** 預設的新畫作基本資料。 */
const defaultPaintingData = {
id: null,
paintingName: "",
description: "",
tags: [],
viewAuthority: 0,
createdTime: "無",
lastModified: "無",
activity: null,
isFinished: false,
isLocked: false
};
/**
* 頁面「繪圖創作」的插值函式。
* @param {BasicLayout} renderData 基本插值資料物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function DrawingRender(renderData, route, session, callback) {
let paintingId = route.substr(9);
let datas = renderData.datas;
// 若使用者有登入
if (renderData.hasLogin) {
// 先尋找使用者資料
User.findOne({"username": renderData.username})
.select("autoSaveEnable tags paintings")
.populate({ path: "paintings", select: { "id": 1 } })
.exec((err, userDocs) => {
if (err) return callback(err, null);
// 先將使用者的自動儲存設定、所有標填上
datas.isAutoSave = userDocs.autoSaveEnable;
datas.userTags = userDocs.tags;
// 若沒有指定圖畫ID將預設的畫作資料填上後回呼。
if (!paintingId) {
datas.painting = defaultPaintingData;
return callback(null, true);
}
// 若該圖畫不屬於使用者的話就回乎錯誤Error_PaintingNotExist。
if (!userDocs.IsPaintingsOwner(paintingId)) {
return callback(Painting.GetError_PaintingNotExist(), null);
}
// 若該圖畫屬於使用者則將目標畫作的基本資料填入然後回呼true。
Painting.GetDrawingPageInfoById(paintingId, (err, paintingInfo) => {
if (err) return callback(err, null);
datas.painting = paintingInfo;
callback(null, true);
});
}
);
}
// 若使用者沒有登入
else {
datas.isAutoSave = false;
datas.userTags = [];
datas.painting = defaultPaintingData;
callback(null, true);
}
}
module.exports.Render = DrawingRender;

View File

@ -0,0 +1,26 @@
const User = require("../mongooseSchemas/User");
/**
* 頁面「編輯個人資料」的插值函式。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function EditPersonalInfoRender(renderData, route, session, callback) {
User.findOne({"username": renderData.username})
.exec((err, userDocs) => {
if (err) return callback(err, null);
if (userDocs) {
renderData.datas = userDocs.personalInfo;
callback(null, true);
}
else {
callback(User.Error_UserNotExist(), null);
}
}
);
}
module.exports.Render = EditPersonalInfoRender;

View File

@ -0,0 +1,27 @@
var User = require("../mongooseSchemas/User");
/**
* 頁面「意見回饋」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function FeedBackRender(renderData, route, session, callback) {
renderData.datas.currentDate = (new Date()).toLocaleDateString();
// 如果使用者沒有登入則設定hasPostFeedback為false並呼叫回呼函式。
if (!renderData.hasLogin) {
renderData.datas.hasPostFeedback = false;
callback(null, true);
return;
}
// 如果使用者有登入則尋找資料庫中指定的使用者資料的「hasPostFeedback」欄位。
User.findOne({ "username": renderData.username }).select("hasPostFeedback").exec(function (err, userDoc) {
if (err) {
callback(err, null);
return;
}
renderData.datas.hasPostFeedback = userDoc.hasPostFeedback;
callback(null, true);
});
}
module.exports.Render = FeedBackRender;

View File

@ -0,0 +1,30 @@
let PaintingSpotlight = require("../mongooseSchemas/PaintingSpotlight");
let Season = require("../mongooseSchemas/Season");
/**
* 頁面「傑作藝廊」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。傳回錯誤訊息或資料插值設定是否成功。
*/
function GalleryRender(renderData, route, session, callback) {
// 先取得傑作藝廊中的精選輯
PaintingSpotlight.GetCarouselInfo("gallery", function (err, carouselInfo) {
if (err || !carouselInfo) {
callback(err, null);
return;
}
renderData.datas.paintings = carouselInfo.paintings;
// 再取得活動相關的訊息
Season.GetGalleryNeedInfo(function (err, seasonsInfo) {
if (err) {
callback(err, null);
}
else {
renderData.datas.seasons = seasonsInfo;
callback(err, true);
}
});
});
}
module.exports.Render = GalleryRender;

View File

@ -0,0 +1,20 @@
var PaintingSpotlight = require("../mongooseSchemas/PaintingSpotlight");
/**
* 取得首頁的插值資料。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function IndexRender(renderData, route, session, callback) {
PaintingSpotlight.GetCarouselInfo("index", function (err, infos) {
if (err) {
callback(err, null);
}
else {
renderData.datas = infos;
callback(null, true);
}
});
}
module.exports.Render = IndexRender;

View File

@ -0,0 +1,12 @@
/**
* 頁面「登入」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function LoginRender(renderData, route, session, callback) {
// 登入頁面目前不需要做任何插值
callback(null, true);
}
module.exports.Render = LoginRender;

View File

@ -0,0 +1,101 @@
/**
* 設定轉跳訊息頁面下的插值處理。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function MessageFormRender(renderData, route, session, callback) {
switch(true) {
case route == "/signupmsg": // 註冊成功的轉跳頁面
renderData.datas.title = "註冊成功!";
renderData.datas.content = "您現在可以用您所註冊的帳號登入了!";
renderData.datas.button1 = "登入";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/login'));";
break;
case route.includes("/homenotexist/"): // 在「個人頁面」下找不到目標使用者時的轉跳頁面
// 確認使用者名稱存在於路徑中
if (route.length > 14) {
let username = route.substr(14); // 取得找不到的「使用者名稱」
renderData.datas.title = "找不到 “" + username + "” 的個人頁面!"
}
else {
renderData.datas.title = "找不到指定使用者的個人頁面!";
}
renderData.datas.content = "很抱歉,您所尋找的使用者並不存在!";
renderData.datas.button1 = "首頁";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/'));";
break;
case route == "/personalinfo_updated": // 在「編輯個人資料」頁面下,成功編輯個人資料後的跳轉提示頁面
renderData.datas.title = "個人資料修改成功!";
renderData.datas.content = "您的個人資料已成功更新!";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
break;
case route == "/send_sitemail_successfully": // 在「撰寫站內訊息」頁面下,成功編輯個人資料後的轉跳頁面。
renderData.datas.title = "站內信發送成功!";
renderData.datas.content = "您的站內信件已成功寄送!";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
break;
case route == "/newpw_success": // 在「更變密碼」頁面下,成功更改密碼後的轉跳頁面。
renderData.datas.title = "密碼更改成功!";
renderData.datas.content = "您的密碼已經更改成功!";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
break;
case route == "/painting_finished": // 在「繪圖創作」頁面下,成功完成畫作之後的跳轉頁面。
renderData.datas.title = "作品已完成!";
renderData.datas.content = "您的畫作已經完成,您可以返回去欣賞您的畫作!";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
break;
case route == "/painting_not_exist": // 在找不到指定的圖畫作品之下的跳轉頁面
renderData.datas.title = "找不到您的圖畫作品!";
renderData.datas.content = "請確認您所指定的圖畫作品是否所屬於您,或是該作品是否存在。";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
break;
case route == "/painting_deleted": // 成功刪除指定圖畫作品後的跳轉頁面
renderData.datas.title = "圖畫作品刪除成功!";
renderData.datas.content = "您指定的圖畫作品已刪除成功!" + (session.paintingDeleted_Activity ? "請注意,投稿至活動上的圖畫不會被刪除!" : "");
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/home/" + renderData.username + "'));";
// 若有被標記則刪除paintingDeleted_Activity
if (session.paintingDeleted_Activity)
delete session.paintingDeleted_Activity;
break;
case route == "/newtheme/successful": // 當成功處理了「投稿新主題」後的轉跳頁面
renderData.datas.title = "新主題投稿成功!";
renderData.datas.content = "您所投稿的新主題已成功地上傳!請等待最新一季的結果。";
renderData.datas.button1 = "返回首頁";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/'));";
break;
case route == "/newtheme": // 當使用者在「投稿主題」路由上,但卻已經有投稿過主題的轉跳頁面
renderData.datas.title = "您已經投稿過新主題了!";
renderData.datas.content = "請等待下一次的「投稿主題」活動再進行發起主題的動作!。";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.history.back() );";
break;
case route == "/votetheme/success": // 當成功處理了「主題票選」後的轉跳頁面
renderData.datas.title = "候選主題投票成功!";
renderData.datas.content = "您的投票資料已經成功送出!請等待下一次的主題投票。";
renderData.datas.button1 = "返回首頁";
renderData.datas.script = "$('#btnAction1').on('click', () => window.location.replace('/'));";
break;
case route == "/votetheme": // 當使用者在「主題票選」路由上,但卻已經有票選過主題之後的跳轉頁面
renderData.datas.title = "您已經對候選主題票選過了!";
renderData.datas.content = "請等待下一次的「主題票選」活動再進行主題票選的動作!";
renderData.datas.button1 = "返回";
renderData.datas.script = "$('#btnAction1').on('click', () => window.history.back() );";
break;
default: // 其他未定義的伺服器訊息
return callback(new Error("未定義的對應伺服訊息插值資料。"), null);
}
return callback(false, true);
}
module.exports.Render = MessageFormRender;

View File

@ -0,0 +1,89 @@
const User = require("../mongooseSchemas/User");
const Painting = require("../mongooseSchemas/Painting");
const SiteMail = require("../mongooseSchemas/SiteMail");
/**
* 取得「個人頁面」的插值資料。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function PersonalPageRender(renderData, route, session, callback) {
let paramUsername = route.substr(6); // 取得路徑中所選的目標個人頁面的使用者名稱
// 初步地檢查是否為合法的使用者名稱長度。若是,則進一步地去查詢相關資料
if (4 <= paramUsername.length && paramUsername.length <= 16) {
// 定義Populate Query : 套用的有paintings, siteMsg, siteMail 與 friendList
let populateQuery = [
{ path: "paintings", select: { "id": 1, "name": 1, "links": 1, "description": 1, "viewAuthority": 1, "tags": 1 } },
{ path: "friendList", select: { "username": 1 } },
{ path: "siteMsg.refId" },
{ path: "siteMail" }
];
// 嘗試尋找指定的使用者並做populate來取得相關連的資料
User.findOne({"username": paramUsername})
.populate(populateQuery)
.exec((err, userDocs) => {
// 如果找到使用者的話,將需要的資料填入插值物件中
if (userDocs) {
// 定義 datas 物件,並將一些不需要過濾的資料先加入
let datas = {
isOwner: (renderData.username == userDocs.username), // 若目前正瀏覽的使用者與目標使用者的相同,則為這個個人頁面的擁有者(true),反之為否(false)
username: userDocs.username, // 使用者名稱
nickname: userDocs.personalInfo.nickname, // 暱稱
motto: userDocs.personalInfo.motto, // 短言
userPhotoURL: userDocs.personalInfo.photo, // 個人相片 (連結路徑)
autoSaveEnable: userDocs.autoSaveEnable, // 自動儲存
userTags: userDocs.tags, // 作品標籤
friendList: [], // 好友清單
siteMsg: [], // 網站訊息
paintings: [], // 作品集
siteMail: null // 站內信
};
// 循環將 friendList 加入 datas 中
for (let i = 0, list = userDocs.friendList; i < list.length; i++)
datas.friendList.push(list[i].username);
// 循環將 siteMail 加入 datas 中
for (let list = userDocs.siteMsg, i = list.length - 1; i >= 0; i--) {
// 若為伺服訊息,則引用連接的伺服訊息資料
if (list[i].isServerMessage) {
datas.siteMsg.push({ title: list[i].refId.title, content: list[i].refId.content });
}
else {
datas.siteMsg.push({ title: list[i].title, content: list[i].content });
}
}
datas.siteMail = userDocs.siteMail; // 將 siteMail 加入 datas 中
// 循環將 paintings 加入 datas 中
let isFriend = userDocs.IsUsersFriend(session.passport.user); // 取得目前使用者對目標使用者而言的權限
for (let i = 0, list = userDocs.paintings; i < list.length; i++) {
// 若 觀看權限為「公開」 或 使用者為目標使用者的朋友且觀看權限為「半公開」 或 該使用者即為擁有者,則將幅畫資訊加入
if (list[i].viewAuthority == 0 || isFriend && list[i].viewAuthority == 1 || datas.isOwner) {
datas.paintings.push(list[i]);
}
}
renderData.datas = datas; // 最後將 datas 複寫至 renderData.datas
callback(null, true);
}
// 若沒有找到,則將錯誤「找不到目標使用者」回呼至上層路由
else {
callback(User.Error_UserNotExist(), null);
}
}
);
}
// 如果不為合法的使用者名稱長度,則將錯誤「找不到目標使用者」回呼至上層路由。
else {
callback(User.Error_UserNotExist(), null);
}
}
module.exports.Render = PersonalPageRender;

View File

@ -0,0 +1,166 @@
const User = require("../mongooseSchemas/User");
const Painting = require("../mongooseSchemas/Painting");
const ParticipantInfo = require("../mongooseSchemas/ParticipantInfo");
/**
* 以主使用者與被檢查的使用者之間的關係,取得觀看權限數值。
* @param {User} mainUser 主要使用者的資料。
* @param {string} otherUser_id 目標要查詢的使用者_id。
* @return {number} 觀看權限數值。
*/
function GetAuthorityNumber(mainUser, otherUser_id) {
if (mainUser._id.equals(otherUser_id)) { // 若mainUser與otherUser為同一人則回傳2。
return 2;
}
else if (mainUser.IsUsersFriend(otherUser_id)) { // 若otherUser為mainUser的好友則回傳1。
return 1;
}
else { // 若不為好友則回傳0。
return 0;
}
}
/**
* @typedef PaintingRange
* @prop {number} n 表示第n個區間。
* @prop {string[]} range_id 表示在第n個區間中的圖畫Id。
*/
/**
* 取得目標要尋找的圖畫Id座落的區間與該區間中第一個圖畫Id。
* @param {Painting[]} list 全域的資料。為圖畫陣列。
* @param {string} id 要尋找座落在哪區間的資料。為圖畫Id。
* @param {number} viewAutho 觀看權限數值。
* @return {PaintingRange} 回傳第N個區間整數與該區間中的第一個圖畫Id。為 {n, range_id}。
*/
function GetRangeIndex(list, id, viewAutho) {
let viewList = list.filter((docs => docs.viewAuthority <= viewAutho)); // 用「訪問權限」與觀看權限數值,過濾原本的畫作清單,成新的畫作清單。
let length = viewList.length; // 取得清單內容長度
// 尋找目標畫作在清單中的索引位置
for (let i = 0; i < length; i++) {
if (list[i].id == id) {
// 以十個畫作為一組取得目前在第n組
let n = Math.floor(i / 10);
// 回傳n與第n組中 (n * 10) ~ (n * 10 + 9) 之間的畫作_id清單。
return { n: n, range_id: list.slice(n * 10, n * 10 + 10).filter(docs => docs._id) };
}
}
// 若沒找到則回傳null。
return null;
}
/**
* 為「個人藝廊」模式下的插值方法。
* @param {BasicLayout} renderData 基本差值物件。
* @param {string[]} params 路由路徑中的每項參數。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function PersonalShowcaseRender(renderData, params, session, callback) {
let username = params[2]; // 此個人藝廊的使用者
let paintingId = params[3]; // 指定要瀏覽的畫作Id
let tag = params[4]; // 指定畫作的標籤群組(可有可無)
let datas = renderData.datas;
datas.isActivity = false;
datas.themeTitle = null;
datas.themeOriginator = null;
datas.artist = username;
datas.tag = tag;
datas.paintings = []; // 建立畫作清單欄位
// 建立 Populate Query
let populateQuery = {
path: "paintings",
select: { "id": 1, "viewAuthority": 1 },
match: { "isLocked": false }
};
// 如果有指定tag則將其加入到populateQuery之中作為條件
// 比對每個畫作的標籤中是否有包含tag。
if (tag)
populateQuery.match.tags = tag;
// 以username尋找目標使用者的資料其中選取paintings欄位然後再以populateQuery做畫作連結
User.findOne({"username": username})
.select("personalInfo.photo paintings friendList")
.populate(populateQuery)
.exec((err, userDocs) => {
if (err) callback(err, null);
if (!userDocs) callback(User.Error_UserNotExist(), null); // 若找不到使用者,則帶著相對應的錯誤回呼
let ownersPhotoURL = userDocs.personalInfo.photo; // 擁有此展示藝廊的人的頭像照片
let paintingList = userDocs.paintings; // 取得畫作Id清單
let viewAuth = GetAuthorityNumber(userDocs, session.passport.user); // 取得觀看權限數值
let rangeInfo = GetRangeIndex(paintingList, paintingId, viewAuth); // 取得區間資料
// 如果目標畫作不在清單之中的話,則回呼錯誤 **仍要再改
if (!rangeInfo) return callback(new Error("找不到對應的畫作。"), null);
// 以區間尋找畫作資訊
Painting.find({ "_id": { $in: rangeInfo.range_id } })
.populate([{path: "ratings"}, {path: "comments"}])
.exec((err, paintingDocs) => {
if (err) callback(err, null);
// 循每一個畫作資料,取其中的欄位資料加入至 datas.paintings 中
paintingDocs.forEach((docs) => {
datas.paintings.push({
id: docs.id,
links: docs.links,
name: docs.name,
description: docs.description,
artistInfo: { name: username, photoURL: ownersPhotoURL},
totalScore: docs.totalScore,
userScore: docs.FindRatingScoreByUsername(renderData.username),
comments: docs.comments
});
});
// 最後,取得當前使用者的個人照片
User.findOne({"_id": session.passport.user}, "personalInfo.photo", (err, guestUserDocs) => {
if (err) return callback(err, null);
if (!guestUserDocs) return callback(User.Error_UserNotExist(), null);
datas.photoURL = guestUserDocs.personalInfo.photo; // 取得當前使用者的個人照片
callback(null, true);
});
}
);
}
);
}
/**
* 為「活動藝廊」模式下的差值方法。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string[]} params 路由路徑中的每項參數。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function ActivityShowcaseRender(renderData, params, session, callback) {
}
/**
*
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function ShowcaseRender(renderData, route, session, callback) {
let params = route.split("/").slice(1);
switch(params[1]) {
case "personal":
PersonalShowcaseRender(renderData, params, session, callback);
break;
case "activity":
ActivityShowcaseRender(renderData, params, session, callback);
break;
default:
callback(new Error("未定義的展示藝廊模式。"), null);
}
}
module.exports.Render = ShowcaseRender;

View File

@ -0,0 +1,12 @@
/**
* 頁面「註冊」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function SignUpRender(renderData, route, session, callback) {
// 登入頁面目前不需要做任何插值
callback(null, true);
}
module.exports.Render = SignUpRender;

View File

@ -0,0 +1,14 @@
/**
* 以基本插值資料、路由路徑做資料源設定在submit_theme頁面下該插入什麼資料值。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function SubmitThemeRender(renderData, route, session, callback) {
// 此頁面不需要任何插值資料
callback(null, true);
}
module.exports.Render = SubmitThemeRender;

View File

@ -0,0 +1,21 @@
var Season = require("../mongooseSchemas/Season");
/**
* 頁面「畫作主題」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function ThemeRender(renderData, route, session, callback) {
// 先取得「畫作主題」頁面所需要的季資訊
// 若出現錯誤則回呼錯誤訊息若取得成功則回呼true已表示成功。
Season.GetThemePageNeedInfo(function (err, seasonDatas) {
if (err) {
callback(err, null);
return;
}
renderData.datas = seasonDatas;
callback(null, true);
});
}
module.exports.Render = ThemeRender;

View File

@ -0,0 +1,42 @@
const User : any = require("../mongooseSchemas/User");
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性與會用到的屬性。
*/
interface BasicLayout {datas: any, hasLogin: boolean, username: string}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* 頁面「意見回饋」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function FeedBackRender(renderData: BasicLayout, callback: CallbackFunction) : void
{
renderData.datas.currentDate = (new Date()).toLocaleDateString();
// 如果使用者沒有登入則設定hasPostFeedback為false並呼叫回呼函式。
if (!renderData.hasLogin) {
renderData.datas.hasPostFeedback = false;
callback(null, true);
return;
}
// 如果使用者有登入則尋找資料庫中指定的使用者資料的「hasPostFeedback」欄位。
User.findOne({"username" : renderData.username}).select("hasPostFeedback").exec((err, property) => {
if (err) {
callback(err, null);
return;
}
renderData.datas.hasPostFeedback = property;
callback(null, true);
});
}
module.exports.Render = FeedBackRender;

View File

@ -0,0 +1,94 @@
const PaintingSpotlight = require("../mongooseSchemas/PaintingSpotlight");
const Season = require("../mongooseSchemas/Season");
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性
*/
interface BasicLayout {datas: any}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* 繪圖展示上的簡短訊息。
* @prop {string} links 此畫作的圖片連結。
* @prop {string} name 此畫作的名稱。
* @prop {string} description 此畫作的敘述。
* @prop {string} artist 畫作的作者。
*/
interface PaintingInfo {
links : string
name : string,
description : string,
artist : string
}
/**
* 表示畫作的參賽相關訊息。
* @prop {number} rank 此畫作的名次。
* @prop {string} artist 畫作的作者。
* @prop {string} paintingName 畫作的名稱。
* @prop {Date} postTime 此畫作的參賽時間。
*/
interface ParticipantInfo {
rank : number,
artist : string,
paintingName : string,
postTime : Date
}
/**
* 有關主題的相關訊息。
* @prop {number} order 主題與主題之間的識別號碼(用於版面先後排序用)。
* @prop {string} title 主題的標題。
* @prop {ParticipantInfo[]} participants 此主題的所有參賽畫作資訊。
*/
interface ThemeInfo {
order : number,
title : string,
participants : ParticipantInfo[]
}
/**
* 有關一季活動之中的相關訊息。
* @prop {number} nth 表示目前是第nth季
* @prop {ThemeInfo[]} 儲存這一季之中所有的活動。
*/
interface SeasonInfo {
nth : number,
themes : ThemeInfo[]
}
/**
* 頁面「傑作藝廊」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {CallbackFunction} callback 回呼函式。傳回錯誤訊息或資料插值設定是否成功。
*/
function GalleryRender(renderData : BasicLayout, callback : CallbackFunction) : void {
// 先取得傑作藝廊中的精選輯
PaintingSpotlight.GetCarouselInfo("gallery", (err, carouselInfo) => {
if (err || !carouselInfo) {
callback(err, null);
return;
}
renderData.datas.paintings = carouselInfo.paintings;
// 再取得活動相關的訊息
Season.GetGalleryNeedInfo((err, seasonsInfo) => {
if (err){
callback(err, null);
}
else {
renderData.datas.seasons = seasonsInfo;
callback(err, true);
}
});
});
}
module.exports.Render = GalleryRender;

View File

@ -0,0 +1,50 @@
let PaintingSpotlight = require("../mongooseSchemas/PaintingSpotlight");
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性。
*/
interface BasicLayout {datas: any}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* Painting Information Interface
*/
interface PaintingInfo {
links : string
name : string,
description : string,
artist : string
}
/**
* Data layout of index.
*/
interface IndexLayout {
paintings : PaintingInfo[]
}
/**
* 取得首頁的插值資料。
* @param {BasicLayout} renderData 插值物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function IndexRender(renderData : BasicLayout, callback : CallbackFunction) : void {
PaintingSpotlight.GetCarouselInfo("index", (err, infos) => {
if (err) {
callback(err, null);
}
else {
renderData.datas = infos;
callback(null, true);
}
});
}
module.exports.Render = IndexRender;

View File

@ -0,0 +1,26 @@
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性。
*/
interface BasicLayout {datas: any}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* 頁面「登入」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function LoginRender(renderData: BasicLayout, callback: CallbackFunction) : void
{
// 登入頁面目前不需要做任何插值
callback(null, true);
}
module.exports.Render = LoginRender;

View File

@ -0,0 +1,26 @@
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性。
*/
interface BasicLayout {datas: any}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* 頁面「註冊」的插值函式。
* @param {BasicLayout} renderData 基本插值物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function SignUpRender(renderData: BasicLayout, callback: CallbackFunction) : void
{
// 登入頁面目前不需要做任何插值
callback(null, true);
}
module.exports.Render = SignUpRender;

View File

@ -0,0 +1,70 @@
const Season : any = require("../mongooseSchemas/Season");
/**
* 由DataRender所定義的基本差值物件。這裡僅列出必要的datas屬性
*/
interface BasicLayout {datas: any}
/**
* A Callback function.
* @callback CallbackFunction
* @param {Object} err 錯誤資訊物件。
* @param {Object} obj 成功時所回傳的物件。
*/
interface CallbackFunction { (err: Object, obj: Object) : void }
/**
* 有關主題的相關訊息。
* @prop {number} order 主題與主題之間的識別號碼(用於版面先後排序用)。
* @prop {string} title 主題的標題。
* @prop {string} narrative 主題的敘述。
* @prop {string} imageURL 主題縮圖連結。
* @prop {string} originator 主題發起人。
* @prop {number} participantCount 投稿人數。
* @prop {number} views 瀏覽此主題的人次數。
* @prop {number} commentCount 主題中相關留言人數。
*/
interface ThemeInfo {
order : number,
title : string,
narrative : string,
imageURL : string,
originator : string,
participantCount : number,
views : number,
commentCount : number
}
/**
* 有關一季活動之中的相關訊息。
* @prop {number} nth 表示目前是第nth季
* @prop {Date} endTime 表示該季的結束時間。在資料取得中只有上一季有這個欄位
* @prop {ThemeInfo[]} 儲存這一季之中所有的活動。
*/
interface SeasonInfo {
nth : number,
endTime : Date,
themes : ThemeInfo[]
}
/**
* 頁面「畫作主題」的插值函式。
* @param renderData 基本插值物件。
* @param callback 回呼函式。
*/
function ThemeRender(renderData: BasicLayout, callback: CallbackFunction) : void
{
// 先取得「畫作主題」頁面所需要的季資訊
// 若出現錯誤則回呼錯誤訊息若取得成功則回呼true已表示成功。
Season.GetThemePageNeedInfo((err, seasonDatas) => {
if (err) {
callback(err, null);
return;
}
renderData.datas = seasonDatas;
callback(null, true);
});
}
module.exports.Render = ThemeRender;

View File

@ -0,0 +1,44 @@
const NewTheme = require("../mongooseSchemas/NewTheme");
const ServerStatus = require("../../ServerStatus");
/**
* 以基本插值資料、路由路徑做資料源設定在write_message頁面下該插入什麼資料值。
* @param {BasicLayout} renderData 基本插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function VoteThemeRenderer(renderData, route, session, callback) {
let status = ServerStatus.status; // 取得伺服器的狀態資料
let datas = renderData.datas;
datas.title = "第" + (status.currentSeason + 1) + "季主題票選";
datas.voteCount = status.voteCount; // 取得在主題候選之中,使用者的手中有多少票
// 將候選主題全部找出,並依建立時間來進行排列
NewTheme.find({})
.sort({ "createdTime": 1 })
.exec((err, newThemeDocs) => {
if (err) return callback(err, null);
// 將所有頁面所要的候選主題資料加入到 themes 中
let themes = [];
newThemeDocs.forEach((docs, index) => {
themes.push({
id: index,
title: docs.title,
narrative: docs.narrative,
imageURL: docs.image,
originator: docs.sponsor
});
});
// 隨後再將 themes 加入到 datas 之上
datas.themes = themes;
callback(null, true);
}
);
}
module.exports.Render = VoteThemeRenderer;

View File

@ -0,0 +1,35 @@
let User = require("../mongooseSchemas/User");
/**
* 以基本插值資料、路由路徑做資料源設定在write_message頁面下該插入什麼資料值。
* @param {BasicLayout} renderData 插值物件。
* @param {string} route 路由路徑。
* @param {Express.Session} session Express的Session物件。
* @param {CallbackFunction} callback 回呼函式。
*/
function WriteMessageRender(renderData, route, session, callback) {
let populateQuery = { path: "friendList", select: { "username": 1 } }; // 建立Populate Query連結好友清單中的「Username」欄位。
let recipient = route.substr(15); // 取得指定的使用者
// 尋找目前使用者的好友清單內的所有好友的使用者名稱
User.findOne({"username": renderData.username})
.populate(populateQuery)
.exec((err, docs) => {
// 若有錯誤,則將錯誤回呼並返回
if (err) return callback(err);
// 將收件者加入插值物件中
renderData.datas.recipient = recipient;
// 循環取得好友清單所有的使用者名稱
let friendsUsernames = [];
for (let friend of docs.friendList)
friendsUsernames.push(friend.username);
renderData.datas.friendList = friendsUsernames;
callback(null, true);
}
);
}
module.exports.Render = WriteMessageRender;