Skip to content

Commit

Permalink
merge SPSH-746 into SPSH-751
Browse files Browse the repository at this point in the history
  • Loading branch information
DPDS93CT committed Jul 3, 2024
2 parents 5935f99 + 4249eb0 commit 81aa181
Show file tree
Hide file tree
Showing 24 changed files with 452 additions and 331 deletions.
33 changes: 33 additions & 0 deletions docs/persistence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Developer notes on persistence layer

## Mapping aggregates to entities

During development many aggregates were created and these aggregates have to be persisted as entities.
Therefore, repositories were built which typically contain at least a _mapAggregateToData_ method and a _mapDataToAggregate_ method.

### Troubleshooting

If you encounter following error while writing such a method, 'Type Option<string> is not assignable to type string' e.g. on the id:

```typescript
function mapAggregateToData(aggregate: Aggregate<boolean>): RequiredEntityData<Aggregate> {
return {
// Don't assign createdAt and updatedAt, they are auto-generated!
id: aggregate.id, //Here following error is shown: Type Option<string> is not assignable to type string
personId: rel(PersonEntity, aggregate.personId),
value: aggregate.value,
};
}
```
make sure that id is the primary key for the entity, additional declarations of primary key attributes may result in the error mentioned above, like e.g. in this entity:
```typescript
@Entity({ tableName: 'tableName' })
export class Entitiy extends TimestampedEntity {
//...
public [PrimaryKeyProp]?: ['anotherProp'];
}
```




9 changes: 9 additions & 0 deletions seeding/dev/01/04_rolle.json
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,15 @@
"MIGRATION_DURCHFUEHREN"
],
"serviceProviderIds": []
},
{
"id": 6,
"administeredBySchulstrukturknoten": 0,
"name": "Schulbegleitung",
"rollenart": "LERN",
"merkmale": [],
"systemrechte": [],
"serviceProviderIds": []
}
]
}
7 changes: 5 additions & 2 deletions src/modules/email/domain/email-address.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { EmailID } from '../../../shared/types/index.js';
import { PersonID } from '../../../shared/types/index.js';

export class EmailAddress<WasPersisted extends boolean> {
public constructor(
public email: Persisted<EmailID, WasPersisted>,
public id: Persisted<string, WasPersisted>,
public readonly createdAt: Persisted<Date, WasPersisted>,
public readonly updatedAt: Persisted<Date, WasPersisted>,
public personId: PersonID,
public address: string,
public enabled: boolean,
) {}
Expand Down
56 changes: 36 additions & 20 deletions src/modules/email/domain/email-event-handler.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import { PersonRepository } from '../../person/persistence/person.repository.js'
import { EventModule, EventService } from '../../../core/eventbus/index.js';
import { EmailFactory } from './email.factory.js';
import { Email } from './email.js';
import { EmailID, PersonID, RolleID } from '../../../shared/types/index.js';
import { EmailAddressID, PersonID, RolleID } from '../../../shared/types/index.js';
import { EmailInvalidError } from '../error/email-invalid.error.js';
import { ClassLogger } from '../../../core/logging/class-logger.js';
import { EmailAddress } from './email-address.js';
Expand All @@ -39,16 +39,11 @@ import { PersonRenamedEvent } from '../../../shared/events/person-renamed.event.
import { Person } from '../../person/domain/person.js';

function getEmail(emaiGeneratorService: EmailGeneratorService, personRepository: PersonRepository): Email<true> {
const fakeEmailId: EmailID = faker.string.uuid();
return Email.construct(
fakeEmailId,
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
emaiGeneratorService,
personRepository,
[new EmailAddress<true>(fakeEmailId, faker.internet.email(), true)],
);
const fakePersonId: PersonID = faker.string.uuid();
const fakeEmailAddressId: string = faker.string.uuid();
return Email.construct(fakePersonId, emaiGeneratorService, personRepository, [
new EmailAddress<boolean>(fakeEmailAddressId, undefined, undefined, fakePersonId, faker.internet.email(), true),
]);
}

function getPerson(): Person<true> {
Expand Down Expand Up @@ -157,7 +152,7 @@ describe('Email Event Handler', () => {
describe('when existing email is found', () => {
it('should enable existing email', async () => {
const fakePersonId: PersonID = faker.string.uuid();
const emailId: EmailID = faker.string.uuid();
const emailAddressId: EmailAddressID = faker.string.uuid();
const event: PersonenkontextCreatedEvent = new PersonenkontextCreatedEvent(
fakePersonId,
faker.string.uuid(),
Expand All @@ -168,7 +163,14 @@ describe('Email Event Handler', () => {
serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap);

const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(emailId, faker.internet.email(), true),
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
fakePersonId,
faker.internet.email(),
true,
),
];
// eslint-disable-next-line @typescript-eslint/require-await
emailRepoMock.findByPerson.mockImplementationOnce(async (personId: PersonID) => {
Expand All @@ -184,7 +186,7 @@ describe('Email Event Handler', () => {
get currentAddress(): Option<string> {
return '[email protected]';
},
id: emailId,
personId: fakePersonId,
emailAddresses: emailAddresses,
}),
};
Expand All @@ -203,7 +205,7 @@ describe('Email Event Handler', () => {
describe('when existing email is found but enabling results in error', () => {
it('should log error', async () => {
const fakePersonId: PersonID = faker.string.uuid();
const emailId: EmailID = faker.string.uuid();
const emailAddressId: EmailAddressID = faker.string.uuid();
const event: PersonenkontextCreatedEvent = new PersonenkontextCreatedEvent(
fakePersonId,
faker.string.uuid(),
Expand All @@ -214,7 +216,14 @@ describe('Email Event Handler', () => {
serviceProviderRepoMock.findByIds.mockResolvedValueOnce(spMap);

const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(emailId, faker.internet.email(), true),
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
fakePersonId,
faker.internet.email(),
true,
),
];
// eslint-disable-next-line @typescript-eslint/require-await
emailRepoMock.findByPerson.mockImplementationOnce(async (personId: PersonID) => {
Expand Down Expand Up @@ -255,7 +264,16 @@ describe('Email Event Handler', () => {

emailFactoryMock.createNew.mockImplementationOnce((personId: PersonID) => {
const emailMock: DeepMocked<Email<false>> = createMock<Email<false>>({
emailAddresses: [new EmailAddress<false>(undefined, faker.internet.email(), true)],
emailAddresses: [
new EmailAddress<false>(
undefined,
undefined,
undefined,
personId,
faker.internet.email(),
true,
),
],
personId: personId,
});
const emailAddress: EmailAddress<false> = createMock<EmailAddress<false>>({
Expand Down Expand Up @@ -390,8 +408,6 @@ describe('Email Event Handler', () => {

describe('when deletion is successful', () => {
it('should log info', async () => {
emailRepoMock.deleteById.mockResolvedValueOnce(true);

await emailEventHandler.asyncPersonDeletedEventHandler(event);

expect(loggerMock.info).toHaveBeenCalledWith(`Successfully deactivated email-address:${emailAddress}`);
Expand Down Expand Up @@ -503,7 +519,7 @@ describe('Email Event Handler', () => {
get currentAddress(): Option<string> {
return '[email protected]';
},
id: faker.string.uuid(),
personId: faker.string.uuid(),
emailAddresses: [],
}),
};
Expand Down
2 changes: 1 addition & 1 deletion src/modules/email/domain/email-event-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import { ServiceProvider } from '../../service-provider/domain/service-provider.
import { ServiceProviderTarget } from '../../service-provider/domain/service-provider.enum.js';
import { EmailFactory } from './email.factory.js';
import { Email } from './email.js';
import { EmailRepo } from '../persistence/email.repo.js';
import { PersonDeletedEvent } from '../../../shared/events/person-deleted.event.js';
import { DomainError } from '../../../shared/error/index.js';
import { PersonID } from '../../../shared/types/index.js';
Expand All @@ -19,6 +18,7 @@ import { EmailAddressNotFoundError } from '../error/email-address-not-found.erro
import { PersonRenamedEvent } from '../../../shared/events/person-renamed.event.js';
import { PersonRepository } from '../../person/persistence/person.repository.js';
import { Person } from '../../person/domain/person.js';
import { EmailRepo } from '../persistence/email.repo.js';

@Injectable()
export class EmailEventHandler {
Expand Down
18 changes: 2 additions & 16 deletions src/modules/email/domain/email.factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,8 @@ export class EmailFactory {
private readonly personRepository: PersonRepository,
) {}

public construct(
id: string,
createdAt: Date,
updatedAt: Date,
personId: PersonID,
emailAddresses: EmailAddress<true>[],
): Email<true> {
return Email.construct(
id,
createdAt,
updatedAt,
personId,
this.emailGeneratorService,
this.personRepository,
emailAddresses,
);
public construct(personId: PersonID, emailAddresses: EmailAddress<true>[]): Email<true> {
return Email.construct(personId, this.emailGeneratorService, this.personRepository, emailAddresses);
}

public createNew(personId: PersonID): Email<false> {
Expand Down
40 changes: 24 additions & 16 deletions src/modules/email/domain/email.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,15 +46,16 @@ describe('Email Aggregate', () => {
beforeEach(() => {
jest.resetAllMocks();
newEmail = emailFactory.createNew(faker.string.uuid());
firstEmailAddress = new EmailAddress<true>(faker.string.uuid(), faker.internet.email(), false);
emailAddresses = [firstEmailAddress];
existingEmail = emailFactory.construct(
firstEmailAddress = new EmailAddress<true>(
faker.string.uuid(),
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
emailAddresses,
faker.internet.email(),
false,
);
emailAddresses = [firstEmailAddress];
existingEmail = emailFactory.construct(faker.string.uuid(), emailAddresses);
newNames = {
vorname: faker.person.firstName(),
familienname: faker.person.lastName(),
Expand All @@ -64,6 +65,19 @@ describe('Email Aggregate', () => {
describe('enable', () => {
describe('when emailAddresses are already present on aggregate', () => {
it('should return successfully', async () => {
/* const emailAddressId: EmailAddressID = faker.string.uuid();
const emailAddresses: EmailAddress<true>[] = [
new EmailAddress<true>(
emailAddressId,
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
faker.internet.email(),
false,
),
];
const existingEmail: Email<true> = emailFactory.construct(faker.string.uuid(), emailAddresses);
*/
const result: Result<Email<true>> = await existingEmail.enable();

expect(result.ok).toBeTruthy();
Expand Down Expand Up @@ -188,17 +202,14 @@ describe('Email Aggregate', () => {
describe('when emailAddresses exist and at least one is enabled', () => {
it('should return true ', () => {
const emailAddress: EmailAddress<true> = new EmailAddress<true>(
faker.string.uuid(),
faker.internet.email(),
true,
);
const email: Email<true> = emailFactory.construct(
faker.string.uuid(),
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
[emailAddress],
faker.internet.email(),
true,
);
const email: Email<true> = emailFactory.construct(faker.string.uuid(), [emailAddress]);
const result: boolean = email.isEnabled();

expect(result).toBeTruthy();
Expand All @@ -216,17 +227,14 @@ describe('Email Aggregate', () => {
describe('when emailAddresses exist and at least one is enabled', () => {
it('should return the emailAddress-address string', () => {
const emailAddress: EmailAddress<true> = new EmailAddress<true>(
faker.string.uuid(),
faker.internet.email(),
true,
);
const email: Email<true> = emailFactory.construct(
faker.string.uuid(),
faker.date.past(),
faker.date.recent(),
faker.string.uuid(),
[emailAddress],
faker.internet.email(),
true,
);
const email: Email<true> = emailFactory.construct(faker.string.uuid(), [emailAddress]);
const result: Option<string> = email.currentAddress;

expect(result).toBeDefined();
Expand Down
Loading

0 comments on commit 81aa181

Please sign in to comment.