cabloy 5.1.240 → 5.1.242

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 (55) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +24 -0
  3. package/package.json +1 -1
  4. package/repo-docs/backend/i18n-guide.md +109 -11
  5. package/repo-docs/backend/openapi-guide.md +18 -0
  6. package/repo-docs/frontend/environment-config-guide.md +43 -0
  7. package/repo-docs/frontend/module-scope.md +88 -0
  8. package/repo-docs/frontend/system-startup-guide.md +31 -0
  9. package/repo-e2e/specs/home-user-account.spec.ts +53 -8
  10. package/vona/packages-vona/vona/package.json +1 -1
  11. package/vona/pnpm-lock.yaml +17 -142
  12. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/dto/operatorContext.ts +4 -1
  13. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/src/service/operator.ts +1 -0
  14. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/operatorAccess.test.ts +1 -0
  15. package/vona/src/suite/a-home/modules/home-user/src/bean/meta.version.ts +1 -0
  16. package/vona/src/suite/a-home/modules/home-user/src/controller/passport.ts +1 -0
  17. package/vona/src/suite/a-home/modules/home-user/src/dto/accountCurrent.ts +3 -0
  18. package/vona/src/suite/a-home/modules/home-user/src/dto/accountProfileUpdate.ts +9 -2
  19. package/vona/src/suite/a-home/modules/home-user/src/dto/passportUser.ts +3 -0
  20. package/vona/src/suite/a-home/modules/home-user/src/entity/user.ts +3 -0
  21. package/vona/src/suite/a-home/modules/home-user/src/service/account.ts +7 -3
  22. package/vona/src/suite/a-home/modules/home-user/src/service/userAdapter.ts +1 -0
  23. package/vona/src/suite/a-home/modules/home-user/test/account.test.ts +133 -8
  24. package/vona/src/suite/a-home/modules/home-user/test/accountOpenapi.test.ts +32 -5
  25. package/vona/src/suite/a-home/modules/home-user/test/passport.test.ts +159 -1
  26. package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
  27. package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
  28. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/package.json +1 -1
  29. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/bean/bean.captcha.ts +271 -137
  30. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/bean/cacheRedis.captcha.ts +199 -2
  31. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/lib/captchaLua.ts +90 -0
  32. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/types/captcha.ts +15 -1
  33. package/vona/src/suite-vendor/a-captcha/modules/a-captcha/src/types/captchaProvider.ts +38 -1
  34. package/vona/src/suite-vendor/a-captcha/package.json +1 -1
  35. package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
  36. package/vona/src/suite-vendor/a-vona/modules/a-permission/src/bean/bean.permission.ts +54 -8
  37. package/vona/src/suite-vendor/a-vona/modules/a-permission/test/permission.test.ts +12 -15
  38. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +1 -1
  39. package/vona/src/suite-vendor/a-vona/modules/a-user/package.json +1 -1
  40. package/vona/src/suite-vendor/a-vona/modules/a-user/src/types/user.ts +1 -0
  41. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  42. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/schemas.ts +68 -52
  43. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/api/openapi/types.ts +530 -344
  44. package/zova/src/suite/a-commerce/modules/commerce-siteadmin/src/page/dashboard/controller.tsx +7 -1
  45. package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +6 -1
  46. package/zova/src/suite/a-home/modules/home-base/src/config/locale/en-us.ts +1 -0
  47. package/zova/src/suite/a-home/modules/home-base/src/config/locale/zh-cn.ts +1 -0
  48. package/zova/src/suite/a-home/modules/home-layoutadmin/src/component/layoutAdmin/render.user.tsx +1 -1
  49. package/zova/src/suite/a-home/modules/home-layoutweb/src/component/layoutWeb/render.user.tsx +3 -1
  50. package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +4 -0
  51. package/zova/src/suite/a-home/modules/home-user/src/config/locale/en-us.ts +1 -0
  52. package/zova/src/suite/a-home/modules/home-user/src/config/locale/zh-cn.ts +1 -0
  53. package/zova/src/suite/a-home/modules/home-user/src/model/account.ts +2 -1
  54. package/zova/src/suite/a-home/modules/home-user/src/page/account/controller.tsx +17 -3
  55. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/test/aggregate.test.ts +0 -247
@@ -995,11 +995,11 @@ importers:
995
995
  specifier: npm:@cabloy/zod@4.3.8
996
996
  version: '@cabloy/zod@4.3.8'
997
997
  zova:
998
- specifier: ^5.1.176
999
- version: 5.1.176(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
998
+ specifier: ^5.1.177
999
+ version: 5.1.177(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1000
1000
  zova-jsx:
1001
- specifier: ^1.1.94
1002
- version: 1.1.94(typescript@5.9.3)
1001
+ specifier: ^1.1.95
1002
+ version: 1.1.95(typescript@5.9.3)
1003
1003
  zova-module-a-api:
1004
1004
  specifier: ^5.1.23
1005
1005
  version: 5.1.23
@@ -1061,8 +1061,8 @@ importers:
1061
1061
  specifier: ^5.1.36
1062
1062
  version: 5.1.36
1063
1063
  zova-module-a-ssr:
1064
- specifier: ^5.1.41
1065
- version: 5.1.41
1064
+ specifier: ^5.1.42
1065
+ version: 5.1.42
1066
1066
  zova-module-a-ssrhmr:
1067
1067
  specifier: ^5.1.24
1068
1068
  version: 5.1.24
@@ -1079,8 +1079,8 @@ importers:
1079
1079
  specifier: ^5.1.39
1080
1080
  version: 5.1.39
1081
1081
  zova-module-a-zova:
1082
- specifier: ^5.1.113
1083
- version: 5.1.113(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1082
+ specifier: ^5.1.114
1083
+ version: 5.1.114(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1084
1084
  zova-module-pay-mock:
1085
1085
  specifier: ^5.0.6
1086
1086
  version: 5.0.6
@@ -1124,11 +1124,11 @@ importers:
1124
1124
  specifier: npm:@cabloy/zod@4.3.8
1125
1125
  version: '@cabloy/zod@4.3.8'
1126
1126
  zova:
1127
- specifier: ^5.1.176
1128
- version: 5.1.176(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1127
+ specifier: ^5.1.177
1128
+ version: 5.1.177(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1129
1129
  zova-jsx:
1130
- specifier: ^1.1.94
1131
- version: 1.1.94(typescript@5.9.3)
1130
+ specifier: ^1.1.95
1131
+ version: 1.1.95(typescript@5.9.3)
1132
1132
  zova-module-a-api:
1133
1133
  specifier: ^5.1.23
1134
1134
  version: 5.1.23
@@ -1190,8 +1190,8 @@ importers:
1190
1190
  specifier: ^5.1.36
1191
1191
  version: 5.1.36
1192
1192
  zova-module-a-ssr:
1193
- specifier: ^5.1.41
1194
- version: 5.1.41
1193
+ specifier: ^5.1.42
1194
+ version: 5.1.42
1195
1195
  zova-module-a-ssrhmr:
1196
1196
  specifier: ^5.1.24
1197
1197
  version: 5.1.24
@@ -1208,8 +1208,8 @@ importers:
1208
1208
  specifier: ^5.1.39
1209
1209
  version: 5.1.39
1210
1210
  zova-module-a-zova:
1211
- specifier: ^5.1.113
1212
- version: 5.1.113(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1211
+ specifier: ^5.1.114
1212
+ version: 5.1.114(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
1213
1213
  zova-module-pay-mock:
1214
1214
  specifier: ^5.0.6
1215
1215
  version: 5.0.6
@@ -1990,7 +1990,7 @@ importers:
1990
1990
  specifier: ^5.0.3
1991
1991
  version: link:modules/a-markdown
1992
1992
  vona-module-a-rbac:
1993
- specifier: ^5.0.17
1993
+ specifier: ^5.0.18
1994
1994
  version: link:modules/a-rbac
1995
1995
  vona-module-a-socket:
1996
1996
  specifier: workspace:^
@@ -9412,15 +9412,9 @@ packages:
9412
9412
  resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
9413
9413
  engines: {node: '>=10'}
9414
9414
 
9415
- zova-core@5.1.94:
9416
- resolution: {integrity: sha512-puo3pqa4/ngwZj//Md457XMloZ4FRaoJlmwf+zyPyxKL7GoV81BaJeUPi9szzY8aM9EkoWXBhSVA0cp6mTXBhw==}
9417
-
9418
9415
  zova-core@5.1.95:
9419
9416
  resolution: {integrity: sha512-nvBb3zVdJEQ9SWi+cl5j2PLs/T0FRRIULfHqU3SBFUn3jFSZFizniRlak+qoSq3mzj+LGD61BfnFJQb2JJ8+Zg==}
9420
9417
 
9421
- zova-jsx@1.1.94:
9422
- resolution: {integrity: sha512-4QGgNE5J32PXYCgHBt8JBrTRp8CXADgZzIBUsovQmKfIwtMY6DK5+zPQeWBVqHoV5vK95RKhRPwdAKFGOLupeg==}
9423
-
9424
9418
  zova-jsx@1.1.95:
9425
9419
  resolution: {integrity: sha512-gzmR01Qt4oBrMCejH4W0fqfFx62L+XQOfOROs6mBVdFYGmbyXzy/lDXtTH4v4WjlrmVbuoAyZE/BrYmNxn2swA==}
9426
9420
 
@@ -9484,9 +9478,6 @@ packages:
9484
9478
  zova-module-a-routertabs@5.1.36:
9485
9479
  resolution: {integrity: sha512-QYkeK7ql0B2AdFcCj9a+oIyqZi7oW/Ay/YHMFSseRgoRU/dTQnK79/v/DWB8+JluxY0tWMakLDcSq7bIolRdgg==}
9486
9480
 
9487
- zova-module-a-ssr@5.1.41:
9488
- resolution: {integrity: sha512-2p19mMzc9MjvYDdEbV6qjOngxuM7vdzwA1+Z4+/81pOfptU7nBobqDaDUNZxblHEoiIHrDM8NCeghPBO6Fz2sg==}
9489
-
9490
9481
  zova-module-a-ssr@5.1.42:
9491
9482
  resolution: {integrity: sha512-XaZcUHwDxwyQi4VNLK6CxkdG4lV5aUNgPNkiLjN3LWh9l2yS0u8CQqGV8+gEKcpOOj7XhZIubu4gb4GUNNQoGw==}
9492
9483
 
@@ -9505,9 +9496,6 @@ packages:
9505
9496
  zova-module-a-zod@5.1.39:
9506
9497
  resolution: {integrity: sha512-nyVgDGt7Xly/8ccGau/ZRck3PJfo+9WFMR4EudXo2YBFMahNeXUegTyUPeSV6DfJDUjCqpy2V060PipBVJ5QzQ==}
9507
9498
 
9508
- zova-module-a-zova@5.1.113:
9509
- resolution: {integrity: sha512-PuasOjYgRz8F94oVPSjQepwVxL+LySauwJQKNzly+yD7YCbQJcHy6tw7IejwcaSTjlBe3M1Iy6IWX7IwrOrfyw==}
9510
-
9511
9499
  zova-module-a-zova@5.1.114:
9512
9500
  resolution: {integrity: sha512-H16jQ2YRw3Q9KSO7RDKMl+QRFZSZ7J/AkUUD71BTQA1oDLY+zBLuKTABwicEgS3S44R4qJC79rR/yuTXLlZnkw==}
9513
9501
 
@@ -9523,15 +9511,9 @@ packages:
9523
9511
  zova-module-rest-resource@5.1.57:
9524
9512
  resolution: {integrity: sha512-xpx42J+ZCx3KGzFZPQBHfUUDxgYTPlQGrUTrN0/SCY8PD/qYu77kqQ29+B5B9WcUeSnSAzUtbZx0UzzFia1slQ==}
9525
9513
 
9526
- zova-suite-a-zova@5.1.175:
9527
- resolution: {integrity: sha512-cjN97iaDZ3WhvOtu27YnqfZpEnnKptLGqqpllIhAtOFaTXtuMGiGVTmH7Q1tc/k9OPEHzvvCThsYKRxzYbR5DQ==}
9528
-
9529
9514
  zova-suite-a-zova@5.1.176:
9530
9515
  resolution: {integrity: sha512-w7JQ0cutO3zaZosxfsSLn9/mcY5deftZWTGkiPJTTG6I6eGGZlFJRDCQpu+dBd8eYI66xe4kstdH3KGhLSt74A==}
9531
9516
 
9532
- zova@5.1.176:
9533
- resolution: {integrity: sha512-7E/Kc9QkuB6ckDX2ual9DFmiVrcPy/umDVlC+QlRFy5A30L8D5/WNKzWxmBubXYsXWmsAd/VZQUQPwditMmNkA==}
9534
-
9535
9517
  zova@5.1.177:
9536
9518
  resolution: {integrity: sha512-35jZVs08Qn4cybwNqu1/PE3qz1J7Vi64y7AgTD98rVv5uOnDPkuwIIIVgMYZllV/C3kCsWPBLoC3tz/UG4SEcA==}
9537
9519
 
@@ -15880,37 +15862,6 @@ snapshots:
15880
15862
 
15881
15863
  yocto-queue@0.1.0: {}
15882
15864
 
15883
- zova-core@5.1.94(typescript@5.9.3):
15884
- dependencies:
15885
- '@cabloy/compose': link:packages-utils/compose
15886
- '@cabloy/extend': link:packages-utils/extend
15887
- '@cabloy/json5': link:packages-utils/json5
15888
- '@cabloy/localeutil': link:packages-utils/localeutil
15889
- '@cabloy/logger': 1.1.25
15890
- '@cabloy/module-info': 2.0.3
15891
- '@cabloy/utils': link:packages-utils/utils
15892
- '@cabloy/vue-compiler-sfc': 3.5.14
15893
- '@cabloy/vue-reactivity': 3.5.16
15894
- '@cabloy/vue-router': 4.4.18(vue@3.5.43(typescript@5.9.3))
15895
- '@cabloy/vue-runtime-core': 3.5.57
15896
- '@cabloy/vue-runtime-dom': 3.5.13
15897
- '@cabloy/vue-server-renderer': 3.5.18(vue@3.5.43(typescript@5.9.3))
15898
- '@cabloy/word-utils': 2.1.14
15899
- '@cabloy/zod-errors-custom': link:packages-utils/zod-errors-custom
15900
- '@cabloy/zod-openapi': link:packages-utils/zod-openapi
15901
- '@cabloy/zod-query': link:packages-utils/zod-query
15902
- '@types/js-cookie': 3.0.6
15903
- '@types/ms': 2.1.0
15904
- '@vue/shared': 3.5.36
15905
- deep-equal: 2.2.3
15906
- ms: 2.1.3
15907
- reflect-metadata: 0.2.2
15908
- vue: 3.5.43(typescript@5.9.3)
15909
- vue-plugin-render-freeze: 2.0.4(vue@3.5.43(typescript@5.9.3))
15910
- zod: '@cabloy/zod@4.3.8'
15911
- transitivePeerDependencies:
15912
- - typescript
15913
-
15914
15865
  zova-core@5.1.95(patch_hash=44197c3f359a73a1a5bb3dc0a8a97e705a55b30f9a434717aa24d75a96f2c6b6)(typescript@5.9.3):
15915
15866
  dependencies:
15916
15867
  '@cabloy/compose': link:packages-utils/compose
@@ -15942,17 +15893,6 @@ snapshots:
15942
15893
  transitivePeerDependencies:
15943
15894
  - typescript
15944
15895
 
15945
- zova-jsx@1.1.94(typescript@5.9.3):
15946
- dependencies:
15947
- '@cabloy/compose': link:packages-utils/compose
15948
- '@cabloy/utils': link:packages-utils/utils
15949
- '@cabloy/word-utils': 2.1.14
15950
- typestyle: 2.4.0
15951
- vue: 3.5.43(typescript@5.9.3)
15952
- zova-core: 5.1.94(typescript@5.9.3)
15953
- transitivePeerDependencies:
15954
- - typescript
15955
-
15956
15896
  zova-jsx@1.1.95(typescript@5.9.3):
15957
15897
  dependencies:
15958
15898
  '@cabloy/compose': link:packages-utils/compose
@@ -16033,11 +15973,6 @@ snapshots:
16033
15973
 
16034
15974
  zova-module-a-routertabs@5.1.36: {}
16035
15975
 
16036
- zova-module-a-ssr@5.1.41:
16037
- dependencies:
16038
- devalue: 5.9.4
16039
- ms: 2.1.3
16040
-
16041
15976
  zova-module-a-ssr@5.1.42:
16042
15977
  dependencies:
16043
15978
  devalue: 5.9.4
@@ -16071,22 +16006,6 @@ snapshots:
16071
16006
  '@cabloy/zod-query': link:packages-utils/zod-query
16072
16007
  zod: '@cabloy/zod@4.3.8'
16073
16008
 
16074
- zova-module-a-zova@5.1.113(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16075
- dependencies:
16076
- '@cabloy/compose': link:packages-utils/compose
16077
- '@cabloy/deps': link:packages-utils/deps
16078
- '@cabloy/json5': link:packages-utils/json5
16079
- '@cabloy/module-info': 2.0.3
16080
- '@cabloy/utils': link:packages-utils/utils
16081
- '@cabloy/vue-router': 4.4.18(vue@3.5.43(typescript@5.9.3))
16082
- '@cabloy/word-utils': 2.1.14
16083
- defu: 6.1.7
16084
- luxon: 3.7.2
16085
- zova-jsx: 1.1.94(typescript@5.9.3)
16086
- transitivePeerDependencies:
16087
- - typescript
16088
- - vue
16089
-
16090
16009
  zova-module-a-zova@5.1.114(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16091
16010
  dependencies:
16092
16011
  '@cabloy/compose': link:packages-utils/compose
@@ -16111,39 +16030,6 @@ snapshots:
16111
16030
 
16112
16031
  zova-module-rest-resource@5.1.57: {}
16113
16032
 
16114
- zova-suite-a-zova@5.1.175(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16115
- dependencies:
16116
- zova-module-a-api: 5.1.23
16117
- zova-module-a-app: 5.1.27
16118
- zova-module-a-bean: 5.1.37
16119
- zova-module-a-behavior: 5.1.28
16120
- zova-module-a-behaviors: 5.1.23
16121
- zova-module-a-command: 5.1.36
16122
- zova-module-a-fetch: 5.1.26(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)
16123
- zova-module-a-form: 5.1.55(vue@3.5.43(typescript@5.9.3))
16124
- zova-module-a-icon: 5.1.28
16125
- zova-module-a-interceptor: 5.1.32
16126
- zova-module-a-logger: 5.1.29
16127
- zova-module-a-meta: 5.1.23
16128
- zova-module-a-model: 5.1.39(vue@3.5.43(typescript@5.9.3))
16129
- zova-module-a-openapi: 5.1.59
16130
- zova-module-a-router: 5.1.43
16131
- zova-module-a-routerstack: 5.1.30
16132
- zova-module-a-routertabs: 5.1.36
16133
- zova-module-a-ssr: 5.1.41
16134
- zova-module-a-ssrhmr: 5.1.24
16135
- zova-module-a-ssrserver: 5.1.27
16136
- zova-module-a-style: 5.1.36
16137
- zova-module-a-table: 5.1.45(vue@3.5.43(typescript@5.9.3))
16138
- zova-module-a-zod: 5.1.39
16139
- zova-module-a-zova: 5.1.113(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
16140
- transitivePeerDependencies:
16141
- - '@vue/composition-api'
16142
- - debug
16143
- - supports-color
16144
- - typescript
16145
- - vue
16146
-
16147
16033
  zova-suite-a-zova@5.1.176(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16148
16034
  dependencies:
16149
16035
  zova-module-a-api: 5.1.23
@@ -16177,17 +16063,6 @@ snapshots:
16177
16063
  - typescript
16178
16064
  - vue
16179
16065
 
16180
- zova@5.1.176(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16181
- dependencies:
16182
- zova-core: 5.1.94(typescript@5.9.3)
16183
- zova-suite-a-zova: 5.1.175(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3))
16184
- transitivePeerDependencies:
16185
- - '@vue/composition-api'
16186
- - debug
16187
- - supports-color
16188
- - typescript
16189
- - vue
16190
-
16191
16066
  zova@5.1.177(debug@4.4.3(supports-color@7.2.0))(supports-color@7.2.0)(typescript@5.9.3)(vue@3.5.43(typescript@5.9.3)):
16192
16067
  dependencies:
16193
16068
  zova-core: 5.1.95(patch_hash=44197c3f359a73a1a5bb3dc0a8a97e705a55b30f9a434717aa24d75a96f2c6b6)(typescript@5.9.3)
@@ -1,6 +1,6 @@
1
1
  import type { IDecoratorDtoOptions } from 'vona-module-a-web';
2
2
 
3
- import { Api } from 'vona-module-a-openapiutils';
3
+ import { Api, v } from 'vona-module-a-openapiutils';
4
4
  import { Dto } from 'vona-module-a-web';
5
5
 
6
6
  export interface IDtoOptionsOperatorContext extends IDecoratorDtoOptions {}
@@ -18,4 +18,7 @@ export class DtoOperatorContext {
18
18
 
19
19
  @Api.field()
20
20
  userName: string;
21
+
22
+ @Api.field(v.optional())
23
+ displayName?: string;
21
24
  }
@@ -13,6 +13,7 @@ export class ServiceOperator extends BeanBase {
13
13
  instanceName: instanceName ?? '',
14
14
  userId: user.id.toString(),
15
15
  userName: user.name,
16
+ displayName: user.displayName,
16
17
  };
17
18
  }
18
19
  }
@@ -47,6 +47,7 @@ describe('operatorAccess.test.ts', () => {
47
47
  assert.equal(result.instanceName, app.ctx.instanceName);
48
48
  assert.equal(result.userId, app.bean.passport.current!.user!.id.toString());
49
49
  assert.equal(result.userName, app.bean.passport.current!.user!.name);
50
+ assert.equal(result.displayName, app.bean.passport.current!.user!.displayName);
50
51
  } finally {
51
52
  await app.bean.passport.signout();
52
53
  }
@@ -32,6 +32,7 @@ export class MetaVersion extends BeanBase implements IMetaVersionUpdate, IMetaVe
32
32
  table.comment(entityUser.$comment.$table);
33
33
  table.basicFields();
34
34
  table.string(entityUser.name, 255).comment(entityUser.$comment.name);
35
+ table.string(entityUser.displayName, 100).comment(entityUser.$comment.displayName);
35
36
  table.string(entityUser.avatar, 255).comment(entityUser.$comment.avatar);
36
37
  table.string(entityUser.email, 255).comment(entityUser.$comment.email);
37
38
  table.string(entityUser.mobile, 255).comment(entityUser.$comment.mobile);
@@ -157,6 +157,7 @@ export class ControllerPassport extends BeanBase {
157
157
  user: {
158
158
  id: user.id,
159
159
  name: user.name,
160
+ displayName: user.displayName,
160
161
  avatar: user.avatar,
161
162
  email: user.email,
162
163
  mobile: user.mobile,
@@ -11,6 +11,9 @@ export class DtoAccountCurrent {
11
11
  @Api.field()
12
12
  name: string;
13
13
 
14
+ @Api.field(v.optional())
15
+ displayName?: string;
16
+
14
17
  @Api.field(v.optional())
15
18
  avatar?: string;
16
19
 
@@ -29,8 +29,15 @@ const avatarSchema = avatar.openapi({
29
29
 
30
30
  @Dto<IDtoOptionsAccountProfileUpdate>()
31
31
  export class DtoAccountProfileUpdate {
32
- @Api.field(v.title($locale('AccountDisplayName')), v.min(1), v.max(100), v.trim())
33
- name: string;
32
+ @Api.field(
33
+ v.title($locale('AccountDisplayName')),
34
+ z.preprocess(
35
+ // Preserve an explicit clear before optional-string coercion treats '' as omitted.
36
+ value => (value === '' ? null : value),
37
+ z.string().trim().max(100).nullable().optional(),
38
+ ),
39
+ )
40
+ displayName?: string | null;
34
41
 
35
42
  @Api.field(v.title($locale('AccountAvatar')), v.optional(), v.nullable(), avatarSchema)
36
43
  avatar?: string | null;
@@ -17,6 +17,9 @@ export class DtoPassportUser {
17
17
  @Api.field()
18
18
  name: string;
19
19
 
20
+ @Api.field(v.optional())
21
+ displayName?: string;
22
+
20
23
  @Api.field(v.optional())
21
24
  avatar?: string;
22
25
 
@@ -15,6 +15,9 @@ export class EntityUser extends EntityBase implements IUser {
15
15
  @Api.field(v.title($locale('UserName')))
16
16
  name: string;
17
17
 
18
+ @Api.field(v.title($locale('AccountDisplayName')), z.string().trim().max(100).optional())
19
+ displayName?: string;
20
+
18
21
  @Api.field(v.title($locale('UserAvatar')), v.optional())
19
22
  avatar?: string;
20
23
 
@@ -63,7 +63,10 @@ export class ServiceAccount extends BeanBase {
63
63
  const locale = this._resolveLocale(command.locale, user.locale);
64
64
  const tz = this._resolveTimezone(command.tz, user.tz);
65
65
  await this.scope.model.user.updateById(user.id, {
66
- name: command.name,
66
+ displayName:
67
+ command.displayName === undefined
68
+ ? user.displayName
69
+ : command.displayName?.trim() || undefined,
67
70
  avatar,
68
71
  locale,
69
72
  tz,
@@ -218,12 +221,12 @@ export class ServiceAccount extends BeanBase {
218
221
  await this.bean.mail.send({
219
222
  to: currentUser.email,
220
223
  subject: replaceTemplate(this.scope.locale.ActivationEmailSubject(), {
221
- userName: currentUser.name,
224
+ userName: currentUser.displayName || currentUser.name,
222
225
  link,
223
226
  siteName: this.ctx.instance.title || this.app.meta.env.APP_TITLE,
224
227
  }),
225
228
  text: replaceTemplate(this.scope.locale.ActivationEmailBody(), {
226
- userName: currentUser.name,
229
+ userName: currentUser.displayName || currentUser.name,
227
230
  link,
228
231
  siteName: this.ctx.instance.title || this.app.meta.env.APP_TITLE,
229
232
  }),
@@ -521,6 +524,7 @@ export class ServiceAccount extends BeanBase {
521
524
  const eligibleEmail = user.email?.trim() || undefined;
522
525
  return {
523
526
  name: user.name,
527
+ displayName: user.displayName,
524
528
  avatar: user.avatar,
525
529
  locale: user.locale,
526
530
  tz: user.tz,
@@ -13,6 +13,7 @@ export class ServiceUserAdapter extends BeanBase implements IUserAdapter {
13
13
  async userOfProfile(profile: IAuthUserProfile): Promise<Partial<IUser>> {
14
14
  return {
15
15
  name: profile.username!,
16
+ displayName: profile.displayName?.trim() || undefined,
16
17
  email: profile.emails?.[0].value,
17
18
  avatar: profile.photos?.[0].value,
18
19
  locale: profile.locale || this.ctx.locale,
@@ -34,19 +34,22 @@ describe('account.test.ts', { concurrency: false }, () => {
34
34
  innerAccess: false,
35
35
  });
36
36
  assert.equal(current.name, fixture!.name);
37
+ assert.equal(current.displayName, undefined);
37
38
  assert.equal(current.hasSimpleAuth, true);
38
39
  assert.equal(current.canSendSetPasswordLink, false);
39
40
 
40
41
  const updated = await app.bean.executor.performAction('patch', profilePath, {
41
42
  innerAccess: false,
42
43
  body: {
43
- name: `${fixture!.name}-updated`,
44
+ displayName: ' Updated display name ',
45
+ name: `${fixture!.name}-must-not-rename`,
44
46
  locale: 'en_us',
45
47
  tz: 'America/New_York',
46
48
  ignored: 'must-not-persist',
47
49
  },
48
50
  });
49
- assert.equal(updated.name, `${fixture!.name}-updated`);
51
+ assert.equal(updated.name, fixture!.name);
52
+ assert.equal(updated.displayName, 'Updated display name');
50
53
  assert.equal(updated.locale, 'en-us');
51
54
  assert.equal(updated.tz, 'America/New_York');
52
55
  } finally {
@@ -56,13 +59,23 @@ describe('account.test.ts', { concurrency: false }, () => {
56
59
 
57
60
  await app.bean.executor.mockCtx(async () => {
58
61
  const user = await app.bean.user.findOneById(fixture!.userId);
59
- assert.equal(user?.name, `${fixture!.name}-updated`);
62
+ assert.equal(user?.name, fixture!.name);
63
+ assert.equal(user?.displayName, 'Updated display name');
60
64
  assert.ok(user);
61
65
  assert.equal('ignored' in user, false);
66
+ assert.equal(
67
+ await app.bean.user.findOneByName(`${fixture!.name}-must-not-rename`),
68
+ undefined,
69
+ );
62
70
  });
63
71
 
64
72
  await app.bean.executor.mockCtx(async () => {
65
- await app.bean.passport.signinSystem('mock', -10002 as any, `${fixture!.name}-updated`);
73
+ const jwt = await app.bean.auth.authenticate('auth-simple:simple', {
74
+ clientName: 'default',
75
+ state: { intention: 'login' },
76
+ clientOptions: { username: fixture!.name, password: 'initial-password' },
77
+ });
78
+ assert.ok(jwt?.accessToken);
66
79
  try {
67
80
  const result = await app.bean.executor.performAction('post', passwordChangePath, {
68
81
  innerAccess: false,
@@ -91,6 +104,105 @@ describe('account.test.ts', { concurrency: false }, () => {
91
104
  }
92
105
  });
93
106
 
107
+ it('retains omitted displayName, clears null or blank, and validates the trimmed limit', async () => {
108
+ let fixture: IAccountFixture | undefined;
109
+ try {
110
+ fixture = await createFixture();
111
+ const displayName = 'd'.repeat(100);
112
+ const updated = await updateProfile(fixture, { displayName: ` ${displayName} ` });
113
+ assert.equal(updated.displayName, displayName);
114
+ assert.equal(updated.name, fixture.name);
115
+
116
+ const retained = await updateProfile(fixture, { locale: 'en-us' });
117
+ assert.equal(retained.displayName, displayName);
118
+
119
+ await app.bean.executor.mockCtx(async () => {
120
+ await app.bean.passport.signinSystem('mock', -10005 as any, fixture!.name);
121
+ try {
122
+ const account = app.scope('home-user').service.account;
123
+ const result = await account.updateProfile({
124
+ name: `${fixture!.name}-service-must-not-rename`,
125
+ } as unknown as Parameters<typeof account.updateProfile>[0]);
126
+ assert.equal(result.name, fixture!.name);
127
+ assert.equal(result.displayName, displayName);
128
+ } finally {
129
+ await app.bean.passport.signout();
130
+ }
131
+ });
132
+
133
+ for (const invalid of ['d'.repeat(101), ` ${'d'.repeat(101)} `]) {
134
+ await assert.rejects(
135
+ () => updateProfile(fixture!, { displayName: invalid }),
136
+ (error: { code?: number }) => error.code === 422,
137
+ );
138
+ }
139
+ await app.bean.executor.mockCtx(async () => {
140
+ const user = await app.bean.user.findOneById(fixture!.userId);
141
+ assert.equal(user?.name, fixture!.name);
142
+ assert.equal(user?.displayName, displayName);
143
+ });
144
+
145
+ for (const empty of [null, ' \t\n ', '']) {
146
+ await updateProfile(fixture, { displayName: 'Before clearing' });
147
+ const cleared = await updateProfile(fixture, { displayName: empty });
148
+ assert.equal(cleared.name, fixture.name);
149
+ assert.equal(cleared.displayName, undefined);
150
+ await app.bean.executor.mockCtx(async () => {
151
+ const user = await app.bean.user.findOneById(fixture!.userId);
152
+ assert.ok(user);
153
+ assert.ok(user.displayName == null);
154
+ await app.bean.passport.signinSystem('mock', -10006 as any, fixture!.name);
155
+ try {
156
+ const current = await app.bean.executor.performAction('get', currentPath, {
157
+ innerAccess: false,
158
+ });
159
+ assert.equal(current.displayName, undefined);
160
+ } finally {
161
+ await app.bean.passport.signout();
162
+ }
163
+ });
164
+ }
165
+ } finally {
166
+ if (fixture) await removeFixture(fixture);
167
+ }
168
+ });
169
+
170
+ it('allows two users to share displayName while retaining their original username logins', async () => {
171
+ const fixtures: IAccountFixture[] = [];
172
+ const displayName = `Shared ${crypto.randomUUID()}`;
173
+ try {
174
+ fixtures.push(await createFixture());
175
+ fixtures.push(await createFixture());
176
+ for (const fixture of fixtures) {
177
+ const updated = await updateProfile(fixture, { displayName });
178
+ assert.equal(updated.name, fixture.name);
179
+ assert.equal(updated.displayName, displayName);
180
+ }
181
+ for (const fixture of fixtures) {
182
+ await app.bean.executor.mockCtx(async () => {
183
+ try {
184
+ const jwt = await app.bean.auth.authenticate('auth-simple:simple', {
185
+ clientName: 'default',
186
+ state: { intention: 'login' },
187
+ clientOptions: { username: fixture.name, password: 'initial-password' },
188
+ });
189
+ assert.ok(jwt?.accessToken);
190
+ assert.equal(app.bean.passport.currentUser?.id.toString(), fixture.userId);
191
+ const current = await app.bean.executor.performAction('get', currentPath, {
192
+ innerAccess: false,
193
+ });
194
+ assert.equal(current.name, fixture.name);
195
+ assert.equal(current.displayName, displayName);
196
+ } finally {
197
+ await app.bean.passport.signout();
198
+ }
199
+ });
200
+ }
201
+ } finally {
202
+ for (const fixture of fixtures.reverse()) await removeFixture(fixture);
203
+ }
204
+ });
205
+
94
206
  it('accepts a freshly uploaded avatar during profile update', async () => {
95
207
  let fixture: IAccountFixture | undefined;
96
208
  let imageId: string | number | undefined;
@@ -133,7 +245,6 @@ describe('account.test.ts', { concurrency: false }, () => {
133
245
  const updated = await app.bean.executor.performAction('patch', profilePath, {
134
246
  innerAccess: false,
135
247
  body: {
136
- name: fixture!.name,
137
248
  avatar: uploadedUrl,
138
249
  },
139
250
  });
@@ -145,7 +256,7 @@ describe('account.test.ts', { concurrency: false }, () => {
145
256
 
146
257
  const preserved = await app.bean.executor.performAction('patch', profilePath, {
147
258
  innerAccess: false,
148
- body: { name: fixture!.name },
259
+ body: {},
149
260
  });
150
261
  assert.equal(preserved.avatar, uploadedUrl);
151
262
 
@@ -153,7 +264,6 @@ describe('account.test.ts', { concurrency: false }, () => {
153
264
  const thirdPartyUpdated = await app.bean.executor.performAction('patch', profilePath, {
154
265
  innerAccess: false,
155
266
  body: {
156
- name: fixture!.name,
157
267
  avatar: thirdPartyAvatar,
158
268
  },
159
269
  });
@@ -161,7 +271,7 @@ describe('account.test.ts', { concurrency: false }, () => {
161
271
 
162
272
  const cleared = await app.bean.executor.performAction('patch', profilePath, {
163
273
  innerAccess: false,
164
- body: { name: fixture!.name, avatar: null },
274
+ body: { avatar: null },
165
275
  });
166
276
  assert.equal(cleared.avatar, undefined);
167
277
  } finally {
@@ -214,6 +324,20 @@ describe('account.test.ts', { concurrency: false }, () => {
214
324
  });
215
325
  });
216
326
 
327
+ async function updateProfile(fixture: IAccountFixture, body: Record<string, unknown>) {
328
+ return await app.bean.executor.mockCtx(async () => {
329
+ await app.bean.passport.signinSystem('mock', -10007 as any, fixture.name);
330
+ try {
331
+ return await app.bean.executor.performAction('patch', profilePath, {
332
+ innerAccess: false,
333
+ body,
334
+ });
335
+ } finally {
336
+ await app.bean.passport.signout();
337
+ }
338
+ });
339
+ }
340
+
217
341
  async function createFixture(): Promise<IAccountFixture> {
218
342
  return await app.bean.executor.mockCtx(async () => {
219
343
  const name = `account-test-${crypto.randomUUID()}`;
@@ -244,6 +368,7 @@ async function removeFixture(fixture: IAccountFixture): Promise<void> {
244
368
  await app.bean.executor.mockCtx(async () => {
245
369
  await app.scope('a-auth').model.auth.deleteById(fixture.authId);
246
370
  await app.scope('auth-simple').model.authSimple.deleteById(fixture.authSimpleId);
371
+ await app.scope('home-user').model.roleUser.delete({ userId: fixture.userId });
247
372
  await app.bean.user.removeById(fixture.userId);
248
373
  });
249
374
  }