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

feat(envited.ascs.digital): stuctured logger #81

Merged
merged 2 commits into from
Feb 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions apps/envited.ascs.digital/common/constants/errors.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,16 @@
export const ERRORS = {
CANNOT_CONNECT_TO_DATABASE: 'Cannot connect to database',
UNAUTHORIZED: 'Unauthorized',
BAD_REQUEST: 'Bad Request',
FORBIDDEN: 'Forbidden',
INTERNAL_SERVER_ERROR: 'Internal Server Error',
NOT_FOUND: 'Not found',
}

export const ERROR_CODES = {
BAD_REQUEST: 400,
UNAUTHORIZED: 401,
FORBIDDEN: 403,
NOT_FOUND: 404,
INTERNAL_SERVER_ERROR: 500,
}
2 changes: 2 additions & 0 deletions apps/envited.ascs.digital/common/logger/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { log } from './logger'
export type { Log } from './logger'
11 changes: 11 additions & 0 deletions apps/envited.ascs.digital/common/logger/logger.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export interface Log {
info: typeof console.info
warn: typeof console.warn
error: typeof console.error
}

export const log = {
info: console.info,
warn: console.warn,
error: console.error,
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ERRORS } from '../../constants'
import { Role } from '../../types'
import * as SUT from './get'

Expand Down Expand Up @@ -32,10 +33,13 @@ describe('serverActions/profiles/get', () => {
},
]),
})
const logStub = {
error: jest.fn(),
} as any

const slug = 'PROFILE_SLUG'

const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub })(slug)
const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(slug)
const db = await dbStub()
expect(result).toEqual({
name: 'USER_PRINCIPAL_NAME',
Expand Down Expand Up @@ -77,10 +81,13 @@ describe('serverActions/profiles/get', () => {
},
]),
})
const logStub = {
error: jest.fn(),
} as any

const slug = 'PROFILE_SLUG'

const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub })(slug)
const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(slug)
const db = await dbStub()
expect(result).toEqual({
name: 'USER_PRINCIPAL_NAME',
Expand Down Expand Up @@ -115,10 +122,13 @@ describe('serverActions/profiles/get', () => {
},
]),
})
const logStub = {
error: jest.fn(),
} as any

const slug = 'PROFILE_SLUG'

const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub })(slug)
const result = await SUT._get({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(slug)
const db = await dbStub()
expect(result).toEqual({
name: 'USER_NAME',
Expand All @@ -140,15 +150,20 @@ describe('serverActions/profiles/get', () => {
},
},
])
const logStub = {
error: jest.fn(),
} as any

const dbStub = jest.fn().mockResolvedValue({})

const slug = ''

expect.assertions(3)
await expect(() => SUT._get({ db: dbStub, getServerSession: getServerSessionStub })(slug)).rejects.toThrow()
await expect(
SUT._get({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(slug),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(getServerSessionStub).not.toHaveBeenCalledWith()
expect(dbStub).not.toHaveBeenCalled()
expect(logStub.error).toHaveBeenCalledWith({ message: 'Missing slug', name: 'BadRequestError' })
})
})

Expand All @@ -161,16 +176,22 @@ describe('serverActions/profiles/get', () => {
role: Role.principal,
},
})
const logStub = {
error: jest.fn(),
} as any

const dbStub = jest.fn().mockResolvedValue({
getProfileBySlug: jest.fn().mockResolvedValue(null),
getProfileBySlug: jest.fn().mockResolvedValue([]),
})

const slug = 'NON_EXISTANT_PROFILE_SLUG'
const db = await dbStub()
await expect(() => SUT._get({ db: dbStub, getServerSession: getServerSessionStub })(slug)).rejects.toThrow()
await expect(SUT._get({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(slug)).rejects.toThrow(
ERRORS.INTERNAL_SERVER_ERROR,
)
expect(getServerSessionStub).toHaveBeenCalledWith()
expect(dbStub).toHaveBeenCalledWith()
expect(db.getProfileBySlug).toHaveBeenCalledWith(slug)
expect(logStub.error).toHaveBeenCalledWith({ message: 'Not found', name: 'NotFoundError' })
})
})
17 changes: 9 additions & 8 deletions apps/envited.ascs.digital/common/serverActions/profiles/get.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,23 +7,24 @@ import { RESTRICTED_PROFILE_FIELDS } from '../../constants'
import { db } from '../../database/queries'
import { Database } from '../../database/types'
import { isOwnProfile, isUsersCompanyProfile } from '../../guards'
import { Log, log } from '../../logger'
import { Session } from '../../types'
import { badRequestError, error, notFoundError } from '../../utils'
import { badRequestError, formatError, internalServerErrorError, notFoundError } from '../../utils'

export const _get =
({ db, getServerSession }: { db: Database; getServerSession: () => Promise<Session | null> }) =>
({ db, getServerSession, log }: { db: Database; getServerSession: () => Promise<Session | null>; log: Log }) =>
async (slug: string) => {
try {
if (isNil(slug) || isEmpty(slug)) {
throw badRequestError('Missing slug')
throw badRequestError({ resource: 'profiles', resourceId: slug, message: 'Missing slug' })
}

const session = await getServerSession()
const connection = await db()
const [profile] = await connection.getProfileBySlug(slug)

if (isNil(profile) || isEmpty(profile)) {
throw notFoundError()
throw notFoundError({ resource: 'profiles', resourceId: slug, userId: session?.user.id })
}

if (!isNil(session)) {
Expand All @@ -40,10 +41,10 @@ export const _get =
}

return omit(RESTRICTED_PROFILE_FIELDS)(profile)
} catch (e) {
console.log('error', e)
throw error()
} catch (error: unknown) {
log.error(formatError(error))
throw internalServerErrorError()
}
}

export const get = _get({ db, getServerSession })
export const get = _get({ db, getServerSession, log })
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ERRORS } from '../../constants'
import { Role } from '../../types'
import * as SUT from './update'

Expand Down Expand Up @@ -29,8 +30,11 @@ describe('serverActions/profiles/update', () => {
updateProfile: jest.fn().mockResolvedValue([newProfile]),
maybeUpdatePublishedState: jest.fn().mockResolvedValue([newProfile]),
})
const logStub = {
error: jest.fn(),
} as any

const result = await SUT._update({ db: dbStub, getServerSession: getServerSessionStub })(newProfile)
const result = await SUT._update({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(newProfile)
const db = await dbStub()
expect(result).toEqual(newProfile)
expect(db.getUserWithProfileById).toHaveBeenCalledWith('USER_PKH')
Expand Down Expand Up @@ -58,10 +62,14 @@ describe('serverActions/profiles/update', () => {
getUserWithProfileById: jest.fn().mockResolvedValue([user]),
updateProfile: jest.fn().mockResolvedValue(newProfile),
})
const logStub = {
error: jest.fn(),
} as any

await expect(() =>
SUT._update({ db: dbStub, getServerSession: getServerSessionStub })(newProfile),
).rejects.toThrow()
await expect(
SUT._update({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(newProfile),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({ message: 'Unauthorized', name: 'UnauthorizedError' })
})

it('should throw with incorrect role', async () => {
Expand All @@ -88,10 +96,14 @@ describe('serverActions/profiles/update', () => {
getUserWithProfileById: jest.fn().mockResolvedValue([user]),
updateProfile: jest.fn().mockResolvedValue(newProfile),
})
const logStub = {
error: jest.fn(),
} as any

await expect(() =>
SUT._update({ db: dbStub, getServerSession: getServerSessionStub })(newProfile),
).rejects.toThrow()
await expect(
SUT._update({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(newProfile),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({ message: 'Incorrect role', name: 'ForbiddenError' })
})

it('should throw when user is not found', async () => {
Expand All @@ -113,10 +125,14 @@ describe('serverActions/profiles/update', () => {
getUserWithProfileById: jest.fn().mockResolvedValue([user]),
updateProfile: jest.fn().mockResolvedValue(newProfile),
})
const logStub = {
error: jest.fn(),
} as any

await expect(() =>
SUT._update({ db: dbStub, getServerSession: getServerSessionStub })(newProfile),
).rejects.toThrow()
await expect(
SUT._update({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(newProfile),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({ message: 'User not found', name: 'BadRequestError' })
})

it('should throw if user is updating someone elses profile', async () => {
Expand Down Expand Up @@ -144,9 +160,13 @@ describe('serverActions/profiles/update', () => {
getUserWithProfileById: jest.fn().mockResolvedValue([user]),
updateProfile: jest.fn().mockResolvedValue(newProfile),
})
const logStub = {
error: jest.fn(),
} as any

await expect(() =>
SUT._update({ db: dbStub, getServerSession: getServerSessionStub })(newProfile),
).rejects.toThrow()
await expect(
SUT._update({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(newProfile),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({ message: 'Incorrect profile', name: 'ForbiddenError' })
})
})
36 changes: 26 additions & 10 deletions apps/envited.ascs.digital/common/serverActions/profiles/update.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,40 +6,56 @@ import { getServerSession } from '../../auth'
import { db } from '../../database/queries'
import { Database } from '../../database/types'
import { isFederator, isOwnProfile, isPrincipal } from '../../guards'
import { Log, log } from '../../logger'
import { Profile, Session } from '../../types'
import { badRequestError, error, unauthorizedError } from '../../utils'
import { badRequestError, forbiddenError, formatError, internalServerErrorError, unauthorizedError } from '../../utils'

export const _update =
({ db, getServerSession }: { db: Database; getServerSession: () => Promise<Session | null> }) =>
({ db, getServerSession, log }: { db: Database; getServerSession: () => Promise<Session | null>; log: Log }) =>
async (profile: Partial<Profile>) => {
try {
const session = await getServerSession()
if (isNil(session)) {
throw unauthorizedError()
throw unauthorizedError({ resource: 'profiles', resourceId: profile.id })
}

if (!isFederator(session) && !isPrincipal(session)) {
throw badRequestError('Incorrect role')
throw forbiddenError({
resource: 'profiles',
resourceId: profile.id,
message: 'Incorrect role',
userId: session.user.id,
})
}

const connection = await db()
const [user] = await connection.getUserWithProfileById(session.user.id)

if (isNil(user)) {
throw badRequestError('User not found')
throw badRequestError({
resource: 'profiles',
resourceId: profile.id,
message: 'User not found',
userId: session.user.id,
})
}

if (!isOwnProfile(user)(profile)) {
throw badRequestError('Incorrect profile')
throw forbiddenError({
resource: 'profiles',
resourceId: profile.id,
message: 'Incorrect profile',
userId: session.user.id,
})
}

const [updatedProfile] = await connection.updateProfile(profile)
const [result] = await connection.maybeUpdatePublishedState(updatedProfile)
return result
} catch (e) {
console.log('error', e)
throw error()
} catch (error: unknown) {
log.error(formatError(error))
throw internalServerErrorError()
}
}

export const update = _update({ db, getServerSession })
export const update = _update({ db, getServerSession, log })
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { ERRORS } from '../../constants'
import * as SUT from './deleteUserById'

describe('common/serverActions/users/deleteUserById', () => {
Expand All @@ -21,8 +22,13 @@ describe('common/serverActions/users/deleteUserById', () => {
getUserById: jest.fn().mockResolvedValue([user]),
deleteUserById: jest.fn().mockResolvedValue([{ updatedId: 'USER_PKH' }]),
})
const logStub = {
error: jest.fn(),
} as any

const result = await SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID')
const result = await SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })(
'USER_ID',
)
expect(result).toEqual(expected)
})

Expand All @@ -38,10 +44,14 @@ describe('common/serverActions/users/deleteUserById', () => {
getUserById: jest.fn().mockResolvedValue([user]),
deleteUserById: jest.fn().mockResolvedValue([{ updatedId: 'USER_PKH' }]),
})
const logStub = {
error: jest.fn(),
} as any

await expect(
SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID'),
).rejects.toThrow('Something went wrong')
SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })('USER_ID'),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({ message: 'Unauthorized', name: 'UnauthorizedError' })
})

it('should throw because requester is not allowed to get this resource', async () => {
Expand All @@ -60,9 +70,16 @@ describe('common/serverActions/users/deleteUserById', () => {
getUserById: jest.fn().mockResolvedValue([user]),
deleteUserById: jest.fn().mockResolvedValue([{ updatedId: 'USER_PKH' }]),
})
const logStub = {
error: jest.fn(),
} as any

await expect(
SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub })('USER_ID'),
).rejects.toThrow('Something went wrong')
SUT._deleteUserById({ db: dbStub, getServerSession: getServerSessionStub, log: logStub })('USER_ID'),
).rejects.toThrow(ERRORS.INTERNAL_SERVER_ERROR)
expect(logStub.error).toHaveBeenCalledWith({
message: 'Not allowed to delete this resource',
name: 'ForbiddenError',
})
})
})
Loading
Loading