@proteinjs/user-server 1.5.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/LICENSE +21 -0
  3. package/dist/generated/index.d.ts +1 -4
  4. package/dist/generated/index.d.ts.map +1 -1
  5. package/dist/generated/index.js +6 -7
  6. package/dist/generated/index.js.map +1 -1
  7. package/dist/src/authentication/DbSessionStore.d.ts +0 -1
  8. package/dist/src/authentication/DbSessionStore.d.ts.map +1 -1
  9. package/dist/src/routes/avatar.d.ts +19 -0
  10. package/dist/src/routes/avatar.d.ts.map +1 -0
  11. package/dist/src/routes/avatar.js +108 -0
  12. package/dist/src/routes/avatar.js.map +1 -0
  13. package/dist/src/routes/devLogin.d.ts.map +1 -1
  14. package/dist/src/routes/devLogin.js +11 -0
  15. package/dist/src/routes/devLogin.js.map +1 -1
  16. package/dist/src/services/UpdateUserInfo.d.ts +40 -2
  17. package/dist/src/services/UpdateUserInfo.d.ts.map +1 -1
  18. package/dist/src/services/UpdateUserInfo.js +183 -12
  19. package/dist/src/services/UpdateUserInfo.js.map +1 -1
  20. package/dist/test/UpdateUserInfoAvatar.test.d.ts +2 -0
  21. package/dist/test/UpdateUserInfoAvatar.test.d.ts.map +1 -0
  22. package/dist/test/UpdateUserInfoAvatar.test.js +337 -0
  23. package/dist/test/UpdateUserInfoAvatar.test.js.map +1 -0
  24. package/dist/test/UpdateUserInfoSessionRefresh.test.d.ts +2 -0
  25. package/dist/test/UpdateUserInfoSessionRefresh.test.d.ts.map +1 -0
  26. package/dist/test/UpdateUserInfoSessionRefresh.test.js +94 -0
  27. package/dist/test/UpdateUserInfoSessionRefresh.test.js.map +1 -0
  28. package/dist/test/UserServerTestEnvironment.d.ts +26 -0
  29. package/dist/test/UserServerTestEnvironment.d.ts.map +1 -0
  30. package/dist/test/UserServerTestEnvironment.js +202 -0
  31. package/dist/test/UserServerTestEnvironment.js.map +1 -0
  32. package/dist/test/avatarRoute.test.d.ts +2 -0
  33. package/dist/test/avatarRoute.test.d.ts.map +1 -0
  34. package/dist/test/avatarRoute.test.js +222 -0
  35. package/dist/test/avatarRoute.test.js.map +1 -0
  36. package/generated/index.ts +21 -25
  37. package/jest.config.js +4 -1
  38. package/package.json +9 -5
  39. package/src/heic-decode.d.ts +13 -0
  40. package/src/routes/avatar.ts +57 -0
  41. package/src/routes/devLogin.ts +5 -0
  42. package/src/services/UpdateUserInfo.ts +124 -13
  43. package/test/UpdateUserInfoAvatar.test.ts +174 -0
  44. package/test/UpdateUserInfoSessionRefresh.test.ts +37 -0
  45. package/test/UserServerTestEnvironment.ts +97 -0
  46. package/test/avatarRoute.test.ts +111 -0
  47. package/test/emulatorLock.globalSetup.js +11 -0
  48. package/test/emulatorLock.globalTeardown.js +8 -0
  49. package/test/emulatorLock.js +124 -0
  50. package/test/fixtures/fixture.heic +0 -0
  51. package/tsconfig.json +1 -0
@@ -51,10 +51,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
51
51
  };
52
52
  Object.defineProperty(exports, "__esModule", { value: true });
53
53
  exports.UpdateUserInfo = void 0;
54
+ var sharp_1 = __importDefault(require("sharp"));
55
+ var heic_decode_1 = __importDefault(require("heic-decode"));
56
+ var sha256_1 = __importDefault(require("crypto-js/sha256"));
54
57
  var db_1 = require("@proteinjs/db");
58
+ var db_file_1 = require("@proteinjs/db-file");
55
59
  var user_1 = require("@proteinjs/user");
56
60
  var email_server_1 = require("@proteinjs/email-server");
57
- var sha256_1 = __importDefault(require("crypto-js/sha256"));
58
61
  var UpdateUserInfo = /** @class */ (function () {
59
62
  function UpdateUserInfo() {
60
63
  this.serviceMetadata = {
@@ -65,16 +68,10 @@ var UpdateUserInfo = /** @class */ (function () {
65
68
  }
66
69
  UpdateUserInfo.prototype.updateName = function (name) {
67
70
  return __awaiter(this, void 0, void 0, function () {
68
- var db, user;
69
71
  return __generator(this, function (_a) {
70
72
  switch (_a.label) {
71
- case 0:
72
- db = (0, db_1.getDbAsSystem)();
73
- user = new user_1.UserRepo().getUser();
74
- return [4 /*yield*/, db.update(user_1.tables.User, { name: name }, { id: user.id })];
75
- case 1:
76
- _a.sent();
77
- return [2 /*return*/];
73
+ case 0: return [4 /*yield*/, this.saveUserInfo({ name: name })];
74
+ case 1: return [2 /*return*/, _a.sent()];
78
75
  }
79
76
  });
80
77
  });
@@ -119,9 +116,7 @@ var UpdateUserInfo = /** @class */ (function () {
119
116
  case 5:
120
117
  _b.trys.push([5, 7, , 8]);
121
118
  hashedNewPassword = (0, sha256_1.default)(newPassword).toString();
122
- return [4 /*yield*/, db.update(user_1.tables.User, {
123
- password: hashedNewPassword,
124
- }, { id: user.id })];
119
+ return [4 /*yield*/, this.saveUserInfo({ password: hashedNewPassword })];
125
120
  case 6:
126
121
  _b.sent();
127
122
  return [3 /*break*/, 8];
@@ -138,6 +133,182 @@ var UpdateUserInfo = /** @class */ (function () {
138
133
  });
139
134
  });
140
135
  };
136
+ UpdateUserInfo.prototype.updateAvatarPhoto = function (fileData, mimeType) {
137
+ return __awaiter(this, void 0, void 0, function () {
138
+ var imageBytes, jpeg, file;
139
+ return __generator(this, function (_a) {
140
+ switch (_a.label) {
141
+ case 0:
142
+ if (!UpdateUserInfo.ACCEPTED_AVATAR_MIME_TYPES.includes(mimeType)) {
143
+ throw new Error("Unsupported avatar image type '".concat(mimeType, "'. Accepted: ").concat(UpdateUserInfo.ACCEPTED_AVATAR_MIME_TYPES.join(', ')));
144
+ }
145
+ imageBytes = Buffer.from(fileData, 'base64');
146
+ if (imageBytes.length > UpdateUserInfo.MAX_AVATAR_PHOTO_BYTES) {
147
+ throw new Error("Avatar photo is too large (max 10MB)");
148
+ }
149
+ return [4 /*yield*/, this.toAvatarJpeg(imageBytes, mimeType)];
150
+ case 1:
151
+ jpeg = _a.sent();
152
+ return [4 /*yield*/, new db_file_1.FileStorage().createFile({ name: 'avatar.jpg', type: 'image/jpeg', size: jpeg.length }, jpeg.toString('base64'))];
153
+ case 2:
154
+ file = _a.sent();
155
+ return [4 /*yield*/, this.setAvatar({ avatarEmoji: null, avatarFileId: file.id })];
156
+ case 3: return [2 /*return*/, _a.sent()];
157
+ }
158
+ });
159
+ });
160
+ };
161
+ UpdateUserInfo.prototype.updateAvatarEmoji = function (emoji) {
162
+ return __awaiter(this, void 0, void 0, function () {
163
+ var trimmed;
164
+ return __generator(this, function (_a) {
165
+ switch (_a.label) {
166
+ case 0:
167
+ trimmed = emoji.trim();
168
+ if (!trimmed ||
169
+ trimmed.length > UpdateUserInfo.MAX_AVATAR_EMOJI_LENGTH ||
170
+ /\s/.test(trimmed) ||
171
+ !UpdateUserInfo.EMOJI_PATTERN.test(trimmed)) {
172
+ throw new Error("Avatar emoji must be a single short emoji");
173
+ }
174
+ return [4 /*yield*/, this.setAvatar({ avatarEmoji: trimmed, avatarFileId: null })];
175
+ case 1: return [2 /*return*/, _a.sent()];
176
+ }
177
+ });
178
+ });
179
+ };
180
+ UpdateUserInfo.prototype.clearAvatar = function () {
181
+ return __awaiter(this, void 0, void 0, function () {
182
+ return __generator(this, function (_a) {
183
+ switch (_a.label) {
184
+ case 0: return [4 /*yield*/, this.setAvatar({ avatarEmoji: null, avatarFileId: null })];
185
+ case 1: return [2 /*return*/, _a.sent()];
186
+ }
187
+ });
188
+ });
189
+ };
190
+ /**
191
+ * The single door to the avatar columns — every mutation assigns BOTH, which is what enforces
192
+ * the exactly-one-active invariant (photo OR emoji, never both). Also the previous photo's
193
+ * cleanup point: an avatar file the user row no longer points to is unreachable (the /avatar
194
+ * route serves only the id on the user row), so it is deleted here, after the row moves off it.
195
+ */
196
+ UpdateUserInfo.prototype.setAvatar = function (avatar) {
197
+ return __awaiter(this, void 0, void 0, function () {
198
+ var userId, previousAvatarFileId, updated;
199
+ return __generator(this, function (_a) {
200
+ switch (_a.label) {
201
+ case 0:
202
+ userId = new user_1.UserRepo().getUser().id;
203
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { id: userId })];
204
+ case 1:
205
+ previousAvatarFileId = (_a.sent()).avatarFileId;
206
+ return [4 /*yield*/, this.saveUserInfo(avatar)];
207
+ case 2:
208
+ updated = _a.sent();
209
+ if (!(previousAvatarFileId && previousAvatarFileId !== avatar.avatarFileId)) return [3 /*break*/, 4];
210
+ return [4 /*yield*/, new db_file_1.FileStorage().deleteFile(previousAvatarFileId)];
211
+ case 3:
212
+ _a.sent();
213
+ _a.label = 4;
214
+ case 4: return [2 /*return*/, updated];
215
+ }
216
+ });
217
+ });
218
+ };
219
+ /**
220
+ * The one path every user-info mutation persists through: writes the changes AND refreshes the
221
+ * server session cache in the same stroke. userCache serializes the user into session data at
222
+ * request start; a bare DB write leaves every read through UserRepo in the same context — and
223
+ * long-lived contexts that keep their session data (sockets) — serving the STALE user until
224
+ * re-login (the rename wart). Fusing persist + refresh here means no future mutation can
225
+ * reintroduce that class of staleness.
226
+ */
227
+ UpdateUserInfo.prototype.saveUserInfo = function (changes) {
228
+ return __awaiter(this, void 0, void 0, function () {
229
+ var userRepo, userId, db, updated;
230
+ return __generator(this, function (_a) {
231
+ switch (_a.label) {
232
+ case 0:
233
+ userRepo = new user_1.UserRepo();
234
+ userId = userRepo.getUser().id;
235
+ db = (0, db_1.getDbAsSystem)();
236
+ return [4 /*yield*/, db.update(user_1.tables.User, changes, { id: userId })];
237
+ case 1:
238
+ _a.sent();
239
+ return [4 /*yield*/, db.get(user_1.tables.User, { id: userId })];
240
+ case 2:
241
+ updated = _a.sent();
242
+ delete updated.password; // the same password-less shape userCache caches
243
+ userRepo.setUser(updated);
244
+ return [2 /*return*/, updated];
245
+ }
246
+ });
247
+ });
248
+ };
249
+ /**
250
+ * Minimal avatar pipeline: decode, auto-orient from EXIF, cover-crop square, re-encode JPEG.
251
+ * sharp drops metadata (EXIF orientation/GPS) on re-encode by default — the stored avatar
252
+ * carries no camera metadata. HEIC/HEIF (default iPhone camera format) is decoded with WASM
253
+ * libheif (`heic-decode`) because sharp's prebuilt libvips has no HEVC decoder. NOTE: this
254
+ * duplicates the HEIC-normalization approach of chat-server's FileTransformer, which this
255
+ * package cannot import (layering) — future extraction target: a shared image-transform seam.
256
+ */
257
+ UpdateUserInfo.prototype.toAvatarJpeg = function (imageBytes, mimeType) {
258
+ return __awaiter(this, void 0, void 0, function () {
259
+ var pipeline, _a;
260
+ return __generator(this, function (_b) {
261
+ switch (_b.label) {
262
+ case 0:
263
+ if (!(mimeType === 'image/heic' || mimeType === 'image/heif')) return [3 /*break*/, 2];
264
+ return [4 /*yield*/, this.decodeHeic(imageBytes)];
265
+ case 1:
266
+ _a = _b.sent();
267
+ return [3 /*break*/, 3];
268
+ case 2:
269
+ _a = (0, sharp_1.default)(imageBytes).rotate();
270
+ _b.label = 3;
271
+ case 3:
272
+ pipeline = _a;
273
+ return [4 /*yield*/, pipeline
274
+ .resize(UpdateUserInfo.AVATAR_SIZE, UpdateUserInfo.AVATAR_SIZE, { fit: 'cover' })
275
+ .jpeg({ quality: UpdateUserInfo.AVATAR_JPEG_QUALITY })
276
+ .toBuffer()];
277
+ case 4: return [2 /*return*/, _b.sent()];
278
+ }
279
+ });
280
+ });
281
+ };
282
+ UpdateUserInfo.prototype.decodeHeic = function (imageBytes) {
283
+ return __awaiter(this, void 0, void 0, function () {
284
+ var _a, width, height, data;
285
+ return __generator(this, function (_b) {
286
+ switch (_b.label) {
287
+ case 0: return [4 /*yield*/, (0, heic_decode_1.default)({ buffer: imageBytes })];
288
+ case 1:
289
+ _a = _b.sent(), width = _a.width, height = _a.height, data = _a.data;
290
+ return [2 /*return*/, (0, sharp_1.default)(Buffer.from(data.buffer, data.byteOffset, data.byteLength), {
291
+ raw: { width: width, height: height, channels: 4 },
292
+ })];
293
+ }
294
+ });
295
+ });
296
+ };
297
+ /** Stored avatar photos are exactly this: cover-cropped square JPEG. */
298
+ UpdateUserInfo.AVATAR_SIZE = 512;
299
+ UpdateUserInfo.AVATAR_JPEG_QUALITY = 85;
300
+ UpdateUserInfo.MAX_AVATAR_PHOTO_BYTES = 10 * 1024 * 1024;
301
+ UpdateUserInfo.ACCEPTED_AVATAR_MIME_TYPES = [
302
+ 'image/jpeg',
303
+ 'image/png',
304
+ 'image/webp',
305
+ 'image/heic',
306
+ 'image/heif',
307
+ ];
308
+ /** One emoji (possibly a ZWJ/flag/keycap sequence) — a handful of code units, never a sentence. */
309
+ UpdateUserInfo.MAX_AVATAR_EMOJI_LENGTH = 16;
310
+ /** Constructed (not a literal) because tsconfig targets es5; runs on Node, which supports `\p`. */
311
+ UpdateUserInfo.EMOJI_PATTERN = new RegExp('[\\p{Extended_Pictographic}\\p{Regional_Indicator}\\u20E3]', 'u');
141
312
  return UpdateUserInfo;
142
313
  }());
143
314
  exports.UpdateUserInfo = UpdateUserInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"UpdateUserInfo.js","sourceRoot":"","sources":["../../../src/services/UpdateUserInfo.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,oCAA8C;AAC9C,wCAAkG;AAClG,wDAAmG;AACnG,4DAAsC;AAEtC;IAAA;QACS,oBAAe,GAAG;YACvB,IAAI,EAAE;gBACJ,QAAQ,EAAE,IAAI;aACf;SACF,CAAC;IAgEJ,CAAC;IA9DO,mCAAU,GAAhB,UAAiB,IAAY;;;;;;wBACrB,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;wBACrB,IAAI,GAAG,IAAI,eAAQ,EAAE,CAAC,OAAO,EAAE,CAAC;wBACtC,qBAAM,EAAE,CAAC,MAAM,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,IAAI,MAAA,EAAE,EAAE,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAAC,EAAA;;wBAAvD,SAAuD,CAAC;;;;;KACzD;IAEK,uCAAc,GAApB,UAAqB,eAAuB,EAAE,WAAmB;;;;;;;wBACzD,EAAE,GAAG,IAAA,kBAAa,GAAE,CAAC;wBACrB,MAAM,GAAG,IAAI,eAAQ,EAAE,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;wBAGrC,qBAAqB,GAAG,IAAA,gBAAM,EAAC,eAAe,CAAC,CAAC,QAAQ,EAAE,CAAC;wBACpD,qBAAM,EAAE,CAAC,GAAG,CAAC,aAAM,CAAC,IAAI,EAAE,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,EAAA;;wBAAhD,IAAI,GAAG,SAAyC;wBACtD,IAAI,qBAAqB,KAAK,IAAI,CAAC,QAAQ,EAAE;4BAC3C,sBAAO;oCACL,OAAO,EAAE,KAAK;oCACd,KAAK,EAAE,kBAAkB;iCAC1B,EAAC;yBACH;;;;wBAGO,WAAW,GAAG,IAAI,0BAAW,EAAE,CAAC;wBAChC,oBAAoB,GAAG,IAAA,0DAA2C,GAAE,CAAC;wBACrE,MAAM,GAAG,oBAAoB,CAAC,SAAS,EAAE,CAAC;wBAEhD,iDAAiD;wBACjD,qBAAM,WAAW,CAAC,SAAS,YACzB,EAAE,EAAE,IAAI,CAAC,KAAK,EACd,OAAO,EAAE,CAAA,MAAA,MAAM,CAAC,OAAO,0CAAE,OAAO,KAAI,gCAAgC,EACpE,IAAI,EAAE,MAAM,CAAC,IAAI,EACjB,IAAI,EAAE,MAAM,CAAC,IAAI,IACd,MAAM,CAAC,OAAO,EACjB,EAAA;;wBAPF,iDAAiD;wBACjD,SAME,CAAC;;;;wBAEH,sBAAO;gCACL,OAAO,EAAE,KAAK;gCACd,KAAK,EAAE,sBAAsB;6BAC9B,EAAC;;;wBAMI,iBAAiB,GAAG,IAAA,gBAAM,EAAC,WAAW,CAAC,CAAC,QAAQ,EAAE,CAAC;wBACzD,qBAAM,EAAE,CAAC,MAAM,CACb,aAAM,CAAC,IAAI,EACX;gCACE,QAAQ,EAAE,iBAAiB;6BAC5B,EACD,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,CAChB,EAAA;;wBAND,SAMC,CAAC;;;;wBAEF,sBAAO;gCACL,OAAO,EAAE,KAAK;gCACd,KAAK,EAAE,2BAA2B;6BACnC,EAAC;4BAGJ,sBAAO;4BACL,OAAO,EAAE,IAAI;yBACd,EAAC;;;;KACH;IACH,qBAAC;AAAD,CAAC,AArED,IAqEC;AArEY,wCAAc"}
1
+ {"version":3,"file":"UpdateUserInfo.js","sourceRoot":"","sources":["../../../src/services/UpdateUserInfo.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=UpdateUserInfoAvatar.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpdateUserInfoAvatar.test.d.ts","sourceRoot":"","sources":["../../test/UpdateUserInfoAvatar.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,337 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
13
+ return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ var fs_1 = __importDefault(require("fs"));
43
+ var path_1 = __importDefault(require("path"));
44
+ var sharp_1 = __importDefault(require("sharp"));
45
+ var db_1 = require("@proteinjs/db");
46
+ var db_file_1 = require("@proteinjs/db-file");
47
+ var user_1 = require("@proteinjs/user");
48
+ var UserServerTestEnvironment_1 = require("./UserServerTestEnvironment");
49
+ var UpdateUserInfo_1 = require("../src/services/UpdateUserInfo");
50
+ var testEnv = new UserServerTestEnvironment_1.UserServerTestEnvironment();
51
+ /** JPEG magic bytes (SOI + marker prefix). */
52
+ var isJpeg = function (buffer) { return buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff; };
53
+ var getFileRow = function (fileId) { return __awaiter(void 0, void 0, void 0, function () { return __generator(this, function (_a) {
54
+ switch (_a.label) {
55
+ case 0: return [4 /*yield*/, (0, user_1.getScopedDbAsSystem)().get(db_file_1.tables.File, { id: fileId })];
56
+ case 1: return [2 /*return*/, _a.sent()];
57
+ }
58
+ }); }); };
59
+ /**
60
+ * Avatars are photo OR emoji — exactly one active — stored as 512x512 JPEG, previous photo file
61
+ * deleted on every change, and the server session cache refreshed in the same stroke (the same
62
+ * helper that fixes the updateName rename-staleness wart). All assertions are outcomes: rows,
63
+ * bytes, cache reads.
64
+ */
65
+ describe('UpdateUserInfo avatar mutations', function () {
66
+ var service = new UpdateUserInfo_1.UpdateUserInfo();
67
+ beforeAll(function () { return __awaiter(void 0, void 0, void 0, function () {
68
+ return __generator(this, function (_a) {
69
+ switch (_a.label) {
70
+ case 0: return [4 /*yield*/, testEnv.beforeAll()];
71
+ case 1:
72
+ _a.sent();
73
+ return [2 /*return*/];
74
+ }
75
+ });
76
+ }); });
77
+ afterAll(function () { return __awaiter(void 0, void 0, void 0, function () {
78
+ return __generator(this, function (_a) {
79
+ switch (_a.label) {
80
+ case 0: return [4 /*yield*/, testEnv.afterAll()];
81
+ case 1:
82
+ _a.sent();
83
+ return [2 /*return*/];
84
+ }
85
+ });
86
+ }); });
87
+ it('updateAvatarPhoto stores a 512x512 JPEG file and points the user row at it', function () { return __awaiter(void 0, void 0, void 0, function () {
88
+ var user, png, updated, row, file, bytes, _a, _b, metadata;
89
+ return __generator(this, function (_c) {
90
+ switch (_c.label) {
91
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Photo user', email: 'photo@test.local' })];
92
+ case 1:
93
+ user = _c.sent();
94
+ testEnv.actAs(user);
95
+ return [4 /*yield*/, (0, sharp_1.default)({
96
+ create: { width: 800, height: 600, channels: 3, background: { r: 200, g: 80, b: 40 } },
97
+ })
98
+ .png()
99
+ .toBuffer()];
100
+ case 2:
101
+ png = _c.sent();
102
+ return [4 /*yield*/, service.updateAvatarPhoto(png.toString('base64'), 'image/png')];
103
+ case 3:
104
+ updated = _c.sent();
105
+ // Returned user: photo active, emoji null, no password hash.
106
+ expect(updated.avatarFileId).toBeTruthy();
107
+ expect(updated.avatarEmoji).toBeFalsy();
108
+ expect(updated.password).toBeUndefined();
109
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { id: user.id })];
110
+ case 4:
111
+ row = _c.sent();
112
+ expect(row.avatarFileId).toBe(updated.avatarFileId);
113
+ expect(row.avatarEmoji).toBeFalsy();
114
+ return [4 /*yield*/, getFileRow(row.avatarFileId)];
115
+ case 5:
116
+ file = _c.sent();
117
+ expect(file).toBeTruthy();
118
+ expect(file.type).toBe('image/jpeg');
119
+ _b = (_a = Buffer).from;
120
+ return [4 /*yield*/, new db_file_1.FileStorage().getFileData(file.id)];
121
+ case 6:
122
+ bytes = _b.apply(_a, [_c.sent(), 'base64']);
123
+ expect(isJpeg(bytes)).toBe(true);
124
+ expect(file.size).toBe(bytes.length);
125
+ return [4 /*yield*/, (0, sharp_1.default)(bytes).metadata()];
126
+ case 7:
127
+ metadata = _c.sent();
128
+ expect(metadata.width).toBe(512);
129
+ expect(metadata.height).toBe(512);
130
+ // Session cache refreshed in the same stroke.
131
+ expect(new user_1.UserRepo().getUser().avatarFileId).toBe(row.avatarFileId);
132
+ return [2 /*return*/];
133
+ }
134
+ });
135
+ }); });
136
+ it('a second updateAvatarPhoto deletes the previous avatar file', function () { return __awaiter(void 0, void 0, void 0, function () {
137
+ var user, png, first, second, _a, _b, _c;
138
+ return __generator(this, function (_d) {
139
+ switch (_d.label) {
140
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Replacer', email: 'replace@test.local' })];
141
+ case 1:
142
+ user = _d.sent();
143
+ testEnv.actAs(user);
144
+ return [4 /*yield*/, (0, sharp_1.default)({ create: { width: 300, height: 300, channels: 3, background: { r: 0, g: 120, b: 200 } } })
145
+ .png()
146
+ .toBuffer()];
147
+ case 2:
148
+ png = _d.sent();
149
+ return [4 /*yield*/, service.updateAvatarPhoto(png.toString('base64'), 'image/png')];
150
+ case 3:
151
+ first = _d.sent();
152
+ return [4 /*yield*/, service.updateAvatarPhoto(png.toString('base64'), 'image/png')];
153
+ case 4:
154
+ second = _d.sent();
155
+ expect(second.avatarFileId).not.toBe(first.avatarFileId);
156
+ // The prior file is gone — row and bytes.
157
+ _a = expect;
158
+ return [4 /*yield*/, getFileRow(first.avatarFileId)];
159
+ case 5:
160
+ // The prior file is gone — row and bytes.
161
+ _a.apply(void 0, [_d.sent()]).toBeFalsy();
162
+ _b = expect;
163
+ return [4 /*yield*/, new db_file_1.FileStorage().getFileData(first.avatarFileId)];
164
+ case 6:
165
+ _b.apply(void 0, [_d.sent()]).toBe('');
166
+ // The new one is intact.
167
+ _c = expect;
168
+ return [4 /*yield*/, getFileRow(second.avatarFileId)];
169
+ case 7:
170
+ // The new one is intact.
171
+ _c.apply(void 0, [_d.sent()]).toBeTruthy();
172
+ return [2 /*return*/];
173
+ }
174
+ });
175
+ }); });
176
+ it('updateAvatarPhoto normalizes HEIC (default iPhone camera format) to JPEG', function () { return __awaiter(void 0, void 0, void 0, function () {
177
+ var user, heic, updated, bytes, _a, _b, metadata;
178
+ return __generator(this, function (_c) {
179
+ switch (_c.label) {
180
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'iPhone user', email: 'heic@test.local' })];
181
+ case 1:
182
+ user = _c.sent();
183
+ testEnv.actAs(user);
184
+ heic = fs_1.default.readFileSync(path_1.default.join(__dirname, 'fixtures', 'fixture.heic'));
185
+ return [4 /*yield*/, service.updateAvatarPhoto(heic.toString('base64'), 'image/heic')];
186
+ case 2:
187
+ updated = _c.sent();
188
+ _b = (_a = Buffer).from;
189
+ return [4 /*yield*/, new db_file_1.FileStorage().getFileData(updated.avatarFileId)];
190
+ case 3:
191
+ bytes = _b.apply(_a, [_c.sent(), 'base64']);
192
+ expect(isJpeg(bytes)).toBe(true);
193
+ return [4 /*yield*/, (0, sharp_1.default)(bytes).metadata()];
194
+ case 4:
195
+ metadata = _c.sent();
196
+ expect(metadata.width).toBe(512);
197
+ expect(metadata.height).toBe(512);
198
+ return [2 /*return*/];
199
+ }
200
+ });
201
+ }); });
202
+ it('updateAvatarEmoji sets the emoji, nulls the photo, and deletes the previous photo file', function () { return __awaiter(void 0, void 0, void 0, function () {
203
+ var user, png, withPhoto, updated, row, _a;
204
+ return __generator(this, function (_b) {
205
+ switch (_b.label) {
206
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Emoji user', email: 'emoji@test.local' })];
207
+ case 1:
208
+ user = _b.sent();
209
+ testEnv.actAs(user);
210
+ return [4 /*yield*/, (0, sharp_1.default)({ create: { width: 64, height: 64, channels: 3, background: { r: 9, g: 9, b: 9 } } })
211
+ .png()
212
+ .toBuffer()];
213
+ case 2:
214
+ png = _b.sent();
215
+ return [4 /*yield*/, service.updateAvatarPhoto(png.toString('base64'), 'image/png')];
216
+ case 3:
217
+ withPhoto = _b.sent();
218
+ return [4 /*yield*/, service.updateAvatarEmoji('🦞')];
219
+ case 4:
220
+ updated = _b.sent();
221
+ // Exactly-one invariant, in the row and the returned user.
222
+ expect(updated.avatarEmoji).toBe('🦞');
223
+ expect(updated.avatarFileId).toBeFalsy();
224
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { id: user.id })];
225
+ case 5:
226
+ row = _b.sent();
227
+ expect(row.avatarEmoji).toBe('🦞');
228
+ expect(row.avatarFileId).toBeFalsy();
229
+ // Previous photo file deleted; session cache refreshed.
230
+ _a = expect;
231
+ return [4 /*yield*/, getFileRow(withPhoto.avatarFileId)];
232
+ case 6:
233
+ // Previous photo file deleted; session cache refreshed.
234
+ _a.apply(void 0, [_b.sent()]).toBeFalsy();
235
+ expect(new user_1.UserRepo().getUser().avatarEmoji).toBe('🦞');
236
+ return [2 /*return*/];
237
+ }
238
+ });
239
+ }); });
240
+ it('accepts multi-codepoint emoji (ZWJ sequences)', function () { return __awaiter(void 0, void 0, void 0, function () {
241
+ var user, updated;
242
+ return __generator(this, function (_a) {
243
+ switch (_a.label) {
244
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Astronaut', email: 'zwj@test.local' })];
245
+ case 1:
246
+ user = _a.sent();
247
+ testEnv.actAs(user);
248
+ return [4 /*yield*/, service.updateAvatarEmoji('👩‍🚀')];
249
+ case 2:
250
+ updated = _a.sent();
251
+ expect(updated.avatarEmoji).toBe('👩‍🚀');
252
+ return [2 /*return*/];
253
+ }
254
+ });
255
+ }); });
256
+ it('clearAvatar removes both representations and the stored photo file', function () { return __awaiter(void 0, void 0, void 0, function () {
257
+ var user, png, withPhoto, updated, row, _a;
258
+ return __generator(this, function (_b) {
259
+ switch (_b.label) {
260
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Clearer', email: 'clear@test.local' })];
261
+ case 1:
262
+ user = _b.sent();
263
+ testEnv.actAs(user);
264
+ return [4 /*yield*/, (0, sharp_1.default)({ create: { width: 64, height: 64, channels: 3, background: { r: 1, g: 2, b: 3 } } })
265
+ .png()
266
+ .toBuffer()];
267
+ case 2:
268
+ png = _b.sent();
269
+ return [4 /*yield*/, service.updateAvatarPhoto(png.toString('base64'), 'image/png')];
270
+ case 3:
271
+ withPhoto = _b.sent();
272
+ return [4 /*yield*/, service.clearAvatar()];
273
+ case 4:
274
+ updated = _b.sent();
275
+ expect(updated.avatarEmoji).toBeFalsy();
276
+ expect(updated.avatarFileId).toBeFalsy();
277
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { id: user.id })];
278
+ case 5:
279
+ row = _b.sent();
280
+ expect(row.avatarEmoji).toBeFalsy();
281
+ expect(row.avatarFileId).toBeFalsy();
282
+ _a = expect;
283
+ return [4 /*yield*/, getFileRow(withPhoto.avatarFileId)];
284
+ case 6:
285
+ _a.apply(void 0, [_b.sent()]).toBeFalsy();
286
+ expect(new user_1.UserRepo().getUser().avatarFileId).toBeFalsy();
287
+ return [2 /*return*/];
288
+ }
289
+ });
290
+ }); });
291
+ it('rejects oversized photos before transforming, and unsupported types', function () { return __awaiter(void 0, void 0, void 0, function () {
292
+ var user, oversized, row;
293
+ return __generator(this, function (_a) {
294
+ switch (_a.label) {
295
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Rejected', email: 'reject@test.local' })];
296
+ case 1:
297
+ user = _a.sent();
298
+ testEnv.actAs(user);
299
+ oversized = Buffer.alloc(10 * 1024 * 1024 + 1);
300
+ return [4 /*yield*/, expect(service.updateAvatarPhoto(oversized.toString('base64'), 'image/png')).rejects.toThrow(/too large/)];
301
+ case 2:
302
+ _a.sent();
303
+ return [4 /*yield*/, expect(service.updateAvatarPhoto(Buffer.alloc(10).toString('base64'), 'image/gif')).rejects.toThrow(/Unsupported avatar image type/)];
304
+ case 3:
305
+ _a.sent();
306
+ return [4 /*yield*/, (0, db_1.getDbAsSystem)().get(user_1.tables.User, { id: user.id })];
307
+ case 4:
308
+ row = _a.sent();
309
+ expect(row.avatarFileId).toBeFalsy();
310
+ expect(row.avatarEmoji).toBeFalsy();
311
+ return [2 /*return*/];
312
+ }
313
+ });
314
+ }); });
315
+ it('rejects non-emoji strings as avatar emoji', function () { return __awaiter(void 0, void 0, void 0, function () {
316
+ var user;
317
+ return __generator(this, function (_a) {
318
+ switch (_a.label) {
319
+ case 0: return [4 /*yield*/, testEnv.createUser({ name: 'Wordy', email: 'wordy@test.local' })];
320
+ case 1:
321
+ user = _a.sent();
322
+ testEnv.actAs(user);
323
+ return [4 /*yield*/, expect(service.updateAvatarEmoji('hello')).rejects.toThrow(/single short emoji/)];
324
+ case 2:
325
+ _a.sent();
326
+ return [4 /*yield*/, expect(service.updateAvatarEmoji('')).rejects.toThrow(/single short emoji/)];
327
+ case 3:
328
+ _a.sent();
329
+ return [4 /*yield*/, expect(service.updateAvatarEmoji('🦞🦞🦞🦞🦞🦞🦞🦞🦞')).rejects.toThrow(/single short emoji/)];
330
+ case 4:
331
+ _a.sent();
332
+ return [2 /*return*/];
333
+ }
334
+ });
335
+ }); });
336
+ });
337
+ //# sourceMappingURL=UpdateUserInfoAvatar.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpdateUserInfoAvatar.test.js","sourceRoot":"","sources":["../../test/UpdateUserInfoAvatar.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=UpdateUserInfoSessionRefresh.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"UpdateUserInfoSessionRefresh.test.d.ts","sourceRoot":"","sources":["../../test/UpdateUserInfoSessionRefresh.test.ts"],"names":[],"mappings":""}