Skip to content

Commit

Permalink
Merge branch 'main' into docs/database-design
Browse files Browse the repository at this point in the history
  • Loading branch information
xixas authored Nov 29, 2024
2 parents a8e0847 + 910f118 commit 77383b0
Show file tree
Hide file tree
Showing 11 changed files with 246 additions and 34 deletions.
5 changes: 3 additions & 2 deletions apps/api/src/common/graphql/services/core.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ export abstract class CoreService<TEntity> {

const queryBuilder = this.repository.createQueryBuilder(alias);

// Perform a Left Join to fetch and display related applicationDetails only for 'notification' findAll
if (alias === 'notification') {
// Perform a Left Join to fetch and display related applicationDetails & providerDetails
// For 'notification' or 'archivedNotification' findAll
if (alias === 'notification' || alias === 'archivedNotification') {
queryBuilder.leftJoinAndSelect(`${alias}.applicationDetails`, 'application');
queryBuilder.leftJoinAndSelect(`${alias}.providerDetails`, 'provider');
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,14 @@ import { TypeOrmModule } from '@nestjs/typeorm';
import { NotificationsModule } from '../notifications/notifications.module';
import { ConfigService } from '@nestjs/config';
import { ArchivedNotificationsController } from './archived-notifications.controller';
import { ArchivedNotificationsResolver } from './archived-notifications.resolver';

@Module({
imports: [
TypeOrmModule.forFeature([ArchivedNotification]),
forwardRef(() => NotificationsModule),
],
providers: [ArchivedNotificationsService, Logger, ConfigService],
providers: [ArchivedNotificationsService, ArchivedNotificationsResolver, Logger, ConfigService],
exports: [ArchivedNotificationsService],
controllers: [ArchivedNotificationsController],
})
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Test, TestingModule } from '@nestjs/testing';
import { ArchivedNotificationsResolver } from './archived-notifications.resolver';

describe('ArchivedNotificationsResolver', () => {
let resolver: ArchivedNotificationsResolver;

beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
providers: [ArchivedNotificationsResolver],
}).compile();

resolver = module.get<ArchivedNotificationsResolver>(ArchivedNotificationsResolver);
});

it('should be defined', () => {
expect(resolver).toBeDefined();
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Args, Context, Query, Resolver } from '@nestjs/graphql';
import { ArchivedNotificationsService } from './archived-notifications.service';
import { ArchivedNotification } from './entities/archived-notification.entity';
import { UseGuards } from '@nestjs/common';
import { ArchivedNotificationResponse } from './dtos/archived-notification-response.dto';
import { QueryOptionsDto } from 'src/common/graphql/dtos/query-options.dto';
import { GqlAuthGuard } from 'src/common/guards/api-key/gql-auth.guard';

@Resolver(() => ArchivedNotification)
@UseGuards(GqlAuthGuard)
export class ArchivedNotificationsResolver {
constructor(private readonly archivedNotificationsService: ArchivedNotificationsService) {}

@Query(() => ArchivedNotificationResponse, { name: 'archivedNotifications' })
async findAll(
@Context() context,
@Args('options', { type: () => QueryOptionsDto, nullable: true, defaultValue: {} })
options: QueryOptionsDto,
): Promise<ArchivedNotificationResponse> {
return this.archivedNotificationsService.getAllArchivedNotifications(options);
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { Injectable, Logger } from '@nestjs/common';
import { ArchivedNotification } from './entities/archived-notification.entity';
import { DataSource, In, QueryRunner } from 'typeorm';
import { DataSource, In, QueryRunner, Repository } from 'typeorm';
import { Notification } from 'src/modules/notifications/entities/notification.entity';
import { ConfigService } from '@nestjs/config';
import { DeliveryStatus } from 'src/common/constants/notifications';
import { Status } from 'src/common/constants/database';
import { QueryOptionsDto } from 'src/common/graphql/dtos/query-options.dto';
import { ArchivedNotificationResponse } from './dtos/archived-notification-response.dto';
import { InjectRepository } from '@nestjs/typeorm';
import { CoreService } from 'src/common/graphql/services/core.service';

@Injectable()
export class ArchivedNotificationsService {
export class ArchivedNotificationsService extends CoreService<ArchivedNotification> {
protected readonly logger = new Logger(ArchivedNotificationsService.name);

constructor(
@InjectRepository(ArchivedNotification)
private readonly archivedNotificationRepository: Repository<ArchivedNotification>,
private readonly configService: ConfigService,
private dataSource: DataSource,
) {}
) {
super(archivedNotificationRepository);
}

private convertToArchivedNotifications(notifications: Notification[]): ArchivedNotification[] {
return notifications.map((notification) => {
Expand Down Expand Up @@ -108,4 +116,21 @@ export class ArchivedNotificationsService {
throw error;
}
}

async getAllArchivedNotifications(
options: QueryOptionsDto,
): Promise<ArchivedNotificationResponse> {
this.logger.log('Getting all archived notifications with options.');

const baseConditions = [{ field: 'status', value: Status.ACTIVE }];
const searchableFields = ['createdBy', 'data', 'result'];

const { items, total } = await super.findAll(
options,
'archivedNotification',
searchableFields,
baseConditions,
);
return new ArchivedNotificationResponse(items, total, options.offset, options.limit);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { ObjectType, Field, Int } from '@nestjs/graphql';
import { ArchivedNotification } from '../entities/archived-notification.entity';

@ObjectType()
export class ArchivedNotificationResponse {
@Field(() => [ArchivedNotification])
archivedNotifications: ArchivedNotification[];

@Field(() => Int)
total: number;

@Field(() => Int)
offset: number;

@Field(() => Int)
limit: number;

constructor(items: ArchivedNotification[], total: number, offset?: number, limit?: number) {
this.archivedNotifications = items;
this.total = total;
this.offset = offset ?? 0;
this.limit = limit ?? items.length;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export class ArchivedNotification {
id: number;

@Column({ name: 'notification_id' })
@Field()
notificationId: number;

@Column({ name: 'provider_id', default: null })
Expand Down
30 changes: 30 additions & 0 deletions apps/portal/src/app/graphql/graphql.queries.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,36 @@ export const GetNotifications = gql`
}
`;

export const GetArchivedNotifications = gql`
query GetArchivedNotifications($limit: Int!, $offset: Int!, $filters: [UniversalFilter!]) {
archivedNotifications(
options: {
limit: $limit
offset: $offset
sortBy: "createdOn"
sortOrder: DESC
filters: $filters
}
) {
archivedNotifications {
channelType
createdBy
createdOn
data
deliveryStatus
notificationId
result
status
updatedBy
updatedOn
}
total
offset
limit
}
}
`;

export const GetApplications = gql`
query GetApplications($limit: Int!, $offset: Int!, $filters: [UniversalFilter!]) {
applications(
Expand Down
13 changes: 13 additions & 0 deletions apps/portal/src/app/views/notifications/notification.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,16 @@ export interface NotificationResponse {
offset: number;
limit: number;
}

export interface ArchivedNotification {
notificationId: number;
channelType: number;
data: Record<string, unknown>;
deliveryStatus: number;
result?: Record<string, unknown> | null;
createdOn: Date;
updatedOn: Date;
createdBy: string;
updatedBy: string;
status: number;
}
86 changes: 60 additions & 26 deletions apps/portal/src/app/views/notifications/notifications.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ export class NotificationsComponent implements OnInit {

toggleArchive() {
this.archivedNotificationToggle = !this.archivedNotificationToggle;
// Now that toggle has been activated, load notifications
this.loadNotificationsLazy({ first: 0, rows: this.pageSize });
}

getApplications() {
Expand Down Expand Up @@ -320,34 +322,66 @@ export class NotificationsComponent implements OnInit {
// Set current page
this.currentPage = Math.floor(event.first / event.rows) + 1;

// Fetch notifications and handle errors
this.notificationService
.getNotifications(variables, loginToken)
.pipe(
// catchError operator to handle errors
catchError((error) => {
this.messageService.add({
key: 'tst',
severity: 'error',
summary: 'Error',
detail: `There was an error while loading notifications. Reason: ${error.message}`,
});
// Check if we need to fetch from archive table or notifications table
if (this.archivedNotificationToggle) {
// Fetch archived notifications and handle errors
this.notificationService
.getArchivedNotifications(variables, loginToken)
.pipe(
// catchError operator to handle errors
catchError((error) => {
this.messageService.add({
key: 'tst',
severity: 'error',
summary: 'Error',
detail: `There was an error while loading notifications. Reason: ${error.message}`,
});
this.loading = false;
return of(null);
}),
)
.subscribe((notificationResponse: NotificationResponse | null) => {
if (notificationResponse && notificationResponse.notifications) {
// pagination is handled by p-table component of primeng
this.notifications = notificationResponse.notifications;
this.totalRecords = notificationResponse.total;
} else {
this.notifications = [];
this.totalRecords = 0;
}

this.loading = false;
return of(null);
}),
)
.subscribe((notificationResponse: NotificationResponse | null) => {
if (notificationResponse && notificationResponse.notifications) {
// pagination is handled by p-table component of primeng
this.notifications = notificationResponse.notifications;
this.totalRecords = notificationResponse.total;
} else {
this.notifications = [];
this.totalRecords = 0;
}
});
} else {
// Fetch notifications and handle errors
this.notificationService
.getNotifications(variables, loginToken)
.pipe(
// catchError operator to handle errors
catchError((error) => {
this.messageService.add({
key: 'tst',
severity: 'error',
summary: 'Error',
detail: `There was an error while loading notifications. Reason: ${error.message}`,
});
this.loading = false;
return of(null);
}),
)
.subscribe((notificationResponse: NotificationResponse | null) => {
if (notificationResponse && notificationResponse.notifications) {
// pagination is handled by p-table component of primeng
this.notifications = notificationResponse.notifications;
this.totalRecords = notificationResponse.total;
} else {
this.notifications = [];
this.totalRecords = 0;
}

this.loading = false;
});
this.loading = false;
});
}
}

showJsonObject(json: Record<string, unknown>): void {
Expand Down
47 changes: 45 additions & 2 deletions apps/portal/src/app/views/notifications/notifications.service.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { Injectable } from '@angular/core';
import { Observable, catchError, map } from 'rxjs';
import { GraphqlService } from 'src/app/graphql/graphql.service';
import { GetNotifications } from 'src/app/graphql/graphql.queries';
import { GetArchivedNotifications, GetNotifications } from 'src/app/graphql/graphql.queries';
import { ApolloQueryResult } from '@apollo/client/core';
import { Notification, NotificationResponse } from './notification.model';
import { ArchivedNotification, Notification, NotificationResponse } from './notification.model';

interface GetNotificationsResponse {
notifications: {
Expand All @@ -13,6 +13,16 @@ interface GetNotificationsResponse {
limit?: number;
};
}

interface GetArchivedNotificationsResponse {
archivedNotifications: {
archivedNotifications?: ArchivedNotification[];
total?: number;
offset?: number;
limit?: number;
};
}

@Injectable({
providedIn: 'root',
})
Expand All @@ -38,4 +48,37 @@ export class NotificationsService {
}),
);
}

getArchivedNotifications(variables, inputToken): Observable<NotificationResponse> {
return this.graphqlService.query(GetArchivedNotifications, variables, inputToken).pipe(
map((response: ApolloQueryResult<GetArchivedNotificationsResponse>) => {
const archivedNotificationArray =
response.data?.archivedNotifications.archivedNotifications;

const notificationResponseObject: NotificationResponse = {
notifications:
archivedNotificationArray?.map((item) => ({
id: item.notificationId,
channelType: item.channelType,
data: item.data,
deliveryStatus: item.deliveryStatus,
result: item.result,
createdOn: item.createdOn,
updatedOn: item.updatedOn,
createdBy: item.createdBy,
updatedBy: item.updatedBy,
status: item.status,
})) ?? [],
total: response.data?.archivedNotifications.total,
offset: response.data?.archivedNotifications.offset,
limit: response.data?.archivedNotifications.limit,
};
return notificationResponseObject;
}),
catchError((error) => {
const errorMessage: string = error.message;
throw new Error(errorMessage);
}),
);
}
}

0 comments on commit 77383b0

Please sign in to comment.