Avatar tárolási architektúra
Ez a dokumentum a profilkép (avatar) tárolás technikai megvalósítását írja le a CADENSA rendszerben, Wasabi S3 objektumtárolás használatával.
Áttekintés
A profilképek Wasabi S3 felhőtárhelyen tárolódnak, nem a helyi fájlrendszeren vagy az adatbázisban. A backend minden feltöltött képet feldolgoz mielőtt tárolná: 400×400 pixelre méretezi, WebP formátumra konvertálja, majd az optimalizált fájlt feltölti a cadensa-avatars bucketbe.
A tárolt képekhez való hozzáférés előre aláírt URL-eken (pre-signed URL) keresztül történik, 7 napos lejárattal. A nyers Wasabi objektumkulcs kerül az adatbázisba; az aláírt URL minden API válasz esetén generálódik.
Infrastruktúra
| Tulajdonság | Érték |
|---|---|
| Szolgáltató | Wasabi |
| Régió | eu-central-2 |
| Bucket | cadensa-avatars |
| Object Lock | Kikapcsolva |
| Publikus hozzáférés | ❌ Nem (csak előre aláírt URL-en keresztül) |
| Signed URL lejárat | 7 nap |
Környezeti változók
WASABI_ACCESS_KEY_ID=...
WASABI_SECRET_ACCESS_KEY=...
WASABI_REGION=eu-central-2
WASABI_ENDPOINT=https://s3.eu-central-2.wasabisys.com
WASABI_BUCKET=cadensa-files # általános célú bucket
WASABI_BUCKET_AVATARS=cadensa-avatars
Feltöltési pipeline
Kliens (böngésző/app)
│
│ POST /api/upload/avatar (multipart/form-data)
▼
multer (memoryStorage)
│ req.file.buffer ← kép RAM-ban, soha nem kerül lemezre
▼
avatarOptimizer.ts
│ sharp: 400×400 cover crop átméretezés
│ WebP konverzió, 80% minőség
│ visszaad: { buffer, contentType: 'image/webp', ext: '.webp', originalSize, optimizedSize }
▼
wasabi-s3.service.ts → uploadAvatar()
│ kulcs: avatars/{userId}/{timestamp}.webp
│ bucket: cadensa-avatars
▼
Adatbázis
│ user.avatar = "wasabi:avatars/{userId}/{timestamp}.webp"
▼
API válasz
│ url: "wasabi:avatars/..." (tárolt kulcs, DB-hez)
│ signedUrl: "https://..." (előre aláírt, 7 nap)
Objektumkulcs formátuma
Az objektumkulcsok a következő mintát követik:
avatars/{userId}/{unixTimestampMs}.webp
Példa:
avatars/6627a3f1e2b4c10012345678/1748000000000.webp
A MongoDB-ben a user.avatar mezőben tárolt érték wasabi: előtaggal van ellátva:
wasabi:avatars/6627a3f1e2b4c10012345678/1748000000000.webp
Ez az előtag lehetővé teszi a backend számára, hogy megkülönböztesse a Wasabi kulcsokat a régebbi adatoktól (pl. Gravatar URL-ek, helyi fájlnevek).
Képoptimalizálás
Megvalósítva a cadensa-backend/src/services/upload/avatarOptimizer.ts fájlban, a sharp library használatával.
| Paraméter | Érték |
|---|---|
| Méretek | 400 × 400 px |
| Illesztés | cover (középre igazított vágás) |
| Formátum | WebP |
| Minőség | 80% |
| Tipikus méretcsökkentés | ~95–97% az eredeti JPEG/PNG-hez képest |
// avatarOptimizer.ts (egyszerűsítve)
export async function optimizeAvatar(buffer: Buffer, originalSize: number) {
const optimized = await sharp(buffer)
.resize(400, 400, { fit: 'cover', position: 'centre' })
.webp({ quality: 80 })
.toBuffer();
return {
buffer: optimized,
contentType: 'image/webp',
ext: '.webp',
originalSize,
optimizedSize: optimized.length,
};
}
Előre aláírt URL folyamat
Mivel a cadensa-avatars bucket nem publikusan elérhető, minden olvasáshoz előre aláírt URL szükséges.
1. Kliens meghív egy végpontot, amely felhasználói adatokat ad vissza
(GET /auth/me, GET /user/profile, GET /team/:id/members, …)
2. Backend feloldja a nyers avatar kulcsot:
resolveAvatarUrl("wasabi:avatars/...") → getSignedUrl(...)
3. Az AWS SDK generál egy ideiglenes aláírt URL-t (7 napos lejárat)
4. Az aláírt URL visszakerül a kliensnek a JSON válaszban
5. A kliens közvetlenül az aláírt URL-ről jeleníti meg a képet
Az aláírt URL lekérdezési paramétereiben tartalmazza a hitelesítési adatokat, és 7 nap után automatikusan lejár. A következő API hívásnál új aláírt URL generálódik.
Főbb forrásfájlok
| Fájl | Feladat |
|---|---|
src/services/storage/wasabi-s3.service.ts | S3 kliens, uploadAvatar(), deleteAvatar(), getAvatarSignedUrl() |
src/services/upload/avatarOptimizer.ts | sharp-alapú képátméretezés + WebP konverzió |
src/services/upload/Upload.service.ts | multer konfiguráció (memoryStorage) |
src/controllers/upload.controller.ts | POST /api/upload/avatar kezelő |
src/utils/avatarResolver.ts | resolveAvatarUrl() + resolveAvatarsInList() segédfüggvények |
avatarResolver.ts használata
A resolveAvatarUrl() az adatbázis nyers értékét aláírt URL-lé alakítja (vagy üres stringet ad vissza üres/nem-Wasabi értékek esetén):
import { resolveAvatarUrl, resolveAvatarsInList } from '../utils/avatarResolver';
// Egy felhasználó
const signedUrl = await resolveAvatarUrl(user.avatar);
// Csapattagok listája
await resolveAvatarsInList(
members,
(m) => m.avatar,
(m, url) => { m.avatar = url; }
);
Mindig hívd meg ezeket a segédfüggvényeket az API válasz elküldése előtt, hogy a kliensek soha ne kapjanak nyers wasabi: kulcsot.
Controllerek, amelyek feloldják az avatarokat
| Controller | Végpont | Feloldás helye |
|---|---|---|
auth.controller.ts | POST /auth/login | Bejelentkezés után, JWT válasz előtt |
auth.controller.ts | GET /auth/me | fetchCurrentUser kezelőben |
user.controller.ts | GET /user/profile | getProfile kezelőben |
team.controller.ts | GET /team/:id/members | enrichedMembers lista felépítése után |
Frontend kezelés
- Sikeres feltöltés után a frontend megkapja az
url(awasabi:kulcs) és asignedUrl(az ideiglenes képURL) értékeket. - A
wasabi:kulcs kerül mentésre a felhasználói profilban (a jövőbeli API hívásokhoz). - A
signedUrlaz azonnali helyi megjelenítéshez használatos, extra API körút nélkül. - Az
avatarUtils.tsésavatarUrl.tsundefined-t ad vissza mindenwasabi:-val kezdődő értékre, így a komponens kezdőbetűkre esik vissza — a feloldott aláírt URL mindig az API-tól kell érkezzen, nem a Redux store-ból.
Avatar törlése
Amikor a felhasználó eltávolítja a profilképét, a deleteAvatar(key) meghívódik a Wasabi szolgáltatáson, mielőtt a user.avatar mező törlődne az adatbázisban:
await wasabiService.deleteAvatar('avatars/{userId}/{timestamp}.webp');
A delete metódusnak a wasabi: előtag nélküli nyers kulcs kerül átadásra.