mirror of
https://github.com/colanode/colanode.git
synced 2025-12-29 00:25:03 +01:00
Implement logout
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
47
desktop/src/main/handlers/mutations/logout.ts
Normal file
47
desktop/src/main/handlers/mutations/logout.ts
Normal 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',
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
17
desktop/src/operations/mutations/logout.ts
Normal file
17
desktop/src/operations/mutations/logout.ts
Normal 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;
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user