profile-plus
Version:
253 lines • 11.2 kB
JavaScript
//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