HBE-147 refactor: Introduce shortcodes into self-host refactored to pseudo-fp format (#22)
* refactor: refactor all queries,mutations and subscriptions for shortcode module * test: rewrote test cases for shortcodes * chore: modified shortcode error code * chore: created helper function to do shortcode type conversion in service file * chore: simplifed logic to fetch user shortcodes with cursor pagination * chore: removed migrations file * chore: removed unused imports in shortcodes module * chore: modified generateUniqueShortCodeID function * chore: modified generateUniqueShortCodeID function * chore: changed jwtService to use verify instead of decode * docs: added teacher comments to all shortcodes service methods * chore: removed stale test cases from shortcode modules
This commit is contained in:
@@ -13,6 +13,7 @@ import { TeamEnvironmentsModule } from './team-environments/team-environments.mo
|
|||||||
import { TeamCollectionModule } from './team-collection/team-collection.module';
|
import { TeamCollectionModule } from './team-collection/team-collection.module';
|
||||||
import { TeamRequestModule } from './team-request/team-request.module';
|
import { TeamRequestModule } from './team-request/team-request.module';
|
||||||
import { TeamInvitationModule } from './team-invitation/team-invitation.module';
|
import { TeamInvitationModule } from './team-invitation/team-invitation.module';
|
||||||
|
import { ShortcodeModule } from './shortcode/shortcode.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -55,6 +56,7 @@ import { TeamInvitationModule } from './team-invitation/team-invitation.module';
|
|||||||
TeamCollectionModule,
|
TeamCollectionModule,
|
||||||
TeamRequestModule,
|
TeamRequestModule,
|
||||||
TeamInvitationModule,
|
TeamInvitationModule,
|
||||||
|
ShortcodeModule,
|
||||||
],
|
],
|
||||||
providers: [GQLComplexityPlugin],
|
providers: [GQLComplexityPlugin],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -155,16 +155,29 @@ export const TEAM_INVITE_EMAIL_DO_NOT_MATCH =
|
|||||||
export const TEAM_INVITE_NOT_VALID_VIEWER =
|
export const TEAM_INVITE_NOT_VALID_VIEWER =
|
||||||
'team_invite/not_valid_viewer' as const;
|
'team_invite/not_valid_viewer' as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShortCode not found in DB
|
||||||
|
* (ShortcodeService)
|
||||||
|
*/
|
||||||
export const SHORTCODE_NOT_FOUND = 'shortcode/not_found' as const;
|
export const SHORTCODE_NOT_FOUND = 'shortcode/not_found' as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalid ShortCode format
|
||||||
|
* (ShortcodeService)
|
||||||
|
*/
|
||||||
export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
|
export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ShortCode already exists in DB
|
||||||
|
* (ShortcodeService)
|
||||||
|
*/
|
||||||
|
export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid or non-existent TEAM ENVIRONMMENT ID
|
* Invalid or non-existent TEAM ENVIRONMMENT ID
|
||||||
* (TeamEnvironmentsService)
|
* (TeamEnvironmentsService)
|
||||||
*/
|
*/
|
||||||
export const TEAM_ENVIRONMENT_NOT_FOUND =
|
export const TEAM_ENVIRONMENT_NOT_FOUND = 'team_environment/not_found' as const;
|
||||||
'team_environment/not_found' as const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user is not a member of the team of the given environment
|
* The user is not a member of the team of the given environment
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { TeamEnvironment } from 'src/team-environments/team-environments.model';
|
|||||||
import { TeamCollection } from 'src/team-collection/team-collection.model';
|
import { TeamCollection } from 'src/team-collection/team-collection.model';
|
||||||
import { TeamRequest } from 'src/team-request/team-request.model';
|
import { TeamRequest } from 'src/team-request/team-request.model';
|
||||||
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
||||||
|
import { Shortcode } from 'src/shortcode/shortcode.model';
|
||||||
|
|
||||||
// A custom message type that defines the topic and the corresponding payload.
|
// 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.
|
// For every module that publishes a subscription add its type def and the possible subscription type.
|
||||||
@@ -36,4 +37,5 @@ export type TopicDef = {
|
|||||||
[topic: `team_req/${string}/req_deleted`]: string;
|
[topic: `team_req/${string}/req_deleted`]: string;
|
||||||
[topic: `team/${string}/invite_added`]: TeamInvitation;
|
[topic: `team/${string}/invite_added`]: TeamInvitation;
|
||||||
[topic: `team/${string}/invite_removed`]: string;
|
[topic: `team/${string}/invite_removed`]: string;
|
||||||
|
[topic: `shortcode/${string}/${'created' | 'revoked'}`]: Shortcode;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
import { PrismaModule } from 'src/prisma/prisma.module';
|
import { PrismaModule } from 'src/prisma/prisma.module';
|
||||||
import { PubSubModule } from 'src/pubsub/pubsub.module';
|
import { PubSubModule } from 'src/pubsub/pubsub.module';
|
||||||
import { UserModule } from 'src/user/user.module';
|
import { UserModule } from 'src/user/user.module';
|
||||||
@@ -7,7 +7,14 @@ import { ShortcodeResolver } from './shortcode.resolver';
|
|||||||
import { ShortcodeService } from './shortcode.service';
|
import { ShortcodeService } from './shortcode.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [PrismaModule, UserModule, PubSubModule],
|
imports: [
|
||||||
|
PrismaModule,
|
||||||
|
UserModule,
|
||||||
|
PubSubModule,
|
||||||
|
JwtModule.register({
|
||||||
|
secret: process.env.JWT_SECRET,
|
||||||
|
}),
|
||||||
|
],
|
||||||
providers: [ShortcodeService, ShortcodeResolver],
|
providers: [ShortcodeService, ShortcodeResolver],
|
||||||
exports: [ShortcodeService],
|
exports: [ShortcodeService],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -7,23 +7,19 @@ import {
|
|||||||
Resolver,
|
Resolver,
|
||||||
Subscription,
|
Subscription,
|
||||||
} from '@nestjs/graphql';
|
} from '@nestjs/graphql';
|
||||||
import { pipe } from 'fp-ts/function';
|
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import * as T from 'fp-ts/Task';
|
|
||||||
import * as TO from 'fp-ts/TaskOption';
|
|
||||||
import * as TE from 'fp-ts/TaskEither';
|
|
||||||
import { UseGuards } from '@nestjs/common';
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
import { Shortcode } from './shortcode.model';
|
import { Shortcode } from './shortcode.model';
|
||||||
import { ShortcodeService } from './shortcode.service';
|
import { ShortcodeService } from './shortcode.service';
|
||||||
import { UserService } from 'src/user/user.service';
|
import { UserService } from 'src/user/user.service';
|
||||||
import { throwErr } from 'src/utils';
|
import { throwErr } from 'src/utils';
|
||||||
import { SHORTCODE_INVALID_JSON } from 'src/errors';
|
|
||||||
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
||||||
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
|
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
|
||||||
import { User } from 'src/user/user.model';
|
import { User } from 'src/user/user.model';
|
||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { AuthUser } from '../types/AuthUser';
|
import { AuthUser } from '../types/AuthUser';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { PaginationArgs } from 'src/types/input-types.args';
|
||||||
|
|
||||||
@Resolver(() => Shortcode)
|
@Resolver(() => Shortcode)
|
||||||
export class ShortcodeResolver {
|
export class ShortcodeResolver {
|
||||||
@@ -31,155 +27,95 @@ export class ShortcodeResolver {
|
|||||||
private readonly shortcodeService: ShortcodeService,
|
private readonly shortcodeService: ShortcodeService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly pubsub: PubSubService,
|
private readonly pubsub: PubSubService,
|
||||||
|
private jwtService: JwtService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/* Queries */
|
/* Queries */
|
||||||
|
|
||||||
@Query(() => Shortcode, {
|
@Query(() => Shortcode, {
|
||||||
description: 'Resolves and returns a shortcode data',
|
description: 'Resolves and returns a shortcode data',
|
||||||
nullable: true,
|
nullable: true,
|
||||||
})
|
})
|
||||||
shortcode(
|
async shortcode(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'code',
|
name: 'code',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
description: 'The shortcode to resolve',
|
description: 'The shortcode to resolve',
|
||||||
})
|
})
|
||||||
code: string,
|
code: string,
|
||||||
): Promise<Shortcode | null> {
|
) {
|
||||||
return pipe(
|
const result = await this.shortcodeService.getShortCode(code);
|
||||||
this.shortcodeService.resolveShortcode(code),
|
|
||||||
TO.getOrElseW(() => T.of(null)),
|
if (E.isLeft(result)) throwErr(result.left);
|
||||||
)();
|
return result.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Query(() => [Shortcode], {
|
@Query(() => [Shortcode], {
|
||||||
description: 'List all shortcodes the current user has generated',
|
description: 'List all shortcodes the current user has generated',
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard)
|
@UseGuards(GqlAuthGuard)
|
||||||
myShortcodes(
|
async myShortcodes(@GqlUser() user: AuthUser, @Args() args: PaginationArgs) {
|
||||||
@GqlUser() user: AuthUser,
|
return this.shortcodeService.fetchUserShortCodes(user.uid, args);
|
||||||
@Args({
|
|
||||||
name: 'cursor',
|
|
||||||
type: () => ID,
|
|
||||||
description:
|
|
||||||
'The ID of the last returned shortcode (used for pagination)',
|
|
||||||
nullable: true,
|
|
||||||
})
|
|
||||||
cursor?: string,
|
|
||||||
): Promise<Shortcode[]> {
|
|
||||||
return this.shortcodeService.fetchUserShortCodes(
|
|
||||||
user.uid,
|
|
||||||
cursor ?? null,
|
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Mutations */
|
/* Mutations */
|
||||||
|
@Mutation(() => Shortcode, {
|
||||||
|
description: 'Create a shortcode for the given request.',
|
||||||
|
})
|
||||||
|
async createShortcode(
|
||||||
|
@Args({
|
||||||
|
name: 'request',
|
||||||
|
description: 'JSON string of the request object',
|
||||||
|
})
|
||||||
|
request: string,
|
||||||
|
@Context() ctx: any,
|
||||||
|
) {
|
||||||
|
const decodedAccessToken = this.jwtService.verify(
|
||||||
|
ctx.req.cookies['access_token'],
|
||||||
|
);
|
||||||
|
const result = await this.shortcodeService.createShortcode(
|
||||||
|
request,
|
||||||
|
decodedAccessToken?.sub,
|
||||||
|
);
|
||||||
|
|
||||||
// TODO: Create a shortcode resolver pending implementation
|
if (E.isLeft(result)) throwErr(result.left);
|
||||||
// @Mutation(() => Shortcode, {
|
return result.right;
|
||||||
// description: 'Create a shortcode for the given request.',
|
}
|
||||||
// })
|
|
||||||
// createShortcode(
|
|
||||||
// @Args({
|
|
||||||
// name: 'request',
|
|
||||||
// description: 'JSON string of the request object',
|
|
||||||
// })
|
|
||||||
// request: string,
|
|
||||||
// @Context() ctx: any,
|
|
||||||
// ): Promise<Shortcode> {
|
|
||||||
// return pipe(
|
|
||||||
// TE.Do,
|
|
||||||
//
|
|
||||||
// // Get the user
|
|
||||||
// TE.bind('user', () =>
|
|
||||||
// pipe(
|
|
||||||
// TE.tryCatch(
|
|
||||||
// () => {
|
|
||||||
// const authString: string | undefined | null =
|
|
||||||
// ctx.reqHeaders.authorization;
|
|
||||||
//
|
|
||||||
// if (
|
|
||||||
// !authString ||
|
|
||||||
// !authString.includes(' ') ||
|
|
||||||
// !authString.startsWith('Bearer ')
|
|
||||||
// ) {
|
|
||||||
// return Promise.reject('no auth token');
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// const authToken = authString.split(' ')[1];
|
|
||||||
//
|
|
||||||
// return this.userService.authenticateWithIDToken(authToken);
|
|
||||||
// },
|
|
||||||
// (e) => e,
|
|
||||||
// ),
|
|
||||||
// TE.getOrElseW(() => T.of(undefined)),
|
|
||||||
// TE.fromTask,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
//
|
|
||||||
// // Get the Request JSON
|
|
||||||
// TE.bind('reqJSON', () =>
|
|
||||||
// pipe(
|
|
||||||
// E.tryCatch(
|
|
||||||
// () => JSON.parse(request),
|
|
||||||
// () => SHORTCODE_INVALID_JSON,
|
|
||||||
// ),
|
|
||||||
// TE.fromEither,
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
//
|
|
||||||
// // Create the shortcode
|
|
||||||
// TE.chain(({ reqJSON, user }) => {
|
|
||||||
// return TE.fromTask(
|
|
||||||
// this.shortcodeService.createShortcode(reqJSON, user),
|
|
||||||
// );
|
|
||||||
// }),
|
|
||||||
//
|
|
||||||
// // Return or throw if there is an error
|
|
||||||
// TE.getOrElse(throwErr),
|
|
||||||
// )();
|
|
||||||
// }
|
|
||||||
|
|
||||||
// TODO: Implement revoke shortcode
|
@Mutation(() => Boolean, {
|
||||||
// @Mutation(() => Boolean, {
|
description: 'Revoke a user generated shortcode',
|
||||||
// description: 'Revoke a user generated shortcode',
|
})
|
||||||
// })
|
@UseGuards(GqlAuthGuard)
|
||||||
// @UseGuards(GqlAuthGuard)
|
async revokeShortcode(
|
||||||
// revokeShortcode(
|
@GqlUser() user: User,
|
||||||
// @GqlUser() user: User,
|
@Args({
|
||||||
// @Args({
|
name: 'code',
|
||||||
// name: 'code',
|
type: () => ID,
|
||||||
// type: () => ID,
|
description: 'The shortcode to resolve',
|
||||||
// description: 'The shortcode to resolve',
|
})
|
||||||
// })
|
code: string,
|
||||||
// code: string,
|
) {
|
||||||
// ): Promise<boolean> {
|
const result = await this.shortcodeService.revokeShortCode(code, user.uid);
|
||||||
// return pipe(
|
|
||||||
// this.shortcodeService.revokeShortCode(code, user.uid),
|
if (E.isLeft(result)) throwErr(result.left);
|
||||||
// TE.map(() => true), // Just return true on success, no resource to return
|
return result.right;
|
||||||
// TE.getOrElse(throwErr),
|
}
|
||||||
// )();
|
|
||||||
// }
|
|
||||||
|
|
||||||
/* Subscriptions */
|
/* Subscriptions */
|
||||||
|
@Subscription(() => Shortcode, {
|
||||||
|
description: 'Listen for shortcode creation',
|
||||||
|
resolve: (value) => value,
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard)
|
||||||
|
myShortcodesCreated(@GqlUser() user: AuthUser) {
|
||||||
|
return this.pubsub.asyncIterator(`shortcode/${user.uid}/created`);
|
||||||
|
}
|
||||||
|
|
||||||
// TODO: update subscription after fixing service methods
|
@Subscription(() => Shortcode, {
|
||||||
// @Subscription(() => Shortcode, {
|
description: 'Listen for shortcode deletion',
|
||||||
// description: 'Listen for shortcode creation',
|
resolve: (value) => value,
|
||||||
// resolve: (value) => value,
|
})
|
||||||
// })
|
@UseGuards(GqlAuthGuard)
|
||||||
// @UseGuards(GqlAuthGuard)
|
myShortcodesRevoked(@GqlUser() user: AuthUser): AsyncIterator<Shortcode> {
|
||||||
// myShortcodesCreated(@GqlUser() user: AuthUser) {
|
return this.pubsub.asyncIterator(`shortcode/${user.uid}/revoked`);
|
||||||
// return this.pubsub.asyncIterator(`shortcode/${user.uid}/created`);
|
}
|
||||||
// }
|
|
||||||
//
|
|
||||||
// @Subscription(() => Shortcode, {
|
|
||||||
// description: 'Listen for shortcode deletion',
|
|
||||||
// resolve: (value) => value,
|
|
||||||
// })
|
|
||||||
// @UseGuards(GqlAuthGuard)
|
|
||||||
// myShortcodesRevoked(@GqlUser() user: AuthUser): AsyncIterator<Shortcode> {
|
|
||||||
// return this.pubsub.asyncIterator(`shortcode/${user.uid}/revoked`);
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import {
|
||||||
import { SHORTCODE_NOT_FOUND } from 'src/errors';
|
SHORTCODE_ALREADY_EXISTS,
|
||||||
import { User } from 'src/user/user.model';
|
SHORTCODE_INVALID_JSON,
|
||||||
|
SHORTCODE_NOT_FOUND,
|
||||||
|
} from 'src/errors';
|
||||||
import { Shortcode } from './shortcode.model';
|
import { Shortcode } from './shortcode.model';
|
||||||
import { ShortcodeService } from './shortcode.service';
|
import { ShortcodeService } from './shortcode.service';
|
||||||
import { UserService } from 'src/user/user.service';
|
import { UserService } from 'src/user/user.service';
|
||||||
@@ -34,258 +36,76 @@ beforeEach(() => {
|
|||||||
mockReset(mockPrisma);
|
mockReset(mockPrisma);
|
||||||
mockPubSub.publish.mockClear();
|
mockPubSub.publish.mockClear();
|
||||||
});
|
});
|
||||||
|
const createdOn = new Date();
|
||||||
|
|
||||||
|
const shortCodeWithOutUser = {
|
||||||
|
id: '123',
|
||||||
|
request: '{}',
|
||||||
|
createdOn: createdOn,
|
||||||
|
creatorUid: null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortCodeWithUser = {
|
||||||
|
id: '123',
|
||||||
|
request: '{}',
|
||||||
|
createdOn: createdOn,
|
||||||
|
creatorUid: 'user_uid_1',
|
||||||
|
};
|
||||||
|
|
||||||
|
const shortcodes = [
|
||||||
|
{
|
||||||
|
id: 'blablabla',
|
||||||
|
request: {
|
||||||
|
hello: 'there',
|
||||||
|
},
|
||||||
|
creatorUid: 'testuser',
|
||||||
|
createdOn: new Date(),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'blablabla1',
|
||||||
|
request: {
|
||||||
|
hello: 'there',
|
||||||
|
},
|
||||||
|
creatorUid: 'testuser',
|
||||||
|
createdOn: new Date(),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
describe('ShortcodeService', () => {
|
describe('ShortcodeService', () => {
|
||||||
describe('resolveShortcode', () => {
|
describe('getShortCode', () => {
|
||||||
test('returns Some for a valid existent shortcode', () => {
|
test('should return a valid shortcode with valid shortcode ID', async () => {
|
||||||
mockPrisma.shortcode.findFirst.mockResolvedValueOnce({
|
mockPrisma.shortcode.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
id: 'blablablabla',
|
shortCodeWithOutUser,
|
||||||
createdOn: new Date(),
|
);
|
||||||
request: {
|
|
||||||
hello: 'there',
|
|
||||||
},
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
});
|
|
||||||
|
|
||||||
return expect(
|
const result = await shortcodeService.getShortCode(
|
||||||
shortcodeService.resolveShortcode('blablablabla')(),
|
shortCodeWithOutUser.id,
|
||||||
).resolves.toBeSome();
|
);
|
||||||
});
|
expect(result).toEqualRight(<Shortcode>{
|
||||||
|
id: shortCodeWithOutUser.id,
|
||||||
test('returns the correct info for a valid shortcode', () => {
|
createdOn: shortCodeWithOutUser.createdOn,
|
||||||
const shortcode = {
|
request: JSON.stringify(shortCodeWithOutUser.request),
|
||||||
id: 'blablablabla',
|
|
||||||
createdOn: new Date(),
|
|
||||||
request: {
|
|
||||||
hello: 'there',
|
|
||||||
},
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
};
|
|
||||||
|
|
||||||
mockPrisma.shortcode.findFirst.mockResolvedValueOnce(shortcode);
|
|
||||||
|
|
||||||
return expect(
|
|
||||||
shortcodeService.resolveShortcode('blablablabla')(),
|
|
||||||
).resolves.toEqualSome(<Shortcode>{
|
|
||||||
id: shortcode.id,
|
|
||||||
request: JSON.stringify(shortcode.request),
|
|
||||||
createdOn: shortcode.createdOn,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns None for non-existent shortcode', () => {
|
test('should throw SHORTCODE_NOT_FOUND error when shortcode ID is invalid', async () => {
|
||||||
mockPrisma.shortcode.findFirst.mockResolvedValueOnce(null);
|
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
|
||||||
|
'NotFoundError',
|
||||||
|
);
|
||||||
|
|
||||||
return expect(
|
const result = await shortcodeService.getShortCode('invalidID');
|
||||||
shortcodeService.resolveShortcode('blablablabla')(),
|
expect(result).toEqualLeft(SHORTCODE_NOT_FOUND);
|
||||||
).resolves.toBeNone();
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Implement create shortcode
|
|
||||||
// describe('createShortcode', () => {
|
|
||||||
// test('creates the shortcode entry in the db', async () => {
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce({
|
|
||||||
// id: 'itvalidreqid',
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: null,
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// await shortcodeService.createShortcode({ hello: 'there' })();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('returns a valid Shortcode Model object', () => {
|
|
||||||
// const shortcode = {
|
|
||||||
// id: 'blablablabla',
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: 'testuser',
|
|
||||||
// };
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce(shortcode);
|
|
||||||
//
|
|
||||||
// expect(
|
|
||||||
// shortcodeService.createShortcode({ hello: 'there' })(),
|
|
||||||
// ).resolves.toEqual(<Shortcode>{
|
|
||||||
// id: shortcode.id,
|
|
||||||
// request: JSON.stringify(shortcode.request),
|
|
||||||
// createdOn: shortcode.createdOn,
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('if a creator is specified, their UID is stored in the DB', async () => {
|
|
||||||
// const testUser: User = {
|
|
||||||
// uid: 'testuid',
|
|
||||||
// displayName: 'Test User',
|
|
||||||
// email: 'test@hoppscotch.io',
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const shortcode = {
|
|
||||||
// id: 'blablablabla',
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: testUser.uid,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce(shortcode);
|
|
||||||
//
|
|
||||||
// const result = await shortcodeService.createShortcode(
|
|
||||||
// { hello: 'there' },
|
|
||||||
// testUser,
|
|
||||||
// )();
|
|
||||||
//
|
|
||||||
// expect(mockPrisma.shortcode.create).toHaveBeenCalledWith(
|
|
||||||
// expect.objectContaining({
|
|
||||||
// data: {
|
|
||||||
// id: expect.any(String),
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: testUser.uid,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('if a creator is not specified the creator uid is stored as null', async () => {
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce({
|
|
||||||
// id: 'itvalidreqid',
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: null,
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// await shortcodeService.createShortcode({ hello: 'there' })();
|
|
||||||
//
|
|
||||||
// expect(mockPrisma.shortcode.create).toHaveBeenCalledWith(
|
|
||||||
// expect.objectContaining({
|
|
||||||
// data: {
|
|
||||||
// id: expect.any(String),
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: undefined,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('generates shortcodes which are 12 character alphanumerics', async () => {
|
|
||||||
// mockPrisma.shortcode.create.mockImplementation((args) => {
|
|
||||||
// return Promise.resolve({
|
|
||||||
// id: args.data.id,
|
|
||||||
// request: args.data.request,
|
|
||||||
// creatorUid: args.data.creatorUid,
|
|
||||||
// createdOn: args.data.createdOn,
|
|
||||||
// }) as any;
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// // Generate 100 shortcodes
|
|
||||||
// const shortcodeEntries: Shortcode[] = [];
|
|
||||||
// for (let i = 0; i < 100; i++) {
|
|
||||||
// shortcodeEntries.push(
|
|
||||||
// await shortcodeService.createShortcode({ hello: 'there' })(),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// expect(shortcodeEntries.every((entry) => entry.id.length === 12)).toBe(
|
|
||||||
// true,
|
|
||||||
// );
|
|
||||||
// expect(
|
|
||||||
// shortcodeEntries.every((entry) => /^[a-zA-Z0-9]*$/.test(entry.id)),
|
|
||||||
// ).toBe(true);
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('if creator is not specified, doesnt publish to pubsub anything', async () => {
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce({
|
|
||||||
// id: 'itvalidreqid',
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: null,
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// await shortcodeService.createShortcode({ hello: 'there' })();
|
|
||||||
//
|
|
||||||
// expect(mockPubSub.publish).not.toHaveBeenCalled();
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// test('if creator is specified, publishes to the proper pubsub topic `shortcode.{uid}.created`', async () => {
|
|
||||||
// const testUser: User = {
|
|
||||||
// uid: 'testuid',
|
|
||||||
// displayName: 'Test User',
|
|
||||||
// email: 'test@hoppscotch.io',
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// const shortcode = {
|
|
||||||
// id: 'blablablabla',
|
|
||||||
// createdOn: new Date(),
|
|
||||||
// request: {
|
|
||||||
// hello: 'there',
|
|
||||||
// },
|
|
||||||
// creatorUid: testUser.uid,
|
|
||||||
// };
|
|
||||||
//
|
|
||||||
// mockPrisma.shortcode.create.mockResolvedValueOnce(shortcode);
|
|
||||||
//
|
|
||||||
// const result = await shortcodeService.createShortcode(
|
|
||||||
// { hello: 'there' },
|
|
||||||
// testUser,
|
|
||||||
// )();
|
|
||||||
//
|
|
||||||
// expect(mockPubSub.publish).toHaveBeenCalledWith(
|
|
||||||
// `shortcode/testuid/created`,
|
|
||||||
// { ...result },
|
|
||||||
// );
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
describe('fetchUserShortCodes', () => {
|
describe('fetchUserShortCodes', () => {
|
||||||
test('returns all shortcodes for a user with no provided cursor', async () => {
|
test('should return list of shortcodes with valid inputs and no cursor', async () => {
|
||||||
const shortcodes = [
|
mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodes);
|
||||||
{
|
|
||||||
id: 'blablabla',
|
|
||||||
request: {
|
|
||||||
hello: 'there',
|
|
||||||
},
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
createdOn: new Date(),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: 'blablabla1',
|
|
||||||
request: {
|
|
||||||
hello: 'there',
|
|
||||||
},
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
createdOn: new Date(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
mockPrisma.shortcode.findMany.mockResolvedValue(shortcodes);
|
|
||||||
|
|
||||||
const result = await shortcodeService.fetchUserShortCodes(
|
const result = await shortcodeService.fetchUserShortCodes('testuser', {
|
||||||
'testuser',
|
cursor: null,
|
||||||
null,
|
|
||||||
)();
|
|
||||||
|
|
||||||
expect(mockPrisma.shortcode.findMany).toHaveBeenCalledWith({
|
|
||||||
take: 10,
|
take: 10,
|
||||||
where: {
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdOn: 'desc',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(<Shortcode[]>[
|
expect(result).toEqual(<Shortcode[]>[
|
||||||
{
|
{
|
||||||
id: shortcodes[0].id,
|
id: shortcodes[0].id,
|
||||||
@@ -300,221 +120,192 @@ describe('ShortcodeService', () => {
|
|||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('return shortcodes for a user with a provided cursor', async () => {
|
test('should return list of shortcodes with valid inputs and cursor', async () => {
|
||||||
const shortcodes = [
|
mockPrisma.shortcode.findMany.mockResolvedValue([shortcodes[1]]);
|
||||||
{
|
|
||||||
id: 'blablabla1',
|
|
||||||
request: {
|
|
||||||
hello: 'there',
|
|
||||||
},
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
createdOn: new Date(),
|
|
||||||
},
|
|
||||||
];
|
|
||||||
mockPrisma.shortcode.findMany.mockResolvedValue(shortcodes);
|
|
||||||
|
|
||||||
const result = await shortcodeService.fetchUserShortCodes(
|
const result = await shortcodeService.fetchUserShortCodes('testuser', {
|
||||||
'testuser',
|
cursor: 'blablabla',
|
||||||
'blablabla',
|
|
||||||
)();
|
|
||||||
|
|
||||||
expect(mockPrisma.shortcode.findMany).toHaveBeenCalledWith({
|
|
||||||
take: 10,
|
take: 10,
|
||||||
skip: 1,
|
|
||||||
cursor: {
|
|
||||||
id: 'blablabla',
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
creatorUid: 'testuser',
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdOn: 'desc',
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(<Shortcode[]>[
|
expect(result).toEqual(<Shortcode[]>[
|
||||||
{
|
{
|
||||||
id: shortcodes[0].id,
|
id: shortcodes[1].id,
|
||||||
request: JSON.stringify(shortcodes[0].request),
|
request: JSON.stringify(shortcodes[1].request),
|
||||||
createdOn: shortcodes[0].createdOn,
|
createdOn: shortcodes[1].createdOn,
|
||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns an empty array for an invalid cursor', async () => {
|
test('should return an empty array for an invalid cursor', async () => {
|
||||||
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
const result = await shortcodeService.fetchUserShortCodes(
|
const result = await shortcodeService.fetchUserShortCodes('testuser', {
|
||||||
'testuser',
|
cursor: 'invalidcursor',
|
||||||
'invalidcursor',
|
take: 10,
|
||||||
)();
|
});
|
||||||
|
|
||||||
expect(result).toHaveLength(0);
|
expect(result).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns an empty array for an invalid user id and null cursor', async () => {
|
test('should return an empty array for an invalid user id and null cursor', async () => {
|
||||||
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
const result = await shortcodeService.fetchUserShortCodes(
|
const result = await shortcodeService.fetchUserShortCodes('invalidid', {
|
||||||
'invalidid',
|
cursor: null,
|
||||||
null,
|
take: 10,
|
||||||
)();
|
});
|
||||||
|
|
||||||
expect(result).toHaveLength(0);
|
expect(result).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns an empty array for an invalid user id and an invalid cursor', async () => {
|
test('should return an empty array for an invalid user id and an invalid cursor', async () => {
|
||||||
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
||||||
|
|
||||||
const result = await shortcodeService.fetchUserShortCodes(
|
const result = await shortcodeService.fetchUserShortCodes('invalidid', {
|
||||||
'invalidid',
|
cursor: 'invalidcursor',
|
||||||
'invalidcursor',
|
take: 10,
|
||||||
)();
|
});
|
||||||
|
|
||||||
expect(result).toHaveLength(0);
|
expect(result).toHaveLength(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO: Implement revoke shortcode and user shortcode deletion
|
describe('createShortcode', () => {
|
||||||
// describe('revokeShortCode', () => {
|
test('should throw SHORTCODE_INVALID_JSON error if incoming request data is invalid', async () => {
|
||||||
// test('returns details of deleted shortcode, when user uid and shortcode is valid', async () => {
|
const result = await shortcodeService.createShortcode(
|
||||||
// const shortcode = {
|
'invalidRequest',
|
||||||
// id: 'blablablabla',
|
'user_uid_1',
|
||||||
// createdOn: new Date(),
|
);
|
||||||
// request: {
|
expect(result).toEqualLeft(SHORTCODE_INVALID_JSON);
|
||||||
// hello: 'there',
|
});
|
||||||
// },
|
|
||||||
// creatorUid: 'testuser',
|
test('should successfully create a new shortcode with valid user uid', async () => {
|
||||||
// };
|
// generateUniqueShortCodeID --> getShortCode
|
||||||
//
|
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
|
||||||
// mockPrisma.shortcode.delete.mockResolvedValueOnce(shortcode);
|
'NotFoundError',
|
||||||
//
|
);
|
||||||
// const result = await shortcodeService.revokeShortCode(
|
mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
|
||||||
// shortcode.id,
|
|
||||||
// shortcode.creatorUid,
|
const result = await shortcodeService.createShortcode('{}', 'user_uid_1');
|
||||||
// )();
|
expect(result).toEqualRight({
|
||||||
//
|
id: shortCodeWithUser.id,
|
||||||
// expect(mockPrisma.shortcode.delete).toHaveBeenCalledWith({
|
createdOn: shortCodeWithUser.createdOn,
|
||||||
// where: {
|
request: JSON.stringify(shortCodeWithUser.request),
|
||||||
// creator_uid_shortcode_unique: {
|
});
|
||||||
// creatorUid: shortcode.creatorUid,
|
});
|
||||||
// id: shortcode.id,
|
|
||||||
// },
|
test('should successfully create a new shortcode with null user uid', async () => {
|
||||||
// },
|
// generateUniqueShortCodeID --> getShortCode
|
||||||
// });
|
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
|
||||||
//
|
'NotFoundError',
|
||||||
// expect(result).toEqualRight(<Shortcode>{
|
);
|
||||||
// id: shortcode.id,
|
mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
|
||||||
// request: JSON.stringify(shortcode.request),
|
|
||||||
// createdOn: shortcode.createdOn,
|
const result = await shortcodeService.createShortcode('{}', null);
|
||||||
// });
|
expect(result).toEqualRight({
|
||||||
// });
|
id: shortCodeWithUser.id,
|
||||||
//
|
createdOn: shortCodeWithUser.createdOn,
|
||||||
// test('returns SHORTCODE_NOT_FOUND error when shortcode is invalid and user uid is valid', async () => {
|
request: JSON.stringify(shortCodeWithOutUser.request),
|
||||||
// mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
});
|
||||||
// expect(
|
});
|
||||||
// shortcodeService.revokeShortCode('invalid', 'testuser')(),
|
|
||||||
// ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
test('should send pubsub message to `shortcode/{uid}/created` on successful creation of shortcode', async () => {
|
||||||
// });
|
// generateUniqueShortCodeID --> getShortCode
|
||||||
//
|
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
|
||||||
// test('returns SHORTCODE_NOT_FOUND error when shortcode is valid and user uid is invalid', async () => {
|
'NotFoundError',
|
||||||
// mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
);
|
||||||
// expect(
|
mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
|
||||||
// shortcodeService.revokeShortCode('blablablabla', 'invalidUser')(),
|
|
||||||
// ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
const result = await shortcodeService.createShortcode('{}', 'user_uid_1');
|
||||||
// });
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
//
|
`shortcode/${shortCodeWithUser.creatorUid}/created`,
|
||||||
// test('returns SHORTCODE_NOT_FOUND error when both shortcode and user uid are invalid', async () => {
|
{
|
||||||
// mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
id: shortCodeWithUser.id,
|
||||||
// expect(
|
createdOn: shortCodeWithUser.createdOn,
|
||||||
// shortcodeService.revokeShortCode('invalid', 'invalid')(),
|
request: JSON.stringify(shortCodeWithUser.request),
|
||||||
// ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
},
|
||||||
// });
|
);
|
||||||
//
|
});
|
||||||
// test('if creator is specified in the deleted shortcode, pubsub message is sent to `shortcode/{uid}/revoked`', async () => {
|
});
|
||||||
// const shortcode = {
|
|
||||||
// id: 'blablablabla',
|
describe('revokeShortCode', () => {
|
||||||
// createdOn: new Date(),
|
test('should return true on successful deletion of shortcode with valid inputs', async () => {
|
||||||
// request: {
|
mockPrisma.shortcode.delete.mockResolvedValueOnce(shortCodeWithUser);
|
||||||
// hello: 'there',
|
|
||||||
// },
|
const result = await shortcodeService.revokeShortCode(
|
||||||
// creatorUid: 'testuser',
|
shortCodeWithUser.id,
|
||||||
// };
|
shortCodeWithUser.creatorUid,
|
||||||
//
|
);
|
||||||
// mockPrisma.shortcode.delete.mockResolvedValueOnce(shortcode);
|
|
||||||
//
|
expect(mockPrisma.shortcode.delete).toHaveBeenCalledWith({
|
||||||
// const result = await shortcodeService.revokeShortCode(
|
where: {
|
||||||
// shortcode.id,
|
creator_uid_shortcode_unique: {
|
||||||
// shortcode.creatorUid,
|
creatorUid: shortCodeWithUser.creatorUid,
|
||||||
// )();
|
id: shortCodeWithUser.id,
|
||||||
//
|
},
|
||||||
// expect(result).toBeRight();
|
},
|
||||||
// expect(mockPubSub.publish).toHaveBeenCalledWith(
|
});
|
||||||
// `shortcode/testuser/revoked`,
|
|
||||||
// { ...(result as any).right },
|
expect(result).toEqualRight(true);
|
||||||
// );
|
});
|
||||||
// });
|
|
||||||
// });
|
test('should return SHORTCODE_NOT_FOUND error when shortcode is invalid and user uid is valid', async () => {
|
||||||
//
|
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
||||||
// describe('deleteUserShortcodes', () => {
|
expect(
|
||||||
// test('should return undefined when the user uid is valid and contains shortcodes data', async () => {
|
shortcodeService.revokeShortCode('invalid', 'testuser'),
|
||||||
// const testUserUID = 'testuser1';
|
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
||||||
// const shortcodesList = [
|
});
|
||||||
// {
|
|
||||||
// id: 'blablablabla',
|
test('should return SHORTCODE_NOT_FOUND error when shortcode is valid and user uid is invalid', async () => {
|
||||||
// createdOn: new Date(),
|
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
||||||
// request: {
|
expect(
|
||||||
// hello: 'there',
|
shortcodeService.revokeShortCode('blablablabla', 'invalidUser'),
|
||||||
// },
|
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
||||||
// creatorUid: testUserUID,
|
});
|
||||||
// },
|
|
||||||
// ];
|
test('should return SHORTCODE_NOT_FOUND error when both shortcode and user uid are invalid', async () => {
|
||||||
//
|
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
||||||
// mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodesList);
|
expect(
|
||||||
// mockPrisma.shortcode.delete.mockResolvedValueOnce(shortcodesList[0]);
|
shortcodeService.revokeShortCode('invalid', 'invalid'),
|
||||||
//
|
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
|
||||||
// const result = await shortcodeService.deleteUserShortcodes(testUserUID)();
|
});
|
||||||
//
|
|
||||||
// expect(mockPrisma.shortcode.findMany).toHaveBeenCalledWith({
|
test('should send pubsub message to `shortcode/{uid}/revoked` on successful deletion of shortcode', async () => {
|
||||||
// where: {
|
mockPrisma.shortcode.delete.mockResolvedValueOnce(shortCodeWithUser);
|
||||||
// creatorUid: testUserUID,
|
|
||||||
// },
|
const result = await shortcodeService.revokeShortCode(
|
||||||
// });
|
shortCodeWithUser.id,
|
||||||
//
|
shortCodeWithUser.creatorUid,
|
||||||
// expect(result).toBeUndefined();
|
);
|
||||||
// });
|
|
||||||
//
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
// test('should return undefined when user uid is valid but user has no shortcode data', async () => {
|
`shortcode/${shortCodeWithUser.creatorUid}/revoked`,
|
||||||
// const testUserUID = 'testuser1';
|
{
|
||||||
// const shortcodesList = [];
|
id: shortCodeWithUser.id,
|
||||||
//
|
createdOn: shortCodeWithUser.createdOn,
|
||||||
// mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodesList);
|
request: JSON.stringify(shortCodeWithUser.request),
|
||||||
//
|
},
|
||||||
// const result = await shortcodeService.deleteUserShortcodes(testUserUID)();
|
);
|
||||||
//
|
});
|
||||||
// expect(mockPrisma.shortcode.findMany).toHaveBeenCalledWith({
|
});
|
||||||
// where: {
|
|
||||||
// creatorUid: testUserUID,
|
describe('deleteUserShortCodes', () => {
|
||||||
// },
|
test('should successfully delete all users shortcodes with valid user uid', async () => {
|
||||||
// });
|
mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 1 });
|
||||||
//
|
|
||||||
// expect(result).toBeUndefined();
|
const result = await shortcodeService.deleteUserShortCodes(
|
||||||
// });
|
shortCodeWithUser.creatorUid,
|
||||||
//
|
);
|
||||||
// test('should return undefined when the user uid is invalid', async () => {
|
expect(result).toEqual(1);
|
||||||
// const testUserUID = 'invalidtestuser';
|
});
|
||||||
// const shortcodesList = [];
|
|
||||||
//
|
test('should return 0 when user uid is invalid', async () => {
|
||||||
// mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodesList);
|
mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 0 });
|
||||||
// const result = await shortcodeService.deleteUserShortcodes(testUserUID)();
|
|
||||||
//
|
const result = await shortcodeService.deleteUserShortCodes(
|
||||||
// expect(mockPrisma.shortcode.findMany).toHaveBeenCalledWith({
|
shortCodeWithUser.creatorUid,
|
||||||
// where: {
|
);
|
||||||
// creatorUid: testUserUID,
|
expect(result).toEqual(0);
|
||||||
// },
|
});
|
||||||
// });
|
});
|
||||||
//
|
|
||||||
// expect(result).toBeUndefined();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
import { Injectable, OnModuleInit } from '@nestjs/common';
|
import { Injectable, OnModuleInit } from '@nestjs/common';
|
||||||
import { flow, pipe } from 'fp-ts/function';
|
|
||||||
import * as T from 'fp-ts/Task';
|
import * as T from 'fp-ts/Task';
|
||||||
import * as TE from 'fp-ts/TaskEither';
|
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import * as TO from 'fp-ts/TaskOption';
|
import * as TO from 'fp-ts/TaskOption';
|
||||||
import * as A from 'fp-ts/Array';
|
import * as E from 'fp-ts/Either';
|
||||||
|
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { SHORTCODE_NOT_FOUND } from 'src/errors';
|
import {
|
||||||
|
SHORTCODE_ALREADY_EXISTS,
|
||||||
|
SHORTCODE_INVALID_JSON,
|
||||||
|
SHORTCODE_NOT_FOUND,
|
||||||
|
} from 'src/errors';
|
||||||
import { User } from 'src/user/user.model';
|
import { User } from 'src/user/user.model';
|
||||||
import { UserDataHandler } from 'src/user/user.data.handler';
|
import { UserDataHandler } from 'src/user/user.data.handler';
|
||||||
import { Shortcode } from './shortcode.model';
|
import { Shortcode } from './shortcode.model';
|
||||||
|
import { Shortcode as DBShortCode } from '@prisma/client';
|
||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { UserService } from 'src/user/user.service';
|
import { UserService } from 'src/user/user.service';
|
||||||
|
import { stringToJson } from 'src/utils';
|
||||||
|
import { PaginationArgs } from 'src/types/input-types.args';
|
||||||
|
|
||||||
const SHORT_CODE_LENGTH = 12;
|
const SHORT_CODE_LENGTH = 12;
|
||||||
const SHORT_CODE_CHARS =
|
const SHORT_CODE_CHARS =
|
||||||
@@ -39,7 +43,26 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
|
|||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
private generateShortcodeID(): string {
|
/**
|
||||||
|
* Converts a Prisma Shortcode type into the Shortcode model
|
||||||
|
*
|
||||||
|
* @param shortcodeInfo Prisma Shortcode type
|
||||||
|
* @returns GQL Shortcode
|
||||||
|
*/
|
||||||
|
private returnShortCode(shortcodeInfo: DBShortCode): Shortcode {
|
||||||
|
return <Shortcode>{
|
||||||
|
id: shortcodeInfo.id,
|
||||||
|
request: JSON.stringify(shortcodeInfo.request),
|
||||||
|
createdOn: shortcodeInfo.createdOn,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a shortcode
|
||||||
|
*
|
||||||
|
* @returns generated shortcode
|
||||||
|
*/
|
||||||
|
private generateShortCodeID(): string {
|
||||||
let result = '';
|
let result = '';
|
||||||
for (let i = 0; i < SHORT_CODE_LENGTH; i++) {
|
for (let i = 0; i < SHORT_CODE_LENGTH; i++) {
|
||||||
result +=
|
result +=
|
||||||
@@ -48,189 +71,142 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async generateUniqueShortcodeID(): Promise<string> {
|
/**
|
||||||
|
* Check to see if ShortCode is already present in DB
|
||||||
|
*
|
||||||
|
* @returns Shortcode
|
||||||
|
*/
|
||||||
|
private async generateUniqueShortCodeID() {
|
||||||
while (true) {
|
while (true) {
|
||||||
const code = this.generateShortcodeID();
|
const code = this.generateShortCodeID();
|
||||||
|
|
||||||
const data = await this.resolveShortcode(code)();
|
const data = await this.getShortCode(code);
|
||||||
|
|
||||||
if (O.isNone(data)) return code;
|
if (E.isLeft(data)) return E.right(code);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resolveShortcode(shortcode: string): TO.TaskOption<Shortcode> {
|
/**
|
||||||
return pipe(
|
* Fetch details regarding a ShortCode
|
||||||
// The task to perform
|
*
|
||||||
() => this.prisma.shortcode.findFirst({ where: { id: shortcode } }),
|
* @param shortcode ShortCode
|
||||||
TO.fromTask, // Convert to Task to TaskOption
|
* @returns Either of ShortCode details or error
|
||||||
TO.chain(TO.fromNullable), // Remove nullability
|
*/
|
||||||
TO.map((data) => {
|
async getShortCode(shortcode: string) {
|
||||||
return <Shortcode>{
|
try {
|
||||||
id: data.id,
|
const shortcodeInfo = await this.prisma.shortcode.findFirstOrThrow({
|
||||||
request: JSON.stringify(data.request),
|
where: { id: shortcode },
|
||||||
createdOn: data.createdOn,
|
});
|
||||||
};
|
return E.right(this.returnShortCode(shortcodeInfo));
|
||||||
}),
|
} catch (error) {
|
||||||
);
|
return E.left(SHORTCODE_NOT_FOUND);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement create shortcode and the user service method
|
/**
|
||||||
// createShortcode(request: any, creator?: User): T.Task<Shortcode> {
|
* Create a new ShortCode
|
||||||
// return pipe(
|
*
|
||||||
// T.Do,
|
* @param request JSON string of request details
|
||||||
//
|
* @param userUID user UID, if present
|
||||||
// // Get shortcode
|
* @returns Either of ShortCode or error
|
||||||
// T.bind('shortcode', () => () => this.generateUniqueShortcodeID()),
|
*/
|
||||||
//
|
async createShortcode(request: string, userUID: string | null) {
|
||||||
// // Create
|
const shortcodeData = stringToJson(request);
|
||||||
// T.chain(
|
if (E.isLeft(shortcodeData)) return E.left(SHORTCODE_INVALID_JSON);
|
||||||
// ({ shortcode }) =>
|
|
||||||
// () =>
|
|
||||||
// this.prisma.shortcode.create({
|
|
||||||
// data: {
|
|
||||||
// id: shortcode,
|
|
||||||
// request: request,
|
|
||||||
// creatorUid: creator?.uid,
|
|
||||||
// },
|
|
||||||
// }),
|
|
||||||
// ),
|
|
||||||
//
|
|
||||||
// T.chainFirst((shortcode) => async () => {
|
|
||||||
// // Only publish event if creator is not null
|
|
||||||
// if (shortcode.creatorUid) {
|
|
||||||
// this.pubsub.publish(`shortcode/${shortcode.creatorUid}/created`, <
|
|
||||||
// Shortcode
|
|
||||||
// >{
|
|
||||||
// id: shortcode.id,
|
|
||||||
// request: JSON.stringify(shortcode.request),
|
|
||||||
// createdOn: shortcode.createdOn,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }),
|
|
||||||
//
|
|
||||||
// // Map to valid return type
|
|
||||||
// T.map(
|
|
||||||
// (data) =>
|
|
||||||
// <Shortcode>{
|
|
||||||
// id: data.id,
|
|
||||||
// request: JSON.stringify(data.request),
|
|
||||||
// createdOn: data.createdOn,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
fetchUserShortCodes(uid: string, cursor: string | null) {
|
const user = await this.userService.findUserById(userUID);
|
||||||
return pipe(
|
|
||||||
cursor,
|
const generatedShortCode = await this.generateUniqueShortCodeID();
|
||||||
O.fromNullable,
|
if (E.isLeft(generatedShortCode)) return E.left(generatedShortCode.left);
|
||||||
O.fold(
|
|
||||||
() =>
|
const createdShortCode = await this.prisma.shortcode.create({
|
||||||
pipe(
|
data: {
|
||||||
() =>
|
id: generatedShortCode.right,
|
||||||
this.prisma.shortcode.findMany({
|
request: shortcodeData.right,
|
||||||
take: 10,
|
creatorUid: O.isNone(user) ? null : user.value.uid,
|
||||||
where: {
|
},
|
||||||
creatorUid: uid,
|
});
|
||||||
},
|
|
||||||
orderBy: {
|
// Only publish event if creator is not null
|
||||||
createdOn: 'desc',
|
if (createdShortCode.creatorUid) {
|
||||||
},
|
this.pubsub.publish(
|
||||||
}),
|
`shortcode/${createdShortCode.creatorUid}/created`,
|
||||||
T.map((codes) =>
|
this.returnShortCode(createdShortCode),
|
||||||
codes.map(
|
);
|
||||||
(data) =>
|
}
|
||||||
<Shortcode>{
|
|
||||||
id: data.id,
|
return E.right(this.returnShortCode(createdShortCode));
|
||||||
request: JSON.stringify(data.request),
|
|
||||||
createdOn: data.createdOn,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
(cursor) =>
|
|
||||||
pipe(
|
|
||||||
() =>
|
|
||||||
this.prisma.shortcode.findMany({
|
|
||||||
take: 10,
|
|
||||||
skip: 1,
|
|
||||||
cursor: {
|
|
||||||
id: cursor,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
creatorUid: uid,
|
|
||||||
},
|
|
||||||
orderBy: {
|
|
||||||
createdOn: 'desc',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
T.map((codes) =>
|
|
||||||
codes.map(
|
|
||||||
(data) =>
|
|
||||||
<Shortcode>{
|
|
||||||
id: data.id,
|
|
||||||
request: JSON.stringify(data.request),
|
|
||||||
createdOn: data.createdOn,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement revoke shortcode and user shortcode deletion feature
|
/**
|
||||||
// revokeShortCode(shortcode: string, uid: string) {
|
* Fetch ShortCodes created by a User
|
||||||
// return pipe(
|
*
|
||||||
// TE.tryCatch(
|
* @param uid User Uid
|
||||||
// () =>
|
* @param args Pagination arguments
|
||||||
// this.prisma.shortcode.delete({
|
* @returns Array of ShortCodes
|
||||||
// where: {
|
*/
|
||||||
// creator_uid_shortcode_unique: {
|
async fetchUserShortCodes(uid: string, args: PaginationArgs) {
|
||||||
// creatorUid: uid,
|
const shortCodes = await this.prisma.shortcode.findMany({
|
||||||
// id: shortcode,
|
where: {
|
||||||
// },
|
creatorUid: uid,
|
||||||
// },
|
},
|
||||||
// }),
|
orderBy: {
|
||||||
// () => SHORTCODE_NOT_FOUND,
|
createdOn: 'desc',
|
||||||
// ),
|
},
|
||||||
// TE.chainFirst((shortcode) =>
|
skip: 1,
|
||||||
// TE.fromTask(() =>
|
take: args.take,
|
||||||
// this.pubsub.publish(`shortcode/${shortcode.creatorUid}/revoked`, <
|
cursor: args.cursor ? { id: args.cursor } : undefined,
|
||||||
// Shortcode
|
});
|
||||||
// >{
|
|
||||||
// id: shortcode.id,
|
|
||||||
// request: JSON.stringify(shortcode.request),
|
|
||||||
// createdOn: shortcode.createdOn,
|
|
||||||
// }),
|
|
||||||
// ),
|
|
||||||
// ),
|
|
||||||
// TE.map(
|
|
||||||
// (data) =>
|
|
||||||
// <Shortcode>{
|
|
||||||
// id: data.id,
|
|
||||||
// request: JSON.stringify(data.request),
|
|
||||||
// createdOn: data.createdOn,
|
|
||||||
// },
|
|
||||||
// ),
|
|
||||||
// );
|
|
||||||
// }
|
|
||||||
|
|
||||||
// deleteUserShortcodes(uid: string) {
|
const fetchedShortCodes: Shortcode[] = shortCodes.map((code) =>
|
||||||
// return pipe(
|
this.returnShortCode(code),
|
||||||
// () =>
|
);
|
||||||
// this.prisma.shortcode.findMany({
|
|
||||||
// where: {
|
return fetchedShortCodes;
|
||||||
// creatorUid: uid,
|
}
|
||||||
// },
|
|
||||||
// }),
|
/**
|
||||||
// T.chain(
|
* Delete a ShortCode
|
||||||
// flow(
|
*
|
||||||
// A.map((shortcode) => this.revokeShortCode(shortcode.id, uid)),
|
* @param shortcode ShortCode
|
||||||
// T.sequenceArray,
|
* @param uid User Uid
|
||||||
// ),
|
* @returns Boolean on successful deletion
|
||||||
// ),
|
*/
|
||||||
// T.map(() => undefined),
|
async revokeShortCode(shortcode: string, uid: string) {
|
||||||
// );
|
try {
|
||||||
// }
|
const deletedShortCodes = await this.prisma.shortcode.delete({
|
||||||
|
where: {
|
||||||
|
creator_uid_shortcode_unique: {
|
||||||
|
creatorUid: uid,
|
||||||
|
id: shortcode,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pubsub.publish(
|
||||||
|
`shortcode/${deletedShortCodes.creatorUid}/revoked`,
|
||||||
|
this.returnShortCode(deletedShortCodes),
|
||||||
|
);
|
||||||
|
|
||||||
|
return E.right(true);
|
||||||
|
} catch (error) {
|
||||||
|
return E.left(SHORTCODE_NOT_FOUND);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete all of Users ShortCodes
|
||||||
|
*
|
||||||
|
* @param uid User Uid
|
||||||
|
* @returns number of all deleted user ShortCodes
|
||||||
|
*/
|
||||||
|
async deleteUserShortCodes(uid: string) {
|
||||||
|
const deletedShortCodes = await this.prisma.shortcode.deleteMany({
|
||||||
|
where: {
|
||||||
|
creatorUid: uid,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return deletedShortCodes.count;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user