Implement logout

This commit is contained in:
Hakan Shehu
2024-10-10 17:36:48 +02:00
parent 4df3aefc41
commit ea0ca808e5
6 changed files with 187 additions and 16 deletions

View File

@@ -65,6 +65,34 @@ class DatabaseManager {
return workspaceDatabase;
}
public async deleteAccountData(accountId: string): Promise<void> {
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,

View File

@@ -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<MutationMap[K]['input']>;
@@ -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(),
};

View File

@@ -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<LogoutMutationInput>
{
async handleMutation(
input: LogoutMutationInput,
): Promise<MutationResult<LogoutMutationInput>> {
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',
},
],
};
}
}

View File

@@ -12,8 +12,7 @@ const EVENT_LOOP_INTERVAL = 100;
class Synchronizer {
private initiated: boolean = false;
private readonly workspaceBackoffs: Map<string, BackoffCalculator> =
new Map();
private readonly backoffs: Map<string, BackoffCalculator> = 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();
}
}

View File

@@ -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;
};
}
}

View File

@@ -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 (
<AlertDialog
open={true}
@@ -38,14 +40,28 @@ export const AccountLogout = ({ id, onCancel }: AccountLogoutProps) => {
<AlertDialogCancel>Cancel</AlertDialogCancel>
<Button
variant="destructive"
disabled={isLoggingOut}
disabled={isPending}
onClick={async () => {
setIsLoggingOut(true);
await window.neuron.logout(id);
window.location.href = '/';
mutate({
input: {
type: 'logout',
accountId: id,
},
onSuccess() {
window.location.href = '/';
},
onError() {
toast({
title: 'Failed to logout',
description:
'Something went wrong trying to logout. Please try again.',
variant: 'destructive',
});
},
});
}}
>
{isLoggingOut && <Spinner className="mr-1" />}
{isPending && <Spinner className="mr-1" />}
Logout
</Button>
</AlertDialogFooter>