ITOTEAM-523 notification status

This commit is contained in:
Peter Maquiran
2024-06-27 16:53:45 +01:00
parent 78c13d1bfb
commit 11587cc944
23 changed files with 686 additions and 111 deletions
@@ -0,0 +1,37 @@
import { z } from "zod";
import { NotificationTable } from '../../infra/db/notification.db'
type Changes = {
insert: NotificationTable[];
update: NotificationTable[];
remove: NotificationTable[];
};
export function NotificationListChanges(
localList: NotificationTable[],
serverList: NotificationTable[]
): Changes {
const changes: Changes = { insert: [], update: [], remove: [] };
const localMap = new Map(localList.map(item => [item.notificationId, item]));
const serverMap = new Map(serverList.map(item => [item.notificationId, item]));
// Detect new or updated items
for (const [id, serverItem] of serverMap) {
const localItem = localMap.get(id);
if (!localItem) {
changes.insert.push(serverItem);
} else if (localItem.status !== serverItem.status) {
changes.update.push(serverItem);
}
}
// Detect deleted items
for (const [id, localItem] of localMap) {
if (!serverMap.has(id)) {
changes.remove.push(localItem);
}
}
return changes;
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { FirebasePushNotificationService } from './firebase-push-notification.service';
describe('FirebasePushNotificationService', () => {
let service: FirebasePushNotificationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(FirebasePushNotificationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,80 @@
import { Injectable } from '@angular/core';
import { AlertController, Platform } from '@ionic/angular';
import { Capacitor } from '@capacitor/core';
import { ActionPerformed, PushNotifications, PushNotificationSchema } from '@capacitor/push-notifications';
import { AngularFireMessaging } from '@angular/fire/messaging';
@Injectable({
providedIn: 'root'
})
export class FirebasePushNotificationService {
isPushNotificationsAvailable = Capacitor.isPluginAvailable('PushNotifications');
active = false
constructor(
private platform: Platform,
private afMessaging: AngularFireMessaging,
) {}
onReceiveBackground(callback: Function) {
if (this.platform.is('mobile')) {
if (!this.isPushNotificationsAvailable) {
return false
}
PushNotifications.addListener('pushNotificationActionPerformed',
(notification: ActionPerformed) => {
this.active = true
callback(notification)
console.log('NOtification Listener Backgroud', notification)
}
);
} else {
try {
navigator.serviceWorker.onmessage = (event) => {
console.log('Mensagem recebida do Service Worker:', event.data);
let object = {
notification: event.data
}
callback(object)
if (event.data.notificationClicked) {
console.log('Notificação push do Firebase clicada em segundo plano!');
}
};
} catch(e) {
console.log(e)
}
}
}
onReceiveForeground(callback: Function) {
if (this.platform.is('mobile')) {
if (!this.isPushNotificationsAvailable) {
return false
}
PushNotifications.addListener('pushNotificationReceived',
(notification: PushNotificationSchema) => {
this.active = true
console.log('NOtification Listener', notification)
callback(notification)
}
);
} else {
this.afMessaging.messages.subscribe((notification) => {
console.log('NOtification Listener', notification)
callback(notification)
// Handle the received message, e.g., show a notification
});
}
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { LocalNotificationService } from './local-notification.service';
describe('LocalNotificationService', () => {
let service: LocalNotificationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(LocalNotificationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,86 @@
import { Injectable } from '@angular/core';
import { NotificationDataSource, NotificationTable, NotificationTableSchema } from '../infra/db/notification.db';
import { err, ok } from 'neverthrow';
import { from } from 'rxjs';
import { liveQuery } from 'Dexie';
@Injectable({
providedIn: 'root'
})
export class LocalNotificationService {
constructor() { }
async addNotification(data: NotificationTable) {
// db.eve
try {
const result = await NotificationDataSource.notification.add(data)
return ok(result)
} catch (e) {
return err(false)
}
}
async updateNotification(data: NotificationTable) {
// db.eve
try {
const result = await NotificationDataSource.notification.update(data.notificationId, data)
return ok(result)
} catch (e) {
return err(false)
}
}
async updateNotifications(data: NotificationTable[]) {
// db.eve
try {
const result = await NotificationDataSource.notification.bulkUpdate(data.map(e => ({
key: e.notificationId,
changes: { status: e.status }
})))
return ok(result)
} catch (e) {
return err(false)
}
}
async addNotifications(notifications: NotificationTable[]) {
// Validate each notification
const failed = []
const validNotifications = notifications.filter(notification => {
const result = NotificationTableSchema.safeParse(notification);
if(!result.success) {
failed.push(notification)
}
return result.success;
});
// Add valid notifications to the database
if (validNotifications.length > 0) {
await NotificationDataSource.notification.bulkAdd(validNotifications);
} else {
console.log('No valid notifications to add.');
}
console.log({failed})
return ok(failed)
}
getNotificationLive() {
return from(liveQuery( () => {
return NotificationDataSource.notification.orderBy('createdAt').reverse().toArray()
}))
}
async getNotification() {
return NotificationDataSource.notification.toArray()
}
clear() {
return NotificationDataSource.notification.clear()
}
}
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { RemoteNotificationService } from './remote-notification.service';
describe('RemoteNotificationService', () => {
let service: RemoteNotificationService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(RemoteNotificationService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,29 @@
import { Injectable } from '@angular/core';
import { HttpService } from 'src/app/services/http.service';
import { NotificationInputDTO } from '../dto/NotificationInputDTO';
import { NotificationOutputDTO, NotificationOutputDTOSchema } from '../dto/NotificationOutputDTO';
import { APIReturn } from 'src/app/services/decorator/api-validate-schema.decorator';
@Injectable({
providedIn: 'root'
})
export class RemoteNotificationService {
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2';
constructor(
private httpService: HttpService
) { }
@APIReturn(NotificationOutputDTOSchema, 'Get/Notifications')
async getNotification(queryParameter: NotificationInputDTO) {
return await this.httpService.get<NotificationOutputDTO>(`${this.baseUrl}/Notifications`, queryParameter);
}
async notificationStatus(id: string) {
return await this.httpService.patch<NotificationOutputDTO>(`${this.baseUrl}/Notifications/${id}/status`);
}
}
@@ -0,0 +1,9 @@
import { z } from 'zod';
export const NotificationInputDTOSchema = z.object({
userId: z.string(),
PageNumber: z.string(),
PageSize: z.string(),
})
export type NotificationInputDTO = z.infer<typeof NotificationInputDTOSchema>;
@@ -0,0 +1,31 @@
import { z } from 'zod';
export const NotificationOutputDTOSchema = z.object({
success: z.boolean(),
message: z.string(),
data: z.object({
total: z.number(),
result: z.array(
z.object({
id: z.string(),
service: z.string(),
title: z.string(),
body: z.string(),
object: z.string(),
objectId: z.string(),
folderId: z.string().nullable(),
createdAt: z.string(),
viewDate: z.string().nullable(),
status: z.boolean(),
startDate: z.string().nullable(),
endDate: z.string().nullable(),
bodyEvent: z.string().nullable(),
location: z.string().optional().nullable(),
})
)
}),
})
export type NotificationOutputDTO = z.infer<typeof NotificationOutputDTOSchema>;
@@ -0,0 +1,25 @@
import { Dexie, EntityTable, liveQuery } from 'Dexie';
import { z } from 'zod';
export const NotificationTableSchema = z.object({
notificationId: z.string().nullable(),
title: z.string().optional().nullable(),
service: z.string().nullable(),
object: z.string().optional().nullable(),
idObject: z.string().nullable(),
folderId: z.string().optional().nullable(),
dateInit: z.string().optional().nullable(),
dateEnd: z.string().optional().nullable(),
location: z.string().optional().nullable(),
status: z.boolean().optional(),
})
export type NotificationTable = z.infer<typeof NotificationTableSchema>
// Database declaration (move this to its own module also)
export const NotificationDataSource = new Dexie('NotificationDataSource') as Dexie & {
notification: EntityTable<NotificationTable, 'notificationId'>;
};
NotificationDataSource.version(1).stores({
notification: 'notificationId, title, service, object, idObject, folderId, dateInit, dateEnd, location, createdAt'
});
@@ -0,0 +1,16 @@
import { TestBed } from '@angular/core/testing';
import { NotificationRepositoryService } from './notification-repository.service';
describe('NotificationRepositoryService', () => {
let service: NotificationRepositoryService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(NotificationRepositoryService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -0,0 +1,83 @@
import { Injectable } from '@angular/core';
import { NotificationInputDTO } from './dto/NotificationInputDTO';
import { RemoteNotificationService } from './datasource/remote-notification.service'
import { FirebasePushNotificationService } from './datasource/firebase-push-notification.service'
import { LocalNotificationService } from './datasource/local-notification.service'
import { SessionStore } from 'src/app/store/session.service';
import { NotificationListMapper } from '../domain/mapper/notificationListMapper';
import { NotificationListChanges } from './async/changes/notificationListChange';
import { NotificationTable } from './infra/db/notification.db';
@Injectable({
providedIn: 'root'
})
export class NotificationRepositoryService {
constructor(
private RemoteNotificationService: RemoteNotificationService,
private FirebasePushNotificationService: FirebasePushNotificationService,
private LocalNotificationService: LocalNotificationService
) {
this.FirebasePushNotificationService.onReceiveForeground(async (data)=> {
console.log('FirebasePushNotificationService', data)
this.init()
})
this.init()
}
async init() {
const result = await this.getNotification({
PageNumber: "1",
PageSize: "50",
userId: SessionStore.user.UserId.toString()
})
if(result.isOk()) {
console.log('notification-list', result.value.data.result)
if(result.value.data.result.length >= 1) {
const localList = await this.LocalNotificationService.getNotification()
const serverList = NotificationListMapper(result.value)
const { insert, update } = NotificationListChanges(localList, serverList)
console.log({insert, update})
this.LocalNotificationService.addNotifications(insert)
this.LocalNotificationService.updateNotifications(update)
// this.LocalNotificationService.addNotifications.
} else {
this.LocalNotificationService.clear()
}
}
}
async getNotification(queryParameter: NotificationInputDTO) {
return await this.RemoteNotificationService.getNotification(queryParameter)
}
getNotificationLive() {
return this.LocalNotificationService.getNotificationLive()
}
async notificationStatus(item: NotificationTable) {
await this.RemoteNotificationService.notificationStatus(item.notificationId)
item.status = true
this.LocalNotificationService.updateNotification(item)
this
this.init()
return
}
async localNotificationStatus(item: NotificationTable) {
item.status = true
this.LocalNotificationService.updateNotification(item)
this.init()
return
}
}
@@ -0,0 +1,21 @@
import { NotificationTable } from "../../data/infra/db/notification.db";
import { NotificationOutputDTO } from '../../data/dto/NotificationOutputDTO'
export function NotificationListMapper(NotificationOutputDTO: NotificationOutputDTO): NotificationTable[] {
return NotificationOutputDTO.data.result.map( e => (
{
notificationId: e.id,
title: e.title,
service: e.service,
object: e.object,
idObject: e.objectId,
folderId: e.folderId,
dateInit: e.startDate,
dateEnd: e.endDate,
createdAt: e.createdAt,
status: e.status,
location: e.location
}
))
}