From c22b90c7f49568f5325eb095832c434a7027921a Mon Sep 17 00:00:00 2001 From: chenhang Date: Mon, 8 Jun 2026 13:07:37 +0800 Subject: [PATCH] feat: setup barrelsby for automatic barrel file generation Add barrelsby as a dev dependency with a `generate-barrels` npm script and a `barrelesby.json` config targeting `./src`. This automates creation of barrel/index files to simplify imports across the project. --- barrelesby.json | 3 + package.json | 4 +- pnpm-lock.yaml | 284 ++++++++++++++++++ src/data/models/auth/apple_login_request.ts | 37 +++ .../models/auth/facebook_login_request.ts | 47 +++ src/data/models/auth/fb_id_login_request.ts | 37 +++ src/data/models/auth/google_login_request.ts | 47 +++ src/data/models/auth/guest_login_request.ts | 26 ++ src/data/models/auth/guest_login_response.ts | 73 +++++ src/data/models/auth/index.ts | 14 + src/data/models/auth/login_request.ts | 58 ++++ src/data/models/auth/login_response.ts | 43 +++ src/data/models/auth/logout_response.ts | 22 ++ src/data/models/auth/refresh_token_request.ts | 26 ++ .../models/auth/refresh_token_response.ts | 45 +++ src/data/models/auth/register_request.ts | 57 ++++ src/data/models/auth/send_code_request.ts | 26 ++ src/data/models/chat/chat_history_response.ts | 60 ++++ src/data/models/chat/chat_message.ts | 53 ++++ src/data/models/chat/chat_send_response.ts | 98 ++++++ src/data/models/chat/chat_sync_data.ts | 37 +++ src/data/models/chat/chat_sync_request.ts | 28 ++ src/data/models/chat/guest_chat_quota.ts | 113 +++++++ src/data/models/chat/image_upload_response.ts | 79 +++++ src/data/models/chat/index.ts | 11 + src/data/models/chat/send_message_request.ts | 82 +++++ src/data/models/chat/stt_data.ts | 24 ++ src/data/models/chat/sync_message.ts | 41 +++ src/data/models/index.ts | 5 + src/data/models/metrics/app_event.ts | 41 +++ src/data/models/metrics/index.ts | 3 + src/data/models/metrics/pwa_event.ts | 68 +++++ src/data/models/user/avatar_data.ts | 35 +++ src/data/models/user/credits_data.ts | 35 +++ src/data/models/user/credits_history_data.ts | 57 ++++ src/data/models/user/index.ts | 9 + src/data/models/user/personality_traits.ts | 48 +++ src/data/models/user/recent_memory.ts | 59 ++++ .../models/user/update_profile_request.ts | 47 +++ src/data/models/user/user.ts | 128 ++++++++ src/data/models/user/user_stats_response.ts | 156 ++++++++++ 41 files changed, 2165 insertions(+), 1 deletion(-) create mode 100644 barrelesby.json create mode 100644 src/data/models/auth/apple_login_request.ts create mode 100644 src/data/models/auth/facebook_login_request.ts create mode 100644 src/data/models/auth/fb_id_login_request.ts create mode 100644 src/data/models/auth/google_login_request.ts create mode 100644 src/data/models/auth/guest_login_request.ts create mode 100644 src/data/models/auth/guest_login_response.ts create mode 100644 src/data/models/auth/index.ts create mode 100644 src/data/models/auth/login_request.ts create mode 100644 src/data/models/auth/login_response.ts create mode 100644 src/data/models/auth/logout_response.ts create mode 100644 src/data/models/auth/refresh_token_request.ts create mode 100644 src/data/models/auth/refresh_token_response.ts create mode 100644 src/data/models/auth/register_request.ts create mode 100644 src/data/models/auth/send_code_request.ts create mode 100644 src/data/models/chat/chat_history_response.ts create mode 100644 src/data/models/chat/chat_message.ts create mode 100644 src/data/models/chat/chat_send_response.ts create mode 100644 src/data/models/chat/chat_sync_data.ts create mode 100644 src/data/models/chat/chat_sync_request.ts create mode 100644 src/data/models/chat/guest_chat_quota.ts create mode 100644 src/data/models/chat/image_upload_response.ts create mode 100644 src/data/models/chat/index.ts create mode 100644 src/data/models/chat/send_message_request.ts create mode 100644 src/data/models/chat/stt_data.ts create mode 100644 src/data/models/chat/sync_message.ts create mode 100644 src/data/models/index.ts create mode 100644 src/data/models/metrics/app_event.ts create mode 100644 src/data/models/metrics/index.ts create mode 100644 src/data/models/metrics/pwa_event.ts create mode 100644 src/data/models/user/avatar_data.ts create mode 100644 src/data/models/user/credits_data.ts create mode 100644 src/data/models/user/credits_history_data.ts create mode 100644 src/data/models/user/index.ts create mode 100644 src/data/models/user/personality_traits.ts create mode 100644 src/data/models/user/recent_memory.ts create mode 100644 src/data/models/user/update_profile_request.ts create mode 100644 src/data/models/user/user.ts create mode 100644 src/data/models/user/user_stats_response.ts diff --git a/barrelesby.json b/barrelesby.json new file mode 100644 index 00000000..93aa81a8 --- /dev/null +++ b/barrelesby.json @@ -0,0 +1,3 @@ +{ + "directory": "./src" +} \ No newline at end of file diff --git a/package.json b/package.json index 72f906e4..372f9f3e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "build": "next build", "start": "next start", "lint": "eslint", - "lint:fix": "eslint --fix" + "lint:fix": "eslint --fix", + "generate-barrels": "barrelsby --delete" }, "dependencies": { "next": "16.2.7", @@ -19,6 +20,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "barrelsby": "^2.8.1", "eslint": "^9", "eslint-config-next": "16.2.7", "tailwindcss": "^4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0e56796f..6e5ad650 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,9 @@ importers: '@types/react-dom': specifier: ^19 version: 19.2.3(@types/react@19.2.16) + barrelsby: + specifier: ^2.8.1 + version: 2.8.1 eslint: specifier: ^9 version: 9.39.4(jiti@2.7.0) @@ -553,6 +556,12 @@ packages: '@types/react@19.2.16': resolution: {integrity: sha512-esJiCAnl0kfpNdE69f3So4WJUXy95dLZydX0KwK46riIHDzHM7O9Vtf9xCHW0PXIqvgqNrswl522kA/5yx+F4w==} + '@types/yargs-parser@21.0.3': + resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} + + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} + '@typescript-eslint/eslint-plugin@8.60.1': resolution: {integrity: sha512-JQ4S5GB0tfjO8BuJ4fcX+HodkzJjYBV+7OJ+wLygaX7OGQ7FudyHL4NSCA6ob+w3Yn+5MkKIozOwQhXeM7opVg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} @@ -745,6 +754,14 @@ packages: ajv@6.15.0: resolution: {integrity: sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==} + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -814,6 +831,11 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} + barrelsby@2.8.1: + resolution: {integrity: sha512-barN2MVKqUVwmjRy3JLSMYufrBDcdWUc2pjlR0V9P8S3aMvvJ4StFz1GJMzEi5GBoQlnBIWOcCxBDzI2xfaaGw==} + engines: {node: '>=6.0.0'} + hasBin: true + baseline-browser-mapping@2.10.33: resolution: {integrity: sha512-bA6+tcSLpz2tIEdDXZPpPTIuxBcC4+w6SieaYyfigIa4h8GlFxbA17v22Vx3JUtuZQj9SgOsnbK+aTBzyDyEuw==} engines: {node: '>=6.0.0'} @@ -854,6 +876,10 @@ packages: caniuse-lite@1.0.30001793: resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==} + chalk@2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -861,10 +887,20 @@ packages: client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} + color-name@1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -939,6 +975,9 @@ packages: electron-to-chromium@1.5.368: resolution: {integrity: sha512-7RckJJK4uESJF9PxvfMWd3TGqIiieUTG4HxnKaKuIpGbcr+r2ZEB3g2gAhCP3Fqm42vJSzLfgab9eva/C4/XVw==} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + emoji-regex@9.2.2: resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} @@ -946,6 +985,9 @@ packages: resolution: {integrity: sha512-0rxICaFZ7NQho/sHely2bvOPRP0Eu2B0NZ9zM54YvRvWMn7jfz3DmnOZDR9LlXDdDcqntAVc6Hfy4gr/tdH/Ag==} engines: {node: '>=10.13.0'} + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + es-abstract@1.24.2: resolution: {integrity: sha512-2FpH9Q5i2RRwyEP1AylXe6nYLR5OhaJTZwmlcP0dL/+JCbgg7yyEo/sEK6HeGZRf3dFpWwThaRHVApXSkW3xeg==} engines: {node: '>= 0.4'} @@ -982,6 +1024,10 @@ packages: resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} engines: {node: '>=6'} + escape-string-regexp@1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1131,6 +1177,10 @@ packages: picomatch: optional: true + figures@2.0.0: + resolution: {integrity: sha512-Oa2M9atig69ZkfwiApY8F2Yy+tzMbazyvqv21R0NsSC8floSOC09BbT1ITWAdoMGQvJ/aZnR1KMwdx9tvHnTNA==} + engines: {node: '>=4'} + file-entry-cache@8.0.0: resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==} engines: {node: '>=16.0.0'} @@ -1139,6 +1189,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} + find-up@2.1.0: + resolution: {integrity: sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==} + engines: {node: '>=4'} + find-up@5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1172,6 +1226,10 @@ packages: resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} engines: {node: '>=6.9.0'} + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.3.0: resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} engines: {node: '>= 0.4'} @@ -1218,6 +1276,10 @@ packages: resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} engines: {node: '>= 0.4'} + has-flag@3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1271,6 +1333,9 @@ packages: resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} engines: {node: '>= 0.4'} + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + is-async-function@2.1.1: resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} engines: {node: '>= 0.4'} @@ -1310,6 +1375,10 @@ packages: resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} engines: {node: '>= 0.4'} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.1.2: resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} engines: {node: '>= 0.4'} @@ -1399,6 +1468,9 @@ packages: json-buffer@3.0.1: resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + json-parse-better-errors@1.0.2: + resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} + json-schema-traverse@0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} @@ -1506,6 +1578,14 @@ packages: resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} engines: {node: '>= 12.0.0'} + load-json-file@4.0.0: + resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} + engines: {node: '>=4'} + + locate-path@2.0.0: + resolution: {integrity: sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==} + engines: {node: '>=4'} + locate-path@6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1630,18 +1710,38 @@ packages: resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} engines: {node: '>= 0.4'} + p-limit@1.3.0: + resolution: {integrity: sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==} + engines: {node: '>=4'} + p-limit@3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} + p-locate@2.0.0: + resolution: {integrity: sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==} + engines: {node: '>=4'} + p-locate@5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} + p-try@1.0.0: + resolution: {integrity: sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==} + engines: {node: '>=4'} + parent-module@1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} + parse-json@4.0.0: + resolution: {integrity: sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==} + engines: {node: '>=4'} + + path-exists@3.0.0: + resolution: {integrity: sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==} + engines: {node: '>=4'} + path-exists@4.0.0: resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} engines: {node: '>=8'} @@ -1664,6 +1764,14 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} + pify@3.0.0: + resolution: {integrity: sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==} + engines: {node: '>=4'} + + pkg-conf@2.1.0: + resolution: {integrity: sha512-C+VUP+8jis7EsQZIhDYmS5qlNtjv2yP4SNtjXK9AP1ZcTRlnSfuumaTnRfYZnYgUUYVIKqL0fRvmUGDV2fmp6g==} + engines: {node: '>=4'} + possible-typed-array-names@1.1.0: resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} engines: {node: '>= 0.4'} @@ -1710,6 +1818,10 @@ packages: resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1793,6 +1905,10 @@ packages: resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} engines: {node: '>= 0.4'} + signale@1.4.0: + resolution: {integrity: sha512-iuh+gPf28RkltuJC7W5MRi6XAjTDCAPC/prJUpQoG4vIP3MJZ+GTydVnodXA7pwvTKb2cA0m9OFZW/cdWy/I/w==} + engines: {node: '>=6'} + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -1804,6 +1920,10 @@ packages: resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} engines: {node: '>= 0.4'} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.includes@2.0.1: resolution: {integrity: sha512-o7+c9bW6zpAdJHTtujeePODAhkuicdAryFsfVKwA+wGw89wJ4GTY484WTucM9hLtDEOpOvI+aHnzqnC5lHp4Rg==} engines: {node: '>= 0.4'} @@ -1827,6 +1947,10 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + strip-bom@3.0.0: resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} engines: {node: '>=4'} @@ -1848,6 +1972,10 @@ packages: babel-plugin-macros: optional: true + supports-color@5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1959,9 +2087,25 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -2422,6 +2566,12 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/yargs-parser@21.0.3': {} + + '@types/yargs@17.0.35': + dependencies: + '@types/yargs-parser': 21.0.3 + '@typescript-eslint/eslint-plugin@8.60.1(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.2 @@ -2596,6 +2746,12 @@ snapshots: json-schema-traverse: 0.4.1 uri-js: 4.4.1 + ansi-regex@5.0.1: {} + + ansi-styles@3.2.1: + dependencies: + color-convert: 1.9.3 + ansi-styles@4.3.0: dependencies: color-convert: 2.0.1 @@ -2687,6 +2843,12 @@ snapshots: balanced-match@4.0.4: {} + barrelsby@2.8.1: + dependencies: + '@types/yargs': 17.0.35 + signale: 1.4.0 + yargs: 17.7.2 + baseline-browser-mapping@2.10.33: {} brace-expansion@1.1.15: @@ -2731,6 +2893,12 @@ snapshots: caniuse-lite@1.0.30001793: {} + chalk@2.4.2: + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + chalk@4.1.2: dependencies: ansi-styles: 4.3.0 @@ -2738,10 +2906,22 @@ snapshots: client-only@0.0.1: {} + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@1.9.3: + dependencies: + color-name: 1.1.3 + color-convert@2.0.1: dependencies: color-name: 1.1.4 + color-name@1.1.3: {} + color-name@1.1.4: {} concat-map@0.0.1: {} @@ -2812,6 +2992,8 @@ snapshots: electron-to-chromium@1.5.368: {} + emoji-regex@8.0.0: {} + emoji-regex@9.2.2: {} enhanced-resolve@5.22.2: @@ -2819,6 +3001,10 @@ snapshots: graceful-fs: 4.2.11 tapable: 2.3.3 + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + es-abstract@1.24.2: dependencies: array-buffer-byte-length: 1.0.2 @@ -2922,6 +3108,8 @@ snapshots: escalade@3.2.0: {} + escape-string-regexp@1.0.5: {} + escape-string-regexp@4.0.0: {} eslint-config-next@16.2.7(@typescript-eslint/parser@8.60.1(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3))(eslint@9.39.4(jiti@2.7.0))(typescript@5.9.3): @@ -3151,6 +3339,10 @@ snapshots: optionalDependencies: picomatch: 4.0.4 + figures@2.0.0: + dependencies: + escape-string-regexp: 1.0.5 + file-entry-cache@8.0.0: dependencies: flat-cache: 4.0.1 @@ -3159,6 +3351,10 @@ snapshots: dependencies: to-regex-range: 5.0.1 + find-up@2.1.0: + dependencies: + locate-path: 2.0.0 + find-up@5.0.0: dependencies: locate-path: 6.0.0 @@ -3192,6 +3388,8 @@ snapshots: gensync@1.0.0-beta.2: {} + get-caller-file@2.0.5: {} + get-intrinsic@1.3.0: dependencies: call-bind-apply-helpers: 1.0.2 @@ -3243,6 +3441,8 @@ snapshots: has-bigints@1.1.0: {} + has-flag@3.0.0: {} + has-flag@4.0.0: {} has-property-descriptors@1.0.2: @@ -3292,6 +3492,8 @@ snapshots: call-bound: 1.0.4 get-intrinsic: 1.3.0 + is-arrayish@0.2.1: {} + is-async-function@2.1.1: dependencies: async-function: 1.0.0 @@ -3336,6 +3538,8 @@ snapshots: dependencies: call-bound: 1.0.4 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.1.2: dependencies: call-bound: 1.0.4 @@ -3423,6 +3627,8 @@ snapshots: json-buffer@3.0.1: {} + json-parse-better-errors@1.0.2: {} + json-schema-traverse@0.4.1: {} json-stable-stringify-without-jsonify@1.0.1: {} @@ -3504,6 +3710,18 @@ snapshots: lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + load-json-file@4.0.0: + dependencies: + graceful-fs: 4.2.11 + parse-json: 4.0.0 + pify: 3.0.0 + strip-bom: 3.0.0 + + locate-path@2.0.0: + dependencies: + p-locate: 2.0.0 + path-exists: 3.0.0 + locate-path@6.0.0: dependencies: p-locate: 5.0.0 @@ -3639,18 +3857,35 @@ snapshots: object-keys: 1.1.1 safe-push-apply: 1.0.0 + p-limit@1.3.0: + dependencies: + p-try: 1.0.0 + p-limit@3.1.0: dependencies: yocto-queue: 0.1.0 + p-locate@2.0.0: + dependencies: + p-limit: 1.3.0 + p-locate@5.0.0: dependencies: p-limit: 3.1.0 + p-try@1.0.0: {} + parent-module@1.0.1: dependencies: callsites: 3.1.0 + parse-json@4.0.0: + dependencies: + error-ex: 1.3.4 + json-parse-better-errors: 1.0.2 + + path-exists@3.0.0: {} + path-exists@4.0.0: {} path-key@3.1.1: {} @@ -3663,6 +3898,13 @@ snapshots: picomatch@4.0.4: {} + pify@3.0.0: {} + + pkg-conf@2.1.0: + dependencies: + find-up: 2.1.0 + load-json-file: 4.0.0 + possible-typed-array-names@1.1.0: {} postcss@8.4.31: @@ -3718,6 +3960,8 @@ snapshots: gopd: 1.2.0 set-function-name: 2.0.2 + require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve-pkg-maps@1.0.0: {} @@ -3850,6 +4094,12 @@ snapshots: side-channel-map: 1.0.1 side-channel-weakmap: 1.0.2 + signale@1.4.0: + dependencies: + chalk: 2.4.2 + figures: 2.0.0 + pkg-conf: 2.1.0 + source-map-js@1.2.1: {} stable-hash@0.0.5: {} @@ -3859,6 +4109,12 @@ snapshots: es-errors: 1.3.0 internal-slot: 1.1.0 + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.includes@2.0.1: dependencies: call-bind: 1.0.9 @@ -3909,6 +4165,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.1.2 + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + strip-bom@3.0.0: {} strip-json-comments@3.1.1: {} @@ -3920,6 +4180,10 @@ snapshots: optionalDependencies: '@babel/core': 7.29.7 + supports-color@5.5.0: + dependencies: + has-flag: 3.0.0 + supports-color@7.2.0: dependencies: has-flag: 4.0.0 @@ -4095,8 +4359,28 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + y18n@5.0.8: {} + yallist@3.1.1: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} zod-validation-error@4.0.2(zod@4.4.3): diff --git a/src/data/models/auth/apple_login_request.ts b/src/data/models/auth/apple_login_request.ts new file mode 100644 index 00000000..0f3dfd59 --- /dev/null +++ b/src/data/models/auth/apple_login_request.ts @@ -0,0 +1,37 @@ +/** + * Apple 登录请求 + * 原始 Dart: AppleLoginRequest (lib/data/models/auth/auth_request.dart) + */ +export class AppleLoginRequest { + readonly identityToken: string; + readonly platform: string; + + constructor(params: { identityToken: string; platform: string }) { + this.identityToken = params.identityToken; + this.platform = params.platform; + Object.freeze(this); + } + + toJson(): Record { + return { + identityToken: this.identityToken, + platform: this.platform, + }; + } + + static fromJson(json: Record): AppleLoginRequest { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `AppleLoginRequest.${key} is required and must be a string` + ); + } + return v; + }; + return new AppleLoginRequest({ + identityToken: requireString("identityToken"), + platform: requireString("platform"), + }); + } +} diff --git a/src/data/models/auth/facebook_login_request.ts b/src/data/models/auth/facebook_login_request.ts new file mode 100644 index 00000000..6847b6fa --- /dev/null +++ b/src/data/models/auth/facebook_login_request.ts @@ -0,0 +1,47 @@ +/** + * Facebook 登录请求 + * 原始 Dart: FacebookLoginRequest (lib/data/models/auth/auth_request.dart) + * + * 注:原始 Dart 中 `guestId` 为 `String?`,按"全部非空"约定统一兜底为空串。 + */ +export class FacebookLoginRequest { + readonly accessToken: string; + readonly platform: string; + readonly guestId: string; + + constructor(params: { + accessToken: string; + platform: string; + guestId?: string; + }) { + this.accessToken = params.accessToken; + this.platform = params.platform; + this.guestId = params.guestId ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + accessToken: this.accessToken, + platform: this.platform, + guestId: this.guestId, + }; + } + + static fromJson(json: Record): FacebookLoginRequest { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `FacebookLoginRequest.${key} is required and must be a string` + ); + } + return v; + }; + return new FacebookLoginRequest({ + accessToken: requireString("accessToken"), + platform: requireString("platform"), + guestId: typeof json.guestId === "string" ? json.guestId : undefined, + }); + } +} diff --git a/src/data/models/auth/fb_id_login_request.ts b/src/data/models/auth/fb_id_login_request.ts new file mode 100644 index 00000000..bc45f1a0 --- /dev/null +++ b/src/data/models/auth/fb_id_login_request.ts @@ -0,0 +1,37 @@ +/** + * Facebook ID 登录请求 + * 原始 Dart: FbIdLoginRequest (lib/data/models/auth/auth_request.dart) + * + * 注:原始 Dart 中 `avatarUrl` 为 `String?`,按"全部非空"约定统一兜底为空串。 + */ +export class FbIdLoginRequest { + readonly fbId: string; + readonly avatarUrl: string; + + constructor(params: { fbId: string; avatarUrl?: string }) { + this.fbId = params.fbId; + this.avatarUrl = params.avatarUrl ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + fbId: this.fbId, + avatarUrl: this.avatarUrl, + }; + } + + static fromJson(json: Record): FbIdLoginRequest { + const fbId = json.fbId; + if (typeof fbId !== "string") { + throw new Error( + "FbIdLoginRequest.fbId is required and must be a string" + ); + } + return new FbIdLoginRequest({ + fbId, + avatarUrl: + typeof json.avatarUrl === "string" ? json.avatarUrl : undefined, + }); + } +} diff --git a/src/data/models/auth/google_login_request.ts b/src/data/models/auth/google_login_request.ts new file mode 100644 index 00000000..9331c140 --- /dev/null +++ b/src/data/models/auth/google_login_request.ts @@ -0,0 +1,47 @@ +/** + * Google 登录请求 + * 原始 Dart: GoogleLoginRequest (lib/data/models/auth/auth_request.dart) + * + * 注:原始 Dart 中 `guestId` 为 `String?`,按"全部非空"约定统一兜底为空串。 + */ +export class GoogleLoginRequest { + readonly idToken: string; + readonly platform: string; + readonly guestId: string; + + constructor(params: { + idToken: string; + platform: string; + guestId?: string; + }) { + this.idToken = params.idToken; + this.platform = params.platform; + this.guestId = params.guestId ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + idToken: this.idToken, + platform: this.platform, + guestId: this.guestId, + }; + } + + static fromJson(json: Record): GoogleLoginRequest { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `GoogleLoginRequest.${key} is required and must be a string` + ); + } + return v; + }; + return new GoogleLoginRequest({ + idToken: requireString("idToken"), + platform: requireString("platform"), + guestId: typeof json.guestId === "string" ? json.guestId : undefined, + }); + } +} diff --git a/src/data/models/auth/guest_login_request.ts b/src/data/models/auth/guest_login_request.ts new file mode 100644 index 00000000..cfd6d695 --- /dev/null +++ b/src/data/models/auth/guest_login_request.ts @@ -0,0 +1,26 @@ +/** + * 游客登录请求 + * 原始 Dart: GuestLoginRequest (lib/data/models/auth/auth_request.dart) + */ +export class GuestLoginRequest { + readonly deviceId: string; + + constructor(params: { deviceId: string }) { + this.deviceId = params.deviceId; + Object.freeze(this); + } + + toJson(): Record { + return { deviceId: this.deviceId }; + } + + static fromJson(json: Record): GuestLoginRequest { + const deviceId = json.deviceId; + if (typeof deviceId !== "string") { + throw new Error( + "GuestLoginRequest.deviceId is required and must be a string" + ); + } + return new GuestLoginRequest({ deviceId }); + } +} diff --git a/src/data/models/auth/guest_login_response.ts b/src/data/models/auth/guest_login_response.ts new file mode 100644 index 00000000..00004957 --- /dev/null +++ b/src/data/models/auth/guest_login_response.ts @@ -0,0 +1,73 @@ +/** + * 游客登录响应 + * 原始 Dart: GuestLoginResponse (lib/data/models/auth/auth_response.dart) + * + * 注:原始 Dart 中 `user` 为 `User?`,按"全部非空"约定兜底为默认 User 实例 + * (id/username 为空串,使用方可通过 user.id === "" 判定无用户数据)。 + */ +import { User } from "../user/user"; + +export class GuestLoginResponse { + readonly token: string; + readonly deviceId: string; + readonly userId: string; + readonly isDeviceUser: boolean; + readonly expiresIn: number; + readonly user: User; + + constructor(params: { + token: string; + deviceId: string; + userId: string; + isDeviceUser?: boolean; + expiresIn?: number; + user?: User; + }) { + this.token = params.token; + this.deviceId = params.deviceId; + this.userId = params.userId; + this.isDeviceUser = params.isDeviceUser ?? true; + this.expiresIn = params.expiresIn ?? 2592000; + this.user = params.user ?? new User({ id: "", username: "" }); + Object.freeze(this); + } + + toJson(): Record { + return { + token: this.token, + deviceId: this.deviceId, + userId: this.userId, + isDeviceUser: this.isDeviceUser, + expiresIn: this.expiresIn, + user: this.user.toJson(), + }; + } + + static fromJson(json: Record): GuestLoginResponse { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `GuestLoginResponse.${key} is required and must be a string` + ); + } + return v; + }; + const userJson = json.user; + const user: User = + typeof userJson === "object" && userJson !== null + ? User.fromJson(userJson as Record) + : new User({ id: "", username: "" }); + return new GuestLoginResponse({ + token: requireString("token"), + deviceId: requireString("deviceId"), + userId: requireString("userId"), + isDeviceUser: + typeof json.isDeviceUser === "boolean" + ? json.isDeviceUser + : undefined, + expiresIn: typeof json.expiresIn === "number" ? json.expiresIn : undefined, + user, + }); + } +} diff --git a/src/data/models/auth/index.ts b/src/data/models/auth/index.ts new file mode 100644 index 00000000..ed667286 --- /dev/null +++ b/src/data/models/auth/index.ts @@ -0,0 +1,14 @@ +// auth 子目录 barrel export +export { AppleLoginRequest } from "./apple_login_request"; +export { FbIdLoginRequest } from "./fb_id_login_request"; +export { FacebookLoginRequest } from "./facebook_login_request"; +export { GoogleLoginRequest } from "./google_login_request"; +export { GuestLoginRequest } from "./guest_login_request"; +export { GuestLoginResponse } from "./guest_login_response"; +export { LoginRequest } from "./login_request"; +export { LoginResponse } from "./login_response"; +export { LogoutResponse } from "./logout_response"; +export { RefreshTokenRequest } from "./refresh_token_request"; +export { RefreshTokenResponse } from "./refresh_token_response"; +export { RegisterRequest } from "./register_request"; +export { SendCodeRequest } from "./send_code_request"; diff --git a/src/data/models/auth/login_request.ts b/src/data/models/auth/login_request.ts new file mode 100644 index 00000000..5e49f28e --- /dev/null +++ b/src/data/models/auth/login_request.ts @@ -0,0 +1,58 @@ +/** + * 登录请求 - 支持用户名或邮箱登录 + * 原始 Dart: LoginRequest (lib/data/models/auth/auth_request.dart) + * + * 注:原始 Dart 中 `email`/`username`/`guestId` 为 `String?`,按"全部非空"约定统一兜底为空串。 + * 后端校验 email 与 username 至少传入一个。 + */ +export class LoginRequest { + readonly email: string; + readonly username: string; + readonly password: string; + readonly platform: string; + readonly guestId: string; + + constructor(params: { + email?: string; + username?: string; + password: string; + platform: string; + guestId?: string; + }) { + this.email = params.email ?? ""; + this.username = params.username ?? ""; + this.password = params.password; + this.platform = params.platform; + this.guestId = params.guestId ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + email: this.email, + username: this.username, + password: this.password, + platform: this.platform, + guestId: this.guestId, + }; + } + + static fromJson(json: Record): LoginRequest { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `LoginRequest.${key} is required and must be a string` + ); + } + return v; + }; + return new LoginRequest({ + email: typeof json.email === "string" ? json.email : undefined, + username: typeof json.username === "string" ? json.username : undefined, + password: requireString("password"), + platform: requireString("platform"), + guestId: typeof json.guestId === "string" ? json.guestId : undefined, + }); + } +} diff --git a/src/data/models/auth/login_response.ts b/src/data/models/auth/login_response.ts new file mode 100644 index 00000000..12b858a8 --- /dev/null +++ b/src/data/models/auth/login_response.ts @@ -0,0 +1,43 @@ +/** + * 登录响应 + * 原始 Dart: LoginResponse (lib/data/models/auth/auth_response.dart) + */ +import { User } from "../user/user"; + +export class LoginResponse { + readonly user: User; + readonly token: string; + readonly refreshToken: string; + + constructor(params: { user: User; token: string; refreshToken?: string }) { + this.user = params.user; + this.token = params.token; + this.refreshToken = params.refreshToken ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + user: this.user.toJson(), + token: this.token, + refreshToken: this.refreshToken, + }; + } + + static fromJson(json: Record): LoginResponse { + const token = json.token; + if (typeof token !== "string") { + throw new Error("LoginResponse.token is required and must be a string"); + } + const userJson = json.user; + if (typeof userJson !== "object" || userJson === null) { + throw new Error("LoginResponse.user is required and must be an object"); + } + return new LoginResponse({ + user: User.fromJson(userJson as Record), + token, + refreshToken: + typeof json.refreshToken === "string" ? json.refreshToken : undefined, + }); + } +} diff --git a/src/data/models/auth/logout_response.ts b/src/data/models/auth/logout_response.ts new file mode 100644 index 00000000..53b54d5e --- /dev/null +++ b/src/data/models/auth/logout_response.ts @@ -0,0 +1,22 @@ +/** + * 退出登录响应 + * 原始 Dart: LogoutResponse (lib/data/models/auth/auth_response.dart) + */ +export class LogoutResponse { + readonly success: boolean; + + constructor(params: { success?: boolean }) { + this.success = params.success ?? true; + Object.freeze(this); + } + + toJson(): Record { + return { success: this.success }; + } + + static fromJson(json: Record): LogoutResponse { + return new LogoutResponse({ + success: typeof json.success === "boolean" ? json.success : undefined, + }); + } +} diff --git a/src/data/models/auth/refresh_token_request.ts b/src/data/models/auth/refresh_token_request.ts new file mode 100644 index 00000000..44feabb4 --- /dev/null +++ b/src/data/models/auth/refresh_token_request.ts @@ -0,0 +1,26 @@ +/** + * 刷新 Token 请求 + * 原始 Dart: RefreshTokenRequest (lib/data/models/auth/auth_request.dart) + */ +export class RefreshTokenRequest { + readonly refreshToken: string; + + constructor(params: { refreshToken: string }) { + this.refreshToken = params.refreshToken; + Object.freeze(this); + } + + toJson(): Record { + return { refreshToken: this.refreshToken }; + } + + static fromJson(json: Record): RefreshTokenRequest { + const refreshToken = json.refreshToken; + if (typeof refreshToken !== "string") { + throw new Error( + "RefreshTokenRequest.refreshToken is required and must be a string" + ); + } + return new RefreshTokenRequest({ refreshToken }); + } +} diff --git a/src/data/models/auth/refresh_token_response.ts b/src/data/models/auth/refresh_token_response.ts new file mode 100644 index 00000000..774bdc70 --- /dev/null +++ b/src/data/models/auth/refresh_token_response.ts @@ -0,0 +1,45 @@ +/** + * 刷新 Token 响应 + * 原始 Dart: RefreshTokenResponse (lib/data/models/auth/auth_response.dart) + */ +export class RefreshTokenResponse { + readonly token: string; + readonly refreshToken: string; + readonly userId: string; + + constructor(params: { + token: string; + refreshToken: string; + userId: string; + }) { + this.token = params.token; + this.refreshToken = params.refreshToken; + this.userId = params.userId; + Object.freeze(this); + } + + toJson(): Record { + return { + token: this.token, + refreshToken: this.refreshToken, + userId: this.userId, + }; + } + + static fromJson(json: Record): RefreshTokenResponse { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `RefreshTokenResponse.${key} is required and must be a string` + ); + } + return v; + }; + return new RefreshTokenResponse({ + token: requireString("token"), + refreshToken: requireString("refreshToken"), + userId: requireString("userId"), + }); + } +} diff --git a/src/data/models/auth/register_request.ts b/src/data/models/auth/register_request.ts new file mode 100644 index 00000000..70ba5868 --- /dev/null +++ b/src/data/models/auth/register_request.ts @@ -0,0 +1,57 @@ +/** + * 注册请求 + * 原始 Dart: RegisterRequest (lib/data/models/auth/auth_request.dart) + * + * 注:原始 Dart 中 `guestId` 为 `String?`,按"全部非空"约定统一兜底为空串。 + */ +export class RegisterRequest { + readonly username: string; + readonly email: string; + readonly password: string; + readonly platform: string; + readonly guestId: string; + + constructor(params: { + username: string; + email: string; + password: string; + platform: string; + guestId?: string; + }) { + this.username = params.username; + this.email = params.email; + this.password = params.password; + this.platform = params.platform; + this.guestId = params.guestId ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + username: this.username, + email: this.email, + password: this.password, + platform: this.platform, + guestId: this.guestId, + }; + } + + static fromJson(json: Record): RegisterRequest { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `RegisterRequest.${key} is required and must be a string` + ); + } + return v; + }; + return new RegisterRequest({ + username: requireString("username"), + email: requireString("email"), + password: requireString("password"), + platform: requireString("platform"), + guestId: typeof json.guestId === "string" ? json.guestId : undefined, + }); + } +} diff --git a/src/data/models/auth/send_code_request.ts b/src/data/models/auth/send_code_request.ts new file mode 100644 index 00000000..14282865 --- /dev/null +++ b/src/data/models/auth/send_code_request.ts @@ -0,0 +1,26 @@ +/** + * 发送验证码请求 + * 原始 Dart: SendCodeRequest (lib/data/models/auth/auth_request.dart) + */ +export class SendCodeRequest { + readonly email: string; + + constructor(params: { email: string }) { + this.email = params.email; + Object.freeze(this); + } + + toJson(): Record { + return { email: this.email }; + } + + static fromJson(json: Record): SendCodeRequest { + const email = json.email; + if (typeof email !== "string") { + throw new Error( + "SendCodeRequest.email is required and must be a string" + ); + } + return new SendCodeRequest({ email }); + } +} diff --git a/src/data/models/chat/chat_history_response.ts b/src/data/models/chat/chat_history_response.ts new file mode 100644 index 00000000..f81cd14d --- /dev/null +++ b/src/data/models/chat/chat_history_response.ts @@ -0,0 +1,60 @@ +/** + * 聊天历史响应 + * 原始 Dart: ChatHistoryResponse (lib/data/models/chat/chat_response.dart) + * + * 注:原始 Dart 中 `total` 为 `int?`,按"全部非空"约定兜底为 0。 + */ +import { ChatMessage } from "./chat_message"; + +export class ChatHistoryResponse { + readonly messages: ChatMessage[]; + readonly total: number; + readonly limit: number; + readonly offset: number; + + constructor(params: { + messages: ChatMessage[]; + total?: number; + limit: number; + offset: number; + }) { + this.messages = params.messages; + this.total = params.total ?? 0; + this.limit = params.limit; + this.offset = params.offset; + Object.freeze(this); + } + + toJson(): Record { + return { + messages: this.messages.map((m) => m.toJson()), + total: this.total, + limit: this.limit, + offset: this.offset, + }; + } + + static fromJson(json: Record): ChatHistoryResponse { + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error( + `ChatHistoryResponse.${key} is required and must be a number` + ); + } + return v; + }; + const rawMessages = json.messages; + const messages: ChatMessage[] = Array.isArray(rawMessages) + ? (rawMessages as Record[]).map((m) => + ChatMessage.fromJson(m) + ) + : []; + return new ChatHistoryResponse({ + messages, + total: typeof json.total === "number" ? json.total : undefined, + limit: requireNumber("limit"), + offset: requireNumber("offset"), + }); + } +} diff --git a/src/data/models/chat/chat_message.ts b/src/data/models/chat/chat_message.ts new file mode 100644 index 00000000..1d1be8c7 --- /dev/null +++ b/src/data/models/chat/chat_message.ts @@ -0,0 +1,53 @@ +/** + * 聊天历史中的单条消息 + * 原始 Dart: ChatMessage (lib/data/models/chat/chat_response.dart) + * + * 注:原始 Dart 中 `id`/`createdAt` 为 `String?`,按"全部非空"约定兜底为空串。 + */ +export class ChatMessage { + readonly role: string; + readonly content: string; + readonly id: string; + readonly createdAt: string; + + constructor(params: { + role: string; + content: string; + id?: string; + createdAt?: string; + }) { + this.role = params.role; + this.content = params.content; + this.id = params.id ?? ""; + this.createdAt = params.createdAt ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + role: this.role, + content: this.content, + id: this.id, + createdAt: this.createdAt, + }; + } + + static fromJson(json: Record): ChatMessage { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `ChatMessage.${key} is required and must be a string` + ); + } + return v; + }; + return new ChatMessage({ + role: requireString("role"), + content: requireString("content"), + id: typeof json.id === "string" ? json.id : undefined, + createdAt: + typeof json.createdAt === "string" ? json.createdAt : undefined, + }); + } +} diff --git a/src/data/models/chat/chat_send_response.ts b/src/data/models/chat/chat_send_response.ts new file mode 100644 index 00000000..cdc063c3 --- /dev/null +++ b/src/data/models/chat/chat_send_response.ts @@ -0,0 +1,98 @@ +/** + * 发送消息响应 + * 原始 Dart: ChatSendResponse (lib/data/models/chat/chat_response.dart) + * + * 注:原始 Dart 中 `mode`/`currentMood`/`isGuest` 为可空字段,按"全部非空"约定兜底: + * - 字符串 → "" + * - bool → false + * - int → 0 + */ +export class ChatSendResponse { + readonly mode: string; + readonly reply: string; + readonly voiceUrl: string; + readonly audioUrl: string; + readonly intimacyChange: number; + readonly newIntimacy: number; + readonly relationshipStage: string; + readonly currentMood: string; + readonly messageId: string; + readonly isGuest: boolean; + readonly timestamp: number; + + constructor(params: { + mode?: string; + reply: string; + voiceUrl?: string; + audioUrl?: string; + intimacyChange?: number; + newIntimacy?: number; + relationshipStage: string; + currentMood?: string; + messageId: string; + isGuest?: boolean; + timestamp?: number; + }) { + this.mode = params.mode ?? ""; + this.reply = params.reply; + this.voiceUrl = params.voiceUrl ?? ""; + this.audioUrl = params.audioUrl ?? ""; + this.intimacyChange = params.intimacyChange ?? 0; + this.newIntimacy = params.newIntimacy ?? 0; + this.relationshipStage = params.relationshipStage; + this.currentMood = params.currentMood ?? ""; + this.messageId = params.messageId; + this.isGuest = params.isGuest ?? false; + this.timestamp = params.timestamp ?? 0; + Object.freeze(this); + } + + toJson(): Record { + return { + mode: this.mode, + reply: this.reply, + voiceUrl: this.voiceUrl, + audioUrl: this.audioUrl, + intimacyChange: this.intimacyChange, + newIntimacy: this.newIntimacy, + relationshipStage: this.relationshipStage, + currentMood: this.currentMood, + messageId: this.messageId, + isGuest: this.isGuest, + timestamp: this.timestamp, + }; + } + + static fromJson(json: Record): ChatSendResponse { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `ChatSendResponse.${key} is required and must be a string` + ); + } + return v; + }; + return new ChatSendResponse({ + mode: typeof json.mode === "string" ? json.mode : undefined, + reply: requireString("reply"), + voiceUrl: + typeof json.voiceUrl === "string" ? json.voiceUrl : undefined, + audioUrl: + typeof json.audioUrl === "string" ? json.audioUrl : undefined, + intimacyChange: + typeof json.intimacyChange === "number" + ? json.intimacyChange + : undefined, + newIntimacy: + typeof json.newIntimacy === "number" ? json.newIntimacy : undefined, + relationshipStage: requireString("relationshipStage"), + currentMood: + typeof json.currentMood === "string" ? json.currentMood : undefined, + messageId: requireString("messageId"), + isGuest: typeof json.isGuest === "boolean" ? json.isGuest : undefined, + timestamp: + typeof json.timestamp === "number" ? json.timestamp : undefined, + }); + } +} diff --git a/src/data/models/chat/chat_sync_data.ts b/src/data/models/chat/chat_sync_data.ts new file mode 100644 index 00000000..a6c2287d --- /dev/null +++ b/src/data/models/chat/chat_sync_data.ts @@ -0,0 +1,37 @@ +/** + * 消息同步响应 + * 原始 Dart: ChatSyncData (lib/data/models/chat/chat_sync.dart) + */ +export class ChatSyncData { + readonly syncedCount: number; + readonly totalHistory: number; + + constructor(params: { syncedCount: number; totalHistory: number }) { + this.syncedCount = params.syncedCount; + this.totalHistory = params.totalHistory; + Object.freeze(this); + } + + toJson(): Record { + return { + syncedCount: this.syncedCount, + totalHistory: this.totalHistory, + }; + } + + static fromJson(json: Record): ChatSyncData { + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error( + `ChatSyncData.${key} is required and must be a number` + ); + } + return v; + }; + return new ChatSyncData({ + syncedCount: requireNumber("syncedCount"), + totalHistory: requireNumber("totalHistory"), + }); + } +} diff --git a/src/data/models/chat/chat_sync_request.ts b/src/data/models/chat/chat_sync_request.ts new file mode 100644 index 00000000..2a2acec8 --- /dev/null +++ b/src/data/models/chat/chat_sync_request.ts @@ -0,0 +1,28 @@ +/** + * 消息同步请求 + * 原始 Dart: ChatSyncRequest (lib/data/models/chat/chat_sync.dart) + */ +import { SyncMessage } from "./sync_message"; + +export class ChatSyncRequest { + readonly messages: SyncMessage[]; + + constructor(params: { messages: SyncMessage[] }) { + this.messages = params.messages; + Object.freeze(this); + } + + toJson(): Record { + return { messages: this.messages.map((m) => m.toJson()) }; + } + + static fromJson(json: Record): ChatSyncRequest { + const rawMessages = json.messages; + const messages: SyncMessage[] = Array.isArray(rawMessages) + ? (rawMessages as Record[]).map((m) => + SyncMessage.fromJson(m) + ) + : []; + return new ChatSyncRequest({ messages }); + } +} diff --git a/src/data/models/chat/guest_chat_quota.ts b/src/data/models/chat/guest_chat_quota.ts new file mode 100644 index 00000000..3ec456be --- /dev/null +++ b/src/data/models/chat/guest_chat_quota.ts @@ -0,0 +1,113 @@ +/** + * 游客每日聊天配额数据 + * 原始 Dart: GuestChatQuota (lib/data/models/chat/guest_chat_quota.dart) + * + * 注:原始 Dart 中的静态常量与运行时环境判断方法(isDevelopment)已迁移为 + * 静态成员。环境检测逻辑(threshold / totalQuotaDefault 等)保留为方法, + * 内部使用 `process.env.NODE_ENV` 替代 Dart 中的 `AppEnvUtil.isDevelopment`。 + * `initial()` 工厂方法已迁移为静态方法;`needReset` getter 保留为实例方法。 + * + * Dart 中 `initial()` 依赖 `app_date.DateUtils.todayString()`, + * 在 TS 中由调用方通过 `date` 字段传入。 + */ +export class GuestChatQuota { + readonly remaining: number; + readonly date: string; + + // 静态常量 + static readonly maxQuotaPerDay = 40; + static readonly maxQuotaPerDayTest = 4; + static readonly quotaWarningThreshold = 20; + static readonly quotaWarningThresholdTest = 2; + static readonly defaultTotalQuota = 50; + static readonly defaultTotalQuotaDev = 5; + static readonly quotaExhausted = 0; + + constructor(params: { remaining: number; date: string }) { + this.remaining = params.remaining; + this.date = params.date; + Object.freeze(this); + } + + /** + * 配额耗尽状态值(便捷引用) + */ + static get exhausted(): number { + return GuestChatQuota.quotaExhausted; + } + + /** + * 是否处于开发环境 + * 通过 `process.env.NODE_ENV` 判断,替代 Dart 中的 `AppEnvUtil.isDevelopment` + */ + static get isDevelopment(): boolean { + return process.env.NODE_ENV !== "production"; + } + + /** + * 获取当前环境的配额警告阈值 + */ + static get warningThreshold(): number { + return GuestChatQuota.isDevelopment + ? GuestChatQuota.quotaWarningThresholdTest + : GuestChatQuota.quotaWarningThreshold; + } + + /** + * 获取当前环境的每日最大配额 + */ + static get threshold(): number { + return GuestChatQuota.isDevelopment + ? GuestChatQuota.maxQuotaPerDayTest + : GuestChatQuota.maxQuotaPerDay; + } + + /** + * 获取当前环境的总配额默认值 + */ + static get totalQuotaDefault(): number { + return GuestChatQuota.isDevelopment + ? GuestChatQuota.defaultTotalQuotaDev + : GuestChatQuota.defaultTotalQuota; + } + + /** + * 创建初始配额实例 + * @param todayString 当天日期字符串(YYYY-MM-DD),由调用方提供 + */ + static initial(todayString: string): GuestChatQuota { + return new GuestChatQuota({ + remaining: GuestChatQuota.threshold, + date: todayString, + }); + } + + /** + * 是否需要重置(跨天) + * @param todayString 当天日期字符串(YYYY-MM-DD),由调用方提供 + */ + needsReset(todayString: string): boolean { + return this.date !== todayString; + } + + toJson(): Record { + return { + remaining: this.remaining, + date: this.date, + }; + } + + static fromJson(json: Record): GuestChatQuota { + const remaining = json.remaining; + const date = json.date; + if (typeof remaining !== "number") { + throw new Error( + "GuestChatQuota.remaining is required and must be a number" + ); + } + if (typeof date !== "string") { + throw new Error("GuestChatQuota.date is required and must be a string"); + } + return new GuestChatQuota({ remaining, date }); + } +} diff --git a/src/data/models/chat/image_upload_response.ts b/src/data/models/chat/image_upload_response.ts new file mode 100644 index 00000000..04fbb080 --- /dev/null +++ b/src/data/models/chat/image_upload_response.ts @@ -0,0 +1,79 @@ +/** + * 图片上传响应 + * 原始 Dart: ImageUploadResponse (lib/data/models/chat/image_upload_response.dart) + */ +export class ImageUploadResponse { + readonly success: boolean; + readonly imageId: string; + readonly thumbUrl: string; + readonly mediumUrl: string; + readonly originalUrl: string; + readonly width: number; + readonly height: number; + readonly bytes: number; + + constructor(params: { + success?: boolean; + imageId: string; + thumbUrl: string; + mediumUrl: string; + originalUrl: string; + width: number; + height: number; + bytes: number; + }) { + this.success = params.success ?? true; + this.imageId = params.imageId; + this.thumbUrl = params.thumbUrl; + this.mediumUrl = params.mediumUrl; + this.originalUrl = params.originalUrl; + this.width = params.width; + this.height = params.height; + this.bytes = params.bytes; + Object.freeze(this); + } + + toJson(): Record { + return { + success: this.success, + imageId: this.imageId, + thumbUrl: this.thumbUrl, + mediumUrl: this.mediumUrl, + originalUrl: this.originalUrl, + width: this.width, + height: this.height, + bytes: this.bytes, + }; + } + + static fromJson(json: Record): ImageUploadResponse { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `ImageUploadResponse.${key} is required and must be a string` + ); + } + return v; + }; + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error( + `ImageUploadResponse.${key} is required and must be a number` + ); + } + return v; + }; + return new ImageUploadResponse({ + success: typeof json.success === "boolean" ? json.success : undefined, + imageId: requireString("imageId"), + thumbUrl: requireString("thumbUrl"), + mediumUrl: requireString("mediumUrl"), + originalUrl: requireString("originalUrl"), + width: requireNumber("width"), + height: requireNumber("height"), + bytes: requireNumber("bytes"), + }); + } +} diff --git a/src/data/models/chat/index.ts b/src/data/models/chat/index.ts new file mode 100644 index 00000000..d55cf531 --- /dev/null +++ b/src/data/models/chat/index.ts @@ -0,0 +1,11 @@ +// chat 子目录 barrel export +export { ChatHistoryResponse } from "./chat_history_response"; +export { ChatMessage } from "./chat_message"; +export { ChatSendResponse } from "./chat_send_response"; +export { ChatSyncData } from "./chat_sync_data"; +export { ChatSyncRequest } from "./chat_sync_request"; +export { GuestChatQuota } from "./guest_chat_quota"; +export { ImageUploadResponse } from "./image_upload_response"; +export { SendMessageRequest } from "./send_message_request"; +export { SttData } from "./stt_data"; +export { SyncMessage } from "./sync_message"; diff --git a/src/data/models/chat/send_message_request.ts b/src/data/models/chat/send_message_request.ts new file mode 100644 index 00000000..ab636a7b --- /dev/null +++ b/src/data/models/chat/send_message_request.ts @@ -0,0 +1,82 @@ +/** + * 发送消息请求 - 用于 /api/chat/send 接口 + * 原始 Dart: SendMessageRequest (lib/data/models/chat/send_message_request.dart) + * + * 注:原始 Dart 中所有图片相关字段为可空,按"全部非空"约定兜底: + * - 字符串 → "" + * - 数字 → 0 + */ +export class SendMessageRequest { + readonly message: string; + readonly image: string; + readonly imageId: string; + readonly imageThumbUrl: string; + readonly imageMediumUrl: string; + readonly imageOriginalUrl: string; + readonly imageWidth: number; + readonly imageHeight: number; + readonly useWebSocket: boolean; + + constructor(params: { + message?: string; + image?: string; + imageId?: string; + imageThumbUrl?: string; + imageMediumUrl?: string; + imageOriginalUrl?: string; + imageWidth?: number; + imageHeight?: number; + useWebSocket?: boolean; + }) { + this.message = params.message ?? ""; + this.image = params.image ?? ""; + this.imageId = params.imageId ?? ""; + this.imageThumbUrl = params.imageThumbUrl ?? ""; + this.imageMediumUrl = params.imageMediumUrl ?? ""; + this.imageOriginalUrl = params.imageOriginalUrl ?? ""; + this.imageWidth = params.imageWidth ?? 0; + this.imageHeight = params.imageHeight ?? 0; + this.useWebSocket = params.useWebSocket ?? false; + Object.freeze(this); + } + + toJson(): Record { + return { + message: this.message, + image: this.image, + imageId: this.imageId, + imageThumbUrl: this.imageThumbUrl, + imageMediumUrl: this.imageMediumUrl, + imageOriginalUrl: this.imageOriginalUrl, + imageWidth: this.imageWidth, + imageHeight: this.imageHeight, + useWebSocket: this.useWebSocket, + }; + } + + static fromJson(json: Record): SendMessageRequest { + return new SendMessageRequest({ + message: typeof json.message === "string" ? json.message : undefined, + image: typeof json.image === "string" ? json.image : undefined, + imageId: typeof json.imageId === "string" ? json.imageId : undefined, + imageThumbUrl: + typeof json.imageThumbUrl === "string" + ? json.imageThumbUrl + : undefined, + imageMediumUrl: + typeof json.imageMediumUrl === "string" + ? json.imageMediumUrl + : undefined, + imageOriginalUrl: + typeof json.imageOriginalUrl === "string" + ? json.imageOriginalUrl + : undefined, + imageWidth: + typeof json.imageWidth === "number" ? json.imageWidth : undefined, + imageHeight: + typeof json.imageHeight === "number" ? json.imageHeight : undefined, + useWebSocket: + typeof json.useWebSocket === "boolean" ? json.useWebSocket : undefined, + }); + } +} diff --git a/src/data/models/chat/stt_data.ts b/src/data/models/chat/stt_data.ts new file mode 100644 index 00000000..23349291 --- /dev/null +++ b/src/data/models/chat/stt_data.ts @@ -0,0 +1,24 @@ +/** + * STT(语音转文字)响应 + * 原始 Dart: SttData (lib/data/models/chat/stt_response.dart) + */ +export class SttData { + readonly text: string; + + constructor(params: { text: string }) { + this.text = params.text; + Object.freeze(this); + } + + toJson(): Record { + return { text: this.text }; + } + + static fromJson(json: Record): SttData { + const text = json.text; + if (typeof text !== "string") { + throw new Error("SttData.text is required and must be a string"); + } + return new SttData({ text }); + } +} diff --git a/src/data/models/chat/sync_message.ts b/src/data/models/chat/sync_message.ts new file mode 100644 index 00000000..cb9d98b2 --- /dev/null +++ b/src/data/models/chat/sync_message.ts @@ -0,0 +1,41 @@ +/** + * 待同步的单条消息 + * 原始 Dart: SyncMessage (lib/data/models/chat/chat_sync.dart) + */ +export class SyncMessage { + readonly role: string; + readonly content: string; + readonly timestamp: string; + + constructor(params: { role: string; content: string; timestamp: string }) { + this.role = params.role; + this.content = params.content; + this.timestamp = params.timestamp; + Object.freeze(this); + } + + toJson(): Record { + return { + role: this.role, + content: this.content, + timestamp: this.timestamp, + }; + } + + static fromJson(json: Record): SyncMessage { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `SyncMessage.${key} is required and must be a string` + ); + } + return v; + }; + return new SyncMessage({ + role: requireString("role"), + content: requireString("content"), + timestamp: requireString("timestamp"), + }); + } +} diff --git a/src/data/models/index.ts b/src/data/models/index.ts new file mode 100644 index 00000000..11bbbe72 --- /dev/null +++ b/src/data/models/index.ts @@ -0,0 +1,5 @@ +// models 顶层 barrel export - 集中导出所有分类 +export * from "./auth"; +export * from "./chat"; +export * from "./metrics"; +export * from "./user"; diff --git a/src/data/models/metrics/app_event.ts b/src/data/models/metrics/app_event.ts new file mode 100644 index 00000000..f7de520e --- /dev/null +++ b/src/data/models/metrics/app_event.ts @@ -0,0 +1,41 @@ +/** + * 应用事件上报请求 - 用于 /api/data/report-user-info 接口 + * 原始 Dart: AppEvent (lib/data/models/metrics/app_event.dart) + */ +export class AppEvent { + readonly userId: string; + readonly browser: string; + readonly userAgent: string; + + constructor(params: { userId: string; browser: string; userAgent: string }) { + this.userId = params.userId; + this.browser = params.browser; + this.userAgent = params.userAgent; + Object.freeze(this); + } + + toJson(): Record { + return { + userId: this.userId, + browser: this.browser, + userAgent: this.userAgent, + }; + } + + static fromJson(json: Record): AppEvent { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `AppEvent.${key} is required and must be a string` + ); + } + return v; + }; + return new AppEvent({ + userId: requireString("userId"), + browser: requireString("browser"), + userAgent: requireString("userAgent"), + }); + } +} diff --git a/src/data/models/metrics/index.ts b/src/data/models/metrics/index.ts new file mode 100644 index 00000000..4e8cd219 --- /dev/null +++ b/src/data/models/metrics/index.ts @@ -0,0 +1,3 @@ +// metrics 子目录 barrel export +export { AppEvent } from "./app_event"; +export { PwaEvent } from "./pwa_event"; diff --git a/src/data/models/metrics/pwa_event.ts b/src/data/models/metrics/pwa_event.ts new file mode 100644 index 00000000..af753aeb --- /dev/null +++ b/src/data/models/metrics/pwa_event.ts @@ -0,0 +1,68 @@ +/** + * PWA 事件上报请求 - 用于 /api/metrics/pwa/event 接口 + * 原始 Dart: PwaEvent (lib/data/models/metrics/pwa_event.dart) + */ +export class PwaEvent { + readonly deviceId: string; + readonly deviceType: string; + readonly timestamp: number; + readonly pwaInstalled: boolean; + readonly pwaSupported: boolean; + + constructor(params: { + deviceId: string; + deviceType: string; + timestamp: number; + pwaInstalled?: boolean; + pwaSupported?: boolean; + }) { + this.deviceId = params.deviceId; + this.deviceType = params.deviceType; + this.timestamp = params.timestamp; + this.pwaInstalled = params.pwaInstalled ?? false; + this.pwaSupported = params.pwaSupported ?? false; + Object.freeze(this); + } + + toJson(): Record { + return { + deviceId: this.deviceId, + deviceType: this.deviceType, + timestamp: this.timestamp, + pwaInstalled: this.pwaInstalled, + pwaSupported: this.pwaSupported, + }; + } + + static fromJson(json: Record): PwaEvent { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `PwaEvent.${key} is required and must be a string` + ); + } + return v; + }; + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error(`PwaEvent.${key} is required and must be a number`); + } + return v; + }; + return new PwaEvent({ + deviceId: requireString("deviceId"), + deviceType: requireString("deviceType"), + timestamp: requireNumber("timestamp"), + pwaInstalled: + typeof json.pwaInstalled === "boolean" + ? json.pwaInstalled + : undefined, + pwaSupported: + typeof json.pwaSupported === "boolean" + ? json.pwaSupported + : undefined, + }); + } +} diff --git a/src/data/models/user/avatar_data.ts b/src/data/models/user/avatar_data.ts new file mode 100644 index 00000000..ebcea4df --- /dev/null +++ b/src/data/models/user/avatar_data.ts @@ -0,0 +1,35 @@ +/** + * 头像数据模型 - 对应后端 AvatarData schema + * 原始 Dart: AvatarData (lib/data/models/user/user.dart) + * + * 注:原始 Dart 中 `avatarUrl` 为 `String?`,按"全部非空"约定兜底为空串。 + */ +export class AvatarData { + readonly avatarUrl: string; + readonly userId: string; + + constructor(params: { avatarUrl?: string; userId: string }) { + this.avatarUrl = params.avatarUrl ?? ""; + this.userId = params.userId; + Object.freeze(this); + } + + toJson(): Record { + return { + avatarUrl: this.avatarUrl, + userId: this.userId, + }; + } + + static fromJson(json: Record): AvatarData { + const userId = json.userId; + if (typeof userId !== "string") { + throw new Error("AvatarData.userId is required and must be a string"); + } + return new AvatarData({ + userId, + avatarUrl: + typeof json.avatarUrl === "string" ? json.avatarUrl : undefined, + }); + } +} diff --git a/src/data/models/user/credits_data.ts b/src/data/models/user/credits_data.ts new file mode 100644 index 00000000..0f8b7f92 --- /dev/null +++ b/src/data/models/user/credits_data.ts @@ -0,0 +1,35 @@ +/** + * 积分数据模型 - 对应后端 CreditsData schema + * 原始 Dart: CreditsData (lib/data/models/user/user.dart) + */ +export class CreditsData { + readonly userId: string; + readonly dolBalance: number; + + constructor(params: { userId: string; dolBalance: number }) { + this.userId = params.userId; + this.dolBalance = params.dolBalance; + Object.freeze(this); + } + + toJson(): Record { + return { + userId: this.userId, + dolBalance: this.dolBalance, + }; + } + + static fromJson(json: Record): CreditsData { + const userId = json.userId; + const dolBalance = json.dolBalance; + if (typeof userId !== "string") { + throw new Error("CreditsData.userId is required and must be a string"); + } + if (typeof dolBalance !== "number") { + throw new Error( + "CreditsData.dolBalance is required and must be a number" + ); + } + return new CreditsData({ userId, dolBalance }); + } +} diff --git a/src/data/models/user/credits_history_data.ts b/src/data/models/user/credits_history_data.ts new file mode 100644 index 00000000..f74e6986 --- /dev/null +++ b/src/data/models/user/credits_history_data.ts @@ -0,0 +1,57 @@ +/** + * 积分历史记录模型 - 对应后端 CreditsHistoryData schema + * 原始 Dart: CreditsHistoryData (lib/data/models/user/user.dart) + * + * 注:原始 Dart 中 `records` 为 `List>`, + * 迁移为 `Record[]`。 + */ +export class CreditsHistoryData { + readonly records: Record[]; + readonly total: number; + readonly limit: number; + readonly offset: number; + + constructor(params: { + records: Record[]; + total: number; + limit: number; + offset: number; + }) { + this.records = params.records; + this.total = params.total; + this.limit = params.limit; + this.offset = params.offset; + Object.freeze(this); + } + + toJson(): Record { + return { + records: this.records, + total: this.total, + limit: this.limit, + offset: this.offset, + }; + } + + static fromJson(json: Record): CreditsHistoryData { + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error( + `CreditsHistoryData.${key} is required and must be a number` + ); + } + return v; + }; + const rawRecords = json.records; + const records: Record[] = Array.isArray(rawRecords) + ? (rawRecords as Record[]) + : []; + return new CreditsHistoryData({ + records, + total: requireNumber("total"), + limit: requireNumber("limit"), + offset: requireNumber("offset"), + }); + } +} diff --git a/src/data/models/user/index.ts b/src/data/models/user/index.ts new file mode 100644 index 00000000..c79fb904 --- /dev/null +++ b/src/data/models/user/index.ts @@ -0,0 +1,9 @@ +// user 子目录 barrel export +export { AvatarData } from "./avatar_data"; +export { CreditsData } from "./credits_data"; +export { CreditsHistoryData } from "./credits_history_data"; +export { PersonalityTraits } from "./personality_traits"; +export { RecentMemory } from "./recent_memory"; +export { UpdateProfileRequest } from "./update_profile_request"; +export { User } from "./user"; +export { UserStatsResponse } from "./user_stats_response"; diff --git a/src/data/models/user/personality_traits.ts b/src/data/models/user/personality_traits.ts new file mode 100644 index 00000000..3b3d46e2 --- /dev/null +++ b/src/data/models/user/personality_traits.ts @@ -0,0 +1,48 @@ +/** + * 个性特征模型 + * 原始 Dart: PersonalityTraits (lib/data/models/user/user.dart) + * + * 注:所有 double 字段在 Dart 中使用 @Default(0.5)。 + */ +export class PersonalityTraits { + readonly cheerful: number; + readonly caring: number; + readonly playful: number; + readonly serious: number; + readonly romantic: number; + + constructor(params: { + cheerful?: number; + caring?: number; + playful?: number; + serious?: number; + romantic?: number; + }) { + this.cheerful = params.cheerful ?? 0.5; + this.caring = params.caring ?? 0.5; + this.playful = params.playful ?? 0.5; + this.serious = params.serious ?? 0.5; + this.romantic = params.romantic ?? 0.5; + Object.freeze(this); + } + + toJson(): Record { + return { + cheerful: this.cheerful, + caring: this.caring, + playful: this.playful, + serious: this.serious, + romantic: this.romantic, + }; + } + + static fromJson(json: Record): PersonalityTraits { + return new PersonalityTraits({ + cheerful: typeof json.cheerful === "number" ? json.cheerful : undefined, + caring: typeof json.caring === "number" ? json.caring : undefined, + playful: typeof json.playful === "number" ? json.playful : undefined, + serious: typeof json.serious === "number" ? json.serious : undefined, + romantic: typeof json.romantic === "number" ? json.romantic : undefined, + }); + } +} diff --git a/src/data/models/user/recent_memory.ts b/src/data/models/user/recent_memory.ts new file mode 100644 index 00000000..f66ebb7a --- /dev/null +++ b/src/data/models/user/recent_memory.ts @@ -0,0 +1,59 @@ +/** + * 最近记忆模型 + * 原始 Dart: RecentMemory (lib/data/models/user/recent_memory.dart) + * + * 注:原始 Dart 中 `createdAt` 为 `String?`,按"全部非空"约定兜底为空串。 + */ +export class RecentMemory { + readonly type: string; + readonly content: string; + readonly importance: number; + readonly createdAt: string; + + constructor(params: { + type: string; + content: string; + importance: number; + createdAt?: string; + }) { + this.type = params.type; + this.content = params.content; + this.importance = params.importance; + this.createdAt = params.createdAt ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + type: this.type, + content: this.content, + importance: this.importance, + createdAt: this.createdAt, + }; + } + + static fromJson(json: Record): RecentMemory { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `RecentMemory.${key} is required and must be a string` + ); + } + return v; + }; + const importance = json.importance; + if (typeof importance !== "number") { + throw new Error( + "RecentMemory.importance is required and must be a number" + ); + } + return new RecentMemory({ + type: requireString("type"), + content: requireString("content"), + importance, + createdAt: + typeof json.createdAt === "string" ? json.createdAt : undefined, + }); + } +} diff --git a/src/data/models/user/update_profile_request.ts b/src/data/models/user/update_profile_request.ts new file mode 100644 index 00000000..507f5708 --- /dev/null +++ b/src/data/models/user/update_profile_request.ts @@ -0,0 +1,47 @@ +/** + * 更新个人资料请求 + * 原始 Dart: UpdateProfileRequest (lib/data/models/user/user.dart) + * + * 注:原始 Dart 中所有字段为 `String?`,按"全部非空"约定兜底为空串。 + */ +export class UpdateProfileRequest { + readonly username: string; + readonly nickname: string; + readonly preferredLanguage: string; + readonly currentMood: string; + + constructor(params: { + username?: string; + nickname?: string; + preferredLanguage?: string; + currentMood?: string; + }) { + this.username = params.username ?? ""; + this.nickname = params.nickname ?? ""; + this.preferredLanguage = params.preferredLanguage ?? ""; + this.currentMood = params.currentMood ?? ""; + Object.freeze(this); + } + + toJson(): Record { + return { + username: this.username, + nickname: this.nickname, + preferredLanguage: this.preferredLanguage, + currentMood: this.currentMood, + }; + } + + static fromJson(json: Record): UpdateProfileRequest { + return new UpdateProfileRequest({ + username: typeof json.username === "string" ? json.username : undefined, + nickname: typeof json.nickname === "string" ? json.nickname : undefined, + preferredLanguage: + typeof json.preferredLanguage === "string" + ? json.preferredLanguage + : undefined, + currentMood: + typeof json.currentMood === "string" ? json.currentMood : undefined, + }); + } +} diff --git a/src/data/models/user/user.ts b/src/data/models/user/user.ts new file mode 100644 index 00000000..3ba9d3b1 --- /dev/null +++ b/src/data/models/user/user.ts @@ -0,0 +1,128 @@ +/** + * 用户模型 - 对应后端 UserData schema + * 原始 Dart: User (lib/data/models/user/user.dart) + * + * 注:原始 Dart 中 `email`/`personalityTraits`/`preferredLanguage`/`createdAt`/ + * `lastMessageAt`/`avatarUrl` 为可空字段,按"全部非空"约定统一兜底: + * - 字符串字段 → 空串 "" + * - PersonalityTraits → 默认实例(全 0.5) + */ +import { PersonalityTraits } from "./personality_traits"; + +export class User { + readonly id: string; + readonly username: string; + readonly email: string; + readonly platform: string; + readonly intimacy: number; + readonly dolBalance: number; + readonly relationshipStage: string; + readonly currentMood: string; + readonly personalityTraits: PersonalityTraits; + readonly preferredLanguage: string; + readonly createdAt: string; + readonly lastMessageAt: string; + readonly avatarUrl: string; + readonly loginProvider: string; + readonly isGuest: boolean; + + constructor(params: { + id: string; + username: string; + email?: string; + platform?: string; + intimacy?: number; + dolBalance?: number; + relationshipStage?: string; + currentMood?: string; + personalityTraits?: PersonalityTraits; + preferredLanguage?: string; + createdAt?: string; + lastMessageAt?: string; + avatarUrl?: string; + loginProvider?: string; + isGuest?: boolean; + }) { + this.id = params.id; + this.username = params.username; + this.email = params.email ?? ""; + this.platform = params.platform ?? "web"; + this.intimacy = params.intimacy ?? 0; + this.dolBalance = params.dolBalance ?? 0; + this.relationshipStage = params.relationshipStage ?? "密友"; + this.currentMood = params.currentMood ?? "happy"; + this.personalityTraits = params.personalityTraits ?? new PersonalityTraits({}); + this.preferredLanguage = params.preferredLanguage ?? ""; + this.createdAt = params.createdAt ?? ""; + this.lastMessageAt = params.lastMessageAt ?? ""; + this.avatarUrl = params.avatarUrl ?? ""; + this.loginProvider = params.loginProvider ?? "email"; + this.isGuest = params.isGuest ?? false; + Object.freeze(this); + } + + toJson(): Record { + return { + id: this.id, + username: this.username, + email: this.email, + platform: this.platform, + intimacy: this.intimacy, + dolBalance: this.dolBalance, + relationshipStage: this.relationshipStage, + currentMood: this.currentMood, + personalityTraits: this.personalityTraits.toJson(), + preferredLanguage: this.preferredLanguage, + createdAt: this.createdAt, + lastMessageAt: this.lastMessageAt, + avatarUrl: this.avatarUrl, + loginProvider: this.loginProvider, + isGuest: this.isGuest, + }; + } + + static fromJson(json: Record): User { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error(`User.${key} is required and must be a string`); + } + return v; + }; + return new User({ + id: requireString("id"), + username: requireString("username"), + email: typeof json.email === "string" ? json.email : undefined, + platform: typeof json.platform === "string" ? json.platform : undefined, + intimacy: typeof json.intimacy === "number" ? json.intimacy : undefined, + dolBalance: + typeof json.dolBalance === "number" ? json.dolBalance : undefined, + relationshipStage: + typeof json.relationshipStage === "string" + ? json.relationshipStage + : undefined, + currentMood: + typeof json.currentMood === "string" ? json.currentMood : undefined, + personalityTraits: json.personalityTraits + ? PersonalityTraits.fromJson( + json.personalityTraits as Record + ) + : undefined, + preferredLanguage: + typeof json.preferredLanguage === "string" + ? json.preferredLanguage + : undefined, + createdAt: + typeof json.createdAt === "string" ? json.createdAt : undefined, + lastMessageAt: + typeof json.lastMessageAt === "string" ? json.lastMessageAt : undefined, + avatarUrl: + typeof json.avatarUrl === "string" ? json.avatarUrl : undefined, + loginProvider: + typeof json.loginProvider === "string" + ? json.loginProvider + : undefined, + isGuest: typeof json.isGuest === "boolean" ? json.isGuest : undefined, + }); + } +} diff --git a/src/data/models/user/user_stats_response.ts b/src/data/models/user/user_stats_response.ts new file mode 100644 index 00000000..cf50f0c5 --- /dev/null +++ b/src/data/models/user/user_stats_response.ts @@ -0,0 +1,156 @@ +/** + * 用户统计响应 + * 原始 Dart: UserStatsResponse (lib/data/models/user/user_stats_response.dart) + * + * 注:原始 Dart 中 `lastMessageAt`/`accountCreatedAt`/`currentMood` 为 `String?`, + * `personalityTraits` 为 `PersonalityTraits?`,按"全部非空"约定兜底: + * - 字符串 → "" + * - PersonalityTraits → 默认实例 + * - recentMemories 缺失或 null 时使用 [] + */ +import { PersonalityTraits } from "./personality_traits"; +import { RecentMemory } from "./recent_memory"; + +export class UserStatsResponse { + readonly userId: string; + readonly username: string; + readonly platform: string; + readonly intimacy: number; + readonly relationshipStage: string; + readonly dolBalance: number; + readonly totalMessages: number; + readonly lastMessageAt: string; + readonly accountCreatedAt: string; + readonly currentMood: string; + readonly personalityTraits: PersonalityTraits; + readonly recentMemories: RecentMemory[]; + readonly promptTokensTotal: number; + readonly completionTokensTotal: number; + readonly cacheHitTokensTotal: number; + readonly tokensTotal: number; + + constructor(params: { + userId: string; + username: string; + platform: string; + intimacy: number; + relationshipStage: string; + dolBalance: number; + totalMessages: number; + lastMessageAt?: string; + accountCreatedAt?: string; + currentMood?: string; + personalityTraits?: PersonalityTraits; + recentMemories?: RecentMemory[]; + promptTokensTotal?: number; + completionTokensTotal?: number; + cacheHitTokensTotal?: number; + tokensTotal?: number; + }) { + this.userId = params.userId; + this.username = params.username; + this.platform = params.platform; + this.intimacy = params.intimacy; + this.relationshipStage = params.relationshipStage; + this.dolBalance = params.dolBalance; + this.totalMessages = params.totalMessages; + this.lastMessageAt = params.lastMessageAt ?? ""; + this.accountCreatedAt = params.accountCreatedAt ?? ""; + this.currentMood = params.currentMood ?? ""; + this.personalityTraits = + params.personalityTraits ?? new PersonalityTraits({}); + this.recentMemories = params.recentMemories ?? []; + this.promptTokensTotal = params.promptTokensTotal ?? 0; + this.completionTokensTotal = params.completionTokensTotal ?? 0; + this.cacheHitTokensTotal = params.cacheHitTokensTotal ?? 0; + this.tokensTotal = params.tokensTotal ?? 0; + Object.freeze(this); + } + + toJson(): Record { + return { + userId: this.userId, + username: this.username, + platform: this.platform, + intimacy: this.intimacy, + relationshipStage: this.relationshipStage, + dolBalance: this.dolBalance, + totalMessages: this.totalMessages, + lastMessageAt: this.lastMessageAt, + accountCreatedAt: this.accountCreatedAt, + currentMood: this.currentMood, + personalityTraits: this.personalityTraits.toJson(), + recentMemories: this.recentMemories.map((m) => m.toJson()), + promptTokensTotal: this.promptTokensTotal, + completionTokensTotal: this.completionTokensTotal, + cacheHitTokensTotal: this.cacheHitTokensTotal, + tokensTotal: this.tokensTotal, + }; + } + + static fromJson(json: Record): UserStatsResponse { + const requireString = (key: string): string => { + const v = json[key]; + if (typeof v !== "string") { + throw new Error( + `UserStatsResponse.${key} is required and must be a string` + ); + } + return v; + }; + const requireNumber = (key: string): number => { + const v = json[key]; + if (typeof v !== "number") { + throw new Error( + `UserStatsResponse.${key} is required and must be a number` + ); + } + return v; + }; + const rawMemories = json.recentMemories; + const recentMemories: RecentMemory[] = Array.isArray(rawMemories) + ? (rawMemories as Record[]).map((m) => + RecentMemory.fromJson(m) + ) + : []; + return new UserStatsResponse({ + userId: requireString("userId"), + username: requireString("username"), + platform: requireString("platform"), + intimacy: requireNumber("intimacy"), + relationshipStage: requireString("relationshipStage"), + dolBalance: requireNumber("dolBalance"), + totalMessages: requireNumber("totalMessages"), + lastMessageAt: + typeof json.lastMessageAt === "string" + ? json.lastMessageAt + : undefined, + accountCreatedAt: + typeof json.accountCreatedAt === "string" + ? json.accountCreatedAt + : undefined, + currentMood: + typeof json.currentMood === "string" ? json.currentMood : undefined, + personalityTraits: json.personalityTraits + ? PersonalityTraits.fromJson( + json.personalityTraits as Record + ) + : undefined, + recentMemories, + promptTokensTotal: + typeof json.promptTokensTotal === "number" + ? json.promptTokensTotal + : undefined, + completionTokensTotal: + typeof json.completionTokensTotal === "number" + ? json.completionTokensTotal + : undefined, + cacheHitTokensTotal: + typeof json.cacheHitTokensTotal === "number" + ? json.cacheHitTokensTotal + : undefined, + tokensTotal: + typeof json.tokensTotal === "number" ? json.tokensTotal : undefined, + }); + } +}