Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

7 add user to db with API endpoint #53

Merged
merged 10 commits into from
Jan 18, 2024
17 changes: 17 additions & 0 deletions apps/envited.ascs.digital/app/api/user/route.ts
Original file line number Diff line number Diff line change
@@ -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)
jeroenbranje marked this conversation as resolved.
Show resolved Hide resolved
}
}

export const dynamic = 'force-dynamic'
5 changes: 3 additions & 2 deletions apps/envited.ascs.digital/common/database/database.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { initDb } from './database'
import * as schema from './schema'

describe('common/database', () => {
describe('database', () => {
Expand Down Expand Up @@ -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')
})
Expand Down Expand Up @@ -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')
})
Expand Down
9 changes: 5 additions & 4 deletions apps/envited.ascs.digital/common/database/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,18 @@ import { equals } from 'ramda'

import { ERRORS } from '../constants'
import { getSecret } from '../secretsManager'
import * as schema from './schema'

export const initDb =
({
drizzle,
postgres,
getSecret,
}: {
drizzle: (client: postgres.Sql) => PostgresJsDatabase
drizzle: (client: postgres.Sql, config: any) => PostgresJsDatabase<typeof schema>
postgres: (options: postgres.Options<any>) => postgres.Sql
getSecret: (secretId: string) => Promise<Record<string, any>>
}): (() => Promise<PostgresJsDatabase>) =>
}): (() => Promise<PostgresJsDatabase<typeof schema>>) =>
async () => {
let config = {
host: process.env.POSTGRES_HOST || 'localhost', // Postgres ip address[s] or domain name[s]
Expand All @@ -31,7 +32,7 @@ export const initDb =
config = {
host,
port,
database: dbname,
database: dbname as string,
jeroenbranje marked this conversation as resolved.
Show resolved Hide resolved
username,
password,
max: 1,
Expand All @@ -41,7 +42,7 @@ export const initDb =
}
}

return drizzle(postgres(config))
return drizzle(postgres(config), { schema })
}

export const connectDb = initDb({ drizzle, postgres, getSecret })
2 changes: 1 addition & 1 deletion apps/envited.ascs.digital/common/database/migrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<typeof schema>, { migrationsFolder: `../drizzle/${process.env.ENV}` })
return
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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<typeof schema>) => async () =>
db.execute(sql`select * from information_schema.tables;`)
16 changes: 13 additions & 3 deletions apps/envited.ascs.digital/common/database/queries/queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<PostgresJsDatabase>) =>
(queries: Record<string, (db: PostgresJsDatabase) => () => Promise<postgres.RowList<Record<string, unknown>[]>>>) =>
(connectDb: () => Promise<PostgresJsDatabase<typeof schema>>) =>
(
queries: Record<
string,
(
db: PostgresJsDatabase<typeof schema>,
) => (...args: any[]) => Promise<postgres.RowList<Record<string, unknown>[]> | 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<typeof schema>) => any]): [any, any] => [
key,
value(connection),
]),
Expand Down
206 changes: 206 additions & 0 deletions apps/envited.ascs.digital/common/database/queries/users.test.ts
Original file line number Diff line number Diff line change
@@ -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 () => {
jeroenbranje marked this conversation as resolved.
Show resolved Hide resolved
// 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()
jeroenbranje marked this conversation as resolved.
Show resolved Hide resolved
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))
})
})
})
Loading