Skip to content

Commit

Permalink
Merge pull request #47 from wwWallet/feat/delete-account-transaction
Browse files Browse the repository at this point in the history
Your solution looks more complete than the feat/delete-account branch. I will proceed with merging.
  • Loading branch information
kkmanos committed Feb 14, 2024
2 parents 4090890 + 1b468b3 commit 00c8300
Show file tree
Hide file tree
Showing 6 changed files with 74 additions and 46 deletions.
6 changes: 3 additions & 3 deletions src/entities/FcmToken.entity.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn, Repository } from "typeorm";
import { Column, Entity, EntityManager, ManyToOne, PrimaryGeneratedColumn, Repository } from "typeorm";
import { UserEntity } from "./user.entity";
import AppDataSource from "../AppDataSource";
import { Err, Ok, Result } from "ts-results";
Expand All @@ -20,9 +20,9 @@ const fcmTokenRepository: Repository<FcmTokenEntity> = AppDataSource.getReposito
enum DeleteFcmTokenErr {
DB_ERR = "DB_ERR"
}
async function deleteAllFcmTokensForUser(did: string): Promise<Result<{}, DeleteFcmTokenErr>> {
async function deleteAllFcmTokensForUser(did: string, options?: { entityManager?: EntityManager }): Promise<Result<{}, DeleteFcmTokenErr>> {
try {
return await fcmTokenRepository.manager.transaction(async (manager) => {
return await (options?.entityManager || fcmTokenRepository.manager).transaction(async (manager) => {
const tokens = await manager.find(FcmTokenEntity, { where: { user: { did: did } } });
await manager.remove(tokens);
return Ok({});
Expand Down
20 changes: 11 additions & 9 deletions src/entities/VerifiableCredential.entity.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Err, Ok, Result } from "ts-results";
import { Entity, PrimaryGeneratedColumn, Column, Repository} from "typeorm"
import { Entity, EntityManager, PrimaryGeneratedColumn, Column, Repository} from "typeorm"
import AppDataSource from "../AppDataSource";
import { VerifiableCredentialFormat } from "../types/oid4vci";
import { deletePresentationsByCredentialId } from './VerifiablePresentation.entity';
Expand Down Expand Up @@ -172,15 +172,17 @@ async function getVerifiableCredentialByCredentialIdentifier(holderDID: string,
}
}

async function deleteAllCredentialsWithHolderDID(holderDID: string): Promise<Result<{}, DeleteVerifiableCredentialErr>> {
async function deleteAllCredentialsWithHolderDID(holderDID: string, options?: { entityManager: EntityManager }): Promise<Result<{}, DeleteVerifiableCredentialErr>> {
try {
await verifiableCredentialRepository
.createQueryBuilder()
.from(VerifiableCredentialEntity, "vc")
.delete()
.where("holderDID = :did", { did: holderDID })
.execute();
return Ok({});
return await (options?.entityManager || verifiableCredentialRepository.manager).transaction(async (manager) => {
await manager
.createQueryBuilder()
.from(VerifiableCredentialEntity, "vc")
.delete()
.where("holderDID = :did", { did: holderDID })
.execute();
return Ok({});
});
}
catch(e) {
console.log(e);
Expand Down
21 changes: 11 additions & 10 deletions src/entities/VerifiablePresentation.entity.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Err, Ok, Result } from "ts-results";
import { Entity, PrimaryGeneratedColumn, Column, Repository} from "typeorm"
import { Entity, EntityManager, PrimaryGeneratedColumn, Column, Repository} from "typeorm"
import AppDataSource from "../AppDataSource";
import { Col } from "sequelize/types/utils";

// export enum PresentationTypes {
// JWT_VP = 'jwt_vp',
Expand Down Expand Up @@ -179,15 +178,17 @@ async function getPresentationByIdentifier(holderDID: string, presentationIdenti

}

async function deleteAllPresentationsWithHolderDID(holderDID: string): Promise<Result<{}, DeleteVerifiablePresentationErr>> {
async function deleteAllPresentationsWithHolderDID(holderDID: string, options?: { entityManager: EntityManager }): Promise<Result<{}, DeleteVerifiablePresentationErr>> {
try {
await verifiablePresentationRepository
.createQueryBuilder()
.from(VerifiablePresentationEntity, "vp")
.delete()
.where("holderDID = :did", { did: holderDID })
.execute();
return Ok({});
return await (options?.entityManager || verifiablePresentationRepository.manager).transaction(async (manager) => {
await manager
.createQueryBuilder()
.from(VerifiablePresentationEntity, "vp")
.delete()
.where("holderDID = :did", { did: holderDID })
.execute();
return Ok({});
});
}
catch(e) {
console.log(e);
Expand Down
30 changes: 30 additions & 0 deletions src/entities/common.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Result } from "ts-results";
import { EntityManager } from "typeorm"

import AppDataSource from "../AppDataSource";

/**
* Run the provided callback in a database transaction. The `entityManager` can
* be passed as an argument down the call stack to make the transaction cover
* any database operation that can take the `EntityManager` to use as an
* argument.
*
* This function accepts `Err` `Result`s to signal that the transaction should
* be aborted, in addition to the conventional signals of throwing an exception
* or returning a rejected `Promise`. `Ok<T>` results are unpacked to return
* just the contained `T` type.
*/
export async function runTransaction<T, E>(runInTransaction: (entityManager: EntityManager) => Promise<Result<T, E> | T>): Promise<T> {
return await AppDataSource.manager.transaction(async (entityManager) => {
const result = await runInTransaction(entityManager);
if ("val" in result && "ok" in result && "err" in result) {
if (result.ok) {
return Promise.resolve(result.val);
} else {
return Promise.reject(result.val);
}
} else {
return result;
}
});
}
4 changes: 2 additions & 2 deletions src/entities/user.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,9 +215,9 @@ async function getUserByDID(did: string): Promise<Result<UserEntity, GetUserErr>
}
}

async function deleteUserByDID(did: string): Promise<Result<{}, DeleteUserErr>> {
async function deleteUserByDID(did: string, options?: { entityManager: EntityManager }): Promise<Result<{}, DeleteUserErr>> {
try {
return await userRepository.manager.transaction(async (manager) => {
return await (options?.entityManager || userRepository.manager).transaction(async (manager) => {
const userRes = await manager.findOne(UserEntity, { where: { did: did }});

await manager.delete(WebauthnCredentialEntity, {
Expand Down
39 changes: 17 additions & 22 deletions src/routers/user.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import * as uuid from 'uuid';
import crypto from 'node:crypto';
import * as SimpleWebauthn from '@simplewebauthn/server';
import base64url from 'base64url';
import { EntityManager } from "typeorm"

import config from '../../config';
import { CreateUser, createUser, deleteUserByDID, deleteWebauthnCredential, getUserByCredentials, getUserByDID, getUserByWebauthnCredential, newWebauthnCredentialEntity, updateUserByDID, UpdateUserErr, updateWebauthnCredential, updateWebauthnCredentialById, UserEntity } from '../entities/user.entity';
Expand All @@ -15,9 +16,11 @@ import * as scrypt from "../scrypt";
import { appContainer } from '../services/inversify.config';
import { RegistrationParams, WalletKeystoreManager } from '../services/interfaces';
import { TYPES } from '../services/types';
import { runTransaction } from '../entities/common.entity';
import { deleteAllFcmTokensForUser, FcmTokenEntity } from '../entities/FcmToken.entity';
import { deleteAllPresentationsWithHolderDID } from '../entities/VerifiablePresentation.entity';
import { deleteAllCredentialsWithHolderDID } from '../entities/VerifiableCredential.entity';
import { Err, Ok, Result } from 'ts-results';



Expand Down Expand Up @@ -468,30 +471,22 @@ userController.post('/webauthn/credential/:id/delete', async (req: Request, res:

userController.delete('/', async (req: Request, res: Response) => {
const userDID = req.user.did;
const [ fcmTokenDeletionRes, credentialsDeletionRes, presentationsDeletionRes ] = await Promise.all([
deleteAllFcmTokensForUser(userDID),
deleteAllCredentialsWithHolderDID(userDID),
deleteAllPresentationsWithHolderDID(userDID)
]);

if (fcmTokenDeletionRes.err) {
return res.status(400).send({ result: fcmTokenDeletionRes.val })
}

if (credentialsDeletionRes.err) {
return res.status(400).send({ result: credentialsDeletionRes.val })
}

if (presentationsDeletionRes.err) {
return res.status(400).send({ result: presentationsDeletionRes.val })
}
try {
await runTransaction(async (entityManager: EntityManager) => {
// Note: this executes all four branches before checking if any failed.
// ts-results does not seem to provide an async-optimized version of Result.all(),
// and it turned out nontrivial to write one that preserves the Ok and Err types like Result.all() does.
return Result.all(
await deleteAllFcmTokensForUser(userDID, { entityManager }),
await deleteAllCredentialsWithHolderDID(userDID, { entityManager }),
await deleteAllPresentationsWithHolderDID(userDID, { entityManager }),
await deleteUserByDID(userDID, { entityManager }),
);
});

const result = await deleteUserByDID(userDID);
if (!result.err) {
return res.send({ result: "DELETED" });
}
else {
return res.status(400).send({ result: result.val })
} catch (e) {
return res.status(400).send({ result: e })
}
});
// /**
Expand Down

0 comments on commit 00c8300

Please sign in to comment.