const fileSystem = require("fs"); const mongoose = require("mongoose"); const ServerStatus = require("../ServerStatus"); const COUT = process.stdout; /** 集合所有的資料庫模板資料。 */ const DBModels = { "User": require("./mongooseSchemas/User"), "Painting": require("./mongooseSchemas/Painting"), "Season": require("./mongooseSchemas/Season"), "Theme": require("./mongooseSchemas/Theme"), "Participation": require("./mongooseSchemas/Participation"), "ParticipantInfo": require("./mongooseSchemas/ParticipantInfo"), "SiteMail": require("./mongooseSchemas/SiteMail"), "SiteMessage": require("./mongooseSchemas/SiteMessage"), "Rating": require("./mongooseSchemas/Rating"), "Comment": require("./mongooseSchemas/Comment"), "NewTheme": require("./mongooseSchemas/NewTheme"), "PaintingSpotlight": require("./mongooseSchemas/PaintingSpotlight"), "ServerMessage": require("./mongooseSchemas/ServerMessage") }; /** 指令字串切割方式 */ const cmdSpliter = / +/g; /** 目前正用於處理指令輸入操作的函式。 */ let CurrentOperationFunc = CommandRoutes; /** 承接頂層的LineReader之指令處理函式。 */ function MiddleHandler(line) { CurrentOperationFunc(line); } /** 關閉、離開伺服器運行的方法函式。 */ let ExitServerFunction; /** * 初始化「指令路由」的初始化函式。 * @param {Function} ExitServFunc 停止伺服器運作的函式。此方法由外部傳入。 */ function Init(ExitServFunc) { ExitServerFunction = ExitServFunc; return MiddleHandler; } /** * 指令路由函式。 * @param {string} line 從標準文字輸入所接收到的一行輸入字串。 */ function CommandRoutes(line) { let query = line.split(cmdSpliter); // 以空格來切割每一個指令 switch(query[0]) { case ".exit": ExitServerFunction(); break; case "showstatus": ShowServerStatus(); break; case "act": ActivityFunction(query); break; case "servmsg": ServMessageFunction(query); break; case "db": DatabaseFunctions(line, query); break; case "restore": RestoreServer(); break; case "help": HelpInformation(); break; default: console.log("\n錯誤: 找不到指令或巨集。\n"); } } //#region ==================== Show Status Operation Command ========================= /** * 顯示目前伺服器的狀態。 */ function ShowServerStatus() { let status = ServerStatus.status; if (status) { let info = "目前舉行的活動為第 " + status.currentSeason + " 季\n" + "主題投稿活動: " + ( status.submitThemeEvent ? "運行中\n" : "未運行\n" ) + "主題投票活動: " + ( status.voteThemeEvent ? "運行中\n" : "未運行\n" ) + "在主題投票活動中,每位使用者能夠投選" + status.voteCount + "個候選主題\n" + "在主題投票活動完成之後,會選出前" + status.promotionCount + "高票個候選主題作為新一季的主題\n" + "自動活動排程系統目前" + ( status.onSchedule ? "正在運行中\n" : "停止使用中\n" ) + "伺服狀態自動儲存: " + ( status.autoSaveStatus ? "開啟\n" : "關閉\n" ); console.log("\n==================== 伺服器狀態 ===================="); console.log(info); } else { console.log("\nPlease wait for loading server status file.\n"); } } //#endregion ========================================================================= //#region ==================== Activity Operation Commands ==================== /** * 與藝廊活動有關的相關操作函式。 * @param {string[]} query 解析後的指令片段。 */ function ActivityFunction(query) { let operation = query[1]; switch(operation) { case "status": Act_Status(); break; case "push": Act_Push(); break; case "help": Act_Help(); break; default: console.log("\n錯誤: 找不到指令或巨集。\n"); } } /** * 顯示目前活動的狀態。 */ function Act_Status() { let status = ServerStatus.status; let info = "\n目前舉行的活動為第 " + status.currentSeason + " 季\n"; // 若「投稿主題」正在進行中 if (status.submitThemeEvent) { info += "當前正為下一季的活動做準備,正舉行著「投稿主題」中。\n"; } // 若「票選主題」正在進行中 else if (status.voteThemeEvent) { info += "當前正為下一季的活動做準備,正舉行著「投稿主題」中。\n"; } info += "目前的活動規劃" + (status.onSchedule ? "由排程系統自動進行中。\n" : "為手動操作。\n"); console.log(info); } /** * 以手動的方式推進活動狀態。 * 若活動排程系統為啟用的狀態,則這個操作將會失效。 * @param {string[]} query 解析後的指令片段。 */ function Act_Push(query) { let status = ServerStatus.status; // 若排程系統是啟用的,則印出訊息。 if (status.onSchedule) { console.log("\n目前的活動規劃由排程系統管理中,無法做手動操作的部分。"); console.log("若想要進行「活動階段推進」的動作,請先將排程系統關閉。\n"); return; } // 若排程系統是關閉的,則先確認是否要執行活動狀態推進的動作。 function AskPush(line) { if (line == "Y" || line == "y") { // 若使用者確認,則將處理文字輸入的主函式改回,並進行推進的動作 CurrentOperationFunc = CommandRoutes; PushActivity(); } else if (line == "N" || line == "n") { // 若使用者確認,則將處理文字輸入的主函式改回,並進行推進的動作 CurrentOperationFunc = CommandRoutes; console.log(); } else { console.log("\n請輸入Y或N來決定是否要推進狀態: \n"); } } let curSeason = status.currentSeason; // 取得目前的季數 COUT.write("\n目前的活動狀態為"); // 依不同的狀態印出訊息 switch(true) { case status.submitThemeEvent: // 若目前「投稿主題」活動正在進行 COUT.write("「投稿主題」。\n"); console.log("若要推進,則活動狀態會到第" + curSeason + "季與第" + (curSeason + 1) + "季的「主題票選」活動。\n"); break; case status.voteThemeEvent: // 若目前「主題票選」活動正在進行 COUT.write("「主題票選」。\n"); console.log("若要推進,則活動狀態會到第" + (curSeason + 1) + "季,也就是更新到全新的一季。\n"); break; default: // 若目前僅進行一般的季活動 COUT.write("「一般活動」。\n"); console.log("若要推進,則活動狀態會到第" + curSeason + "季與第" + (curSeason + 1) + "季的「投稿主題」活動。\n"); } console.log("\n請問是否要推進? (Y/N) \n"); CurrentOperationFunc = AskPush; // 將處理文字輸入的主函式改為AskPush,也就是詢問是否要推進。 } //#region Functions that Act_Push use. /** * 實作「活動階段推進」的程式動作。 (順序 1) */ function PushActivity() { let status = ServerStatus.status; // 取得伺服器狀態物件 // 若目前「投稿主題」活動正在進行 if (status.submitThemeEvent) { status.submitThemeEvent = false; status.voteThemeEvent = true; // 刷新使用者資料中的 hasVotedNewTheme 欄位,將其轉變為false。 DBModels.User.Refresh_HasVotedNewTheme((err) => { if (err) return console.log("\n設定使用者資料的時候發生了錯誤。請確認是否有與MongoDB連線。\n"); // 詢問並設定每一位使用者投票的票數 AskPromotionTheme(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason); }); } // 若目前「主題票選」活動正在進行 else if (status.voteThemeEvent) { status.voteThemeEvent = false; status.currentSeason += 1; // 將新的一季活動做推進 DBModels.Season.PushNewSeason((err, result) => { if (err) return console.log(result); AskBroadcastServerMessage(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason); }); } // 若目前僅一般主題在進行 else { status.submitThemeEvent = true; // 刷新使用者資料中的 hasPostNewTheme 欄位,將其轉變為false。 DBModels.User.Refresh_HasPostNewTheme((err) => { if (err) return console.log("\n設定使用者資料的時候發生了錯誤。請確認是否有與MongoDB連線。\n"); // 詢問是否進行伺服訊息廣播 AskBroadcastServerMessage(status.submitThemeEvent, status.voteThemeEvent, status.currentSeason); }); } } /** * 詢問使用者要選取NewTheme中前幾個主題做為最新一季的主題。 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。 * @param {number} curSeason 目前最新的季。 */ function AskPromotionTheme(submitTheme, voteTheme, curSeason) { let validator = str => /^([1-9]\d*)$/.test(str); let ask = "\n在此次主題票選當中,在最後要選取多少候選主題出來作為新一季的主題: "; /** 處理使用者的回應 */ function HandleResponse(line) { // 檢查使用者輸入的數值是否為大於0的正整數 if (!validator(line)) { COUT.write("請輸入大於0的正整數、小於等於總候選主題數的數字: "); return; } // 檢查使用者輸入的數字是否小於等於候選主題的總數 let count = parseInt(line); DBModels.NewTheme.count({}, (err, number) => { if (err) { console.log("\n檢查候選主題總數時發生了錯誤。請檢查MongoDB是否開啟或稍後重新操作。\n"); CurrentOperationFunc = CommandRoutes; return; } // 若有小於等於候選主題總數,則進行下一步「詢問是否要對所有使用者廣播訊息」 if (count <= number) { ServerStatus.status.promotionCount = count; // 將數量記在伺服狀態的promotionCount中 AskBroadcastServerMessage(submitTheme, voteTheme, curSeason); } else { COUT.write("請輸入大於0的正整數、小於等於總候選主題數的數字: "); } }); } COUT.write(ask); CurrentOperationFunc = HandleResponse; } /** * 詢問是否要進行伺服器的訊息廣播。 (順序 2) * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。 * @param {number} curSeason 目前最新的季。 */ function AskBroadcastServerMessage(submitTheme, voteTheme, curSeason) { let ask = "是否要對個使用者做新活動的訊息廣播? (Y/N) "; let title, content; COUT.write(ask); /** 定義處理使用者輸入訊息標題的函式 (順序 3.A.1) */ function HandleTitleResponse(line) { title = line; console.log("請輸入訊息內容:"); CurrentOperationFunc = HandleContentResponse; } /** 定義處理使用者輸入訊息內容的函式 (順序 3.A.2) */ function HandleContentResponse(line) { content = line; COUT.write("確認廣播此伺服訊息? (Y/N) "); CurrentOperationFunc = ConfirmServerMessage; } /** 定義處理使用者是否使用預設的訊息內容 (順序 3.B.1) */ function DefaultResponse(line) { if (line == "Y" || line == "y") { // 以資料title、content來對所有使用者進行廣播 BroadcastServerMessage(title, content, (err, result) => { if (err) return console.log("\n" + result + "\n"); // 依照不同狀態輸出成功訊息 console.log("\n已成功地將最新的活動訊息廣播給所有使用者!"); ShowFinishMessage(submitTheme, voteTheme, curSeason); // 最後將主要處理文字輸入的方法設定回主指令路由函式中 CurrentOperationFunc = CommandRoutes; }); } else if (line == "N" || line == "n") { // 如果使用者拒絕使用預設的伺服器廣播訊息,則輸出最終訊息,並將處理文字輸入的變數函式指回主處理函式 ShowFinishMessage(submitTheme, voteTheme, curSeason); CurrentOperationFunc = CommandRoutes; } else { COUT.write("請問是否要使用預設的廣播訊息內容? (Y/N) "); } } /** 確認使用者是否要廣播此伺服訊息 (順序 3.A.3) */ function ConfirmServerMessage(line) { if (line == "Y" || line == "y") { // 以資料title、content來對所有使用者進行廣播 BroadcastServerMessage(title, content, (err, result) => { if (err) return console.log("\n" + result + "\n"); // 依照不同狀態輸出成功訊息 console.log("\n已成功地將最新的活動訊息廣播給所有使用者!"); ShowFinishMessage(submitTheme, voteTheme, curSeason); // 最後將主要處理文字輸入的方法設定回主指令路由函式中 CurrentOperationFunc = CommandRoutes; }); } else if (line == "N" || line == "n") { // 如果選擇不廣播伺服訊息,則再一次詢問「是否要做活動訓席廣播」 AskBroadcastServerMessage(submitTheme, voteTheme, curSeason); } else { COUT.write("確認廣播此伺服訊息? (Y/N) "); } } /** 處理回應 (順序 3) */ function HandleResponse(line) { // 若決定要對使用者進行廣播,則先詢問廣播訊息的標題 if (line == "Y" || line == "y") { console.log("請輸入訊息標題:"); CurrentOperationFunc = HandleTitleResponse; } // 若沒有決定要自行廣播,則再詢問是否要進行預設的伺服器訊息廣播 else if (line == "N" || line == "n") { [title, content] = GetDefaultActivityServMsg(submitTheme, voteTheme, curSeason); console.log("\n預設的廣播格式為:"); console.log("標題: %s", title); console.log("內容: %s\n", content); COUT.write("請問是否要使用預設的廣播訊息內容? (Y/N) "); CurrentOperationFunc = HandleDefaultResponse; } else { COUT.write(ask); } } // 變更主要處理文字輸入的函式 CurrentOperationFunc = HandleResponse; } /** * 在「活動階段推進」之中,最後完成動作時所顯示的訊息。 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。 * @param {number} curSeason 目前最新的季。 */ function ShowFinishMessage(submitTheme, voteTheme, curSeason) { if (submitTheme) console.log("\n第%d季的「投稿主題」活動已經開始!\n", curSeason + 1); else if (voteTheme) console.log("\n第%d季的「投稿主題」活動已經結束。活動「主題票選」已經開始!\n", curSeason + 1); else console.log("\n第%d季的繪圖投稿活動與第%d季的「主題票選」活動已經結束。\n最新第%d季的活動已經開始!\n", curSeason - 1, curSeason, curSeason); } /** * 取得預設的伺服器廣播訊息。 * @param {boolean} submitTheme 「投稿主題」活動是否為進行中。 * @param {boolean} voteTheme 「主題票選」活動是否為進行中。 * @param {number} curSeason 目前最新的季。 * @return {string[]} 預設的伺服訊息中的 [標題, 內容] 的成對。 */ function GetDefaultActivityServMsg(submitTheme, voteTheme, curSeason) { switch(true) { case submitTheme: return ["第" + (curSeason + 1) + "季的「投稿主題」活動已經開始!", "最新的「主題投稿」活動已經開始了!無論是有創意、有熱情、有特殊想法的作家們,趕緊來發起你們心中所想要的主題吧!人人都有機會在版面上看到自己所投稿的主題噢!"]; case voteTheme: return ["第" + (curSeason + 1) + "季的「主題票選」活動已經開始!", "最新的「主題票選」活動已經開始了!無論你在先前有投稿主題或沒有投稿主題,趕緊來這個活動看看,有沒有你所想要、中意的主題,有的話就把你手中的票朝那主題投下去吧!"]; default: return ["最新一季繪畫活動,第" + curSeason + "季已經開始了!!", "第" + curSeason + "季的繪畫活動已經開始了!來看看在這最新的一季當中,有沒有你所喜歡的、所愛的主題,有的話就開始動起你手中的創意、發揮你的天份,來開始畫畫吧!此外,第" + ( curSeason - 1 ) + "季的繪畫活動結果已經出爐了,可以去瞧一瞧是哪幾位大師登上旁行榜吧!"]; } } //#endregion /** * 查詢指令"act"的相關功能與說明。 */ function Act_Help() { console.log("\nact - 與伺服器活動有相關的操作"); console.log("act status : 檢視目前伺服器活動的狀態訊息。"); console.log("act push : 以手動的方式做「活動階段推進」的動作。"); console.log("act help : 查詢指令act的相關操作方式。\n") } /** * 新增、廣播伺服訊息。 * @param {string} title 訊息的標題。 * @param {string} content 訊息的內容。 * @param {CallbackFunction} callback 回呼函式。 */ function BroadcastServerMessage(title, content, callback) { let ServerMessage = DBModels.ServerMessage; let User = DBModels.User; // 建立一個新的伺服訊息資料 ServerMessage.createNewServerMessage({ title, content }, (err, _id) => { if (err) return callback(err, "在建立新的伺服器訊息時發生了錯誤,請改用一般的廣播方式來進行廣播。"); // 透過伺服訊息資料的_id,加入至各使用者的siteMsg中 User.BroadcastServerMessage(_id, (err, result) => { if (err) return callback(err, "在對所有使用者進行廣播動作時發生了錯誤。請更改使用單一或多項指定來傳送伺服訊息。"); callback(null, true); });; }); } //#endregion ====================================================================== //#region ==================== Server Message Operation Commands ==================== /** * 撰寫伺服器訊息相關功能的函式。 * @param {string[]} query 解析後的指令片段。 */ function ServMessageFunction(query) { let operation = query[1]; switch(operation) { case "sendone": WriteServMsg(ServMsg_SendOne, false); break; case "sendmany": WriteServMsg(ServMsg_SendOne, false); break; case "broadcast": WriteServMsg(ServMsg_Broadcast, true); break; case "help": ServMsg_Help(); break; default: console.log("\n錯誤: 找不到指令或巨集。\n"); } } /** * 實行指令"servmsg sendone"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。 * @param {string} title 伺服訊息的標題。 * @param {string} content 伺服訊息的內容。 * @param {boolean} isPrivate 是否為私密訊息。 */ function ServMsg_SendOne(title, content, isPrivate) { let User = DBModels.User; /** 接收輸入的使用者名稱。 */ function InputUsername(username) { // 嘗試取得目標使用者 User.findOne({ "username": username }, (err, userDocs) => { if (err) { console.log("\n尋找目標使用者時發生了錯誤。請確認是否有連接上MongoDB。"); CurrentOperationFunc = CommandRoutes; } else if (!userDocs) { console.log("\n找不到目標使用者,請再輸入一次: (使用者名稱) "); } else { userDocs.AddNewSiteMessage(title, content, isPrivate); userDocs.save((err) => { if (err) { console.log("\n使用者資料儲存失敗。請檢查是否與MongoDB連線正常。\n"); } else { console.log("\n伺服訊息發送成功!\n"); } CurrentOperationFunc = CommandRoutes; }); } }); } console.log("\n請問要發送給哪位使用者? (使用者名稱) ") CurrentOperationFunc = InputUsername; } /** * 實行指令"servmsg sendmany"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。 * @param {string} title 伺服訊息的標題。 * @param {string} content 伺服訊息的內容。 * @param {boolean} isPrivate 是否為私密訊息。 */ function ServMsg_SendMany(title, content, isPrivate) { let User = DBModels.User; /** 處理使用者輸入的使用者名稱。 */ function InputUsername(username) { // 若有輸入文字則進行以下動作 if (username.length > 0) { // 以使用者輸入的 username 尋找目標使用者是否存在 User.findOne({ "username": username }, (err, userDocs) => { if (err) { console.log("\n尋找目標使用者時發生了錯誤。請確認是否有連接上MongoDB。"); CurrentOperationFunc = CommandRoutes; } else if (!userDocs) { console.log("\n找不到目標使用者,請在輸入一次: (輸入使用者名稱,或直接按下Enter來完成動作)"); } else { // 使用者存在,則以title、content與isPrivate新增主頁訊息 userDocs.AddNewSiteMessage(title, content, isPrivate); // 新增完後做儲存動作。 userDocs.save((err) => { if (err) { console.log("\n使用者資料儲存失敗。請檢查是否與MongoDB連線正常。\n"); CurrentOperationFunc = CommandRoutes; } else { console.log("\n伺服訊息發送成功,請繼續輸入? (輸入使用者名稱,或直接按下Enter來完成動作)"); } }); } }); } else { console.log("\n已完成訊息發送動作。\n"); CurrentOperationFunc = CommandRoutes; } } console.log("\n請問要發送給哪位使用者? (輸入使用者名稱,或直接按下Enter來完成動作)"); CurrentOperationFunc = InputUsername; } /** * 實行指令"servmsg broadcast"之指令。此函式必須要透過WriteServMsg函式內部來呼叫。 * @param {string} title 伺服訊息的標題。 * @param {string} content 伺服訊息的內容。 */ function ServMsg_Broadcast(title, content) { let ServerMessage = DBModels.ServerMessage; let User = DBModels.User; // 以title、content建立伺服訊息(廣播訊息) ServerMessage.createNewServerMessage({ title, content }, (err, _id) => { if (err) { console.log("\n建立伺服訊息資料時發生了錯誤,請檢查是否有連接上MongoDB。\n"); CurrentOperationFunc = CommandRoutes; return; } // 定義要加入到各個使用者資料上的主頁訊息資料 let data = { isServerMessage: true, refId: _id, postTime: new Date(), isSeen: false, isPrivate: false }; // 更新所有使用者資料,將資料加入至siteMsg之中。 User.updateMany({}, { $push: { siteMsg: data } }, (err, raw) => { if (err) { console.log("\n將新的伺服訊息更新至所有使用者的資料之中時發生了錯誤,請檢查是否有連接上MongoDB。\n"); } else { console.log("\n您的伺服訊息已成功地廣播給所有使用者了!\n"); } CurrentOperationFunc = CommandRoutes; }); }); } /** * 撰寫完訊息之後所要將標題、內容交給目標函式處理的函式。 * @typedef {Function} WritenMethod * @param {string} title 訊息的標題。 * @param {string} content 訊息的內容。 * @param {boolean} isPrivate 是否為私密訊息。 */ /** * 撰寫伺服器訊息。 * @param {WritenMethod} sendMethod 當伺服訊息完成之後,所要處理標題、內容的函式。 * @param {boolean} isBroadcast 是否為廣播訊息。 */ function WriteServMsg(sendMethod, isBroadcast) { let title = "", content = ""; let isPrivate; /** 撰寫訊息的標題。 */ function WriteTitle(line) { title = line; console.log("\n請在以下轉寫你所要的訊息內容,直到內容中出現「\\0」才會進行下一步驟:"); CurrentOperationFunc = WriteContent; } /** 撰寫訊息內容。 */ function WriteContent(line) { let endIndex = line.search(/\\0/); if (endIndex < 0) { content += line; } else { content += line.substr(0, endIndex); // 若是廣播的話,則跳過觀看權限詢問。 if (isBroadcast) { console.log("\n您的伺服訊息已經撰寫完成,內容如下。"); console.log("標題: %s", title); console.log("內容: %s", content); console.log("* 由於此訊息為廣播訊息,因此全部使用者皆有觀看權限。 *"); console.log("\n以上是否已為你所想要的內容呢? (Y/N) "); CurrentOperationFunc = ConfirmIsDone; } else { console.log("\n此訊息是否僅限使用者本身能看到: (Y/N) "); CurrentOperationFunc = SetIsPrivate; } } } /** 設定此訊息的觀看權限。 */ function SetIsPrivate(line) { if (line == "Y" || line == "y" || line == "N" || line == "n") { isPrivate = (line == "Y" || line == "y"); console.log("\n您的伺服訊息已經撰寫完成,內容如下。"); console.log("標題: %s", title); console.log("內容: %s", content); console.log("僅限擁有者能觀看: %s", isPrivate ? "Yes" : "No"); console.log("\n以上是否已為你所想要的內容呢? (Y/N) "); CurrentOperationFunc = ConfirmIsDone; } else { console.log("\n此訊息是否僅限使用者本身能看到: (Y/N) "); } } /** 確認訊息是否撰寫完成。 */ function ConfirmIsDone(line) { if (line == "Y" || line == "y") { sendMethod(title, content, isPrivate); } else if (line == "N" || line == "n") { console.log("\n是否要再撰寫一份新的伺服訊息呢? (Y/N) "); CurrentOperationFunc = ConfirmAgain; } else { console.log("\n以上是否已為你所想要的內容呢? (Y/N) "); } } /** 已經被回拒,但再次確認是否要再撰寫一次。 */ function ConfirmAgain(line) { if (line == "Y" || line == "y") { content = ""; console.log("請寫上您所想要的伺服訊息標題: "); CurrentOperationFunc = WriteTitle; } else if (line == "N" || line == "n") { console.log(); CurrentOperationFunc = CommandRoutes; } else { console.log("\n以上是否已為你所想要的內容呢? (Y/N) "); } } console.log("\n請寫上您所想要的伺服訊息標題: "); CurrentOperationFunc = WriteTitle; } /** * 查詢伺服器訊息指令"servmsg"的相關操作用法。 */ function ServMsg_Help() { console.log("\nservmsg - 與伺服器傳送訊息有關的操作"); console.log("servmsg sendone : 撰寫伺服器訊息,並將訊息傳送給指定的使用者。"); console.log("servmsg sendmany : 撰寫伺服器訊息,並將訊息傳給選定的多對使用者。"); console.log("servmsg broadcast : 撰寫伺服器訊息,並將訊息廣播給全部的使用者。"); console.log("servmsg help : 查詢此指令的相關操作方式。\n"); } //#endregion ====================================================================== //#region ====================== Database Operation Commands ====================== /** * 有關於資料庫操作的相關指令動作。 * @param {string} original 原始完整的指令碼。 * @param {string[]} query 解析後的指令片段。 */ function DatabaseFunctions(original, query) { let operation = query[1]; switch(operation) { case "find": DBOp_Find(original, query); break; case "user": DBOp_User(query); break; case "help": DBOp_Help(); break; default: console.log("\n錯誤: 找不到指令或巨集。\n"); } } /** * 資料庫指令操作中的「搜尋」。 * @param {string} original 原始完整的指令碼。 * @param {string[]} query 解析後的指令片段。 */ function DBOp_Find(original, query) { let target = query[2]; let condition = original.substr(original.indexOf(query[3])); // 若顯示目標為"schema",則秀出所有的模板名稱。 if (target == "schemas") { console.log("\n=============== 資料庫中所有的模板 ==============="); for (let schemaName in DBModels) { console.log(schemaName); } console.log(); return; } // 若使用者有輸入條件參數,則嘗試轉換成JSON物件;若無,則設定為空JSON物件。 if (query[3] && condition.length > 0) { try { condition = JSON.parse(condition); } catch (err) { return console.log("\n條件參數格式錯誤,請寫下正確的JSON格式。\n"); } } else { condition = {}; } // 再透過目標(target)找出指定的資料表。 let model = DBModels[target]; let index = 0, length; // 若目標的資料集合存在,則繼續下一步 if (model) { // 先計算出目標資料有多少個 model.count(condition, (err, count) => { length = count; // 若數量為0,則印出訊息表示找不到並返回 if (length == 0) { console.log("\n指定尋找的資料不存在。\n"); return; } /** 詢問使否連續觀看資料後的處理。 */ function HandleIsContinue(line) { if (line == "Y" || line == "y") { SearchAndShows(); } else if (line == "N" || line == "n") { CurrentOperationFunc = CommandRoutes; } else { console.log("\n以下還有%s筆資料,是否再向下顯示10筆? (Y/N)\n", length - index); } } /** 循環尋找,並且將資料印出。 */ function SearchAndShows() { model.find(condition) .skip(index) .limit(10) .exec((err, docsList) => { if (err) { console.log("\n搜尋資料庫中的指定資料時發生了錯誤。請檢查是否有與MongoDB連線。\n"); CurrentOperationFunc = CommandRoutes; return; } console.log(); console.log(docsList); // 將資料印出 console.log(); index += 10; // 推移10個量。 // 判斷是否全數顯示完成,若全數顯示完成,則將文字處理介面指回原先的處理函式。 if (index >= length) { CurrentOperationFunc = CommandRoutes; } else { console.log("\n以下還有%s筆資料,請問是否再向下顯示10筆? (Y/N)\n", length - index); CurrentOperationFunc = HandleIsContinue; } } ); } SearchAndShows(); }); } // 若不存在,則印出 else { console.log("\n找不到指定的模板。\n"); } } /** * 與使用者資料操作相關的功能。 * @param {string[]} query 解析後的指令片段。 */ function DBOp_User(query) { let operation = query[2]; switch(operation) { case "list": DBOp_User_List(); break; default: console.log("\n錯誤: 找不到指令或巨集。\n"); } } //#region Commands under "db user" operation. /** * 將所有的使用者名稱以清單的方式列出。 */ function DBOp_User_List() { let User = DBModels.User; // 先取得所有使用者名稱的數量 User.count({}, (err, userCount) => { if (err) return console.log("\n向資料庫發送請求時發生了錯誤。請檢查是否有與MongoDB連線。\n"); let index = 0; /** 是否繼續顯示使用者名稱。 */ function HandleIsContinue(line) { if (line == "Y" || line == "y") { ShowUsernames(); } else if (line == "N" || line == "n") { CurrentOperationFunc = CommandRoutes; } else { console.log("\n還有剩餘的%s筆資料尚未顯示出來,是否繼續向下顯示80筆? (Y/N)"); } } /** 顯示使用者名稱。 */ function ShowUsernames() { User.find({}) .select("username") .skip(index) .limit(80) .exec((err, docsList) => { if (err) { console.log("\n資料庫搜尋資料時發生了錯誤。請檢查是否有與MongoDB連線。\n"); CurrentOperationFunc = CommandRoutes; return; } // 將使用者資料有序的顯示出來 let length = docsList.length, spaceChar = ' ', formatSpace; for (let i = 0; i < length; i++) { formatSpace = 17 - docsList[i].username.length; console.log("%s%s%s", docsList[i].username, spaceChar.repeat(formatSpace), i % 4 == 3 ? "\n" : ""); } // 檢查是否還有剩餘的資料沒有顯示出來 index += 80; if (index >= userCount) { CurrentOperationFunc = CommandRoutes; } else { console.log("\n還有剩餘的%s筆資料尚未顯示出來,是否繼續向下顯示80筆? (Y/N)"); CurrentOperationFunc = HandleIsContinue; } } ); } }); } //#endregion /** * 查詢伺服器訊息指令"db"的相關操作用法。 */ function DBOp_Help() { console.log("\ndb - 與資料庫操作有關的指令"); console.log("db find [condition] : 尋找指定資料表中的資料,或列出所有的資料表名稱。"); console.log("db user : 與使用者資料相關的操作。\n"); console.log("db help : 查詢此指令的相關操作方式。\n"); } //#endregion ====================================================================== //#region ============================= Restore Command ============================= /** * 將整個JMuseum伺服器恢復的最初始、最原本的狀態。 */ function ServerRestoreCommand() { let times = 3; /** 再三地確認是否要恢復JMuseum伺服器。 */ function ConfirmRestore(line) { if (line == "Y" || line == "y") { time -= 1; if (times > 0) { console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤,因此會詢問%s次) (Y/N)", times); } else { RESTORE_SERVER(); } } else if (line == "N" || line == "n") { console.log(); CurrentOperationFunc = CommandRoutes; } else { console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤,因此會詢問%s次)", times); } } console.log("\n是否確定要將JMuseum伺服器恢復到最初始的狀態? (為了確保非為輸入錯誤,因此會詢問%s次)", times); CurrentOperationFunc = ConfirmRestore; } //#endregion ======================================================================== //#region ============================== Help Command ============================== /** * 打印出所有可用的指令種類。 */ function HelpInformation() { console.log("\n所有可用的指令操作種類:"); console.log("showstatus : 查看目前JMuseum伺服器的狀態。"); console.log(" act : 繪圖藝廊活動有關的操作,如查看狀態、推進活動等等。"); console.log(" servmsg : 伺服訊息有關的操作,如對使用者撰寫訊息、發起伺服廣播訊息等等。") console.log(" db : 資料庫操作相關的指令群,如搜尋、刪除、加入等等。"); console.log(" restore : 將JMuseum伺服器回復到最初始的狀態。"); console.log(" help : 查詢指令操作說明。"); console.log(" .exit : 關閉JMuseum伺服器。\n"); } //#endregion ======================================================================= //#region ============================== SERVER RESTORE ============================== /** * 呼叫此函式會還原、回覆整個伺服器狀態與資料庫內的資料。 */ function RESTORE_SERVER() { // 讀取還原預設資料檔案 fileSystem.readFile(global.__dirname + "/db/restore_datas.json", { encoding: "utf8" }, (err, datas) => { if (err) return console.log("\n讀取還原之預設資料時發生了錯誤。請檢查在專案目錄之下的\"/db/restore_datas.json\"是否資料格式正確,或至Github上重新下載一個新的還原預設資料。\n"); let restoreDatas = JSON.parse(datas); // 取得原始資料 // 先將資料庫中所有的資料清除 CLEAR_COLLECTIONS().then(CREATE_RESTORING_DATAS(restoreDatas)) }); } /** * 將資料庫中所有資料清除。 * @return {Promise} 讓外頭的函式可以繼續執行下一步。 */ function CLEAR_COLLECTIONS() { let emptyCondition = {}; /** 發生錯誤的時候的處理。 */ function OnError(error) { console.log("\n清除資料庫中所有資料時發生了錯誤。請檢查是否有與MongoDB連線。\n"); return false; } return DBModels.User.remove(emptyCondition).exec() .then(result => result ? DBModels.Painting.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.Season.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.Theme.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.Participation.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.Comment.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.ParticipantInfo.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.Rating.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.SiteMessage.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.SiteMail.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.ServerMessage.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.PaintingSpotlight.remove(emptyCondition).exec() : false, OnError) .then(result => result ? DBModels.NewTheme.remove(emptyCondition).exec() : false, OnError) .then(result => result ? mongoose.connection.collection("sessions").remove(emptyCondition).exec() : false, OnError) .then(result => result ? Promise.resolve(true) : false , OnError); } /** * 建立回復伺服器的基本資料。 * @param {Object} restoreDatas 回復伺服器時所用的資料。 * @return {Function => Promise} Promise物件。 */ function CREATE_RESTORING_DATAS(restoreDatas) { /** 發生錯誤時的處理函式。 */ function OnError(error) { console.log("\n在建立回復伺服器之基本資料時發生了錯誤。請檢查是否有連接上MongoDB。\n"); return false; } return function (result) { if (!result) return false; return DBModels.User.insertMany(restoreDatas.User) .then(result => result ? DBModels.Painting.insertMany(restoreDatas.Painting) : false, OnError) .then(result => result ? DBModels.Season.insertMany(restoreDatas.Season) : false, OnError) .then(result => result ? DBModels.Theme.insertMany(restoreDatas.Theme) : false, OnError) .then(result => result ? DBModels.ParticipantInfo.insertMany(restoreDatas.ParticipantInfo) : false, OnError) .then(result => result ? DBModels.Participation.insertMany(restoreDatas.Participation) : false, OnError) .then(result => result ? DBModels.PaintingSpotlight.insertMany(restoreDatas.PaintingSpotlight) : false, OnError) .then(result => result ? DBModels.ServerMessage.insertMany(restoreDatas.ServerMessage) : false, OnError) } } /** * 將有關的回復資料做連結。 */ function CONNECT_RELATIVE_DATAS() { let User = DBModels.User, Painting = DBModels.Painting, Season = DBModels.Season; let Theme = DBModels.Theme, ParticipantInfo = DBModels.ParticipantInfo, Participation = DBModels.Participation; let PaintingSpotlight = DBModels.PaintingSpotlight, ServerMessage = DBModels.ServerMessage; let userDocs, paintingDocs, seasonDocs, themeDocs; let participantInfoDocs, participationDocs, serverMessageDocs; /** 當讀取資料庫之資料時發生錯誤的錯誤處理。 */ function OnLoadError(error) { console.log("\n讀取資料庫中的資料時發生錯誤。請檢查是否有連接上MongoDB。\n"); return false; } /** 當資料儲存時所發生的錯誤之錯誤處理。 */ function OnSaveError(error) { console.log("\n將資料回存至資料庫時發生了錯誤。請檢查是否有連接上MongoDB。\n"); } // 將與使用者有關的繪圖、主頁訊息加入其中 DBModels.User.find({}).select("username paintings siteMsg").exec() .then(docs => docs ? (userDocs = docs, DBModels.Painting.find({}).select("artist activity").exec()) : false, OnLoadError) .then(docs => docs ? (paintingDocs = docs, DBModels.Season.findOne({}).select("themes").exec()) : false, OnLoadError) .then(docs => docs ? (seasonDocs = docs, DBModels.Theme.find({}).select("title participants").exec()) : false, OnLoadError) .then(docs => docs ? (themeDocs = docs, DBModels.ParticipantInfo.find({}).select("paintingName").exec()) : false, OnLoadError) .then(docs => docs ? (participantInfoDocs = docs, DBModels.Participation.find({}).select("themeName activityRank").exec()) : false, OnLoadError) .then(docs => docs ? (participationDocs = docs, DBModels.ServerMessage.findOne({}).select("_id").exec()) : false, OnLoadError) .then(docs => docs ? (serverMessageDocs = docs, true) : false, OnLoadError) .then(result => { if (!result) return false; // 建立新的主頁訊息資料,並將其主頁資料加入到使用者資料中 let newSiteMessage = { isServerMessage: true, refId: serverMessageDocs._id, postTime: new Date(), isSeen: true, isPrivate: false }; userDocs.map((docs) => { }); }); } //#endregion ========================================================================= module.exports = Init;