diff --git a/src/about-me/person/notifications/__test/make-person-notifier.test.ts b/src/about-me/person/notifications/__test/make-person-notifier.test.ts new file mode 100644 index 0000000..eb84fdd --- /dev/null +++ b/src/about-me/person/notifications/__test/make-person-notifier.test.ts @@ -0,0 +1,262 @@ +import { Email, Person, PersonNotifier, Phone } from '../../types' +import { makePersonNotifier } from '../make-person-notifier' + +const notImplemented = () => { throw new Error('not implemented') } + +describe('makePersonNotifier().notifyPersonUpdates() -> notifyPhoneChanged()', () => { + const captureNotifications = async (initialPerson: Person, updatedPerson: Person): Promise => { + const nofificationLog: Phone[] = [] + const fakeNotifier: PersonNotifier = makePersonNotifier({ + notifyEmailChanged: notImplemented, + notifyPhoneChanged: async (phone?: Phone) => (phone && nofificationLog.push(phone), true), + }) + await fakeNotifier.notifyPersonUpdates(initialPerson, updatedPerson) + return nofificationLog + } + + it('notifies when phone number is added', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + }, + { + id: 'test-person-123', + phone: { + number: '+46721234567', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toMatchObject([ + { + number: '+46721234567', + verificationCode: 'vc-1234', + }, + ]) + }) + + it('notifies when phone number is changed', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + phone: { + number: '000-111', + }, + }, + { + id: 'test-person-123', + phone: { + number: '+46721234567', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toMatchObject([ + { + number: '+46721234567', + verificationCode: 'vc-1234', + }, + ]) + }) + it('does nothing if phone number is not set', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + }, + { + id: 'test-person-123', + } + ) + expect(log).toHaveLength(0) + }) + + it('does nothing is phone number is unchanged', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + phone: { + number: '+721234567', + }, + }, + { + id: 'test-person-123', + phone: { + number: '+721234567', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toHaveLength(0) + }) + it('does nothing if new phone number is verified', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + phone: { + number: '+721234567', + }, + }, + { + id: 'test-person-123', + phone: { + number: '+721234567', + isVerified: true, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toHaveLength(0) + }) + it('does nothing if verificationCode is missing', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + phone: { + number: '000-111', + }, + }, + { + id: 'test-person-123', + phone: { + number: '+721234567', + isVerified: false, + verificationCode: '', + }, + } + ) + expect(log).toHaveLength(0) + }) +}) + +describe('makePersonNotifier().notifyPersonUpdates() -> notifyEmailChanged()', () => { + + const captureNotifications = async (initialPerson: Person, updatedPerson: Person): Promise => { + const nofificationLog: Email[] = [] + const fakeNotifier: PersonNotifier = makePersonNotifier({ + notifyEmailChanged: async (email?: Email) => (email && nofificationLog.push(email), true), + notifyPhoneChanged: notImplemented, + }) + await fakeNotifier.notifyPersonUpdates(initialPerson, updatedPerson) + return nofificationLog + } + + it('notifies when email is added', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + }, + { + id: 'test-person-123', + email: { + address: 'a@b.com', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toMatchObject([ + { + address: 'a@b.com', + verificationCode: 'vc-1234', + }, + ]) + }) + + it('notifies when email is changed', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + email: { + address: 'to@be.replaced', + }, + }, + { + id: 'test-person-123', + email: { + address: 'a@b.com', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toMatchObject([ + { + address: 'a@b.com', + verificationCode: 'vc-1234', + }, + ]) + }) + it('does nothing if mail is not set', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + }, + { + id: 'test-person-123', + } + ) + expect(log).toHaveLength(0) + }) + + it('does nothing is mail is unchanged', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + email: { + address: 'a@b.com', + }, + }, + { + id: 'test-person-123', + email: { + address: 'a@b.com', + isVerified: false, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toHaveLength(0) + }) + it('does nothing if new mail is verified', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + email: { + address: 'a@b.com', + }, + }, + { + id: 'test-person-123', + email: { + address: 'updated@b.com', + isVerified: true, + verificationCode: 'vc-1234', + }, + } + ) + expect(log).toHaveLength(0) + }) + it('does nothing if verificationCode is missing', async () => { + const log = await captureNotifications( + { + id: 'test-person-123', + email: { + address: 'a@b.com', + }, + }, + { + id: 'test-person-123', + email: { + address: 'updated@b.com', + isVerified: false, + verificationCode: '', + }, + } + ) + expect(log).toHaveLength(0) + }) + +}) \ No newline at end of file diff --git a/src/about-me/person/notifications/__test/notify-email-changed-updater.test.ts b/src/about-me/person/notifications/__test/notify-email-changed-updater.test.ts deleted file mode 100644 index a1092be..0000000 --- a/src/about-me/person/notifications/__test/notify-email-changed-updater.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Email, Person, PersonNotifier, PersonUpdater } from '../../types' -import { createNotifyEmailChangedUpdater } from '../notify-email-changed-updater' - -const notImplemented = () => { throw new Error('not implemented') } - -describe('notifyEmailChangedUpdater', () => { - - const captureLogFromUpdates = async (initialPerson: Person, updatedPerson: Person): Promise => { - const nofificationLog: Email[] = [] - const fakeUpdater: PersonUpdater = { - updatePerson: async (): Promise => updatedPerson, - } - const fakeNotifier: PersonNotifier = { - notifyEmailChanged: async (email?: Email) => (email && nofificationLog.push(email), true), - notifyPhoneChanged: notImplemented, - } - const updater = createNotifyEmailChangedUpdater(fakeUpdater, fakeNotifier) - await updater.updatePerson(initialPerson, {}) - return nofificationLog - } - - - - it('notifies when email is added', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - }, - { - id: 'test-person-123', - email: { - address: 'a@b.com', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toMatchObject([ - { - address: 'a@b.com', - verificationCode: 'vc-1234', - }, - ]) - }) - - it('notifies when email is changed', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - email: { - address: 'to@be.replaced', - }, - }, - { - id: 'test-person-123', - email: { - address: 'a@b.com', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toMatchObject([ - { - address: 'a@b.com', - verificationCode: 'vc-1234', - }, - ]) - }) - it('does nothing if mail is not set', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - }, - { - id: 'test-person-123', - } - ) - expect(log).toHaveLength(0) - }) - - it('does nothing is mail is unchanged', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - email: { - address: 'a@b.com', - }, - }, - { - id: 'test-person-123', - email: { - address: 'a@b.com', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toHaveLength(0) - }) - it('does nothing if new mail is verified', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - email: { - address: 'a@b.com', - }, - }, - { - id: 'test-person-123', - email: { - address: 'updated@b.com', - isVerified: true, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toHaveLength(0) - }) - it('does nothing if verificationCode is missing', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - email: { - address: 'a@b.com', - }, - }, - { - id: 'test-person-123', - email: { - address: 'updated@b.com', - isVerified: false, - verificationCode: '', - }, - } - ) - expect(log).toHaveLength(0) - }) - -}) \ No newline at end of file diff --git a/src/about-me/person/notifications/__test/notify-phone-changed-updater.test.ts b/src/about-me/person/notifications/__test/notify-phone-changed-updater.test.ts deleted file mode 100644 index 779b25c..0000000 --- a/src/about-me/person/notifications/__test/notify-phone-changed-updater.test.ts +++ /dev/null @@ -1,140 +0,0 @@ -import { Person, PersonNotifier, PersonUpdater, Phone } from '../../types' -import { createNotifyPhoneChangedUpdater } from '../notify-phone-changed-updater' - -const notImplemented = () => { throw new Error('not implemented') } - -describe('notifyEmailChangedUpdater', () => { - - const captureLogFromUpdates = async (initialPerson: Person, updatedPerson: Person): Promise => { - const nofificationLog: Phone[] = [] - const fakeUpdater: PersonUpdater = { - updatePerson: async (): Promise => updatedPerson, - } - const fakeNotifier: PersonNotifier = { - notifyEmailChanged: notImplemented, - notifyPhoneChanged: async (phone?: Phone) => (phone && nofificationLog.push(phone), true), - } - const updater = createNotifyPhoneChangedUpdater(fakeUpdater, fakeNotifier) - await updater.updatePerson(initialPerson, {}) - return nofificationLog - } - - - - it('notifies when phone number is added', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - }, - { - id: 'test-person-123', - phone: { - number: '+46721234567', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toMatchObject([ - { - number: '+46721234567', - verificationCode: 'vc-1234', - }, - ]) - }) - - it('notifies when phone number is changed', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - phone: { - number: '000-111', - }, - }, - { - id: 'test-person-123', - phone: { - number: '+46721234567', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toMatchObject([ - { - number: '+46721234567', - verificationCode: 'vc-1234', - }, - ]) - }) - it('does nothing if phone number is not set', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - }, - { - id: 'test-person-123', - } - ) - expect(log).toHaveLength(0) - }) - - it('does nothing is phone number is unchanged', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - phone: { - number: '+721234567', - }, - }, - { - id: 'test-person-123', - phone: { - number: '+721234567', - isVerified: false, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toHaveLength(0) - }) - it('does nothing if new phone number is verified', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - phone: { - number: '+721234567', - }, - }, - { - id: 'test-person-123', - phone: { - number: '+721234567', - isVerified: true, - verificationCode: 'vc-1234', - }, - } - ) - expect(log).toHaveLength(0) - }) - it('does nothing if verificationCode is missing', async () => { - const log = await captureLogFromUpdates( - { - id: 'test-person-123', - phone: { - number: '000-111', - }, - }, - { - id: 'test-person-123', - phone: { - number: '+721234567', - isVerified: false, - verificationCode: '', - }, - } - ) - expect(log).toHaveLength(0) - }) - -}) \ No newline at end of file diff --git a/src/about-me/person/notifications/amqp/amqp-person-notifier.ts b/src/about-me/person/notifications/amqp/amqp-person-notifier.ts index a13ef84..8805cb6 100644 --- a/src/about-me/person/notifications/amqp/amqp-person-notifier.ts +++ b/src/about-me/person/notifications/amqp/amqp-person-notifier.ts @@ -1,4 +1,5 @@ import { Email, PersonNotifier, Phone } from '../../types' +import { makePersonNotifier } from '../make-person-notifier' import { AmqpSession, createAmqpSession } from './amqp-session' import { AmqpConfiguration, EmailChangedMessage, PhoneChangedMessage } from './types' @@ -25,7 +26,7 @@ const withPhone = async (phone: Phone|null, action: ((message: PhoneChangedMessa return false } -const createPersonNotifierOnAmqpSession = ({ notifyEmailTopic, notifyPhoneTopic }: AmqpConfiguration, session: AmqpSession): PersonNotifier => ({ +const createPersonNotifierOnAmqpSession = ({ notifyEmailTopic, notifyPhoneTopic }: AmqpConfiguration, session: AmqpSession): PersonNotifier => makePersonNotifier({ notifyEmailChanged: async (email?: Email) => withEmail(email, message => session.publish(notifyEmailTopic, message).then(() => true)), notifyPhoneChanged: async (phone?: Phone) => withPhone(phone, message => session.publish(notifyPhoneTopic, message).then(() => true)), }) diff --git a/src/about-me/person/notifications/index.ts b/src/about-me/person/notifications/index.ts index 4550a9e..f3ea8b3 100644 --- a/src/about-me/person/notifications/index.ts +++ b/src/about-me/person/notifications/index.ts @@ -12,4 +12,4 @@ export const createPersonNotifierFromEnv = (): PersonNotifier => tryCreateAmqpPe /** * create a nofifier that does nothing (besides possibly debug logging) */ -export const ceateDefaultPersonNotifier = (): PersonNotifier => createNullPersonNotifier() +export const createDefaultPersonNotifier = (): PersonNotifier => createNullPersonNotifier() diff --git a/src/about-me/person/notifications/make-person-notifier.ts b/src/about-me/person/notifications/make-person-notifier.ts new file mode 100644 index 0000000..8cfed83 --- /dev/null +++ b/src/about-me/person/notifications/make-person-notifier.ts @@ -0,0 +1,12 @@ +import { Person, PersonNotifier } from '../types' + +export const makePersonNotifier = (inner: Omit): PersonNotifier => ({ + notifyPersonUpdates: async (initial?: Person, updated?: Person) => { + const email = updated?.email && !updated?.email.isVerified && updated?.email?.verificationCode && (updated?.email?.address != initial?.email?.address) && updated?.email + const phone = updated?.phone && !updated.phone.isVerified && updated?.phone?.verificationCode && (updated.phone?.number != initial?.phone?.number) && updated.phone + email && await inner.notifyEmailChanged(email) + phone && await inner.notifyPhoneChanged(phone) + }, + notifyEmailChanged: email => inner.notifyEmailChanged(email), + notifyPhoneChanged: phone => inner.notifyPhoneChanged(phone), +}) \ No newline at end of file diff --git a/src/about-me/person/notifications/notify-email-changed-updater.ts b/src/about-me/person/notifications/notify-email-changed-updater.ts deleted file mode 100644 index f0bbca3..0000000 --- a/src/about-me/person/notifications/notify-email-changed-updater.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PersonNotifier, PersonUpdater } from '../types' - -export const createNotifyEmailChangedUpdater = (inner: PersonUpdater, notifier: PersonNotifier): PersonUpdater => ({ - notifier, - updatePerson: async (initialPerson, update) => { - const updatedPerson = await inner.updatePerson(initialPerson, update) - - const shouldNotify = updatedPerson?.email?.address - && !updatedPerson?.email?.isVerified - && updatedPerson?.email?.address !== initialPerson?.email?.address - && updatedPerson?.email?.verificationCode - - shouldNotify && notifier.notifyEmailChanged(updatedPerson.email) - - return updatedPerson - }, -}) diff --git a/src/about-me/person/notifications/notify-phone-changed-updater.ts b/src/about-me/person/notifications/notify-phone-changed-updater.ts deleted file mode 100644 index 6f3ee79..0000000 --- a/src/about-me/person/notifications/notify-phone-changed-updater.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { PersonNotifier, PersonUpdater } from '../types' - -export const createNotifyPhoneChangedUpdater = (inner: PersonUpdater, notifier: PersonNotifier): PersonUpdater => ({ - notifier, - updatePerson: async (initialPerson, update) => { - const updatedPerson = await inner.updatePerson(initialPerson, update) - - const shouldNotify = updatedPerson?.phone?.number - && !updatedPerson?.phone?.isVerified - && updatedPerson?.phone?.number !== initialPerson?.phone?.number - && updatedPerson?.phone?.verificationCode - - shouldNotify && notifier.notifyPhoneChanged(updatedPerson.phone) - - return updatedPerson - }, -}) diff --git a/src/about-me/person/notifications/null-person-notifier.ts b/src/about-me/person/notifications/null-person-notifier.ts index 085393f..48b0985 100644 --- a/src/about-me/person/notifications/null-person-notifier.ts +++ b/src/about-me/person/notifications/null-person-notifier.ts @@ -1,8 +1,9 @@ import Debug from 'debug' import { PersonNotifier } from '../types' +import { makePersonNotifier } from './make-person-notifier' const debug = Debug('application:person-notifier') -export const createNullPersonNotifier = (): PersonNotifier => ({ +export const createNullPersonNotifier = (): PersonNotifier => makePersonNotifier({ notifyEmailChanged: email => debug(email), notifyPhoneChanged: phone => debug(phone), }) diff --git a/src/about-me/person/repositories/hbg-datatorget/__test/repository-but-person-is-unknown.test.ts b/src/about-me/person/repositories/hbg-datatorget/__test/repository-but-person-is-unknown.test.ts index 5346f96..ea460d4 100644 --- a/src/about-me/person/repositories/hbg-datatorget/__test/repository-but-person-is-unknown.test.ts +++ b/src/about-me/person/repositories/hbg-datatorget/__test/repository-but-person-is-unknown.test.ts @@ -1,3 +1,4 @@ +import { createNullPersonNotifier } from '../../../notifications/null-person-notifier' import { createDefaultPersonUpdater } from '../../../updaters/index' import { createHbgDatatorgetPersonRepository } from '../hbg-datatorget-person-respository' import { PersonInformation, RestClient } from '../rest-api' @@ -9,9 +10,10 @@ const createRepo = (client: Partial) => createHbgDatatorgetPersonRep updateContactDetails: notImplemented, verifyContactDetails: notImplemented, ...client, -}, createDefaultPersonUpdater()) - -const typed = (value: T): T => value +}, +createDefaultPersonUpdater(), +createNullPersonNotifier() +) const setAdd = (s: Set, v: T) => s.has(v) ? false : (s.add(v), true) diff --git a/src/about-me/person/repositories/hbg-datatorget/__test/repository.test.ts b/src/about-me/person/repositories/hbg-datatorget/__test/repository.test.ts index 6b0f700..f2edf5b 100644 --- a/src/about-me/person/repositories/hbg-datatorget/__test/repository.test.ts +++ b/src/about-me/person/repositories/hbg-datatorget/__test/repository.test.ts @@ -1,4 +1,5 @@ import { createDefaultPersonUpdater } from '../../../updaters/index' +import { createNullPersonNotifier } from '../../../notifications/null-person-notifier' import { createHbgDatatorgetPersonRepository } from '../hbg-datatorget-person-respository' import { CATEGORY_PRIVATE, CONTACT_TYPE_EMAIL, CONTACT_TYPE_PHONE, PersonInformation, RestClient } from '../rest-api' @@ -9,7 +10,9 @@ const createRepo = (client: Partial) => createHbgDatatorgetPersonRep updateContactDetails: notImplemented, verifyContactDetails: notImplemented, ...client, -}, createDefaultPersonUpdater()) +}, +createDefaultPersonUpdater(), +createNullPersonNotifier()) const typed = (value: T): T => value diff --git a/src/about-me/person/repositories/hbg-datatorget/hbg-datatorget-person-respository.ts b/src/about-me/person/repositories/hbg-datatorget/hbg-datatorget-person-respository.ts index 1c9ee3e..2ca8cc5 100644 --- a/src/about-me/person/repositories/hbg-datatorget/hbg-datatorget-person-respository.ts +++ b/src/about-me/person/repositories/hbg-datatorget/hbg-datatorget-person-respository.ts @@ -1,4 +1,4 @@ -import { Person, PersonRepository, PersonUpdater } from '../../types' +import { Person, PersonNotifier, PersonRepository, PersonUpdater } from '../../types' import { ContactDetailsUpdate, PersonInformation, RestClient } from './rest-api' export const toPerson = (p: PersonInformation): Person => p ? ({ @@ -21,7 +21,7 @@ export const toPerson = (p: PersonInformation): Person => p ? ({ }))[0] || null, }) : null -export const createHbgDatatorgetPersonRepository = (client: RestClient, updater: PersonUpdater) : PersonRepository => ({ +export const createHbgDatatorgetPersonRepository = (client: RestClient, updater: PersonUpdater, notifier: PersonNotifier) : PersonRepository => ({ getPerson: async (id, knownFromElsewhere) => { const found = await client.getPerson(id) return toPerson(found) // || knownFromElsewhere?.() @@ -46,7 +46,9 @@ export const createHbgDatatorgetPersonRepository = (client: RestClient, updater: ].filter(v => v) await client.updateContactDetails(found.person_id, updates) - return client.getPerson(id).then(toPerson) + const final = await client.getPerson(id).then(toPerson) + await notifier.notifyPersonUpdates(toPerson(found), final) + return final }, verifyEmail: async (verificationCode) => client .verifyContactDetails(verificationCode) @@ -56,11 +58,11 @@ export const createHbgDatatorgetPersonRepository = (client: RestClient, updater: .then(() => ({} as Person), () => null), notifyEmail: async (id) => { const found = toPerson(await client.getPerson(id)) - return found && await updater?.notifier?.notifyEmailChanged(found?.email) + return found && await notifier.notifyEmailChanged(found?.email) }, notifyPhone: async (id) => { const found = toPerson(await client.getPerson(id)) - return found && await updater?.notifier?.notifyPhoneChanged(found?.phone) + return found && await notifier.notifyPhoneChanged(found?.phone) }, checkHealth: async () => true, }) diff --git a/src/about-me/person/repositories/hbg-datatorget/index.ts b/src/about-me/person/repositories/hbg-datatorget/index.ts index 82dc9d4..20d3924 100644 --- a/src/about-me/person/repositories/hbg-datatorget/index.ts +++ b/src/about-me/person/repositories/hbg-datatorget/index.ts @@ -1,14 +1,14 @@ import { getEnv } from '@helsingborg-stad/gdi-api-node' -import { PersonRepository, PersonUpdater } from '../../types' +import { PersonNotifier, PersonRepository, PersonUpdater } from '../../types' import { createHbgDatatorgetPersonRepository } from './hbg-datatorget-person-respository' import { createRestClient } from './rest-api' export { toPerson } from './hbg-datatorget-person-respository' export { toUpdateContactDetailsRequest, toVerifyContactDetailsRequest } from './rest-api' -export const tryCreateDatatorgetFrendsPersonRepositoryFromEnv = (updater: PersonUpdater): PersonRepository | null => { +export const tryCreateDatatorgetFrendsPersonRepositoryFromEnv = (updater: PersonUpdater, notifier: PersonNotifier): PersonRepository | null => { const uri = getEnv('HBG_DATATORGET_URI',{ trim: true, fallback: '' }) const apiKey = getEnv('HBG_DATATORGET_APIKEY',{ trim: true, fallback: '' }) return uri && apiKey ? createHbgDatatorgetPersonRepository( - createRestClient({ uri, apiKey }), updater) : null + createRestClient({ uri, apiKey }), updater, notifier) : null } diff --git a/src/about-me/person/repositories/in-memory/inmemory-person-repository.ts b/src/about-me/person/repositories/in-memory/inmemory-person-repository.ts index d8549f6..45ab86c 100644 --- a/src/about-me/person/repositories/in-memory/inmemory-person-repository.ts +++ b/src/about-me/person/repositories/in-memory/inmemory-person-repository.ts @@ -1,10 +1,11 @@ import { createDefaultPersonUpdater } from '../../updaters/index' -import { PersonRepository, PersonUpdater } from '../../types' +import { createDefaultPersonNotifier } from '../../notifications/index' +import { PersonNotifier, PersonRepository, PersonUpdater } from '../../types' /** * create person repository thats works totally in memory */ -export const createInMemoryPersonRepository = (db: object, updater: PersonUpdater = createDefaultPersonUpdater()): PersonRepository => ({ +export const createInMemoryPersonRepository = (db: object, updater: PersonUpdater = createDefaultPersonUpdater(), notifier: PersonNotifier = createDefaultPersonNotifier()): PersonRepository => ({ getPerson: async (id, knownFromElsewhere) => db[id] || knownFromElsewhere?.(), updatePerson: (id, update, knownFromElsewhere) => updater.updatePerson({ id, ...(db[id] || knownFromElsewhere?.()) }, update) .then(person => { @@ -30,14 +31,14 @@ export const createInMemoryPersonRepository = (db: object, updater: PersonUpdate notifyEmail: async id => { const found = db[id] if (found?.email) { - return updater.notifier?.notifyEmailChanged(found.email) + return notifier?.notifyEmailChanged(found.email) } return false }, notifyPhone: async id => { const found = db[id] if (found?.phone) { - return updater.notifier?.notifyPhoneChanged(found.email) + return notifier?.notifyPhoneChanged(found.email) } return false }, diff --git a/src/about-me/person/repositories/index.ts b/src/about-me/person/repositories/index.ts index b12f65a..5cedcc3 100644 --- a/src/about-me/person/repositories/index.ts +++ b/src/about-me/person/repositories/index.ts @@ -1,4 +1,5 @@ -import { PersonRepository, PersonUpdater } from '../types' +import { createPersonNotifierFromEnv } from '../notifications/index' +import { PersonNotifier, PersonRepository, PersonUpdater } from '../types' import { createPersonUpdaterFromEnv } from '../updaters/index' import { tryCreateDatatorgetFrendsPersonRepositoryFromEnv } from './hbg-datatorget/index' import { createInMemoryPersonRepository } from './in-memory/inmemory-person-repository' @@ -10,7 +11,9 @@ export { createInMemoryPersonRepository } * create person repository based on configuration from environment */ /* istanbul ignore next : runtime configuration read */ -export const createPersonRepositoryFromEnv = (updater: PersonUpdater = createPersonUpdaterFromEnv()): PersonRepository => - tryCreateDatatorgetFrendsPersonRepositoryFromEnv(updater) - || tryCreateMongoPersonRepositoryFromEnv(updater) - || createInMemoryPersonRepository({}, updater) +export const createPersonRepositoryFromEnv = ( + updater: PersonUpdater = createPersonUpdaterFromEnv(), + notifier: PersonNotifier = createPersonNotifierFromEnv()): PersonRepository => + tryCreateDatatorgetFrendsPersonRepositoryFromEnv(updater, notifier) + || tryCreateMongoPersonRepositoryFromEnv(updater, notifier) + || createInMemoryPersonRepository({}, updater, notifier) diff --git a/src/about-me/person/repositories/mongo/__test/mongo-test-utils.ts b/src/about-me/person/repositories/mongo/__test/mongo-test-utils.ts index 4cedb3d..65316ca 100644 --- a/src/about-me/person/repositories/mongo/__test/mongo-test-utils.ts +++ b/src/about-me/person/repositories/mongo/__test/mongo-test-utils.ts @@ -1,8 +1,9 @@ import { MongoClient } from 'mongo-mock' import { createMongoPersonRepository, MongoPersonRepository } from '../mongo-person-repository' -import { createDefaultPersonUpdater, createDefaultNotifyingUpdater } from '../../../updaters' +import { createDefaultPersonUpdater } from '../../../updaters' import { randomUUID } from 'crypto' import { Email, Phone } from '../../../types' +import { makePersonNotifier } from '../../../notifications/make-person-notifier' // We create a mocked mongodb const createMongoClient = (uri) => MongoClient.connect(uri) @@ -27,9 +28,9 @@ export const createTestRepo = (notificationsLog: any[] = []): MongoPersonReposit collectionName: 'persons', testMode: true, }, - createDefaultNotifyingUpdater({ + createDefaultPersonUpdater(), + makePersonNotifier({ notifyEmailChanged: async (email?: Email) => (notificationsLog.push(email), true), notifyPhoneChanged: async (phone?: Phone) => (notificationsLog.push(phone), true), - }, - createDefaultPersonUpdater()), + }), (url) => createMongoClient(url))) diff --git a/src/about-me/person/repositories/mongo/index.ts b/src/about-me/person/repositories/mongo/index.ts index d590ca1..6c4d732 100644 --- a/src/about-me/person/repositories/mongo/index.ts +++ b/src/about-me/person/repositories/mongo/index.ts @@ -1,14 +1,14 @@ import { getEnv } from '@helsingborg-stad/gdi-api-node' -import { PersonRepository, PersonUpdater } from '../../types' +import { PersonNotifier, PersonRepository, PersonUpdater } from '../../types' import { createMongoPersonRepository } from './mongo-person-repository' export { createMongoPersonRepository } /* istanbul ignore next : runtime configuration read */ -export const tryCreateMongoPersonRepositoryFromEnv = (updater: PersonUpdater): PersonRepository | null => { +export const tryCreateMongoPersonRepositoryFromEnv = (updater: PersonUpdater, notifier: PersonNotifier): PersonRepository | null => { const uri = getEnv('MONGODB_URI',{ trim: true, fallback: '' }) return uri ? createMongoPersonRepository({ uri, collectionName: getEnv('MONGODB_COLLECTION',{ trim: true, fallback: 'persons' }), - }, updater) : null + }, updater, notifier) : null } diff --git a/src/about-me/person/repositories/mongo/mongo-person-repository.ts b/src/about-me/person/repositories/mongo/mongo-person-repository.ts index 88e930a..28728ea 100644 --- a/src/about-me/person/repositories/mongo/mongo-person-repository.ts +++ b/src/about-me/person/repositories/mongo/mongo-person-repository.ts @@ -1,5 +1,5 @@ import { MongoClient, Collection, Db, MongoClientOptions } from 'mongodb' -import { Person, PersonRepository, PersonUpdater } from '../../types' +import { Person, PersonNotifier, PersonRepository, PersonUpdater } from '../../types' export interface MongoPersonRepository extends PersonRepository { inspect: (inspector: (connection: Connection) => any) => Promise @@ -36,7 +36,11 @@ const connect = async ({ uri, collectionName = 'persons', testMode = false }: Mo collection: db.collection('persons'), } } -export const createMongoPersonRepository = (config: MongoRepositoryConfiguration, updater: PersonUpdater, clientFactory: MongoClientFactory = defaultMongoClientFactory) : MongoPersonRepository => { +export const createMongoPersonRepository = ( + config: MongoRepositoryConfiguration, + updater: PersonUpdater, + notifier: PersonNotifier, + clientFactory: MongoClientFactory = defaultMongoClientFactory) : MongoPersonRepository => { const c = connect(config, clientFactory) const withConnection = (handler: (connection: Connection) => Promise): Promise => c.then(connection => handler(connection)) return { @@ -56,7 +60,9 @@ export const createMongoPersonRepository = (config: MongoRepositoryConfiguration }, update) // await (found ? collection.replaceOne({ id }, updated) : collection.insertOne(updated)) await (found ? collection.updateOne({ id }, { $set: updated }) : collection.insertOne(updated)) - return (await collection.findOne({ id })) as unknown as Person + const final = (await collection.findOne({ id })) as unknown as Person + await notifier.notifyPersonUpdates(found as unknown as Person, final) + return final }), verifyEmail: (verificationCode) => withConnection(async ({ collection }) => { await collection.updateOne( @@ -83,11 +89,11 @@ export const createMongoPersonRepository = (config: MongoRepositoryConfiguration }), notifyEmail: (id) => withConnection(async ({ collection }) => { const found = await collection.findOne({ id }) as unknown as Person - return found && await updater?.notifier?.notifyEmailChanged(found?.email) + return found && await notifier.notifyEmailChanged(found?.email) }), notifyPhone: (id) => withConnection(async ({ collection }) => { const found = await collection.findOne({ id }) as unknown as Person - return found && await updater?.notifier?.notifyPhoneChanged(found?.phone) + return found && await notifier.notifyPhoneChanged(found?.phone) }), checkHealth: async () => withConnection(async ({ collection }) => collection.findOne({ id: 'id-for-healthcheck-purposes' })).then(() => true), inspect: handler => withConnection(handler), diff --git a/src/about-me/person/types.ts b/src/about-me/person/types.ts index 63846a2..374989f 100644 --- a/src/about-me/person/types.ts +++ b/src/about-me/person/types.ts @@ -36,11 +36,11 @@ export interface PersonRepository { } export interface PersonUpdater { - notifier?: PersonNotifier updatePerson: (person: Person, update: PersonInput) => Promise } export interface PersonNotifier { + notifyPersonUpdates: (initial?: Person, updated?: Person) => Promise notifyEmailChanged: (email?: Email) => Promise notifyPhoneChanged: (phone?: Phone) => Promise } \ No newline at end of file diff --git a/src/about-me/person/updaters/email-updater.ts b/src/about-me/person/updaters/email-updater.ts index e759cbd..4222d7a 100644 --- a/src/about-me/person/updaters/email-updater.ts +++ b/src/about-me/person/updaters/email-updater.ts @@ -14,6 +14,8 @@ export const createEmailUpdater = (): PersonUpdater => ({ }), }) +export const createEmailUpdaterFromEnv = (): PersonUpdater => createEmailUpdater() + const getValidatedEmail = (email: string): string|null|false => email && EmailValidator.validate(email) && email const patchEmail = (email: Email, update: string|null|false): Email | null => @@ -28,6 +30,6 @@ const patchEmail = (email: Email, update: string|null|false): Email | null => address: update, isVerified: false, verifiedDate: null, - verificationCode: randomUUID(), + verificationCode: email?.verificationCode || randomUUID(), }) : email diff --git a/src/about-me/person/updaters/index.ts b/src/about-me/person/updaters/index.ts index 5ceae5d..6c7af05 100644 --- a/src/about-me/person/updaters/index.ts +++ b/src/about-me/person/updaters/index.ts @@ -1,23 +1,19 @@ -import { ceateDefaultPersonNotifier as createDefaultPersonNotifier, createPersonNotifierFromEnv } from '../notifications/index' -import { createNotifyEmailChangedUpdater } from '../notifications/notify-email-changed-updater' -import { createNotifyPhoneChangedUpdater } from '../notifications/notify-phone-changed-updater' -import { PersonNotifier, PersonUpdater } from '../types' -import { createEmailUpdater } from './email-updater' +import { PersonUpdater } from '../types' +import { createEmailUpdater, createEmailUpdaterFromEnv } from './email-updater' import { createPhoneUpdater, createPhoneUpdaterFromEnv } from './phone-updater' /** - * Create person updater that handles phone, email as well as change notifications agains configured services + * Create person updater that handles phone and email */ -export const createDefaultPersonUpdater = (regionCode = 'SE'): PersonUpdater => createDefaultNotifyingUpdater( - createDefaultPersonNotifier(), createEmailUpdater(), createPhoneUpdater(regionCode)) +export const createDefaultPersonUpdater = (regionCode = 'SE'): PersonUpdater => createCompositePersonUpdater( + createEmailUpdater(), createPhoneUpdater(regionCode)) /** * Create person updater that handles phone, email as well as change notifications agains configured services */ -export const createPersonUpdaterFromEnv = (): PersonUpdater => createDefaultNotifyingUpdater( - createPersonNotifierFromEnv(), - createEmailUpdater(), +export const createPersonUpdaterFromEnv = (): PersonUpdater => createCompositePersonUpdater( + createEmailUpdaterFromEnv(), createPhoneUpdaterFromEnv()) /** @@ -26,12 +22,3 @@ export const createPersonUpdaterFromEnv = (): PersonUpdater => createDefaultNoti export const createCompositePersonUpdater = (...updaters: PersonUpdater[]): PersonUpdater => ({ updatePerson: async (person, update) => updaters.reduce((previous, updater) => previous.then(person => updater.updatePerson(person, update)), Promise.resolve(person)), }) - -export const createDefaultNotifyingUpdater = (notifier: PersonNotifier, ...updaters: PersonUpdater[]): PersonUpdater => ({ - notifier, - ...createNotifyEmailChangedUpdater( - createNotifyPhoneChangedUpdater( - createCompositePersonUpdater(...updaters) - , notifier),notifier), -}) -