diff --git a/app/controllers/admin.js b/app/controllers/admin.js index c4b4ac0..936ce3f 100644 --- a/app/controllers/admin.js +++ b/app/controllers/admin.js @@ -13,8 +13,6 @@ const User = mongoose.model('User'); const ChatImage = mongoose.model('Image'); const Video = mongoose.model('Video'); -import express from 'express'; - import { SiteController } from '../../lib/site-lib.js'; export default class AdminController extends SiteController { @@ -31,8 +29,8 @@ export default class AdminController extends SiteController { const authCheck = sessionService.authCheckMiddleware({ requireLogin: true, requireAdmin: true }); - const router = express.Router(); - this.dtp.app.use('/admin', authCheck, router); + const router = this.createRouter('/admin'); + router.use(authCheck); router.use('/user', await this.loadChild(path.join(__dirname, 'admin', 'user.js'))); diff --git a/app/controllers/admin/user.js b/app/controllers/admin/user.js index 4111f3f..c498886 100644 --- a/app/controllers/admin/user.js +++ b/app/controllers/admin/user.js @@ -7,6 +7,9 @@ import express from 'express'; import { SiteController, SiteError } from '../../../lib/site-lib.js'; +import { + populateUserId, +} from '../lib/populators.js'; export default class UserAdminController extends SiteController { @@ -20,7 +23,7 @@ export default class UserAdminController extends SiteController { async start ( ) { const router = express.Router(); - router.param('userId', this.populateUserId.bind(this)); + router.param('userId', populateUserId(this)); router.post( '/:userId', @@ -40,33 +43,19 @@ export default class UserAdminController extends SiteController { return router; } - async populateUserId (req, res, next, userId) { - const { user: userService } = this.dtp.services; - try { - res.locals.userAccount = await userService.getUserAccount(userId); - if (!res.locals.userAccount) { - throw new SiteError(404, 'User not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate user account', { userId, error }); - return next(error); - } - } - 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); + await userService.updateForAdmin(res.locals.userProfile, req.body); break; case 'ban': - if (res.locals.userAccount._id.equals(req.user._id)) { + if (res.locals.userProfile._id.equals(req.user._id)) { throw new SiteError(400, "You can't ban yourself"); } - await userService.banUser(res.locals.userAccount); + await userService.banUser(res.locals.userProfile); break; } res.redirect(`/admin/user`); diff --git a/app/controllers/auth.js b/app/controllers/auth.js index 8bc42c5..527fa66 100644 --- a/app/controllers/auth.js +++ b/app/controllers/auth.js @@ -4,7 +4,6 @@ 'use strict'; -import express from 'express'; import multer from 'multer'; import mongoose from 'mongoose'; @@ -38,8 +37,7 @@ export default class AuthController extends SiteController { const upload = multer({ }); const authRequired = sessionService.authCheckMiddleware({ requireLogin: true }); - const router = express.Router(); - this.dtp.app.use('/auth', router); + const router = this.createRouter('/auth'); router.use(async (req, res, next) => { res.locals.currentView = 'auth'; diff --git a/app/controllers/email.js b/app/controllers/email.js index 9af3172..75ad62f 100644 --- a/app/controllers/email.js +++ b/app/controllers/email.js @@ -5,7 +5,6 @@ 'use strict'; import path from 'node:path'; -import express from 'express'; import SvgCaptcha from 'svg-captcha'; @@ -29,8 +28,7 @@ export default class EmailController extends SiteController { attempts: 3 }); - const router = express.Router(); - this.dtp.app.use('/email', router); + const router = this.createRouter('/email'); router.post( '/verify', diff --git a/app/controllers/home.js b/app/controllers/home.js index a1e4bd1..17117af 100644 --- a/app/controllers/home.js +++ b/app/controllers/home.js @@ -4,8 +4,6 @@ 'use strict'; -import express from 'express'; - import { SiteController } from '../../lib/site-controller.js'; export default class HomeController extends SiteController { @@ -19,8 +17,7 @@ export default class HomeController extends SiteController { } async start ( ) { - const router = express.Router(); - this.dtp.app.use('/', router); + const router = this.createRouter('/'); router.get('/', this.getHome.bind(this)); diff --git a/app/controllers/image.js b/app/controllers/image.js index 4f39f04..66d0c1e 100644 --- a/app/controllers/image.js +++ b/app/controllers/image.js @@ -5,10 +5,11 @@ 'use strict'; import fs from 'node:fs'; -import express from 'express'; -import mongoose from 'mongoose'; import { SiteController, SiteError } from '../../lib/site-lib.js'; +import { + populateImageId, +} from './lib/populators.js'; export default class ImageController extends SiteController { @@ -24,8 +25,7 @@ export default class ImageController extends SiteController { const { limiter: limiterService } = dtp.services; const limiterConfig = limiterService.config.image; - const router = express.Router(); - dtp.app.use('/image', router); + const router = this.createRouter('/image'); const imageUpload = this.createMulter(ImageController.slug, { limits: { @@ -38,7 +38,7 @@ export default class ImageController extends SiteController { return next(); }); - router.param('imageId', this.populateImage.bind(this)); + router.param('imageId', populateImageId(this)); router.post('/', limiterService.create(limiterConfig.postCreateImage), @@ -63,21 +63,6 @@ export default class ImageController extends SiteController { ); } - async populateImage (req, res, next, imageId) { - const { image: imageService } = this.dtp.services; - try { - res.locals.imageId = mongoose.Types.ObjectId.createFromHexString(imageId); - res.locals.image = await imageService.getImageById(res.locals.imageId); - if (!res.locals.image) { - throw new SiteError(404, 'Image not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate image', { error }); - return next(error); - } - } - async postCreateImage (req, res, next) { const { image: imageService } = this.dtp.services; try { diff --git a/app/controllers/lib/populators.js b/app/controllers/lib/populators.js index 84eb21d..7f343a6 100644 --- a/app/controllers/lib/populators.js +++ b/app/controllers/lib/populators.js @@ -1,24 +1,57 @@ 'use strict'; +import mongoose from 'mongoose'; + import { SiteError } from '../../../lib/site-lib.js'; -/* - * This is a sample populator. It doesn't run. There is no sample service, etc. - * This is simply the pattern you follow to declare a new ExpressJS parameter - * populator and export it from your populators library. - */ -export function populateSampleParameter (controller) { - return async function (req, res, next, sampleParameter) { - const { sample: sampleService } = controller.dtp.services; +export function populateImageId (controller) { + const { image: imageService } = controller.dtp.services; + return async function (req, res, next, imageId) { + try { + res.locals.imageId = mongoose.Types.ObjectId.createFromHexString(imageId); + res.locals.image = await imageService.getImageById(res.locals.imageId); + if (!res.locals.image) { + throw new SiteError(404, 'Image not found'); + } + return next(); + } catch (error) { + controller.log.error('failed to populate imageId', { imageId, error }); + return next(error); + } + }; +} + +export function populateUserId (controller) { + const { user: userService } = controller.dtp.services; + return async function (req, res, next, userId) { + if (!mongoose.Types.ObjectId.isValid(userId)) { + return next(new SiteError(406, 'Invalid User')); + } + try { + res.locals.userProfile = await userService.getUserAccount(userId); + if (!res.locals.userProfile) { + throw new SiteError(404, 'User not found'); + } + return next(); + } catch (error) { + controller.log.error('failed to populate userId', { userId, error }); + return next(error); + } + }; +} + +export function populateVideoId (controller) { + const { video: videoService } = controller.dtp.services; + return async function (req, res, next, videoId) { try { - res.locals.sample = await sampleService.getSample(sampleParameter); - if (!res.locals.sample) { - throw new SiteError(404, 'Sample not found'); + res.locals.video = await videoService.getVideoById(videoId); + if (!res.locals.video) { + throw new SiteError(404, 'Video not found'); } return next(); } catch (error) { - controller.log.error('failed to populate sampleParameter', { sampleParamater, error }); + controller.log.error('failed to populate video', { videoId, error }); return next(error); } }; -} \ No newline at end of file +} diff --git a/app/controllers/manifest.js b/app/controllers/manifest.js index 8e68048..b44fe82 100644 --- a/app/controllers/manifest.js +++ b/app/controllers/manifest.js @@ -6,8 +6,6 @@ const DTP_COMPONENT_NAME = 'manifest'; -import express from 'express'; - import { SiteController } from '../../lib/site-controller.js'; export default class ManifestController extends SiteController { @@ -28,8 +26,7 @@ export default class ManifestController extends SiteController { const { dtp } = this; const { limiter: limiterService } = dtp.services; - const router = express.Router(); - dtp.app.use('/manifest.json', router); + const router = this.createRouter('/manifest.json'); router.use(async (req, res, next) => { res.locals.currentView = DTP_COMPONENT_NAME; diff --git a/app/controllers/user.js b/app/controllers/user.js index cc00fa8..c6d03af 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -4,11 +4,10 @@ 'use strict'; -import express from 'express'; -import mongoose from 'mongoose'; -import multer from 'multer'; - import { SiteController, SiteError } from '../../lib/site-lib.js'; +import { + populateUserId, +} from './lib/populators.js'; export default class UserController extends SiteController { @@ -28,9 +27,13 @@ export default class UserController extends SiteController { session: sessionService, } = dtp.services; - const upload = multer({ dest: `/tmp/dtp/${this.dtp.config.site.domainKey}/uploads/${UserController.name}` }); - const router = express.Router(); - dtp.app.use('/user', router); + const upload = this.createMulter(UserController.slug, { + limits: { + fileSize: 1024 * 1000 * 15, + }, + }); + + const router = this.createRouter('/user'); const authCheck = sessionService.authCheckMiddleware({ requireLogin: true }); @@ -64,7 +67,7 @@ export default class UserController extends SiteController { return next(); } - router.param('userId', this.populateUser.bind(this)); + router.param('userId', populateUserId(this)); router.post( '/:userId/profile-photo', @@ -173,23 +176,6 @@ export default class UserController extends SiteController { ); } - async populateUser (req, res, next, userId) { - const { user: userService } = this.dtp.services; - if (!req.user) { - return res.redirect('/welcome'); - } - if (!mongoose.Types.ObjectId.isValid(userId)) { - return next(new SiteError(406, 'Invalid User')); - } - try { - res.locals.userProfile = await userService.getUserAccount(userId); - return next(); - } catch (error) { - this.log.error('failed to populate userId', { userId, error }); - return next(error); - } - } - async postCreateUser (req, res, next) { const { user: userService } = this.dtp.services; try { diff --git a/app/controllers/video.js b/app/controllers/video.js index 793223f..5b4ceeb 100644 --- a/app/controllers/video.js +++ b/app/controllers/video.js @@ -4,11 +4,12 @@ 'use strict'; -import express from 'express'; - import { pipeline } from 'node:stream'; -import { SiteController, SiteError } from '../../lib/site-lib.js'; +import { SiteController } from '../../lib/site-lib.js'; +import { + populateVideoId, +} from './lib/populators.js'; export default class VideoController extends SiteController { @@ -26,21 +27,20 @@ export default class VideoController extends SiteController { } = this.dtp.services; const limiterConfig = limiterService.config.video; - this.templates = { - passwordResetComplete: this.loadViewTemplate('auth/password-reset-complete.pug'), - }; - const authRequired = sessionService.authCheckMiddleware({ requireLogin: true }); - const router = express.Router(); - this.dtp.app.use('/video', authRequired, router); + const router = this.createRouter('/video'); + this.dtp.app.use('/video', router); - router.use(async (req, res, next) => { - res.locals.currentView = 'video'; - return next(); - }); + router.use( + authRequired, + async (req, res, next) => { + res.locals.currentView = 'video'; + return next(); + }, + ); - router.param('videoId', this.populateVideoId.bind(this)); + router.param('videoId', populateVideoId(this)); router.get( '/:videoId/media', @@ -51,20 +51,6 @@ export default class VideoController extends SiteController { return router; } - async populateVideoId (req, res, next, videoId) { - const { video: videoService } = this.dtp.services; - try { - res.locals.video = await videoService.getVideoById(videoId); - if (!res.locals.video) { - throw new SiteError(404, 'Video not found'); - } - return next(); - } catch (error) { - this.log.error('failed to populate video', { videoId, error }); - return next(error); - } - } - async getVideoMedia (req, res, next) { const { minio: minioService } = this.dtp.services; try { diff --git a/app/controllers/welcome.js b/app/controllers/welcome.js index 80e323d..5721011 100644 --- a/app/controllers/welcome.js +++ b/app/controllers/welcome.js @@ -4,8 +4,6 @@ 'use strict'; -import express from 'express'; - import { SiteController } from '../../lib/site-controller.js'; export default class WelcomeController extends SiteController { @@ -18,13 +16,11 @@ export default class WelcomeController extends SiteController { } async start ( ) { - const { dtp } = this; // const { limiter: limiterService } = dtp.services; this.log.info('WelcomeController starting'); - const router = express.Router(); - dtp.app.use('/welcome', router); + const router = this.createRouter('/welcome'); router.use(async (req, res, next) => { res.locals.currentView = WelcomeController.name; diff --git a/app/views/admin/user/view.pug b/app/views/admin/user/view.pug index b1cdf97..2a53e17 100644 --- a/app/views/admin/user/view.pug +++ b/app/views/admin/user/view.pug @@ -3,49 +3,49 @@ block admin-content include ../../user/components/profile-picture - form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form + form(method="POST", action=`/admin/user/${userProfile._id}`).uk-form .uk-card.uk-card-default.uk-card-small.uk-border-rounded .uk-card-header div(uk-grid).uk-grid-small .uk-width-auto - +renderProfilePicture(userAccount) + +renderProfilePicture(userProfile) .uk-width-expand .uk-margin - .uk-text-lead= userAccount.displayName + .uk-text-lead= userProfile.displayName div(uk-grid).uk-grid-small.uk-grid-divider - .uk-width-auto @#{userAccount.username} - .uk-width-auto= userAccount.email - .uk-width-auto Created #{dayjs(userAccount.created).fromNow()} on #{dayjs(userAccount.created).format('MMMM D, YYYY')} + .uk-width-auto @#{userProfile.username} + .uk-width-auto= userProfile.email + .uk-width-auto Created #{dayjs(userProfile.created).fromNow()} on #{dayjs(userProfile.created).format('MMMM D, YYYY')} .uk-card-body .uk-margin label(for="profile-bio").uk-form-label bio - #profile-bio.markdown-block!= marked.parse(userAccount.bio || '(no bio provided)', { renderer: fullMarkdownRenderer }) + #profile-bio.markdown-block!= marked.parse(userProfile.bio || '(no bio provided)', { renderer: fullMarkdownRenderer }) .uk-margin label(for="profile-badges").uk-form-label Profile Badges - input(id="profile-badges", type= "text", placeholder= "Comma-separated list of badge names", value= userAccount.badges.join(',')).uk-input + input(id="profile-badges", type= "text", placeholder= "Comma-separated list of badge names", value= userProfile.badges.join(',')).uk-input .uk-margin - form(method="POST", action=`/admin/user/${userAccount._id}`).uk-form + form(method="POST", action=`/admin/user/${userProfile._id}`).uk-form .uk-margin label.uk-form-label Flags .uk-margin-small div(uk-grid).uk-grid-small .uk-width-auto .pretty.p-default - input(type="checkbox", name="flags.isAdmin", checked= userAccount.flags.isAdmin) + input(type="checkbox", name="flags.isAdmin", checked= userProfile.flags.isAdmin) .state.p-success label Admin .uk-width-auto .pretty.p-default - input(type="checkbox", name="flags.isModerator", checked= userAccount.flags.isModerator) + input(type="checkbox", name="flags.isModerator", checked= userProfile.flags.isModerator) .state.p-success label Moderator .uk-width-auto .pretty.p-default - input(type="checkbox", name="flags.isEmailVerified", checked= userAccount.flags.isEmailVerified) + input(type="checkbox", name="flags.isEmailVerified", checked= userProfile.flags.isEmailVerified) .state.p-success label Email Verified @@ -55,17 +55,17 @@ block admin-content div(uk-grid).uk-grid-small .uk-width-auto .pretty.p-default - input(type="checkbox", name="permissions.canLogin", checked= userAccount.permissions.canLogin) + input(type="checkbox", name="permissions.canLogin", checked= userProfile.permissions.canLogin) .state.p-success label Can Login .uk-width-auto .pretty.p-default - input(type="checkbox", name="permissions.canReport", checked= userAccount.permissions.canReport) + input(type="checkbox", name="permissions.canReport", checked= userProfile.permissions.canReport) .state.p-success label Can Report .uk-width-auto .pretty.p-default - input(type="checkbox", name="permissions.canShareLinks", checked= userAccount.permissions.canShareLinks) + input(type="checkbox", name="permissions.canShareLinks", checked= userProfile.permissions.canShareLinks) .state.p-success label Can Share Links @@ -75,12 +75,12 @@ block admin-content div(uk-grid).uk-grid-small .uk-width-auto .pretty.p-default - input(type="checkbox", name="optIn.system", checked= userAccount.optIn.system) + input(type="checkbox", name="optIn.system", checked= userProfile.optIn.system) .state.p-success label System Messages .uk-width-auto .pretty.p-default - input(type="checkbox", name="optIn.marketing", checked= userAccount.optIn.marketing) + input(type="checkbox", name="optIn.marketing", checked= userProfile.optIn.marketing) .state.p-success label Marketing diff --git a/lib/site-controller.js b/lib/site-controller.js index 1a4cf72..515f724 100644 --- a/lib/site-controller.js +++ b/lib/site-controller.js @@ -6,6 +6,7 @@ import path from 'node:path'; +import express from 'express'; import multer from 'multer'; import slugTool from 'slug'; @@ -57,6 +58,12 @@ export class SiteController extends SiteCommon { return pagination; } + createRouter (route) { + const router = express.Router(); + this.dtp.app.use(route, router); + return router; + } + createMulter (slug, options) { if (!!slug && (typeof slug === 'object')) { options = slug;