From a913e7c1be09cf3d88413776f29f51f51d8b7a39 Mon Sep 17 00:00:00 2001 From: rob Date: Tue, 21 Dec 2021 04:17:59 -0500 Subject: [PATCH] added header image --- app/controllers/user.js | 60 +++++++++++++++++++-- app/models/user.js | 1 + app/services/image.js | 8 --- app/services/user.js | 45 +++++++++++++--- app/views/components/file-upload-image.pug | 7 ++- app/views/layouts/public-profile.pug | 48 ++++++++++------- app/views/user/settings.pug | 26 +++++++-- client/img/default-header.png | Bin 0 -> 9147 bytes client/js/site-app.js | 35 ++++++------ client/less/site/profile.less | 31 +++++++++++ client/less/style.less | 1 + config/limiter.js | 10 ++++ 12 files changed, 207 insertions(+), 65 deletions(-) create mode 100644 client/img/default-header.png create mode 100644 client/less/site/profile.less diff --git a/app/controllers/user.js b/app/controllers/user.js index 3f1a9ba..27bea30 100644 --- a/app/controllers/user.js +++ b/app/controllers/user.js @@ -64,6 +64,12 @@ class UserController extends SiteController { this.postProfilePhoto.bind(this), ); + router.post('/:userId/header-image', + limiterService.create(limiterService.config.user.postHeaderImage), + upload.single('imageFile'), + this.postHeaderImage.bind(this), + ); + router.post('/:userId/settings', limiterService.create(limiterService.config.user.postUpdateSettings), upload.none(), @@ -96,6 +102,13 @@ class UserController extends SiteController { checkProfileOwner, this.deleteProfilePhoto.bind(this), ); + + router.delete('/:userId/header-image', + limiterService.create(limiterService.config.user.deleteHeaderImage), + authRequired, + checkProfileOwner, + this.deleteHeaderImage.bind(this), + ); } async populateUser (req, res, next, userId) { @@ -153,10 +166,7 @@ class UserController extends SiteController { const { user: userService } = this.dtp.services; try { const displayList = this.createDisplayList('profile-photo'); - - this.log.debug('received profile photo', { file: req.file }); await userService.updatePhoto(req.user, req.file); - displayList.showNotification( 'Profile photo updated successfully.', 'success', @@ -173,6 +183,27 @@ class UserController extends SiteController { } } + async postHeaderImage (req, res) { + const { user: userService } = this.dtp.services; + try { + const displayList = this.createDisplayList('header-image'); + await userService.updateHeaderImage(req.user, req.file); + displayList.showNotification( + 'Header image updated successfully.', + 'success', + 'bottom-center', + 2000, + ); + res.status(200).json({ success: true, displayList }); + } catch (error) { + this.log.error('failed to update header image', { error }); + return res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } + async postUpdateSettings (req, res) { const { user: userService } = this.dtp.services; try { @@ -219,9 +250,7 @@ class UserController extends SiteController { const { user: userService } = this.dtp.services; try { const displayList = this.createDisplayList('app-settings'); - await userService.removePhoto(req.user); - displayList.showNotification( 'Profile photo removed successfully.', 'success', @@ -237,6 +266,27 @@ class UserController extends SiteController { }); } } + + async deleteHeaderImage (req, res) { + const { user: userService } = this.dtp.services; + try { + const displayList = this.createDisplayList('remove-header-image'); + await userService.removeHeaderImage(req.user); + displayList.showNotification( + 'Header image removed successfully.', + 'success', + 'bottom-center', + 2000, + ); + res.status(200).json({ success: true, displayList }); + } catch (error) { + this.log.error('failed to remove header image', { error }); + return res.status(error.statusCode || 500).json({ + success: false, + message: error.message, + }); + } + } } module.exports = { diff --git a/app/models/user.js b/app/models/user.js index 84aaf40..9a764d1 100644 --- a/app/models/user.js +++ b/app/models/user.js @@ -34,6 +34,7 @@ const UserSchema = new Schema({ large: { type: Schema.ObjectId, ref: 'Image' }, small: { type: Schema.ObjectId, ref: 'Image' }, }, + header: { type: Schema.ObjectId, ref: 'Image' }, flags: { type: UserFlagsSchema, select: false }, permissions: { type: UserPermissionsSchema, select: false }, stats: { type: ResourceStats, default: ResourceStatsDefaults, required: true }, diff --git a/app/services/image.js b/app/services/image.js index dbb0f29..2a13a65 100644 --- a/app/services/image.js +++ b/app/services/image.js @@ -35,8 +35,6 @@ class ImageService extends SiteService { const NOW = new Date(); const { minio: minioService } = this.dtp.services; try { - this.log.debug('processing uploaded image', { imageDefinition, file }); - const sharpImage = await sharp(file.path); const metadata = await sharpImage.metadata(); @@ -72,7 +70,6 @@ class ImageService extends SiteService { // save the Image model to the db await image.save(); - this.log.info('processed uploaded image', { ownerId, imageId, fileKey }); return image.toObject(); } catch (error) { this.log.error('failed to process image', { error }); @@ -111,7 +108,6 @@ class ImageService extends SiteService { } async processImageFile (owner, file, outputs, options) { - this.log.debug('processing image file', { owner, file, outputs }); const sharpImage = sharp(file.path); return this.processImage(owner, sharpImage, outputs, options); } @@ -133,8 +129,6 @@ class ImageService extends SiteService { outputMetadata.width = output.width; outputMetadata.height = output.height; - service.log.debug('processing image', { output, outputMetadata }); - const image = new SiteImage(); image.created = NOW; image.owner = owner._id; @@ -180,8 +174,6 @@ class ImageService extends SiteService { // save the Image model to the db await image.save(); - service.log.info('processed uploaded image', { ownerId, imageId, fileKey }); - if (options.removeWorkFiles) { service.log.debug('removing work file', { path: output.filePath }); await fs.promises.unlink(output.filePath); diff --git a/app/services/user.js b/app/services/user.js index 401965d..d437a71 100644 --- a/app/services/user.js +++ b/app/services/user.js @@ -270,6 +270,9 @@ class UserService { { path: 'picture.small', }, + { + path: 'header', + }, ]) .lean(); if (!user) { @@ -313,7 +316,7 @@ class UserService { username = username.trim().toLowerCase(); const user = await User .findOne({ username_lc: username }) - .select('_id created username username_lc displayName bio picture'); + .select('_id created username username_lc displayName bio picture header'); return user; } @@ -450,7 +453,6 @@ class UserService { async updatePhoto (user, file) { const { image: imageService } = this.dtp.services; - const images = [ { width: 512, @@ -484,19 +486,48 @@ class UserService { async removePhoto (user) { const { image: imageService } = this.dtp.services; - this.log.info('remove profile photo', { user }); + this.log.info('remove profile photo', { user: user._id }); user = await this.getUserAccount(user._id); - await imageService.deleteImage(user.picture.large); - await imageService.deleteImage(user.picture.small); + if (user.picture.large) { + await imageService.deleteImage(user.picture.large); + } + if (user.picture.small) { + await imageService.deleteImage(user.picture.small); + } + await User.updateOne({ _id: user._id }, { $unset: { 'picture': '' } }); + } + + async updateHeaderImage (user, file) { + const { image: imageService } = this.dtp.services; + const images = [ + { + width: 1400, + height: 400, + format: 'jpeg', + formatParameters: { + quality: 80, + }, + }, + ]; + await imageService.processImageFile(user, file, images); await User.updateOne( { _id: user._id }, { - $unset: { - 'picture': '', + $set: { + 'header': images[0].image._id, }, }, ); } + + async removeHeaderImage (user) { + const { image: imageService } = this.dtp.services; + user = await this.getUserAccount(user._id); + if (user.header) { + await imageService.deleteImage(user.header); + } + await User.updateOne({ _id: user._id }, { $unset: { 'header': '' } }); + } } module.exports = { diff --git a/app/views/components/file-upload-image.pug b/app/views/components/file-upload-image.pug index 9e20473..52869d0 100644 --- a/app/views/components/file-upload-image.pug +++ b/app/views/components/file-upload-image.pug @@ -8,9 +8,9 @@ mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaul div(class="uk-width-1-1 uk-width-auto@m") .upload-image-container.size-512 if !!currentImage - img(id= imageId, data-cropper-options= cropperOptions, src= `/image/${currentImage._id}`, class= imageClass).sb-large + img(id= imageId, src= `/image/${currentImage._id}`, class= imageClass).sb-large else - img(id= imageId, data-cropper-options= cropperOptions, src= defaultImage, class= imageClass).sb-large + img(id= imageId, src= defaultImage, class= imageClass).sb-large div(class="uk-width-1-1 uk-width-auto@m") .uk-text-small.uk-margin(hidden= !!currentImage) @@ -28,8 +28,7 @@ mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaul data-file-size-element= "file-size", data-file-max-size= 15 * 1024000, data-image-id= imageId, - data-image-w= 512, - data-image-h= 512, + data-cropper-options= cropperOptions, onchange="return dtp.app.selectImageFile(event);", ) button(type="button", tabindex="-1").uk-button.dtp-button-default Select diff --git a/app/views/layouts/public-profile.pug b/app/views/layouts/public-profile.pug index c0910cb..748f378 100644 --- a/app/views/layouts/public-profile.pug +++ b/app/views/layouts/public-profile.pug @@ -1,30 +1,38 @@ extends main block content-container + section.uk-section.uk-section-default.public-profile + .uk-container + if userProfile.header + .uk-margin.profile-header-container.header-offset + img(src= `/image/${userProfile.header._id}`).profile-header + if userProfile.picture && userProfile.picture.large + img(src= `/image/${userProfile.picture.large._id}`).profile-picture + else + img(src= "/img/icon/icon-512x512.png").profile-picture + else + .uk-margin.profile-header-container + if userProfile.picture && userProfile.picture.large + img(src= `/image/${userProfile.picture.large._id}`).profile-picture + else + img(src= "/img/icon/icon-512x512.png").profile-picture + + .uk-width-xlarge.uk-margin-auto + //- if user && user.flags.isAdmin + //- .uk-margin.uk-text-center + //- a(href=`/admin/user/${userProfile._id}`).uk-button.dtp-button-danger User Admin - section.uk-section.uk-section-default - .uk-container.uk-width-xlarge - if user && user.flags.isAdmin .uk-margin.uk-text-center - a(href=`/admin/user/${userProfile._id}`).uk-button.dtp-button-danger User Admin + if userProfile.displayName + h1.uk-margin-remove= userProfile.displayName + .uk-text-muted @#{userProfile.username} + else + h1.uk-margin-remove= userProfile.username - .uk-margin.uk-margin-auto.uk-width-small - if userProfile.picture && userProfile.picture.large - img(src= `/image/${userProfile.picture.large._id}`).responsive - else - img(src= "/img/icon/icon-512x512.png").responsive - - .uk-margin.uk-text-center - if userProfile.displayName - h1.uk-margin-remove= userProfile.displayName - .uk-text-muted @#{userProfile.username} - else - h1.uk-margin-remove= userProfile.username - - .uk-margin.uk-text-center - div= userProfile.bio + .uk-margin.uk-text-center + div= userProfile.bio - block content + block content block page-footer section.uk-section.uk-section-default.uk-section-small.dtp-site-footer diff --git a/app/views/user/settings.pug b/app/views/user/settings.pug index cea99fd..3496e5d 100644 --- a/app/views/user/settings.pug +++ b/app/views/user/settings.pug @@ -14,11 +14,31 @@ block content div(uk-grid) div(class="uk-width-1-1 uk-width-1-3@m") - - var currentImage = null; + var currentProfile = null; if (user.picture && user.picture.large) { - currentImage = user.picture.large; + currentProfile = user.picture.large; } - +renderFileUploadImage(`/user/${user._id}/profile-photo`, 'test-image-upload', 'profile-picture-file', 'site-profile-picture', `/img/default-member.png`, currentImage) + .uk-margin + +renderFileUploadImage( + `/user/${user._id}/header-image`, + 'header-image-upload', + 'header-image-file', + 'header-image-picture', + `/img/default-header.png`, + user.header, + { aspectRatio: 1400 / 400 }, + ) + + .uk-margin + +renderFileUploadImage( + `/user/${user._id}/profile-photo`, + 'profile-picture-upload', + 'profile-picture-file', + 'site-profile-picture', + `/img/default-member.png`, + currentProfile, + { aspectRatio: 1 }, + ) div(class="uk-width-1-1 uk-width-expand@m") form(method="POST", action=`/user/${user._id}/settings`, onsubmit="return dtp.app.submitForm(event, 'user account update');").uk-form diff --git a/client/img/default-header.png b/client/img/default-header.png new file mode 100644 index 0000000000000000000000000000000000000000..765f888c6f49fe319cc6b7b6ce3a75763a65d91b GIT binary patch literal 9147 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 { const img = document.getElementById(imageId); img.onload = (e) => { console.log('image loaded', e, img.naturalWidth, img.naturalHeight); - // if (img.naturalWidth !== IMAGE_WIDTH || img.naturalHeight !== IMAGE_HEIGHT) { - // UIkit.modal.alert(`This image must be ${IMAGE_WIDTH}x${IMAGE_HEIGHT}`); - // img.setAttribute('hidden', ''); - // img.src = ''; - // return; - // } fileSelectContainer.querySelector('#file-name').textContent = selectedFile.name; fileSelectContainer.querySelector('#file-modified').textContent = moment(selectedFile.lastModifiedDate).fromNow(); @@ -315,16 +309,15 @@ export default class DtpSiteApp extends DtpApp { img.src = e.target.result; //z create cropper and set options here - this.createImageCropper(img); + this.createImageCropper(img, cropperOptions); }; // read in the file, which will trigger everything else in the event handler above. reader.readAsDataURL(selectedFile); } - async createImageCropper (img) { - this.log.info("createImageCropper", "Creating image cropper", { img }); - this.cropper = new Cropper(img, { + async createImageCropper (img, options) { + options = Object.assign({ aspectRatio: 1, dragMode: 'move', autoCropArea: 0.85, @@ -332,11 +325,13 @@ export default class DtpSiteApp extends DtpApp { guides: false, center: false, highlight: false, - cropBoxMovable: false, - cropBoxResizable: false, + cropBoxMovable: true, + cropBoxResizable: true, toggleDragModeOnDblclick: false, modal: true, - }); + }, options); + this.log.info("createImageCropper", "Creating image cropper", { img }); + this.cropper = new Cropper(img, options); } async attachTinyMCE (editor) { @@ -505,7 +500,11 @@ export default class DtpSiteApp extends DtpApp { let response; switch (imageType) { - case 'profile-picture-file': + case 'header-image-file': + response = await fetch(`/user/${this.user._id}/header-image`, { method: 'DELETE' }); + break; + + case 'profile-picture-file': response = await fetch(`/user/${this.user._id}/profile-photo`, { method: 'DELETE' }); break; diff --git a/client/less/site/profile.less b/client/less/site/profile.less new file mode 100644 index 0000000..dd81059 --- /dev/null +++ b/client/less/site/profile.less @@ -0,0 +1,31 @@ +.public-profile { + + .profile-header-container { + position: relative; + + img.profile-header { + border: solid 2px #4a4a4a; + border-radius: 16px; + } + + img.profile-picture { + display: block; + width: 128px; + height: auto; + margin: 0 auto; + + border: solid 2px #4a4a4a; + border-radius: 50%; + } + + &.header-offset { + margin-bottom: 64px; + + img.profile-picture { + position: absolute; + bottom: -48px; + left: calc(50% - 64px); + } + } + } +} \ No newline at end of file diff --git a/client/less/style.less b/client/less/style.less index 219a1b5..db823cf 100644 --- a/client/less/style.less +++ b/client/less/style.less @@ -13,6 +13,7 @@ @import "site/nav.less"; @import "site/dashboard.less"; +@import "site/profile.less"; @import "site/site.less"; @import "site/site-home.less"; @import "site/form.less"; diff --git a/config/limiter.js b/config/limiter.js index a5c08f1..7e53fe3 100644 --- a/config/limiter.js +++ b/config/limiter.js @@ -161,6 +161,11 @@ module.exports = { expire: ONE_MINUTE * 5, message: 'You are updating your profile photo too quickly', }, + postHeaderImage: { + total: 5, + expire: ONE_MINUTE * 5, + message: 'You are updating your header image too quickly', + }, postUpdateSettings: { total: 4, expire: ONE_MINUTE, @@ -181,6 +186,11 @@ module.exports = { expire: ONE_MINUTE * 5, message: 'You are deleting your profile photo too quickly', }, + deleteHeaderImage: { + total: 5, + expire: ONE_MINUTE * 5, + message: 'You are deleting your header images too quickly', + }, }, welcome: {