@allbluecn/web-app 0.23.1 → 0.23.3

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 (60) hide show
  1. package/package.json +3 -3
  2. package/src/components/desktop/__tests__/desktop-state-listener.test.tsx +183 -0
  3. package/src/components/desktop/__tests__/sync-indicator.test.tsx +130 -0
  4. package/src/components/desktop/cloud-login.tsx +108 -6
  5. package/src/components/desktop/desktop-state-listener.tsx +76 -0
  6. package/src/components/desktop/shell-widgets.tsx +12 -5
  7. package/src/components/desktop/sync-indicator.tsx +62 -13
  8. package/src/components/layout/sidebar-03/__tests__/user-menu-anonymous.test.tsx +4 -0
  9. package/src/components/layout/sidebar-03/user-menu.tsx +63 -0
  10. package/src/lib/__tests__/session.test.ts +95 -0
  11. package/src/lib/auth/auth.config.ts +13 -0
  12. package/src/lib/changelog/sdk-versions.ts +1 -1
  13. package/src/lib/desktop-bridge-types.ts +36 -0
  14. package/src/lib/session.ts +15 -1
  15. package/src/paraglide/messages/_index.d.ts +11 -0
  16. package/src/paraglide/messages/_index.js +11 -0
  17. package/src/paraglide/messages/desktop_cloud_exchange_failed.d.ts +16 -0
  18. package/src/paraglide/messages/desktop_cloud_exchange_failed.js +29 -0
  19. package/src/paraglide/messages/desktop_state_cloud.d.ts +16 -0
  20. package/src/paraglide/messages/desktop_state_cloud.js +29 -0
  21. package/src/paraglide/messages/desktop_state_guest.d.ts +16 -0
  22. package/src/paraglide/messages/desktop_state_guest.js +29 -0
  23. package/src/paraglide/messages/desktop_state_transferring.d.ts +16 -0
  24. package/src/paraglide/messages/desktop_state_transferring.js +29 -0
  25. package/src/paraglide/messages/sync_bind_failed.d.ts +16 -0
  26. package/src/paraglide/messages/sync_bind_failed.js +29 -0
  27. package/src/paraglide/messages/sync_bind_in_progress.d.ts +16 -0
  28. package/src/paraglide/messages/sync_bind_in_progress.js +29 -0
  29. package/src/paraglide/messages/sync_bind_nonempty.d.ts +16 -0
  30. package/src/paraglide/messages/sync_bind_nonempty.js +29 -0
  31. package/src/paraglide/messages/sync_bind_probe_unreachable.d.ts +16 -0
  32. package/src/paraglide/messages/sync_bind_probe_unreachable.js +29 -0
  33. package/src/paraglide/messages/sync_bind_shell_unsupported.d.ts +16 -0
  34. package/src/paraglide/messages/sync_bind_shell_unsupported.js +29 -0
  35. package/src/paraglide/messages/sync_logout_failed.d.ts +16 -0
  36. package/src/paraglide/messages/sync_logout_failed.js +29 -0
  37. package/src/paraglide/messages/sync_logout_in_progress.d.ts +16 -0
  38. package/src/paraglide/messages/sync_logout_in_progress.js +29 -0
  39. package/src/routeTree.gen.ts +21 -0
  40. package/src/routes/api/sync/__tests__/account-status.test.ts +109 -0
  41. package/src/routes/api/sync/account-status.ts +39 -0
  42. package/src/server/__tests__/exchange-remote-token.test.ts +113 -0
  43. package/src/server/__tests__/remote-auth.test.ts +131 -1
  44. package/src/server/ai-gateway/__tests__/tools-authorization.test.ts +4 -2
  45. package/src/server/ai-gateway/tools/server.ts +7 -0
  46. package/src/server/desktop-bootstrap.ts +19 -3
  47. package/src/server/desktop-session-merge.ts +51 -0
  48. package/src/server/remote-auth.ts +61 -3
  49. package/src/server/serverFns/__tests__/knowledge-readable-kb.test.ts +21 -0
  50. package/src/server/serverFns/__tests__/workspace.test.ts +46 -0
  51. package/src/server/serverFns/auth/session.ts +28 -28
  52. package/src/server/serverFns/harness/attach-to-kb.ts +3 -1
  53. package/src/server/serverFns/knowledge.ts +4 -1
  54. package/src/server/serverFns/sync/transfer.ts +168 -0
  55. package/src/server/serverFns/workspace.ts +15 -0
  56. package/src/server/serverfn-public-allowlist.ts +4 -0
  57. package/src/server/sync/__tests__/ownership-transfer.test.ts +896 -0
  58. package/src/server/sync/account-status.ts +36 -0
  59. package/src/server/sync/ownership-transfer.ts +291 -0
  60. package/src/server/sync/saas-profile.ts +70 -0
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Desktop_State_TransferringInputs */
7
+
8
+ const zh_desktop_state_transferring = /** @type {(inputs: Desktop_State_TransferringInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`归属转移中…`)
10
+ };
11
+
12
+ const en_desktop_state_transferring = /** @type {(inputs: Desktop_State_TransferringInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Transferring data…`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Transferring data…" |
20
+ *
21
+ * @param {Desktop_State_TransferringInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const desktop_state_transferring = /** @type {((inputs?: Desktop_State_TransferringInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Desktop_State_TransferringInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_desktop_state_transferring(inputs)
28
+ return zh_desktop_state_transferring(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "Local data transfer failed. Local guest state restored and cloud sign-out applied. Please retry." |
5
+ *
6
+ * @param {Sync_Bind_FailedInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_bind_failed: ((inputs?: Sync_Bind_FailedInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Bind_FailedInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Bind_FailedInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Bind_FailedInputs */
7
+
8
+ const zh_sync_bind_failed = /** @type {(inputs: Sync_Bind_FailedInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`本地数据转移失败,已恢复本地 guest 状态并退出云登录,请重试`)
10
+ };
11
+
12
+ const en_sync_bind_failed = /** @type {(inputs: Sync_Bind_FailedInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Local data transfer failed. Local guest state restored and cloud sign-out applied. Please retry.`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Local data transfer failed. Local guest state restored and cloud sign-out applied. Please retry." |
20
+ *
21
+ * @param {Sync_Bind_FailedInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_bind_failed = /** @type {((inputs?: Sync_Bind_FailedInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Bind_FailedInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_bind_failed(inputs)
28
+ return zh_sync_bind_failed(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "Transferring local data to your cloud account…" |
5
+ *
6
+ * @param {Sync_Bind_In_ProgressInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_bind_in_progress: ((inputs?: Sync_Bind_In_ProgressInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Bind_In_ProgressInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Bind_In_ProgressInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Bind_In_ProgressInputs */
7
+
8
+ const zh_sync_bind_in_progress = /** @type {(inputs: Sync_Bind_In_ProgressInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`正在转移本地数据到云端账号…`)
10
+ };
11
+
12
+ const en_sync_bind_in_progress = /** @type {(inputs: Sync_Bind_In_ProgressInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Transferring local data to your cloud account…`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Transferring local data to your cloud account…" |
20
+ *
21
+ * @param {Sync_Bind_In_ProgressInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_bind_in_progress = /** @type {((inputs?: Sync_Bind_In_ProgressInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Bind_In_ProgressInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_bind_in_progress(inputs)
28
+ return zh_sync_bind_in_progress(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "This cloud account already has data. Binding transfer is not supported yet (empty accounts only in phase one)." |
5
+ *
6
+ * @param {Sync_Bind_NonemptyInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_bind_nonempty: ((inputs?: Sync_Bind_NonemptyInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Bind_NonemptyInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Bind_NonemptyInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Bind_NonemptyInputs */
7
+
8
+ const zh_sync_bind_nonempty = /** @type {(inputs: Sync_Bind_NonemptyInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`该云账号已有数据,暂不支持绑定转移(一期仅支持空账号)`)
10
+ };
11
+
12
+ const en_sync_bind_nonempty = /** @type {(inputs: Sync_Bind_NonemptyInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`This cloud account already has data. Binding transfer is not supported yet (empty accounts only in phase one).`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "This cloud account already has data. Binding transfer is not supported yet (empty accounts only in phase one)." |
20
+ *
21
+ * @param {Sync_Bind_NonemptyInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_bind_nonempty = /** @type {((inputs?: Sync_Bind_NonemptyInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Bind_NonemptyInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_bind_nonempty(inputs)
28
+ return zh_sync_bind_nonempty(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "Could not verify the cloud account status (network error). Local data was not transferred this time." |
5
+ *
6
+ * @param {Sync_Bind_Probe_UnreachableInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_bind_probe_unreachable: ((inputs?: Sync_Bind_Probe_UnreachableInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Bind_Probe_UnreachableInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Bind_Probe_UnreachableInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Bind_Probe_UnreachableInputs */
7
+
8
+ const zh_sync_bind_probe_unreachable = /** @type {(inputs: Sync_Bind_Probe_UnreachableInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`暂时无法确认云账号状态(网络异常),本次未转移本地数据`)
10
+ };
11
+
12
+ const en_sync_bind_probe_unreachable = /** @type {(inputs: Sync_Bind_Probe_UnreachableInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Could not verify the cloud account status (network error). Local data was not transferred this time.`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Could not verify the cloud account status (network error). Local data was not transferred this time." |
20
+ *
21
+ * @param {Sync_Bind_Probe_UnreachableInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_bind_probe_unreachable = /** @type {((inputs?: Sync_Bind_Probe_UnreachableInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Bind_Probe_UnreachableInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_bind_probe_unreachable(inputs)
28
+ return zh_sync_bind_probe_unreachable(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "This desktop shell version does not support binding transfer. Please upgrade the app." |
5
+ *
6
+ * @param {Sync_Bind_Shell_UnsupportedInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_bind_shell_unsupported: ((inputs?: Sync_Bind_Shell_UnsupportedInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Bind_Shell_UnsupportedInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Bind_Shell_UnsupportedInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Bind_Shell_UnsupportedInputs */
7
+
8
+ const zh_sync_bind_shell_unsupported = /** @type {(inputs: Sync_Bind_Shell_UnsupportedInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`当前桌面壳版本不支持绑定转移,请升级桌面应用`)
10
+ };
11
+
12
+ const en_sync_bind_shell_unsupported = /** @type {(inputs: Sync_Bind_Shell_UnsupportedInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`This desktop shell version does not support binding transfer. Please upgrade the app.`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "This desktop shell version does not support binding transfer. Please upgrade the app." |
20
+ *
21
+ * @param {Sync_Bind_Shell_UnsupportedInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_bind_shell_unsupported = /** @type {((inputs?: Sync_Bind_Shell_UnsupportedInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Bind_Shell_UnsupportedInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_bind_shell_unsupported(inputs)
28
+ return zh_sync_bind_shell_unsupported(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "Sign-out failed: cloud data transfer back to this device did not complete. Local data is untouched. Please retry." |
5
+ *
6
+ * @param {Sync_Logout_FailedInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_logout_failed: ((inputs?: Sync_Logout_FailedInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Logout_FailedInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Logout_FailedInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Logout_FailedInputs */
7
+
8
+ const zh_sync_logout_failed = /** @type {(inputs: Sync_Logout_FailedInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`登出失败:云端数据转回本机未完成,本机数据未受影响,请重试`)
10
+ };
11
+
12
+ const en_sync_logout_failed = /** @type {(inputs: Sync_Logout_FailedInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Sign-out failed: cloud data transfer back to this device did not complete. Local data is untouched. Please retry.`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Sign-out failed: cloud data transfer back to this device did not complete. Local data is untouched. Please retry." |
20
+ *
21
+ * @param {Sync_Logout_FailedInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_logout_failed = /** @type {((inputs?: Sync_Logout_FailedInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Logout_FailedInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_logout_failed(inputs)
28
+ return zh_sync_logout_failed(inputs)
29
+ });
@@ -0,0 +1,16 @@
1
+ /**
2
+ * | output |
3
+ * | --- |
4
+ * | "Transferring cloud data back to this device…" |
5
+ *
6
+ * @param {Sync_Logout_In_ProgressInputs} inputs
7
+ * @param {{ locale?: "zh" | "en" }} options
8
+ * @returns {LocalizedString}
9
+ */
10
+ export const sync_logout_in_progress: ((inputs?: Sync_Logout_In_ProgressInputs, options?: {
11
+ locale?: "zh" | "en";
12
+ }) => LocalizedString) & import("../runtime.js").MessageMetadata<Sync_Logout_In_ProgressInputs, {
13
+ locale?: "zh" | "en";
14
+ }, {}>;
15
+ export type LocalizedString = import("../runtime.js").LocalizedString;
16
+ export type Sync_Logout_In_ProgressInputs = {};
@@ -0,0 +1,29 @@
1
+ /* eslint-disable */
2
+ import { getLocale, experimentalStaticLocale } from '../runtime.js';
3
+
4
+ /** @typedef {import('../runtime.js').LocalizedString} LocalizedString */
5
+
6
+ /** @typedef {{}} Sync_Logout_In_ProgressInputs */
7
+
8
+ const zh_sync_logout_in_progress = /** @type {(inputs: Sync_Logout_In_ProgressInputs) => LocalizedString} */ () => {
9
+ return /** @type {LocalizedString} */ (`正在把云端数据转回本机…`)
10
+ };
11
+
12
+ const en_sync_logout_in_progress = /** @type {(inputs: Sync_Logout_In_ProgressInputs) => LocalizedString} */ () => {
13
+ return /** @type {LocalizedString} */ (`Transferring cloud data back to this device…`)
14
+ };
15
+
16
+ /**
17
+ * | output |
18
+ * | --- |
19
+ * | "Transferring cloud data back to this device…" |
20
+ *
21
+ * @param {Sync_Logout_In_ProgressInputs} inputs
22
+ * @param {{ locale?: "zh" | "en" }} options
23
+ * @returns {LocalizedString}
24
+ */
25
+ export const sync_logout_in_progress = /** @type {((inputs?: Sync_Logout_In_ProgressInputs, options?: { locale?: "zh" | "en" }) => LocalizedString) & import('../runtime.js').MessageMetadata<Sync_Logout_In_ProgressInputs, { locale?: "zh" | "en" }, {}>} */ ((inputs = {}, options = {}) => {
26
+ const locale = experimentalStaticLocale ?? options.locale ?? getLocale()
27
+ if (locale === "en") return en_sync_logout_in_progress(inputs)
28
+ return zh_sync_logout_in_progress(inputs)
29
+ });
@@ -105,6 +105,7 @@ import { Route as ApiLicenseActivateRouteRouteImport } from './routes/api/licens
105
105
  import { Route as ApiLicenseHeartbeatRouteRouteImport } from './routes/api/license/heartbeat/route'
106
106
  import { Route as ApiMcpBugsRouteImport } from './routes/api/mcp/bugs'
107
107
  import { Route as ApiNotificationsStreamRouteImport } from './routes/api/notifications/stream'
108
+ import { Route as ApiSyncAccountStatusRouteImport } from './routes/api/sync/account-status'
108
109
  import { Route as ApiSyncBootstrapRouteImport } from './routes/api/sync/bootstrap'
109
110
  import { Route as ApiSyncPullRouteImport } from './routes/api/sync/pull'
110
111
  import { Route as ApiSyncPushRouteImport } from './routes/api/sync/push'
@@ -751,6 +752,11 @@ const ApiNotificationsStreamRoute = ApiNotificationsStreamRouteImport.update({
751
752
  path: '/api/notifications/stream',
752
753
  getParentRoute: () => rootRouteImport,
753
754
  } as any)
755
+ const ApiSyncAccountStatusRoute = ApiSyncAccountStatusRouteImport.update({
756
+ id: '/api/sync/account-status',
757
+ path: '/api/sync/account-status',
758
+ getParentRoute: () => rootRouteImport,
759
+ } as any)
754
760
  const ApiSyncBootstrapRoute = ApiSyncBootstrapRouteImport.update({
755
761
  id: '/api/sync/bootstrap',
756
762
  path: '/api/sync/bootstrap',
@@ -1194,6 +1200,7 @@ export interface FileRoutesByFullPath {
1194
1200
  '/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
1195
1201
  '/api/mcp/bugs': typeof ApiMcpBugsRoute
1196
1202
  '/api/notifications/stream': typeof ApiNotificationsStreamRoute
1203
+ '/api/sync/account-status': typeof ApiSyncAccountStatusRoute
1197
1204
  '/api/sync/bootstrap': typeof ApiSyncBootstrapRoute
1198
1205
  '/api/sync/pull': typeof ApiSyncPullRoute
1199
1206
  '/api/sync/push': typeof ApiSyncPushRoute
@@ -1317,6 +1324,7 @@ export interface FileRoutesByTo {
1317
1324
  '/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
1318
1325
  '/api/mcp/bugs': typeof ApiMcpBugsRoute
1319
1326
  '/api/notifications/stream': typeof ApiNotificationsStreamRoute
1327
+ '/api/sync/account-status': typeof ApiSyncAccountStatusRoute
1320
1328
  '/api/sync/bootstrap': typeof ApiSyncBootstrapRoute
1321
1329
  '/api/sync/pull': typeof ApiSyncPullRoute
1322
1330
  '/api/sync/push': typeof ApiSyncPushRoute
@@ -1459,6 +1467,7 @@ export interface FileRoutesById {
1459
1467
  '/api/knowledge/sync-github': typeof ApiKnowledgeSyncGithubRoute
1460
1468
  '/api/mcp/bugs': typeof ApiMcpBugsRoute
1461
1469
  '/api/notifications/stream': typeof ApiNotificationsStreamRoute
1470
+ '/api/sync/account-status': typeof ApiSyncAccountStatusRoute
1462
1471
  '/api/sync/bootstrap': typeof ApiSyncBootstrapRoute
1463
1472
  '/api/sync/pull': typeof ApiSyncPullRoute
1464
1473
  '/api/sync/push': typeof ApiSyncPushRoute
@@ -1599,6 +1608,7 @@ export interface FileRouteTypes {
1599
1608
  | '/api/knowledge/sync-github'
1600
1609
  | '/api/mcp/bugs'
1601
1610
  | '/api/notifications/stream'
1611
+ | '/api/sync/account-status'
1602
1612
  | '/api/sync/bootstrap'
1603
1613
  | '/api/sync/pull'
1604
1614
  | '/api/sync/push'
@@ -1722,6 +1732,7 @@ export interface FileRouteTypes {
1722
1732
  | '/api/knowledge/sync-github'
1723
1733
  | '/api/mcp/bugs'
1724
1734
  | '/api/notifications/stream'
1735
+ | '/api/sync/account-status'
1725
1736
  | '/api/sync/bootstrap'
1726
1737
  | '/api/sync/pull'
1727
1738
  | '/api/sync/push'
@@ -1863,6 +1874,7 @@ export interface FileRouteTypes {
1863
1874
  | '/api/knowledge/sync-github'
1864
1875
  | '/api/mcp/bugs'
1865
1876
  | '/api/notifications/stream'
1877
+ | '/api/sync/account-status'
1866
1878
  | '/api/sync/bootstrap'
1867
1879
  | '/api/sync/pull'
1868
1880
  | '/api/sync/push'
@@ -1949,6 +1961,7 @@ export interface RootRouteChildren {
1949
1961
  ApiKnowledgeSyncGithubRoute: typeof ApiKnowledgeSyncGithubRoute
1950
1962
  ApiMcpBugsRoute: typeof ApiMcpBugsRoute
1951
1963
  ApiNotificationsStreamRoute: typeof ApiNotificationsStreamRoute
1964
+ ApiSyncAccountStatusRoute: typeof ApiSyncAccountStatusRoute
1952
1965
  ApiSyncBootstrapRoute: typeof ApiSyncBootstrapRoute
1953
1966
  ApiSyncPullRoute: typeof ApiSyncPullRoute
1954
1967
  ApiSyncPushRoute: typeof ApiSyncPushRoute
@@ -2620,6 +2633,13 @@ declare module '@tanstack/react-router' {
2620
2633
  preLoaderRoute: typeof ApiNotificationsStreamRouteImport
2621
2634
  parentRoute: typeof rootRouteImport
2622
2635
  }
2636
+ '/api/sync/account-status': {
2637
+ id: '/api/sync/account-status'
2638
+ path: '/api/sync/account-status'
2639
+ fullPath: '/api/sync/account-status'
2640
+ preLoaderRoute: typeof ApiSyncAccountStatusRouteImport
2641
+ parentRoute: typeof rootRouteImport
2642
+ }
2623
2643
  '/api/sync/bootstrap': {
2624
2644
  id: '/api/sync/bootstrap'
2625
2645
  path: '/api/sync/bootstrap'
@@ -3445,6 +3465,7 @@ const rootRouteChildren: RootRouteChildren = {
3445
3465
  ApiKnowledgeSyncGithubRoute: ApiKnowledgeSyncGithubRoute,
3446
3466
  ApiMcpBugsRoute: ApiMcpBugsRoute,
3447
3467
  ApiNotificationsStreamRoute: ApiNotificationsStreamRoute,
3468
+ ApiSyncAccountStatusRoute: ApiSyncAccountStatusRoute,
3448
3469
  ApiSyncBootstrapRoute: ApiSyncBootstrapRoute,
3449
3470
  ApiSyncPullRoute: ApiSyncPullRoute,
3450
3471
  ApiSyncPushRoute: ApiSyncPushRoute,
@@ -0,0 +1,109 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // account-status 端点(归属转移前置探测,桌面本地优先 P2-A5)回归哨兵:
6
+ // - 空账号口径 = 当前用户六表 count 全 0(复审⑥ P1-9 扩口径:知识库两表计入,
7
+ // 防仅有知识库数据的云账号被误判空走归属转移);
8
+ // - 属主字段钉死:story/bug/testCase → createdById;project → ownerId;
9
+ // knowledgeBase → userId;knowledgeDocument → createdBy(以 schema 为准,
10
+ // 改错字段 = 探测失真,测试即报);
11
+ // - 未登录 401 且零查库。
12
+ import { beforeEach, describe, expect, it, vi } from 'vitest'
13
+
14
+ const mocks = vi.hoisted(() => {
15
+ const count = () => vi.fn()
16
+ return {
17
+ prisma: {
18
+ story: { count: count() },
19
+ bug: { count: count() },
20
+ testCase: { count: count() },
21
+ project: { count: count() },
22
+ knowledgeBase: { count: count() },
23
+ knowledgeDocument: { count: count() },
24
+ },
25
+ requireAuth: vi.fn(),
26
+ }
27
+ })
28
+
29
+ vi.mock('@allbluecn/database/get-prisma', () => ({
30
+ getPrisma: async () => mocks.prisma,
31
+ }))
32
+ vi.mock('@/server/auth-helpers', () => ({
33
+ requireAuth: mocks.requireAuth,
34
+ UnauthorizedError: class UnauthorizedError extends Error {
35
+ constructor(message = 'UNAUTHORIZED') {
36
+ super(message)
37
+ this.name = 'UnauthorizedError'
38
+ }
39
+ },
40
+ }))
41
+
42
+ import { Route } from '../account-status'
43
+ import { UnauthorizedError } from '@/server/auth-helpers'
44
+
45
+ type Handler = (ctx: { request: Request }) => Promise<Response>
46
+ const GET = (Route.options.server as { handlers?: { GET?: Handler } } | undefined)?.handlers?.GET
47
+ if (!GET) throw new Error('GET handler missing')
48
+
49
+ const call = () =>
50
+ GET({ request: new Request('https://allblue.test/api/sync/account-status') })
51
+
52
+ /** 六表 count 桩:按 { 表名: 值 } 设定,未提及的表默认 0 */
53
+ const seedCounts = (values: Partial<Record<keyof typeof mocks.prisma, number>>) => {
54
+ for (const [table, model] of Object.entries(mocks.prisma)) {
55
+ model.count.mockReset()
56
+ model.count.mockResolvedValue(values[table as keyof typeof values] ?? 0)
57
+ }
58
+ }
59
+
60
+ beforeEach(() => {
61
+ vi.clearAllMocks()
62
+ mocks.requireAuth.mockResolvedValue({ user: { id: 'u1' } })
63
+ seedCounts({})
64
+ })
65
+
66
+ describe('/api/sync/account-status', () => {
67
+ it('空账号:六表 count 全 0 → { empty: true },各表按属主字段过滤', async () => {
68
+ const res = await call()
69
+
70
+ expect(res.status).toBe(200)
71
+ expect(await res.json()).toEqual({ empty: true })
72
+
73
+ // 属主字段口径钉死(改字段 = 探测失真,此处即报)
74
+ expect(mocks.prisma.story.count).toHaveBeenCalledWith({ where: { createdById: 'u1' } })
75
+ expect(mocks.prisma.bug.count).toHaveBeenCalledWith({ where: { createdById: 'u1' } })
76
+ expect(mocks.prisma.testCase.count).toHaveBeenCalledWith({ where: { createdById: 'u1' } })
77
+ expect(mocks.prisma.project.count).toHaveBeenCalledWith({ where: { ownerId: 'u1' } })
78
+ expect(mocks.prisma.knowledgeBase.count).toHaveBeenCalledWith({ where: { userId: 'u1' } })
79
+ expect(mocks.prisma.knowledgeDocument.count).toHaveBeenCalledWith({ where: { createdBy: 'u1' } })
80
+ })
81
+
82
+ it('任一表有数据(单表 >0 其余 0)→ { empty: false }(六表逐一参数化)', async () => {
83
+ for (const table of Object.keys(mocks.prisma) as Array<keyof typeof mocks.prisma>) {
84
+ seedCounts({ [table]: 2 })
85
+ const res = await call()
86
+ expect(res.status).toBe(200)
87
+ expect(await res.json()).toEqual({ empty: false })
88
+ }
89
+ })
90
+
91
+ it('仅知识库有数据(复审⑥ P1-9 扩口径):kb + 文档 >0、业务四表 0 → { empty: false }', async () => {
92
+ seedCounts({ knowledgeBase: 1, knowledgeDocument: 5 })
93
+
94
+ const res = await call()
95
+
96
+ expect(res.status).toBe(200)
97
+ expect(await res.json()).toEqual({ empty: false })
98
+ })
99
+
100
+ it('未登录 → 401,且零查库', async () => {
101
+ mocks.requireAuth.mockRejectedValue(new UnauthorizedError())
102
+
103
+ const res = await call()
104
+
105
+ expect(res.status).toBe(401)
106
+ expect(mocks.prisma.story.count).not.toHaveBeenCalled()
107
+ expect(mocks.prisma.knowledgeDocument.count).not.toHaveBeenCalled()
108
+ })
109
+ })
@@ -0,0 +1,39 @@
1
+ // SPDX-License-Identifier: AGPL-3.0-or-later
2
+ // AllBlue - 理想之海
3
+ // Copyright (C) 2026 AllBlue Contributors
4
+
5
+ // account-status 端点(桌面本地优先 P2-A5,迁移设计归属转移前置核对):桌面端绑定
6
+ // 云账号前探测目标账号是否「空账号」——仅空账号才走归属转移(一期唯一路径)。
7
+ // 六表 count 口径与属主字段实查收敛在 server/sync/account-status.ts 的
8
+ // isAccountEmpty(A8a 抽取,transferOwnershipFn 复核复用同源),此处只做
9
+ // requireAuth + 响应映射。
10
+ import { createFileRoute } from "@tanstack/react-router"
11
+ import { getPrisma } from "@allbluecn/database/get-prisma"
12
+ import { requireAuth, UnauthorizedError } from "@/server/auth-helpers"
13
+ import { isAccountEmpty } from "@/server/sync/account-status"
14
+
15
+ export const Route = createFileRoute("/api/sync/account-status")({
16
+ server: {
17
+ handlers: {
18
+ GET: async ({ request }) => {
19
+ try {
20
+ const session = await requireAuth(request)
21
+ const userId = session.user.id
22
+
23
+ const prisma = await getPrisma()
24
+ const empty = await isAccountEmpty(prisma, userId)
25
+
26
+ return Response.json({ empty })
27
+ } catch (e) {
28
+ // server.handlers 路由不走 serverFn middleware 链:401 映射在 handler 内
29
+ // (与 attachments/content 同款;message 兜底防 requireAuth 抛裸 UNAUTHORIZED)
30
+ if (e instanceof UnauthorizedError || (e instanceof Error && e.message === "UNAUTHORIZED")) {
31
+ return new Response("Unauthorized", { status: 401 })
32
+ }
33
+ console.error("[sync/account-status] 探测失败", e)
34
+ return new Response("Internal Server Error", { status: 500 })
35
+ }
36
+ },
37
+ },
38
+ },
39
+ })