Merge pull request #10 from hoppscotch/refactor/subscriptionHandler
HBE-85 - Refactor SubscriptionHandler
This commit is contained in:
@@ -4,6 +4,7 @@ import { default as Redis, RedisOptions } from 'ioredis';
|
|||||||
|
|
||||||
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
import { RedisPubSub } from 'graphql-redis-subscriptions';
|
||||||
import { PubSub as LocalPubSub } from 'graphql-subscriptions';
|
import { PubSub as LocalPubSub } from 'graphql-subscriptions';
|
||||||
|
import { TopicDef } from './topicsDefs';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RedisPubSub uses JSON parsing for back and forth conversion, which loses Date objects, hence this reviver brings them back
|
* RedisPubSub uses JSON parsing for back and forth conversion, which loses Date objects, hence this reviver brings them back
|
||||||
@@ -70,7 +71,7 @@ export class PubSubService implements OnModuleInit {
|
|||||||
return this.pubsub.asyncIterator(topic, options);
|
return this.pubsub.asyncIterator(topic, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
async publish(topic: string, payload: any) {
|
async publish<T extends keyof TopicDef>(topic: T, payload: TopicDef[T]) {
|
||||||
await this.pubsub.publish(topic, payload);
|
await this.pubsub.publish(topic, payload);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
10
packages/hoppscotch-backend/src/pubsub/topicsDefs.ts
Normal file
10
packages/hoppscotch-backend/src/pubsub/topicsDefs.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { UserEnvironment } from '../user-environment/user-environments.model';
|
||||||
|
|
||||||
|
// A custom message type that defines the topic and the corresponding payload.
|
||||||
|
// For every module that publishes a subscription add its type def and the possible subscription type.
|
||||||
|
export type TopicDef = {
|
||||||
|
[
|
||||||
|
topic: `user_environment/${string}/${'created' | 'updated' | 'deleted'}`
|
||||||
|
]: UserEnvironment;
|
||||||
|
[topic: `user_environment/${string}/deleted_many`]: number;
|
||||||
|
};
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
import { PubSubService } from './pubsub/pubsub.service';
|
|
||||||
import { PrimitiveTypes } from './types/primitive-types';
|
|
||||||
import { CustomModuleTypes } from './types/custom-module-types';
|
|
||||||
import { SubscriptionType } from './types/subscription-types';
|
|
||||||
|
|
||||||
// Custom generic type to indicate the type of module
|
|
||||||
type ModuleType = PrimitiveTypes | CustomModuleTypes;
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class SubscriptionHandler {
|
|
||||||
constructor(private readonly pubsub: PubSubService) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Publishes a subscription using the pubsub module
|
|
||||||
* @param topic a string containing the "module_name/identifier"
|
|
||||||
* @param subscriptionType type of subscription being published
|
|
||||||
* @param moduleType type of the module model being called
|
|
||||||
* @returns a promise of type void
|
|
||||||
*/
|
|
||||||
async publish(
|
|
||||||
topic: string,
|
|
||||||
subscriptionType: SubscriptionType,
|
|
||||||
moduleType: ModuleType,
|
|
||||||
) {
|
|
||||||
switch (subscriptionType) {
|
|
||||||
case SubscriptionType.Created:
|
|
||||||
await this.pubsub.publish(`${topic}/created`, moduleType);
|
|
||||||
break;
|
|
||||||
case SubscriptionType.Updated:
|
|
||||||
await this.pubsub.publish(`${topic}/updated`, moduleType);
|
|
||||||
break;
|
|
||||||
case SubscriptionType.Deleted:
|
|
||||||
await this.pubsub.publish(`${topic}/deleted`, moduleType);
|
|
||||||
break;
|
|
||||||
case SubscriptionType.DeleteMany:
|
|
||||||
await this.pubsub.publish(`${topic}/delete_many`, moduleType);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
import { UserEnvironment } from '../user-environment/user-environments.model';
|
|
||||||
import { User } from '../user/user.model';
|
|
||||||
|
|
||||||
export type CustomModuleTypes = UserEnvironment | User;
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
export type PrimitiveTypes = number | string | boolean;
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
// Contains constants for the subscription types we use in Subscription Handler
|
|
||||||
export enum SubscriptionType {
|
|
||||||
Created = 'created',
|
|
||||||
Updated = 'updated',
|
|
||||||
Deleted = 'deleted',
|
|
||||||
DeleteMany = 'delete_many',
|
|
||||||
}
|
|
||||||
@@ -5,7 +5,6 @@ import { UserModule } from '../user/user.module';
|
|||||||
import { UserEnvsUserResolver } from './user.resolver';
|
import { UserEnvsUserResolver } from './user.resolver';
|
||||||
import { UserEnvironmentsResolver } from './user-environments.resolver';
|
import { UserEnvironmentsResolver } from './user-environments.resolver';
|
||||||
import { UserEnvironmentsService } from './user-environments.service';
|
import { UserEnvironmentsService } from './user-environments.service';
|
||||||
import { SubscriptionHandler } from '../subscription-handler';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, PubSubModule, UserModule],
|
imports: [PrismaModule, PubSubModule, UserModule],
|
||||||
@@ -13,7 +12,6 @@ import { SubscriptionHandler } from '../subscription-handler';
|
|||||||
UserEnvironmentsResolver,
|
UserEnvironmentsResolver,
|
||||||
UserEnvironmentsService,
|
UserEnvironmentsService,
|
||||||
UserEnvsUserResolver,
|
UserEnvsUserResolver,
|
||||||
SubscriptionHandler,
|
|
||||||
],
|
],
|
||||||
exports: [UserEnvironmentsService],
|
exports: [UserEnvironmentsService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ export class UserEnvironmentsResolver {
|
|||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
userEnvironmentDeleteMany(@GqlUser() user: User) {
|
userEnvironmentDeleteMany(@GqlUser() user: User) {
|
||||||
return this.pubsub.asyncIterator(
|
return this.pubsub.asyncIterator(
|
||||||
`user_environment/${user.uid}/delete_many`,
|
`user_environment/${user.uid}/deleted_many`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,19 +9,15 @@ import {
|
|||||||
USER_ENVIRONMENT_INVALID_ENVIRONMENT_NAME,
|
USER_ENVIRONMENT_INVALID_ENVIRONMENT_NAME,
|
||||||
} from '../errors';
|
} from '../errors';
|
||||||
import { PubSubService } from '../pubsub/pubsub.service';
|
import { PubSubService } from '../pubsub/pubsub.service';
|
||||||
import { SubscriptionHandler } from '../subscription-handler';
|
|
||||||
import { SubscriptionType } from '../types/subscription-types';
|
|
||||||
|
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
const mockPubSub = mockDeep<PubSubService>();
|
const mockPubSub = mockDeep<PubSubService>();
|
||||||
const mockSubscriptionHandler = mockDeep<SubscriptionHandler>();
|
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const userEnvironmentsService = new UserEnvironmentsService(
|
const userEnvironmentsService = new UserEnvironmentsService(
|
||||||
mockPrisma,
|
mockPrisma,
|
||||||
mockPubSub as any,
|
mockPubSub as any,
|
||||||
mockSubscriptionHandler,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const userPersonalEnvironments = [
|
const userPersonalEnvironments = [
|
||||||
@@ -245,9 +241,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.userUid}`,
|
`user_environment/${result.userUid}/created`,
|
||||||
SubscriptionType.Created,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -276,9 +271,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
true,
|
true,
|
||||||
);
|
);
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.userUid}`,
|
`user_environment/${result.userUid}/created`,
|
||||||
SubscriptionType.Created,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -374,9 +368,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
'[{}]',
|
'[{}]',
|
||||||
);
|
);
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.id}`,
|
`user_environment/${result.id}/updated`,
|
||||||
SubscriptionType.Updated,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -404,9 +397,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
'[{}]',
|
'[{}]',
|
||||||
);
|
);
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.id}`,
|
`user_environment/${result.id}/updated`,
|
||||||
SubscriptionType.Updated,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -470,9 +462,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
|
|
||||||
await userEnvironmentsService.deleteUserEnvironment('abc123', 'env1');
|
await userEnvironmentsService.deleteUserEnvironment('abc123', 'env1');
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.id}`,
|
`user_environment/${result.id}/deleted`,
|
||||||
SubscriptionType.Deleted,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -486,9 +477,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
|
|
||||||
await userEnvironmentsService.deleteUserEnvironments('abc123');
|
await userEnvironmentsService.deleteUserEnvironments('abc123');
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/abc123`,
|
`user_environment/${'abc123'}/deleted_many`,
|
||||||
SubscriptionType.DeleteMany,
|
|
||||||
1,
|
1,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -566,9 +556,8 @@ describe('UserEnvironmentsService', () => {
|
|||||||
|
|
||||||
await userEnvironmentsService.clearGlobalEnvironments('abc123', 'env1');
|
await userEnvironmentsService.clearGlobalEnvironments('abc123', 'env1');
|
||||||
|
|
||||||
return expect(mockSubscriptionHandler.publish).toHaveBeenCalledWith(
|
return expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`user_environment/${result.id}`,
|
`user_environment/${result.id}/updated`,
|
||||||
SubscriptionType.Updated,
|
|
||||||
result,
|
result,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,8 +13,6 @@ import {
|
|||||||
USER_ENVIRONMENT_UPDATE_FAILED,
|
USER_ENVIRONMENT_UPDATE_FAILED,
|
||||||
USER_ENVIRONMENT_INVALID_ENVIRONMENT_NAME,
|
USER_ENVIRONMENT_INVALID_ENVIRONMENT_NAME,
|
||||||
} from '../errors';
|
} from '../errors';
|
||||||
import { SubscriptionHandler } from '../subscription-handler';
|
|
||||||
import { SubscriptionType } from '../types/subscription-types';
|
|
||||||
import { stringToJson } from '../utils';
|
import { stringToJson } from '../utils';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@@ -22,7 +20,6 @@ export class UserEnvironmentsService {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService,
|
||||||
private readonly pubsub: PubSubService,
|
private readonly pubsub: PubSubService,
|
||||||
private readonly subscriptionHandler: SubscriptionHandler,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -119,9 +116,8 @@ export class UserEnvironmentsService {
|
|||||||
isGlobal: createdEnvironment.isGlobal,
|
isGlobal: createdEnvironment.isGlobal,
|
||||||
};
|
};
|
||||||
// Publish subscription for environment creation
|
// Publish subscription for environment creation
|
||||||
await this.subscriptionHandler.publish(
|
await this.pubsub.publish(
|
||||||
`user_environment/${userEnvironment.userUid}`,
|
`user_environment/${userEnvironment.userUid}/created`,
|
||||||
SubscriptionType.Created,
|
|
||||||
userEnvironment,
|
userEnvironment,
|
||||||
);
|
);
|
||||||
return E.right(userEnvironment);
|
return E.right(userEnvironment);
|
||||||
@@ -154,9 +150,8 @@ export class UserEnvironmentsService {
|
|||||||
isGlobal: updatedEnvironment.isGlobal,
|
isGlobal: updatedEnvironment.isGlobal,
|
||||||
};
|
};
|
||||||
// Publish subscription for environment update
|
// Publish subscription for environment update
|
||||||
await this.subscriptionHandler.publish(
|
await this.pubsub.publish(
|
||||||
`user_environment/${updatedUserEnvironment.id}`,
|
`user_environment/${updatedUserEnvironment.id}/updated`,
|
||||||
SubscriptionType.Updated,
|
|
||||||
updatedUserEnvironment,
|
updatedUserEnvironment,
|
||||||
);
|
);
|
||||||
return E.right(updatedUserEnvironment);
|
return E.right(updatedUserEnvironment);
|
||||||
@@ -196,9 +191,8 @@ export class UserEnvironmentsService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Publish subscription for environment deletion
|
// Publish subscription for environment deletion
|
||||||
await this.subscriptionHandler.publish(
|
await this.pubsub.publish(
|
||||||
`user_environment/${deletedUserEnvironment.id}`,
|
`user_environment/${deletedUserEnvironment.id}/deleted`,
|
||||||
SubscriptionType.Deleted,
|
|
||||||
deletedUserEnvironment,
|
deletedUserEnvironment,
|
||||||
);
|
);
|
||||||
return E.right(true);
|
return E.right(true);
|
||||||
@@ -220,9 +214,9 @@ export class UserEnvironmentsService {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.subscriptionHandler.publish(
|
// Publish subscription for multiple environment deletions
|
||||||
`user_environment/${uid}`,
|
await this.pubsub.publish(
|
||||||
SubscriptionType.DeleteMany,
|
`user_environment/${uid}/deleted_many`,
|
||||||
deletedEnvironments.count,
|
deletedEnvironments.count,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -258,9 +252,8 @@ export class UserEnvironmentsService {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Publish subscription for environment update
|
// Publish subscription for environment update
|
||||||
await this.subscriptionHandler.publish(
|
await this.pubsub.publish(
|
||||||
`user_environment/${updatedUserEnvironment.id}`,
|
`user_environment/${updatedUserEnvironment.id}/updated`,
|
||||||
SubscriptionType.Updated,
|
|
||||||
updatedUserEnvironment,
|
updatedUserEnvironment,
|
||||||
);
|
);
|
||||||
return E.right(updatedUserEnvironment);
|
return E.right(updatedUserEnvironment);
|
||||||
|
|||||||
Reference in New Issue
Block a user