Browse Source

user profile photo (large and small) with remove

pull/1/head
Rob Colbert 3 years ago
parent
commit
1baddaad0f
  1. 60
      app/controllers/user.js
  2. 58
      app/services/user.js
  3. 4
      app/views/components/file-upload-image.pug
  4. 4
      app/views/components/navbar.pug
  5. 5
      app/views/layouts/public-profile.pug
  6. 1
      app/views/user/settings.pug
  7. 42
      client/js/site-app.js
  8. 10
      config/limiter.js

60
app/controllers/user.js

@ -58,6 +58,12 @@ class UserController extends SiteController {
router.param('userId', this.populateUser.bind(this));
router.post('/:userId/profile-photo',
limiterService.create(limiterService.config.user.postProfilePhoto),
upload.single('imageFile'),
this.postProfilePhoto.bind(this),
);
router.post('/:userId/settings',
limiterService.create(limiterService.config.user.postUpdateSettings),
upload.none(),
@ -83,6 +89,13 @@ class UserController extends SiteController {
checkProfileOwner,
this.getUserView.bind(this),
);
router.delete('/:userId/profile-photo',
limiterService.create(limiterService.config.user.deleteProfilePhoto),
authRequired,
checkProfileOwner,
this.deleteProfilePhoto.bind(this),
);
}
async populateUser (req, res, next, userId) {
@ -136,6 +149,30 @@ class UserController extends SiteController {
}
}
async postProfilePhoto (req, res) {
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',
'bottom-center',
2000,
);
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to update profile photo', { error });
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
});
}
}
async postUpdateSettings (req, res) {
const { user: userService } = this.dtp.services;
try {
@ -177,6 +214,29 @@ class UserController extends SiteController {
return next(error);
}
}
async deleteProfilePhoto (req, res) {
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',
'bottom-center',
2000,
);
res.status(200).json({ success: true, displayList });
} catch (error) {
this.log.error('failed to remove profile photo', { error });
return res.status(error.statusCode || 500).json({
success: false,
message: error.message,
});
}
}
}
module.exports = {

58
app/services/user.js

@ -263,6 +263,14 @@ class UserService {
const user = await User
.findById(userId)
.select('+email +flags +permissions')
.populate([
{
path: 'picture.large',
},
{
path: 'picture.small',
},
])
.lean();
if (!user) {
throw new SiteError(404, 'Member account not found');
@ -439,6 +447,56 @@ class UserService {
async getTotalCount ( ) {
return await User.estimatedDocumentCount();
}
async updatePhoto (user, file) {
const { image: imageService } = this.dtp.services;
const images = [
{
width: 512,
height: 512,
format: 'jpeg',
formatParameters: {
quality: 80,
},
},
{
width: 64,
height: 64,
format: 'jpeg',
formatParameters: {
conpressionLevel: 9,
},
},
];
await imageService.processImageFile(user, file, images);
await User.updateOne(
{ _id: user._id },
{
$set: {
'picture.large': images[0].image._id,
'picture.small': images[1].image._id,
},
},
);
}
async removePhoto (user) {
const { image: imageService } = this.dtp.services;
this.log.info('remove profile photo', { user });
user = await this.getUserAccount(user._id);
await imageService.deleteImage(user.picture.large);
await imageService.deleteImage(user.picture.small);
await User.updateOne(
{ _id: user._id },
{
$unset: {
'picture': '',
},
},
);
}
}
module.exports = {

4
app/views/components/file-upload-image.pug

@ -1,6 +1,6 @@
mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaultImage, currentImage, cropperOptions)
div(id= containerId).dtp-file-upload
form(method="POST", action= actionUrl, enctype="multipart/form-data", onsubmit= "return dtp.app.submitForm(event);").uk-form
form(method="POST", action= actionUrl, enctype="multipart/form-data", onsubmit= "return dtp.app.submitImageForm(event);").uk-form
.uk-margin
.uk-card.uk-card-default.uk-card-small
.uk-card-body
@ -21,7 +21,6 @@ mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaul
div(uk-form-custom).uk-margin-small-left
input(
type="file",
name="imageFile",
formenctype="multipart/form-data",
accept=".jpg,.png,image/jpeg,image/png",
data-file-select-container= containerId,
@ -51,6 +50,7 @@ mixin renderFileUploadImage (actionUrl, containerId, imageId, imageClass, defaul
#remove-btn(hidden= !currentImage).uk-width-auto
button(
type= "button",
data-image-type= imageId,
onclick= "return dtp.app.removeImageFile(event);",
).uk-button.uk-button-danger Remove

4
app/views/components/navbar.pug

@ -16,9 +16,9 @@ nav(uk-navbar).uk-navbar-container.uk-position-fixed.uk-position-top
.uk-navbar-item
if user
div.no-select
if user.picture_url
if user.picture && user.picture.small
img(
src= user.picture_url,
src= `/image/${user.picture.small._id}`,
title="Member Menu",
).site-profile-picture.sb-navbar
else

5
app/views/layouts/public-profile.pug

@ -9,7 +9,10 @@ block content-container
a(href=`/admin/user/${userProfile._id}`).uk-button.dtp-button-danger User Admin
.uk-margin.uk-margin-auto.uk-width-small
img(src="/img/icon/icon-512x512.png").responsive
if userProfile.picture && userProfile.picture.large
img(src= `/image/${user.picture.large._id}`).responsive
else
img(src= "/img/icon/icon-512x512.png").responsive
.uk-margin.uk-text-center
if userProfile.displayName

1
app/views/user/settings.pug

@ -19,6 +19,7 @@ block content
currentImage = user.picture.large;
}
+renderFileUploadImage(`/user/${user._id}/profile-photo`, 'test-image-upload', 'profile-picture-file', 'site-profile-picture', `/img/default-member.png`, currentImage)
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
.uk-margin

42
client/js/site-app.js

@ -194,6 +194,43 @@ export default class DtpSiteApp extends DtpApp {
return;
}
async submitImageForm (event) {
event.preventDefault();
event.stopPropagation();
const formElement = event.currentTarget || event.target;
const form = new FormData(formElement);
this.cropper.getCroppedCanvas().toBlob(async (imageData) => {
try {
form.append('imageFile', imageData, 'profile.png');
this.log.info('submitImageForm', 'updating user settings', { event, action: formElement.action });
const response = await fetch(formElement.action, {
method: formElement.method,
body: form,
});
if (!response.ok) {
let json;
try {
json = await response.json();
} catch (error) {
throw new Error('Server error');
}
throw new Error(json.message || 'Server error');
}
await this.processResponse(response);
window.location.reload();
} catch (error) {
UIkit.modal.alert(`Failed to update profile photo: ${error.message}`);
}
});
return;
}
async copyHtmlToText (event, textContentId) {
const content = this.editor.getContent({ format: 'text' });
const text = document.getElementById(textContentId);
@ -468,6 +505,10 @@ export default class DtpSiteApp extends DtpApp {
let response;
switch (imageType) {
case 'profile-picture-file':
response = await fetch(`/user/${this.user._id}/profile-photo`, { method: 'DELETE' });
break;
case 'channel-app-icon':
const channelId = (event.target || event.currentTarget).getAttribute('data-channel-id');
response = await fetch(`/channel/${channelId}/app-icon`, {
@ -484,6 +525,7 @@ export default class DtpSiteApp extends DtpApp {
}
await this.processResponse(response);
window.location.reload();
} catch (error) {
UIkit.modal.alert(`Failed to remove image: ${error.message}`);
}

10
config/limiter.js

@ -156,6 +156,11 @@ module.exports = {
expire: ONE_MINUTE,
message: 'You are creating accounts too quickly',
},
postProfilePhoto: {
total: 5,
expire: ONE_MINUTE * 5,
message: 'You are updating your profile photo too quickly',
},
postUpdateSettings: {
total: 4,
expire: ONE_MINUTE,
@ -171,6 +176,11 @@ module.exports = {
expire: ONE_MINUTE,
message: 'You are requesting user profiles too quickly',
},
deleteProfilePhoto: {
total: 5,
expire: ONE_MINUTE * 5,
message: 'You are deleting your profile photo too quickly',
},
},
welcome: {

Loading…
Cancel
Save