refactor: update notifications.resolver to handle filtering

- Updates the getNotifications function to use the refactored getNotificationsFromPaths function
- Adds filtering logic to the updated  getNotificationsFromPaths function
- Update JSdocs
This commit is contained in:
mdatelle
2024-09-12 12:39:12 -04:00
committed by Pujit Mehrotra
parent 17e7d2a2de
commit 90ff980a00
5 changed files with 123 additions and 40 deletions

3
.gitignore vendored
View File

@@ -55,6 +55,9 @@ typings/
# OSX
.DS_Store
# Jetbrains Settings Files
.idea
# Temp dir for tests
test/__temp__/*

82
api/.gitignore vendored Normal file
View File

@@ -0,0 +1,82 @@
# Logs
./logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
coverage-ts
# nyc test coverage
.nyc_output
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# next.js build output
.next
# Visual Studio Code workspace
.vscode/*
!.vscode/extensions.json
# OSX
.DS_Store
# Temp dir for tests
test/__temp__/*
# Built files
dist
# Typescript
typescript
# Ultra runner
.ultra.cache.json
# Github actions
RELEASE_NOTES.md
# Docker Deploy Folder
deploy/*
!deploy/.gitkeep
# pkg cache
.pkg-cache
# IDE Settings Files
.idea

View File

@@ -4,7 +4,6 @@ import {
Importance,
NotificationType,
type Notification,
type NotificationInput,
} from '@app/graphql/generated/api/types';
import { NotificationSchema } from '@app/graphql/generated/api/operations';
import { type RootState, type AppDispatch } from '@app/store/index';

View File

@@ -1,7 +1,5 @@
import { type NotificationFilter } from '@app/graphql/generated/api/types';
import { getters } from '@app/store/index';
import { Query, Resolver, Args, Subscription } from '@nestjs/graphql';
import { GraphQLError } from 'graphql';
import { UseRoles } from 'nest-access-control';
import { PUBSUB_CHANNEL, createSubscription } from '@app/core/pubsub';
import { NotificationsService } from './notifications.service';
@@ -18,9 +16,9 @@ export class NotificationsResolver {
})
public async notifications(
@Args('filter')
{ limit, importance, type, offset }: NotificationFilter
filters: NotificationFilter
) {
return await this.notificationsService.getNotifications();
await this.notificationsService.getNotifications(filters);
}
@Subscription('notificationAdded')

View File

@@ -1,10 +1,10 @@
import { NotificationIni } from '@app/core/types/states/notification';
import { parseConfig } from '@app/core/utils/misc/parse-config';
import { NotificationSchema } from '@app/graphql/generated/api/operations';
import { Importance, NotificationType, type Notification, } from '@app/graphql/generated/api/types';
import {Importance, NotificationType, type Notification, NotificationFilter,} from '@app/graphql/generated/api/types';
import { getters } from '@app/store';
import { Injectable } from '@nestjs/common';
import { readdir, stat } from 'fs/promises';
import { readdir } from 'fs/promises';
import { join } from 'path';
import { Logger } from '@nestjs/common';
import { isFulfilled, isRejected } from '@app/utils';
@@ -18,58 +18,59 @@ export class NotificationsService {
*
* @returns An array of all notifications in the system.
*/
public async getNotifications(): Promise<Notification[]> {
public async getNotifications(filters: NotificationFilter): Promise<Notification[]> {
this.logger.debug('Getting Notifications');
const logErrors = (error: unknown) => this.logger.error(error);
const notificationBasePath = getters.dynamix().notify!.path;
const unreadDirectoryPath = join(notificationBasePath, NotificationType.UNREAD.toLowerCase());
const archivedDirectoryPath = join(notificationBasePath, NotificationType.ARCHIVE.toLowerCase());
const directoryPath = filters.type === NotificationType.ARCHIVE ? archivedDirectoryPath : unreadDirectoryPath;
const unreadFiles = await this.getFilesInFolder(directoryPath);
const [notifications] = await this.getNotificationsFromPaths(unreadFiles, filters);
const [unreadNotifications, unreadNotificationErrors] = await this.getUnreadNotifications(notificationBasePath);
this.logger.debug(`${unreadNotifications.length} Unread Notifications`);
this.logger.debug(`${unreadNotificationErrors.length} Unread Notification Errors`);
unreadNotificationErrors.forEach(logErrors);
const [archivedNotifications, archivedNotificationErrors] = await this.getArchiveNotifications(notificationBasePath);
this.logger.debug(`${archivedNotifications.length} Archived Notifications`);
this.logger.debug(`${archivedNotificationErrors.length} Archived Notification Errors`);
archivedNotificationErrors.forEach(logErrors);
return [...unreadNotifications, ...archivedNotifications];
return notifications;
}
private getUnreadNotifications(notificationsDirectory: string) {
const unreadsDirectoryPath = join(notificationsDirectory, NotificationType.UNREAD.toLowerCase());
return this.getNotificationsFromDirectory(unreadsDirectoryPath, NotificationType.UNREAD);
}
private async getFilesInFolder(folderPath: string): Promise<string[]> {
const contents = await readdir(folderPath);
private getArchiveNotifications(notificationsDirectory: string) {
const archivedDirectoryPath = join(notificationsDirectory, NotificationType.ARCHIVE.toLowerCase());
return this.getNotificationsFromDirectory(archivedDirectoryPath, NotificationType.ARCHIVE);
return contents.map((content) => join(folderPath, content));
}
/**
* Given a directory path, reads all the files in the directory,
* Given a an array of files, reads and filters all the files in the directory,
* and attempts to parse each file as a Notification.
* Returns an array of two elements:
* - the first element is an array of successfully parsed Notifications,
* - the first element is an array of successfully parsed and filtered Notifications,
* - the second element is an array of errors for any files that failed parsing.
* @param containingDirectory the directory to read
* @param type the type of notification to load
* @param files the files to read
* @param filters the filters to apply to the notifications
* @returns an array of two elements: [successes, errors/failures]
*/
private async getNotificationsFromDirectory(containingDirectory: string, type: NotificationType): Promise<[Notification[], unknown[]]> {
const loadNotification = (filePath: string) => this.loadNotificationFile(filePath, type);
private async getNotificationsFromPaths(files: string[], filters: NotificationFilter): Promise<[Notification[], unknown[]]> {
const { limit, importance, type, offset } = filters;
const contents = await readdir(containingDirectory);
const absolutePaths = contents.map((content) => join(containingDirectory, content));
const fileReads = absolutePaths.map(loadNotification);
const fileReads = files.slice(offset, limit + offset).map((file) => this.loadNotificationFile(file, type ?? NotificationType.UNREAD));
const results = await Promise.allSettled(fileReads);
return [
results.filter(isFulfilled).map((result) => result.value),
results.filter(isRejected).map((result) => result.reason),
results.filter(isFulfilled).map(result => result.value).filter((notification) => {
if (importance && importance !== notification.importance) {
return false;
}
if (type && type !== notification.type) {
return false;
}
return true;
})
.sort(
(a, b) =>
new Date(b.timestamp ?? 0).getTime() -
new Date(a.timestamp ?? 0).getTime()
),
results.filter(isRejected).map((result) => result.reason),
];
}