diff --git a/desktop/src/main/data/database-manager.ts b/desktop/src/main/data/database-manager.ts index 1d941bd3..7732d65e 100644 --- a/desktop/src/main/data/database-manager.ts +++ b/desktop/src/main/data/database-manager.ts @@ -65,6 +65,34 @@ class DatabaseManager { return workspaceDatabase; } + public async deleteAccountData(accountId: string): Promise { + await this.waitForInit(); + + const workspaces = await this.appDatabase + .selectFrom('workspaces') + .selectAll() + .where('account_id', '=', accountId) + .execute(); + + for (const workspace of workspaces) { + await this.deleteWorkspaceDatabase( + accountId, + workspace.workspace_id, + workspace.user_id, + ); + } + + await this.appDatabase + .deleteFrom('workspaces') + .where('account_id', '=', accountId) + .execute(); + + const accountDir = path.join(this.appPath, accountId); + if (fs.existsSync(accountDir)) { + fs.rmSync(accountDir, { recursive: true, force: true }); + } + } + public async deleteWorkspaceDatabase( accountId: string, workspaceId: string, diff --git a/desktop/src/main/handlers/mutations/index.ts b/desktop/src/main/handlers/mutations/index.ts index 0c715062..4e9150ec 100644 --- a/desktop/src/main/handlers/mutations/index.ts +++ b/desktop/src/main/handlers/mutations/index.ts @@ -36,6 +36,7 @@ import { NodeCollaboratorServerUpdateMutationHandler } from '@/main/handlers/mut import { NodeCollaboratorServerDeleteMutationHandler } from '@/main/handlers/mutations/node-collaborator-server-delete'; import { NodeReactionServerCreateMutationHandler } from '@/main/handlers/mutations/node-reaction-server-create'; import { NodeReactionServerDeleteMutationHandler } from '@/main/handlers/mutations/node-reaction-server-delete'; +import { LogoutMutationHandler } from '@/main/handlers/mutations/logout'; type MutationHandlerMap = { [K in keyof MutationMap]: MutationHandler; @@ -83,4 +84,5 @@ export const mutationHandlerMap: MutationHandlerMap = { new NodeCollaboratorServerDeleteMutationHandler(), node_reaction_server_create: new NodeReactionServerCreateMutationHandler(), node_reaction_server_delete: new NodeReactionServerDeleteMutationHandler(), + logout: new LogoutMutationHandler(), }; diff --git a/desktop/src/main/handlers/mutations/logout.ts b/desktop/src/main/handlers/mutations/logout.ts new file mode 100644 index 00000000..30601608 --- /dev/null +++ b/desktop/src/main/handlers/mutations/logout.ts @@ -0,0 +1,47 @@ +import { databaseManager } from '@/main/data/database-manager'; +import { LogoutMutationInput } from '@/operations/mutations/logout'; +import { MutationHandler, MutationResult } from '@/operations/mutations'; + +export class LogoutMutationHandler + implements MutationHandler +{ + async handleMutation( + input: LogoutMutationInput, + ): Promise> { + const account = await databaseManager.appDatabase + .selectFrom('accounts') + .selectAll() + .where('id', '=', input.accountId) + .executeTakeFirst(); + + if (!account) { + return { + output: { + success: false, + }, + }; + } + + await databaseManager.appDatabase + .updateTable('accounts') + .set({ + status: 'logged_out', + }) + .where('id', '=', input.accountId) + .execute(); + + await databaseManager.deleteAccountData(input.accountId); + + return { + output: { + success: true, + }, + changes: [ + { + type: 'app', + table: 'accounts', + }, + ], + }; + } +} diff --git a/desktop/src/main/synchronizer.ts b/desktop/src/main/synchronizer.ts index 6cd1adec..b8411955 100644 --- a/desktop/src/main/synchronizer.ts +++ b/desktop/src/main/synchronizer.ts @@ -12,8 +12,7 @@ const EVENT_LOOP_INTERVAL = 100; class Synchronizer { private initiated: boolean = false; - private readonly workspaceBackoffs: Map = - new Map(); + private readonly backoffs: Map = new Map(); constructor() { this.executeEventLoop = this.executeEventLoop.bind(this); @@ -30,6 +29,7 @@ class Synchronizer { private async executeEventLoop() { try { + await this.checkForLoggedOutAccount(); await this.checkForWorkspaceChanges(); } catch (error) { console.log('error', error); @@ -175,6 +175,65 @@ class Synchronizer { } } + private async checkForLoggedOutAccount() { + const accounts = await databaseManager.appDatabase + .selectFrom('accounts') + .innerJoin('servers', 'accounts.server', 'servers.domain') + .select([ + 'accounts.id', + 'accounts.token', + 'servers.domain', + 'servers.attributes', + ]) + .where('status', '=', 'logged_out') + .execute(); + + if (accounts.length === 0) { + return; + } + + for (const account of accounts) { + const backoffKey = account.id; + if (this.backoffs.has(backoffKey)) { + const backoff = this.backoffs.get(backoffKey); + if (!backoff.canRetry()) { + return; + } + } + + try { + const axios = buildAxiosInstance( + account.domain, + account.attributes, + account.token, + ); + + const { status } = await axios.delete(`/v1/accounts/logout`); + + if (status !== 200) { + return; + } + + await databaseManager.deleteAccountData(account.id); + await databaseManager.appDatabase + .deleteFrom('accounts') + .where('id', '=', account.id) + .execute(); + + if (this.backoffs.has(backoffKey)) { + this.backoffs.delete(backoffKey); + } + } catch (error) { + if (!this.backoffs.has(backoffKey)) { + this.backoffs.set(backoffKey, new BackoffCalculator()); + } + + const backoff = this.backoffs.get(account.id); + backoff.increaseError(); + } + } + } + private async checkForWorkspaceChanges() { const workspaces = await databaseManager.appDatabase .selectFrom('workspaces') @@ -190,8 +249,9 @@ class Synchronizer { .execute(); for (const workspace of workspaces) { - if (this.workspaceBackoffs.has(workspace.user_id)) { - const backoff = this.workspaceBackoffs.get(workspace.user_id); + const backoffKey = workspace.user_id; + if (this.backoffs.has(backoffKey)) { + const backoff = this.backoffs.get(backoffKey); if (!backoff.canRetry()) { return; } @@ -257,15 +317,16 @@ class Synchronizer { .where('retry_count', '>=', 5) .execute(); } + + if (this.backoffs.has(backoffKey)) { + this.backoffs.delete(backoffKey); + } } catch (error) { - if (!this.workspaceBackoffs.has(workspace.user_id)) { - this.workspaceBackoffs.set( - workspace.user_id, - new BackoffCalculator(), - ); + if (!this.backoffs.has(backoffKey)) { + this.backoffs.set(backoffKey, new BackoffCalculator()); } - const backoff = this.workspaceBackoffs.get(workspace.user_id); + const backoff = this.backoffs.get(backoffKey); backoff.increaseError(); } } diff --git a/desktop/src/operations/mutations/logout.ts b/desktop/src/operations/mutations/logout.ts new file mode 100644 index 00000000..51811bc6 --- /dev/null +++ b/desktop/src/operations/mutations/logout.ts @@ -0,0 +1,17 @@ +export type LogoutMutationInput = { + type: 'logout'; + accountId: string; +}; + +export type LogoutMutationOutput = { + success: boolean; +}; + +declare module '@/operations/mutations' { + interface MutationMap { + logout: { + input: LogoutMutationInput; + output: LogoutMutationOutput; + }; + } +} diff --git a/desktop/src/renderer/components/accounts/account-logout.tsx b/desktop/src/renderer/components/accounts/account-logout.tsx index f0abcfa1..cd7c2b3e 100644 --- a/desktop/src/renderer/components/accounts/account-logout.tsx +++ b/desktop/src/renderer/components/accounts/account-logout.tsx @@ -10,6 +10,8 @@ import { } from '@/renderer/components/ui/alert-dialog'; import { Button } from '@/renderer/components/ui/button'; import { Spinner } from '@/renderer/components/ui/spinner'; +import { useMutation } from '@/renderer/hooks/use-mutation'; +import { toast } from '@/renderer/components/ui/use-toast'; interface AccountLogoutProps { id: string; @@ -17,7 +19,7 @@ interface AccountLogoutProps { } export const AccountLogout = ({ id, onCancel }: AccountLogoutProps) => { - const [isLoggingOut, setIsLoggingOut] = React.useState(false); + const { mutate, isPending } = useMutation(); return ( { Cancel