From cf16acf18f7dd5775f159e0134c837d6b8e2083a Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Tue, 16 Jan 2024 16:13:44 +0100 Subject: [PATCH 1/8] feat: add user to db with API endpoint Signed-off-by: Jeroen Branje --- .../app/api/user/route.ts | 17 ++ .../common/database/database.ts | 9 +- .../common/database/migrate.ts | 2 +- .../common/database/queries/common/common.ts | 4 +- .../common/database/queries/queries.ts | 16 +- .../common/database/queries/users.test.ts | 206 ++++++++++++++++ .../common/database/queries/users.ts | 194 +++++++++++++++ .../common/database/schema.ts | 2 +- .../common/database/types.ts | 77 ++++++ .../common/fixtures/index.ts | 2 + .../common/fixtures/memberCredential.ts | 75 ++++++ .../common/fixtures/userCredential.ts | 69 ++++++ ..._oracle.sql => 0000_lean_the_stranger.sql} | 15 +- .../development/meta/0000_snapshot.json | 16 +- .../drizzle/development/meta/_journal.json | 4 +- package-lock.json | 222 +++++++++--------- 16 files changed, 798 insertions(+), 132 deletions(-) create mode 100644 apps/envited.ascs.digital/app/api/user/route.ts create mode 100644 apps/envited.ascs.digital/common/database/queries/users.test.ts create mode 100644 apps/envited.ascs.digital/common/database/queries/users.ts create mode 100644 apps/envited.ascs.digital/common/database/types.ts create mode 100644 apps/envited.ascs.digital/common/fixtures/index.ts create mode 100644 apps/envited.ascs.digital/common/fixtures/memberCredential.ts create mode 100644 apps/envited.ascs.digital/common/fixtures/userCredential.ts rename apps/envited.ascs.digital/drizzle/development/{0000_thankful_oracle.sql => 0000_lean_the_stranger.sql} (78%) diff --git a/apps/envited.ascs.digital/app/api/user/route.ts b/apps/envited.ascs.digital/app/api/user/route.ts new file mode 100644 index 00000000..d87a45cb --- /dev/null +++ b/apps/envited.ascs.digital/app/api/user/route.ts @@ -0,0 +1,17 @@ +import { db } from '../../../common/database/queries' +import { ok } from '../../../common/utils' + +export async function POST(request: Request) { + try { + const credential = await request.json() + + const connection = await db() + const newUser = await connection.insertUserTx(credential) + return ok(newUser) + } catch (error) { + console.log('error', error) + return Response.json(error) + } +} + +export const dynamic = 'force-dynamic' diff --git a/apps/envited.ascs.digital/common/database/database.ts b/apps/envited.ascs.digital/common/database/database.ts index b8969f58..121a3e35 100644 --- a/apps/envited.ascs.digital/common/database/database.ts +++ b/apps/envited.ascs.digital/common/database/database.ts @@ -4,6 +4,7 @@ import { equals } from 'ramda' import { ERRORS } from '../constants' import { getSecret } from '../secretsManager' +import * as schema from './schema' export const initDb = ({ @@ -11,10 +12,10 @@ export const initDb = postgres, getSecret, }: { - drizzle: (client: postgres.Sql) => PostgresJsDatabase + drizzle: (client: postgres.Sql, config: any) => PostgresJsDatabase postgres: (options: postgres.Options) => postgres.Sql getSecret: (secretId: string) => Promise> - }): (() => Promise) => + }): (() => Promise>) => async () => { let config = { host: process.env.POSTGRES_HOST || 'localhost', // Postgres ip address[s] or domain name[s] @@ -31,7 +32,7 @@ export const initDb = config = { host, port, - database: dbname, + database: dbname as string, username, password, max: 1, @@ -41,7 +42,7 @@ export const initDb = } } - return drizzle(postgres(config)) + return drizzle(postgres(config), { schema }) } export const connectDb = initDb({ drizzle, postgres, getSecret }) diff --git a/apps/envited.ascs.digital/common/database/migrate.ts b/apps/envited.ascs.digital/common/database/migrate.ts index 9d1d008f..281ee90f 100644 --- a/apps/envited.ascs.digital/common/database/migrate.ts +++ b/apps/envited.ascs.digital/common/database/migrate.ts @@ -12,7 +12,7 @@ const runMigration = async () => { try { if (process.env.ENV === 'development') { const db = await connectDb() - await PGMigrate(db as PostgresJsDatabase, { migrationsFolder: `../drizzle/${process.env.ENV}` }) + await PGMigrate(db as PostgresJsDatabase, { migrationsFolder: `../drizzle/${process.env.ENV}` }) return } diff --git a/apps/envited.ascs.digital/common/database/queries/common/common.ts b/apps/envited.ascs.digital/common/database/queries/common/common.ts index 90081ba9..3772cbba 100644 --- a/apps/envited.ascs.digital/common/database/queries/common/common.ts +++ b/apps/envited.ascs.digital/common/database/queries/common/common.ts @@ -1,5 +1,7 @@ import { sql } from 'drizzle-orm' import { PostgresJsDatabase } from 'drizzle-orm/postgres-js' -export const fetchTables = (db: PostgresJsDatabase) => async () => +import * as schema from '../../schema' + +export const fetchTables = (db: PostgresJsDatabase) => async () => db.execute(sql`select * from information_schema.tables;`) diff --git a/apps/envited.ascs.digital/common/database/queries/queries.ts b/apps/envited.ascs.digital/common/database/queries/queries.ts index 514d1d0e..5cfaee45 100644 --- a/apps/envited.ascs.digital/common/database/queries/queries.ts +++ b/apps/envited.ascs.digital/common/database/queries/queries.ts @@ -3,21 +3,31 @@ import postgres from 'postgres' import { fromPairs, map, pipe, toPairs } from 'ramda' import { connectDb } from '../database' +import * as schema from '../schema' import { fetchTables } from './common' +import { insertUserTx } from './users' const queries = { fetchTables, + insertUserTx, } export const init = - (connectDb: () => Promise) => - (queries: Record () => Promise[]>>>) => + (connectDb: () => Promise>) => + ( + queries: Record< + string, + ( + db: PostgresJsDatabase, + ) => (...args: any[]) => Promise[]> | postgres.Row> + >, + ) => async () => { const connection = await connectDb() return pipe( toPairs, - map(([key, value]: [key: string, value: (connection: PostgresJsDatabase) => any]): [any, any] => [ + map(([key, value]: [key: string, value: (connection: PostgresJsDatabase) => any]): [any, any] => [ key, value(connection), ]), diff --git a/apps/envited.ascs.digital/common/database/queries/users.test.ts b/apps/envited.ascs.digital/common/database/queries/users.test.ts new file mode 100644 index 00000000..5a8a15d6 --- /dev/null +++ b/apps/envited.ascs.digital/common/database/queries/users.test.ts @@ -0,0 +1,206 @@ +import { USER_CREDENTIAL } from '../../fixtures' +import { addressType, credentialType, issuer, usersToRoles } from '../schema' +import { Credential } from '../types' +import { + _insertUserTx, + _txn, + insertAddressTypeTx, + insertCredentialTypeTx, + insertIssuerTx, + insertUsersToRolesTx, +} from './users' + +describe('common/database/users', () => { + describe('insertAddressTypeTx', () => { + it('should insert a new addresType', async () => { + // when ... we want to insert a new addresType + // then ... we should get the queries as expected + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({ + onConflictDoNothing: jest.fn().mockReturnValue({ + returning: jest.fn().mockResolvedValue([{ id: 'ADDRESS_TYPE_ID' }]), + }), + }), + }), + select: jest.fn().mockReturnValue({ + from: jest.fn().mockReturnValue({ + where: jest.fn().mockReturnValue({ + limit: jest.fn().mockReturnValue([{ id: 'ADDRESS_TYPE_ID' }]), + }), + }), + }), + } as any + + const result = await insertAddressTypeTx(tx)('ADDRESS_TYPE') + + expect(tx.insert).toHaveBeenCalledWith(addressType) + expect(tx.insert().values).toHaveBeenCalledWith({ name: 'ADDRESS_TYPE' }) + expect(tx.insert().values().onConflictDoNothing).toHaveBeenCalledWith() + expect(tx.insert().values().onConflictDoNothing().returning).toHaveBeenCalledWith() + expect(result).toEqual([{ id: 'ADDRESS_TYPE_ID' }]) + }) + }) + + describe('insertCredentialTypeTx', () => { + it('should insert a credentialType and add a relation on usersToCredentialTypes', async () => { + // when ... we want insert a credential type + // then ... we should connect it to a user as expected + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({ + onConflictDoNothing: jest.fn().mockReturnValue({ + returning: jest.fn().mockResolvedValue({}), + }), + }), + }), + select: jest.fn().mockReturnValue({ + from: jest.fn().mockReturnValue({ + where: jest.fn().mockReturnValue({ + limit: jest.fn().mockReturnValue([{ id: 'CREDENTIAL_TYPE_ID' }]), + }), + }), + }), + } as any + + const result = await insertCredentialTypeTx(tx)({ + userId: 'USER_ID', + type: 'TYPE', + }) + + expect(tx.insert).toHaveBeenCalledWith(credentialType) + expect(tx.insert().values).toHaveBeenNthCalledWith(1, { + name: 'TYPE', + }) + expect(tx.insert().values().onConflictDoNothing).toHaveBeenCalledWith() + expect(tx.insert().values).toHaveBeenNthCalledWith(2, { + userId: 'USER_ID', + credentialTypeId: 'CREDENTIAL_TYPE_ID', + }) + expect(result).toEqual({}) + }) + }) + + describe('insertIssuerTx', () => { + it('should insert a issuer', async () => { + // when ... we to insert a issuer + // then ... we should get the issuer result + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({ + onConflictDoNothing: jest.fn().mockReturnValue({ + returning: jest.fn().mockResolvedValue({ + id: 'ID', + type: 'TYPE', + name: 'NAME', + url: 'URL', + }), + }), + }), + }), + select: jest.fn().mockReturnValue({}), + } as any + + const result = await insertIssuerTx(tx)({ + id: 'ID', + type: 'TYPE', + name: 'NAME', + url: 'URL', + }) + + expect(tx.insert).toHaveBeenCalledWith(issuer) + expect(tx.insert().values).toHaveBeenCalledWith({ + id: 'ID', + type: 'TYPE', + name: 'NAME', + url: 'URL', + }) + expect(tx.insert().values().onConflictDoNothing).toHaveBeenCalledWith() + expect(tx.insert().values().onConflictDoNothing().returning).toHaveBeenCalledWith() + expect(result).toEqual({ + id: 'ID', + type: 'TYPE', + name: 'NAME', + url: 'URL', + }) + }) + }) + + describe('insertUsersToRolesTx', () => { + it('should connect users to roles', async () => { + // when ... we want to connect a user to a role + // then ... we should get the result as expected + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({}), + }), + } as any + + const result = await insertUsersToRolesTx(tx)({ + userId: 'USER_ID', + roleId: 'ROLE_ID', + }) + + expect(tx.insert).toHaveBeenCalledWith(usersToRoles) + expect(tx.insert().values).toHaveBeenNthCalledWith(1, { + userId: 'USER_ID', + roleId: 'ROLE_ID', + }) + expect(result).toEqual({}) + }) + }) + + describe('txn', () => { + it('should insert a user with all the relational', async () => { + // when ... we want to connect a user to a role + // then ... we should get the result as expected + const dependencies = { + insertAddressTypeTx: () => jest.fn().mockResolvedValue([{ id: 'ADDRESS_TYPE_ID' }]), + insertIssuerTx: () => jest.fn().mockResolvedValue([{ id: 'ISSUER_ID' }]), + insertUsersToRolesTx: () => jest.fn().mockResolvedValue([{ id: 'ISSUER_ID' }]), + insertCredentialTypeTx: () => jest.fn().mockResolvedValue([{ id: 'CREDENTIAL_TYPE_ID' }]), + } as any + + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({ + onConflictDoNothing: jest.fn().mockReturnValue({ + returning: jest.fn().mockResolvedValue({}), + }), + returning: jest.fn().mockResolvedValue([{ id: 'USER_ID' }]), + }), + }), + select: jest.fn().mockReturnValue({ + from: jest.fn().mockReturnValue({ + where: jest.fn().mockReturnValue([{ id: 'ROLE_ID' }]), + }), + }), + rollback: jest.fn().mockResolvedValue({}), + } + + const transaction = await _txn(dependencies)(USER_CREDENTIAL)(tx as any) + + expect(tx.insert().values).toHaveBeenCalled() + expect(transaction).toEqual({ + id: 'USER_ID', + }) + }) + }) + + describe('_insertUserTx', () => { + it('should insert a user with all the relational connections', async () => { + // when ... we want to connect a user to a role + // then ... we should get the result as expected + const db = { + transaction: jest.fn(), + } + const credential = {} as Credential + const transaction = jest.fn().mockResolvedValue('x') + + _insertUserTx(transaction)(db as any)(credential) + + expect(transaction).toHaveBeenCalledWith(credential) + expect(db.transaction).toHaveBeenCalledWith(transaction(credential)) + }) + }) +}) diff --git a/apps/envited.ascs.digital/common/database/queries/users.ts b/apps/envited.ascs.digital/common/database/queries/users.ts new file mode 100644 index 00000000..762ce518 --- /dev/null +++ b/apps/envited.ascs.digital/common/database/queries/users.ts @@ -0,0 +1,194 @@ +import { ExtractTablesWithRelations, eq, inArray } from 'drizzle-orm' +import { PgTransaction } from 'drizzle-orm/pg-core' +import { PostgresJsDatabase } from 'drizzle-orm/postgres-js' +import { PostgresJsQueryResultHKT } from 'drizzle-orm/postgres-js' +import postgres from 'postgres' +import { isEmpty, prop, propOr } from 'ramda' + +import * as schema from '../schema' +import { addressType, credentialType, issuer, role, user, usersToCredentialTypes, usersToRoles } from '../schema' +import { Credential, Issuer, User } from '../types' + +export const insertUsersToRolesTx = + (tx: PgTransaction>) => + async ({ userId, roleId }: { userId: string; roleId: string }) => + await tx.insert(usersToRoles).values({ userId, roleId }) + +export const insertIssuerTx = + (tx: PgTransaction>) => + async (newIssuer: Issuer) => + await tx + .insert(issuer) + .values({ + ...newIssuer, + id: newIssuer.id, + name: newIssuer.name, + url: newIssuer.url, + type: newIssuer.type, + }) + .onConflictDoNothing() + .returning() + +export const insertAddressTypeTx = + (tx: PgTransaction>) => + async (type: string) => { + let result = await tx + .insert(addressType) + .values({ + name: type, + }) + .onConflictDoNothing() + .returning() + + if (isEmpty(result)) { + result = await tx.select().from(addressType).where(eq(addressType.name, type)).limit(1) + } + + return result + } + +export const insertCredentialTypeTx = + (tx: PgTransaction>) => + async ({ userId, type }: { userId: string; type: string }) => { + let result = await tx + .insert(credentialType) + .values({ + name: type, + }) + .onConflictDoNothing() + .returning() + + if (isEmpty(result)) { + result = await tx.select().from(credentialType).where(eq(credentialType.name, type)).limit(1) + } + + const credentialTypeId = result[0].id + + return tx.insert(usersToCredentialTypes).values({ userId, credentialTypeId }).onConflictDoNothing().returning() + } + +export const _txn = + ({ + insertAddressTypeTx, + insertIssuerTx, + insertUsersToRolesTx, + insertCredentialTypeTx, + }: { + insertAddressTypeTx: ( + tx: PgTransaction>, + ) => (type: string) => Promise< + { + id: string + name: string | null + description: string | null + }[] + > + insertIssuerTx: ( + tx: PgTransaction>, + ) => (newIssuer: Issuer) => Promise< + { + id: string + name: string | null + url: string | null + type: string | null + }[] + > + insertUsersToRolesTx: ( + tx: PgTransaction>, + ) => ({ userId, roleId }: { userId: string; roleId: string }) => Promise> + insertCredentialTypeTx: ( + tx: PgTransaction>, + ) => ({ userId, type }: { userId: string; type: string }) => Promise< + { + userId: string + credentialTypeId: string + }[] + > + }) => + (credential: Credential) => + async (tx: PgTransaction>) => { + try { + const { issuer: newIssuer, type: credentialTypes, issuanceDate, expirationDate, credentialSubject } = credential + + const [addressType] = await insertAddressTypeTx(tx)(credentialSubject.address.type) + const { id: addressTypeId } = addressType + + const [issuer] = await insertIssuerTx(tx)(newIssuer) + const { id } = issuer + + const [newUser] = await tx + .insert(user) + .values({ + id: prop('id')(credentialSubject), + name: prop('name')(credentialSubject), + email: propOr('email', '')(credentialSubject), + vatId: propOr('vatId', '')(credentialSubject), + privacyPolicyAccepted: prop('privacyPolicy')(credentialSubject), + articlesOfAssociationAccepted: propOr('articlesOfAssociation', '')(credentialSubject), + contributionRulesAccepted: propOr('contributionRules', '')(credentialSubject), + isAscsMember: prop('isAscsMember')(credentialSubject), + isEnvitedMember: prop('isEnvitedMember')(credentialSubject), + addressTypeId, + streetAddress: credentialSubject.address.streetAddress, + postalCode: credentialSubject.address.postalCode, + addressLocality: credentialSubject.address.addressLocality, + addressCountry: credentialSubject.address.addressCountry, + issuerId: id, + issuanceDate: new Date(issuanceDate), + expirationDate: new Date(expirationDate), + createdAt: new Date(), + updatedAt: new Date(), + }) + .returning() + + const roleFilterArray = + credentialSubject.type === 'AscsMember' ? ['principal', 'provider', 'user'] : ['provider', 'user'] + + const roles = await tx.select().from(role).where(inArray(role.id, roleFilterArray)) + + const insertUsersToRolesPromises = roles.map(({ id }: { id: string }) => + insertUsersToRolesTx(tx)({ + userId: newUser.id, + roleId: id, + }), + ) + + await Promise.all(insertUsersToRolesPromises) + + const insertCredentialTypeTxPromises = credentialTypes.map((type: string) => + insertCredentialTypeTx(tx)({ + userId: newUser.id, + type, + }), + ) + + await Promise.all(insertCredentialTypeTxPromises) + + return newUser + } catch (error) { + console.log(error) + tx.rollback() + throw error + } + } + +export const txn = _txn({ + insertAddressTypeTx, + insertIssuerTx, + insertUsersToRolesTx, + insertCredentialTypeTx, +}) + +export const _insertUserTx = + ( + transaction: ( + credential: Credential, + ) => ( + tx: PgTransaction>, + ) => Promise, + ) => + (db: PostgresJsDatabase) => + (credential: Credential) => + db.transaction(transaction(credential)) + +export const insertUserTx = _insertUserTx(txn) diff --git a/apps/envited.ascs.digital/common/database/schema.ts b/apps/envited.ascs.digital/common/database/schema.ts index 6599382f..1b65b18c 100644 --- a/apps/envited.ascs.digital/common/database/schema.ts +++ b/apps/envited.ascs.digital/common/database/schema.ts @@ -42,7 +42,7 @@ export const roleRelations = relations(role, ({ many }) => ({ export const addressType = pgTable('addressType', { id: uuid('id').defaultRandom().primaryKey(), - name: text('name'), + name: text('name').unique(), description: text('description'), }) diff --git a/apps/envited.ascs.digital/common/database/types.ts b/apps/envited.ascs.digital/common/database/types.ts new file mode 100644 index 00000000..4f447ac8 --- /dev/null +++ b/apps/envited.ascs.digital/common/database/types.ts @@ -0,0 +1,77 @@ +import { PostgresJsDatabase } from 'drizzle-orm/postgres-js' + +import * as schema from './schema' +import { StringChange } from '@nx/devkit' + +export type DatabaseConnection = PostgresJsDatabase + +export interface Issuer { + id: string + type: string + name: string + url: string +} + +export interface Address { + type: string + streetAddress: string + postalCode: string + addressLocality: string + addressCountry: string +} + +export interface AscsMember { + id: string + type: string + name: string + url: string + address: Address + vatID: string + isAscsMember: boolean + isEnvitedMember: boolean + privacyPolicy: string + articlesOfAssociation: string + contributionRules: string +} + +export interface AscsUser { + id: string + type: string + name: string + email: string + address: Address + isAscsMember: boolean + isEnvitedMember: boolean + privacyPolicy: string +} + +export interface Credential { + type: string[] + issuanceDate: string + expirationDate: string + id: string + issuer: Issuer + credentialSubject: AscsMember | AscsUser +} + +export interface User { + id: string + name: string + email: string + vatId: string + privacyPolicyAccepted: string + articlesOfAssociationAccepted: string + contributionRulesAccepted: string + isAscsMember: boolean + isEnvitedMember: boolean + addressTypeId: string + streetAddress: StringChange + postalCode: StringChange + addressLocality: StringChange + addressCountry: StringChange + issuerId: StringChange + issuanceDate: string + expirationDate: string + createdAt: string + updatedAt: string +} diff --git a/apps/envited.ascs.digital/common/fixtures/index.ts b/apps/envited.ascs.digital/common/fixtures/index.ts new file mode 100644 index 00000000..e275c80e --- /dev/null +++ b/apps/envited.ascs.digital/common/fixtures/index.ts @@ -0,0 +1,2 @@ +export { MEMBER_CREDENTIAL } from './memberCredential' +export { USER_CREDENTIAL } from './userCredential' diff --git a/apps/envited.ascs.digital/common/fixtures/memberCredential.ts b/apps/envited.ascs.digital/common/fixtures/memberCredential.ts new file mode 100644 index 00000000..fe60384f --- /dev/null +++ b/apps/envited.ascs.digital/common/fixtures/memberCredential.ts @@ -0,0 +1,75 @@ +export const MEMBER_CREDENTIAL = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@version': 1.1, + '@protected': true, + 'AscsMemberCredential': 'https://schema.ascs.digital/AscsMemberCredential/v1#', + 'AscsIssuer': { + '@id': 'https://schema.ascs.digital/AscsMemberCredential/v1#AscsIssuer', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'url': 'https://schema.org/url', + }, + }, + 'AscsMember': { + '@id': 'https://schema.ascs.digital/AscsMemberCredential/v1#AscsMember', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'url': 'https://schema.org/url', + 'address': 'http://schema.org/address', + 'vatID': 'http://schema.org/vatID', + 'isAscsMember': 'http://schema.org/Boolean', + 'isEnvitedMember': 'http://schema.org/Boolean', + 'privacyPolicy': 'https://schema.org/termsOfService', + 'articlesOfAssociation': 'https://schema.org/termsOfService', + 'contributionRules': 'https://schema.org/termsOfService', + }, + }, + 'PostalAddress': { + '@id': 'http://schema.org/PostalAddress', + '@context': { + '@version': 1.1, + '@protected': true, + 'streetAddress': 'http://schema.org/streetAddress', + 'postalCode': 'http://schema.org/postalCode', + 'addressLocality': 'http://schema.org/addressLocality', + 'addressCountry': 'https://schema.org/addressCountry', + }, + }, + }, + ], + 'type': ['VerifiableCredential', 'AscsMemberCredential'], + 'issuanceDate': '2023-11-22T17:14:33Z', + 'expirationDate': '2102-09-15T17:14:33Z', + 'id': 'urn:uuid:576fbefb-35e8-4b71-bb1a-53d1803c86de', + 'issuer': { + id: 'did:pkh:tz:tz1ggujjYjA7oYoaZBzTg1tYSXn3VMjcgDuv', + type: 'AscsIssuer', + name: 'Automotive Solution Center for Simulation e.V.', + url: 'https://identity.ascs.digital/', + }, + 'credentialSubject': { + id: 'did:pkh:tz:tz1bpeJArd7apJyTUryfXH1SD6w8GL6Gwhj8', + type: 'AscsMember', + name: 'Testcompany GmbH', + url: 'https://test.de/', + address: { + type: 'PostalAddress', + streetAddress: 'Teststraße 1', + postalCode: '12345', + addressLocality: 'Munich', + addressCountry: 'DE', + }, + vatID: 'DE123456789', + isAscsMember: true, + isEnvitedMember: true, + privacyPolicy: 'https://media.ascs.digital/terms/ascs_privacy_policy_2020-07-08.pdf#SHA-256', + articlesOfAssociation: 'https://media.ascs.digital/terms/ascs_articles_of_association_2021-09-17.pdf#SHA-256', + contributionRules: 'https://media.ascs.digital/terms/ascs_contribution_rules_2020-07-08.pdf#SHA-256', + }, +} diff --git a/apps/envited.ascs.digital/common/fixtures/userCredential.ts b/apps/envited.ascs.digital/common/fixtures/userCredential.ts new file mode 100644 index 00000000..6141dc7d --- /dev/null +++ b/apps/envited.ascs.digital/common/fixtures/userCredential.ts @@ -0,0 +1,69 @@ +export const USER_CREDENTIAL = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@version': 1.1, + '@protected': true, + 'AscsUserCredential': 'https://schema.ascs.digital/AscsUserCredential/v1#', + 'AscsIssuer': { + '@id': 'https://schema.ascs.digital/AscsUserCredential/v1#AscsIssuer', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'url': 'https://schema.org/url', + }, + }, + 'AscsUser': { + '@id': 'https://schema.ascs.digital/AscsUserCredential/v1#AscsUser', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'email': 'https://schema.org/email', + 'address': 'http://schema.org/address', + 'isAscsMember': 'http://schema.org/Boolean', + 'isEnvitedMember': 'http://schema.org/Boolean', + 'privacyPolicy': 'https://schema.org/termsOfService', + }, + }, + 'PostalAddress': { + '@id': 'http://schema.org/PostalAddress', + '@context': { + '@version': 1.1, + '@protected': true, + 'streetAddress': 'http://schema.org/streetAddress', + 'postalCode': 'http://schema.org/postalCode', + 'addressLocality': 'http://schema.org/addressLocality', + 'addressCountry': 'https://schema.org/addressCountry', + }, + }, + }, + ], + 'type': ['VerifiableCredential', 'AscsUserCredential'], + 'issuanceDate': '2023-11-22T17:14:33Z', + 'expirationDate': '2102-09-15T17:14:33Z', + 'id': 'urn:uuid:cf1f329d-9c4c-458e-ba0a-a762a296b79c', + 'issuer': { + id: 'did:pkh:tz:tz1bpeJArd7apJyTUryfXH1SD6w8GL6Gwhj8', + type: 'AscsIssuer', + name: 'Testcompany GmbH', + url: 'https://test.de/', + }, + 'credentialSubject': { + id: 'did:pkh:tz:tz1SfdVU1mor3Sgej3FmmwMH4HM1EjTzqqeE', + type: 'AscsUser', + name: 'User', + email: 'mailto:user@test.de', + address: { + type: 'PostalAddress', + streetAddress: 'Teststraße 1', + postalCode: '12345', + addressLocality: 'Munich', + addressCountry: 'DE', + }, + isAscsMember: true, + isEnvitedMember: true, + privacyPolicy: 'https://media.ascs.digital/terms/ascs_privacy_policy_2020-07-08.pdf#SHA-256', + }, +} diff --git a/apps/envited.ascs.digital/drizzle/development/0000_thankful_oracle.sql b/apps/envited.ascs.digital/drizzle/development/0000_lean_the_stranger.sql similarity index 78% rename from apps/envited.ascs.digital/drizzle/development/0000_thankful_oracle.sql rename to apps/envited.ascs.digital/drizzle/development/0000_lean_the_stranger.sql index 462399db..b36b43a5 100644 --- a/apps/envited.ascs.digital/drizzle/development/0000_thankful_oracle.sql +++ b/apps/envited.ascs.digital/drizzle/development/0000_lean_the_stranger.sql @@ -1,7 +1,8 @@ CREATE TABLE IF NOT EXISTS "addressType" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "name" text, - "description" text + "description" text, + CONSTRAINT "addressType_name_unique" UNIQUE("name") ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "credentialType" ( @@ -59,37 +60,37 @@ CREATE TABLE IF NOT EXISTS "usersToRoles" ( ); --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "user" ADD CONSTRAINT "user_issuer_id_issuer_id_fk" FOREIGN KEY ("issuer_id") REFERENCES "issuer"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "user" ADD CONSTRAINT "user_issuer_id_issuer_id_fk" FOREIGN KEY ("issuer_id") REFERENCES "public"."issuer"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "user" ADD CONSTRAINT "user_address_type_id_addressType_id_fk" FOREIGN KEY ("address_type_id") REFERENCES "addressType"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "user" ADD CONSTRAINT "user_address_type_id_addressType_id_fk" FOREIGN KEY ("address_type_id") REFERENCES "public"."addressType"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_credential_type_id_credentialType_id_fk" FOREIGN KEY ("credential_type_id") REFERENCES "credentialType"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_credential_type_id_credentialType_id_fk" FOREIGN KEY ("credential_type_id") REFERENCES "public"."credentialType"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "role"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json b/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json index f7361978..7f3c9e2d 100644 --- a/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json +++ b/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "80905e30-6a30-4250-a963-25e52e2b88a7", + "id": "96c668c6-208a-4336-907b-bd99e7497d16", "prevId": "00000000-0000-0000-0000-000000000000", "version": "5", "dialect": "pg", @@ -31,7 +31,13 @@ "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": { + "addressType_name_unique": { + "name": "addressType_name_unique", + "nullsNotDistinct": false, + "columns": ["name"] + } + } }, "credentialType": { "name": "credentialType", @@ -261,6 +267,7 @@ "name": "user_issuer_id_issuer_id_fk", "tableFrom": "user", "tableTo": "issuer", + "schemaTo": "public", "columnsFrom": ["issuer_id"], "columnsTo": ["id"], "onDelete": "no action", @@ -270,6 +277,7 @@ "name": "user_address_type_id_addressType_id_fk", "tableFrom": "user", "tableTo": "addressType", + "schemaTo": "public", "columnsFrom": ["address_type_id"], "columnsTo": ["id"], "onDelete": "no action", @@ -308,6 +316,7 @@ "name": "usersToCredentialTypes_user_id_user_id_fk", "tableFrom": "usersToCredentialTypes", "tableTo": "user", + "schemaTo": "public", "columnsFrom": ["user_id"], "columnsTo": ["id"], "onDelete": "no action", @@ -317,6 +326,7 @@ "name": "usersToCredentialTypes_credential_type_id_credentialType_id_fk", "tableFrom": "usersToCredentialTypes", "tableTo": "credentialType", + "schemaTo": "public", "columnsFrom": ["credential_type_id"], "columnsTo": ["id"], "onDelete": "no action", @@ -349,6 +359,7 @@ "name": "usersToRoles_user_id_user_id_fk", "tableFrom": "usersToRoles", "tableTo": "user", + "schemaTo": "public", "columnsFrom": ["user_id"], "columnsTo": ["id"], "onDelete": "no action", @@ -358,6 +369,7 @@ "name": "usersToRoles_role_id_role_id_fk", "tableFrom": "usersToRoles", "tableTo": "role", + "schemaTo": "public", "columnsFrom": ["role_id"], "columnsTo": ["id"], "onDelete": "no action", diff --git a/apps/envited.ascs.digital/drizzle/development/meta/_journal.json b/apps/envited.ascs.digital/drizzle/development/meta/_journal.json index e14f29a3..3a7372f9 100644 --- a/apps/envited.ascs.digital/drizzle/development/meta/_journal.json +++ b/apps/envited.ascs.digital/drizzle/development/meta/_journal.json @@ -5,8 +5,8 @@ { "idx": 0, "version": "5", - "when": 1704721774957, - "tag": "0000_thankful_oracle", + "when": 1705324440754, + "tag": "0000_lean_the_stranger", "breakpoints": true } ] diff --git a/package-lock.json b/package-lock.json index 438d6035..87ab3e25 100644 --- a/package-lock.json +++ b/package-lock.json @@ -558,16 +558,16 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" }, "node_modules/@aws-sdk/client-cloudformation": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.489.0.tgz", - "integrity": "sha512-wPrnaypFs54B4+QWd1jaTAPFNLCL9W1fBl9RREtddU6YMuChk+39jJOTTZcPY/PemmfY8gOZoXmtG9OqMGvGJg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cloudformation/-/client-cloudformation-3.490.0.tgz", + "integrity": "sha512-WtHIxAAEpWID4u3UeZSq+wE2fXgSi04LaqQ/ZDtCJ4UbR/ml5y05lX0HCXdkPJbqbmBXzoZRPKUPGicJ03Hy0w==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -612,16 +612,16 @@ } }, "node_modules/@aws-sdk/client-cognito-identity": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.489.0.tgz", - "integrity": "sha512-NGcvONQwyq/zEVy8S++G+aKpNeUYa2QngxKa2qP+5sL6LGIigXQQjR8+IfG/zbu91hsx5R9P0QYuUnvXrlZotQ==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-cognito-identity/-/client-cognito-identity-3.490.0.tgz", + "integrity": "sha512-P2C8yBOUK0iIIYMb6AUkiE5qoWu032tMVxIZWya9dBYu8uqlnzO0duC5P3UGn6lETZX/59PQ926vRc/6YMyMLg==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -663,16 +663,16 @@ } }, "node_modules/@aws-sdk/client-ecs": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.489.0.tgz", - "integrity": "sha512-s/7YonQAqr8YTz02m56rlc+2iIOZkCBQyJYjofTgr+NV+fixA01NWkSP930Q09ybryfkDZlzgliw39jA7XSiYg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ecs/-/client-ecs-3.490.0.tgz", + "integrity": "sha512-7t8ecW4vOg1tPq6uJVM6/wcGufwcyXXXTsCaGiF2nwtkEbks9ID7MP+Y/l53z93zmqFG5Kx77vH1pA3d3YqOVw==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -716,16 +716,16 @@ } }, "node_modules/@aws-sdk/client-eventbridge": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-eventbridge/-/client-eventbridge-3.489.0.tgz", - "integrity": "sha512-Eikx9ZFxikGXLjDFWq6UkNMeVPAdQ3ZIYpSU9FZgtIwkdcTcWKSg3NJ+IrGvtEEG0MtAiN3NbAAroh0hheBZYg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-eventbridge/-/client-eventbridge-3.490.0.tgz", + "integrity": "sha512-ypEhS/5ZKkKFpBVxsP5JxGmK6JJVb150Nmk0S1Y59UhRs0Wl08S/hxeMYjJZaTEYRqaxPSQFrtbWXD76wVat1w==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -767,16 +767,16 @@ } }, "node_modules/@aws-sdk/client-iam": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.489.0.tgz", - "integrity": "sha512-hXlv8T8v9XyJuLfvzCQcNGrbp5n7gjYcMDkcR7zn1ilbtpHKoncYPbW6YBtv6utiAJXPi5jPvj3D9SiRd/bonA==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iam/-/client-iam-3.490.0.tgz", + "integrity": "sha512-jMGKufnBd26DRcwWvv4MxR+mXgLh7KezpxZrT16lxR88U5sD8gOXohj1I1t5f8W8ihON1Ml8EJU++ui6KAowMw==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -820,16 +820,16 @@ } }, "node_modules/@aws-sdk/client-iot": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.489.0.tgz", - "integrity": "sha512-ejB3ayrBjJtjSoSFLvl0agPVj4PEciF1HBmInNeOw8Mn8SNJaAW04bK/Cq5GJ0OEtzAB4HkPbdm5ymwRkWJ2bA==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot/-/client-iot-3.490.0.tgz", + "integrity": "sha512-ODgqRcUzy9blehL1Nv7h2HuDrZ4NA67VHpkdN99Cwru6uJYwLC5HUiuLEGLFPh/kZPKRf8j2i7FOD1AZ3Vj3cg==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -872,16 +872,16 @@ } }, "node_modules/@aws-sdk/client-iot-data-plane": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot-data-plane/-/client-iot-data-plane-3.489.0.tgz", - "integrity": "sha512-mVBTYC5p6bH5CoUGnLo21E3JVAHuuCKMVrARBLi/mjAYcMsmcA4O/QKcQeAZ1bZ3MjdcUZfG9zXkc9Dmuav6Cg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-iot-data-plane/-/client-iot-data-plane-3.490.0.tgz", + "integrity": "sha512-MxShd3DPYBL6EmHVZTaS71XdEZrRriUwVtqAycTepORdaykN/xhw1woX4bOvoyO1xUDlM3hSWMP7viF/GIg+Jg==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -924,16 +924,16 @@ } }, "node_modules/@aws-sdk/client-lambda": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.489.0.tgz", - "integrity": "sha512-KKW5p4whow9dc581jWwY0MGUjLasvoApWVhi8p6AcSBv7YWKXURUKURQ48VGAKRpdYFqQwCs8qfm1z1+S2J1Ow==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-lambda/-/client-lambda-3.490.0.tgz", + "integrity": "sha512-lj29UGeaBuylTHiMdb4f16t2wABdBp8FADLolSsfTxDcY5kVZ3a7299G+Lyb9m27Rq42pMUKvJBzizIduyqkMA==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -980,16 +980,16 @@ } }, "node_modules/@aws-sdk/client-rds-data": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-rds-data/-/client-rds-data-3.489.0.tgz", - "integrity": "sha512-6VIu7DqvZf+XGyloCySuO2dyqqyogE5YZpNcz0zCBY9HnTM2CqwMRSQpN5f9XiYprgpxo0WtSZ3Pmd2X9BOo6g==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-rds-data/-/client-rds-data-3.490.0.tgz", + "integrity": "sha512-lFtejV/j0bIUxYHg68QDJchMygPVQWYAFcS6/UqUWcX1ONC1JLT9FDc2l2w1ZM9p33Z/AT31YS96Kd9nwLSTBg==", "devOptional": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -1031,17 +1031,17 @@ } }, "node_modules/@aws-sdk/client-s3": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.489.0.tgz", - "integrity": "sha512-8NvXqtncf0GJIVqunxwIKjCZv7L7LEEYgY38Mm/EWjiKZ76E7TJ7gsZAB7Wgp2kabmMvDDqDsA7Q5X2qg65AdA==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-s3/-/client-s3-3.490.0.tgz", + "integrity": "sha512-fBj3CJ3+5R+l/sc93Z9mKw8gM2b9K6vEhC9qSCG2XNymLd9YqlRft1peQ7VymrWywAHX3Koz1GCUrFEVNONiMw==", "dev": true, "dependencies": { "@aws-crypto/sha1-browser": "3.0.0", "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-bucket-endpoint": "3.489.0", "@aws-sdk/middleware-expect-continue": "3.489.0", "@aws-sdk/middleware-flexible-checksums": "3.489.0", @@ -1100,15 +1100,15 @@ } }, "node_modules/@aws-sdk/client-secrets-manager": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.489.0.tgz", - "integrity": "sha512-o1rCISvzWoU28fn7ewkWWZztJixzChbol5vKPMpQILlOsb4CEa/HUCEmI+r7A4b24TtCL4TLFcqBuPir1rl04w==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-secrets-manager/-/client-secrets-manager-3.490.0.tgz", + "integrity": "sha512-wd+PBIMOfSY9CpHfAAE9ztwwde7wom3/VdSqLbBUAUAvAlArANmBS610iCZSFP3ds38v8rnliAV9fjzf7IVnxQ==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -1151,16 +1151,16 @@ } }, "node_modules/@aws-sdk/client-ssm": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.489.0.tgz", - "integrity": "sha512-zmHUzjYSYfBOnY/IxNjDHLdBg4j9aI+9UnwHcbJ4l9TgBkhc1/1vjmgmkgsGm3Ogft2mHgRpB6+FTYGZ29DmKQ==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-ssm/-/client-ssm-3.490.0.tgz", + "integrity": "sha512-nPqFZky4NngZRljaNq+fpt/VVCHhtVLjrvcfEOImx6Ga0pcP0ENsVdba1lAWMcH6M31QFpHG3kzfGKf478zqnQ==", "dev": true, "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -1204,13 +1204,13 @@ } }, "node_modules/@aws-sdk/client-sso": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.489.0.tgz", - "integrity": "sha512-SZPXiYnByYnd3Vy0qY/PnWD2e9JA3Lwi000Tyz+ZQvjK9emH0B6aeWaxFZ7W4jscJVwQVc5kgvRPsJi5zY3w1w==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sso/-/client-sso-3.490.0.tgz", + "integrity": "sha512-yfxoHmCL1w/IKmFRfzCxdVCQrGlSQf4eei9iVEm5oi3iE8REFyPj3o/BmKQEHG3h2ITK5UbdYDb5TY4xoYHsyA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.485.0", + "@aws-sdk/core": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -1251,14 +1251,14 @@ } }, "node_modules/@aws-sdk/client-sts": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.489.0.tgz", - "integrity": "sha512-AAQ9+oEJPIPHXWtQL7ahZCKata+d+vZMXpQp92st7KzgmcgsUBdDTBOH0ImN8LXwZwIMAzfn98wWf4s1xtqUeg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/client-sts/-/client-sts-3.490.0.tgz", + "integrity": "sha512-n2vQ5Qu2qi2I0XMI+IH99ElpIRHOJTa1+sqNC4juMYxKQBMvw+EnsqUtaL3QvTHoyxNB/R7mpkeBB6SzPQ1TtA==", "dependencies": { "@aws-crypto/sha256-browser": "3.0.0", "@aws-crypto/sha256-js": "3.0.0", - "@aws-sdk/core": "3.485.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/core": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/middleware-host-header": "3.489.0", "@aws-sdk/middleware-logger": "3.489.0", "@aws-sdk/middleware-recursion-detection": "3.489.0", @@ -1366,9 +1366,9 @@ } }, "node_modules/@aws-sdk/core": { - "version": "3.485.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.485.0.tgz", - "integrity": "sha512-Yvi80DQcbjkYCft471ClE3HuetuNVqntCs6eFOomDcrJaqdOFrXv2kJAxky84MRA/xb7bGlDGAPbTuj1ICputg==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/core/-/core-3.490.0.tgz", + "integrity": "sha512-TSBWkXtxMU7q1Zo6w3v5wIOr/sj7P5Jw3OyO7lJrFGsPsDC2xwpxkVqTesDxkzgMRypO52xjYEmveagn1xxBHg==", "dependencies": { "@smithy/core": "^1.2.2", "@smithy/protocol-http": "^3.0.12", @@ -1382,12 +1382,12 @@ } }, "node_modules/@aws-sdk/credential-provider-cognito-identity": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.489.0.tgz", - "integrity": "sha512-OlcNLOeIq4kdMAJNhM/FuiolTUf4B6e/kb4LJW4vzcI8/Il55xfLGihuGJHIgbKi4HW5RSvMXCVAoUAALQAsiQ==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-cognito-identity/-/credential-provider-cognito-identity-3.490.0.tgz", + "integrity": "sha512-tm07p+jladfKJYFhFqQjT8PC3mM0zagVud/NnYx6w/MB7pHPrixhCRoG1hK+ckAjnUAUVP2uuGXhTVkTfrkTXg==", "dev": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.489.0", + "@aws-sdk/client-cognito-identity": "3.490.0", "@aws-sdk/types": "3.489.0", "@smithy/property-provider": "^2.0.0", "@smithy/types": "^2.8.0", @@ -1432,13 +1432,13 @@ } }, "node_modules/@aws-sdk/credential-provider-ini": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.489.0.tgz", - "integrity": "sha512-lB5yufriHMzraQaAlsVKgzXKLGhRHt+ybgcVD+SIegw0QwabWL2va8h1KuRUGqEOUFH6BNTCx9HnI+uH5EadVA==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.490.0.tgz", + "integrity": "sha512-7m63zyCpVqj9FsoDxWMWWRvL6c7zZzOcXYkHZmHujVVlmAXH0RT/vkXFkYgt+Ku+ov+v5NQrzwO5TmVoRt6O8g==", "dependencies": { "@aws-sdk/credential-provider-env": "3.489.0", "@aws-sdk/credential-provider-process": "3.489.0", - "@aws-sdk/credential-provider-sso": "3.489.0", + "@aws-sdk/credential-provider-sso": "3.490.0", "@aws-sdk/credential-provider-web-identity": "3.489.0", "@aws-sdk/types": "3.489.0", "@smithy/credential-provider-imds": "^2.0.0", @@ -1452,14 +1452,14 @@ } }, "node_modules/@aws-sdk/credential-provider-node": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.489.0.tgz", - "integrity": "sha512-HXjYjG5oqQflLOSkxjDTfWOeE5UX3CvPhcvexZLen8TWyI7azIT81PjFVLq5CJdnFaoeVRxvhp/DIgL7RrNivw==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-node/-/credential-provider-node-3.490.0.tgz", + "integrity": "sha512-Gh33u2O5Xbout8G3z/Z5H/CZzdG1ophxf/XS3iMFxA1cazQ7swY1UMmGvB7Lm7upvax5anXouItD1Ph3gzKc4w==", "dependencies": { "@aws-sdk/credential-provider-env": "3.489.0", - "@aws-sdk/credential-provider-ini": "3.489.0", + "@aws-sdk/credential-provider-ini": "3.490.0", "@aws-sdk/credential-provider-process": "3.489.0", - "@aws-sdk/credential-provider-sso": "3.489.0", + "@aws-sdk/credential-provider-sso": "3.490.0", "@aws-sdk/credential-provider-web-identity": "3.489.0", "@aws-sdk/types": "3.489.0", "@smithy/credential-provider-imds": "^2.0.0", @@ -1488,11 +1488,11 @@ } }, "node_modules/@aws-sdk/credential-provider-sso": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.489.0.tgz", - "integrity": "sha512-tN+7q7xKA4VZmVSMolStvBd8UeHf43kt3TR/tTfqaSvOQR1hKUrDyVgg2rTdyXWxyQPy1O3rtwMKPsorhc/BTA==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.490.0.tgz", + "integrity": "sha512-3UUBUoPbFvT58IhS4Vb23omYj/QPNkjgxu9p9ruQ3KSjLkanI4w8t/l/jljA65q83P7CoLnM5UKG9L7RA8/V1Q==", "dependencies": { - "@aws-sdk/client-sso": "3.489.0", + "@aws-sdk/client-sso": "3.490.0", "@aws-sdk/token-providers": "3.489.0", "@aws-sdk/types": "3.489.0", "@smithy/property-provider": "^2.0.0", @@ -1519,21 +1519,21 @@ } }, "node_modules/@aws-sdk/credential-providers": { - "version": "3.489.0", - "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.489.0.tgz", - "integrity": "sha512-18u+qDG4R5YuGORgTZfVTxSTFbNL9UgTNdihJXCxlVeJzK3LcboilMabig9fN0pEJs8BryRocBzQm/pZyem8gw==", + "version": "3.490.0", + "resolved": "https://registry.npmjs.org/@aws-sdk/credential-providers/-/credential-providers-3.490.0.tgz", + "integrity": "sha512-b66SfI3A2H5qVKYkuaYtnNmHApcj2Vju6wRWDr+nZX2iVqBcpCFIs6jMBY0QWmwn+xhlVvAX9tI4AoqGumzKWg==", "dev": true, "dependencies": { - "@aws-sdk/client-cognito-identity": "3.489.0", - "@aws-sdk/client-sso": "3.489.0", - "@aws-sdk/client-sts": "3.489.0", - "@aws-sdk/credential-provider-cognito-identity": "3.489.0", + "@aws-sdk/client-cognito-identity": "3.490.0", + "@aws-sdk/client-sso": "3.490.0", + "@aws-sdk/client-sts": "3.490.0", + "@aws-sdk/credential-provider-cognito-identity": "3.490.0", "@aws-sdk/credential-provider-env": "3.489.0", "@aws-sdk/credential-provider-http": "3.489.0", - "@aws-sdk/credential-provider-ini": "3.489.0", - "@aws-sdk/credential-provider-node": "3.489.0", + "@aws-sdk/credential-provider-ini": "3.490.0", + "@aws-sdk/credential-provider-node": "3.490.0", "@aws-sdk/credential-provider-process": "3.489.0", - "@aws-sdk/credential-provider-sso": "3.489.0", + "@aws-sdk/credential-provider-sso": "3.490.0", "@aws-sdk/credential-provider-web-identity": "3.489.0", "@aws-sdk/types": "3.489.0", "@smithy/credential-provider-imds": "^2.0.0", From 58c7e3e6f5114cabf8c285b3e6c3a0915d7ac1da Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Tue, 16 Jan 2024 16:36:44 +0100 Subject: [PATCH 2/8] refactor: fix database test Signed-off-by: Jeroen Branje --- apps/envited.ascs.digital/common/database/database.test.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/apps/envited.ascs.digital/common/database/database.test.ts b/apps/envited.ascs.digital/common/database/database.test.ts index a7f6e53f..3ed9abba 100644 --- a/apps/envited.ascs.digital/common/database/database.test.ts +++ b/apps/envited.ascs.digital/common/database/database.test.ts @@ -1,4 +1,5 @@ import { initDb } from './database' +import * as schema from './schema' describe('common/database', () => { describe('database', () => { @@ -26,7 +27,7 @@ describe('common/database', () => { const db = await initDb({ drizzle, postgres, getSecret })() expect(postgres).toHaveBeenCalledWith(config) - expect(drizzle).toHaveBeenCalledWith(postgres(config)) + expect(drizzle).toHaveBeenCalledWith(postgres(config), { schema }) expect(getSecret).not.toHaveBeenCalled() expect(db).toEqual('DB_CONNECTION') }) @@ -59,7 +60,7 @@ describe('common/database', () => { const db = await initDb({ drizzle, postgres, getSecret })() expect(postgres).toHaveBeenCalledWith(config) - expect(drizzle).toHaveBeenCalledWith(postgres(config)) + expect(drizzle).toHaveBeenCalledWith(postgres(config), { schema }) expect(getSecret).toHaveBeenCalled() expect(db).toEqual('DB_CONNECTION') }) From 5c72c317bc64513b87632ea69210f00ea92fd904 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Wed, 17 Jan 2024 12:45:15 +0100 Subject: [PATCH 3/8] refactor: test with rollback and fixes Signed-off-by: Jeroen Branje --- .../app/api/user/route.ts | 6 ++-- .../common/database/database.ts | 2 +- .../common/database/queries/users.test.ts | 32 +++++++++++++++++++ .../common/database/queries/users.ts | 1 - .../common/database/types.ts | 2 +- 5 files changed, 38 insertions(+), 5 deletions(-) diff --git a/apps/envited.ascs.digital/app/api/user/route.ts b/apps/envited.ascs.digital/app/api/user/route.ts index d87a45cb..d1560bbd 100644 --- a/apps/envited.ascs.digital/app/api/user/route.ts +++ b/apps/envited.ascs.digital/app/api/user/route.ts @@ -1,5 +1,5 @@ import { db } from '../../../common/database/queries' -import { ok } from '../../../common/utils' +import { internalServerError, ok } from '../../../common/utils' export async function POST(request: Request) { try { @@ -7,10 +7,12 @@ export async function POST(request: Request) { const connection = await db() const newUser = await connection.insertUserTx(credential) + return ok(newUser) } catch (error) { console.log('error', error) - return Response.json(error) + + return internalServerError() } } diff --git a/apps/envited.ascs.digital/common/database/database.ts b/apps/envited.ascs.digital/common/database/database.ts index 121a3e35..580f16e2 100644 --- a/apps/envited.ascs.digital/common/database/database.ts +++ b/apps/envited.ascs.digital/common/database/database.ts @@ -32,7 +32,7 @@ export const initDb = config = { host, port, - database: dbname as string, + database: dbname, username, password, max: 1, diff --git a/apps/envited.ascs.digital/common/database/queries/users.test.ts b/apps/envited.ascs.digital/common/database/queries/users.test.ts index 5a8a15d6..6faab7ef 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.test.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.test.ts @@ -187,6 +187,38 @@ describe('common/database/users', () => { }) }) + it('should rollback a user with all the relational', async () => { + // when ... we want to insert and get an rejected user + // then ... we should get the rollback as expected + const dependencies = { + insertAddressTypeTx: () => jest.fn().mockResolvedValue([{ id: 'ADDRESS_TYPE_ID' }]), + insertIssuerTx: () => jest.fn().mockResolvedValue([{ id: 'ISSUER_ID' }]), + insertUsersToRolesTx: () => jest.fn().mockResolvedValue([{ id: 'ISSUER_ID' }]), + insertCredentialTypeTx: () => jest.fn().mockResolvedValue([{ id: 'CREDENTIAL_TYPE_ID' }]), + } as any + + const tx = { + insert: jest.fn().mockReturnValue({ + values: jest.fn().mockReturnValue({ + onConflictDoNothing: jest.fn().mockReturnValue({ + returning: jest.fn().mockResolvedValue({}), + }), + returning: jest.fn().mockRejectedValue('ERROR'), + }), + }), + select: jest.fn().mockReturnValue({ + from: jest.fn().mockReturnValue({ + where: jest.fn().mockReturnValue([{ id: 'ROLE_ID' }]), + }), + }), + rollback: jest.fn().mockResolvedValue({}), + } + + await _txn(dependencies)(USER_CREDENTIAL)(tx as any) + + expect(tx.rollback).toHaveBeenCalled() + }) + describe('_insertUserTx', () => { it('should insert a user with all the relational connections', async () => { // when ... we want to connect a user to a role diff --git a/apps/envited.ascs.digital/common/database/queries/users.ts b/apps/envited.ascs.digital/common/database/queries/users.ts index 762ce518..56b41b96 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.ts @@ -168,7 +168,6 @@ export const _txn = } catch (error) { console.log(error) tx.rollback() - throw error } } diff --git a/apps/envited.ascs.digital/common/database/types.ts b/apps/envited.ascs.digital/common/database/types.ts index 4f447ac8..efb360ff 100644 --- a/apps/envited.ascs.digital/common/database/types.ts +++ b/apps/envited.ascs.digital/common/database/types.ts @@ -1,7 +1,7 @@ +import { StringChange } from '@nx/devkit' import { PostgresJsDatabase } from 'drizzle-orm/postgres-js' import * as schema from './schema' -import { StringChange } from '@nx/devkit' export type DatabaseConnection = PostgresJsDatabase From b373388fcfcb5085a1c8d49c23d1a1d3c4710244 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Wed, 17 Jan 2024 13:29:44 +0100 Subject: [PATCH 4/8] refactor: _txn test with called with object Signed-off-by: Jeroen Branje --- .../common/database/queries/users.test.ts | 26 ++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/apps/envited.ascs.digital/common/database/queries/users.test.ts b/apps/envited.ascs.digital/common/database/queries/users.test.ts index 6faab7ef..b592184a 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.test.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.test.ts @@ -150,6 +150,10 @@ describe('common/database/users', () => { }) }) + beforeAll(() => { + jest.useFakeTimers('modern') + jest.setSystemTime(new Date(2020, 3, 1)) + }); describe('txn', () => { it('should insert a user with all the relational', async () => { // when ... we want to connect a user to a role @@ -180,7 +184,27 @@ describe('common/database/users', () => { const transaction = await _txn(dependencies)(USER_CREDENTIAL)(tx as any) - expect(tx.insert().values).toHaveBeenCalled() + expect(tx.insert().values).toHaveBeenCalledWith({ + addressCountry: 'DE', + addressLocality: 'Munich', + addressTypeId: 'ADDRESS_TYPE_ID', + articlesOfAssociationAccepted: 'articlesOfAssociation', + contributionRulesAccepted: 'contributionRules', + createdAt: new Date(), + email: 'email', + expirationDate: new Date('2102-09-15T17:14:33.000Z'), + id: 'did:pkh:tz:tz1SfdVU1mor3Sgej3FmmwMH4HM1EjTzqqeE', + isAscsMember: true, + isEnvitedMember: true, + issuanceDate: new Date('2023-11-22T17:14:33.000Z'), + issuerId: 'ISSUER_ID', + name: 'User', + postalCode: '12345', + privacyPolicyAccepted: 'https://media.ascs.digital/terms/ascs_privacy_policy_2020-07-08.pdf#SHA-256', + streetAddress: 'Teststraße 1', + updatedAt: new Date(), + vatId: 'vatId', + }) expect(transaction).toEqual({ id: 'USER_ID', }) From 5d928f524c4cc4d24d9ae6fa325d2199d9136346 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Thu, 18 Jan 2024 10:46:49 +0100 Subject: [PATCH 5/8] refactor: use drizzle upsert Signed-off-by: Jeroen Branje --- .../common/database/queries/users.test.ts | 28 +++++--- .../common/database/queries/users.ts | 15 ++-- .../common/fixtures/invalidUserCredential.ts | 69 +++++++++++++++++++ 3 files changed, 90 insertions(+), 22 deletions(-) create mode 100644 apps/envited.ascs.digital/common/fixtures/invalidUserCredential.ts diff --git a/apps/envited.ascs.digital/common/database/queries/users.test.ts b/apps/envited.ascs.digital/common/database/queries/users.test.ts index b592184a..c5305818 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.test.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.test.ts @@ -18,7 +18,7 @@ describe('common/database/users', () => { const tx = { insert: jest.fn().mockReturnValue({ values: jest.fn().mockReturnValue({ - onConflictDoNothing: jest.fn().mockReturnValue({ + onConflictDoUpdate: jest.fn().mockReturnValue({ returning: jest.fn().mockResolvedValue([{ id: 'ADDRESS_TYPE_ID' }]), }), }), @@ -36,8 +36,11 @@ describe('common/database/users', () => { expect(tx.insert).toHaveBeenCalledWith(addressType) expect(tx.insert().values).toHaveBeenCalledWith({ name: 'ADDRESS_TYPE' }) - expect(tx.insert().values().onConflictDoNothing).toHaveBeenCalledWith() - expect(tx.insert().values().onConflictDoNothing().returning).toHaveBeenCalledWith() + expect(tx.insert().values().onConflictDoUpdate).toHaveBeenCalledWith({ + target: addressType.name, + set: { name: 'ADDRESS_TYPE' }, + }) + expect(tx.insert().values().onConflictDoUpdate().returning).toHaveBeenCalledWith() expect(result).toEqual([{ id: 'ADDRESS_TYPE_ID' }]) }) }) @@ -88,9 +91,9 @@ describe('common/database/users', () => { const tx = { insert: jest.fn().mockReturnValue({ values: jest.fn().mockReturnValue({ - onConflictDoNothing: jest.fn().mockReturnValue({ + onConflictDoUpdate: jest.fn().mockReturnValue({ returning: jest.fn().mockResolvedValue({ - id: 'ID', + id: 'ISSUER_ID', type: 'TYPE', name: 'NAME', url: 'URL', @@ -102,7 +105,7 @@ describe('common/database/users', () => { } as any const result = await insertIssuerTx(tx)({ - id: 'ID', + id: 'ISSUER_ID', type: 'TYPE', name: 'NAME', url: 'URL', @@ -110,15 +113,18 @@ describe('common/database/users', () => { expect(tx.insert).toHaveBeenCalledWith(issuer) expect(tx.insert().values).toHaveBeenCalledWith({ - id: 'ID', + id: 'ISSUER_ID', type: 'TYPE', name: 'NAME', url: 'URL', }) - expect(tx.insert().values().onConflictDoNothing).toHaveBeenCalledWith() - expect(tx.insert().values().onConflictDoNothing().returning).toHaveBeenCalledWith() + expect(tx.insert().values().onConflictDoUpdate).toHaveBeenCalledWith({ + target: issuer.id, + set: { id: 'ISSUER_ID' }, + }) + expect(tx.insert().values().onConflictDoUpdate().returning).toHaveBeenCalledWith() expect(result).toEqual({ - id: 'ID', + id: 'ISSUER_ID', type: 'TYPE', name: 'NAME', url: 'URL', @@ -153,7 +159,7 @@ describe('common/database/users', () => { beforeAll(() => { jest.useFakeTimers('modern') jest.setSystemTime(new Date(2020, 3, 1)) - }); + }) describe('txn', () => { it('should insert a user with all the relational', async () => { // when ... we want to connect a user to a role diff --git a/apps/envited.ascs.digital/common/database/queries/users.ts b/apps/envited.ascs.digital/common/database/queries/users.ts index 56b41b96..a1de05d8 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.ts @@ -26,27 +26,20 @@ export const insertIssuerTx = url: newIssuer.url, type: newIssuer.type, }) - .onConflictDoNothing() + .onConflictDoUpdate({ target: issuer.id, set: { id: newIssuer.id } }) .returning() export const insertAddressTypeTx = (tx: PgTransaction>) => - async (type: string) => { - let result = await tx + async (type: string) => + await tx .insert(addressType) .values({ name: type, }) - .onConflictDoNothing() + .onConflictDoUpdate({ target: addressType.name, set: { name: type } }) .returning() - if (isEmpty(result)) { - result = await tx.select().from(addressType).where(eq(addressType.name, type)).limit(1) - } - - return result - } - export const insertCredentialTypeTx = (tx: PgTransaction>) => async ({ userId, type }: { userId: string; type: string }) => { diff --git a/apps/envited.ascs.digital/common/fixtures/invalidUserCredential.ts b/apps/envited.ascs.digital/common/fixtures/invalidUserCredential.ts new file mode 100644 index 00000000..1c2aac08 --- /dev/null +++ b/apps/envited.ascs.digital/common/fixtures/invalidUserCredential.ts @@ -0,0 +1,69 @@ +export const INVALID_USER_CREDENTIAL = { + '@context': [ + 'https://www.w3.org/2018/credentials/v1', + { + '@version': 1.1, + '@protected': true, + 'AscsUserCredential': 'https://schema.ascs.digital/AscsUserCredential/v1#', + 'AscsIssuer': { + '@id': 'https://schema.ascs.digital/AscsUserCredential/v1#AscsIssuer', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'url': 'https://schema.org/url', + }, + }, + 'AscsUser': { + '@id': 'https://schema.ascs.digital/AscsUserCredential/v1#AscsUser', + '@context': { + '@version': 1.1, + '@protected': true, + 'name': 'https://schema.org/name', + 'email': 'https://schema.org/email', + 'address': 'http://schema.org/address', + 'isAscsMember': 'http://schema.org/Boolean', + 'isEnvitedMember': 'http://schema.org/Boolean', + 'privacyPolicy': 'https://schema.org/termsOfService', + }, + }, + 'PostalAddress': { + '@id': 'http://schema.org/PostalAddress', + '@context': { + '@version': 1.1, + '@protected': true, + 'streetAddress': 'http://schema.org/streetAddress', + 'postalCode': 'http://schema.org/postalCode', + 'addressLocality': 'http://schema.org/addressLocality', + 'addressCountry': 'https://schema.org/addressCountry', + }, + }, + }, + ], + 'type': ['VerifiableCredential', 'AscsUserCredential'], + 'issuanceDate': '2023-11-22T17:14:33Z', + 'expirationDate': '2102-09-15T17:14:33Z', + 'id': 'urn:uuid:cf1f329d-9c4c-458e-ba0a-a762a296b79c', + 'issuer': { + id: 'did:pkh:tz:tz1bfenLGF9XFdvy4FCLqsFLueqw6XuDFYCo', + type: 'AscsIssuer', + name: 'Invalid GmbH', + url: 'https://test.de/', + }, + 'credentialSubject': { + id: 'did:pkh:tz:tz1SfdVU1mor3Sgej3FmmwMH4HM1EjTzqqeE', + type: 'AscsUser', + name: 'User', + email: 'mailto:user@test.de', + address: { + type: 'PostalAddress', + streetAddress: 'Teststraße 1', + postalCode: '12345', + addressLocality: 'Munich', + addressCountry: 'DE', + }, + isAscsMember: true, + isEnvitedMember: true, + privacyPolicy: 'https://media.ascs.digital/terms/ascs_privacy_policy_2020-07-08.pdf#SHA-256', + }, +} From 8697ec5be8ccfb17ab3371fdd0edb20072d324d5 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Thu, 18 Jan 2024 11:05:04 +0100 Subject: [PATCH 6/8] refactor: insertIssuerTx Signed-off-by: Jeroen Branje --- apps/envited.ascs.digital/common/database/queries/users.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/apps/envited.ascs.digital/common/database/queries/users.ts b/apps/envited.ascs.digital/common/database/queries/users.ts index a1de05d8..b23b73d3 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.ts @@ -21,10 +21,6 @@ export const insertIssuerTx = .insert(issuer) .values({ ...newIssuer, - id: newIssuer.id, - name: newIssuer.name, - url: newIssuer.url, - type: newIssuer.type, }) .onConflictDoUpdate({ target: issuer.id, set: { id: newIssuer.id } }) .returning() From de1bb7c90bc2c3f4898576da12b802c6cecae9e6 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Thu, 18 Jan 2024 11:38:10 +0100 Subject: [PATCH 7/8] feat: add db generate staging Signed-off-by: Jeroen Branje --- ...ranger.sql => 0000_fantastic_the_fury.sql} | 0 .../development/meta/0000_snapshot.json | 68 +++++++++++----- .../drizzle/development/meta/_journal.json | 6 +- ...stalgic_mauler.sql => 0000_sour_brood.sql} | 15 ++-- .../drizzle/staging/meta/0000_snapshot.json | 80 ++++++++++++++----- .../drizzle/staging/meta/_journal.json | 6 +- 6 files changed, 126 insertions(+), 49 deletions(-) rename apps/envited.ascs.digital/drizzle/development/{0000_lean_the_stranger.sql => 0000_fantastic_the_fury.sql} (100%) rename apps/envited.ascs.digital/drizzle/staging/{0000_nostalgic_mauler.sql => 0000_sour_brood.sql} (78%) diff --git a/apps/envited.ascs.digital/drizzle/development/0000_lean_the_stranger.sql b/apps/envited.ascs.digital/drizzle/development/0000_fantastic_the_fury.sql similarity index 100% rename from apps/envited.ascs.digital/drizzle/development/0000_lean_the_stranger.sql rename to apps/envited.ascs.digital/drizzle/development/0000_fantastic_the_fury.sql diff --git a/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json b/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json index 7f3c9e2d..201cb0b6 100644 --- a/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json +++ b/apps/envited.ascs.digital/drizzle/development/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "96c668c6-208a-4336-907b-bd99e7497d16", + "id": "8fbe3d01-c68f-48ae-9a7f-49222e392a4a", "prevId": "00000000-0000-0000-0000-000000000000", "version": "5", "dialect": "pg", @@ -35,7 +35,9 @@ "addressType_name_unique": { "name": "addressType_name_unique", "nullsNotDistinct": false, - "columns": ["name"] + "columns": [ + "name" + ] } } }, @@ -104,7 +106,9 @@ "issuer_id_unique": { "name": "issuer_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -138,7 +142,9 @@ "role_id_unique": { "name": "role_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -268,8 +274,12 @@ "tableFrom": "user", "tableTo": "issuer", "schemaTo": "public", - "columnsFrom": ["issuer_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "issuer_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -278,8 +288,12 @@ "tableFrom": "user", "tableTo": "addressType", "schemaTo": "public", - "columnsFrom": ["address_type_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "address_type_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -289,7 +303,9 @@ "user_id_unique": { "name": "user_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -317,8 +333,12 @@ "tableFrom": "usersToCredentialTypes", "tableTo": "user", "schemaTo": "public", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -327,8 +347,12 @@ "tableFrom": "usersToCredentialTypes", "tableTo": "credentialType", "schemaTo": "public", - "columnsFrom": ["credential_type_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "credential_type_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -360,8 +384,12 @@ "tableFrom": "usersToRoles", "tableTo": "user", "schemaTo": "public", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -370,8 +398,12 @@ "tableFrom": "usersToRoles", "tableTo": "role", "schemaTo": "public", - "columnsFrom": ["role_id"], - "columnsTo": ["id"], + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -387,4 +419,4 @@ "schemas": {}, "tables": {} } -} +} \ No newline at end of file diff --git a/apps/envited.ascs.digital/drizzle/development/meta/_journal.json b/apps/envited.ascs.digital/drizzle/development/meta/_journal.json index 3a7372f9..d3cc0cad 100644 --- a/apps/envited.ascs.digital/drizzle/development/meta/_journal.json +++ b/apps/envited.ascs.digital/drizzle/development/meta/_journal.json @@ -5,9 +5,9 @@ { "idx": 0, "version": "5", - "when": 1705324440754, - "tag": "0000_lean_the_stranger", + "when": 1705574231633, + "tag": "0000_fantastic_the_fury", "breakpoints": true } ] -} +} \ No newline at end of file diff --git a/apps/envited.ascs.digital/drizzle/staging/0000_nostalgic_mauler.sql b/apps/envited.ascs.digital/drizzle/staging/0000_sour_brood.sql similarity index 78% rename from apps/envited.ascs.digital/drizzle/staging/0000_nostalgic_mauler.sql rename to apps/envited.ascs.digital/drizzle/staging/0000_sour_brood.sql index 462399db..b36b43a5 100644 --- a/apps/envited.ascs.digital/drizzle/staging/0000_nostalgic_mauler.sql +++ b/apps/envited.ascs.digital/drizzle/staging/0000_sour_brood.sql @@ -1,7 +1,8 @@ CREATE TABLE IF NOT EXISTS "addressType" ( "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, "name" text, - "description" text + "description" text, + CONSTRAINT "addressType_name_unique" UNIQUE("name") ); --> statement-breakpoint CREATE TABLE IF NOT EXISTS "credentialType" ( @@ -59,37 +60,37 @@ CREATE TABLE IF NOT EXISTS "usersToRoles" ( ); --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "user" ADD CONSTRAINT "user_issuer_id_issuer_id_fk" FOREIGN KEY ("issuer_id") REFERENCES "issuer"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "user" ADD CONSTRAINT "user_issuer_id_issuer_id_fk" FOREIGN KEY ("issuer_id") REFERENCES "public"."issuer"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "user" ADD CONSTRAINT "user_address_type_id_addressType_id_fk" FOREIGN KEY ("address_type_id") REFERENCES "addressType"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "user" ADD CONSTRAINT "user_address_type_id_addressType_id_fk" FOREIGN KEY ("address_type_id") REFERENCES "public"."addressType"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_credential_type_id_credentialType_id_fk" FOREIGN KEY ("credential_type_id") REFERENCES "credentialType"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToCredentialTypes" ADD CONSTRAINT "usersToCredentialTypes_credential_type_id_credentialType_id_fk" FOREIGN KEY ("credential_type_id") REFERENCES "public"."credentialType"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "user"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; --> statement-breakpoint DO $$ BEGIN - ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "role"("id") ON DELETE no action ON UPDATE no action; + ALTER TABLE "usersToRoles" ADD CONSTRAINT "usersToRoles_role_id_role_id_fk" FOREIGN KEY ("role_id") REFERENCES "public"."role"("id") ON DELETE no action ON UPDATE no action; EXCEPTION WHEN duplicate_object THEN null; END $$; diff --git a/apps/envited.ascs.digital/drizzle/staging/meta/0000_snapshot.json b/apps/envited.ascs.digital/drizzle/staging/meta/0000_snapshot.json index ce9c4c88..77d24f03 100644 --- a/apps/envited.ascs.digital/drizzle/staging/meta/0000_snapshot.json +++ b/apps/envited.ascs.digital/drizzle/staging/meta/0000_snapshot.json @@ -1,5 +1,5 @@ { - "id": "9a29c149-759a-4275-b107-d83917a4a9ab", + "id": "0f18334e-00d5-49b3-a2e4-c4f1e330f663", "prevId": "00000000-0000-0000-0000-000000000000", "version": "5", "dialect": "pg", @@ -31,7 +31,15 @@ "indexes": {}, "foreignKeys": {}, "compositePrimaryKeys": {}, - "uniqueConstraints": {} + "uniqueConstraints": { + "addressType_name_unique": { + "name": "addressType_name_unique", + "nullsNotDistinct": false, + "columns": [ + "name" + ] + } + } }, "credentialType": { "name": "credentialType", @@ -98,7 +106,9 @@ "issuer_id_unique": { "name": "issuer_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -132,7 +142,9 @@ "role_id_unique": { "name": "role_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -261,8 +273,13 @@ "name": "user_issuer_id_issuer_id_fk", "tableFrom": "user", "tableTo": "issuer", - "columnsFrom": ["issuer_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "issuer_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -270,8 +287,13 @@ "name": "user_address_type_id_addressType_id_fk", "tableFrom": "user", "tableTo": "addressType", - "columnsFrom": ["address_type_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "address_type_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -281,7 +303,9 @@ "user_id_unique": { "name": "user_id_unique", "nullsNotDistinct": false, - "columns": ["id"] + "columns": [ + "id" + ] } } }, @@ -308,8 +332,13 @@ "name": "usersToCredentialTypes_user_id_user_id_fk", "tableFrom": "usersToCredentialTypes", "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -317,8 +346,13 @@ "name": "usersToCredentialTypes_credential_type_id_credentialType_id_fk", "tableFrom": "usersToCredentialTypes", "tableTo": "credentialType", - "columnsFrom": ["credential_type_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "credential_type_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -349,8 +383,13 @@ "name": "usersToRoles_user_id_user_id_fk", "tableFrom": "usersToRoles", "tableTo": "user", - "columnsFrom": ["user_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" }, @@ -358,8 +397,13 @@ "name": "usersToRoles_role_id_role_id_fk", "tableFrom": "usersToRoles", "tableTo": "role", - "columnsFrom": ["role_id"], - "columnsTo": ["id"], + "schemaTo": "public", + "columnsFrom": [ + "role_id" + ], + "columnsTo": [ + "id" + ], "onDelete": "no action", "onUpdate": "no action" } @@ -375,4 +419,4 @@ "schemas": {}, "tables": {} } -} +} \ No newline at end of file diff --git a/apps/envited.ascs.digital/drizzle/staging/meta/_journal.json b/apps/envited.ascs.digital/drizzle/staging/meta/_journal.json index 657a65fe..d10c5148 100644 --- a/apps/envited.ascs.digital/drizzle/staging/meta/_journal.json +++ b/apps/envited.ascs.digital/drizzle/staging/meta/_journal.json @@ -5,9 +5,9 @@ { "idx": 0, "version": "5", - "when": 1704723156913, - "tag": "0000_nostalgic_mauler", + "when": 1705574199077, + "tag": "0000_sour_brood", "breakpoints": true } ] -} +} \ No newline at end of file From 3eb870694dfea6781401857e4b443f628eba2497 Mon Sep 17 00:00:00 2001 From: Jeroen Branje Date: Thu, 18 Jan 2024 11:50:57 +0100 Subject: [PATCH 8/8] refactor: change rollback test Signed-off-by: Jeroen Branje --- apps/envited.ascs.digital/common/database/queries/users.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/envited.ascs.digital/common/database/queries/users.test.ts b/apps/envited.ascs.digital/common/database/queries/users.test.ts index c5305818..79221c58 100644 --- a/apps/envited.ascs.digital/common/database/queries/users.test.ts +++ b/apps/envited.ascs.digital/common/database/queries/users.test.ts @@ -246,7 +246,7 @@ describe('common/database/users', () => { await _txn(dependencies)(USER_CREDENTIAL)(tx as any) - expect(tx.rollback).toHaveBeenCalled() + expect(tx.rollback).toHaveBeenCalledWith() }) describe('_insertUserTx', () => {