diff --git a/.env b/.env new file mode 100644 index 0000000..9c6f97e --- /dev/null +++ b/.env @@ -0,0 +1 @@ +PORT=2000 diff --git a/dump.rdb b/dump.rdb new file mode 100644 index 0000000..242bfbc Binary files /dev/null and b/dump.rdb differ diff --git a/package.json b/package.json index b2ba5c0..6982f91 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "jsondiffpatch": "^0.4.1", "moment": "^2.29.1", "mongoose": "^5.9.26", - "nodemon": "^1.19.4" + "nodemon": "^1.19.4", + "redis": "3.1.0" }, "devDependencies": { "eslint": "^6.5.1", diff --git a/src/controllers/article_controller.js b/src/controllers/article_controller.js index 0f3961b..c7bfd82 100644 --- a/src/controllers/article_controller.js +++ b/src/controllers/article_controller.js @@ -12,6 +12,11 @@ const mockController = require('../mock') const mongoose = require('mongoose') const categories = ['政經', '社會', '環境', '運動', '國際', '科技', '生活'] +const Redis = require('redis') + +const redisClient = Redis.createClient() +const DEFAULT_EXPIRATION = 3600 + const isExistedAuthor = (originalAuthors, targetAuthor) => { return originalAuthors.some(author => JSON.stringify(author) === JSON.stringify(targetAuthor)) } @@ -186,22 +191,22 @@ module.exports = { message: '查無搜尋結果' }) } else { - const latestNewsCount = await LatestNews.find({}).sort({ _id: -1 }) - console.log(latestNewsCount) + // const latestNewsCount = await LatestNews.find({}).sort({ _id: -1 }) + // console.log(latestNewsCount) const doc2 = [] - var i = 0 - for (const latestNews of latestNewsCount) { - if (i <= 6) { - try { - const { category, title, viewsCount, _id, tags, lastUpdatedAt, editedCount, blocks } = await Article.findById(latestNews.articleId) - // console.log(await Article.findById(latestNews.articleId)) - doc2.push({ category, title, viewsCount, _id, tags, lastUpdatedAt, editedCount, blocks }) - } catch (error) { - console.log(error) - } - } - i += 1 - } + // var i = 0 + // for (const latestNews of latestNewsCount) { + // if (i <= 6) { + // try { + // const { category, title, viewsCount, _id, tags, lastUpdatedAt, editedCount, blocks } = await Article.findById(latestNews.articleId) + // // console.log(await Article.findById(latestNews.articleId)) + // doc2.push({ category, title, viewsCount, _id, tags, lastUpdatedAt, editedCount, blocks }) + // } catch (error) { + // console.log(error) + // } + // } + // i += 1 + // } res.json({ code: 200, type: 'success', @@ -210,6 +215,97 @@ module.exports = { } }) }, + async getPopularArticle (req, res, next) { + console.log('getting Popular articles') + if (req.query.mode === MODE.DEBUG) { + console.log('[Mock][Articles] use mock data') + mockController.mockArticles(req, res) + return + } + if (req.query.limit === undefined) { + req.query.limit = 6 /// default value + console.log(`default limit value : ${req.query.limit}`) + } + console.log(req.query.limit, ' limit') + const keyword = req.query.q || '' + + const limit = Number(req.query.limit) + console.log('getArticles: ' + keyword + ',' + limit, ' .......') + Article.find( + { + $or: [ + { + title: { + $regex: keyword, + $options: 'i' + } + }, + { + outline: { + $regex: keyword, + $options: 'i' + } + } + ] + }, + null, + { limit: limit, sort: { viewsCount: -1 } } + ).exec(async (err, doc) => { + if (err || doc.length === 0) { + res.status(200).send({ + code: 404, + type: 'success', + message: '查無搜尋結果' + }) + } else { + for (let i = 0; i < doc.length; i++) { + console.log(i, doc[i].title, doc[i]._id, doc[i].viewsCount) + } + res.json({ + code: 200, + type: 'success', + data: [doc] + }) + } + }) + }, + async getArticlesOthers (req, res, next) { + console.log('getting Popular articles') + if (req.query.mode === MODE.DEBUG) { + console.log('[Mock][Articles] use mock data') + mockController.mockArticles(req, res) + return + } + if (req.query.limit === undefined) { + req.query.limit = 6 /// default value + console.log(`default limit value : ${req.query.limit}`) + } + console.log(req.query.limit, ' limit') + const keyword = req.query.q || '' + + const limit = Number(req.query.limit) + console.log('getArticles: ' + keyword + ',' + limit, ' .......') + Article.aggregate( + [{ $sample: { size: 6 } }] + ).exec(async (err, doc) => { + if (err || doc.length === 0) { + res.status(200).send({ + code: 404, + type: 'success', + message: '查無搜尋結果' + }) + } else { + for (let i = 0; i < doc.length; i++) { + console.log(i, doc[i].title, doc[i]._id, doc[i].viewsCount, 'random') + } + res.json({ + code: 200, + type: 'success', + data: [doc] + }) + } + }) + }, searchArticles (req, res, next) { const keyword = req.query.q || '' const checkQueryLimit = Number(req.query.limit) @@ -736,21 +832,163 @@ module.exports = { console.log(error) } }, - async getPopularArticle (req, res, next) { - const latestNewsCount = await LatestNews.find({}).sort({ _id: -1 }) - const doc = [] - var i = 0 - for (const latestNews of latestNewsCount) { - if (i <= 6) { - const { category, title, viewsCount } = Article.findById(latestNews.articleId) - doc.push({ category, title, viewsCount }) + // async getPopularArticle (req, res, next) { + // const latestNewsCount = await LatestNews.find({}).sort({ _id: -1 }) + // const doc = [] + // var i = 0 + // for (const latestNews of latestNewsCount) { + // if (i <= 6) { + // const { category, title, viewsCount } = Article.findById(latestNews.articleId) + // doc.push({ category, title, viewsCount }) + // } + // i += 1 + // } + // res.status(200).send({ + // code: 200, + // type: 'success', + // data: doc + // }) + // }, + async getArticleAuthorsImg (req, res, next) { + console.log('article/getArticleAuthors.....') + try { + const articleId = req.params.id + const authorsData = await getOrSetCache(`ArticleId:${articleId}_AuthorsImg`, async () => { + const doc = await Article.findById(articleId).exec() + const authors = [] + for (const author of doc.authors) { + const { displayName, photoURL, email } = await Utils.firebase.getUserInfoById( + author.uid + ) + authors.push({ uid: author.uid, displayName: displayName, photoURL: photoURL, email: email, isAnomynous: author.isAnonymous }) + } + return authors + }) + + res.status(200).send({ + code: 200, + type: 'success', + data: authorsData, + message: '已成功抓取作者' + }) + } catch (error) { + console.log(error) + } + }, + async putDeleteArticleById (req, res, next) { + try { + const { id } = req.body + const deletedArticle = await Article.findById(id) + if (deletedArticle === undefined) { + return res.status(200).send({ + code: 500, + type: 'error', + message: '文章的ID輸入有誤,請重新查詢' + }) } - i += 1 + + // 刪除 ../models/article.js 的 articleSchema // 還有 blockSchema (增加刪除標記) + deletedArticle.blocks.forEach((block, index) => { + if (block.blockDateTime == null) { + console.log( + 'blockDateTime bug 就算沒有打blockDatetime 也可以發布新block' + ) + block.blockDateTime = Date.now() // default 把 block date time設定為現在 + } + block.isdeleted = true + }) + deletedArticle.isdeleted = true + + await deletedArticle.save() + + const allArticleblocks = await Block.find({ articleId: id }) + + // 刪除 ../models/block.js 的blockSchema // 還有 block 裡面的 revision schema + allArticleblocks.forEach(async (block, blockIndex) => { + block.isdeleted = true + block.revisions.forEach(async (rev, revIndex) => { + rev.isdeleted = true + }) + await block.save() + }) + // 刪除../models/content.js 的 content schema + const allContents = await Content.find({ articleId: id }) + allContents.forEach(async (content, contentIndex) => { + content.isdeleted = true + await content.save() + }) + // latestNews + const allLatest = await LatestNews.find({ articleId: id }) + allLatest.forEach(async (latest, Index) => { + latest.isdeleted = true + await latest.save() + }) + + // version + const allVersion = await Version.find({ articleId: id }) + console.log(allVersion, ' allv') + allVersion.forEach(async (ver, verIndex) => { + ver.versions.forEach((v2, v2Index) => { + v2.blocks.forEach((v3, v3Index) => { + console.log(v3, ` v33 ${v3Index}`) + v3.isdeleted = true + }) + v2.isdeleted = true + }) + ver.isdeleted = true + await ver.save() + }) + return res.status(200).send({ + test: '軟刪除成功' + }) + } catch (error) { + console.log(error) + res.status(200).send({ + code: 500, + type: 'error', + message: error.message + }) + } + }, + async deleteArticleById (req, res, next) { + try { + const { id } = req.body + const article = await Article.findOne({ _id: id }) + await Article.deleteOne({ _id: id }) + + await Block.deleteMany({ articleId: id }) + await Content.deleteMany({ articleId: id }) + await LatestNews.deleteMany({ articleId: id }) + await Version.deleteMany({ articleId: id }) + + return res.status(200).send({ + test: `硬刪除成功 articleid: ${id} ${article.title}` + }) + } catch (error) { + console.log(error) + res.status(200).send({ + code: 500, + type: 'error', + message: error.message + }) } - res.status(200).send({ - code: 200, - type: 'success', - data: doc - }) } } + +function getOrSetCache (key, cb) { + return new Promise((resolve, reject) => { + redisClient.get(key, async (error, data) => { + if (error) { + return reject(error) + } + if (data != null) { + console.log('Cache Hit') + return resolve(JSON.parse(data)) + } + console.log('Cache Miss') + const freshData = await cb() + redisClient.setex(key, DEFAULT_EXPIRATION, JSON.stringify(freshData)) + resolve(freshData) + }) + }) +} diff --git a/src/controllers/auth_controller.js b/src/controllers/auth_controller.js index 1e354dc..7f6005c 100644 --- a/src/controllers/auth_controller.js +++ b/src/controllers/auth_controller.js @@ -143,7 +143,57 @@ const auth = { }) } }, - getArticlesInfo + getArticlesInfo, + async getManyUsersInfo (req, res) { + try { + const { uids } = req.body + const userinfo = [] + for (let i = 0; i < uids.length; i++) { + const uid = uids[i] + const { displayName } = await Utils.firebase.getUserInfoById( + uid + ) + const userRef = firebase.db.collection('articles').doc(uid) + const points = await Utils.firebase.handleGetUserPoints(uid) + const doc = await userRef.get() + const editedList = doc.get('edited') || [] + const viewedList = doc.data().viewed || [] + const subscribedList = doc.get('subscribed') || [] + + const editedArticleIds = editedList.map(edited => edited.articleId) + const viewedArticleIds = viewedList + const subscribedArticleIds = subscribedList.map(subscribed => subscribed.articleId) + // for(let j=0;jh) + const q = handleGetArticlesByArray + const result = await Promise.all([q(editedArticleIds), q(viewedArticleIds), q(subscribedArticleIds)]) + console.log(result[0].length, result[1].length, result[2].length, '000000000') + console.log(editedArticleIds.length, viewedArticleIds.length, subscribedArticleIds.length) + result[0].forEach((e, i) => { + console.log(e.title, ':....') + }) + userinfo.push({ + displayName: displayName, + edited: result[0], + viewed: result[1], + subscribed: result[2], + points + }) + } + res.json({ + code: 200, + type: 'success', + message: `已成功傳送${userinfo.length}個使用者資料`, + data: userinfo + }) + } catch (error) { + console.log(error) + res.status(500).send({ + code: 500, + type: 'error', + message: error.message + }) + } + } } async function getArticlesInfo (req, res) { diff --git a/src/controllers/news_controller.js b/src/controllers/news_controller.js index 94b7b55..f4d99a2 100644 --- a/src/controllers/news_controller.js +++ b/src/controllers/news_controller.js @@ -27,9 +27,10 @@ module.exports = { }) }, getNews (req, res, next) { + console.log("/news_controller/getNews") const keyword = req.query.q || '' const checkQueryLimit = Number(req.query.limit) - const limit = isNaN(checkQueryLimit) ? 20 : checkQueryLimit + const limit = isNaN(checkQueryLimit) ? 3 : checkQueryLimit const pageNumber = isNaN(Number(req.query.page)) ? 0 : Number(req.query.page) const time = req.query.tbs const media = req.query.media.toString() || '' @@ -39,6 +40,7 @@ module.exports = { searchQuery = searchMediaIndex >= 0 ? { media } : {} } let timeQuery = {} + console.log(keyword,"limit:",limit,"page",pageNumber,"time:",time,media) switch (time) { case 'qdr:h': timeQuery = { @@ -62,8 +64,7 @@ module.exports = { break default: timeQuery = {} break - } - + } if (keyword) { News.find( { @@ -83,7 +84,7 @@ module.exports = { } ], ...timeQuery - }, null, { limit: limit, skip: pageNumber > 0 ? pageNumber * 20 : 0, sort: { _id: -1 } }) + }, null, { limit: limit, skip: pageNumber > 0 ? pageNumber * limit : 0, sort: { _id: -1 } }) .exec((err, doc) => { if (err || doc.length === 0) { console.error(err) diff --git a/src/index.js b/src/index.js index d0a95a4..c65a8e4 100644 --- a/src/index.js +++ b/src/index.js @@ -1,7 +1,6 @@ const http = require('http') const app = require('./lib/app') const PORT = process.env.PORT || 80 - const server = http.createServer(app) server.listen(PORT, () => { console.log(`Server started on port ${PORT}`) diff --git a/src/models/article.js b/src/models/article.js index bdb831e..21bd16f 100644 --- a/src/models/article.js +++ b/src/models/article.js @@ -11,7 +11,7 @@ const blockSchema = new mongoose.Schema({ }, blockDateTime: { type: Date, - required: true + required: false }, content: { type: Object @@ -19,6 +19,10 @@ const blockSchema = new mongoose.Schema({ authors: { type: Array, required: true + }, + isdeleted: { + type: Boolean, + default: false } }) @@ -65,7 +69,11 @@ const articleSchema = new mongoose.Schema({ type: Number, default: 0 }, - blocks: [blockSchema] + blocks: [blockSchema], + isdeleted: { + type: Boolean, + default: false + } }) const article = mongoose.model('Article', articleSchema, 'Articles') diff --git a/src/models/block.js b/src/models/block.js index b6e8824..b230d5d 100644 --- a/src/models/block.js +++ b/src/models/block.js @@ -22,6 +22,10 @@ const revisionSchema = new mongoose.Schema({ revisionIndex: { type: Number, required: true + }, + isdeleted: { + type: Boolean, + default: false } }) @@ -38,7 +42,11 @@ const blockSchema = new mongoose.Schema({ type: Array, required: true }, - revisions: [revisionSchema] + revisions: [revisionSchema], + isdeleted: { + type: Boolean, + default: false + } }) const block = mongoose.model('Block', blockSchema, 'Blocks') diff --git a/src/models/content.js b/src/models/content.js index e3cae43..b461f12 100644 --- a/src/models/content.js +++ b/src/models/content.js @@ -12,6 +12,10 @@ const contentSchema = new mongoose.Schema({ }, content: { type: Object + }, + isdeleted: { + type: Boolean, + default: false } }) diff --git a/src/models/latestNews.js b/src/models/latestNews.js index 1b3261c..8b6585c 100644 --- a/src/models/latestNews.js +++ b/src/models/latestNews.js @@ -10,6 +10,10 @@ const latestNewsSchema = new mongoose.Schema({ updatedAt: { type: Date, default: Date.now + }, + isdeleted: { + type: Boolean, + default: false } }) diff --git a/src/models/version.js b/src/models/version.js index 62144bc..8606753 100644 --- a/src/models/version.js +++ b/src/models/version.js @@ -18,6 +18,10 @@ const blockSchema = new mongoose.Schema({ revisionIndex: { type: Number, required: true + }, + isdeleted: { + type: Boolean, + default: false } }) @@ -55,7 +59,11 @@ const versionSchema = new mongoose.Schema({ default: 0 } } - }] + }], + isdeleted: { + type: Boolean, + default: false + } }) const version = mongoose.model('Version', versionSchema, 'Versions') diff --git a/src/routes/article_router.js b/src/routes/article_router.js index 40bc5d9..a843284 100644 --- a/src/routes/article_router.js +++ b/src/routes/article_router.js @@ -4,6 +4,9 @@ const ctrl = require('../controllers/article_controller') router.route('/article') .get(ctrl.getArticles) +router.route('/article/popular').get(ctrl.getPopularArticle) +router.route('/article/others').get(ctrl.getArticlesOthers) + router.route('/article/:id') .get(ctrl.getArticleById) @@ -19,4 +22,13 @@ router.route('/article/:id/authors') router.route('/search') .get(ctrl.searchArticles) +router.route('/article/:id/authorsImg') + .get(ctrl.getArticleAuthorsImg) + +router.route('/article/putDeleteArticle') + .put(ctrl.putDeleteArticleById) // req.body--> { "id":article_id} + +router.route('/article/deleteArticle') + .delete(ctrl.deleteArticleById) // req.body--> { "id":article_id} + module.exports = router diff --git a/src/routes/auth_router.js b/src/routes/auth_router.js index a294dc6..ac45b77 100644 --- a/src/routes/auth_router.js +++ b/src/routes/auth_router.js @@ -16,4 +16,7 @@ router.route('/profile/subscribe') router.route('/profile/article') .post(ctrl.getArticlesInfo) +router.route('/profile/manyUsersInfo') + .post(ctrl.getManyUsersInfo) // req.body -->> { "uids":["l18gziuFDxdmaJKdPwdQMeRIOvX2","WmPqnkHg9QfQ7NCuCh3WAV4cenN2","V1t2lp0nHYWrtREtLkCaLVHXMhx2"]} + module.exports = router diff --git a/yarn.lock b/yarn.lock index b589a65..97ce862 100644 --- a/yarn.lock +++ b/yarn.lock @@ -956,6 +956,11 @@ denque@^1.4.1: resolved "https://registry.yarnpkg.com/denque/-/denque-1.4.1.tgz#6744ff7641c148c3f8a69c307e51235c1f4a37cf" integrity sha512-OfzPuSZKGcgr96rf1oODnfjqBFmr1DVoc/TrItj3Ohe0Ah1C5WX5Baquw/9U9KovnQ88EqmJbD66rKYUQYN1tQ== +denque@^1.5.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/denque/-/denque-1.5.1.tgz#07f670e29c9a78f8faecb2566a1e2c11929c5cbf" + integrity sha512-XwE+iZ4D6ZUB7mfYRMb5wByE8L74HCn30FBN7sWnXksWc1LO1bPDl67pBR9o/kC4z/xSNAwkMYcGgqDV3BE3Hw== + depd@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" @@ -3265,6 +3270,33 @@ readdirp@^2.2.1: micromatch "^3.1.10" readable-stream "^2.0.2" +redis-commands@^1.7.0: + version "1.7.0" + resolved "https://registry.yarnpkg.com/redis-commands/-/redis-commands-1.7.0.tgz#15a6fea2d58281e27b1cd1acfb4b293e278c3a89" + integrity sha512-nJWqw3bTFy21hX/CPKHth6sfhZbdiHP6bTawSgQBlKOVRG7EZkfHbbHwQJnrE4vsQf0CMNE+3gJ4Fmm16vdVlQ== + +redis-errors@^1.0.0, redis-errors@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/redis-errors/-/redis-errors-1.2.0.tgz#eb62d2adb15e4eaf4610c04afe1529384250abad" + integrity sha1-62LSrbFeTq9GEMBK/hUpOEJQq60= + +redis-parser@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/redis-parser/-/redis-parser-3.0.0.tgz#b66d828cdcafe6b4b8a428a7def4c6bcac31c8b4" + integrity sha1-tm2CjNyv5rS4pCin3vTGvKwxyLQ= + dependencies: + redis-errors "^1.0.0" + +redis@3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/redis/-/redis-3.1.0.tgz#39daec130d74b78decca93513c61db0af5d86ce6" + integrity sha512-//lAOcEtNIKk2ekZibes5oyWKYUVWMvMB71lyD/hS9KRePNkB7AU3nXGkArX6uDKEb2N23EyJBthAv6pagD0uw== + dependencies: + denque "^1.5.0" + redis-commands "^1.7.0" + redis-errors "^1.2.0" + redis-parser "^3.0.0" + referrer-policy@1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/referrer-policy/-/referrer-policy-1.2.0.tgz#b99cfb8b57090dc454895ef897a4cc35ef67a98e"