Browse Source

removing and refining into a base app that does much nothing

develop
Rob Colbert 2 years ago
parent
commit
117cd5ecf5
  1. 8
      .env.default
  2. 11
      app/models/user.js
  3. 119
      app/services/user.js
  4. 4
      app/views/admin/user/form.pug
  5. 5
      app/views/user/settings.pug
  6. 30
      config/reserved-names.js
  7. 4
      start-local

8
.env.default

@ -41,8 +41,8 @@ MINIO_PORT=9000
MINIO_USE_SSL=disabled
MINIO_ACCESS_KEY=dtp-webapp
MINIO_SECRET_KEY=
MINIO_IMAGE_BUCKET=site-images
MINIO_VIDEO_BUCKET=site-videos
MINIO_IMAGE_BUCKET=webapp-images
MINIO_VIDEO_BUCKET=webapp-videos
#
# ExpressJS/HTTP configuration
@ -61,8 +61,8 @@ DTP_LOG_MONGODB=enabled
DTP_LOG_FILE=enabled
DTP_LOG_FILE_PATH=/tmp/dtp-webapp/logs
DTP_LOG_FILE_NAME_APP=justjoeradio-app.log
DTP_LOG_FILE_NAME_HTTP=justjoeradio-access.log
DTP_LOG_FILE_NAME_APP=webapp-app.log
DTP_LOG_FILE_NAME_HTTP=webapp-access.log
DTP_LOG_DEBUG=enabled
DTP_LOG_INFO=enabled

11
app/models/user.js

@ -20,8 +20,11 @@ const UserPermissionsSchema = new Schema({
canChat: { type: Boolean, default: true, required: true },
canComment: { type: Boolean, default: true, required: true },
canReport: { type: Boolean, default: true, required: true },
canAuthorPages: { type: Boolean, default: false, required: true },
canAuthorPosts: { type: Boolean, default: false, required: true },
});
const UserOptInSchema = new Schema({
system: { type: Boolean, default: true, requred: true },
marketing: { type: Boolean, default: true, requred: true },
});
const UserSchema = new Schema({
@ -32,14 +35,14 @@ const UserSchema = new Schema({
passwordSalt: { type: String, required: true },
password: { type: String, required: true },
displayName: { type: String },
bio: { type: String, maxlength: 300 },
picture: {
large: { type: Schema.ObjectId, ref: 'Image' },
small: { type: Schema.ObjectId, ref: 'Image' },
},
flags: { type: UserFlagsSchema, select: false },
permissions: { type: UserPermissionsSchema, select: false },
optIn: { type: UserOptInSchema, required: true, select: false },
stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true },
});
module.exports = mongoose.model('User', UserSchema);
module.exports = mongoose.model('User', UserSchema);

119
app/services/user.js

@ -4,6 +4,8 @@
'use strict';
const path = require('path');
const mongoose = require('mongoose');
const User = mongoose.model('User');
@ -23,6 +25,8 @@ class UserService {
this.dtp = dtp;
this.log = new SiteLog(dtp, `svc:${module.exports.slug}`);
this.reservedNames = require(path.join(this.dtp.config.root, 'config', 'reserved-names'));
this.populateUser = [
{
path: 'picture.large',
@ -99,13 +103,18 @@ class UserService {
canChat: true,
canComment: true,
canReport: true,
canAuthorPages: false,
canAuthorPosts: false,
};
user.optIn = {
system: true,
newsletter: false,
};
this.log.info('creating new user account', { email: userDefinition.email });
await user.save();
await this.sendWelcomeEmail(user);
return user.toObject();
} catch (error) {
this.log.error('failed to create user', { error });
@ -113,6 +122,71 @@ class UserService {
}
}
async sendWelcomeEmail (user) {
const { email: emailService } = this.dtp.services;
/*
* Remove all pending EmailVerify tokens for the User.
*/
await emailService.removeVerificationTokensForUser(user);
/*
* Create the new/only EmailVerify token for the user. This will be the only
* token accepted. Previous emails sent (if they were received) are invalid
* after this.
*/
const verifyToken = await emailService.createVerificationToken(user);
/*
* Send the welcome email using the new EmailVerify token so it can
* construct a new, valid link to use for verifying the email address.
*/
const templateModel = {
site: this.dtp.config.site,
recipient: user,
emailVerifyToken: verifyToken.token,
};
const message = {
from: process.env.DTP_EMAIL_SMTP_FROM,
to: user.email,
subject: `Welcome to ${this.dtp.config.site.name}!`,
html: await emailService.renderTemplate('welcome', 'html', templateModel),
text: await emailService.renderTemplate('welcome', 'text', templateModel),
};
await emailService.send(message);
}
async setEmailVerification (user, isVerified) {
await User.updateOne(
{ _id: user._id },
{
$set: { 'flags.isEmailVerified': isVerified },
},
);
}
async emailOptOut (userId, category) {
userId = mongoose.Types.ObjectId(userId);
const user = await this.getUserAccount(userId);
if (!user) {
throw new SiteError(406, 'Invalid opt-out token');
}
const updateOp = { $set: { } };
switch (category) {
case 'marketing':
updateOp.$set['optIn.marketing'] = false;
break;
case 'system':
updateOp.$set['optIn.system'] = false;
break;
default:
throw new SiteError(406, 'Invalid opt-out category');
}
await User.updateOne({ _id: userId }, updateOp);
}
async update (user, userDefinition) {
if (!user.flags.canLogin) {
throw SiteError(403, 'Invalid user account operation');
@ -123,9 +197,6 @@ class UserService {
const username_lc = userDefinition.username.toLowerCase();
userDefinition.displayName = striptags(userDefinition.displayName.trim());
if (userDefinition.bio) {
userDefinition.bio = striptags(userDefinition.bio.trim());
}
this.log.info('updating user', { userDefinition });
await User.updateOne(
@ -135,7 +206,8 @@ class UserService {
username: userDefinition.username,
username_lc,
displayName: userDefinition.displayName,
bio: userDefinition.bio,
'optIn.system': userDefinition['optIn.system'] === 'on',
'optIn.marketing': userDefinition['optIn.marketing'] === 'on',
},
},
);
@ -147,9 +219,6 @@ class UserService {
const username_lc = userDefinition.username.toLowerCase();
userDefinition.displayName = striptags(userDefinition.displayName.trim());
if (userDefinition.bio) {
userDefinition.bio = striptags(userDefinition.bio.trim());
}
this.log.info('updating user for admin', { userDefinition });
await User.updateOne(
@ -159,15 +228,12 @@ class UserService {
username: userDefinition.username,
username_lc,
displayName: userDefinition.displayName,
bio: userDefinition.bio,
'flags.isAdmin': userDefinition.isAdmin === 'on',
'flags.isModerator': userDefinition.isModerator === 'on',
'permissions.canLogin': userDefinition.canLogin === 'on',
'permissions.canChat': userDefinition.canChat === 'on',
'permissions.canComment': userDefinition.canComment === 'on',
'permissions.canReport': userDefinition.canReport === 'on',
'permissions.canAuthorPages': userDefinition.canAuthorPages === 'on',
'permissions.canAuthorPosts': userDefinition.canAuthorPosts === 'on',
},
},
);
@ -179,7 +245,6 @@ class UserService {
const username_lc = userDefinition.username.toLowerCase();
userDefinition.displayName = striptags(userDefinition.displayName.trim());
userDefinition.bio = striptags(userDefinition.bio.trim());
this.log.info('updating user settings', { userDefinition });
await User.updateOne(
@ -189,7 +254,6 @@ class UserService {
username: userDefinition.username,
username_lc,
displayName: userDefinition.displayName,
bio: userDefinition.bio,
},
},
);
@ -354,7 +418,7 @@ class UserService {
username = username.trim().toLowerCase();
const user = await User
.findOne({ username_lc: username })
.select('_id created username username_lc displayName bio picture header')
.select('_id created username username_lc displayName picture header')
.populate(this.populateUser)
.lean();
return user;
@ -363,7 +427,7 @@ class UserService {
async getRecent (maxCount = 3) {
const users = User
.find()
.select('_id created username username_lc displayName bio picture')
.select('_id created username username_lc displayName picture')
.sort({ created: -1 })
.limit(maxCount)
.lean();
@ -447,28 +511,7 @@ class UserService {
if (!username || (typeof username !== 'string') || (username.length === 0)) {
throw new SiteError(406, 'Invalid username');
}
const reservedNames = [
'about',
'admin',
'auth',
'digitaltelepresence',
'dist',
'dtp',
'fontawesome',
'fonts',
'img',
'image',
'less',
'manifest.json',
'moment',
'newsletter',
'numeral',
'socket.io',
'uikit',
'user',
'welcome',
];
if (reservedNames.includes(username.trim().toLowerCase())) {
if (this.reservedNames.includes(username.trim().toLowerCase())) {
throw new SiteError(403, 'That username is reserved for system use');
}

4
app/views/admin/user/form.pug

@ -18,10 +18,6 @@ block content
a(href=`/user/${userAccount._id}`) @#{userAccount.username}
.uk-card-body
.uk-margin
label(for="bio").uk-form-label.sr-only Bio
textarea(id="bio", name="bio", rows="4", placeholder= "Bio is empty", disabled= !userAccount.bio || (userAccount.bio.length === 0)).uk-textarea.uk-resize-vertical= userAccount.bio
.uk-margin
div(uk-grid)
div(class="uk-width-1-1 uk-width-1-2@m")

5
app/views/user/settings.pug

@ -37,9 +37,6 @@ block content
.uk-margin
label(for="display-name").uk-form-label Display Name
input(id="display-name", name="displayName", type="text", placeholder="Enter display name", value= user.displayName).uk-input
.uk-margin
label(for="bio").uk-form-label Profile bio
textarea(id="bio", name="bio", rows="3", placeholder="Tell people about yourself").uk-textarea.uk-resize-vertival= user.bio
.uk-margin
button(type="submit").uk-button.dtp-button-primary Update account settings
button(type="submit").uk-button.dtp-button-primary Update account settings

30
config/reserved-names.js

@ -0,0 +1,30 @@
// reserved-names.js
// Copyright (C) 2022 DTP Technologies, LLC
// License: Apache 2.0
'use strict';
module.exports = [
'.env',
'.env-default',
'about',
'admin',
'auth',
'digitaltelepresence',
'dist',
'dtp',
'fontawesome',
'fonts',
'img',
'image',
'less',
'manifest',
'manifest.json',
'moment',
'newsletter',
'numeral',
'socket.io',
'uikit',
'user',
'welcome',
];

4
start-local

@ -1,6 +1,6 @@
#!/bin/bash
MINIO_ROOT_USER="base"
MINIO_ROOT_USER="webapp"
MINIO_ROOT_PASSWORD="302888b9-c3d8-40f5-92de-6a3c57186af5"
export MINIO_ROOT_USER MINIO_ROOT_PASSWORD
@ -10,4 +10,4 @@ forever start --killSignal=SIGINT app/workers/reeeper.js
minio server ./data/minio --console-address ":9001"
forever stop app/workers/reeeper.js
forever stop app/workers/host-services.js
forever stop app/workers/host-services.js

Loading…
Cancel
Save