Sigex
Sigex
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
I noticed I missed a bit; https://github.com/novuhq/novu/pull/7136/files so I updated the PR.
9 replies
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
Note: The IStoreQuery we use filters using the payload. Which is not available on the notifications.list which can be accessed via useNovu. So that one is not suitable. I am aware it does have archived as a filter.
9 replies
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
Heading over to findBySubscriberChannel we see;
async findBySubscriberChannel(
environmentId: string,
subscriberId: string,
channel: ChannelTypeEnum,
query: { feedId?: string[]; seen?: boolean; read?: boolean; archived?: boolean; payload?: object } = {},
options: { limit: number; skip?: number } = { limit: 10 }
) {
const requestQuery = await this.getFilterQueryForMessage(environmentId, subscriberId, channel, query);

const messages = await this.MongooseModel.find(requestQuery, '', {
limit: options.limit,
skip: options.skip,
sort: '-createdAt',
})
.read('secondaryPreferred')
.populate('template', '_id tags')
.populate('subscriber', '_id firstName lastName avatar subscriberId')
.populate('actorSubscriber', '_id firstName lastName avatar subscriberId');

return this.mapEntities(messages);
}
async findBySubscriberChannel(
environmentId: string,
subscriberId: string,
channel: ChannelTypeEnum,
query: { feedId?: string[]; seen?: boolean; read?: boolean; archived?: boolean; payload?: object } = {},
options: { limit: number; skip?: number } = { limit: 10 }
) {
const requestQuery = await this.getFilterQueryForMessage(environmentId, subscriberId, channel, query);

const messages = await this.MongooseModel.find(requestQuery, '', {
limit: options.limit,
skip: options.skip,
sort: '-createdAt',
})
.read('secondaryPreferred')
.populate('template', '_id tags')
.populate('subscriber', '_id firstName lastName avatar subscriberId')
.populate('actorSubscriber', '_id firstName lastName avatar subscriberId');

return this.mapEntities(messages);
}
Now this is where it gets interesting... So far all the way down this Rabit Hole archived has not been present on the query object.
query: { feedId?: string[]; seen?: boolean; read?: boolean; archived?: boolean; payload?: object } = {},
query: { feedId?: string[]; seen?: boolean; read?: boolean; archived?: boolean; payload?: object } = {},
But... the query object is parsed into getFilterQueryForMessage which does support archived.
private async getFilterQueryForMessage(
environmentId: string,
subscriberId: string,
channel: ChannelTypeEnum,
query: {
feedId?: string[];
tags?: string[];
seen?: boolean;
read?: boolean;
archived?: boolean;
payload?: object;
} = {}
): Promise<MessageQuery & EnforceEnvId> {
// ...
}
private async getFilterQueryForMessage(
environmentId: string,
subscriberId: string,
channel: ChannelTypeEnum,
query: {
feedId?: string[];
tags?: string[];
seen?: boolean;
read?: boolean;
archived?: boolean;
payload?: object;
} = {}
): Promise<MessageQuery & EnforceEnvId> {
// ...
}
This getFilterQueryForMessage is used by getCount as well which does accept archived. So the solution here is to simply pass archived all the way down.
9 replies
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
@UseGuards(AuthGuard('subscriberJwt'))
@Get('/notifications/feed')
@ApiQuery({
name: 'seen',
type: Boolean,
required: false,
})
async getNotificationsFeed(
@SubscriberSession() subscriberSession: SubscriberEntity,
@Query() query: GetNotificationsFeedDto
) {
let feedsQuery: string[] | undefined;
if (query.feedIdentifier) {
feedsQuery = Array.isArray(query.feedIdentifier) ? query.feedIdentifier : [query.feedIdentifier];
}

const command = GetNotificationsFeedCommand.create({
organizationId: subscriberSession._organizationId,
subscriberId: subscriberSession.subscriberId,
environmentId: subscriberSession._environmentId,
page: query.page,
feedId: feedsQuery,
query: { seen: query.seen, read: query.read },
limit: query.limit,
payload: query.payload,
});

return await this.getNotificationsFeedUsecase.execute(command);
}
@UseGuards(AuthGuard('subscriberJwt'))
@Get('/notifications/feed')
@ApiQuery({
name: 'seen',
type: Boolean,
required: false,
})
async getNotificationsFeed(
@SubscriberSession() subscriberSession: SubscriberEntity,
@Query() query: GetNotificationsFeedDto
) {
let feedsQuery: string[] | undefined;
if (query.feedIdentifier) {
feedsQuery = Array.isArray(query.feedIdentifier) ? query.feedIdentifier : [query.feedIdentifier];
}

const command = GetNotificationsFeedCommand.create({
organizationId: subscriberSession._organizationId,
subscriberId: subscriberSession.subscriberId,
environmentId: subscriberSession._environmentId,
page: query.page,
feedId: feedsQuery,
query: { seen: query.seen, read: query.read },
limit: query.limit,
payload: query.payload,
});

return await this.getNotificationsFeedUsecase.execute(command);
}
The main execute for this makes a call to;
const feed = await this.messageRepository.findBySubscriberChannel(
command.environmentId,
subscriber._id,
ChannelTypeEnum.IN_APP,
{ feedId: command.feedId, seen: command.query.seen, read: command.query.read, payload },
{
limit: command.limit,
skip: command.page * command.limit,
}
);
const feed = await this.messageRepository.findBySubscriberChannel(
command.environmentId,
subscriber._id,
ChannelTypeEnum.IN_APP,
{ feedId: command.feedId, seen: command.query.seen, read: command.query.read, payload },
{
limit: command.limit,
skip: command.page * command.limit,
}
);
9 replies
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
Hi Pawan, 1.Yes. We use the same approach as Midday
import { HeadlessService, type IStoreQuery } from "@novu/headless";

// ...

headlessService.fetchNotifications({
query,
listener: ({}) => {},
onSuccess: (response) => {
setLoading(false);
setNotifications(response.data);
},
});
import { HeadlessService, type IStoreQuery } from "@novu/headless";

// ...

headlessService.fetchNotifications({
query,
listener: ({}) => {},
onSuccess: (response) => {
setLoading(false);
setNotifications(response.data);
},
});
https://github.com/midday-ai/midday/blob/main/apps/dashboard/src/hooks/use-notifications.ts 2. Yes. We use @novu/framework to define workflow's and we use @novu/framework/next 3. Novu cloud. --- The query object for the fetchNotifications is of type IStoreQuery
export interface IStoreQuery {
feedIdentifier?: string | string[];
seen?: boolean;
read?: boolean;
limit?: number;
payload?: Record<string, unknown>;
}
export interface IStoreQuery {
feedIdentifier?: string | string[];
seen?: boolean;
read?: boolean;
limit?: number;
payload?: Record<string, unknown>;
}
Notice the lack of archived as one of the allowed filters. The fetchNotifications for the Headless Service is defined in. packages/headless/src/lib/headless.service.ts The heavy work is done by this.api.getNotificationsList(page, query) That hands off too;
await this.httpClient.getFullResponse(`/widgets/notifications/feed`, {
page,
...(payloadString && { payload: payloadString }),
...rest,
});
await this.httpClient.getFullResponse(`/widgets/notifications/feed`, {
page,
...(payloadString && { payload: payloadString }),
...rest,
});
The widgets controller has a Get method which uses CQRS to execute the following command GetNotificationsFeedCommand.
9 replies
NNovu
Created by Sigex on 12/23/2024 in #💬│support
Headless service `fetchNotifications` should accept 'archived' filter
@Pawan Jain
9 replies