Sifa professional network API (Fastify, AT Protocol, Jetstream) sifa.id/

fix(profile): preserve createdAt and account metadata on account reset (#97)

* fix(profile): preserve createdAt and account metadata on account reset

Resetting an account wiped the entire profiles row, causing the user's
original join date to be overwritten with the current timestamp on next
login. This made reset users appear as new signups in admin stats and
reset growth data.

Replace db.delete(profiles) with db.update(profiles).set({...}) that
nulls only user-authored Sifa profile content (headline, about,
industry, location fields, openTo, preferredWorkplace). Preserves
createdAt, langs, headlineOverride, aboutOverride, handle, displayName,
avatarUrl, and pdsHost.

* style: format pnpm-lock.yaml

authored by

Guido X Jansen and committed by
GitHub
16ea155c 451defa7

+413 -8
+298 -7
pnpm-lock.yaml
··· 95 95 husky: 96 96 specifier: 9.1.7 97 97 version: 9.1.7 98 + lint-staged: 99 + specifier: 16.4.0 100 + version: 16.4.0 98 101 prettier: 99 102 specifier: 3.5.3 100 103 version: 3.5.3 ··· 109 112 version: 8.56.1(eslint@10.0.3(jiti@2.6.1))(typescript@5.9.3) 110 113 vitest: 111 114 specifier: 4.0.18 112 - version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(tsx@4.21.0) 115 + version: 4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(tsx@4.21.0)(yaml@2.8.2) 113 116 114 117 packages: 115 118 '@acemir/cssom@0.9.31': ··· 2244 2247 integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==, 2245 2248 } 2246 2249 2250 + ansi-escapes@7.3.0: 2251 + resolution: 2252 + { 2253 + integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==, 2254 + } 2255 + engines: { node: '>=18' } 2256 + 2247 2257 ansi-regex@5.0.1: 2248 2258 resolution: 2249 2259 { ··· 2251 2261 } 2252 2262 engines: { node: '>=8' } 2253 2263 2264 + ansi-regex@6.2.2: 2265 + resolution: 2266 + { 2267 + integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==, 2268 + } 2269 + engines: { node: '>=12' } 2270 + 2254 2271 ansi-styles@4.3.0: 2255 2272 resolution: 2256 2273 { 2257 2274 integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==, 2258 2275 } 2259 2276 engines: { node: '>=8' } 2277 + 2278 + ansi-styles@6.2.3: 2279 + resolution: 2280 + { 2281 + integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==, 2282 + } 2283 + engines: { node: '>=12' } 2260 2284 2261 2285 argparse@2.0.1: 2262 2286 resolution: ··· 2349 2373 integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==, 2350 2374 } 2351 2375 2376 + cli-cursor@5.0.0: 2377 + resolution: 2378 + { 2379 + integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==, 2380 + } 2381 + engines: { node: '>=18' } 2382 + 2383 + cli-truncate@5.2.0: 2384 + resolution: 2385 + { 2386 + integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==, 2387 + } 2388 + engines: { node: '>=20' } 2389 + 2352 2390 cliui@8.0.1: 2353 2391 resolution: 2354 2392 { ··· 2382 2420 integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==, 2383 2421 } 2384 2422 2423 + commander@14.0.3: 2424 + resolution: 2425 + { 2426 + integrity: sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw==, 2427 + } 2428 + engines: { node: '>=20' } 2429 + 2385 2430 compare-func@2.0.0: 2386 2431 resolution: 2387 2432 { ··· 2647 2692 sqlite3: 2648 2693 optional: true 2649 2694 2695 + emoji-regex@10.6.0: 2696 + resolution: 2697 + { 2698 + integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==, 2699 + } 2700 + 2650 2701 emoji-regex@8.0.0: 2651 2702 resolution: 2652 2703 { ··· 2672 2723 integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==, 2673 2724 } 2674 2725 engines: { node: '>=6' } 2726 + 2727 + environment@1.1.0: 2728 + resolution: 2729 + { 2730 + integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==, 2731 + } 2732 + engines: { node: '>=18' } 2675 2733 2676 2734 error-ex@1.3.4: 2677 2735 resolution: ··· 2806 2864 } 2807 2865 engines: { node: '>=0.10.0' } 2808 2866 2867 + eventemitter3@5.0.4: 2868 + resolution: 2869 + { 2870 + integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==, 2871 + } 2872 + 2809 2873 expect-type@1.3.0: 2810 2874 resolution: 2811 2875 { ··· 2959 3023 } 2960 3024 engines: { node: 6.* || 8.* || >= 10.* } 2961 3025 3026 + get-east-asian-width@1.5.0: 3027 + resolution: 3028 + { 3029 + integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==, 3030 + } 3031 + engines: { node: '>=18' } 3032 + 2962 3033 get-tsconfig@4.13.6: 2963 3034 resolution: 2964 3035 { ··· 3111 3182 } 3112 3183 engines: { node: '>=8' } 3113 3184 3185 + is-fullwidth-code-point@5.1.0: 3186 + resolution: 3187 + { 3188 + integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==, 3189 + } 3190 + engines: { node: '>=18' } 3191 + 3114 3192 is-glob@4.0.3: 3115 3193 resolution: 3116 3194 { ··· 3276 3354 integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==, 3277 3355 } 3278 3356 3357 + lint-staged@16.4.0: 3358 + resolution: 3359 + { 3360 + integrity: sha512-lBWt8hujh/Cjysw5GYVmZpFHXDCgZzhrOm8vbcUdobADZNOK/bRshr2kM3DfgrrtR1DQhfupW9gnIXOfiFi+bw==, 3361 + } 3362 + engines: { node: '>=20.17' } 3363 + hasBin: true 3364 + 3365 + listr2@9.0.5: 3366 + resolution: 3367 + { 3368 + integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==, 3369 + } 3370 + engines: { node: '>=20.0.0' } 3371 + 3279 3372 locate-path@6.0.0: 3280 3373 resolution: 3281 3374 { ··· 3356 3449 integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==, 3357 3450 } 3358 3451 3452 + log-update@6.1.0: 3453 + resolution: 3454 + { 3455 + integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==, 3456 + } 3457 + engines: { node: '>=18' } 3458 + 3359 3459 lru-cache@10.4.3: 3360 3460 resolution: 3361 3461 { ··· 3387 3487 integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==, 3388 3488 } 3389 3489 engines: { node: '>=16.10' } 3490 + 3491 + mimic-function@5.0.1: 3492 + resolution: 3493 + { 3494 + integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==, 3495 + } 3496 + engines: { node: '>=18' } 3390 3497 3391 3498 minimatch@10.2.4: 3392 3499 resolution: ··· 3451 3558 { 3452 3559 integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==, 3453 3560 } 3561 + 3562 + onetime@7.0.0: 3563 + resolution: 3564 + { 3565 + integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==, 3566 + } 3567 + engines: { node: '>=18' } 3454 3568 3455 3569 optionator@0.9.4: 3456 3570 resolution: ··· 3774 3888 integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==, 3775 3889 } 3776 3890 3891 + restore-cursor@5.1.0: 3892 + resolution: 3893 + { 3894 + integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==, 3895 + } 3896 + engines: { node: '>=18' } 3897 + 3777 3898 ret@0.5.0: 3778 3899 resolution: 3779 3900 { ··· 3863 3984 integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==, 3864 3985 } 3865 3986 3987 + signal-exit@4.1.0: 3988 + resolution: 3989 + { 3990 + integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==, 3991 + } 3992 + engines: { node: '>=14' } 3993 + 3994 + slice-ansi@7.1.2: 3995 + resolution: 3996 + { 3997 + integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==, 3998 + } 3999 + engines: { node: '>=18' } 4000 + 4001 + slice-ansi@8.0.0: 4002 + resolution: 4003 + { 4004 + integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==, 4005 + } 4006 + engines: { node: '>=20' } 4007 + 3866 4008 sonic-boom@4.2.1: 3867 4009 resolution: 3868 4010 { ··· 3914 4056 integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==, 3915 4057 } 3916 4058 4059 + string-argv@0.3.2: 4060 + resolution: 4061 + { 4062 + integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==, 4063 + } 4064 + engines: { node: '>=0.6.19' } 4065 + 3917 4066 string-width@4.2.3: 3918 4067 resolution: 3919 4068 { ··· 3921 4070 } 3922 4071 engines: { node: '>=8' } 3923 4072 4073 + string-width@7.2.0: 4074 + resolution: 4075 + { 4076 + integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==, 4077 + } 4078 + engines: { node: '>=18' } 4079 + 4080 + string-width@8.2.0: 4081 + resolution: 4082 + { 4083 + integrity: sha512-6hJPQ8N0V0P3SNmP6h2J99RLuzrWz2gvT7VnK5tKvrNqJoyS9W4/Fb8mo31UiPvy00z7DQXkP2hnKBVav76thw==, 4084 + } 4085 + engines: { node: '>=20' } 4086 + 3924 4087 strip-ansi@6.0.1: 3925 4088 resolution: 3926 4089 { 3927 4090 integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==, 3928 4091 } 3929 4092 engines: { node: '>=8' } 4093 + 4094 + strip-ansi@7.2.0: 4095 + resolution: 4096 + { 4097 + integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==, 4098 + } 4099 + engines: { node: '>=12' } 3930 4100 3931 4101 strip-json-comments@5.0.3: 3932 4102 resolution: ··· 4260 4430 } 4261 4431 engines: { node: '>=10' } 4262 4432 4433 + wrap-ansi@9.0.2: 4434 + resolution: 4435 + { 4436 + integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==, 4437 + } 4438 + engines: { node: '>=18' } 4439 + 4263 4440 wrappy@1.0.2: 4264 4441 resolution: 4265 4442 { ··· 4307 4484 integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==, 4308 4485 } 4309 4486 engines: { node: '>=10' } 4487 + 4488 + yaml@2.8.2: 4489 + resolution: 4490 + { 4491 + integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==, 4492 + } 4493 + engines: { node: '>= 14.6' } 4494 + hasBin: true 4310 4495 4311 4496 yargs-parser@21.1.1: 4312 4497 resolution: ··· 5575 5760 chai: 6.2.2 5576 5761 tinyrainbow: 3.1.0 5577 5762 5578 - '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0))': 5763 + '@vitest/mocker@4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2))': 5579 5764 dependencies: 5580 5765 '@vitest/spy': 4.0.18 5581 5766 estree-walker: 3.0.3 5582 5767 magic-string: 0.30.21 5583 5768 optionalDependencies: 5584 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0) 5769 + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) 5585 5770 5586 5771 '@vitest/pretty-format@4.0.18': 5587 5772 dependencies: ··· 5642 5827 json-schema-traverse: 1.0.0 5643 5828 require-from-string: 2.0.2 5644 5829 5830 + ansi-escapes@7.3.0: 5831 + dependencies: 5832 + environment: 1.1.0 5833 + 5645 5834 ansi-regex@5.0.1: {} 5646 5835 5836 + ansi-regex@6.2.2: {} 5837 + 5647 5838 ansi-styles@4.3.0: 5648 5839 dependencies: 5649 5840 color-convert: 2.0.1 5650 5841 5842 + ansi-styles@6.2.3: {} 5843 + 5651 5844 argparse@2.0.1: {} 5652 5845 5653 5846 array-ify@1.0.0: {} ··· 5682 5875 chalk@5.6.2: {} 5683 5876 5684 5877 cjs-module-lexer@2.2.0: {} 5878 + 5879 + cli-cursor@5.0.0: 5880 + dependencies: 5881 + restore-cursor: 5.1.0 5882 + 5883 + cli-truncate@5.2.0: 5884 + dependencies: 5885 + slice-ansi: 8.0.0 5886 + string-width: 8.2.0 5685 5887 5686 5888 cliui@8.0.1: 5687 5889 dependencies: ··· 5699 5901 5700 5902 colorette@2.0.20: {} 5701 5903 5904 + commander@14.0.3: {} 5905 + 5702 5906 compare-func@2.0.0: 5703 5907 dependencies: 5704 5908 array-ify: 1.0.0 ··· 5805 6009 '@types/pg': 8.18.0 5806 6010 pg: 8.20.0 5807 6011 6012 + emoji-regex@10.6.0: {} 6013 + 5808 6014 emoji-regex@8.0.0: {} 5809 6015 5810 6016 end-of-stream@1.4.5: ··· 5814 6020 entities@6.0.1: {} 5815 6021 5816 6022 env-paths@2.2.1: {} 6023 + 6024 + environment@1.1.0: {} 5817 6025 5818 6026 error-ex@1.3.4: 5819 6027 dependencies: ··· 5985 6193 5986 6194 esutils@2.0.3: {} 5987 6195 6196 + eventemitter3@5.0.4: {} 6197 + 5988 6198 expect-type@1.3.0: {} 5989 6199 5990 6200 fast-copy@4.0.2: {} ··· 6077 6287 6078 6288 get-caller-file@2.0.5: {} 6079 6289 6290 + get-east-asian-width@1.5.0: {} 6291 + 6080 6292 get-tsconfig@4.13.6: 6081 6293 dependencies: 6082 6294 resolve-pkg-maps: 1.0.0 ··· 6165 6377 6166 6378 is-fullwidth-code-point@3.0.0: {} 6167 6379 6380 + is-fullwidth-code-point@5.1.0: 6381 + dependencies: 6382 + get-east-asian-width: 1.5.0 6383 + 6168 6384 is-glob@4.0.3: 6169 6385 dependencies: 6170 6386 is-extglob: 2.1.1 ··· 6264 6480 6265 6481 lines-and-columns@1.2.4: {} 6266 6482 6483 + lint-staged@16.4.0: 6484 + dependencies: 6485 + commander: 14.0.3 6486 + listr2: 9.0.5 6487 + picomatch: 4.0.3 6488 + string-argv: 0.3.2 6489 + tinyexec: 1.0.4 6490 + yaml: 2.8.2 6491 + 6492 + listr2@9.0.5: 6493 + dependencies: 6494 + cli-truncate: 5.2.0 6495 + colorette: 2.0.20 6496 + eventemitter3: 5.0.4 6497 + log-update: 6.1.0 6498 + rfdc: 1.4.1 6499 + wrap-ansi: 9.0.2 6500 + 6267 6501 locate-path@6.0.0: 6268 6502 dependencies: 6269 6503 p-locate: 5.0.0 ··· 6294 6528 6295 6529 lodash.upperfirst@4.3.1: {} 6296 6530 6531 + log-update@6.1.0: 6532 + dependencies: 6533 + ansi-escapes: 7.3.0 6534 + cli-cursor: 5.0.0 6535 + slice-ansi: 7.1.2 6536 + strip-ansi: 7.2.0 6537 + wrap-ansi: 9.0.2 6538 + 6297 6539 lru-cache@10.4.3: {} 6298 6540 6299 6541 lru-cache@11.2.7: {} ··· 6305 6547 mdn-data@2.27.1: {} 6306 6548 6307 6549 meow@12.1.1: {} 6550 + 6551 + mimic-function@5.0.1: {} 6308 6552 6309 6553 minimatch@10.2.4: 6310 6554 dependencies: ··· 6329 6573 once@1.4.0: 6330 6574 dependencies: 6331 6575 wrappy: 1.0.2 6576 + 6577 + onetime@7.0.0: 6578 + dependencies: 6579 + mimic-function: 5.0.1 6332 6580 6333 6581 optionator@0.9.4: 6334 6582 dependencies: ··· 6511 6759 6512 6760 resolve-pkg-maps@1.0.0: {} 6513 6761 6762 + restore-cursor@5.1.0: 6763 + dependencies: 6764 + onetime: 7.0.0 6765 + signal-exit: 4.1.0 6766 + 6514 6767 ret@0.5.0: {} 6515 6768 6516 6769 reusify@1.1.0: {} ··· 6572 6825 6573 6826 siginfo@2.0.0: {} 6574 6827 6828 + signal-exit@4.1.0: {} 6829 + 6830 + slice-ansi@7.1.2: 6831 + dependencies: 6832 + ansi-styles: 6.2.3 6833 + is-fullwidth-code-point: 5.1.0 6834 + 6835 + slice-ansi@8.0.0: 6836 + dependencies: 6837 + ansi-styles: 6.2.3 6838 + is-fullwidth-code-point: 5.1.0 6839 + 6575 6840 sonic-boom@4.2.1: 6576 6841 dependencies: 6577 6842 atomic-sleep: 1.0.0 ··· 6593 6858 6594 6859 std-env@3.10.0: {} 6595 6860 6861 + string-argv@0.3.2: {} 6862 + 6596 6863 string-width@4.2.3: 6597 6864 dependencies: 6598 6865 emoji-regex: 8.0.0 6599 6866 is-fullwidth-code-point: 3.0.0 6600 6867 strip-ansi: 6.0.1 6601 6868 6869 + string-width@7.2.0: 6870 + dependencies: 6871 + emoji-regex: 10.6.0 6872 + get-east-asian-width: 1.5.0 6873 + strip-ansi: 7.2.0 6874 + 6875 + string-width@8.2.0: 6876 + dependencies: 6877 + get-east-asian-width: 1.5.0 6878 + strip-ansi: 7.2.0 6879 + 6602 6880 strip-ansi@6.0.1: 6603 6881 dependencies: 6604 6882 ansi-regex: 5.0.1 6605 6883 6884 + strip-ansi@7.2.0: 6885 + dependencies: 6886 + ansi-regex: 6.2.2 6887 + 6606 6888 strip-json-comments@5.0.3: {} 6607 6889 6608 6890 symbol-tree@3.2.4: {} ··· 6692 6974 dependencies: 6693 6975 punycode: 2.3.1 6694 6976 6695 - vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0): 6977 + vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2): 6696 6978 dependencies: 6697 6979 esbuild: 0.27.4 6698 6980 fdir: 6.5.0(picomatch@4.0.3) ··· 6705 6987 fsevents: 2.3.3 6706 6988 jiti: 2.6.1 6707 6989 tsx: 4.21.0 6990 + yaml: 2.8.2 6708 6991 6709 - vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(tsx@4.21.0): 6992 + vitest@4.0.18(@opentelemetry/api@1.9.0)(@types/node@25.3.5)(jiti@2.6.1)(jsdom@28.1.0)(tsx@4.21.0)(yaml@2.8.2): 6710 6993 dependencies: 6711 6994 '@vitest/expect': 4.0.18 6712 - '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)) 6995 + '@vitest/mocker': 4.0.18(vite@7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2)) 6713 6996 '@vitest/pretty-format': 4.0.18 6714 6997 '@vitest/runner': 4.0.18 6715 6998 '@vitest/snapshot': 4.0.18 ··· 6726 7009 tinyexec: 1.0.4 6727 7010 tinyglobby: 0.2.15 6728 7011 tinyrainbow: 3.1.0 6729 - vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0) 7012 + vite: 7.3.1(@types/node@25.3.5)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.2) 6730 7013 why-is-node-running: 2.3.0 6731 7014 optionalDependencies: 6732 7015 '@opentelemetry/api': 1.9.0 ··· 6778 7061 string-width: 4.2.3 6779 7062 strip-ansi: 6.0.1 6780 7063 7064 + wrap-ansi@9.0.2: 7065 + dependencies: 7066 + ansi-styles: 6.2.3 7067 + string-width: 7.2.0 7068 + strip-ansi: 7.2.0 7069 + 6781 7070 wrappy@1.0.2: {} 6782 7071 6783 7072 ws@8.19.0: {} ··· 6789 7078 xtend@4.0.2: {} 6790 7079 6791 7080 y18n@5.0.8: {} 7081 + 7082 + yaml@2.8.2: {} 6792 7083 6793 7084 yargs-parser@21.1.1: {} 6794 7085
+17 -1
src/services/profile-wipe.ts
··· 63 63 await writeToUserPds(session, did, batch); 64 64 } 65 65 66 - await db.delete(profiles).where(eq(profiles.did, did)); 66 + // Update instead of delete: preserve createdAt (join date), langs, headlineOverride, 67 + // aboutOverride, handle, displayName, avatarUrl, and pdsHost -- only null profile content. 68 + await db 69 + .update(profiles) 70 + .set({ 71 + headline: null, 72 + about: null, 73 + industry: null, 74 + locationCountry: null, 75 + locationRegion: null, 76 + locationCity: null, 77 + countryCode: null, 78 + openTo: null, 79 + preferredWorkplace: null, 80 + updatedAt: new Date(), 81 + }) 82 + .where(eq(profiles.did, did)); 67 83 await db.delete(externalAccountVerifications).where(eq(externalAccountVerifications.did, did)); 68 84 }
+98
tests/services/profile-wipe-db.test.ts
··· 1 + import { describe, it, expect, vi } from 'vitest'; 2 + import { wipeSifaData } from '../../src/services/profile-wipe.js'; 3 + import { profiles, externalAccountVerifications } from '../../src/db/schema/index.js'; 4 + import type { Database } from '../../src/db/index.js'; 5 + import type { OAuthSession } from '@atproto/oauth-client'; 6 + 7 + vi.mock('@atproto/api', () => ({ 8 + Agent: class { 9 + com = { 10 + atproto: { 11 + repo: { 12 + listRecords: vi.fn().mockResolvedValue({ data: { records: [] } }), 13 + }, 14 + }, 15 + }; 16 + }, 17 + })); 18 + 19 + vi.mock('../../src/services/pds-writer.js', () => ({ 20 + writeToUserPds: vi.fn().mockResolvedValue(undefined), 21 + buildApplyWritesOp: vi 22 + .fn() 23 + .mockImplementation((type: string, collection: string, rkey: string) => ({ 24 + $type: `com.atproto.repo.applyWrites#${type}`, 25 + collection, 26 + rkey, 27 + })), 28 + })); 29 + 30 + function createMockDb() { 31 + const whereUpdateMock = vi.fn().mockResolvedValue(undefined); 32 + const setMock = vi.fn().mockReturnValue({ where: whereUpdateMock }); 33 + const updateMock = vi.fn().mockReturnValue({ set: setMock }); 34 + 35 + const whereDeleteMock = vi.fn().mockResolvedValue(undefined); 36 + const deleteMock = vi.fn().mockReturnValue({ where: whereDeleteMock }); 37 + 38 + return { 39 + db: { update: updateMock, delete: deleteMock } as unknown as Database, 40 + mocks: { updateMock, setMock, whereUpdateMock, deleteMock, whereDeleteMock }, 41 + }; 42 + } 43 + 44 + const mockSession = {} as OAuthSession; 45 + 46 + describe('wipeSifaData DB behaviour on reset', () => { 47 + it('updates profiles row instead of deleting it', async () => { 48 + const { db, mocks } = createMockDb(); 49 + 50 + await wipeSifaData(mockSession, 'did:plc:test', db); 51 + 52 + expect(mocks.updateMock).toHaveBeenCalledWith(profiles); 53 + expect(mocks.deleteMock).not.toHaveBeenCalledWith(profiles); 54 + }); 55 + 56 + it('does not include createdAt, langs, headlineOverride, aboutOverride, handle, displayName, avatarUrl, or pdsHost in the update set', async () => { 57 + const { db, mocks } = createMockDb(); 58 + 59 + await wipeSifaData(mockSession, 'did:plc:test', db); 60 + 61 + const setArg = mocks.setMock.mock.calls[0][0] as Record<string, unknown>; 62 + expect(setArg).not.toHaveProperty('createdAt'); 63 + expect(setArg).not.toHaveProperty('langs'); 64 + expect(setArg).not.toHaveProperty('headlineOverride'); 65 + expect(setArg).not.toHaveProperty('aboutOverride'); 66 + expect(setArg).not.toHaveProperty('handle'); 67 + expect(setArg).not.toHaveProperty('displayName'); 68 + expect(setArg).not.toHaveProperty('avatarUrl'); 69 + expect(setArg).not.toHaveProperty('pdsHost'); 70 + }); 71 + 72 + it('nulls all profile content fields in the update set', async () => { 73 + const { db, mocks } = createMockDb(); 74 + 75 + await wipeSifaData(mockSession, 'did:plc:test', db); 76 + 77 + const setArg = mocks.setMock.mock.calls[0][0]; 78 + expect(setArg).toMatchObject({ 79 + headline: null, 80 + about: null, 81 + industry: null, 82 + locationCountry: null, 83 + locationRegion: null, 84 + locationCity: null, 85 + countryCode: null, 86 + openTo: null, 87 + preferredWorkplace: null, 88 + }); 89 + }); 90 + 91 + it('still deletes externalAccountVerifications', async () => { 92 + const { db, mocks } = createMockDb(); 93 + 94 + await wipeSifaData(mockSession, 'did:plc:test', db); 95 + 96 + expect(mocks.deleteMock).toHaveBeenCalledWith(externalAccountVerifications); 97 + }); 98 + });