mirror of
https://github.com/wassname/talk.git
synced 2026-08-13 12:40:11 +08:00
replaced eslint:recommended with prettier
This commit is contained in:
+29
-26
@@ -4,35 +4,38 @@ const Schema = mongoose.Schema;
|
||||
const ACTION_TYPES = require('./enum/action_types');
|
||||
const ITEM_TYPES = require('./enum/item_types');
|
||||
|
||||
const ActionSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true
|
||||
},
|
||||
action_type: {
|
||||
type: String,
|
||||
enum: ACTION_TYPES
|
||||
},
|
||||
item_type: {
|
||||
type: String,
|
||||
enum: ITEM_TYPES
|
||||
},
|
||||
item_id: String,
|
||||
user_id: String,
|
||||
const ActionSchema = new Schema(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
},
|
||||
action_type: {
|
||||
type: String,
|
||||
enum: ACTION_TYPES,
|
||||
},
|
||||
item_type: {
|
||||
type: String,
|
||||
enum: ITEM_TYPES,
|
||||
},
|
||||
item_id: String,
|
||||
user_id: String,
|
||||
|
||||
// The element that summaries will additionally group on in addtion to their action_type, item_type, and
|
||||
// item_id.
|
||||
group_id: String,
|
||||
// The element that summaries will additionally group on in addtion to their action_type, item_type, and
|
||||
// item_id.
|
||||
group_id: String,
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: Schema.Types.Mixed
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
// Additional metadata stored on the field.
|
||||
metadata: Schema.Types.Mixed,
|
||||
},
|
||||
{
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
const Action = mongoose.model('Action', ActionSchema);
|
||||
|
||||
|
||||
+76
-68
@@ -3,83 +3,91 @@ const Schema = mongoose.Schema;
|
||||
const uuid = require('uuid');
|
||||
const TagLinkSchema = require('./schema/tag_link');
|
||||
|
||||
const AssetSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
index: true
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
unique: true,
|
||||
index: true
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'assets'
|
||||
},
|
||||
scraped: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
closedAt: {
|
||||
type: Date,
|
||||
default: null
|
||||
},
|
||||
closedMessage: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
title: String,
|
||||
description: String,
|
||||
image: String,
|
||||
section: String,
|
||||
subsection: String,
|
||||
author: String,
|
||||
publication_date: Date,
|
||||
modified_date: Date,
|
||||
const AssetSchema = new Schema(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
url: {
|
||||
type: String,
|
||||
unique: true,
|
||||
index: true,
|
||||
},
|
||||
type: {
|
||||
type: String,
|
||||
default: 'assets',
|
||||
},
|
||||
scraped: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
closedAt: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
closedMessage: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
title: String,
|
||||
description: String,
|
||||
image: String,
|
||||
section: String,
|
||||
subsection: String,
|
||||
author: String,
|
||||
publication_date: Date,
|
||||
modified_date: Date,
|
||||
|
||||
// This object is used exclusivly for storing settings that are to override
|
||||
// the base settings from the base Settings object. This is to be accessed
|
||||
// always after running `rectifySettings` against it.
|
||||
settings: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: {},
|
||||
// This object is used exclusivly for storing settings that are to override
|
||||
// the base settings from the base Settings object. This is to be accessed
|
||||
// always after running `rectifySettings` against it.
|
||||
settings: {
|
||||
type: Schema.Types.Mixed,
|
||||
default: {},
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object
|
||||
{
|
||||
versionKey: false,
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
}
|
||||
}, {
|
||||
versionKey: false,
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
AssetSchema.index({
|
||||
title: 'text',
|
||||
url: 'text',
|
||||
description: 'text',
|
||||
section: 'text',
|
||||
subsection: 'text',
|
||||
author: 'text'
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
AssetSchema.index(
|
||||
{
|
||||
title: 'text',
|
||||
url: 'text',
|
||||
description: 'text',
|
||||
section: 'text',
|
||||
subsection: 'text',
|
||||
author: 'text',
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* Returns true if the asset is closed, false else.
|
||||
*/
|
||||
AssetSchema.virtual('isClosed').get(function() {
|
||||
return Boolean(this.closedAt && this.closedAt.getTime() <= new Date().getTime());
|
||||
return Boolean(
|
||||
this.closedAt && this.closedAt.getTime() <= new Date().getTime()
|
||||
);
|
||||
});
|
||||
|
||||
const Asset = mongoose.model('Asset', AssetSchema);
|
||||
|
||||
+168
-132
@@ -8,22 +8,25 @@ const COMMENT_STATUS = require('./enum/comment_status');
|
||||
* The Mongo schema for a Comment Status.
|
||||
* @type {Schema}
|
||||
*/
|
||||
const StatusSchema = new Schema({
|
||||
type: {
|
||||
type: String,
|
||||
enum: COMMENT_STATUS,
|
||||
},
|
||||
const StatusSchema = new Schema(
|
||||
{
|
||||
type: {
|
||||
type: String,
|
||||
enum: COMMENT_STATUS,
|
||||
},
|
||||
|
||||
// The User ID of the user that assigned the status.
|
||||
assigned_by: {
|
||||
type: String,
|
||||
default: null
|
||||
},
|
||||
// The User ID of the user that assigned the status.
|
||||
assigned_by: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
||||
created_at: Date
|
||||
}, {
|
||||
_id: false
|
||||
});
|
||||
created_at: Date,
|
||||
},
|
||||
{
|
||||
_id: false,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* A record of old body values for a Comment
|
||||
@@ -39,153 +42,186 @@ const BodyHistoryItemSchema = new Schema({
|
||||
required: true,
|
||||
type: Date,
|
||||
default: Date,
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* The Mongo schema for a Comment.
|
||||
* @type {Schema}
|
||||
*/
|
||||
const CommentSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
required: [true, 'The body is required.'],
|
||||
minlength: 2,
|
||||
},
|
||||
body_history: [BodyHistoryItemSchema],
|
||||
asset_id: String,
|
||||
author_id: String,
|
||||
status_history: [StatusSchema],
|
||||
status: {
|
||||
type: String,
|
||||
enum: COMMENT_STATUS,
|
||||
default: 'NONE'
|
||||
},
|
||||
const CommentSchema = new Schema(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
},
|
||||
body: {
|
||||
type: String,
|
||||
required: [true, 'The body is required.'],
|
||||
minlength: 2,
|
||||
},
|
||||
body_history: [BodyHistoryItemSchema],
|
||||
asset_id: String,
|
||||
author_id: String,
|
||||
status_history: [StatusSchema],
|
||||
status: {
|
||||
type: String,
|
||||
enum: COMMENT_STATUS,
|
||||
default: 'NONE',
|
||||
},
|
||||
|
||||
// parent_id is the id of the parent comment (null if there is none).
|
||||
parent_id: String,
|
||||
// parent_id is the id of the parent comment (null if there is none).
|
||||
parent_id: String,
|
||||
|
||||
// The number of replies to this comment directly.
|
||||
reply_count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
// The number of replies to this comment directly.
|
||||
reply_count: {
|
||||
type: Number,
|
||||
default: 0,
|
||||
},
|
||||
|
||||
// Counts to store related to actions taken on the given comment.
|
||||
action_counts: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
|
||||
// Counts to store related to actions taken on the given comment.
|
||||
action_counts: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object,
|
||||
{
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
},
|
||||
}
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
},
|
||||
toJSON: {
|
||||
virtuals: true,
|
||||
},
|
||||
});
|
||||
);
|
||||
|
||||
// Add the indexes for the id of the comment.
|
||||
CommentSchema.index({
|
||||
'id': 1
|
||||
}, {
|
||||
unique: true,
|
||||
background: false
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
id: 1,
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
background: false,
|
||||
}
|
||||
);
|
||||
|
||||
CommentSchema.index({
|
||||
'status': 1,
|
||||
'created_at': 1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
status: 1,
|
||||
created_at: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
CommentSchema.index({
|
||||
'status': 1,
|
||||
'created_at': 1,
|
||||
'asset_id': 1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
status: 1,
|
||||
created_at: 1,
|
||||
asset_id: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Create a sparse index to search across.
|
||||
CommentSchema.index({
|
||||
'created_at': 1,
|
||||
'action_counts.flag': 1,
|
||||
'status': 1,
|
||||
}, {
|
||||
background: true,
|
||||
sparse: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
created_at: 1,
|
||||
'action_counts.flag': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Create a sparse index to search across.
|
||||
CommentSchema.index({
|
||||
'action_counts.flag': 1,
|
||||
'status': 1,
|
||||
}, {
|
||||
background: true,
|
||||
sparse: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
'action_counts.flag': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index that is optimized for finding flagged comments.
|
||||
CommentSchema.index({
|
||||
'asset_id': 1,
|
||||
'created_at': 1,
|
||||
'action_counts.flag': 1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
created_at: 1,
|
||||
'action_counts.flag': 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index for the reply sort.
|
||||
CommentSchema.index({
|
||||
'asset_id': 1,
|
||||
'created_at': -1,
|
||||
'reply_count': -1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
created_at: -1,
|
||||
reply_count: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Optimize for tag searches/counts.
|
||||
CommentSchema.index({
|
||||
'asset_id': 1,
|
||||
'tags.tag.name': 1,
|
||||
'status': 1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
'tags.tag.name': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Optimize for tag searches/counts.
|
||||
CommentSchema.index({
|
||||
'tags.tag.name': 1,
|
||||
'status': 1,
|
||||
}, {
|
||||
background: true,
|
||||
sparse: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
'tags.tag.name': 1,
|
||||
status: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
sparse: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Add an index that is optimized for sorting based on the created_at timestamp
|
||||
// but also good at locating comments that have a specific asset id.
|
||||
CommentSchema.index({
|
||||
'asset_id': 1,
|
||||
'created_at': 1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
CommentSchema.index(
|
||||
{
|
||||
asset_id: 1,
|
||||
created_at: 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
CommentSchema.virtual('edited').get(function() {
|
||||
return this.body_history.length > 1;
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
module.exports = [
|
||||
'LIKE',
|
||||
'FLAG'
|
||||
];
|
||||
module.exports = ['LIKE', 'FLAG'];
|
||||
|
||||
@@ -1,7 +1 @@
|
||||
module.exports = [
|
||||
'ACCEPTED',
|
||||
'REJECTED',
|
||||
'PREMOD',
|
||||
'SYSTEM_WITHHELD',
|
||||
'NONE'
|
||||
];
|
||||
module.exports = ['ACCEPTED', 'REJECTED', 'PREMOD', 'SYSTEM_WITHHELD', 'NONE'];
|
||||
|
||||
@@ -1,5 +1 @@
|
||||
module.exports = [
|
||||
'ASSETS',
|
||||
'COMMENTS',
|
||||
'USERS'
|
||||
];
|
||||
module.exports = ['ASSETS', 'COMMENTS', 'USERS'];
|
||||
|
||||
@@ -1,4 +1 @@
|
||||
module.exports = [
|
||||
'PRE',
|
||||
'POST'
|
||||
];
|
||||
module.exports = ['PRE', 'POST'];
|
||||
|
||||
@@ -1,6 +1 @@
|
||||
module.exports = [
|
||||
'ADMIN',
|
||||
'MODERATOR',
|
||||
'STAFF',
|
||||
'COMMENTER',
|
||||
];
|
||||
module.exports = ['ADMIN', 'MODERATOR', 'STAFF', 'COMMENTER'];
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
module.exports = [
|
||||
|
||||
// UNSET is used when the username can be changed, and does not necessarily
|
||||
// require moderator action to become active. This can be used when the user
|
||||
// signs up with a social login and has the option of setting their own
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ const mongoose = require('../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const MigrationSchema = new Schema({
|
||||
version: Number
|
||||
version: Number,
|
||||
});
|
||||
|
||||
const Migration = mongoose.model('Migration', MigrationSchema);
|
||||
|
||||
+38
-33
@@ -8,46 +8,51 @@ const USER_ROLES = require('../enum/user_roles');
|
||||
* The Mongo schema for a Tag.
|
||||
* @type {Schema}
|
||||
*/
|
||||
const TagSchema = new Schema({
|
||||
const TagSchema = new Schema(
|
||||
{
|
||||
// The actual name of the tag.
|
||||
name: String,
|
||||
|
||||
// The actual name of the tag.
|
||||
name: String,
|
||||
// Contains permission data.
|
||||
permissions: {
|
||||
// Determines if this tag is public or not.
|
||||
public: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
|
||||
// Contains permission data.
|
||||
permissions: {
|
||||
// Determines if the owner of the Model can add/remove this tag on their own
|
||||
// resources.
|
||||
self: Boolean,
|
||||
|
||||
// Determines if this tag is public or not.
|
||||
public: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
// Determines other roles that are allowed to set this tag on other
|
||||
// resources.
|
||||
roles: [
|
||||
{
|
||||
type: String,
|
||||
enum: USER_ROLES,
|
||||
default: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// Determines if the owner of the Model can add/remove this tag on their own
|
||||
// resources.
|
||||
self: Boolean,
|
||||
// A list of all the model types that this tag can be added to.
|
||||
models: [
|
||||
{
|
||||
type: String,
|
||||
enum: ITEM_TYPES,
|
||||
},
|
||||
],
|
||||
|
||||
// Determines other roles that are allowed to set this tag on other
|
||||
// resources.
|
||||
roles: [{
|
||||
type: String,
|
||||
enum: USER_ROLES,
|
||||
default: []
|
||||
}]
|
||||
// The date for when the tag was created.
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date,
|
||||
},
|
||||
},
|
||||
|
||||
// A list of all the model types that this tag can be added to.
|
||||
models: [{
|
||||
type: String,
|
||||
enum: ITEM_TYPES
|
||||
}],
|
||||
|
||||
// The date for when the tag was created.
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date
|
||||
{
|
||||
_id: false,
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
});
|
||||
);
|
||||
|
||||
module.exports = TagSchema;
|
||||
|
||||
+19
-17
@@ -6,26 +6,28 @@ const TagSchema = require('./tag');
|
||||
* The Mongo schema for linking a Tag to a Model.
|
||||
* @type {Schema}
|
||||
*/
|
||||
const TagLinkSchema = new Schema({
|
||||
const TagLinkSchema = new Schema(
|
||||
{
|
||||
// A deep copy of the tag that is the origin for this link. If the ID matches
|
||||
// with existing tags in the global/asset context then content will be
|
||||
// substituted.
|
||||
tag: TagSchema,
|
||||
|
||||
// A deep copy of the tag that is the origin for this link. If the ID matches
|
||||
// with existing tags in the global/asset context then content will be
|
||||
// substituted.
|
||||
tag: TagSchema,
|
||||
// The User ID of the user that assigned the status.
|
||||
assigned_by: {
|
||||
type: String,
|
||||
default: null,
|
||||
},
|
||||
|
||||
// The User ID of the user that assigned the status.
|
||||
assigned_by: {
|
||||
type: String,
|
||||
default: null
|
||||
// The date when the tag was added to the model.
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date,
|
||||
},
|
||||
},
|
||||
|
||||
// The date when the tag was added to the model.
|
||||
created_at: {
|
||||
type: Date,
|
||||
default: Date
|
||||
{
|
||||
_id: false,
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
});
|
||||
);
|
||||
|
||||
module.exports = TagLinkSchema;
|
||||
|
||||
@@ -2,7 +2,6 @@ const mongoose = require('../../services/mongoose');
|
||||
const Schema = mongoose.Schema;
|
||||
|
||||
const TokenSchema = new Schema({
|
||||
|
||||
// ID is the JTI of a given JWT's identifier.
|
||||
id: String,
|
||||
|
||||
@@ -10,7 +9,7 @@ const TokenSchema = new Schema({
|
||||
name: String,
|
||||
|
||||
// Active is used to determine if the token is valid.
|
||||
active: Boolean
|
||||
active: Boolean,
|
||||
});
|
||||
|
||||
module.exports = TokenSchema;
|
||||
|
||||
+105
-103
@@ -7,118 +7,120 @@ const MODERATION_OPTIONS = require('./enum/moderation_options');
|
||||
* SettingSchema manages application settings that get used on front and backend.
|
||||
* @type {Schema}
|
||||
*/
|
||||
const SettingSchema = new Schema({
|
||||
id: {
|
||||
type: String,
|
||||
default: '1'
|
||||
},
|
||||
moderation: {
|
||||
type: String,
|
||||
enum: MODERATION_OPTIONS,
|
||||
default: 'POST'
|
||||
},
|
||||
infoBoxEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
customCssUrl: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
infoBoxContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
questionBoxEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
questionBoxIcon: {
|
||||
type: String,
|
||||
default: 'default'
|
||||
},
|
||||
questionBoxContent: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
premodLinksEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
organizationName: {
|
||||
type: String
|
||||
},
|
||||
autoCloseStream: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
closedTimeout: {
|
||||
type: Number,
|
||||
|
||||
// Two weeks default expiry.
|
||||
default: 60 * 60 * 24 * 7 * 2
|
||||
},
|
||||
closedMessage: {
|
||||
type: String,
|
||||
default: 'Expired'
|
||||
},
|
||||
wordlist: {
|
||||
banned: {
|
||||
type: Array,
|
||||
default: []
|
||||
const SettingSchema = new Schema(
|
||||
{
|
||||
id: {
|
||||
type: String,
|
||||
default: '1',
|
||||
},
|
||||
suspect: {
|
||||
type: Array,
|
||||
default: []
|
||||
}
|
||||
},
|
||||
charCount: {
|
||||
type: Number,
|
||||
default: 5000
|
||||
},
|
||||
charCountEnable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
requireEmailConfirmation: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
domains: {
|
||||
whitelist: {
|
||||
type: Array,
|
||||
default: ['localhost']
|
||||
}
|
||||
},
|
||||
moderation: {
|
||||
type: String,
|
||||
enum: MODERATION_OPTIONS,
|
||||
default: 'POST',
|
||||
},
|
||||
infoBoxEnable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
customCssUrl: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
infoBoxContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
questionBoxEnable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
questionBoxIcon: {
|
||||
type: String,
|
||||
default: 'default',
|
||||
},
|
||||
questionBoxContent: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
premodLinksEnable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
organizationName: {
|
||||
type: String,
|
||||
},
|
||||
autoCloseStream: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
closedTimeout: {
|
||||
type: Number,
|
||||
|
||||
// Length of time (in milliseconds) after a comment is posted that it can still be edited by the author
|
||||
editCommentWindowLength: {
|
||||
type: Number,
|
||||
min: [0, 'Edit Comment Window length must be greater than zero'],
|
||||
default: 30 * 1000,
|
||||
},
|
||||
tags: [TagSchema]
|
||||
}, {
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
},
|
||||
toObject: {
|
||||
transform: (doc, ret) => {
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
// Two weeks default expiry.
|
||||
default: 60 * 60 * 24 * 7 * 2,
|
||||
},
|
||||
closedMessage: {
|
||||
type: String,
|
||||
default: 'Expired',
|
||||
},
|
||||
wordlist: {
|
||||
banned: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
suspect: {
|
||||
type: Array,
|
||||
default: [],
|
||||
},
|
||||
},
|
||||
charCount: {
|
||||
type: Number,
|
||||
default: 5000,
|
||||
},
|
||||
charCountEnable: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
requireEmailConfirmation: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
domains: {
|
||||
whitelist: {
|
||||
type: Array,
|
||||
default: ['localhost'],
|
||||
},
|
||||
},
|
||||
|
||||
return ret;
|
||||
}
|
||||
// Length of time (in milliseconds) after a comment is posted that it can still be edited by the author
|
||||
editCommentWindowLength: {
|
||||
type: Number,
|
||||
min: [0, 'Edit Comment Window length must be greater than zero'],
|
||||
default: 30 * 1000,
|
||||
},
|
||||
tags: [TagSchema],
|
||||
},
|
||||
{
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
toObject: {
|
||||
transform: (doc, ret) => {
|
||||
delete ret._id;
|
||||
delete ret.__v;
|
||||
|
||||
return ret;
|
||||
},
|
||||
},
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
/**
|
||||
* Merges two settings objects.
|
||||
*/
|
||||
SettingSchema.method('merge', function(src) {
|
||||
SettingSchema.eachPath((path) => {
|
||||
|
||||
SettingSchema.eachPath(path => {
|
||||
// Exclude internal fields...
|
||||
if (['id', '_id', '__v', 'created_at', 'updated_at'].includes(path)) {
|
||||
return;
|
||||
|
||||
+217
-201
@@ -5,7 +5,7 @@ const uuid = require('uuid');
|
||||
const TagLinkSchema = require('./schema/tag_link');
|
||||
const TokenSchema = require('./schema/token');
|
||||
const can = require('../perms');
|
||||
const {get} = require('lodash');
|
||||
const { get } = require('lodash');
|
||||
|
||||
// USER_ROLES is the array of roles that is permissible as a user role.
|
||||
const USER_ROLES = require('./enum/user_roles');
|
||||
@@ -16,243 +16,257 @@ const USER_STATUS_USERNAME = require('./enum/user_status_username');
|
||||
|
||||
// ProfileSchema is the mongoose schema defined as the representation of a
|
||||
// User's profile stored in MongoDB.
|
||||
const ProfileSchema = new Schema({
|
||||
const ProfileSchema = new Schema(
|
||||
{
|
||||
// ID provides the identifier for the user profile, in the case of a local
|
||||
// provider, the id would be an email, in the case of a social provider,
|
||||
// the id would be the foreign providers identifier.
|
||||
id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// ID provides the identifier for the user profile, in the case of a local
|
||||
// provider, the id would be an email, in the case of a social provider,
|
||||
// the id would be the foreign providers identifier.
|
||||
id: {
|
||||
type: String,
|
||||
required: true
|
||||
// Provider is simply the name attached to the authentication mode. In the
|
||||
// case of a locally provided profile, this will simply be `local`, or a
|
||||
// social provider which for Facebook would just be `facebook`.
|
||||
provider: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// Metadata provides a place to put provider specific details. An example of
|
||||
// something that could be stored here is the `metadata.confirmed_at` could be
|
||||
// used by the `local` provider to indicate when the email address was
|
||||
// confirmed.
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed,
|
||||
},
|
||||
},
|
||||
|
||||
// Provider is simply the name attached to the authentication mode. In the
|
||||
// case of a locally provided profile, this will simply be `local`, or a
|
||||
// social provider which for Facebook would just be `facebook`.
|
||||
provider: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// Metadata provides a place to put provider specific details. An example of
|
||||
// something that could be stored here is the `metadata.confirmed_at` could be
|
||||
// used by the `local` provider to indicate when the email address was
|
||||
// confirmed.
|
||||
metadata: {
|
||||
type: Schema.Types.Mixed
|
||||
{
|
||||
_id: false,
|
||||
}
|
||||
}, {
|
||||
_id: false
|
||||
});
|
||||
);
|
||||
|
||||
// UserSchema is the mongoose schema defined as the representation of a User in
|
||||
// MongoDB.
|
||||
const UserSchema = new Schema({
|
||||
const UserSchema = new Schema(
|
||||
{
|
||||
// This ID represents the most unique identifier for a user, it is generated
|
||||
// when the user is created as a random uuid.
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// This ID represents the most unique identifier for a user, it is generated
|
||||
// when the user is created as a random uuid.
|
||||
id: {
|
||||
type: String,
|
||||
default: uuid.v4,
|
||||
unique: true,
|
||||
required: true
|
||||
},
|
||||
|
||||
// This is sourced from the social provider or set manually during user setup
|
||||
// and simply provides a name to display for the given user.
|
||||
username: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
|
||||
// TODO: find a way that we can instead utilize MongoDB 3.4's collation
|
||||
// options to build the index in a case insenstive manner:
|
||||
// https://docs.mongodb.com/manual/reference/collation/
|
||||
lowercaseUsername: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true
|
||||
},
|
||||
|
||||
// This provides a source of identity proof for users who login using the
|
||||
// local provider. A local provider will be assumed for users who do not
|
||||
// have any social profiles.
|
||||
password: String,
|
||||
|
||||
// Profiles describes the array of identities for a given user. Any one user
|
||||
// can have multiple profiles associated with them, including multiple email
|
||||
// addresses.
|
||||
profiles: [ProfileSchema],
|
||||
|
||||
// Tokens are the individual personal access tokens for a given user.
|
||||
tokens: [TokenSchema],
|
||||
|
||||
// Role is the specific user role that the user holds.
|
||||
role: {
|
||||
type: String,
|
||||
enum: USER_ROLES,
|
||||
required: true,
|
||||
default: 'COMMENTER',
|
||||
},
|
||||
|
||||
// Status stores the user status information regarding permissions,
|
||||
// capabilities and moderation state.
|
||||
status: {
|
||||
|
||||
// Username stores the current user status for the username as well as the
|
||||
// history of changes.
|
||||
// This is sourced from the social provider or set manually during user setup
|
||||
// and simply provides a name to display for the given user.
|
||||
username: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
|
||||
// Status stores the current username status.
|
||||
status: {
|
||||
type: String,
|
||||
enum: USER_STATUS_USERNAME,
|
||||
},
|
||||
// TODO: find a way that we can instead utilize MongoDB 3.4's collation
|
||||
// options to build the index in a case insenstive manner:
|
||||
// https://docs.mongodb.com/manual/reference/collation/
|
||||
lowercaseUsername: {
|
||||
type: String,
|
||||
required: true,
|
||||
unique: true,
|
||||
},
|
||||
|
||||
// History stores the history of username status changes.
|
||||
history: [{
|
||||
// This provides a source of identity proof for users who login using the
|
||||
// local provider. A local provider will be assumed for users who do not
|
||||
// have any social profiles.
|
||||
password: String,
|
||||
|
||||
// Status stores the historical username status.
|
||||
// Profiles describes the array of identities for a given user. Any one user
|
||||
// can have multiple profiles associated with them, including multiple email
|
||||
// addresses.
|
||||
profiles: [ProfileSchema],
|
||||
|
||||
// Tokens are the individual personal access tokens for a given user.
|
||||
tokens: [TokenSchema],
|
||||
|
||||
// Role is the specific user role that the user holds.
|
||||
role: {
|
||||
type: String,
|
||||
enum: USER_ROLES,
|
||||
required: true,
|
||||
default: 'COMMENTER',
|
||||
},
|
||||
|
||||
// Status stores the user status information regarding permissions,
|
||||
// capabilities and moderation state.
|
||||
status: {
|
||||
// Username stores the current user status for the username as well as the
|
||||
// history of changes.
|
||||
username: {
|
||||
// Status stores the current username status.
|
||||
status: {
|
||||
type: String,
|
||||
enum: USER_STATUS_USERNAME,
|
||||
},
|
||||
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: {type: String, default: null},
|
||||
// History stores the history of username status changes.
|
||||
history: [
|
||||
{
|
||||
// Status stores the historical username status.
|
||||
status: {
|
||||
type: String,
|
||||
enum: USER_STATUS_USERNAME,
|
||||
},
|
||||
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: {type: Date, default: Date.now}
|
||||
}],
|
||||
},
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: { type: String, default: null },
|
||||
|
||||
// Banned stores the current user banned status as well as the history of
|
||||
// changes.
|
||||
banned: {
|
||||
|
||||
// Status stores the current user banned status.
|
||||
status: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: { type: Date, default: Date.now },
|
||||
},
|
||||
],
|
||||
},
|
||||
history: [{
|
||||
|
||||
// Status stores the historical banned status.
|
||||
status: Boolean,
|
||||
// Banned stores the current user banned status as well as the history of
|
||||
// changes.
|
||||
banned: {
|
||||
// Status stores the current user banned status.
|
||||
status: {
|
||||
type: Boolean,
|
||||
required: true,
|
||||
default: false,
|
||||
},
|
||||
history: [
|
||||
{
|
||||
// Status stores the historical banned status.
|
||||
status: Boolean,
|
||||
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: {type: String, default: null},
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: { type: String, default: null },
|
||||
|
||||
// message stores the email content sent to the user.
|
||||
message: {type: String, default: null},
|
||||
// message stores the email content sent to the user.
|
||||
message: { type: String, default: null },
|
||||
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: {type: Date, default: Date.now}
|
||||
}],
|
||||
},
|
||||
|
||||
// Suspension stores the current user suspension status as well as the
|
||||
// history of changes.
|
||||
suspension: {
|
||||
|
||||
// until is the date that the user is suspended until.
|
||||
until: {
|
||||
type: Date,
|
||||
default: null,
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: { type: Date, default: Date.now },
|
||||
},
|
||||
],
|
||||
},
|
||||
history: [{
|
||||
|
||||
// Suspension stores the current user suspension status as well as the
|
||||
// history of changes.
|
||||
suspension: {
|
||||
// until is the date that the user is suspended until.
|
||||
until: Date,
|
||||
until: {
|
||||
type: Date,
|
||||
default: null,
|
||||
},
|
||||
history: [
|
||||
{
|
||||
// until is the date that the user is suspended until.
|
||||
until: Date,
|
||||
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: {type: String, default: null},
|
||||
// assigned_by stores the user id of the user who assigned this status.
|
||||
assigned_by: { type: String, default: null },
|
||||
|
||||
// message stores the email content sent to the user.
|
||||
message: {type: String, default: null},
|
||||
// message stores the email content sent to the user.
|
||||
message: { type: String, default: null },
|
||||
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: {type: Date, default: Date.now}
|
||||
}]
|
||||
}
|
||||
// created_at stores the date when this status was assigned.
|
||||
created_at: { type: Date, default: Date.now },
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
|
||||
// IgnoresUsers is an array of user id's that the current user is ignoring.
|
||||
ignoresUsers: [String],
|
||||
|
||||
// Counts to store related to actions taken on the given user.
|
||||
action_counts: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
},
|
||||
{
|
||||
// This will ensure that we have proper timestamps available on this model.
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at',
|
||||
},
|
||||
|
||||
// IgnoresUsers is an array of user id's that the current user is ignoring.
|
||||
ignoresUsers: [String],
|
||||
|
||||
// Counts to store related to actions taken on the given user.
|
||||
action_counts: {
|
||||
default: {},
|
||||
type: Object,
|
||||
},
|
||||
|
||||
// Tags are added by the self or by administrators.
|
||||
tags: [TagLinkSchema],
|
||||
|
||||
// Additional metadata stored on the field.
|
||||
metadata: {
|
||||
default: {},
|
||||
type: Object
|
||||
toJSON: {
|
||||
transform: function(doc, ret) {
|
||||
delete ret.__v;
|
||||
delete ret._id;
|
||||
delete ret.password;
|
||||
delete ret.status.username.history;
|
||||
delete ret.status.banned.history;
|
||||
delete ret.status.suspension.history;
|
||||
delete ret.tokens;
|
||||
},
|
||||
},
|
||||
}
|
||||
}, {
|
||||
|
||||
// This will ensure that we have proper timestamps available on this model.
|
||||
timestamps: {
|
||||
createdAt: 'created_at',
|
||||
updatedAt: 'updated_at'
|
||||
},
|
||||
|
||||
toJSON: {
|
||||
transform: function (doc, ret) {
|
||||
delete ret.__v;
|
||||
delete ret._id;
|
||||
delete ret.password;
|
||||
delete ret.status.username.history;
|
||||
delete ret.status.banned.history;
|
||||
delete ret.status.suspension.history;
|
||||
delete ret.tokens;
|
||||
}
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
// Add the index on the user profile data.
|
||||
UserSchema.index({
|
||||
'profiles.id': 1,
|
||||
'profiles.provider': 1
|
||||
}, {
|
||||
unique: true,
|
||||
background: false
|
||||
});
|
||||
UserSchema.index(
|
||||
{
|
||||
'profiles.id': 1,
|
||||
'profiles.provider': 1,
|
||||
},
|
||||
{
|
||||
unique: true,
|
||||
background: false,
|
||||
}
|
||||
);
|
||||
|
||||
UserSchema.index({
|
||||
'lowercaseUsername': 1,
|
||||
'profiles.id': 1,
|
||||
'created_at': -1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
UserSchema.index(
|
||||
{
|
||||
lowercaseUsername: 1,
|
||||
'profiles.id': 1,
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// This query is executed often, to count the number of flagged accounts with
|
||||
// usernames.
|
||||
UserSchema.index({
|
||||
'action_counts.flag': 1,
|
||||
'status.username.status': 1,
|
||||
}, {
|
||||
background: true
|
||||
});
|
||||
UserSchema.index(
|
||||
{
|
||||
'action_counts.flag': 1,
|
||||
'status.username.status': 1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
// Sorting users by created at is the default people search.
|
||||
UserSchema.index({
|
||||
'created_at': -1,
|
||||
}, {
|
||||
background: true,
|
||||
});
|
||||
UserSchema.index(
|
||||
{
|
||||
created_at: -1,
|
||||
},
|
||||
{
|
||||
background: true,
|
||||
}
|
||||
);
|
||||
|
||||
/**
|
||||
* returns true if a commenter is staff
|
||||
*/
|
||||
UserSchema.method('isStaff', function () {
|
||||
UserSchema.method('isStaff', function() {
|
||||
return this.role !== 'COMMENTER';
|
||||
});
|
||||
|
||||
@@ -300,8 +314,8 @@ UserSchema.virtual('firstEmail').get(function() {
|
||||
*/
|
||||
UserSchema.virtual('emails').get(function() {
|
||||
return (this.profiles || [])
|
||||
.filter(({provider}) => provider === 'local')
|
||||
.map(({id}) => id);
|
||||
.filter(({ provider }) => provider === 'local')
|
||||
.map(({ id }) => id);
|
||||
});
|
||||
|
||||
/**
|
||||
@@ -310,8 +324,8 @@ UserSchema.virtual('emails').get(function() {
|
||||
*/
|
||||
UserSchema.virtual('hasVerifiedEmail').get(function() {
|
||||
return this.profiles
|
||||
.filter(({provider}) => provider === 'local')
|
||||
.some((profile) => {
|
||||
.filter(({ provider }) => provider === 'local')
|
||||
.some(profile => {
|
||||
const confirmedAt = get(profile, 'metadata.confirmed_at') || null;
|
||||
|
||||
// If the profile doesn't have a metadata field, or it does not have a
|
||||
@@ -340,7 +354,7 @@ UserSchema.virtual('banned')
|
||||
this.status.banned.status = status;
|
||||
this.status.banned.history.push({
|
||||
status,
|
||||
created_at: new Date()
|
||||
created_at: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
@@ -350,13 +364,15 @@ UserSchema.virtual('banned')
|
||||
*/
|
||||
UserSchema.virtual('suspended')
|
||||
.get(function() {
|
||||
return Boolean(this.status.suspension.until && this.status.suspension.until > new Date());
|
||||
return Boolean(
|
||||
this.status.suspension.until && this.status.suspension.until > new Date()
|
||||
);
|
||||
})
|
||||
.set(function(until) {
|
||||
this.status.suspension.until = until;
|
||||
this.status.suspension.history.push({
|
||||
until,
|
||||
created_at: new Date()
|
||||
created_at: new Date(),
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user