Skip to content

Commit

Permalink
feat: [contracts] Fetch more data from ONGHub to accomodate template …
Browse files Browse the repository at this point in the history
…generation
  • Loading branch information
radulescuandrew committed Sep 3, 2024
1 parent c35e38e commit ff94be9
Show file tree
Hide file tree
Showing 17 changed files with 269 additions and 20 deletions.
42 changes: 35 additions & 7 deletions backend/src/api/organization/organization.controller.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
import { Body, Controller, Get, Patch, UseGuards } from '@nestjs/common';
import {
Body,
Controller,
Get,
Patch,
Post,
Req,
UseGuards,
} from '@nestjs/common';
import { ApiBearerAuth, ApiBody } from '@nestjs/swagger';
import { Request } from 'express';
import { ExtractUser } from 'src/common/decorators/extract-user.decorator';
import { WebJwtAuthGuard } from 'src/modules/auth/guards/jwt-web.guard';
import { IAdminUserModel } from 'src/modules/user/models/admin-user.model';
import { GetOrganizationUseCaseService } from 'src/usecases/organization/get-organization.usecase';
import { UpdateOrganizationDescriptionUseCaseService } from 'src/usecases/organization/update-organization-description.usecase';
import { UpdateOrganizationDescriptionDto } from './dto/update-organization-description.dto';
import { OrganizationPresenter } from './presenters/organization-presenter.interface';
import { SyncWithOngHubUseCaseService } from 'src/usecases/organization/sync-with-ngohub.usecase';

// @Roles(Role.ADMIN)
@ApiBearerAuth()
Expand All @@ -16,24 +26,42 @@ export class OrganizationController {
constructor(
private readonly getOrganizationUseCase: GetOrganizationUseCaseService,
private readonly updateOrganizationDescriptionUseCase: UpdateOrganizationDescriptionUseCaseService,
private readonly syncWithOngHubUseCase: SyncWithOngHubUseCaseService,
) {}

@Get()
getOrganization(
async getOrganization(
@ExtractUser() { organizationId }: IAdminUserModel,
): Promise<OrganizationPresenter> {
return this.getOrganizationUseCase.execute(organizationId);
const organization =
await this.getOrganizationUseCase.execute(organizationId);
return new OrganizationPresenter(organization);
}

@ApiBody({ type: UpdateOrganizationDescriptionDto })
@Patch()
patchOrganization(
async patchOrganization(
@ExtractUser() admin: IAdminUserModel,
@Body() { description }: UpdateOrganizationDescriptionDto,
): Promise<OrganizationPresenter> {
return this.updateOrganizationDescriptionUseCase.execute(
description,
admin,
const organization =
await this.updateOrganizationDescriptionUseCase.execute(
description,
admin,
);
return new OrganizationPresenter(organization);
}

@Post('onghub/sync')
async resyncWithOngHub(
@ExtractUser() { organizationId }: IAdminUserModel,
@Req() req: Request,
): Promise<OrganizationPresenter> {
const organization = await this.syncWithOngHubUseCase.execute(
organizationId,
req.headers.authorization.split(' ')[1],
);

return new OrganizationPresenter(organization);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export class OrganizationPresenter {
this.activityArea = organization.activityArea;
this.logo = organization.logo;
this.description = organization.description;
this.cui = organization.cui;
this.legalReprezentativeFullName = organization.legalReprezentativeFullName;
this.legalReprezentativeRole = organization.legalReprezentativeRole;
}

@Expose()
Expand Down Expand Up @@ -63,4 +66,22 @@ export class OrganizationPresenter {
description: 'The description phone of the Organization',
})
description: string;

@Expose()
@ApiProperty({
description: 'CUI Organization',
})
cui: string;

@Expose()
@ApiProperty({
description: 'The legal representative full name of the Organization',
})
legalReprezentativeFullName: string;

@Expose()
@ApiProperty({
description: 'The legal representative role of the Organization',
})
legalReprezentativeRole: string;
}
27 changes: 27 additions & 0 deletions backend/src/migrations/1725349961532-AddOrganizationNewFields.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { MigrationInterface, QueryRunner } from 'typeorm';

export class AddOrganizationNewFields1725349961532
implements MigrationInterface
{
name = 'AddOrganizationNewFields1725349961532';

public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "organization" ADD "cui" text`);
await queryRunner.query(
`ALTER TABLE "organization" ADD "legal_representative_full_name" text`,
);
await queryRunner.query(
`ALTER TABLE "organization" ADD "legal_representative_role" text`,
);
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(
`ALTER TABLE "organization" DROP COLUMN "legal_representative_role"`,
);
await queryRunner.query(
`ALTER TABLE "organization" DROP COLUMN "legal_representative_full_name"`,
);
await queryRunner.query(`ALTER TABLE "organization" DROP COLUMN "cui"`);
}
}
5 changes: 5 additions & 0 deletions backend/src/modules/onghub/exceptions/exceptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export enum OngHubExceptionCodes {
ONG_001 = 'ONG_001',
ONG_002 = 'ONG_002',
ONG_003 = 'ONG_003',
ONG_004 = 'ONG_004',
}

type OngHubExceptionCodeType = keyof typeof OngHubExceptionCodes;
Expand All @@ -25,4 +26,8 @@ export const OngHubExceptionMessages: Record<
message: 'There was unexpected issue while requesting data from ONG Hub',
code_error: OngHubExceptionCodes.ONG_003,
},
[OngHubExceptionCodes.ONG_004]: {
message: 'Could not update organization with the data from ONG Hub',
code_error: OngHubExceptionCodes.ONG_004,
},
};
13 changes: 13 additions & 0 deletions backend/src/modules/organization/entities/organization.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,19 @@ export class OrganizationEntity extends BaseEntity {
@Column({ type: 'text', name: 'logo', nullable: true })
logo: string;

@Column({ type: 'text', name: 'cui', nullable: true })
cui: string;

@Column({
type: 'text',
name: 'legal_representative_full_name',
nullable: true,
})
legalReprezentativeFullName: string;

@Column({ type: 'text', name: 'legal_representative_role', nullable: true })
legalReprezentativeRole: string;

@OneToMany(() => VolunteerEntity, (volunteer) => volunteer.organization)
volunteers: VolunteerEntity[];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import {
ICreateOrganizationModel,
IFindOrganizationModel,
IOrganizationModel,
IUpdateOrganizationModel,
} from '../models/organization.model';
import { Pagination } from 'src/infrastructure/base/repository-with-pagination.class';
import { OrganizationEntity } from '../entities/organization.entity';
Expand All @@ -15,7 +16,10 @@ import { IOrganizationVolunteerModel } from '../models/organization-volunteer.mo
export interface IOrganizationRepository
extends IRepositoryWithPagination<OrganizationEntity> {
create(organization: ICreateOrganizationModel): Promise<IOrganizationModel>;
update(id: string, description: string): Promise<IOrganizationModel>;
update(
id: string,
updates: IUpdateOrganizationModel,
): Promise<IOrganizationModel>;
find(
options:
| Partial<IFindOrganizationModel>
Expand Down
13 changes: 13 additions & 0 deletions backend/src/modules/organization/models/organization.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,13 @@ export interface IOrganizationModel {
activityArea: string;
logo: string;
description: string;
cui?: string;
legalReprezentativeFullName?: string;
legalReprezentativeRole?: string;
}

export type ICreateOrganizationModel = Omit<IOrganizationModel, 'id'>;
export type IUpdateOrganizationModel = Partial<ICreateOrganizationModel>;

export type IFindOrganizationModel = Pick<
IOrganizationModel,
Expand All @@ -37,6 +41,10 @@ export class OrganizationTransformer {
activityArea: organizationEntity.activityArea,
logo: organizationEntity.logo,
description: organizationEntity.description,
cui: organizationEntity.cui,
legalReprezentativeFullName:
organizationEntity.legalReprezentativeFullName,
legalReprezentativeRole: organizationEntity.legalReprezentativeRole,
};
}

Expand All @@ -51,6 +59,11 @@ export class OrganizationTransformer {
organizationEntity.activityArea = organizationModel.activityArea;
organizationEntity.logo = organizationModel.logo;
organizationEntity.description = organizationModel.description;
organizationEntity.cui = organizationModel.cui;
organizationEntity.legalReprezentativeFullName =
organizationModel.legalReprezentativeFullName;
organizationEntity.legalReprezentativeRole =
organizationModel.legalReprezentativeRole;
return organizationEntity;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
ICreateOrganizationModel,
IFindOrganizationModel,
IOrganizationModel,
IUpdateOrganizationModel,
OrganizationTransformer,
} from '../models/organization.model';
import {
Expand Down Expand Up @@ -62,10 +63,10 @@ export class OrganizationRepositoryService

public async update(
id: string,
description: string,
updates: IUpdateOrganizationModel,
): Promise<IOrganizationModel> {
// update organization entity
await this.organizationRepository.update({ id }, { description });
await this.organizationRepository.update({ id }, { ...updates });

// return organization model
return this.find({ id });
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
ICreateOrganizationModel,
IFindOrganizationModel,
IOrganizationModel,
IUpdateOrganizationModel,
} from '../models/organization.model';
import { OrganizationRepositoryService } from '../repositories/organization.repository';
import { Pagination } from 'src/infrastructure/base/repository-with-pagination.class';
Expand All @@ -31,7 +32,14 @@ export class OrganizationFacadeService {
organizationId: string,
description: string,
): Promise<IOrganizationModel> {
return this.organizationRepository.update(organizationId, description);
return this.organizationRepository.update(organizationId, { description });
}

public async updateOrganization(
organizationId: string,
updates: IUpdateOrganizationModel,
): Promise<IOrganizationModel> {
return this.organizationRepository.update(organizationId, updates);
}

public async createOrganization(
Expand Down
69 changes: 69 additions & 0 deletions backend/src/usecases/organization/sync-with-ngohub.usecase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Injectable, Logger } from '@nestjs/common';
import { IUseCaseService } from 'src/common/interfaces/use-case-service.interface';
import { ExceptionsService } from 'src/infrastructure/exceptions/exceptions.service';
import { OngHubExceptionMessages } from 'src/modules/onghub/exceptions/exceptions';
import { OngHubService } from 'src/modules/onghub/services/ong-hub.service';
import { IOrganizationModel } from 'src/modules/organization/models/organization.model';
import { OrganizationFacadeService } from 'src/modules/organization/services/organization.facade';

@Injectable()
export class SyncWithOngHubUseCaseService
implements IUseCaseService<IOrganizationModel>
{
private readonly logger = new Logger(SyncWithOngHubUseCaseService.name);

constructor(
private readonly ongHubService: OngHubService,
private readonly organizationService: OrganizationFacadeService,
private readonly exceptionService: ExceptionsService,
) {}

/**
* Synchronizes the organization data with ONG Hub.
*
* @param organizationId - The ID of the organization to be synchronized.
* @param token - The authentication token for ONG Hub which is the same as the one from VIC because we share the same Cognito User Pool.
* @returns A Promise that resolves to the updated IOrganizationModel.
* @throws InternalServerErrorException if there's an error fetching data from ONG Hub or updating the organization.
*/
async execute(
organizationId: string,
token: string,
): Promise<IOrganizationModel> {
const userWithOrganization =
await this.ongHubService.getUserAndOrganizationDataFromOngHub(token);

if (!userWithOrganization || !userWithOrganization.organization) {
this.exceptionService.internalServerErrorException(
OngHubExceptionMessages.ONG_002,
);
}

try {
const organization = await this.organizationService.updateOrganization(
organizationId,
{
name: userWithOrganization.organization.name,
email: userWithOrganization.organization.email,
phone: userWithOrganization.organization.phone,
address: userWithOrganization.organization.address,
activityArea: userWithOrganization.organization.activityArea,
logo: userWithOrganization.organization.logo,
description: userWithOrganization.organization.description,
cui: userWithOrganization.organization.cui,
legalReprezentativeFullName:
userWithOrganization.organization.legalReprezentativeFullName,
legalReprezentativeRole:
userWithOrganization.organization.legalReprezentativeRole,
},
);

return organization;
} catch (error) {
console.log('[ONGHub Sync] Error updating organization:', error);
this.exceptionService.internalServerErrorException(
OngHubExceptionMessages.ONG_004,
);
}
}
}
3 changes: 3 additions & 0 deletions backend/src/usecases/use-case.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ import { SyncUserOrganizationsUsecase } from './user/sync-user-organizations.use
import { GetRejectedAccessRequestUsecase } from './access-request/get-rejected-access-request.usecase';
import { DeleteAccountRegularUserUsecase } from './user/delete-account.usecase';
import { GeneratePDFsUseCase } from './documents/generate-pdfs.usecase';
import { SyncWithOngHubUseCaseService } from './organization/sync-with-ngohub.usecase';

@Module({
imports: [
Expand Down Expand Up @@ -165,6 +166,7 @@ import { GeneratePDFsUseCase } from './documents/generate-pdfs.usecase';
SwitchOrganizationUsecase,
LeaveOrganizationUsecase,
RejoinOrganizationUsecase,
SyncWithOngHubUseCaseService,
// Access Codes
CreateAccessCodeUseCase,
UpdateAccessCodeUseCase,
Expand Down Expand Up @@ -310,6 +312,7 @@ import { GeneratePDFsUseCase } from './documents/generate-pdfs.usecase';
SwitchOrganizationUsecase,
LeaveOrganizationUsecase,
RejoinOrganizationUsecase,
SyncWithOngHubUseCaseService,
// Access Codes
CreateAccessCodeUseCase,
UpdateAccessCodeUseCase,
Expand Down
5 changes: 4 additions & 1 deletion frontend/src/assets/locales/en/translation.json
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,9 @@
"subtitle": "Data taken from ONGHub.",
"logo": "Organization logo",
"name": "Organization name",
"cui": "CUI",
"legal_representative_full_name": "Legal representative name",
"legal_representative_role": "Legal representative role",
"description": "Organization description",
"vic_description": "Description of the organization in VIC",
"description_placeholder": "The organization description is displayed in the organization profile in the VIC application. You can choose to formulate a specific description for the VIC app, which will attract as many volunteers as possible, or you can use the organization description as it is in ONGHub.",
Expand Down Expand Up @@ -972,4 +975,4 @@
"confirm": "Confirm and sign"
}
}
}
}
Loading

0 comments on commit ff94be9

Please sign in to comment.