Skip to content

Commit

Permalink
Merge pull request #2 from stoqey/notifications
Browse files Browse the repository at this point in the history
fix(notifications) add notifications, badge, fix fetchRates
  • Loading branch information
ceddybi authored Mar 31, 2024
2 parents 30982e3 + 0fb42c7 commit fd6b4db
Show file tree
Hide file tree
Showing 11 changed files with 336 additions and 13 deletions.
97 changes: 97 additions & 0 deletions src/badge/Badge.methods.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// createBadgeByModel {owner, model}
// updateBadgeCount {id or owner + model, count+-}

import { Badge, BadgeModel } from "./Badge.model";

import { CouchbaseConnection } from "couchset";
import { awaitTo } from "couchset/dist/utils";
import { isEmpty } from "lodash";

export const createBadgeByModel = async (owner: string, model: string = owner): Promise<Badge | null> => {
try {
const [, existingBadge] = await awaitTo(BadgeModel.pagination({
where: {
owner,
model
}
}));

if (isEmpty(existingBadge) || !existingBadge) {
return await BadgeModel.create({
owner,
model,
count: 0
});
}
return existingBadge && existingBadge[0]
} catch (error) {
console.log("error creating badge", error);
return null;
}
}

export const getBadges = async (owner: string, models: string[]): Promise<Badge[] | null> => {
try {

const bucket = CouchbaseConnection.Instance.bucketName;

const query = `
SELECT *
FROM \`${bucket}\` badge
WHERE badge._type = "${Badge.name}"
AND badge.owner = "${owner}"
AND badge.model in ${JSON.stringify(models)};
`;

const [errorFetching, data] = await awaitTo(BadgeModel.customQuery(
{
query: query,
params: [owner, models],
limit: 1000
}
));

if (errorFetching) {
throw errorFetching;
}

const [existingBadge = []] = data;
/**
const createNewBadges = models.map(async (model) => {
return await createBadgeByModel(owner, model);
}
const badges = await Promise.all(createNewBadges);
*/

if (isEmpty(existingBadge) || !existingBadge) {
return null;
}

return existingBadge.map((x: any) => BadgeModel.parse(x.badge));

} catch (error) {
console.log("error getting badge", error);
return null;
}
}

export const updateBadgeCount = async (owner: string, model: string, count: number) => {
try {
// find badge
const [errorBadge, badge] = await awaitTo(createBadgeByModel(owner, model));
if (errorBadge) {
throw errorBadge;
}
if (!badge || isEmpty(badge)) {
throw new Error("error updating badge count");
}
const currentCount = badge.count || 0;
const newBadgeCount = currentCount + count;
badge.count = newBadgeCount < 0 ? 0 : newBadgeCount;

return await BadgeModel.updateById(badge.id, badge);
} catch (error) {
console.log("error updating badge count", error);
return null;
}
}
28 changes: 28 additions & 0 deletions src/badge/Badge.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import {Field, InputType, Model, ObjectType} from 'couchset';

@InputType('BadgeInput')
@ObjectType()
export class Badge {
@Field(() => String, {nullable: true, description: 'ID of the badge'})
id = '';

@Field(() => String, {nullable: true, description: 'The owner of the account'})
owner = '';

@Field(() => String, {nullable: true, description: 'The model for this badge'})
model = '';

@Field(() => Number, {nullable: true, description: 'Counts'})
count = 0;
}

export const BadgeModel = new Model(Badge.name, {graphqlType: Badge});

// automatic

export const {
resolver: BadgeDefaultResolver, // there's going to be other custom resolvers
pagination: BadgePagination,
client: BadgeClient,
modelKeys: BadgeModelKeys,
} = BadgeModel.automate();
30 changes: 30 additions & 0 deletions src/badge/Badge.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Resolver, Query, Arg, UseMiddleware, Ctx } from "couchset";
import { log } from "roadman";
import { Badge } from "./Badge.model";
import { getBadges } from "./Badge.methods";
import { ContextType, isAuth } from "@roadmanjs/auth";
import _get from "lodash/get";
import { isEmpty } from "lodash";

@Resolver()
export class BadgeResolver {
@Query(() => [Badge])
@UseMiddleware(isAuth)
async getBadges(
@Ctx() ctx: ContextType,
@Arg("models",() => [String], { nullable: false }) models: string[]
): Promise<Badge[] | null> {
try {

const owner = _get(ctx, 'payload.userId', '');
const badges = await getBadges(owner, models);
if (!badges || isEmpty(badges)) {
throw new Error("not found");
}
return badges;
} catch (error) {
log("error getting Ad category", error);
return null;
}
}
}
1 change: 1 addition & 0 deletions src/badge/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from './Badge.model';
4 changes: 3 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ import { get as _get, isEmpty } from "lodash";

import AdCategoryResolver from "./listing/AdCategory.resolver";
import { AdsListingResolver } from './listing/AdsListing.model';
import { BadgeResolver } from './badge/Badge.resolver';
import CountryResolver from './country/Country.resolver';
import { NotificationResolver } from './notification';
import PgpResolver from './auth/Pgp.resolver';
import SettingsResolver from './settings/settings.resolver';
import UserAuthPwResolver from './auth/User.resolver.pw';
Expand Down Expand Up @@ -46,7 +48,7 @@ const resolvers = [
VendorResolver,
SettingsResolver,
TransactionDefaultResolver,
PgpResolver, UserAuthResolver, UserAuthPwResolver
PgpResolver, UserAuthResolver, UserAuthPwResolver, NotificationResolver, BadgeResolver
];

const app = async (args: RoadmanBuild): Promise<RoadmanBuild> => {
Expand Down
66 changes: 63 additions & 3 deletions src/notification/notification.methods.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { NotificationModel, NotificationType } from "./notification.model";
import { Notification, NotificationModel } from "./notification.model";
import { compact, flattenDeep, isEmpty } from "lodash";

import { awaitTo } from "couchset/dist/utils";
import { createUpdate } from "couchset";
import { updateBadgeCount } from "../badge/Badge.methods";

export const createNotification = async (notification: NotificationType, silent = false): Promise<NotificationType | null> => {
export const createNotification = async (notification: Notification, silent = false): Promise<Notification | null> => {
try {
const { owner } = notification;
// create notification
// send notification if not silent, using owner

const newNotification = await createUpdate<NotificationType>({
const newNotification = await createUpdate<Notification>({
model: NotificationModel,
data: {
...notification,
Expand All @@ -23,4 +26,61 @@ export const createNotification = async (notification: NotificationType, silent
console.error(error);
return null;
}
}

interface UpdateReadStatus {
limit?: number;
read?: boolean;
};

export const updateReadStatus = async (query: any, opt?: UpdateReadStatus): Promise<Notification[] | null> => {
try {
const notifications = await NotificationModel.pagination({
where: query,
limit: opt?.limit || 1000, // TODO pagination
});

if (isEmpty(notifications)) {
throw new Error("error getting notifications");
}

const updateNotificationsNBadge = await Promise.all(notifications.map(async (notification) => {
const [errorUpdate, updatedNotifications] = await awaitTo(NotificationModel.updateById<Notification>(notification.id, {
...notification,
read: opt?.read || true,
}));

if (errorUpdate || !updatedNotifications) {
console.log("error updateNotificationsNBadge",errorUpdate)
return null;
}

await updateBadgeCount(notification.owner as string, Notification.name, -1)

return updatedNotifications;
}));

return compact(updateNotificationsNBadge);

} catch (error) {
console.error("error updating notification", error);
return null
}
}
// TODO updateNotification(id, data)

export const deleteNotification = async (id: string): Promise<boolean> => {
try {
const [err, deleted] = await awaitTo(NotificationModel.delete(id));

if (err) {
throw err;
}

return deleted;

} catch (error) {
console.error("error deleting notification", error);
return false;
}
}
4 changes: 2 additions & 2 deletions src/notification/notification.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ export const NotificationModelName = 'Notification';

@InputType('NotificationInput')
@ObjectType()
export class NotificationType {
export class Notification {
@Field(() => String, { nullable: true })
id?: string = '';

Expand All @@ -30,7 +30,7 @@ export class NotificationType {
read = false;
}

export const NotificationModel: Model = new Model(NotificationModelName, { graphqlType: NotificationType });
export const NotificationModel: Model = new Model(NotificationModelName, { graphqlType: Notification });

// TODO use automatic Notification when couchset byTime is completed
export const {
Expand Down
71 changes: 66 additions & 5 deletions src/notification/notification.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,27 +10,29 @@ import _get from 'lodash/get';
import pickBy from 'lodash/pickBy';
import identity from 'lodash/identity';
import { log } from '@roadmanjs/logs';
import NotificationModel, { NotificationType } from './notification.model';
import NotificationModel, { NotificationModelName, Notification } from './notification.model';
import { ContextType, isAuth } from '@roadmanjs/auth';
import { CouchbaseConnection, getPagination } from 'couchset';
import { awaitTo } from 'couchset/dist/utils';
import { updateReadStatus } from './notification.methods';
import { isEmpty } from 'lodash';

const NotificationPagination = getPagination(NotificationType);
const NotificationPagination = getPagination(Notification);
@Resolver()
export class NotificationResolver {
// TODO move this couchset when byTime Updated
@Query(() => NotificationPagination)
@UseMiddleware(isAuth)
async notifications(
@Ctx() ctx: ContextType,
@Arg('read', () => String, { nullable: true }) read?: boolean, // todo readNotification
@Arg('read', () => Boolean, { nullable: true }) read?: boolean, // todo readNotification
@Arg('filter', () => String, { nullable: true }) filter?: string,
@Arg('sort', () => String, { nullable: true }) sortArg?: string,
@Arg('before', () => Date, { nullable: true }) before?: Date,
@Arg('after', () => Date, { nullable: true }) after?: Date,
@Arg('limit', () => Number, { nullable: true }) limitArg?: number
): Promise<{ items: NotificationType[]; hasNext?: boolean; params?: any }> {
const notificationModelName = NotificationType.name;
): Promise<{ items: Notification[]; hasNext?: boolean; params?: any }> {
const notificationModelName = NotificationModelName;
const owner = _get(ctx, 'payload.userId', '');
const bucket = CouchbaseConnection.Instance.bucketName;
const sign = before ? '<=' : '>=';
Expand Down Expand Up @@ -94,6 +96,65 @@ export class NotificationResolver {
return { items: [], hasNext: false, params: copyParams };
}
}

@Query(() => [Notification])
@UseMiddleware(isAuth)
async readNotifications(
@Ctx() ctx: ContextType,
@Arg('id', () => String, { nullable: true }) id?: string,
@Arg('before', () => Date, { nullable: true }) before?: Date,
@Arg('limit', () => Number, { nullable: true }) limit: number = 1000
): Promise<Notification[] | null> {
try {
const owner = _get(ctx, 'payload.userId', '');

let updatedNotifications: Notification[] | null = [];

if (id) {
updatedNotifications = await updateReadStatus({ id });

} else {
if (!owner || !before) {
throw new Error("owner and before date are required");
}

updatedNotifications = await updateReadStatus({
owner,
before: { $lte: before },
}, { limit });

}
return updatedNotifications;
}
catch (error) {
log("error reading notification", error);
return null;
}

}

@Query(() => Notification, { nullable: true })
@UseMiddleware(isAuth)
async getNotification(
@Arg('id', () => String, { nullable: false }) id: string,
): Promise<Notification | null> {
try {
if (!id || isEmpty(id)) {
throw new Error("id is required");
}

const [error, notification] = await awaitTo(NotificationModel.findById(id));
if (error) {
throw error;
}
return notification;
}
catch (error) {
log("error reading notification", error);
return null;
}

}
}

export default NotificationResolver;
Loading

0 comments on commit fd6b4db

Please sign in to comment.