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 |
|||
i.fas.fa-chevron-left |
|||
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: |
|||
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]: |
|||
version "1.1.1" |
|||
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-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]: |
|||
version "1.0.1" |
|||
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" |
|||
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: |
|||
version "5.10.2" |
|||
resolved "https://registry.yarnpkg.com/tinymce/-/tinymce-5.10.2.tgz#cf1ff01025909be26c64348509e6de8e70d58e1d" |
|||
|
Loading…
Reference in new issue