Files
JMuseum/models/mongooseSchemas/Painting.js
2018-02-26 14:09:18 +08:00

392 lines
16 KiB
JavaScript
Raw 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 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);