mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
74 lines
3.2 KiB
JavaScript
74 lines
3.2 KiB
JavaScript
/***********************************************************************************************************************************************************************
|
|
* File Name: avatar.service.js
|
|
* Type of Program: Service
|
|
* Description: Shared avatar processing/orchestration for admin + client self-profile.
|
|
* Server-side authoritative resize — every avatar is normalized to a fixed
|
|
* 200x200 JPEG regardless of what the client sends, so browser-side cropping
|
|
* (see AvatarUploadDialog.jsx) is a UX convenience, not the enforcement point.
|
|
*
|
|
* Author: Kenneth Obsequio (@lash0000)
|
|
* Date Created: Aug. 10, 2026
|
|
***********************************************************************************************************************************************************************/
|
|
'use strict';
|
|
|
|
const sharp = require('sharp');
|
|
const { uploadFile, deleteFile } = require('./s3.service');
|
|
|
|
const AVATAR_SIZE = 200;
|
|
const JPEG_QUALITY = 90;
|
|
|
|
// ─── resizeAvatarBuffer ─────────────────────────────────────────────────────────
|
|
// Normalizes any accepted input (JPEG/PNG/WebP/GIF) into a fixed 200x200 JPEG.
|
|
// GIF animation and PNG transparency are intentionally dropped — avatars render
|
|
// in an opaque round mask, so a single static frame is all that's ever shown.
|
|
|
|
async function resizeAvatarBuffer(buffer) {
|
|
try {
|
|
return await sharp(buffer)
|
|
.rotate() // respect EXIF orientation before crop
|
|
.resize(AVATAR_SIZE, AVATAR_SIZE, { fit: 'cover', position: 'centre' })
|
|
.jpeg({ quality: JPEG_QUALITY })
|
|
.toBuffer();
|
|
} catch (err) {
|
|
throw Object.assign(new Error('Could not process the uploaded image.'), { status: 400, cause: err });
|
|
}
|
|
}
|
|
|
|
// ─── replaceUserAvatar ──────────────────────────────────────────────────────────
|
|
// Deletes the old S3 object (if any), resizes the new upload, stores it, and
|
|
// returns the avatar metadata object. Does not persist to the user row —
|
|
// callers own that so they can merge it into personal_info their own way.
|
|
|
|
async function replaceUserAvatar(user, file) {
|
|
const oldKey = user.personal_info?.avatar?.uuid;
|
|
if (oldKey) await deleteFile(oldKey).catch(() => {});
|
|
|
|
const resized = await resizeAvatarBuffer(file.buffer);
|
|
|
|
const { url, uuid } = await uploadFile({
|
|
buffer: resized,
|
|
originalname: 'avatar.jpg',
|
|
mimetype: 'image/jpeg',
|
|
ownerType: 'avatar',
|
|
});
|
|
|
|
return {
|
|
url,
|
|
uuid,
|
|
name: file.originalname,
|
|
mime_type: 'image/jpeg',
|
|
size: resized.length,
|
|
};
|
|
}
|
|
|
|
// ─── removeUserAvatar ───────────────────────────────────────────────────────────
|
|
|
|
async function removeUserAvatar(user) {
|
|
const key = user.personal_info?.avatar?.uuid;
|
|
if (!key) throw Object.assign(new Error('No avatar to remove.'), { status: 404 });
|
|
|
|
await deleteFile(key).catch(() => {});
|
|
}
|
|
|
|
module.exports = { resizeAvatarBuffer, replaceUserAvatar, removeUserAvatar };
|