29 changed files with 346 additions and 49 deletions
@ -0,0 +1,24 @@ |
|||||
|
// comment.js
|
||||
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const path = require('path'); |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
const Schema = mongoose.Schema; |
||||
|
|
||||
|
const { CommentStats, CommentStatsDefaults } = require(path.join(__dirname, 'lib', 'resource-stats.js')); |
||||
|
|
||||
|
const CommentSchema = new Schema({ |
||||
|
created: { type: Date, default: Date.now, required: true, index: 1 }, |
||||
|
resourceType: { type: String, enum: ['Post', 'Page', 'Newsletter'], required: true }, |
||||
|
resource: { type: Schema.ObjectId, required: true, index: 1, refPath: 'resourceType' }, |
||||
|
replyTo: { type: Schema.ObjectId, index: 1, ref: 'Comment' }, |
||||
|
author: { type: Schema.ObjectId, required: true, index: 1, ref: 'User' }, |
||||
|
content: { type: String, required: true, maxlength: 3000 }, |
||||
|
stats: { type: CommentStats, default: CommentStatsDefaults, required: true }, |
||||
|
}); |
||||
|
|
||||
|
module.exports = mongoose.model('Comment', CommentSchema); |
@ -0,0 +1,35 @@ |
|||||
|
// lib/resource-stats.js
|
||||
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
|
||||
|
const Schema = mongoose.Schema; |
||||
|
|
||||
|
module.exports.ResourceStats = new Schema({ |
||||
|
totalViewCount: { type: Number, default: 0, required: true }, |
||||
|
upvoteCount: { type: Number, default: 0, required: true }, |
||||
|
downvoteCount: { type: Number, default: 0, required: true }, |
||||
|
commentCount: { type: Number, default: 0, required: true }, |
||||
|
}); |
||||
|
|
||||
|
module.exports.ResourceStatsDefaults = { |
||||
|
totalViewCount: 0, |
||||
|
upvoteCount: 0, |
||||
|
downvoteCount: 0, |
||||
|
commentCount: 0, |
||||
|
}; |
||||
|
|
||||
|
module.exports.CommentStats = new Schema({ |
||||
|
upvoteCount: { type: Number, default: 0, required: true }, |
||||
|
downvoteCount: { type: Number, default: 0, required: true }, |
||||
|
replyCount: { type: Number, default: 0, required: true }, |
||||
|
}); |
||||
|
|
||||
|
module.exports.CommentStatsDefaults = { |
||||
|
upvoteCount: 0, |
||||
|
downvoteCount: 0, |
||||
|
replyCount: 0, |
||||
|
}; |
@ -0,0 +1,30 @@ |
|||||
|
// resource-view.js
|
||||
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
|
||||
|
const Schema = mongoose.Schema; |
||||
|
|
||||
|
const RESOURCE_TYPE_LIST = ['Page', 'Post', 'Newsletter']; |
||||
|
|
||||
|
const ResourceViewSchema = new Schema({ |
||||
|
created: { type: Date, default: Date.now, required: true, index: 1, expires: '30d' }, |
||||
|
resourceType: { type: String, enum: RESOURCE_TYPE_LIST, required: true }, |
||||
|
resource: { type: Schema.ObjectId, required: true, index: 1, refPath: 'resourceType' }, |
||||
|
uniqueKey: { type: String, required: true, index: 1 }, |
||||
|
viewCount: { type: Number, default: 0, required: true }, |
||||
|
}); |
||||
|
|
||||
|
ResourceViewSchema.index({ |
||||
|
created: 1, |
||||
|
resourceType: 1, |
||||
|
resource: 1, |
||||
|
uniqueKey: 1, |
||||
|
}, { |
||||
|
name: 'res_view_daily_unique', |
||||
|
}); |
||||
|
|
||||
|
module.exports = mongoose.model('ResourceView', ResourceViewSchema); |
@ -0,0 +1,72 @@ |
|||||
|
// resource.js
|
||||
|
// Copyright (C) 2021 Digital Telepresence, LLC
|
||||
|
// License: Apache-2.0
|
||||
|
|
||||
|
'use strict'; |
||||
|
|
||||
|
const { SiteService } = require('../../lib/site-lib'); |
||||
|
|
||||
|
const mongoose = require('mongoose'); |
||||
|
|
||||
|
const ResourceView = mongoose.model('ResourceView'); |
||||
|
|
||||
|
class ResourceService extends SiteService { |
||||
|
|
||||
|
constructor (dtp) { |
||||
|
super(dtp, module.exports); |
||||
|
this.populateResourceView = [ |
||||
|
{ |
||||
|
path: 'resource', |
||||
|
}, |
||||
|
]; |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* Records 24-hour unique view counts for a given resource happening on a |
||||
|
* given ExpressJS Request. Views are uniqued by stripping time from the |
||||
|
* current Date, and upserting a tracking object in MongoDB. |
||||
|
* |
||||
|
* @param {Request} req |
||||
|
* @param {String} resourceType 'Post', 'Page', or 'Newsletter' |
||||
|
* @param {mongoose.Types.ObjectId} resourceId The _id of the object for which |
||||
|
* a view is being tracked. |
||||
|
*/ |
||||
|
async recordView (req, resourceType, resourceId) { |
||||
|
const CURRENT_DAY = new Date(); |
||||
|
CURRENT_DAY.setHours(0, 0, 0, 0); |
||||
|
|
||||
|
let uniqueKey = req.ip.toString().trim().toLowerCase(); |
||||
|
if (req.user) { |
||||
|
uniqueKey += `:user:${req.user._id.toString()}`; |
||||
|
} |
||||
|
|
||||
|
const response = await ResourceView.updateOne( |
||||
|
{ |
||||
|
created: CURRENT_DAY, |
||||
|
resourceType, |
||||
|
resource: resourceId, |
||||
|
uniqueKey, |
||||
|
}, |
||||
|
{ |
||||
|
$inc: { viewCount: 1 }, |
||||
|
}, |
||||
|
{ upsert: true }, |
||||
|
); |
||||
|
this.log.debug('resource view', { response }); |
||||
|
if (response.upsertedCount > 0) { |
||||
|
const Model = mongoose.model(resourceType); |
||||
|
await Model.updateOne( |
||||
|
{ _id: resourceId }, |
||||
|
{ |
||||
|
$inc: { 'stats.totalViewCount': 1 }, |
||||
|
}, |
||||
|
); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
module.exports = { |
||||
|
slug: 'resource', |
||||
|
name: 'resource', |
||||
|
create: (dtp) => { return new ResourceService(dtp); }, |
||||
|
}; |
@ -1,4 +1,4 @@ |
|||||
button(type="button", onclick= "return window.history.back();").uk-button.dtp-button-primary |
button(type="button", onclick= "return dtp.app.goBack();").uk-button.dtp-button-primary |
||||
span |
span |
||||
i.fas.fa-chevron-left |
i.fas.fa-chevron-left |
||||
span.uk-margin-small-left Back |
span.uk-margin-small-left Back |
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,13 @@ |
|||||
|
[program:dtp-sites] |
||||
|
numprocs=1 |
||||
|
process_name=%(program_name)s_%(process_num)02d |
||||
|
command=/home/dtp/.nvm/versions/node/v16.13.0/bin/node --optimize_for_size --max_old_space_size=1024 --gc_interval=100 dtp-sites.js |
||||
|
directory=/home/dtp/live/dtp-sites |
||||
|
autostart=true |
||||
|
autorestart=true |
||||
|
startretries=3 |
||||
|
stopsignal=INT |
||||
|
stderr_logfile=/var/log/dtp-sites/dtp-sites.err.log |
||||
|
stdout_logfile=/var/log/dtp-sites/dtp-sites.out.log |
||||
|
user=dtp |
||||
|
environment=HOME='/home/dtp/live/dtp-sites',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=dtp-sites |
@ -0,0 +1,13 @@ |
|||||
|
[program:host-services] |
||||
|
numprocs=1 |
||||
|
process_name=%(program_name)s_%(process_num)02d |
||||
|
command=/home/dtp/.nvm/versions/node/v16.13.0/bin/node --optimize_for_size --max_old_space_size=1024 --gc_interval=100 app/workers/host-services.js |
||||
|
directory=/home/dtp/live/dtp-sites |
||||
|
autostart=true |
||||
|
autorestart=true |
||||
|
startretries=3 |
||||
|
stopsignal=INT |
||||
|
stderr_logfile=/var/log/dtp-sites/host-services.err.log |
||||
|
stdout_logfile=/var/log/dtp-sites/host-services.out.log |
||||
|
user=dtp |
||||
|
environment=HOME='/home/dtp/live/dtp-sites',HTTP_BIND_PORT=30%(process_num)02d,NODE_ENV=production,LOGNAME=host-services |
@ -5787,6 +5787,13 @@ onetime@^6.0.0: |
|||||
dependencies: |
dependencies: |
||||
mimic-fn "^4.0.0" |
mimic-fn "^4.0.0" |
||||
|
|
||||
|
opentype.js@^0.7.3: |
||||
|
version "0.7.3" |
||||
|
resolved "https://registry.yarnpkg.com/opentype.js/-/opentype.js-0.7.3.tgz#40fb8ce18bfd60e74448efdfe442834098397aab" |
||||
|
integrity sha1-QPuM4Yv9YOdESO/f5EKDQJg5eqs= |
||||
|
dependencies: |
||||
|
tiny-inflate "^1.0.2" |
||||
|
|
||||
[email protected]: |
[email protected]: |
||||
version "1.1.1" |
version "1.1.1" |
||||
resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387" |
resolved "https://registry.yarnpkg.com/openurl/-/openurl-1.1.1.tgz#3875b4b0ef7a52c156f0db41d4609dbb0f94b387" |
||||
@ -7523,6 +7530,13 @@ sver-compat@^1.5.0: |
|||||
es6-iterator "^2.0.1" |
es6-iterator "^2.0.1" |
||||
es6-symbol "^3.1.1" |
es6-symbol "^3.1.1" |
||||
|
|
||||
|
svg-captcha@^1.4.0: |
||||
|
version "1.4.0" |
||||
|
resolved "https://registry.yarnpkg.com/svg-captcha/-/svg-captcha-1.4.0.tgz#32ead3c6463936c218bb3bc9ed04fea4eeffe492" |
||||
|
integrity sha512-/fkkhavXPE57zRRCjNqAP3txRCSncpMx3NnNZL7iEoyAtYwUjPhJxW6FQTQPG5UPEmCrbFoXS10C3YdJlW7PDg== |
||||
|
dependencies: |
||||
|
opentype.js "^0.7.3" |
||||
|
|
||||
[email protected]: |
[email protected]: |
||||
version "1.0.1" |
version "1.0.1" |
||||
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" |
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" |
||||
@ -7653,6 +7667,11 @@ time-stamp@^1.0.0: |
|||||
resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" |
resolved "https://registry.yarnpkg.com/time-stamp/-/time-stamp-1.1.0.tgz#764a5a11af50561921b133f3b44e618687e0f5c3" |
||||
integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= |
integrity sha1-dkpaEa9QVhkhsTPztE5hhofg9cM= |
||||
|
|
||||
|
tiny-inflate@^1.0.2: |
||||
|
version "1.0.3" |
||||
|
resolved "https://registry.yarnpkg.com/tiny-inflate/-/tiny-inflate-1.0.3.tgz#122715494913a1805166aaf7c93467933eea26c4" |
||||
|
integrity sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw== |
||||
|
|
||||
tinymce@^5.10.2: |
tinymce@^5.10.2: |
||||
version "5.10.2" |
version "5.10.2" |
||||
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.2.tgz#cf1ff01025909be26c64348509e6de8e70d58e1d" |
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.2.tgz#cf1ff01025909be26c64348509e6de8e70d58e1d" |
||||
|
Loading…
Reference in new issue