mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
chore: relocate backend into apps/api ahead of monorepo merge
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,73 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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 };
|
||||
Reference in New Issue
Block a user