Skip to content

Commit

Permalink
feat: Added the StudentEntity and refactored the migrations
Browse files Browse the repository at this point in the history
  • Loading branch information
zoemaas committed Mar 27, 2024
1 parent 95929d1 commit fb36a51
Show file tree
Hide file tree
Showing 9 changed files with 202 additions and 14 deletions.
2 changes: 1 addition & 1 deletion packages/data-store/src/__tests__/contact.entities.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1410,7 +1410,7 @@ describe('Database entities tests', (): void => {
const partyType2: NonPersistedPartyType = {
type: PartyTypeEnum.NATURAL_PERSON,
tenantId,
name,
name: `${name} + 1`,
}

const partyTypeEntity2: PartyTypeEntity = partyTypeEntityFrom(partyType2)
Expand Down
68 changes: 68 additions & 0 deletions packages/data-store/src/__tests__/contact.store.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,32 @@ describe('Contact store tests', (): void => {
await (await dbConnection).destroy()
})

it('should get a party/student by id', async (): Promise<void> => {
const party: NonPersistedParty = {
uri: 'example.com',
partyType: {
type: PartyTypeEnum.STUDENT,
tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289',
name: 'example_name',
},
contact: {
firstName: 'example_first_name',
middleName: 'example_middle_name',
lastName: 'example_last_name',
grade: '5th',
dateOfBirth: new Date(2016, 0, 5),
displayName: 'example_display_name',
},
}

const savedParty: Party = await contactStore.addParty(party)
expect(savedParty).toBeDefined()

const result: Party = await contactStore.getParty({ partyId: savedParty.id })

expect(result).toBeDefined()
})

it('should get party by id', async (): Promise<void> => {
const party: NonPersistedParty = {
uri: 'example.com',
Expand Down Expand Up @@ -1766,6 +1792,26 @@ describe('Contact store tests', (): void => {
await expect(contactStore.addParty(party)).rejects.toThrow(`Party type ${partyType}, does not match for provided contact`)
})

it('should throw error when adding person party with student contact type', async (): Promise<void> => {
const partyType = PartyTypeEnum.STUDENT
const party: NonPersistedParty = {
uri: 'example.com',
partyType: {
type: partyType,
tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289',
name: 'example_name',
},
contact: {
firstName: 'example_first_name',
middleName: 'example_middle_name',
lastName: 'example_last_name',
displayName: 'example_display_name',
},
}

await expect(contactStore.addParty(party)).rejects.toThrow(`Party type ${partyType}, does not match for provided contact`)
})

it('should throw error when adding organization party with wrong contact type', async (): Promise<void> => {
const partyType = PartyTypeEnum.NATURAL_PERSON
const party: NonPersistedParty = {
Expand All @@ -1784,6 +1830,28 @@ describe('Contact store tests', (): void => {
await expect(contactStore.addParty(party)).rejects.toThrow(`Party type ${partyType}, does not match for provided contact`)
})

it('should throw error when adding student party with wrong contact type', async (): Promise<void> => {
const partyType = PartyTypeEnum.NATURAL_PERSON
const party: NonPersistedParty = {
uri: 'example.com',
partyType: {
type: partyType,
tenantId: '0605761c-4113-4ce5-a6b2-9cbae2f9d289',
name: 'example_name',
},
contact: {
firstName: 'example_first_name',
middleName: 'example_middle_name',
lastName: 'example_last_name',
displayName: 'example_display_name',
grade: '3rd',
dateOfBirth: new Date(2016, 2, 15)
},
}

await expect(contactStore.addParty(party)).rejects.toThrow(`Party type ${partyType}, does not match for provided contact`)
})

it('should get electronic address by id', async (): Promise<void> => {
const party: NonPersistedParty = {
uri: 'example.com',
Expand Down
4 changes: 3 additions & 1 deletion packages/data-store/src/contact/ContactStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
isDidAuthConfig,
isNaturalPerson,
isOpenIdConfig,
isOrganization,
isOrganization, isStudent,
partyEntityFrom,
partyFrom,
partyRelationshipEntityFrom,
Expand Down Expand Up @@ -639,6 +639,8 @@ export class ContactStore extends AbstractContactStore {
return isNaturalPerson(contact)
case PartyTypeEnum.ORGANIZATION:
return isOrganization(contact)
case PartyTypeEnum.STUDENT:
return isStudent(contact)
default:
throw new Error('Party type not supported')
}
Expand Down
6 changes: 3 additions & 3 deletions packages/data-store/src/entities/contact/PartyTypeEntity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,18 @@ export class PartyTypeEntity {
@PrimaryGeneratedColumn('uuid')
id!: string

@Column('simple-enum', { name: 'type', enum: PartyTypeEnum, nullable: false, unique: false })
@Column('simple-enum', { name: 'type', enum: PartyTypeEnum, nullable: false })
type!: PartyTypeEnum

@Column({ name: 'name', length: 255, nullable: false, unique: true })
@IsNotEmpty({ message: 'Blank names are not allowed' })
name!: string

@Column({ name: 'description', length: 255, nullable: true, unique: false })
@Column({ name: 'description', length: 255, nullable: true })
@Validate(IsNonEmptyStringConstraint, { message: 'Blank descriptions are not allowed' })
description?: string

@Column({ name: 'tenant_id', length: 255, nullable: false, unique: false })
@Column({ name: 'tenant_id', length: 255, nullable: true })
@IsNotEmpty({ message: "Blank tenant id's are not allowed" })
tenantId!: string

Expand Down
51 changes: 51 additions & 0 deletions packages/data-store/src/entities/contact/StudentEntity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import {BaseContactEntity} from "./BaseContactEntity";
import {BeforeInsert, BeforeUpdate, ChildEntity, Column} from "typeorm";
import {IsNotEmpty, Validate, ValidationError, validate} from "class-validator";
import {IsNonEmptyStringConstraint} from "../validators";
import {ValidationConstraint} from "../../types";
import {getConstraint} from "../../utils/ValidatorUtils";

@ChildEntity('Student')
export class StudentEntity extends BaseContactEntity {
@Column({ name: 'first_name', length: 255, nullable: false, unique: false })
@IsNotEmpty({ message: 'Blank first names are not allowed' })
firstName!: string

@Column({ name: 'middle_name', length: 255, nullable: true, unique: false })
@Validate(IsNonEmptyStringConstraint, { message: 'Blank middle names are not allowed' })
middleName?: string

@Column({ name: 'last_name', length: 255, nullable: false, unique: false })
@IsNotEmpty({ message: 'Blank last names are not allowed' })
lastName!: string

@Column({ name: 'grade', length: 3, nullable: false})
@IsNotEmpty({ message: 'Blank grade is not allowed'})
grade!: string

@Column({ name: 'date_of_birth', nullable: false})
dateOfBirth!: Date

@Column({name:'owner_id', nullable:true})
ownerId?: string

@Column({name:'tenant_id', nullable:true})
tenantId?: string

@Column({ name: 'display_name', length: 255, nullable: false, unique: false })
@IsNotEmpty({ message: 'Blank display names are not allowed' })
displayName!: string

@BeforeInsert()
@BeforeUpdate()
async validate(): Promise<void> {
const validation: Array<ValidationError> = await validate(this)
if (validation.length > 0) {
const constraint: ValidationConstraint | undefined = getConstraint(validation[0])
if (constraint) {
const message: string = Object.values(constraint!)[0]
return Promise.reject(Error(message))
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ export class CreateContacts1710438363001 implements MigrationInterface {
name = 'CreateContacts1710438363001'

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`CREATE TYPE "identity_origin_type" AS ENUM('INTERNAL', 'EXTERNAL')`)
await queryRunner.query(`CREATE TYPE "public"."IdentityOrigin_type" AS ENUM('internal', 'external')`)

await queryRunner.query(`ALTER TABLE "Party" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "Party" ADD COLUMN "tenant_id" text`);

await queryRunner.query(`ALTER TABLE "Identity" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "Identity" ADD COLUMN "tenant_id" text`);
await queryRunner.query(`ALTER TABLE "Identity" ADD COLUMN "origin" varchar CHECK( "identity_origin_type" IN ('INTERNAL', 'EXTERNAL') ) NOT NULL`);
await queryRunner.query(`ALTER TABLE "Identity" ADD COLUMN "origin" "public"."IdentityOrigin_type" NOT NULL`);

await queryRunner.query(`ALTER TABLE "CorrelationIdentifier" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "CorrelationIdentifier" ADD COLUMN "tenant_id" text`);
Expand All @@ -24,6 +24,8 @@ export class CreateContacts1710438363001 implements MigrationInterface {

await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "tenant_id" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "grade" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "date_of_birth" TIMESTAMP`);

await queryRunner.query(`ALTER TABLE "PartyRelationship" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "PartyRelationship" ADD COLUMN "tenant_id" text`);
Expand All @@ -33,7 +35,7 @@ export class CreateContacts1710438363001 implements MigrationInterface {

await queryRunner.query(`ALTER TABLE "PhysicalAddress" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "PhysicalAddress" ADD COLUMN "tenant_id" text`);

await queryRunner.query(`ALTER TYPE "public"."PartyType_type_enum" ADD VALUE 'student'`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export class CreateContacts1710438363002 implements MigrationInterface {

await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "tenant_id" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "grade" text`);
await queryRunner.query(`ALTER TABLE "BaseContact" ADD COLUMN "date_of_birth" DATETIME`);

await queryRunner.query(`ALTER TABLE "PartyRelationship" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "PartyRelationship" ADD COLUMN "tenant_id" text`);
Expand All @@ -32,6 +34,13 @@ export class CreateContacts1710438363002 implements MigrationInterface {
await queryRunner.query(`ALTER TABLE "PhysicalAddress" ADD COLUMN "owner_id" text`);
await queryRunner.query(`ALTER TABLE "PhysicalAddress" ADD COLUMN "tenant_id" text`);

await queryRunner.query(
`CREATE TABLE "PartyType_new" ("id" varchar PRIMARY KEY NOT NULL, "type" varchar CHECK( "type" IN ('naturalPerson','organization','student') ) NOT NULL, "name" varchar(255) NOT NULL, "description" varchar(255), "tenant_id" varchar(255) NOT NULL, "created_at" datetime NOT NULL DEFAULT (datetime('now')), "last_updated_at" datetime NOT NULL DEFAULT (datetime('now')), CONSTRAINT "UQ_PartyType_name" UNIQUE ("name"))`,
)
await queryRunner.query(`INSERT INTO "PartyType_new" SELECT * FROM "PartyType"`)
await queryRunner.query(`DROP TABLE "PartyType"`)
await queryRunner.query(`ALTER TABLE "PartyType_new" RENAME TO "PartyType"`)
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_PartyType_type_tenant_id" ON "PartyType" ("type", "tenant_id")`)
}

public async down(queryRunner: QueryRunner): Promise<void> {
Expand Down
24 changes: 21 additions & 3 deletions packages/data-store/src/types/contact/contact.ts
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,23 @@ export type NaturalPerson = {
export type NonPersistedNaturalPerson = Omit<NaturalPerson, 'id' | 'createdAt' | 'lastUpdatedAt'>
export type PartialNaturalPerson = Partial<NaturalPerson>

export type Student = {
id: string
firstName: string
lastName: string
middleName?: string
grade: string
dateOfBirth: Date
displayName: string
ownerId?: string
tenantId?: string
createdAt: Date
lastUpdatedAt: Date
}

export type NonPersistedStudent = Omit<Student, 'id' | 'createdAt' | 'lastUpdatedAt'>
export type PartialStudent = Partial<Student>

export type Organization = {
id: string
legalName: string
Expand All @@ -165,9 +182,9 @@ export type Organization = {
export type NonPersistedOrganization = Omit<Organization, 'id' | 'createdAt' | 'lastUpdatedAt'>
export type PartialOrganization = Partial<Organization>

export type Contact = NaturalPerson | Organization
export type NonPersistedContact = NonPersistedNaturalPerson | NonPersistedOrganization
export type PartialContact = PartialNaturalPerson | PartialOrganization
export type Contact = NaturalPerson | Organization | Student
export type NonPersistedContact = NonPersistedNaturalPerson | NonPersistedOrganization | NonPersistedStudent
export type PartialContact = PartialNaturalPerson | PartialOrganization | PartialStudent

export type PartyType = {
id: string
Expand Down Expand Up @@ -258,4 +275,5 @@ export enum CorrelationIdentifierEnum {
export enum PartyTypeEnum {
NATURAL_PERSON = 'naturalPerson',
ORGANIZATION = 'organization',
STUDENT = 'student'
}
44 changes: 41 additions & 3 deletions packages/data-store/src/utils/contact/MappingUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,13 @@ import {
NonPersistedParty,
NonPersistedPartyRelationship,
NonPersistedPartyType,
NonPersistedPhysicalAddress,
NonPersistedPhysicalAddress, NonPersistedStudent,
OpenIdConfig,
Organization,
Party,
PartyRelationship,
PartyType,
PhysicalAddress,
PhysicalAddress, Student,
} from '../../types'
import {PartyEntity} from '../../entities/contact/PartyEntity'
import {IdentityEntity} from '../../entities/contact/IdentityEntity'
Expand All @@ -46,6 +46,7 @@ import {IdentityMetadataItemEntity} from '../../entities/contact/IdentityMetadat
import {OpenIdConfigEntity} from '../../entities/contact/OpenIdConfigEntity'
import {PartyTypeEntity} from '../../entities/contact/PartyTypeEntity'
import {PhysicalAddressEntity} from '../../entities/contact/PhysicalAddressEntity'
import {StudentEntity} from "../../entities/contact/StudentEntity";

export const partyEntityFrom = (party: NonPersistedParty): PartyEntity => {
const partyEntity: PartyEntity = new PartyEntity()
Expand Down Expand Up @@ -92,6 +93,8 @@ export const contactEntityFrom = (contact: NonPersistedContact): BaseContactEnti
return naturalPersonEntityFrom(<NonPersistedNaturalPerson>contact)
} else if (isOrganization(contact)) {
return organizationEntityFrom(<NonPersistedOrganization>contact)
} else if (isStudent(contact)) {
return studentEntityFrom(<NonPersistedStudent>contact)
}

throw new Error('Contact not supported')
Expand All @@ -102,17 +105,22 @@ export const contactFrom = (contact: BaseContactEntity): Contact => {
return naturalPersonFrom(<NaturalPersonEntity>contact)
} else if (isOrganization(contact)) {
return organizationFrom(<OrganizationEntity>contact)
} else if (isStudent(contact)) {
return studentFrom(<StudentEntity>contact)
}

throw new Error(`Contact type not supported`)
}

export const isNaturalPerson = (contact: NonPersistedContact | BaseContactEntity): contact is NonPersistedNaturalPerson | NaturalPersonEntity =>
'firstName' in contact && 'lastName' in contact
'firstName' in contact && 'lastName' in contact && !('grade' in contact) && !('dateOfBirth' in contact)

export const isOrganization = (contact: NonPersistedContact | BaseContactEntity): contact is NonPersistedOrganization | OrganizationEntity =>
'legalName' in contact

export const isStudent = (contact: NonPersistedContact | BaseContactEntity): contact is NonPersistedStudent | StudentEntity =>
'grade' in contact && 'dateOfBirth' in contact

export const connectionEntityFrom = (connection: NonPersistedConnection): ConnectionEntity => {
const connectionEntity: ConnectionEntity = new ConnectionEntity()
connectionEntity.type = connection.type
Expand Down Expand Up @@ -326,6 +334,36 @@ export const organizationEntityFrom = (organization: NonPersistedOrganization):
return organizationEntity
}

export const studentEntityFrom = (student: NonPersistedStudent): StudentEntity => {
const studentEntity: StudentEntity = new StudentEntity()
studentEntity.displayName = student.displayName
studentEntity.firstName = student.firstName
studentEntity.middleName = student.middleName
studentEntity.lastName = student.lastName
studentEntity.grade = student.grade
studentEntity.dateOfBirth = student.dateOfBirth
studentEntity.ownerId = student.ownerId
studentEntity.tenantId = student.tenantId

return studentEntity
}

export const studentFrom = (student: StudentEntity): Student => {
return {
id: student.id,
displayName: student.displayName,
firstName: student.firstName,
middleName: student.middleName,
lastName: student.lastName,
grade: student.grade,
dateOfBirth: student.dateOfBirth,
ownerId: student.ownerId,
tenantId: student.tenantId,
createdAt: student.createdAt,
lastUpdatedAt: student.lastUpdatedAt,
}
}

export const organizationFrom = (organization: OrganizationEntity): Organization => {
return {
id: organization.id,
Expand Down

0 comments on commit fb36a51

Please sign in to comment.