UNPKG

profile-plus

Version:

253 lines 11.2 kB
//import all ShapeProviders here or define a generic BackendProvider, see documentation on https://docs.lincd.org import { BackendProvider } from 'lincd-server-utils/utils/BackendProvider'; import { formidable } from 'formidable'; import { ProfilePicture } from 'profile-pics/shapes/ProfilePicture'; import path from 'path'; import { ImageObject } from 'lincd-schema/shapes/ImageObject'; import sharp from 'sharp'; import fetch from 'node-fetch'; import { uploadSingleFileFromBuffer, uploadSingleFileFromFormData, } from 'lincd-server-utils/utils/Upload'; import { Person } from './shapes/Person.js'; import { onAccountWillBeRemoved } from 'lincd-auth/utils/events'; class ProfilePlusBackendProvider extends BackendProvider { setupBeforeControllers() { onAccountWillBeRemoved((account) => { var _a, _b, _c, _d, _e; if (account.accountOfNode) { let p = new Person(account.accountOfNode); (_a = p.profilePicture) === null || _a === void 0 ? void 0 : _a.remove(); (_b = p.profilePicture2) === null || _b === void 0 ? void 0 : _b.remove(); (_c = p.profilePicture3) === null || _c === void 0 ? void 0 : _c.remove(); (_d = p.profilePicture4) === null || _d === void 0 ? void 0 : _d.remove(); (_e = p.profilePicture5) === null || _e === void 0 ? void 0 : _e.remove(); if (p.address) { p.address.remove(); } if (p.homeLocation) { p.homeLocation.forEach((l) => l.remove()); } if (p.birthPlace) { p.birthPlace.remove(); } } }); } loadProfilePicture(property) { var _a, _b, _c; //TODO: replace uploader with a lincd component const auth = (_a = this.request.linkedAuth) === null || _a === void 0 ? void 0 : _a.userAccount; if (!auth) { console.warn('loadProfilePicture: No user authenticated.'); return; } const user = auth.accountOf; return user && ((_b = user[property]) === null || _b === void 0 ? void 0 : _b.cropped) ? (_c = user[property]) === null || _c === void 0 ? void 0 : _c.cropped : null; } // upload image uploadImage() { const form = formidable({}); return new Promise((resolve, reject) => { var _a; // get property query const property = this.request.query.property; // check if user is authenticated const auth = (_a = this.request.linkedAuth) === null || _a === void 0 ? void 0 : _a.userAccount; if (!auth) { console.warn('No user authenticated and no subject provided.'); return; } // get person const user = auth.accountOf; form.parse(this.request, async (err, fields, file, files, body) => { const { file: upload } = file; if (err) { console.warn('Error parsing uploaded file:' + err.stack); reject(err); return; } try { if (!file) { reject('No file uploaded'); return; } // Upload the file and get the public URL //TODO: camera plugin maybe allows for multiple images? that's why we're getting an array with 1 item? See if that can be changed const singleFileUpload = Array.isArray(upload) ? upload[0] : upload; uploadSingleFileFromFormData({ file: singleFileUpload, allowedExtensions: ProfilePlusBackendProvider.ALLOWED_EXTENSIONS, processDataFn: this.compressImage, }) .then((publicUrl) => { const image = new ImageObject(); image.contentUrl = publicUrl; image.save(); // Check user has profile picture and save image to user profile picture and image object if (!user.profilePicture || !user[property]) { const profilePicture = new ProfilePicture(); if (property) { user[property] = profilePicture; } else { user.profilePicture = profilePicture; } profilePicture.save(); } // Update profile picture's image reference if (property) { const propertyId = user[property]; if (propertyId) { propertyId.image = image; } else { throw new Error(`Property '${property}' does not exist on user.`); } } else { user.profilePicture.image = image; } resolve(publicUrl); }) .catch((error) => { console.warn('Error during file upload', error); resolve(null); }); } catch (error) { console.error('Error uploading file:', error); reject(error); } }); }); } async cropImage(imageUrl, croppedArea, property, fileName) { var _a; try { // check if user is authenticated const auth = (_a = this.request.linkedAuth) === null || _a === void 0 ? void 0 : _a.userAccount; if (!auth) { console.warn('cropImage: No user authenticated and no subject provided.'); return; } const user = auth.accountOf; if (!imageUrl) { console.warn('No imageUrl provided.'); return; } let imageData; // Check if imageUrl is a URL or a base64 string if (imageUrl.startsWith('data:')) { // Decode base64 image data const base64Data = imageUrl.replace(/^data:image\/\w+;base64,/, ''); imageData = Buffer.from(base64Data, 'base64'); } else { // Fetch the image data from the provided URL using Fetch API const response = await fetch(imageUrl); if (!response.ok) { throw new Error('Failed to fetch image from URL'); } imageData = await response.buffer(); } const originalImage = await sharp(imageData); const originalImageMeta = await originalImage.metadata(); // console.log('Original: ' + originalImageMeta.width + ' x ' + originalImageMeta.height); // Extract the cropping parameters from the croppedArea object const { width, height, x, y } = croppedArea; // Crop the image using sharp's .extract() method // console.log(`Cropping: left: ${x}, top: ${y}, width: ${width}, height: ${height}`); if (width > originalImageMeta.width || height > originalImageMeta.height) { //tilt the image console.log('Tilting image'); imageData = await sharp(imageData) .rotate(90) .toBuffer() .catch((err) => { console.warn('Error during sharp extract: ' + err.toString()); return null; }); } const croppedImageData = await sharp(imageData) .extract({ left: x, top: y, width: width, height: height }) .toBuffer() .catch((err) => { console.warn('Error during sharp extract: ' + err.toString()); return null; }); // let fileName; // if (filename) { // fileName = filename; // } else { if (!fileName) { // get the original filename and extension from the URL fileName = path.basename(imageUrl); } try { const publicUrl = await uploadSingleFileFromBuffer({ buffer: croppedImageData, fileName: fileName, allowedExtensions: ProfilePlusBackendProvider.ALLOWED_EXTENSIONS, addSuffix: 'cropped', // add suffix to filename }); // console.log('publicUrl', publicUrl); if (!publicUrl) { throw new Error('Upload failed'); } const image = new ImageObject(); image.contentUrl = publicUrl; image.save(); let profilePicture = user[property] || new ProfilePicture(); // update the cropped image profilePicture.cropped = image; // save profile picture will user doesn't have profilePicture property if (!user[property]) { profilePicture.save(); user[property] = profilePicture; } // Return the image url of the saved cropped image // also return the user, so that the updated data will be sent to the frontend return [user, publicUrl]; } catch (err) { console.error('Error during file cropping', err); } } catch (error) { throw new Error('Error cropping the image: ' + error.message); } } // Compress the image async compressImage(imageData) { try { // Create a single sharp pipeline to handle resizing and compression const pipeline = sharp(imageData).toFormat('jpeg').jpeg({ quality: 90 }); // Resize the image to a maximum width of 1024 pixels if needed // const imageInfo = await sharp(imageData).metadata(); // if (imageInfo.width > 1024) { // pipeline.resize({ width: 1024 }); // } // Execute the pipeline and get the compressed image data const compressedData = await pipeline.toBuffer(); return compressedData; } catch (error) { throw new Error('Error compressing the image: ' + error); } } } ProfilePlusBackendProvider.ALLOWED_EXTENSIONS = [ 'jpg', 'png', 'gif', 'webp', 'tiff', 'psd', 'raw', 'bmp', 'heif', 'indd', 'jpeg', ]; export default ProfilePlusBackendProvider; //# sourceMappingURL=backend.js.map