Implement member image management features in backend and frontend
This commit introduces new functionalities for managing member images, including uploading, deleting, and setting primary images. The memberController and memberService have been updated to handle these operations, while new routes have been added to facilitate image management. The frontend has been enhanced with an ImageViewerDialog component that supports image rotation, deletion, and setting primary images. Additionally, improvements to the member view allow for better image handling and display. These changes enhance the overall user experience and functionality of the member management system.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import UserClub from "../models/UserClub.js";
|
||||
import { checkAccess, getUserByToken, hasUserClubAccess } from "../utils/userUtils.js";
|
||||
import Member from "../models/Member.js";
|
||||
import MemberImage from "../models/MemberImage.js";
|
||||
import Participant from "../models/Participant.js";
|
||||
import DiaryDate from "../models/DiaryDates.js";
|
||||
import path from 'path';
|
||||
@@ -31,51 +32,59 @@ class MemberService {
|
||||
where['active'] = true;
|
||||
}
|
||||
const MemberContact = (await import('../models/MemberContact.js')).default;
|
||||
return await Member.findAll({
|
||||
where,
|
||||
include: [{
|
||||
model: MemberContact,
|
||||
as: 'contacts',
|
||||
required: false,
|
||||
attributes: ['id', 'memberId', 'type', 'value', 'isParent', 'parentName', 'isPrimary', 'createdAt', 'updatedAt']
|
||||
}],
|
||||
raw: false // Ensure we get model instances, not plain objects, so getters are called
|
||||
})
|
||||
.then(members => {
|
||||
return members.map(member => {
|
||||
const imagePath = path.join('images', 'members', `${member.id}.jpg`);
|
||||
const hasImage = fs.existsSync(imagePath);
|
||||
const memberJson = member.toJSON();
|
||||
// Ensure contacts are properly serialized - access via model instance to trigger getters
|
||||
if (member.contacts && Array.isArray(member.contacts)) {
|
||||
memberJson.contacts = member.contacts.map(contact => {
|
||||
// Access properties through the model instance to trigger getters
|
||||
return {
|
||||
id: contact.id,
|
||||
memberId: contact.memberId,
|
||||
type: contact.type,
|
||||
value: contact.value, // Getter should decrypt this
|
||||
isParent: contact.isParent,
|
||||
parentName: contact.parentName, // Getter should decrypt this
|
||||
isPrimary: contact.isPrimary,
|
||||
createdAt: contact.createdAt,
|
||||
updatedAt: contact.updatedAt
|
||||
};
|
||||
});
|
||||
try {
|
||||
const members = await Member.findAll({
|
||||
where,
|
||||
include: [
|
||||
{
|
||||
model: MemberContact,
|
||||
as: 'contacts',
|
||||
required: false,
|
||||
attributes: ['id', 'memberId', 'type', 'value', 'isParent', 'parentName', 'isPrimary', 'createdAt', 'updatedAt']
|
||||
},
|
||||
{
|
||||
model: MemberImage,
|
||||
as: 'images',
|
||||
required: false,
|
||||
attributes: ['id', 'memberId', 'fileName', 'sortOrder', 'createdAt', 'updatedAt']
|
||||
}
|
||||
return {
|
||||
...memberJson,
|
||||
hasImage: hasImage
|
||||
};
|
||||
});
|
||||
})
|
||||
.then(membersWithImageStatus => {
|
||||
return membersWithImageStatus;
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('[getClubMembers] - Error:', error);
|
||||
throw error;
|
||||
],
|
||||
raw: false
|
||||
});
|
||||
|
||||
const results = [];
|
||||
for (const member of members) {
|
||||
const memberJson = member.toJSON();
|
||||
|
||||
if (member.contacts && Array.isArray(member.contacts)) {
|
||||
memberJson.contacts = member.contacts.map(contact => ({
|
||||
id: contact.id,
|
||||
memberId: contact.memberId,
|
||||
type: contact.type,
|
||||
value: contact.value,
|
||||
isParent: contact.isParent,
|
||||
parentName: contact.parentName,
|
||||
isPrimary: contact.isPrimary,
|
||||
createdAt: contact.createdAt,
|
||||
updatedAt: contact.updatedAt
|
||||
}));
|
||||
}
|
||||
|
||||
const imageData = await this._prepareMemberImages(member, { forceReload: true });
|
||||
memberJson.images = imageData.images;
|
||||
memberJson.primaryImageId = imageData.primaryImageId;
|
||||
memberJson.primaryImageUrl = imageData.primaryImageUrl;
|
||||
memberJson.imageUrl = imageData.primaryImageUrl;
|
||||
memberJson.hasImage = imageData.hasImage;
|
||||
|
||||
results.push(memberJson);
|
||||
}
|
||||
|
||||
return results;
|
||||
} catch (error) {
|
||||
console.error('[getClubMembers] - Error:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async setClubMember(userToken, clubId, memberId, firstName, lastName, street, city, postalCode, birthdate, phone, email, active = true, testMembership = false,
|
||||
@@ -182,38 +191,118 @@ class MemberService {
|
||||
}
|
||||
}
|
||||
|
||||
async uploadMemberImage(userToken, clubId, memberId, imageBuffer) {
|
||||
async uploadMemberImage(userToken, clubId, memberId, imageBuffer, options = {}) {
|
||||
const { makePrimary = false } = options;
|
||||
let transaction = null;
|
||||
try {
|
||||
devLog('------>', userToken, clubId, memberId, imageBuffer);
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, error: 'Member not found in this club' };
|
||||
return { status: 404, response: { success: false, error: 'Member not found in this club' } };
|
||||
}
|
||||
const imagePath = path.join('images', 'members', `${memberId}.jpg`);
|
||||
await sharp(imageBuffer)
|
||||
.resize(600, 600)
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(imagePath);
|
||||
|
||||
return { status: 200, message: 'Image uploaded successfully' };
|
||||
await this._migrateLegacyImage(memberId);
|
||||
|
||||
transaction = await MemberImage.sequelize.transaction();
|
||||
|
||||
const maxSortOrder = await MemberImage.max('sortOrder', {
|
||||
where: { memberId },
|
||||
transaction
|
||||
});
|
||||
const sortOrder = Number.isFinite(maxSortOrder) ? (maxSortOrder + 1) : 1;
|
||||
|
||||
const imageRecord = await MemberImage.create({
|
||||
memberId,
|
||||
fileName: '',
|
||||
sortOrder
|
||||
}, { transaction });
|
||||
|
||||
const directoryPath = await this._ensureImageDirectory(memberId);
|
||||
const fileName = `${imageRecord.id}.jpg`;
|
||||
const targetPath = path.join(directoryPath, fileName);
|
||||
|
||||
await sharp(imageBuffer)
|
||||
.resize(600, 600, { fit: 'inside' })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(targetPath);
|
||||
|
||||
imageRecord.fileName = fileName;
|
||||
await imageRecord.save({ transaction });
|
||||
|
||||
if (makePrimary) {
|
||||
await this._setPrimaryImage(memberId, imageRecord.id, { transaction });
|
||||
}
|
||||
|
||||
await transaction.commit();
|
||||
transaction = null;
|
||||
|
||||
const imageData = await this._prepareMemberImages(member, { forceReload: true });
|
||||
const createdImage = imageData.images.find(img => img.id === imageRecord.id) || null;
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
success: true,
|
||||
image: createdImage,
|
||||
images: imageData.images,
|
||||
primaryImageId: imageData.primaryImageId,
|
||||
primaryImageUrl: imageData.primaryImageUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (transaction) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackError) {
|
||||
console.error('[uploadMemberImage] - Rollback failed:', rollbackError);
|
||||
}
|
||||
}
|
||||
console.error('[uploadMemberImage] - Error:', error);
|
||||
return { status: 500, error: 'Failed to upload image' };
|
||||
return { status: 500, response: { success: false, error: 'Failed to upload image' } };
|
||||
}
|
||||
}
|
||||
|
||||
async getMemberImage(userToken, clubId, memberId) {
|
||||
async getMemberImage(userToken, clubId, memberId, imageId = null) {
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, error: 'Member not found in this club' };
|
||||
}
|
||||
const imagePath = path.join('images', 'members', `${memberId}.jpg`);
|
||||
const migratedImage = await this._migrateLegacyImage(memberId);
|
||||
if (migratedImage && (!imageId || imageId === migratedImage.id)) {
|
||||
imageId = migratedImage.id;
|
||||
}
|
||||
|
||||
let imageRecord = null;
|
||||
if (imageId) {
|
||||
imageRecord = await MemberImage.findOne({
|
||||
where: { id: imageId, memberId }
|
||||
});
|
||||
}
|
||||
|
||||
if (!imageRecord) {
|
||||
imageRecord = await MemberImage.findOne({
|
||||
where: { memberId },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']]
|
||||
});
|
||||
}
|
||||
|
||||
if (!imageRecord) {
|
||||
const legacyPath = this._findLegacyImagePath(memberId);
|
||||
if (legacyPath) {
|
||||
return { status: 200, imagePath: path.resolve(legacyPath) };
|
||||
}
|
||||
return { status: 404, error: 'Image not found' };
|
||||
}
|
||||
|
||||
const directoryPath = path.join('images', 'members', String(memberId));
|
||||
const imagePath = path.join(directoryPath, imageRecord.fileName);
|
||||
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
return { status: 404, error: 'Image not found' };
|
||||
}
|
||||
|
||||
return { status: 200, imagePath: path.resolve(imagePath) };
|
||||
} catch (error) {
|
||||
console.error('[getMemberImage] - Error:', error);
|
||||
@@ -527,7 +616,7 @@ class MemberService {
|
||||
return this._updateRatingsInternal(userId, clubId);
|
||||
}
|
||||
|
||||
async rotateMemberImage(userToken, clubId, memberId, direction) {
|
||||
async rotateMemberImage(userToken, clubId, memberId, imageId, direction) {
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId: clubId } });
|
||||
@@ -535,35 +624,49 @@ class MemberService {
|
||||
if (!member) {
|
||||
return { status: 404, response: { success: false, error: 'Member not found in this club' } };
|
||||
}
|
||||
|
||||
const imagePath = path.join('images', 'members', `${memberId}.jpg`);
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
|
||||
await this._migrateLegacyImage(memberId);
|
||||
|
||||
const imageRecord = await MemberImage.findOne({
|
||||
where: { id: imageId, memberId }
|
||||
});
|
||||
|
||||
if (!imageRecord) {
|
||||
return { status: 404, response: { success: false, error: 'Image not found' } };
|
||||
}
|
||||
|
||||
// Read the image
|
||||
|
||||
const imagePath = path.join('images', 'members', String(memberId), imageRecord.fileName);
|
||||
if (!fs.existsSync(imagePath)) {
|
||||
return { status: 404, response: { success: false, error: 'Image file not found on disk' } };
|
||||
}
|
||||
|
||||
const imageBuffer = await fs.promises.readFile(imagePath);
|
||||
|
||||
// Calculate rotation angle (-90 for left, +90 for right)
|
||||
const rotationAngle = direction === 'left' ? -90 : 90;
|
||||
|
||||
// Rotate the image
|
||||
|
||||
const rotatedBuffer = await sharp(imageBuffer)
|
||||
.rotate(rotationAngle)
|
||||
.jpeg({ quality: 80 })
|
||||
.toBuffer();
|
||||
|
||||
// Save the rotated image
|
||||
|
||||
await fs.promises.writeFile(imagePath, rotatedBuffer);
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
success: true,
|
||||
|
||||
await imageRecord.update({ updatedAt: new Date() });
|
||||
|
||||
const imageData = await this._prepareMemberImages(member, { forceReload: true });
|
||||
const updatedImage = imageData.images.find(img => img.id === imageRecord.id) || null;
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
success: true,
|
||||
message: `Bild um ${rotationAngle}° gedreht`,
|
||||
direction: direction,
|
||||
rotation: rotationAngle
|
||||
}
|
||||
direction,
|
||||
rotation: rotationAngle,
|
||||
image: updatedImage,
|
||||
images: imageData.images,
|
||||
primaryImageId: imageData.primaryImageId,
|
||||
primaryImageUrl: imageData.primaryImageUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('[rotateMemberImage] - Error:', error);
|
||||
@@ -577,6 +680,121 @@ class MemberService {
|
||||
}
|
||||
}
|
||||
|
||||
async deleteMemberImage(userToken, clubId, memberId, imageId) {
|
||||
let transaction = null;
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, response: { success: false, error: 'Member not found in this club' } };
|
||||
}
|
||||
|
||||
await this._migrateLegacyImage(memberId);
|
||||
|
||||
transaction = await MemberImage.sequelize.transaction();
|
||||
const imageRecord = await MemberImage.findOne({
|
||||
where: { id: imageId, memberId },
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE
|
||||
});
|
||||
|
||||
if (!imageRecord) {
|
||||
await transaction.rollback();
|
||||
transaction = null;
|
||||
return { status: 404, response: { success: false, error: 'Image not found' } };
|
||||
}
|
||||
|
||||
const imagePath = path.join('images', 'members', String(memberId), imageRecord.fileName);
|
||||
|
||||
await imageRecord.destroy({ transaction });
|
||||
await this._resequenceMemberImages(memberId, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
transaction = null;
|
||||
|
||||
if (fs.existsSync(imagePath)) {
|
||||
await fs.promises.unlink(imagePath).catch(err => {
|
||||
console.warn('[deleteMemberImage] - Failed to remove image file:', err);
|
||||
});
|
||||
}
|
||||
|
||||
const imageData = await this._prepareMemberImages(member, { forceReload: true });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
success: true,
|
||||
images: imageData.images,
|
||||
primaryImageId: imageData.primaryImageId,
|
||||
primaryImageUrl: imageData.primaryImageUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (transaction) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackError) {
|
||||
console.error('[deleteMemberImage] - Rollback failed:', rollbackError);
|
||||
}
|
||||
}
|
||||
console.error('[deleteMemberImage] - Error:', error);
|
||||
return { status: 500, response: { success: false, error: 'Failed to delete image' } };
|
||||
}
|
||||
}
|
||||
|
||||
async setPrimaryMemberImage(userToken, clubId, memberId, imageId) {
|
||||
let transaction = null;
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
const member = await Member.findOne({ where: { id: memberId, clubId } });
|
||||
if (!member) {
|
||||
return { status: 404, response: { success: false, error: 'Member not found in this club' } };
|
||||
}
|
||||
|
||||
await this._migrateLegacyImage(memberId);
|
||||
|
||||
transaction = await MemberImage.sequelize.transaction();
|
||||
const imageRecord = await MemberImage.findOne({
|
||||
where: { id: imageId, memberId },
|
||||
transaction,
|
||||
lock: transaction.LOCK.UPDATE
|
||||
});
|
||||
|
||||
if (!imageRecord) {
|
||||
await transaction.rollback();
|
||||
transaction = null;
|
||||
return { status: 404, response: { success: false, error: 'Image not found' } };
|
||||
}
|
||||
|
||||
await this._setPrimaryImage(memberId, imageId, { transaction });
|
||||
|
||||
await transaction.commit();
|
||||
transaction = null;
|
||||
|
||||
const imageData = await this._prepareMemberImages(member, { forceReload: true });
|
||||
|
||||
return {
|
||||
status: 200,
|
||||
response: {
|
||||
success: true,
|
||||
images: imageData.images,
|
||||
primaryImageId: imageData.primaryImageId,
|
||||
primaryImageUrl: imageData.primaryImageUrl
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
if (transaction) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackError) {
|
||||
console.error('[setPrimaryMemberImage] - Rollback failed:', rollbackError);
|
||||
}
|
||||
}
|
||||
console.error('[setPrimaryMemberImage] - Error:', error);
|
||||
return { status: 500, response: { success: false, error: 'Failed to set primary image' } };
|
||||
}
|
||||
}
|
||||
|
||||
async quickUpdateTestMembership(userToken, clubId, memberId) {
|
||||
try {
|
||||
await checkAccess(userToken, clubId);
|
||||
@@ -651,6 +869,206 @@ class MemberService {
|
||||
return { status: 500, response: { error: 'Failed to deactivate member' } };
|
||||
}
|
||||
}
|
||||
|
||||
async _ensureImageDirectory(memberId) {
|
||||
const directoryPath = path.join('images', 'members', String(memberId));
|
||||
await fs.promises.mkdir(directoryPath, { recursive: true });
|
||||
return directoryPath;
|
||||
}
|
||||
|
||||
async _fetchMemberImages(memberId, transaction = null) {
|
||||
return MemberImage.findAll({
|
||||
where: { memberId },
|
||||
order: [['sortOrder', 'ASC'], ['id', 'ASC']],
|
||||
transaction
|
||||
});
|
||||
}
|
||||
|
||||
async _prepareMemberImages(member, options = {}) {
|
||||
const { forceReload = false } = options;
|
||||
let images = [];
|
||||
|
||||
if (!forceReload && Array.isArray(member.images) && member.images.length > 0) {
|
||||
images = member.images;
|
||||
} else {
|
||||
images = await this._fetchMemberImages(member.id);
|
||||
}
|
||||
|
||||
if (!images || images.length === 0) {
|
||||
const migrated = await this._migrateLegacyImage(member.id);
|
||||
if (migrated) {
|
||||
images = await this._fetchMemberImages(member.id);
|
||||
}
|
||||
}
|
||||
|
||||
if (!images || images.length === 0) {
|
||||
return {
|
||||
images: [],
|
||||
primaryImageId: null,
|
||||
primaryImageUrl: null,
|
||||
hasImage: false
|
||||
};
|
||||
}
|
||||
|
||||
const mappedImages = images
|
||||
.map(image => this._mapMemberImageRecord(member, image))
|
||||
.sort((a, b) => {
|
||||
const sortA = Number.isFinite(a.sortOrder) ? a.sortOrder : parseInt(a.sortOrder || '0', 10);
|
||||
const sortB = Number.isFinite(b.sortOrder) ? b.sortOrder : parseInt(b.sortOrder || '0', 10);
|
||||
if (sortA !== sortB) {
|
||||
return sortA - sortB;
|
||||
}
|
||||
return (a.id || 0) - (b.id || 0);
|
||||
});
|
||||
|
||||
const primary = mappedImages[0] || null;
|
||||
|
||||
mappedImages.forEach((image, index) => {
|
||||
image.isPrimary = index === 0;
|
||||
});
|
||||
|
||||
return {
|
||||
images: mappedImages,
|
||||
primaryImageId: primary ? primary.id : null,
|
||||
primaryImageUrl: primary ? primary.url : null,
|
||||
hasImage: mappedImages.length > 0
|
||||
};
|
||||
}
|
||||
|
||||
_mapMemberImageRecord(member, image) {
|
||||
const data = image && typeof image.toJSON === 'function' ? image.toJSON() : image;
|
||||
const updatedAt = data.updatedAt ? new Date(data.updatedAt) : null;
|
||||
const cacheParam = updatedAt && !Number.isNaN(updatedAt.getTime()) ? updatedAt.getTime() : Date.now();
|
||||
|
||||
return {
|
||||
id: data.id,
|
||||
memberId: member.id,
|
||||
fileName: data.fileName,
|
||||
sortOrder: data.sortOrder,
|
||||
createdAt: data.createdAt,
|
||||
updatedAt: data.updatedAt,
|
||||
url: `/api/clubmembers/image/${member.clubId}/${member.id}/${data.id}?t=${cacheParam}`
|
||||
};
|
||||
}
|
||||
|
||||
async _setPrimaryImage(memberId, imageId, options = {}) {
|
||||
const { transaction = null } = options;
|
||||
const images = await this._fetchMemberImages(memberId, transaction);
|
||||
if (!images || images.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
let nextOrder = 2;
|
||||
for (const image of images) {
|
||||
if (image.id === imageId) {
|
||||
image.sortOrder = 1;
|
||||
} else {
|
||||
image.sortOrder = nextOrder;
|
||||
nextOrder += 1;
|
||||
}
|
||||
await image.save({ transaction });
|
||||
}
|
||||
}
|
||||
|
||||
async _resequenceMemberImages(memberId, options = {}) {
|
||||
const { transaction = null } = options;
|
||||
const images = await this._fetchMemberImages(memberId, transaction);
|
||||
let order = 1;
|
||||
for (const image of images) {
|
||||
if (image.sortOrder !== order) {
|
||||
image.sortOrder = order;
|
||||
await image.save({ transaction });
|
||||
}
|
||||
order += 1;
|
||||
}
|
||||
return images;
|
||||
}
|
||||
|
||||
async _migrateLegacyImage(memberId) {
|
||||
const legacyPath = this._findLegacyImagePath(memberId);
|
||||
if (!legacyPath) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const existing = await MemberImage.findOne({ where: { memberId } });
|
||||
const directoryPath = await this._ensureImageDirectory(memberId);
|
||||
|
||||
if (existing) {
|
||||
const fileName = existing.fileName || `${existing.id}.jpg`;
|
||||
const targetPath = path.join(directoryPath, fileName);
|
||||
await this._processLegacyImage(legacyPath, targetPath);
|
||||
if (!existing.fileName || existing.fileName !== fileName) {
|
||||
existing.fileName = fileName;
|
||||
await existing.save();
|
||||
}
|
||||
return existing;
|
||||
}
|
||||
|
||||
let transaction = null;
|
||||
try {
|
||||
transaction = await MemberImage.sequelize.transaction();
|
||||
const imageRecord = await MemberImage.create({
|
||||
memberId,
|
||||
fileName: '',
|
||||
sortOrder: 1
|
||||
}, { transaction });
|
||||
|
||||
const fileName = `${imageRecord.id}.jpg`;
|
||||
const targetPath = path.join(directoryPath, fileName);
|
||||
await this._processLegacyImage(legacyPath, targetPath);
|
||||
|
||||
imageRecord.fileName = fileName;
|
||||
await imageRecord.save({ transaction });
|
||||
await transaction.commit();
|
||||
transaction = null;
|
||||
return imageRecord;
|
||||
} catch (error) {
|
||||
if (transaction) {
|
||||
try {
|
||||
await transaction.rollback();
|
||||
} catch (rollbackError) {
|
||||
console.error('[MemberService] - Legacy migration rollback failed:', rollbackError);
|
||||
}
|
||||
}
|
||||
console.error('[MemberService] - Legacy image migration failed:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
_findLegacyImagePath(memberId) {
|
||||
const directory = path.join('images', 'members');
|
||||
const extensions = ['jpg', 'jpeg', 'png', 'gif', 'webp'];
|
||||
for (const ext of extensions) {
|
||||
const lowerPath = path.join(directory, `${memberId}.${ext}`);
|
||||
if (fs.existsSync(lowerPath)) {
|
||||
return lowerPath;
|
||||
}
|
||||
const upperPath = path.join(directory, `${memberId}.${ext.toUpperCase()}`);
|
||||
if (fs.existsSync(upperPath)) {
|
||||
return upperPath;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
async _processLegacyImage(sourcePath, targetPath) {
|
||||
try {
|
||||
await sharp(sourcePath)
|
||||
.resize(600, 600, { fit: 'inside' })
|
||||
.jpeg({ quality: 80 })
|
||||
.toFile(targetPath);
|
||||
} catch (error) {
|
||||
console.error('[MemberService] - Failed to process legacy image, falling back to copy:', error);
|
||||
await fs.promises.copyFile(sourcePath, targetPath);
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.promises.unlink(sourcePath);
|
||||
} catch (unlinkError) {
|
||||
// Datei konnte nicht gelöscht werden – loggen, aber nicht blockieren
|
||||
console.warn('[MemberService] - Unable to remove legacy image after migration:', unlinkError.message || unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new MemberService();
|
||||
Reference in New Issue
Block a user