Browse Source

mostly user admin and account create in CLI

develop
Rob Colbert 1 year ago
parent
commit
8b9ff34b8b
  1. 27
      app/controllers/admin/user.js
  2. 18
      app/controllers/chat.js
  3. 30
      app/services/chat.js
  4. 16
      app/services/image.js
  5. 19
      app/services/link.js
  6. 7
      app/services/otp-auth.js
  7. 36
      app/services/user.js
  8. 16
      app/services/video.js
  9. 15
      app/views/admin/user/view.pug
  10. 11
      app/views/chat/room/invite.pug
  11. 15
      app/views/chat/room/view.pug
  12. 9
      app/views/user/settings.pug
  13. 1
      client/css/dtp-site.less
  14. 27
      client/css/site/uk-card.less
  15. 15
      dtp-chat-cli.js
  16. 1
      package.json
  17. 14
      pnpm-lock.yaml

27
app/controllers/admin/user.js

@ -22,6 +22,11 @@ export default class UserAdminController extends SiteController {
router.param('userId', this.populateUserId.bind(this));
router.post(
'/:userId',
this.postUserUpdate.bind(this),
);
router.get(
'/:userId',
this.getUserView.bind(this),
@ -49,6 +54,28 @@ export default class UserAdminController extends SiteController {
}
}
async postUserUpdate (req, res, next) {
const { user: userService } = this.dtp.services;
try {
switch (req.body.action) {
case 'update':
await userService.updateForAdmin(res.locals.userAccount, req.body);
break;
case 'ban':
if (res.locals.userAccount._id.equals(req.user._id)) {
throw new SiteError(400, "You can't ban yourself");
}
await userService.banUser(res.locals.userAccount);
break;
}
res.redirect(`/admin/user`);
} catch (error) {
this.log.error('failed to update user', { error });
return next(error);
}
}
async getUserView (req, res) {
res.render('admin/user/view');
}

18
app/controllers/chat.js

@ -90,6 +90,12 @@ export default class ChatController extends SiteController {
this.getRoomCreateView.bind(this),
);
router.get(
'/room/:roomId/invite',
// limiterService.create(limiterService.config.chat.getRoomInviteForm),
this.getRoomInviteForm.bind(this),
);
router.get(
'/room/:roomId/join',
// limiterService.create(limiterService.config.chat.getRoomJoinView),
@ -159,6 +165,7 @@ export default class ChatController extends SiteController {
async postMessageReaction (req, res) {
const { chat: chatService } = this.dtp.services;
try {
await chatService.checkRoomMember(res.locals.room, req.user);
await chatService.toggleMessageReaction(req.user, res.locals.message, req.body);
return res.status(200).json({ success: true });
} catch (error) {
@ -173,10 +180,7 @@ export default class ChatController extends SiteController {
async postRoomMessage (req, res) {
const { chat: chatService } = this.dtp.services;
try {
this.log.debug('post attachments', {
imageFiles: req.files.imageFiles,
videoFiles: req.files.videoFiles,
});
await chatService.checkRoomMember(res.locals.room, req.user);
await chatService.sendRoomMessage(
res.locals.room,
@ -221,6 +225,10 @@ export default class ChatController extends SiteController {
res.render('chat/room/create');
}
async getRoomInviteForm (req, res) {
res.render('chat/room/invite');
}
async getRoomJoinView (req, res, next) {
const { chat: chatService } = this.dtp.services;
try {
@ -254,6 +262,8 @@ export default class ChatController extends SiteController {
async getRoomView (req, res, next) {
const { chat: chatService } = this.dtp.services;
try {
await chatService.checkRoomMember(res.locals.room, req.user);
res.locals.currentView = 'chat-room';
res.locals.pageTitle = res.locals.room.name;

30
app/services/chat.js

@ -561,4 +561,34 @@ export default class ChatService extends SiteService {
audio: { playSound: 'message-remove' },
});
}
async removeAllForUser (user) {
this.log.info('removing all chat rooms for user', {
user: {
_id: user._id,
username: user.username,
},
});
await ChatRoom
.find({ owner: user._id })
.populate(this.populateChatRoom)
.cursor()
.eachAsync(async (room) => {
await this.destroyRoom(room);
});
this.log.info('removing all chat messages for user', {
user: {
_id: user._id,
username: user.username,
},
});
await ChatMessage
.find({ author: user._id })
.populate(this.populateChatMessage)
.cursor()
.eachAsync(async (message) => {
await this.removeMessage(message);
});
}
}

16
app/services/image.js

@ -114,6 +114,22 @@ export default class ImageService extends SiteService {
await ChatImage.deleteOne({ _id: image._id });
}
async removeForUser (user) {
this.log.info('removing all images for user', {
user: {
_id: user._id,
username: user.username,
},
});
await ChatImage
.find({ owner: user._id })
.populate(this.populateImage)
.cursor()
.eachAsync(async (image) => {
await this.deleteImage(image);
});
}
async processImageFile (owner, file, outputs, options) {
this.log.debug('processing image file', { owner, file, outputs });
const sharpImage = sharp(file.path);

19
app/services/link.js

@ -288,4 +288,23 @@ export default class LinkService extends SiteService {
async renderPreview (viewModel) {
return this.renderTemplate(this.templates.linkPreview, viewModel);
}
async removeForUser (user) {
this.log.info('removing all links for user', {
user: {
_id: user._id,
username: user.username,
},
});
await Link
.find({ submittedBy: user._id })
.populate(this.populateLink)
.cursor()
.eachAsync(async (link) => {
if (link.submittedBy.length > 1) {
return Link.updateOne({ _id: link._id }, { $pull: { submittedBy: user._id } });
}
await Link.deleteOne({ _id: link._id });
});
}
}

7
app/services/otp-auth.js

@ -238,6 +238,13 @@ export default class OtpAuthService extends SiteService {
if (serviceName) {
search.service = serviceName;
}
this.log.info('removing OTP account(s) for user', {
user: {
_id: user._id,
username: user.username,
},
serviceName,
});
await OtpAccount.deleteMany(search);
}
}

36
app/services/user.js

@ -15,7 +15,6 @@ import PassportLocal from 'passport-local';
import { v4 as uuidv4 } from 'uuid';
import { SiteService, SiteError } from '../../lib/site-lib.js';
import { users } from 'systeminformation';
export default class UserService extends SiteService {
@ -664,14 +663,43 @@ export default class UserService extends SiteService {
};
}
async updateForAdmin (user, userDefinition) {
await User.updateOne(
{ _id: user._id },
{
$set: {
'flags.isAdmin': userDefinition['flags.isAdmin'] === 'on',
'flags.isModerator': userDefinition['flags.isModerator'] === 'on',
'flags.isEmailVerified': userDefinition['flags.isEmailVerified'] === 'on',
'permissions.canLogin': userDefinition['permissions.canLogin'] === 'on',
'permissions.canChat': userDefinition['permissions.canChat'] === 'on',
'permissions.canReport': userDefinition['permissions.canReport'] === 'on',
'permissions.canShareLinks': userDefinition['permissions.canShareLinks'] === 'on',
'optIn.system': userDefinition['optIn.system'] === 'on',
'optIn.marketing': userDefinition['optIn.marketing'] === 'on',
},
},
);
}
async banUser (user) {
const { chat: chatService } = this.dtp.services;
const {
chat: chatService,
image: imageService,
link: linkService,
otpAuth: otpAuthService,
video: videoService,
} = this.dtp.services;
const userTag = { _id: user._id, username: user.username };
this.log.alert('banning user', userTag);
this.log.info('removing user chat messages', userTag);
await chatService.deleteAllForUser(user);
await chatService.removeAllForUser(user);
await otpAuthService.removeForUser(user);
await linkService.removeForUser(user);
await imageService.removeForUser(user);
await videoService.removeForUser(user);
this.log.info('removing all user privileges', userTag);
await User.updateOne(

16
app/services/video.js

@ -216,6 +216,22 @@ export default class VideoService extends SiteService {
await minioService.removeObject(video.media.bucket, video.media.key);
}
async removeForUser (user) {
this.log.info('removing all videos for user', {
user: {
_id: user._id,
username: user.username,
},
});
await Video
.find({ owner: user._id })
.populate(this.populateVideo)
.cursor()
.eachAsync(async (video) => {
await this.removeVideo(video);
});
}
async transcodeMov (file) {
const { media: mediaService } = this.dtp.services;

15
app/views/admin/user/view.pug

@ -3,6 +3,7 @@ block admin-content
include ../../user/components/profile-picture
form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form
.uk-card.uk-card-default.uk-card-small.uk-border-rounded
.uk-card-header
div(uk-grid).uk-grid-small
@ -34,17 +35,17 @@ block admin-content
div(uk-grid).uk-grid-small
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="isAdmin", checked= userAccount.flags.isAdmin)
input(type="checkbox", name="flags.isAdmin", checked= userAccount.flags.isAdmin)
.state.p-success
label Admin
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="isModerator", checked= userAccount.flags.isModerator)
input(type="checkbox", name="flags.isModerator", checked= userAccount.flags.isModerator)
.state.p-success
label Moderator
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="isEmailVerified", checked= userAccount.flags.isEmailVerified)
input(type="checkbox", name="flags.isEmailVerified", checked= userAccount.flags.isEmailVerified)
.state.p-success
label Email Verified
@ -54,22 +55,22 @@ block admin-content
div(uk-grid).uk-grid-small
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="canLogin", checked= userAccount.permissions.canLogin)
input(type="checkbox", name="permissions.canLogin", checked= userAccount.permissions.canLogin)
.state.p-success
label Can Login
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="canChat", checked= userAccount.permissions.canChat)
input(type="checkbox", name="permissions.canChat", checked= userAccount.permissions.canChat)
.state.p-success
label Can Chat
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="canReport", checked= userAccount.permissions.canReport)
input(type="checkbox", name="permissions.canReport", checked= userAccount.permissions.canReport)
.state.p-success
label Can Report
.uk-width-auto
.pretty.p-default
input(type="checkbox", name="canShareLinks", checked= userAccount.permissions.canShareLinks)
input(type="checkbox", name="permissions.canShareLinks", checked= userAccount.permissions.canShareLinks)
.state.p-success
label Can Share Links

11
app/views/chat/room/invite.pug

@ -0,0 +1,11 @@
button(type="button", uk-close).uk-modal-close-default
form(method="POST", action=`/chat/room/${room._id}/invite`, style="background: none;").uk-form
.uk-card.uk-card-secondary.uk-card-small
.uk-card-header
h1.uk-card-title Invite New Member
.uk-card-body
label(for="username").uk-form-label Username
input(id="username", name="username", type="text", placeholder="Enter username").uk-input
.uk-card-footer.uk-flex.uk-flex-right
.uk-width-auto
button(type="submit").uk-button.uk-button-primary.uk-border-rounded Invite Member

15
app/views/chat/room/view.pug

@ -15,10 +15,10 @@ block view-content
.live-username.uk-width-expand
.uk-text-truncate= member.displayName || member.username
.uk-width-auto
i.fas.fa-volume-off
i.fa-solid.fa-volume-off
.uk-width-auto
.uk-margin-small-left
i.fas.fa-cog
i.fa-solid.fa-cog
block view-navbar
@ -52,12 +52,19 @@ block view-content
.uk-width-expand
.uk-text-truncate= room.name
if room.owner._id.equals(user._id)
.uk-width-auto
a(
href=`/chat/room/${room._id}/invite`,
uk-tooltip={ title: 'Invite people to the room' },
onclick=`return dtp.app.showForm(event, '/chat/room/${room._id}/invite', 'chat-room-invite')`,
).uk-link-reset
i.fa-solid.fa-user-plus
.uk-width-auto
a(href=`/chat/room/${room._id}/settings`, uk-tooltip={ title: 'Configure room settings' }).uk-link-reset
i.fas.fa-cog
i.fa-solid.fa-cog
.uk-width-auto
a(href="/", uk-tooltip={ title: 'Leave room' }).uk-link-reset
i.fas.fa-person-through-window
i.fa-solid.fa-person-through-window
.chat-media
div(uk-grid).uk-flex-center

9
app/views/user/settings.pug

@ -56,8 +56,6 @@ block view-content
ul#account-settings-tabs.uk-switcher
li
fieldset
legend Profile
.uk-margin
label(for="username").uk-form-label Username
input(id="username", name="username", type="text", placeholder="Enter username", value= userProfile.username).uk-input
@ -74,8 +72,6 @@ block view-content
option(value="chat-dark", selected= (user.ui.theme === 'chat-dark')) Dark
li
fieldset
legend Password
.uk-margin
div(uk-grid).uk-grid-small
.uk-width-1-2
@ -88,9 +84,6 @@ block view-content
input(id="passwordv", name="passwordv", type="password", placeholder="Enter new password again", autocomplete= "new-password").uk-input
li
fieldset
legend Email Preferences
.uk-margin
label(for="email").uk-form-label
span Email Address
@ -127,8 +120,6 @@ block view-content
if user.flags && user.flags.isModerator
li
fieldset
legend Moderator Preferences
.uk-margin
.pretty.p-switch.p-slim
input(id="moderator-cloaked", name="flags.isCloaked", type="checkbox", checked= userProfile.flags ? userProfile.flags.isCloaked : false)

1
client/css/dtp-site.less

@ -1,4 +1,5 @@
@import "site/uk-lightbox.less";
@import "site/uk-card.less";
@import "site/main.less";

27
client/css/site/uk-card.less

@ -0,0 +1,27 @@
.uk-card {
&.uk-card-secondary {
.uk-card-body {
border-top: solid 1px #4a4a4a;
border-bottom: solid 1px #4a4a4a;
}
button.uk-button {
&.uk-button-primary {
background: @button-primary-background;
color: @button-primary-color;
&:hover {
background: @button-primary-hover-background;
color: @button-primary-hover-color;
}
&:active {
background: @button-primary-active-background;
color: @button-primary-active-color;
}
}
}
}
}

15
dtp-chat-cli.js

@ -14,6 +14,8 @@ const __dirname = dirname(fileURLToPath(import.meta.url)); // jshint ignore:line
import mongoose from 'mongoose';
import randomstring from 'randomstring';
import { SiteRuntime } from './lib/site-runtime.js';
class SiteTerminalApp extends SiteRuntime {
@ -34,6 +36,10 @@ class SiteTerminalApp extends SiteRuntime {
help: 'help [command name]',
},
'create-account': {
handler: this.createAccount.bind(this),
help: 'create-account email username [password]',
},
'grant': {
handler: this.grant.bind(this),
help: 'grant [admin|moderator] username',
@ -101,6 +107,15 @@ class SiteTerminalApp extends SiteRuntime {
}
}
async createAccount (args) {
const { user: userService } = this.services;
const email = args.shift();
const username = args.shift();
const password = args.shift() || randomstring.generate(8);
this.log.info('creating user account', { email, username });
await userService.create({ email, username, password });
}
async grant (args) {
const User = mongoose.model('User');

1
package.json

@ -56,6 +56,7 @@
"pretty-checkbox": "^3.0.3",
"pug": "^3.0.2",
"qrcode": "^1.5.3",
"randomstring": "^1.3.0",
"rate-limiter-flexible": "^5.0.0",
"rotating-file-stream": "^3.2.1",
"sharp": "^0.33.3",

14
pnpm-lock.yaml

@ -122,6 +122,9 @@ dependencies:
qrcode:
specifier: ^1.5.3
version: 1.5.3
randomstring:
specifier: ^1.3.0
version: 1.3.0
rate-limiter-flexible:
specifier: ^5.0.0
version: 5.0.0
@ -5385,12 +5388,23 @@ packages:
engines: {node: '>= 0.8'}
dev: false
/[email protected]:
resolution: {integrity: sha512-lDVjxQQFoCG1jcrP06LNo2lbWp4QTShEXnhActFBwYuHprllQV6VUpwreApsYqCgD+N1mHoqJ/BI/4eV4R2GYg==}
dev: false
/[email protected]:
resolution: {integrity: sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==}
dependencies:
safe-buffer: 5.2.1
dev: true
/[email protected]:
resolution: {integrity: sha512-gY7aQ4i1BgwZ8I1Op4YseITAyiDiajeZOPQUbIq9TPGPhUm5FX59izIaOpmKbME1nmnEiABf28d9K2VSii6BBg==}
hasBin: true
dependencies:
randombytes: 2.0.3
dev: false
/[email protected]:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'}

Loading…
Cancel
Save