diff --git a/docker-compose.deploy.yml b/docker-compose.deploy.yml index ccf9f93e835..e9b7919a6ce 100644 --- a/docker-compose.deploy.yml +++ b/docker-compose.deploy.yml @@ -13,7 +13,7 @@ services: test: [ "CMD-SHELL", - "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'" + "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'", ] interval: 5s timeout: 5s @@ -28,21 +28,32 @@ services: environment: # DATABASE_URL is read from the .env file to allow the backend to connect with an external database. # This allows the backend to retain existing data and prevents database resets during deployments. - # DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch + # - DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch - ENABLE_SUBPATH_BASED_ACCESS=true env_file: - ./.env depends_on: hoppscotch-db: condition: service_healthy - command: ["sh", "-c", "pnpm exec prisma migrate deploy && node /usr/src/app/aio_run.mjs"] + command: + [ + "sh", + "-c", + "pnpm exec prisma migrate deploy && node /usr/src/app/aio_run.mjs", + ] healthcheck: test: - - CMD - - curl - - '-f' - - 'http://localhost:80' + [ + "CMD", + "curl", + "-f", + "-s", + "-o", + "/dev/null", + "-w", + "%{http_code}", + "http://localhost:80", + ] interval: 2s timeout: 10s retries: 30 - diff --git a/docker-compose.yml b/docker-compose.yml index 2d708eb71eb..3d58f7c3d26 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -135,7 +135,16 @@ services: # you are using an external postgres instance # This will be exposed at port 5432 hoppscotch-db: - profiles: ["default", "database", "just-backend", "backend", "app", "admin", "deprecated"] + profiles: + [ + "default", + "database", + "just-backend", + "backend", + "app", + "admin", + "deprecated", + ] image: postgres:15 ports: - "5432:5432" diff --git a/package.json b/package.json index 526dc6479c0..cb608711d75 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "execa@0.10.0": "2.0.0", "@babel/runtime@<7.26.10": "7.26.10", "apiconnect-wsdl": "2.0.36", - "@xmldom/xmldom": "0.8.10" + "@xmldom/xmldom": "0.8.10", + "multer@1.4.5-lts.2": "2.0.0" }, "packageExtensions": { "@hoppscotch/httpsnippet": { diff --git a/packages/hoppscotch-backend/.eslintrc.js b/packages/hoppscotch-backend/.eslintrc.js deleted file mode 100644 index f8a28acdd09..00000000000 --- a/packages/hoppscotch-backend/.eslintrc.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - "no-empty-function": "off", - "@typescript-eslint/no-empty-function": "error" - }, -}; diff --git a/packages/hoppscotch-backend/eslint.config.js b/packages/hoppscotch-backend/eslint.config.js new file mode 100644 index 00000000000..d10593407a7 --- /dev/null +++ b/packages/hoppscotch-backend/eslint.config.js @@ -0,0 +1,50 @@ +const { defineConfig, globalIgnores } = require('eslint/config'); +const tsParser = require('@typescript-eslint/parser'); +const typescriptEslintEslintPlugin = require('@typescript-eslint/eslint-plugin'); +const globals = require('globals'); +const js = require('@eslint/js'); + +const { FlatCompat } = require('@eslint/eslintrc'); + +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all, +}); + +module.exports = defineConfig([ + { + languageOptions: { + parser: tsParser, + sourceType: 'module', + + parserOptions: { + project: 'tsconfig.json', + tsconfigRootDir: __dirname, + }, + + globals: { + ...globals.node, + ...globals.jest, + }, + }, + + plugins: { + '@typescript-eslint': typescriptEslintEslintPlugin, + }, + + extends: compat.extends( + 'plugin:@typescript-eslint/recommended', + 'plugin:prettier/recommended', + ), + + rules: { + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-explicit-any': 'off', + 'no-empty-function': 'off', + '@typescript-eslint/no-empty-function': 'error', + }, + }, + globalIgnores(['**/.eslintrc.js']), +]); diff --git a/packages/hoppscotch-backend/package.json b/packages/hoppscotch-backend/package.json index b545542d59e..13b6f0dbb6c 100644 --- a/packages/hoppscotch-backend/package.json +++ b/packages/hoppscotch-backend/package.json @@ -1,6 +1,6 @@ { "name": "hoppscotch-backend", - "version": "2025.4.2", + "version": "2025.5.0", "description": "", "author": "", "private": true, @@ -30,86 +30,87 @@ "do-test": "pnpm run test" }, "dependencies": { - "@apollo/server": "4.11.0", + "@apollo/server": "4.12.1", "@nestjs-modules/mailer": "2.0.2", - "@nestjs/apollo": "12.2.0", - "@nestjs/common": "10.4.16", - "@nestjs/config": "3.2.3", - "@nestjs/core": "10.4.4", - "@nestjs/graphql": "12.2.0", - "@nestjs/jwt": "10.2.0", - "@nestjs/passport": "10.0.3", - "@nestjs/platform-express": "10.4.4", - "@nestjs/schedule": "4.1.1", - "@nestjs/swagger": "7.4.2", - "@nestjs/terminus": "10.2.3", - "@nestjs/throttler": "6.2.1", - "@prisma/client": "5.20.0", - "argon2": "0.41.1", - "bcrypt": "5.1.1", + "@nestjs/apollo": "13.1.0", + "@nestjs/common": "11.1.1", + "@nestjs/config": "4.0.2", + "@nestjs/core": "11.1.1", + "@nestjs/graphql": "13.1.0", + "@nestjs/jwt": "11.0.0", + "@nestjs/passport": "11.0.0", + "@nestjs/platform-express": "11.1.1", + "@nestjs/schedule": "6.0.0", + "@nestjs/swagger": "11.2.0", + "@nestjs/terminus": "11.0.0", + "@nestjs/throttler": "6.4.0", + "@prisma/client": "6.8.2", + "argon2": "0.43.0", + "bcrypt": "6.0.0", "class-transformer": "0.5.1", - "class-validator": "0.14.1", - "cookie": "1.0.0", + "class-validator": "0.14.2", + "cookie": "1.0.2", "cookie-parser": "1.4.7", - "cron": "3.1.7", - "express": "4.21.1", + "cron": "4.3.0", + "express": "5.1.0", "express-session": "1.18.1", - "fp-ts": "2.16.9", - "graphql": "16.9.0", - "graphql-query-complexity": "1.0.0", - "graphql-redis-subscriptions": "2.6.1", - "graphql-subscriptions": "2.0.0", + "fp-ts": "2.16.10", + "graphql": "16.11.0", + "graphql-query-complexity": "1.1.0", + "graphql-redis-subscriptions": "2.7.0", + "graphql-subscriptions": "3.0.0", "handlebars": "4.7.8", - "io-ts": "2.2.21", - "luxon": "3.5.0", - "nodemailer": "6.9.15", + "io-ts": "2.2.22", + "luxon": "3.6.1", + "nodemailer": "7.0.3", "passport": "0.7.0", "passport-github2": "0.1.12", "passport-google-oauth20": "2.0.0", "passport-jwt": "4.0.1", "passport-local": "1.0.0", "passport-microsoft": "2.1.0", - "posthog-node": "4.2.0", - "prisma": "5.20.0", + "posthog-node": "4.17.1", + "prisma": "6.8.2", "reflect-metadata": "0.2.2", "rimraf": "6.0.1", - "rxjs": "7.8.1" + "rxjs": "7.8.2" }, "devDependencies": { - "@nestjs/cli": "10.4.5", - "@nestjs/schematics": "10.1.4", - "@nestjs/testing": "10.4.4", + "@eslint/eslintrc": "3.3.1", + "@eslint/js": "9.27.0", + "@nestjs/cli": "11.0.7", + "@nestjs/schematics": "11.0.5", + "@nestjs/testing": "11.1.1", "@relmify/jest-fp-ts": "2.1.1", "@types/bcrypt": "5.0.2", - "@types/cookie": "0.6.0", - "@types/cookie-parser": "1.4.7", - "@types/express": "5.0.0", - "@types/jest": "29.5.13", - "@types/luxon": "3.4.2", - "@types/node": "22.7.5", - "@types/nodemailer": "6.4.16", + "@types/cookie-parser": "1.4.8", + "@types/express": "5.0.2", + "@types/jest": "29.5.14", + "@types/luxon": "3.6.2", + "@types/node": "22.15.19", + "@types/nodemailer": "6.4.17", "@types/passport-github2": "1.2.9", "@types/passport-google-oauth20": "2.0.16", "@types/passport-jwt": "4.0.1", "@types/passport-microsoft": "1.0.3", - "@types/supertest": "6.0.2", - "@typescript-eslint/eslint-plugin": "8.8.1", - "@typescript-eslint/parser": "8.8.1", + "@types/supertest": "6.0.3", + "@typescript-eslint/eslint-plugin": "8.32.1", + "@typescript-eslint/parser": "8.32.1", "cross-env": "7.0.3", - "eslint": "8.57.0", - "eslint-config-prettier": "9.1.0", - "eslint-plugin-prettier": "5.2.1", + "eslint": "9.27.0", + "eslint-config-prettier": "10.1.5", + "eslint-plugin-prettier": "5.4.0", + "globals": "16.1.0", "jest": "29.7.0", "jest-mock-extended": "4.0.0-beta1", - "jwt": "link:@types/nestjs/jwt", - "prettier": "3.3.3", + "prettier": "3.5.3", "source-map-support": "0.5.21", - "supertest": "7.0.0", - "ts-jest": "29.2.5", - "ts-loader": "9.5.1", + "supertest": "7.1.1", + "ts-jest": "29.3.4", + "ts-loader": "9.5.2", "ts-node": "10.9.2", "tsconfig-paths": "4.2.0", - "typescript": "5.5.4" + "typescript": "5.8.3" }, "jest": { "moduleFileExtensions": [ diff --git a/packages/hoppscotch-backend/prisma/migrations/20250306054346_team_access_role_enum/migration.sql b/packages/hoppscotch-backend/prisma/migrations/20250306054346_team_access_role_enum/migration.sql new file mode 100644 index 00000000000..e695e0ddf71 --- /dev/null +++ b/packages/hoppscotch-backend/prisma/migrations/20250306054346_team_access_role_enum/migration.sql @@ -0,0 +1,2 @@ +-- Alter the enum type "TeamMemberRole" to "TeamAccessRole" +ALTER TYPE "TeamMemberRole" RENAME TO "TeamAccessRole"; diff --git a/packages/hoppscotch-backend/prisma/schema.prisma b/packages/hoppscotch-backend/prisma/schema.prisma index 45d17737212..5d0a0f50a97 100644 --- a/packages/hoppscotch-backend/prisma/schema.prisma +++ b/packages/hoppscotch-backend/prisma/schema.prisma @@ -20,7 +20,7 @@ model Team { model TeamMember { id String @id @default(uuid()) // Membership ID - role TeamMemberRole + role TeamAccessRole userUid String teamID String team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) @@ -34,7 +34,7 @@ model TeamInvitation { team Team @relation(fields: [teamID], references: [id], onDelete: Cascade) creatorUid String inviteeEmail String - inviteeRole TeamMemberRole + inviteeRole TeamAccessRole @@unique([teamID, inviteeEmail]) @@index([teamID]) @@ -207,7 +207,7 @@ model UserCollection { updatedOn DateTime @updatedAt @db.Timestamp(3) } -enum TeamMemberRole { +enum TeamAccessRole { OWNER VIEWER EDITOR diff --git a/packages/hoppscotch-backend/src/access-token/access-token.controller.ts b/packages/hoppscotch-backend/src/access-token/access-token.controller.ts index 4b1bd846005..4812f02ea7b 100644 --- a/packages/hoppscotch-backend/src/access-token/access-token.controller.ts +++ b/packages/hoppscotch-backend/src/access-token/access-token.controller.ts @@ -4,7 +4,6 @@ import { Controller, Delete, Get, - HttpStatus, Param, ParseIntPipe, Post, diff --git a/packages/hoppscotch-backend/src/access-token/access-token.module.ts b/packages/hoppscotch-backend/src/access-token/access-token.module.ts index 344f6f07f56..0e33d418173 100644 --- a/packages/hoppscotch-backend/src/access-token/access-token.module.ts +++ b/packages/hoppscotch-backend/src/access-token/access-token.module.ts @@ -1,18 +1,12 @@ import { Module } from '@nestjs/common'; import { AccessTokenController } from './access-token.controller'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { AccessTokenService } from './access-token.service'; import { TeamCollectionModule } from 'src/team-collection/team-collection.module'; import { TeamEnvironmentsModule } from 'src/team-environments/team-environments.module'; import { TeamModule } from 'src/team/team.module'; @Module({ - imports: [ - PrismaModule, - TeamCollectionModule, - TeamEnvironmentsModule, - TeamModule, - ], + imports: [TeamCollectionModule, TeamEnvironmentsModule, TeamModule], controllers: [AccessTokenController], providers: [AccessTokenService], exports: [AccessTokenService], diff --git a/packages/hoppscotch-backend/src/access-token/access-token.service.ts b/packages/hoppscotch-backend/src/access-token/access-token.service.ts index e6a99774649..563a998d9c6 100644 --- a/packages/hoppscotch-backend/src/access-token/access-token.service.ts +++ b/packages/hoppscotch-backend/src/access-token/access-token.service.ts @@ -12,6 +12,7 @@ import { import { CreateAccessTokenResponse } from './helper'; import { PersonalAccessToken } from '@prisma/client'; import { AccessToken } from 'src/types/AccessToken'; + @Injectable() export class AccessTokenService { constructor(private readonly prisma: PrismaService) {} diff --git a/packages/hoppscotch-backend/src/admin/admin.module.ts b/packages/hoppscotch-backend/src/admin/admin.module.ts index aa5c0d1540b..1fb4c274ac4 100644 --- a/packages/hoppscotch-backend/src/admin/admin.module.ts +++ b/packages/hoppscotch-backend/src/admin/admin.module.ts @@ -1,8 +1,6 @@ import { Module } from '@nestjs/common'; import { AdminResolver } from './admin.resolver'; import { AdminService } from './admin.service'; -import { PrismaModule } from '../prisma/prisma.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; import { UserModule } from '../user/user.module'; import { TeamModule } from '../team/team.module'; import { TeamInvitationModule } from '../team-invitation/team-invitation.module'; @@ -16,8 +14,6 @@ import { UserHistoryModule } from 'src/user-history/user-history.module'; @Module({ imports: [ - PrismaModule, - PubSubModule, UserModule, TeamModule, TeamInvitationModule, diff --git a/packages/hoppscotch-backend/src/admin/admin.resolver.ts b/packages/hoppscotch-backend/src/admin/admin.resolver.ts index 35e02718602..bb1469c4121 100644 --- a/packages/hoppscotch-backend/src/admin/admin.resolver.ts +++ b/packages/hoppscotch-backend/src/admin/admin.resolver.ts @@ -2,9 +2,7 @@ import { Args, ID, Mutation, - Parent, Query, - ResolveField, Resolver, Subscription, } from '@nestjs/graphql'; @@ -33,7 +31,7 @@ import { UserDeletionResult } from 'src/user/user.model'; @Resolver(() => Admin) export class AdminResolver { constructor( - private adminService: AdminService, + private readonly adminService: AdminService, private readonly pubsub: PubSubService, ) {} diff --git a/packages/hoppscotch-backend/src/admin/admin.service.spec.ts b/packages/hoppscotch-backend/src/admin/admin.service.spec.ts index a4370d44b75..766a5c5eb0e 100644 --- a/packages/hoppscotch-backend/src/admin/admin.service.spec.ts +++ b/packages/hoppscotch-backend/src/admin/admin.service.spec.ts @@ -16,7 +16,6 @@ import { ONLY_ONE_ADMIN_ACCOUNT, USER_ALREADY_INVITED, USER_INVITATION_DELETION_FAILED, - USER_NOT_FOUND, } from '../errors'; import { ShortcodeService } from 'src/shortcode/shortcode.service'; import { ConfigService } from '@nestjs/config'; @@ -44,8 +43,8 @@ const adminService = new AdminService( mockTeamRequestService, mockTeamEnvironmentsService, mockTeamInvitationService, - mockPubSub as any, - mockPrisma as any, + mockPubSub, + mockPrisma, mockMailerService, mockShortcodeService, mockConfigService, @@ -99,14 +98,11 @@ const dbAdminUsers: DbUser[] = [ describe('AdminService', () => { describe('fetchInvitedUsers', () => { test('should resolve right and apply pagination correctly', async () => { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore mockPrisma.user.findMany.mockResolvedValue([dbAdminUsers[0]]); - // @ts-ignore mockPrisma.invitedUsers.findMany.mockResolvedValue(invitedUsers); const paginationArgs: OffsetPaginationArgs = { take: 5, skip: 2 }; - const results = await adminService.fetchInvitedUsers(paginationArgs); + await adminService.fetchInvitedUsers(paginationArgs); expect(mockPrisma.invitedUsers.findMany).toHaveBeenCalledWith({ ...paginationArgs, @@ -126,10 +122,7 @@ describe('AdminService', () => { test('should resolve right and return an array of invited users', async () => { const paginationArgs: OffsetPaginationArgs = { take: 10, skip: 0 }; - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore mockPrisma.user.findMany.mockResolvedValue([dbAdminUsers[0]]); - // @ts-ignore mockPrisma.invitedUsers.findMany.mockResolvedValue(invitedUsers); const results = await adminService.fetchInvitedUsers(paginationArgs); @@ -217,7 +210,6 @@ describe('AdminService', () => { }); test('should resolve right and return deleted invitee email', async () => { - const adminUid = 'adminUid'; mockPrisma.invitedUsers.deleteMany.mockResolvedValueOnce({ count: 1 }); const result = await adminService.revokeUserInvitations([ diff --git a/packages/hoppscotch-backend/src/admin/admin.service.ts b/packages/hoppscotch-backend/src/admin/admin.service.ts index 8bd257d802c..d8192dcd2ae 100644 --- a/packages/hoppscotch-backend/src/admin/admin.service.ts +++ b/packages/hoppscotch-backend/src/admin/admin.service.ts @@ -12,7 +12,6 @@ import { INVALID_EMAIL, ONLY_ONE_ADMIN_ACCOUNT, TEAM_INVITE_ALREADY_MEMBER, - TEAM_INVITE_NO_INVITE_FOUND, USERS_NOT_FOUND, USER_ALREADY_INVITED, USER_INVITATION_DELETION_FAILED, @@ -26,7 +25,7 @@ import { TeamCollectionService } from '../team-collection/team-collection.servic import { TeamRequestService } from '../team-request/team-request.service'; import { TeamEnvironmentsService } from '../team-environments/team-environments.service'; import { TeamInvitationService } from '../team-invitation/team-invitation.service'; -import { TeamMemberRole } from '../team/team.model'; +import { TeamAccessRole } from '../team/team.model'; import { ShortcodeService } from 'src/shortcode/shortcode.service'; import { ConfigService } from '@nestjs/config'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; @@ -292,9 +291,9 @@ export class AdminService { async changeRoleOfUserTeam( userUid: string, teamID: string, - newRole: TeamMemberRole, + newRole: TeamAccessRole, ) { - const updatedTeamMember = await this.teamService.updateTeamMemberRole( + const updatedTeamMember = await this.teamService.updateTeamAccessRole( teamID, userUid, newRole, @@ -325,7 +324,7 @@ export class AdminService { * @param role team member role for the user * @returns an Either of boolean or error */ - async addUserToTeam(teamID: string, userEmail: string, role: TeamMemberRole) { + async addUserToTeam(teamID: string, userEmail: string, role: TeamAccessRole) { if (!validateEmail(userEmail)) return E.left(INVALID_EMAIL); const user = await this.userService.findUserByEmail(userEmail); @@ -464,7 +463,7 @@ export class AdminService { }); const nonAdminUsers = allUsersList.filter((user) => !user.isAdmin); - let deletedUserEmails: string[] = []; + const deletedUserEmails: string[] = []; // step 3: delete non-admin users const deletionPromises = nonAdminUsers.map((user) => { diff --git a/packages/hoppscotch-backend/src/admin/infra.resolver.ts b/packages/hoppscotch-backend/src/admin/infra.resolver.ts index 045c1a3f010..c3ce53e4d1f 100644 --- a/packages/hoppscotch-backend/src/admin/infra.resolver.ts +++ b/packages/hoppscotch-backend/src/admin/infra.resolver.ts @@ -39,8 +39,8 @@ import { ServiceStatus } from 'src/infra-config/helper'; @Resolver(() => Infra) export class InfraResolver { constructor( - private adminService: AdminService, - private infraConfigService: InfraConfigService, + private readonly adminService: AdminService, + private readonly infraConfigService: InfraConfigService, ) {} @Query(() => Infra, { diff --git a/packages/hoppscotch-backend/src/admin/input-types.args.ts b/packages/hoppscotch-backend/src/admin/input-types.args.ts index dadd1fa10a5..129a1357ef6 100644 --- a/packages/hoppscotch-backend/src/admin/input-types.args.ts +++ b/packages/hoppscotch-backend/src/admin/input-types.args.ts @@ -1,5 +1,5 @@ import { Field, ID, ArgsType } from '@nestjs/graphql'; -import { TeamMemberRole } from '../team/team.model'; +import { TeamAccessRole } from '../team/team.model'; @ArgsType() export class ChangeUserRoleInTeamArgs { @@ -14,11 +14,11 @@ export class ChangeUserRoleInTeamArgs { }) teamID: string; - @Field(() => TeamMemberRole, { + @Field(() => TeamAccessRole, { name: 'newRole', description: 'updated team role', }) - newRole: TeamMemberRole; + newRole: TeamAccessRole; } @ArgsType() @@ -29,11 +29,11 @@ export class AddUserToTeamArgs { }) teamID: string; - @Field(() => TeamMemberRole, { + @Field(() => TeamAccessRole, { name: 'role', description: 'The role of the user to add in the team', }) - role: TeamMemberRole; + role: TeamAccessRole; @Field({ name: 'userEmail', diff --git a/packages/hoppscotch-backend/src/app.module.ts b/packages/hoppscotch-backend/src/app.module.ts index f61bd187d1a..a42076510e9 100644 --- a/packages/hoppscotch-backend/src/app.module.ts +++ b/packages/hoppscotch-backend/src/app.module.ts @@ -8,7 +8,10 @@ import { UserSettingsModule } from './user-settings/user-settings.module'; import { UserEnvironmentsModule } from './user-environment/user-environments.module'; import { UserRequestModule } from './user-request/user-request.module'; import { UserHistoryModule } from './user-history/user-history.module'; -import { subscriptionContextCookieParser, extractAccessTokenFromAuthRecords } from './auth/helper'; +import { + subscriptionContextCookieParser, + extractAccessTokenFromAuthRecords, +} from './auth/helper'; import { TeamModule } from './team/team.module'; import { TeamEnvironmentsModule } from './team-environments/team-environments.module'; import { TeamCollectionModule } from './team-collection/team-collection.module'; @@ -24,12 +27,14 @@ import { ConfigModule, ConfigService } from '@nestjs/config'; import { InfraConfigModule } from './infra-config/infra-config.module'; import { loadInfraConfiguration } from './infra-config/helper'; import { MailerModule } from './mailer/mailer.module'; -import { PosthogModule } from './posthog/posthog.module'; +import { PostHogModule } from './posthog/posthog.module'; import { ScheduleModule } from '@nestjs/schedule'; import { HealthModule } from './health/health.module'; import { AccessTokenModule } from './access-token/access-token.module'; import { UserLastActiveOnInterceptor } from './interceptors/user-last-active-on.interceptor'; import { InfraTokenModule } from './infra-token/infra-token.module'; +import { PrismaModule } from './prisma/prisma.module'; +import { PubSubModule } from './pubsub/pubsub.module'; @Module({ imports: [ @@ -56,8 +61,9 @@ import { InfraTokenModule } from './infra-token/infra-token.module'; const websocketHeaders = websocket?.upgradeReq?.headers; try { - const accessToken = extractAccessTokenFromAuthRecords(connectionParams); - const authorization = `Bearer ${accessToken}` + const accessToken = + extractAccessTokenFromAuthRecords(connectionParams); + const authorization = `Bearer ${accessToken}`; return { headers: { ...websocketHeaders, authorization } }; } catch (authError) { @@ -74,7 +80,7 @@ import { InfraTokenModule } from './infra-token/infra-token.module'; return { headers: { ...websocketHeaders, cookies } }; } - } + }, }, }, context: ({ req, res, connection }) => ({ @@ -95,6 +101,8 @@ import { InfraTokenModule } from './infra-token/infra-token.module'; }, ], }), + PrismaModule, + PubSubModule, MailerModule.register(), UserModule, AuthModule.register(), @@ -111,7 +119,7 @@ import { InfraTokenModule } from './infra-token/infra-token.module'; UserCollectionModule, ShortcodeModule, InfraConfigModule, - PosthogModule, + PostHogModule, ScheduleModule.forRoot(), HealthModule, AccessTokenModule, diff --git a/packages/hoppscotch-backend/src/auth/auth.controller.ts b/packages/hoppscotch-backend/src/auth/auth.controller.ts index dd7833baacd..72420f4dac7 100644 --- a/packages/hoppscotch-backend/src/auth/auth.controller.ts +++ b/packages/hoppscotch-backend/src/auth/auth.controller.ts @@ -204,7 +204,7 @@ export class AuthController { if (!redirectUri || !redirectUri.startsWith('http://localhost')) { throwHTTPErr({ message: 'Invalid desktop callback URL', - statusCode: 400 + statusCode: 400, }); } diff --git a/packages/hoppscotch-backend/src/auth/auth.module.ts b/packages/hoppscotch-backend/src/auth/auth.module.ts index 8bce41d82e7..4d5eb1703c3 100644 --- a/packages/hoppscotch-backend/src/auth/auth.module.ts +++ b/packages/hoppscotch-backend/src/auth/auth.module.ts @@ -2,7 +2,6 @@ import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UserModule } from 'src/user/user.module'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { PassportModule } from '@nestjs/passport'; import { JwtModule } from '@nestjs/jwt'; import { JwtStrategy } from './strategies/jwt.strategy'; @@ -20,7 +19,6 @@ import { InfraConfigModule } from 'src/infra-config/infra-config.module'; @Module({ imports: [ - PrismaModule, UserModule, PassportModule, JwtModule.registerAsync({ diff --git a/packages/hoppscotch-backend/src/auth/auth.service.spec.ts b/packages/hoppscotch-backend/src/auth/auth.service.spec.ts index 75a6ba7ab58..43f810e1ce1 100644 --- a/packages/hoppscotch-backend/src/auth/auth.service.spec.ts +++ b/packages/hoppscotch-backend/src/auth/auth.service.spec.ts @@ -1,7 +1,7 @@ import { HttpStatus } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { Account, VerificationToken } from '@prisma/client'; -import { mockDeep, mockFn } from 'jest-mock-extended'; +import { mockDeep } from 'jest-mock-extended'; import { INVALID_EMAIL, INVALID_MAGIC_LINK_DATA, @@ -82,9 +82,6 @@ let nowPlus30 = new Date(); nowPlus30.setMinutes(nowPlus30.getMinutes() + 30000); nowPlus30 = new Date(nowPlus30); -const encodedRefreshToken = - '$argon2id$v=19$m=65536,t=3,p=4$JTP8yZ8YXMHdafb5pB9Rfg$tdZrILUxMb9dQbu0uuyeReLgKxsgYnyUNbc5ZxQmy5I'; - describe('signInMagicLink', () => { test('Should throw error if email is not in valid format', async () => { const result = await authService.signInMagicLink('bbbgmail.com', 'admin'); diff --git a/packages/hoppscotch-backend/src/auth/auth.service.ts b/packages/hoppscotch-backend/src/auth/auth.service.ts index c04bdb59f8e..b0f8df4f377 100644 --- a/packages/hoppscotch-backend/src/auth/auth.service.ts +++ b/packages/hoppscotch-backend/src/auth/auth.service.ts @@ -34,12 +34,12 @@ import { InfraConfigService } from 'src/infra-config/infra-config.service'; @Injectable() export class AuthService { constructor( - private usersService: UserService, - private prismaService: PrismaService, - private jwtService: JwtService, + private readonly usersService: UserService, + private readonly prisma: PrismaService, + private readonly jwtService: JwtService, private readonly mailerService: MailerService, private readonly configService: ConfigService, - private infraConfigService: InfraConfigService, + private readonly infraConfigService: InfraConfigService, ) {} /** @@ -59,7 +59,7 @@ export class AuthService { .toISO() .toString(); - const idToken = await this.prismaService.verificationToken.create({ + const idToken = await this.prisma.verificationToken.create({ data: { deviceIdentifier: salt, userUid: user.uid, @@ -78,15 +78,14 @@ export class AuthService { */ private async validatePasswordlessTokens(magicLinkTokens: VerifyMagicDto) { try { - const tokens = - await this.prismaService.verificationToken.findUniqueOrThrow({ - where: { - passwordless_deviceIdentifier_tokens: { - deviceIdentifier: magicLinkTokens.deviceIdentifier, - token: magicLinkTokens.token, - }, + const tokens = await this.prisma.verificationToken.findUniqueOrThrow({ + where: { + passwordless_deviceIdentifier_tokens: { + deviceIdentifier: magicLinkTokens.deviceIdentifier, + token: magicLinkTokens.token, }, - }); + }, + }); return O.some(tokens); } catch (error) { return O.none; @@ -160,7 +159,7 @@ export class AuthService { ) { try { const deletedPasswordlessToken = - await this.prismaService.verificationToken.delete({ + await this.prisma.verificationToken.delete({ where: { passwordless_deviceIdentifier_tokens: { deviceIdentifier: passwordlessTokens.deviceIdentifier, @@ -182,7 +181,7 @@ export class AuthService { * @returns Either of existing user provider Account */ async checkIfProviderAccountExists(user: AuthUser, SSOUserData) { - const provider = await this.prismaService.account.findUnique({ + const provider = await this.prisma.account.findUnique({ where: { verifyProviderAccount: { provider: SSOUserData.provider, @@ -256,9 +255,8 @@ export class AuthService { async verifyMagicLinkTokens( magicLinkIDTokens: VerifyMagicDto, ): Promise | E.Left> { - const passwordlessTokens = await this.validatePasswordlessTokens( - magicLinkIDTokens, - ); + const passwordlessTokens = + await this.validatePasswordlessTokens(magicLinkIDTokens); if (O.isNone(passwordlessTokens)) return E.left({ message: INVALID_MAGIC_LINK_DATA, diff --git a/packages/hoppscotch-backend/src/auth/helper.ts b/packages/hoppscotch-backend/src/auth/helper.ts index c3316910e02..d67f93737cd 100644 --- a/packages/hoppscotch-backend/src/auth/helper.ts +++ b/packages/hoppscotch-backend/src/auth/helper.ts @@ -3,7 +3,12 @@ import { DateTime } from 'luxon'; import { AuthTokens } from 'src/types/AuthTokens'; import { Response } from 'express'; import * as cookie from 'cookie'; -import { AUTH_HEADER_NOT_FOUND, AUTH_PROVIDER_NOT_SPECIFIED, COOKIES_NOT_FOUND, INVALID_AUTH_HEADER } from 'src/errors'; +import { + AUTH_HEADER_NOT_FOUND, + AUTH_PROVIDER_NOT_SPECIFIED, + COOKIES_NOT_FOUND, + INVALID_AUTH_HEADER, +} from 'src/errors'; import { throwErr } from 'src/utils'; import { ConfigService } from '@nestjs/config'; import { IncomingHttpHeaders } from 'http'; @@ -118,8 +123,8 @@ export function authProviderCheck( const envVariables = VITE_ALLOWED_AUTH_PROVIDERS ? VITE_ALLOWED_AUTH_PROVIDERS.split(',').map((provider) => - provider.trim().toUpperCase(), - ) + provider.trim().toUpperCase(), + ) : []; if (!envVariables.includes(provider.toUpperCase())) return false; @@ -132,8 +137,11 @@ export function authProviderCheck( * @param headers HTTP request headers containing auth tokens * @returns Cookie's key-value pairs */ -export const extractCookieAsKeyValuesFromHeaders = (headers: IncomingHttpHeaders) => { - const cookieHeader = headers['cookie'] || headers['Cookie'] || headers['COOKIE']; +export const extractCookieAsKeyValuesFromHeaders = ( + headers: IncomingHttpHeaders, +) => { + const cookieHeader = + headers['cookie'] || headers['Cookie'] || headers['COOKIE']; if (!cookieHeader) { throw new HttpException(COOKIES_NOT_FOUND, 400, { @@ -141,15 +149,21 @@ export const extractCookieAsKeyValuesFromHeaders = (headers: IncomingHttpHeaders }); } - const cookieStr = Array.isArray(cookieHeader) ? cookieHeader[0] : cookieHeader; - - const kv = cookieStr.split(';') - .map(cookie => cookie.trim()) - .reduce((acc, curr) => { - const [key, value] = curr.split('='); - acc[key] = value; - return acc; - }, {} as Record); + const cookieStr = Array.isArray(cookieHeader) + ? cookieHeader[0] + : cookieHeader; + + const kv = cookieStr + .split(';') + .map((cookie) => cookie.trim()) + .reduce( + (acc, curr) => { + const [key, value] = curr.split('='); + acc[key] = value; + return acc; + }, + {} as Record, + ); return kv; }; @@ -159,10 +173,15 @@ export const extractCookieAsKeyValuesFromHeaders = (headers: IncomingHttpHeaders * @param headers HTTP request headers containing auth tokens * @returns AuthTokens for JWT strategy to use */ -export const extractAuthTokensFromCookieHeaders = (headers: IncomingHttpHeaders) => { +export const extractAuthTokensFromCookieHeaders = ( + headers: IncomingHttpHeaders, +) => { const cookieKV = extractCookieAsKeyValuesFromHeaders(headers); - if (!cookieKV[AuthTokenType.ACCESS_TOKEN] || !cookieKV[AuthTokenType.REFRESH_TOKEN]) { + if ( + !cookieKV[AuthTokenType.ACCESS_TOKEN] || + !cookieKV[AuthTokenType.REFRESH_TOKEN] + ) { throw new HttpException(COOKIES_NOT_FOUND, 400, { cause: new Error(COOKIES_NOT_FOUND), }); @@ -179,7 +198,9 @@ export const extractAuthTokensFromCookieHeaders = (headers: IncomingHttpHeaders) * @param headers HTTP request headers containing access tokens * @returns AccessTokens for JWT strategy to use */ -export const extractAccessTokensFromCookieHeaders = (headers: IncomingHttpHeaders) => { +export const extractAccessTokensFromCookieHeaders = ( + headers: IncomingHttpHeaders, +) => { const cookieKV = extractCookieAsKeyValuesFromHeaders(headers); if (!cookieKV[AuthTokenType.ACCESS_TOKEN]) { @@ -198,7 +219,9 @@ export const extractAccessTokensFromCookieHeaders = (headers: IncomingHttpHeader * @param headers HTTP request headers containing bearer token * @returns AccessTokens for JWT strategy */ -export const extractAccessTokenFromAuthRecords = (headers: IncomingHttpHeaders) => { +export const extractAccessTokenFromAuthRecords = ( + headers: IncomingHttpHeaders, +) => { const authHeader = headers['authorization'] || headers['Authorization']; if (!authHeader) { throw new HttpException(AUTH_HEADER_NOT_FOUND, 400, { diff --git a/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts index 2b7a7d32f74..c3bb68bd830 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/jwt.strategy.ts @@ -1,6 +1,10 @@ import { ExtractJwt, Strategy } from 'passport-jwt'; import { PassportStrategy } from '@nestjs/passport'; -import { Injectable, ForbiddenException, UnauthorizedException } from '@nestjs/common'; +import { + Injectable, + ForbiddenException, + UnauthorizedException, +} from '@nestjs/common'; import { AccessTokenPayload } from 'src/types/AuthTokens'; import { UserService } from 'src/user/user.service'; import { ConfigService } from '@nestjs/config'; @@ -8,7 +12,11 @@ import { Request } from 'express'; import * as O from 'fp-ts/Option'; import * as E from 'fp-ts/Either'; import { pipe } from 'fp-ts/function'; -import { COOKIES_NOT_FOUND, INVALID_ACCESS_TOKEN, USER_NOT_FOUND } from 'src/errors'; +import { + COOKIES_NOT_FOUND, + INVALID_ACCESS_TOKEN, + USER_NOT_FOUND, +} from 'src/errors'; /** * Extracts an access token from a cookie in the request. @@ -19,7 +27,7 @@ import { COOKIES_NOT_FOUND, INVALID_ACCESS_TOKEN, USER_NOT_FOUND } from 'src/err const extractFromCookie = (request: Request): O.Option => pipe( O.fromNullable(request.cookies), - O.chain(cookies => O.fromNullable(cookies['access_token'])) + O.chain((cookies) => O.fromNullable(cookies['access_token'])), ); /** @@ -35,13 +43,15 @@ const extractFromAuthHeaders = (request: Request): O.Option => // see `gql-auth.guard` for more info. O.fromNullable( request?.headers?.authorization || - (request && 'authorization' in request ? request['authorization'] : undefined) + (request && 'authorization' in request + ? request['authorization'] + : undefined), ), - O.chain(auth => + O.chain((auth) => typeof auth === 'string' && auth.startsWith('Bearer ') ? O.some(auth.slice(7)) - : O.none - ) + : O.none, + ), ); /** @@ -59,7 +69,7 @@ const extractToken = (request: Request): E.Either => // `COOKIES_NOT_FOUND` for backwards compatibility. E.fromOption(() => { return new ForbiddenException(COOKIES_NOT_FOUND); - }) + }), ); @Injectable() @@ -74,9 +84,13 @@ export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') { pipe( extractToken(request), E.fold( - error => { throw error; }, - token => { return token } - ) + (error) => { + throw error; + }, + (token) => { + return token; + }, + ), ), ]), secretOrKey: configService.get('JWT_SECRET'), diff --git a/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts b/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts index 6f42321fba1..c9351aad35f 100644 --- a/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts +++ b/packages/hoppscotch-backend/src/auth/strategies/rt-jwt.strategy.ts @@ -27,7 +27,7 @@ export class RTJwtStrategy extends PassportStrategy(Strategy, 'jwt-refresh') { (request: Request) => { const RTCookie = request.cookies['refresh_token']; if (!RTCookie) { - console.error("`refresh_token` not found") + console.error('`refresh_token` not found'); throw new ForbiddenException(COOKIES_NOT_FOUND); } return RTCookie; diff --git a/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts b/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts index d59e77af611..d4f6d900ef8 100644 --- a/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts +++ b/packages/hoppscotch-backend/src/guards/rest-pat-auth.guard.ts @@ -36,9 +36,7 @@ export class PATAuthGuard implements CanActivate { const today = DateTime.now().toISO(); if (accessToken.expiresOn.toISOString() > today) return true; - throw new BadRequestException( - createCLIErrorResponse(ACCESS_TOKEN_EXPIRED), - ); + throw new BadRequestException(createCLIErrorResponse(ACCESS_TOKEN_EXPIRED)); } private extractTokenFromHeader(request: Request): string | undefined { diff --git a/packages/hoppscotch-backend/src/health/health.module.ts b/packages/hoppscotch-backend/src/health/health.module.ts index 716456440b5..deedde58680 100644 --- a/packages/hoppscotch-backend/src/health/health.module.ts +++ b/packages/hoppscotch-backend/src/health/health.module.ts @@ -1,10 +1,9 @@ import { Module } from '@nestjs/common'; import { HealthController } from './health.controller'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { TerminusModule } from '@nestjs/terminus'; @Module({ - imports: [PrismaModule, TerminusModule], + imports: [TerminusModule], controllers: [HealthController], }) export class HealthModule {} diff --git a/packages/hoppscotch-backend/src/infra-config/helper.ts b/packages/hoppscotch-backend/src/infra-config/helper.ts index 6348e51d94a..8e9101bd888 100644 --- a/packages/hoppscotch-backend/src/infra-config/helper.ts +++ b/packages/hoppscotch-backend/src/infra-config/helper.ts @@ -1,7 +1,6 @@ import { AuthProvider } from 'src/auth/helper'; import { AUTH_PROVIDER_NOT_CONFIGURED, - DATABASE_TABLE_NOT_EXIST, ENV_INVALID_DATA_ENCRYPTION_KEY, } from 'src/errors'; import { PrismaService } from 'src/prisma/prisma.service'; @@ -68,7 +67,7 @@ export async function loadInfraConfiguration() { const infraConfigs = await prisma.infraConfig.findMany(); - let environmentObject: Record = {}; + const environmentObject: Record = {}; infraConfigs.forEach((infraConfig) => { if (infraConfig.isEncrypted) { environmentObject[infraConfig.name] = decrypt(infraConfig.value); @@ -323,7 +322,7 @@ export async function syncInfraConfigWithEnvFile() { const updateRequiredObjs: (Partial & { id: string })[] = []; for (const dbConfig of dbInfraConfigs) { - let envValue = process.env[dbConfig.name]; + const envValue = process.env[dbConfig.name]; // lastSyncedEnvFileValue null check for backward compatibility from 2024.10.2 and below if (!dbConfig.lastSyncedEnvFileValue && envValue) { @@ -398,7 +397,7 @@ export function stopApp() { export function getConfiguredSSOProvidersFromEnvFile() { const allowedAuthProviders: string[] = process.env.VITE_ALLOWED_AUTH_PROVIDERS.split(','); - let configuredAuthProviders: string[] = []; + const configuredAuthProviders: string[] = []; const addProviderIfConfigured = (provider) => { const configParameters: string[] = AuthProviderConfigurations[provider]; @@ -439,7 +438,7 @@ export async function getConfiguredSSOProvidersFromInfraConfig() { const allowedAuthProviders: string[] = env['INFRA'].VITE_ALLOWED_AUTH_PROVIDERS.split(','); - let configuredAuthProviders: string[] = []; + const configuredAuthProviders: string[] = []; const addProviderIfConfigured = (provider) => { const configParameters: string[] = AuthProviderConfigurations[provider]; diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts index 521f10c9674..8839c99672d 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.module.ts @@ -1,12 +1,9 @@ import { Module } from '@nestjs/common'; import { InfraConfigService } from './infra-config.service'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { SiteController } from './infra-config.controller'; import { InfraConfigResolver } from './infra-config.resolver'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; @Module({ - imports: [PrismaModule, PubSubModule], providers: [InfraConfigResolver, InfraConfigService], exports: [InfraConfigService], controllers: [SiteController], diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts index f1c65f709c2..7f0921efda0 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.spec.ts @@ -19,8 +19,6 @@ const mockPrisma = mockDeep(); const mockConfigService = mockDeep(); const mockPubsub = mockDeep(); -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore const infraConfigService = new InfraConfigService( mockPrisma, mockConfigService, @@ -66,90 +64,84 @@ beforeEach(() => { describe('InfraConfigService', () => { describe('update', () => { it('should update the infra config without backend server restart', async () => { - const name = InfraConfigEnum.GOOGLE_CLIENT_ID; - const value = 'true'; + const name = dbInfraConfigs[0].name; + const value = 'newValue'; - // @ts-ignore - mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ - isEncrypted: false, - }); + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce( + dbInfraConfigs[0], + ); mockPrisma.infraConfig.update.mockResolvedValueOnce({ - id: '', + ...dbInfraConfigs[0], name, value, - lastSyncedEnvFileValue: value, - isEncrypted: false, - createdOn: new Date(), - updatedOn: new Date(), }); jest.spyOn(helper, 'stopApp').mockReturnValueOnce(); - const result = await infraConfigService.update(name, value); + const result = await infraConfigService.update( + name as InfraConfigEnum, + value, + ); expect(helper.stopApp).not.toHaveBeenCalled(); expect(result).toEqualRight({ name, value }); }); it('should update the infra config with backend server restart', async () => { - const name = InfraConfigEnum.GOOGLE_CLIENT_ID; - const value = 'true'; + const name = dbInfraConfigs[0].name; + const value = 'newValue'; - // @ts-ignore - mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ - isEncrypted: false, - }); + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce( + dbInfraConfigs[0], + ); mockPrisma.infraConfig.update.mockResolvedValueOnce({ - id: '', + ...dbInfraConfigs[0], name, value, - lastSyncedEnvFileValue: value, - isEncrypted: false, - createdOn: new Date(), - updatedOn: new Date(), }); jest.spyOn(helper, 'stopApp').mockReturnValueOnce(); - const result = await infraConfigService.update(name, value, true); + const result = await infraConfigService.update( + name as InfraConfigEnum, + value, + true, + ); expect(helper.stopApp).toHaveBeenCalledTimes(1); expect(result).toEqualRight({ name, value }); }); it('should update the infra config', async () => { - const name = InfraConfigEnum.GOOGLE_CLIENT_ID; - const value = 'true'; + const name = dbInfraConfigs[0].name; + const value = 'newValue'; - // @ts-ignore - mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ - isEncrypted: false, - }); + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce( + dbInfraConfigs[0], + ); mockPrisma.infraConfig.update.mockResolvedValueOnce({ - id: '', + ...dbInfraConfigs[0], name, value, - lastSyncedEnvFileValue: value, - isEncrypted: false, - createdOn: new Date(), - updatedOn: new Date(), }); jest.spyOn(helper, 'stopApp').mockReturnValueOnce(); - const result = await infraConfigService.update(name, value); + const result = await infraConfigService.update( + name as InfraConfigEnum, + value, + ); expect(result).toEqualRight({ name, value }); }); it('should pass correct params to prisma update', async () => { - const name = InfraConfigEnum.GOOGLE_CLIENT_ID; - const value = 'true'; + const name = dbInfraConfigs[0].name; + const value = 'newValue'; - // @ts-ignore - mockPrisma.infraConfig.findUnique.mockResolvedValueOnce({ - isEncrypted: false, - }); + mockPrisma.infraConfig.findUnique.mockResolvedValueOnce( + dbInfraConfigs[0], + ); jest.spyOn(helper, 'stopApp').mockReturnValueOnce(); - await infraConfigService.update(name, value); + await infraConfigService.update(name as InfraConfigEnum, value); expect(mockPrisma.infraConfig.update).toHaveBeenCalledWith({ where: { name }, @@ -171,19 +163,13 @@ describe('InfraConfigService', () => { describe('get', () => { it('should get the infra config', async () => { - const name = InfraConfigEnum.GOOGLE_CLIENT_ID; - const value = 'true'; + const name = dbInfraConfigs[0].name; + const value = dbInfraConfigs[0].value; - mockPrisma.infraConfig.findUniqueOrThrow.mockResolvedValueOnce({ - id: '', - name, - value, - lastSyncedEnvFileValue: value, - isEncrypted: false, - createdOn: new Date(), - updatedOn: new Date(), - }); - const result = await infraConfigService.get(name); + mockPrisma.infraConfig.findUniqueOrThrow.mockResolvedValueOnce( + dbInfraConfigs[0], + ); + const result = await infraConfigService.get(name as InfraConfigEnum); expect(result).toEqualRight({ name, value }); }); diff --git a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts index 2c95df8a307..820b07c3565 100644 --- a/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts +++ b/packages/hoppscotch-backend/src/infra-config/infra-config.service.ts @@ -134,7 +134,7 @@ export class InfraConfigService implements OnModuleInit { * @param dbInfraConfig database InfraConfig * @returns InfraConfig model */ - cast(dbInfraConfig: DBInfraConfig) { + private cast(dbInfraConfig: DBInfraConfig) { switch (dbInfraConfig.name) { case InfraConfigEnum.USER_HISTORY_STORE_ENABLED: dbInfraConfig.value = diff --git a/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts b/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts index a8e6f58c858..b12c209e97b 100644 --- a/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts +++ b/packages/hoppscotch-backend/src/infra-token/infra-token.module.ts @@ -1,5 +1,4 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { InfraTokenResolver } from './infra-token.resolver'; import { InfraTokenService } from './infra-token.service'; import { InfraTokensController } from './infra-token.controller'; @@ -7,7 +6,7 @@ import { AdminModule } from 'src/admin/admin.module'; import { UserModule } from 'src/user/user.module'; @Module({ - imports: [PrismaModule, AdminModule, UserModule], + imports: [AdminModule, UserModule], controllers: [InfraTokensController], providers: [InfraTokenResolver, InfraTokenService], }) diff --git a/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts index f3c64f8fc47..99313e49e67 100644 --- a/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts +++ b/packages/hoppscotch-backend/src/infra-token/request-response.dto.ts @@ -1,16 +1,15 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { Expose, Transform, Type } from 'class-transformer'; +import { Expose, Type } from 'class-transformer'; import { ArrayMinSize, IsArray, IsBoolean, - IsEmail, IsNotEmpty, IsOptional, IsString, MinLength, } from 'class-validator'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; // POST v1/infra/user-invitations @@ -140,7 +139,7 @@ export class GetUserWorkspacesResponse { @Expose() name: string; - @ApiProperty({ enum: TeamMemberRole }) + @ApiProperty({ enum: TeamAccessRole }) @Expose() role: string; diff --git a/packages/hoppscotch-backend/src/mailer/mailer.module.ts b/packages/hoppscotch-backend/src/mailer/mailer.module.ts index f8d19726fae..cbe74345b9e 100644 --- a/packages/hoppscotch-backend/src/mailer/mailer.module.ts +++ b/packages/hoppscotch-backend/src/mailer/mailer.module.ts @@ -28,7 +28,7 @@ export class MailerModule { // If mailer is ENABLED, return the module with configuration (service, etc.) // Determine transport configuration based on custom config flag - let transportOption = getTransportOption(env, config); + const transportOption = getTransportOption(env, config); // Get mailer address from environment or config const mailerAddressFrom = getMailerAddressFrom(env, config); diff --git a/packages/hoppscotch-backend/src/main.ts b/packages/hoppscotch-backend/src/main.ts index c8eed7724e8..fb39809a0c8 100644 --- a/packages/hoppscotch-backend/src/main.ts +++ b/packages/hoppscotch-backend/src/main.ts @@ -8,7 +8,6 @@ import { emitGQLSchemaFile } from './gql-schema'; import { checkEnvironmentAuthProvider } from './utils'; import { ConfigService } from '@nestjs/config'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; -import { InfraTokensController } from './infra-token/infra-token.controller'; import { InfraTokenModule } from './infra-token/infra-token.module'; function setupSwagger(app) { @@ -42,7 +41,7 @@ async function bootstrap() { const configService = app.get(ConfigService); - console.log(`Running in production: ${configService.get('PRODUCTION')}`); + console.log(`Running in production: ${configService.get('PRODUCTION')}`); console.log(`Port: ${configService.get('PORT')}`); checkEnvironmentAuthProvider( @@ -63,21 +62,20 @@ async function bootstrap() { }), ); - if (configService.get('PRODUCTION') === 'false') { - console.log('Enabling CORS with development settings'); - + if (configService.get('PRODUCTION') === 'true') { + console.log('Enabling CORS with production settings'); app.enableCors({ origin: configService.get('WHITELISTED_ORIGINS').split(','), credentials: true, }); } else { - console.log('Enabling CORS with production settings'); - + console.log('Enabling CORS with development settings'); app.enableCors({ - origin: configService.get('WHITELISTED_ORIGINS').split(','), + origin: true, credentials: true, }); } + app.enableVersioning({ type: VersioningType.URI, }); diff --git a/packages/hoppscotch-backend/src/posthog/posthog.module.ts b/packages/hoppscotch-backend/src/posthog/posthog.module.ts index 07f56cc29e6..6e20851a8ed 100644 --- a/packages/hoppscotch-backend/src/posthog/posthog.module.ts +++ b/packages/hoppscotch-backend/src/posthog/posthog.module.ts @@ -1,9 +1,7 @@ import { Module } from '@nestjs/common'; -import { PosthogService } from './posthog.service'; -import { PrismaModule } from 'src/prisma/prisma.module'; +import { PostHogService } from './posthog.service'; @Module({ - imports: [PrismaModule], - providers: [PosthogService], + providers: [PostHogService], }) -export class PosthogModule {} +export class PostHogModule {} diff --git a/packages/hoppscotch-backend/src/posthog/posthog.service.ts b/packages/hoppscotch-backend/src/posthog/posthog.service.ts index 9171c5bb8f8..f341b7c4a52 100644 --- a/packages/hoppscotch-backend/src/posthog/posthog.service.ts +++ b/packages/hoppscotch-backend/src/posthog/posthog.service.ts @@ -1,20 +1,21 @@ import { Injectable } from '@nestjs/common'; import { PostHog } from 'posthog-node'; -import { Cron, CronExpression, SchedulerRegistry } from '@nestjs/schedule'; +import { CronExpression, SchedulerRegistry } from '@nestjs/schedule'; import { ConfigService } from '@nestjs/config'; import { PrismaService } from 'src/prisma/prisma.service'; import { CronJob } from 'cron'; import { POSTHOG_CLIENT_NOT_INITIALIZED } from 'src/errors'; import { throwErr } from 'src/utils'; + @Injectable() -export class PosthogService { +export class PostHogService { private postHogClient: PostHog; private POSTHOG_API_KEY = 'phc_9CipPajQC22mSkk2wxe2TXsUA0Ysyupe8dt5KQQELqx'; constructor( private readonly configService: ConfigService, - private readonly prismaService: PrismaService, - private schedulerRegistry: SchedulerRegistry, + private readonly prisma: PrismaService, + private readonly schedulerRegistry: SchedulerRegistry, ) {} async onModuleInit() { @@ -48,8 +49,8 @@ export class PosthogService { event: 'sh_instance', properties: { type: 'COMMUNITY', - total_user_count: await this.prismaService.user.count(), - total_workspace_count: await this.prismaService.team.count(), + total_user_count: await this.prisma.user.count(), + total_workspace_count: await this.prisma.team.count(), version: this.configService.get('npm_package_version'), }, }); diff --git a/packages/hoppscotch-backend/src/prisma/prisma.module.ts b/packages/hoppscotch-backend/src/prisma/prisma.module.ts index 953aa89d865..baddda245ff 100644 --- a/packages/hoppscotch-backend/src/prisma/prisma.module.ts +++ b/packages/hoppscotch-backend/src/prisma/prisma.module.ts @@ -1,6 +1,7 @@ -import { Module } from '@nestjs/common/decorators'; +import { Global, Module } from '@nestjs/common/decorators'; import { PrismaService } from './prisma.service'; +@Global() @Module({ providers: [PrismaService], exports: [PrismaService], diff --git a/packages/hoppscotch-backend/src/pubsub/pubsub.module.ts b/packages/hoppscotch-backend/src/pubsub/pubsub.module.ts index b12ff3a7d20..3cc0f330de8 100644 --- a/packages/hoppscotch-backend/src/pubsub/pubsub.module.ts +++ b/packages/hoppscotch-backend/src/pubsub/pubsub.module.ts @@ -1,6 +1,7 @@ -import { Module } from '@nestjs/common'; +import { Global, Module } from '@nestjs/common'; import { PubSubService } from './pubsub.service'; +@Global() @Module({ providers: [PubSubService], exports: [PubSubService], diff --git a/packages/hoppscotch-backend/src/pubsub/pubsub.service.ts b/packages/hoppscotch-backend/src/pubsub/pubsub.service.ts index a69ec3b424d..b80d27965e5 100644 --- a/packages/hoppscotch-backend/src/pubsub/pubsub.service.ts +++ b/packages/hoppscotch-backend/src/pubsub/pubsub.service.ts @@ -19,7 +19,7 @@ export class PubSubService implements OnModuleInit { } asyncIterator(topic: string | string[]): AsyncIterator { - return this.pubsub.asyncIterator(topic); + return this.pubsub.asyncIterableIterator(topic); } async publish(topic: T, payload: TopicDef[T]) { diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.model.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.model.ts index 40dc28b2e0e..635312a8ec3 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.model.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.model.ts @@ -1,5 +1,4 @@ import { Field, ID, ObjectType } from '@nestjs/graphql'; -import { User } from 'src/user/user.model'; @ObjectType() export class Shortcode { diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.module.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.module.ts index 7febeadf0cb..ad28fb79db9 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.module.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.module.ts @@ -1,12 +1,10 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from 'src/prisma/prisma.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; import { UserModule } from 'src/user/user.module'; import { ShortcodeResolver } from './shortcode.resolver'; import { ShortcodeService } from './shortcode.service'; @Module({ - imports: [PrismaModule, UserModule, PubSubModule], + imports: [UserModule], providers: [ShortcodeService, ShortcodeResolver], exports: [ShortcodeService], }) diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.resolver.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.resolver.ts index 7c10696336d..063aa0c72b6 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.resolver.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.resolver.ts @@ -8,7 +8,7 @@ import { } from '@nestjs/graphql'; import * as E from 'fp-ts/Either'; import { UseGuards } from '@nestjs/common'; -import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model'; +import { Shortcode } from './shortcode.model'; import { ShortcodeService } from './shortcode.service'; import { throwErr } from 'src/utils'; import { GqlUser } from 'src/decorators/gql-user.decorator'; @@ -19,7 +19,6 @@ import { AuthUser } from '../types/AuthUser'; import { PaginationArgs } from 'src/types/input-types.args'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { SkipThrottle } from '@nestjs/throttler'; -import { GqlAdminGuard } from 'src/admin/guards/gql-admin.guard'; @UseGuards(GqlThrottlerGuard) @Resolver(() => Shortcode) diff --git a/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts b/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts index 94b2c8c7bfc..672dfd86c68 100644 --- a/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts +++ b/packages/hoppscotch-backend/src/shortcode/shortcode.service.spec.ts @@ -1,7 +1,6 @@ import { mockDeep, mockReset } from 'jest-mock-extended'; import { PrismaService } from '../prisma/prisma.service'; import { - INVALID_EMAIL, SHORTCODE_INVALID_PROPERTIES_JSON, SHORTCODE_INVALID_REQUEST_JSON, SHORTCODE_NOT_FOUND, @@ -11,27 +10,15 @@ import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model'; import { ShortcodeService } from './shortcode.service'; import { UserService } from 'src/user/user.service'; import { AuthUser } from 'src/types/AuthUser'; +import { PubSubService } from 'src/pubsub/pubsub.service'; const mockPrisma = mockDeep(); +const mockPubSub = mockDeep(); +const mockUserService = mockDeep(); -const mockPubSub = { - publish: jest.fn().mockResolvedValue(null), -}; - -const mockDocFunc = jest.fn(); - -const mockFB = { - firestore: { - doc: mockDocFunc, - }, -}; -const mockUserService = new UserService(mockPrisma as any, mockPubSub as any); - -// eslint-disable-next-line @typescript-eslint/ban-ts-comment -// @ts-ignore const shortcodeService = new ShortcodeService( mockPrisma, - mockPubSub as any, + mockPubSub, mockUserService, ); @@ -286,7 +273,7 @@ describe('ShortcodeService', () => { ); mockPrisma.shortcode.create.mockResolvedValueOnce(mockShortcode); - const result = await shortcodeService.createShortcode('{}', null, user); + await shortcodeService.createShortcode('{}', null, user); expect(mockPubSub.publish).toHaveBeenCalledWith( `shortcode/${mockShortcode.creatorUid}/created`, @@ -306,7 +293,7 @@ describe('ShortcodeService', () => { ); mockPrisma.shortcode.create.mockResolvedValueOnce(mockEmbed); - const result = await shortcodeService.createShortcode('{}', '{}', user); + await shortcodeService.createShortcode('{}', '{}', user); expect(mockPubSub.publish).toHaveBeenCalledWith( `shortcode/${mockEmbed.creatorUid}/created`, @@ -365,7 +352,7 @@ describe('ShortcodeService', () => { test('should send pubsub message to `shortcode/{uid}/revoked` on successful deletion of Shortcode', async () => { mockPrisma.shortcode.delete.mockResolvedValueOnce(mockEmbed); - const result = await shortcodeService.revokeShortCode( + await shortcodeService.revokeShortCode( mockEmbed.id, mockEmbed.creatorUid, ); @@ -456,7 +443,7 @@ describe('ShortcodeService', () => { embedProperties: '{"foo":"bar"}', }); - const result = await shortcodeService.updateEmbedProperties( + await shortcodeService.updateEmbedProperties( mockEmbed.id, user.uid, '{"foo":"bar"}', diff --git a/packages/hoppscotch-backend/src/team-collection/guards/gql-collection-team-member.guard.ts b/packages/hoppscotch-backend/src/team-collection/guards/gql-collection-team-member.guard.ts index cf32a3659e1..7f82190dd42 100644 --- a/packages/hoppscotch-backend/src/team-collection/guards/gql-collection-team-member.guard.ts +++ b/packages/hoppscotch-backend/src/team-collection/guards/gql-collection-team-member.guard.ts @@ -3,7 +3,7 @@ import { Reflector } from '@nestjs/core'; import { GqlExecutionContext } from '@nestjs/graphql'; import { TeamCollectionService } from '../team-collection.service'; import { TeamService } from '../../team/team.service'; -import { TeamMemberRole } from '../../team/team.model'; +import { TeamAccessRole } from '../../team/team.model'; import { BUG_TEAM_NO_REQUIRE_TEAM_ROLE, BUG_AUTH_NO_USER_CTX, @@ -22,7 +22,7 @@ export class GqlCollectionTeamMemberGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const requireRoles = this.reflector.get( + const requireRoles = this.reflector.get( 'requiresTeamRole', context.getHandler(), ); @@ -36,9 +36,8 @@ export class GqlCollectionTeamMemberGuard implements CanActivate { const { collectionID } = gqlExecCtx.getArgs<{ collectionID: string }>(); if (!collectionID) throw new Error(BUG_TEAM_COLL_NO_COLL_ID); - const collection = await this.teamCollectionService.getCollection( - collectionID, - ); + const collection = + await this.teamCollectionService.getCollection(collectionID); if (E.isLeft(collection)) throw new Error(TEAM_INVALID_COLL_ID); const member = await this.teamService.getTeamMember( diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.controller.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.controller.ts index dfeb9d6d045..f137d062437 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.controller.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.controller.ts @@ -11,7 +11,7 @@ import * as E from 'fp-ts/Either'; import { ThrottlerBehindProxyGuard } from 'src/guards/throttler-behind-proxy.guard'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator'; -import { TeamMemberRole } from '@prisma/client'; +import { TeamAccessRole } from '@prisma/client'; import { RESTTeamMemberGuard } from 'src/team/guards/rest-team-member.guard'; import { throwHTTPErr } from 'src/utils'; import { RESTError } from 'src/types/RESTError'; @@ -24,9 +24,9 @@ export class TeamCollectionController { @Get('search/:teamID') @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) @UseGuards(JwtAuthGuard, RESTTeamMemberGuard) async searchByTitle( diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.module.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.module.ts index 53aae5bb5d1..5f1a81945b4 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.module.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.module.ts @@ -1,15 +1,13 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from '../prisma/prisma.module'; import { TeamCollectionService } from './team-collection.service'; import { TeamCollectionResolver } from './team-collection.resolver'; import { GqlCollectionTeamMemberGuard } from './guards/gql-collection-team-member.guard'; import { TeamModule } from '../team/team.module'; import { UserModule } from '../user/user.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; import { TeamCollectionController } from './team-collection.controller'; @Module({ - imports: [PrismaModule, TeamModule, UserModule, PubSubModule], + imports: [TeamModule, UserModule], providers: [ TeamCollectionService, TeamCollectionResolver, diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts index 1fc4c108636..07e322d518f 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts @@ -9,7 +9,7 @@ import { ID, } from '@nestjs/graphql'; import { CollectionReorderData, TeamCollection } from './team-collection.model'; -import { Team, TeamMemberRole } from '../team/team.model'; +import { Team, TeamAccessRole } from '../team/team.model'; import { TeamCollectionService } from './team-collection.service'; import { GqlAuthGuard } from '../guards/gql-auth.guard'; import { GqlTeamMemberGuard } from '../team/guards/gql-team-member.guard'; @@ -86,17 +86,16 @@ export class TeamCollectionResolver { }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) async exportCollectionsToJSON( @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) teamID: string, ) { - const jsonString = await this.teamCollectionService.exportCollectionsToJSON( - teamID, - ); + const jsonString = + await this.teamCollectionService.exportCollectionsToJSON(teamID); if (E.isLeft(jsonString)) throwErr(jsonString.left as string); return jsonString.right; @@ -108,9 +107,9 @@ export class TeamCollectionResolver { }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) async exportCollectionToJSON( @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) @@ -137,9 +136,9 @@ export class TeamCollectionResolver { }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) async rootCollectionsOfTeam(@Args() args: GetRootTeamCollectionsArgs) { return this.teamCollectionService.getTeamRootCollections( @@ -155,9 +154,9 @@ export class TeamCollectionResolver { }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) async collection( @Args({ @@ -167,9 +166,8 @@ export class TeamCollectionResolver { }) collectionID: string, ) { - const teamCollections = await this.teamCollectionService.getCollection( - collectionID, - ); + const teamCollections = + await this.teamCollectionService.getCollection(collectionID); if (E.isLeft(teamCollections)) throwErr(teamCollections.left); return { @@ -188,7 +186,7 @@ export class TeamCollectionResolver { 'Creates a collection at the root of the team hierarchy (no parent collection)', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async createRootCollection(@Args() args: CreateRootTeamCollectionArgs) { const teamCollection = await this.teamCollectionService.createCollection( args.teamID, @@ -205,7 +203,7 @@ export class TeamCollectionResolver { description: 'Import collections from JSON string to the specified Team', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async importCollectionsFromJSON( @Args({ name: 'teamID', @@ -242,7 +240,7 @@ export class TeamCollectionResolver { 'Replace existing collections of a specific team with collections in JSON string', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async replaceCollectionsWithJSON(@Args() args: ReplaceTeamCollectionArgs) { const teamCollection = await this.teamCollectionService.replaceCollectionsWithJSON( @@ -259,7 +257,7 @@ export class TeamCollectionResolver { description: 'Create a collection that has a parent collection', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async createChildCollection(@Args() args: CreateChildTeamCollectionArgs) { const team = await this.teamCollectionService.getTeamOfCollection( args.collectionID, @@ -282,7 +280,7 @@ export class TeamCollectionResolver { deprecationReason: 'Switch to updateTeamCollection mutation instead', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async renameCollection(@Args() args: RenameTeamCollectionArgs) { const updatedTeamCollection = await this.teamCollectionService.renameCollection( @@ -298,7 +296,7 @@ export class TeamCollectionResolver { description: 'Delete a collection', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async deleteCollection( @Args({ name: 'collectionID', @@ -307,9 +305,8 @@ export class TeamCollectionResolver { }) collectionID: string, ) { - const result = await this.teamCollectionService.deleteCollection( - collectionID, - ); + const result = + await this.teamCollectionService.deleteCollection(collectionID); if (E.isLeft(result)) throwErr(result.left); return result.right; @@ -320,7 +317,7 @@ export class TeamCollectionResolver { 'Move a collection into a new parent collection or the root of the team', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async moveCollection(@Args() args: MoveTeamCollectionArgs) { const res = await this.teamCollectionService.moveCollection( args.collectionID, @@ -334,7 +331,7 @@ export class TeamCollectionResolver { description: 'Update the order of collections', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async updateCollectionOrder(@Args() args: UpdateTeamCollectionOrderArgs) { const request = await this.teamCollectionService.updateCollectionOrder( args.collectionID, @@ -348,7 +345,7 @@ export class TeamCollectionResolver { description: 'Update Team Collection details', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async updateTeamCollection(@Args() args: UpdateTeamCollectionArgs) { const updatedTeamCollection = await this.teamCollectionService.updateTeamCollection( @@ -365,7 +362,7 @@ export class TeamCollectionResolver { description: 'Duplicate a Team Collection', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async duplicateTeamCollection( @Args({ name: 'collectionID', @@ -389,9 +386,9 @@ export class TeamCollectionResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -411,9 +408,9 @@ export class TeamCollectionResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -433,9 +430,9 @@ export class TeamCollectionResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -455,9 +452,9 @@ export class TeamCollectionResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -477,9 +474,9 @@ export class TeamCollectionResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts index 32b993a39f8..fbfeb75266a 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.service.spec.ts @@ -84,13 +84,6 @@ const rootTeamCollection_2: DBTeamCollection = { updatedOn: currentTime, }; -const rootTeamCollection_2Casted: TeamCollection = { - id: 'erv', - parentID: null, - data: JSON.stringify(rootTeamCollection_2.data), - title: 'Root Collection 1', -}; - const childTeamCollection: DBTeamCollection = { id: 'rfe', orderIndex: 1, @@ -537,9 +530,8 @@ describe('getParentOfCollection', () => { test('should return null with invalid collectionID', async () => { mockPrisma.teamCollection.findUnique.mockResolvedValueOnce(null); - const result = await teamCollectionService.getParentOfCollection( - 'invalidID', - ); + const result = + await teamCollectionService.getParentOfCollection('invalidID'); expect(result).toEqual(null); }); }); @@ -756,7 +748,7 @@ describe('createCollection', () => { mockPrisma.teamCollection.findMany.mockResolvedValueOnce([]); mockPrisma.teamCollection.create.mockResolvedValueOnce(childTeamCollection); - const result = await teamCollectionService.createCollection( + await teamCollectionService.createCollection( childTeamCollection.teamID, childTeamCollection.title, JSON.stringify(rootTeamCollection.data), @@ -779,7 +771,7 @@ describe('createCollection', () => { mockPrisma.teamCollection.findMany.mockResolvedValueOnce([]); mockPrisma.teamCollection.create.mockResolvedValueOnce(rootTeamCollection); - const result = await teamCollectionService.createCollection( + await teamCollectionService.createCollection( rootTeamCollection.teamID, 'abcdefg', JSON.stringify(rootTeamCollection.data), @@ -849,7 +841,7 @@ describe('renameCollection', () => { title: 'NewTitle', }); - const result = await teamCollectionService.renameCollection( + await teamCollectionService.renameCollection( rootTeamCollection.id, 'NewTitle', ); @@ -938,9 +930,7 @@ describe('deleteCollection', () => { // deleteCollectionData --> removeUserCollection mockPrisma.teamCollection.delete.mockResolvedValueOnce(rootTeamCollection); - const result = await teamCollectionService.deleteCollection( - rootTeamCollection.id, - ); + await teamCollectionService.deleteCollection(rootTeamCollection.id); expect(mockPubSub.publish).toHaveBeenCalledWith( `team_coll/${rootTeamCollection.teamID}/coll_removed`, rootTeamCollection.id, @@ -1113,10 +1103,7 @@ describe('moveCollection', () => { orderIndex: 2, }); - const result = await teamCollectionService.moveCollection( - childTeamCollection.id, - null, - ); + await teamCollectionService.moveCollection(childTeamCollection.id, null); expect(mockPubSub.publish).toHaveBeenCalledWith( `team_coll/${childTeamCollection.teamID}/coll_moved`, { @@ -1194,7 +1181,7 @@ describe('moveCollection', () => { orderIndex: 1, }); - const result = await teamCollectionService.moveCollection( + await teamCollectionService.moveCollection( rootTeamCollection.id, childTeamCollection_2.id, ); @@ -1275,7 +1262,7 @@ describe('moveCollection', () => { orderIndex: 1, }); - const result = await teamCollectionService.moveCollection( + await teamCollectionService.moveCollection( childTeamCollection.id, childTeamCollection_2.id, ); @@ -1372,7 +1359,7 @@ describe('updateCollectionOrder', () => { orderIndex: rootTeamCollectionList.length, }); - const result = await teamCollectionService.updateCollectionOrder( + await teamCollectionService.updateCollectionOrder( rootTeamCollectionList[4].id, null, ); @@ -1453,7 +1440,7 @@ describe('updateCollectionOrder', () => { .mockResolvedValueOnce(childTeamCollectionList[4]) .mockResolvedValueOnce(childTeamCollectionList[2]); - const result = await teamCollectionService.updateCollectionOrder( + await teamCollectionService.updateCollectionOrder( childTeamCollectionList[4].id, childTeamCollectionList[2].id, ); @@ -1520,7 +1507,7 @@ describe('importCollectionsFromJSON', () => { mockPrisma.teamCollection.findMany.mockResolvedValueOnce([]); mockPrisma.$transaction.mockResolvedValueOnce([rootTeamCollection]); - const result = await teamCollectionService.importCollectionsFromJSON( + await teamCollectionService.importCollectionsFromJSON( jsonString, rootTeamCollection.teamID, null, @@ -1639,7 +1626,7 @@ describe('replaceCollectionsWithJSON', () => { mockPrisma.teamCollection.findMany.mockResolvedValueOnce([]); mockPrisma.$transaction.mockResolvedValueOnce([rootTeamCollection]); - const result = await teamCollectionService.replaceCollectionsWithJSON( + await teamCollectionService.replaceCollectionsWithJSON( jsonString, rootTeamCollection.teamID, null, @@ -1731,7 +1718,7 @@ describe('updateTeamCollection', () => { test('should send pubsub message to "team_coll//coll_updated" if TeamCollection is updated successfully', async () => { mockPrisma.teamCollection.update.mockResolvedValueOnce(rootTeamCollection); - const result = await teamCollectionService.updateTeamCollection( + await teamCollectionService.updateTeamCollection( rootTeamCollection.id, JSON.stringify(rootTeamCollection.data), rootTeamCollection.title, @@ -1778,7 +1765,7 @@ describe('getCollectionForCLI', () => { // mockTeamService.getTeamMember.mockResolvedValue({ // membershipID: 'sdc3sfdv', // userUid: user.uid, - // role: TeamMemberRole.OWNER, + // role: TeamAccessRole.OWNER, // }); // const result = await teamCollectionService.getCollectionForCLI( diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts index 6779e64aab1..b00acd219f9 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts @@ -932,7 +932,7 @@ export class TeamCollectionService { }, }); // Step 2: Update orderIndex of collection to length of list - const updatedTeamCollection = await tx.teamCollection.update({ + await tx.teamCollection.update({ where: { id: collection.right.id }, data: { orderIndex: await this.getCollectionCount( @@ -989,7 +989,7 @@ export class TeamCollectionService { }, }); // Step 3: Update OrderIndex of collection - const updatedTeamCollection = await tx.teamCollection.update({ + await tx.teamCollection.update({ where: { id: collection.right.id }, data: { orderIndex: isMovingUp @@ -1262,9 +1262,8 @@ export class TeamCollectionService { ) SELECT * FROM collection_tree; `; - const res = await this.prisma.$queryRaw( - query, - ); + const res = + await this.prisma.$queryRaw(query); const collectionParentTree = this.generateParentTree(res); return E.right(collectionParentTree); @@ -1353,9 +1352,8 @@ export class TeamCollectionService { SELECT * FROM request_collection_tree; `; - const res = await this.prisma.$queryRaw( - query, - ); + const res = + await this.prisma.$queryRaw(query); const requestParentTree = this.generateParentTree(res); return E.right(requestParentTree); diff --git a/packages/hoppscotch-backend/src/team-environments/gql-team-env-team.guard.ts b/packages/hoppscotch-backend/src/team-environments/gql-team-env-team.guard.ts index 735ae8f5fc2..e96e8ebbd04 100644 --- a/packages/hoppscotch-backend/src/team-environments/gql-team-env-team.guard.ts +++ b/packages/hoppscotch-backend/src/team-environments/gql-team-env-team.guard.ts @@ -11,7 +11,7 @@ import { import { TeamService } from 'src/team/team.service'; import { GqlExecutionContext } from '@nestjs/graphql'; import * as E from 'fp-ts/Either'; -import { TeamMemberRole } from '@prisma/client'; +import { TeamAccessRole } from '@prisma/client'; import { throwErr } from 'src/utils'; /** @@ -28,7 +28,7 @@ export class GqlTeamEnvTeamGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const requireRoles = this.reflector.get( + const requireRoles = this.reflector.get( 'requiresTeamRole', context.getHandler(), ); diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.module.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.module.ts index 69e2f5c16e0..61b5a12f21f 100644 --- a/packages/hoppscotch-backend/src/team-environments/team-environments.module.ts +++ b/packages/hoppscotch-backend/src/team-environments/team-environments.module.ts @@ -2,14 +2,12 @@ import { Module } from '@nestjs/common'; import { TeamEnvironmentsService } from './team-environments.service'; import { TeamEnvironmentsResolver } from './team-environments.resolver'; import { UserModule } from 'src/user/user.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; import { TeamModule } from 'src/team/team.module'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard'; import { TeamEnvsTeamResolver } from './team.resolver'; @Module({ - imports: [PrismaModule, PubSubModule, UserModule, TeamModule], + imports: [UserModule, TeamModule], providers: [ TeamEnvironmentsResolver, TeamEnvironmentsService, diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.resolver.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.resolver.ts index 6bece38540e..a49da6adf04 100644 --- a/packages/hoppscotch-backend/src/team-environments/team-environments.resolver.ts +++ b/packages/hoppscotch-backend/src/team-environments/team-environments.resolver.ts @@ -1,14 +1,12 @@ import { UseGuards } from '@nestjs/common'; import { Resolver, Mutation, Args, Subscription, ID } from '@nestjs/graphql'; import { SkipThrottle } from '@nestjs/throttler'; -import { pipe } from 'fp-ts/function'; -import * as TE from 'fp-ts/TaskEither'; import { GqlAuthGuard } from 'src/guards/gql-auth.guard'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { PubSubService } from 'src/pubsub/pubsub.service'; import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator'; import { GqlTeamMemberGuard } from 'src/team/guards/gql-team-member.guard'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; import { throwErr } from 'src/utils'; import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard'; import { TeamEnvironment } from './team-environments.model'; @@ -33,7 +31,7 @@ export class TeamEnvironmentsResolver { description: 'Create a new Team Environment for given Team ID', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async createTeamEnvironment( @Args() args: CreateTeamEnvironmentArgs, ): Promise { @@ -52,7 +50,7 @@ export class TeamEnvironmentsResolver { description: 'Delete a Team Environment for given Team ID', }) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async deleteTeamEnvironment( @Args({ name: 'id', @@ -61,9 +59,8 @@ export class TeamEnvironmentsResolver { }) id: string, ): Promise { - const isDeleted = await this.teamEnvironmentsService.deleteTeamEnvironment( - id, - ); + const isDeleted = + await this.teamEnvironmentsService.deleteTeamEnvironment(id); if (E.isLeft(isDeleted)) throwErr(isDeleted.left); return isDeleted.right; @@ -74,7 +71,7 @@ export class TeamEnvironmentsResolver { 'Add/Edit a single environment variable or variables to a Team Environment', }) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async updateTeamEnvironment( @Args() args: UpdateTeamEnvironmentArgs, @@ -94,7 +91,7 @@ export class TeamEnvironmentsResolver { description: 'Delete all variables from a Team Environment', }) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async deleteAllVariablesFromTeamEnvironment( @Args({ name: 'id', @@ -116,7 +113,7 @@ export class TeamEnvironmentsResolver { description: 'Create a duplicate of an existing environment', }) @UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard) - @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + @RequiresTeamRole(TeamAccessRole.OWNER, TeamAccessRole.EDITOR) async createDuplicateEnvironment( @Args({ name: 'id', @@ -125,9 +122,8 @@ export class TeamEnvironmentsResolver { }) id: string, ): Promise { - const res = await this.teamEnvironmentsService.createDuplicateEnvironment( - id, - ); + const res = + await this.teamEnvironmentsService.createDuplicateEnvironment(id); if (E.isLeft(res)) throwErr(res.left); return res.right; @@ -142,9 +138,9 @@ export class TeamEnvironmentsResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) teamEnvironmentUpdated( @Args({ @@ -164,9 +160,9 @@ export class TeamEnvironmentsResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) teamEnvironmentCreated( @Args({ @@ -186,9 +182,9 @@ export class TeamEnvironmentsResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) teamEnvironmentDeleted( @Args({ diff --git a/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts b/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts index a1f4d0a232f..cda17f4b38f 100644 --- a/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts +++ b/packages/hoppscotch-backend/src/team-environments/team-environments.service.spec.ts @@ -3,13 +3,12 @@ import { PrismaService } from 'src/prisma/prisma.service'; import { TeamEnvironment } from './team-environments.model'; import { TeamEnvironmentsService } from './team-environments.service'; import { - JSON_INVALID, TEAM_ENVIRONMENT_NOT_FOUND, TEAM_ENVIRONMENT_SHORT_NAME, TEAM_MEMBER_NOT_FOUND, } from 'src/errors'; import { TeamService } from 'src/team/team.service'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; const mockPrisma = mockDeep(); @@ -92,7 +91,7 @@ describe('TeamEnvironmentsService', () => { test('should send pubsub message to "team_environment//created" if team environment is created successfully', async () => { mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment); - const result = await teamEnvironmentsService.createTeamEnvironment( + await teamEnvironmentsService.createTeamEnvironment( teamEnvironment.name, teamEnvironment.teamID, JSON.stringify(teamEnvironment.variables), @@ -122,9 +121,8 @@ describe('TeamEnvironmentsService', () => { test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if given id is invalid', async () => { mockPrisma.teamEnvironment.delete.mockRejectedValue('RecordNotFound'); - const result = await teamEnvironmentsService.deleteTeamEnvironment( - 'invalidid', - ); + const result = + await teamEnvironmentsService.deleteTeamEnvironment('invalidid'); expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND); }); @@ -132,9 +130,7 @@ describe('TeamEnvironmentsService', () => { test('should send pubsub message to "team_environment//deleted" if team environment is deleted successfully', async () => { mockPrisma.teamEnvironment.delete.mockResolvedValueOnce(teamEnvironment); - const result = await teamEnvironmentsService.deleteTeamEnvironment( - teamEnvironment.id, - ); + await teamEnvironmentsService.deleteTeamEnvironment(teamEnvironment.id); expect(mockPubSub.publish).toHaveBeenCalledWith( `team_environment/${teamEnvironment.teamID}/deleted`, @@ -244,7 +240,7 @@ describe('TeamEnvironmentsService', () => { test('should send pubsub message to "team_environment//updated" if team environment is updated successfully', async () => { mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment); - const result = await teamEnvironmentsService.updateTeamEnvironment( + await teamEnvironmentsService.updateTeamEnvironment( teamEnvironment.id, teamEnvironment.name, JSON.stringify([{ key: 'value' }]), @@ -289,10 +285,9 @@ describe('TeamEnvironmentsService', () => { test('should send pubsub message to "team_environment//updated" if team environment is updated successfully', async () => { mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment); - const result = - await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( - teamEnvironment.id, - ); + await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment( + teamEnvironment.id, + ); expect(mockPubSub.publish).toHaveBeenCalledWith( `team_environment/${teamEnvironment.teamID}/updated`, @@ -348,7 +343,7 @@ describe('TeamEnvironmentsService', () => { ...teamEnvironment, }); - const result = await teamEnvironmentsService.createDuplicateEnvironment( + await teamEnvironmentsService.createDuplicateEnvironment( teamEnvironment.id, ); @@ -394,7 +389,7 @@ describe('TeamEnvironmentsService', () => { mockTeamService.getTeamMember.mockResolvedValue({ membershipID: 'sdc3sfdv', userUid: '123454', - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); const result = await teamEnvironmentsService.getTeamEnvironmentForCLI( diff --git a/packages/hoppscotch-backend/src/team-invitation/input-type.args.ts b/packages/hoppscotch-backend/src/team-invitation/input-type.args.ts index 9cb3f298785..3bbaedc9e8f 100644 --- a/packages/hoppscotch-backend/src/team-invitation/input-type.args.ts +++ b/packages/hoppscotch-backend/src/team-invitation/input-type.args.ts @@ -1,5 +1,5 @@ import { ArgsType, Field, ID } from '@nestjs/graphql'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; @ArgsType() export class CreateTeamInvitationArgs { @@ -12,9 +12,9 @@ export class CreateTeamInvitationArgs { @Field({ name: 'inviteeEmail', description: 'Email of the user to invite' }) inviteeEmail: string; - @Field(() => TeamMemberRole, { + @Field(() => TeamAccessRole, { name: 'inviteeRole', description: 'Role to be given to the user', }) - inviteeRole: TeamMemberRole; + inviteeRole: TeamAccessRole; } diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.model.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.model.ts index 4b753039d2d..89efd9d0eff 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.model.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.model.ts @@ -1,5 +1,5 @@ import { Field, ID, ObjectType } from '@nestjs/graphql'; -import { TeamMemberRole } from '../team/team.model'; +import { TeamAccessRole } from '../team/team.model'; @ObjectType() export class TeamInvitation { @@ -23,8 +23,8 @@ export class TeamInvitation { }) inviteeEmail: string; - @Field(() => TeamMemberRole, { + @Field(() => TeamAccessRole, { description: 'The role that will be given to the invitee', }) - inviteeRole: TeamMemberRole; + inviteeRole: TeamAccessRole; } diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.module.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.module.ts index f4cd4e36857..4ad961313e7 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.module.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.module.ts @@ -1,6 +1,4 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from 'src/prisma/prisma.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; import { TeamModule } from 'src/team/team.module'; import { UserModule } from 'src/user/user.module'; import { TeamInvitationResolver } from './team-invitation.resolver'; @@ -11,7 +9,7 @@ import { TeamInviteeGuard } from './team-invitee.guard'; import { TeamTeamInviteExtResolver } from './team-teaminvite-ext.resolver'; @Module({ - imports: [PrismaModule, TeamModule, PubSubModule, UserModule], + imports: [TeamModule, UserModule], providers: [ TeamInvitationService, TeamInvitationResolver, diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts index 32a903fa6fb..b9f21a8b232 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts @@ -14,7 +14,7 @@ import { pipe } from 'fp-ts/function'; import * as TE from 'fp-ts/TaskEither'; import * as E from 'fp-ts/Either'; import * as O from 'fp-ts/Option'; -import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model'; +import { Team, TeamMember, TeamAccessRole } from 'src/team/team.model'; import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors'; import { GqlUser } from 'src/decorators/gql-user.decorator'; import { User } from 'src/user/user.model'; @@ -41,7 +41,6 @@ export class TeamInvitationResolver { private readonly userService: UserService, private readonly teamService: TeamService, private readonly teamInvitationService: TeamInvitationService, - private readonly pubsub: PubSubService, ) {} @@ -85,9 +84,8 @@ export class TeamInvitationResolver { }) inviteID: string, ): Promise { - const teamInvitation = await this.teamInvitationService.getInvitation( - inviteID, - ); + const teamInvitation = + await this.teamInvitationService.getInvitation(inviteID); if (O.isNone(teamInvitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); return teamInvitation.value; } @@ -96,7 +94,7 @@ export class TeamInvitationResolver { description: 'Creates a Team Invitation', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) async createTeamInvitation( @GqlUser() user: AuthUser, @Args() args: CreateTeamInvitationArgs, @@ -116,7 +114,7 @@ export class TeamInvitationResolver { description: 'Revokes an invitation and deletes it', }) @UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) async revokeTeamInvitation( @Args({ name: 'inviteID', @@ -125,9 +123,8 @@ export class TeamInvitationResolver { }) inviteID: string, ): Promise { - const isRevoked = await this.teamInvitationService.revokeInvitation( - inviteID, - ); + const isRevoked = + await this.teamInvitationService.revokeInvitation(inviteID); if (E.isLeft(isRevoked)) throwErr(isRevoked.left); return true; } @@ -161,9 +158,9 @@ export class TeamInvitationResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) teamInvitationAdded( @Args({ @@ -183,9 +180,9 @@ export class TeamInvitationResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) teamInvitationRemoved( @Args({ diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts index db292749942..5a18ae64625 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts @@ -3,7 +3,7 @@ import * as O from 'fp-ts/Option'; import * as E from 'fp-ts/Either'; import { PrismaService } from 'src/prisma/prisma.service'; import { TeamInvitation as DBTeamInvitation } from '@prisma/client'; -import { TeamMember, TeamMemberRole } from 'src/team/team.model'; +import { TeamMember, TeamAccessRole } from 'src/team/team.model'; import { TeamService } from 'src/team/team.service'; import { INVALID_EMAIL, @@ -38,10 +38,10 @@ export class TeamInvitationService { * @param dbTeamInvitation database TeamInvitation * @returns TeamInvitation model */ - cast(dbTeamInvitation: DBTeamInvitation): TeamInvitation { + private cast(dbTeamInvitation: DBTeamInvitation): TeamInvitation { return { ...dbTeamInvitation, - inviteeRole: TeamMemberRole[dbTeamInvitation.inviteeRole], + inviteeRole: TeamAccessRole[dbTeamInvitation.inviteeRole], }; } @@ -103,7 +103,7 @@ export class TeamInvitationService { creator: AuthUser, teamID: string, inviteeEmail: string, - inviteeRole: TeamMemberRole, + inviteeRole: TeamAccessRole, ) { // validate email const isEmailValid = validateEmail(inviteeEmail); diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts b/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts index 571872eab22..a8a18cec560 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts @@ -11,7 +11,7 @@ import { TEAM_NOT_REQUIRED_ROLE, } from 'src/errors'; import { throwErr } from 'src/utils'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; /** * This guard only allows team owner to execute the resolver @@ -45,7 +45,7 @@ export class TeamInviteTeamOwnerGuard implements CanActivate { ); if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND); - if (teamMember.role !== TeamMemberRole.OWNER) + if (teamMember.role !== TeamAccessRole.OWNER) throwErr(TEAM_NOT_REQUIRED_ROLE); return true; diff --git a/packages/hoppscotch-backend/src/team-request/guards/gql-request-team-member.guard.ts b/packages/hoppscotch-backend/src/team-request/guards/gql-request-team-member.guard.ts index dc01901ea0d..75ed727783f 100644 --- a/packages/hoppscotch-backend/src/team-request/guards/gql-request-team-member.guard.ts +++ b/packages/hoppscotch-backend/src/team-request/guards/gql-request-team-member.guard.ts @@ -3,7 +3,7 @@ import { GqlExecutionContext } from '@nestjs/graphql'; import { TeamRequestService } from '../team-request.service'; import { TeamService } from '../../team/team.service'; import { Reflector } from '@nestjs/core'; -import { TeamMemberRole } from '../../team/team.model'; +import { TeamAccessRole } from '../../team/team.model'; import { BUG_AUTH_NO_USER_CTX, BUG_TEAM_REQ_NO_REQ_ID, @@ -23,7 +23,7 @@ export class GqlRequestTeamMemberGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const requireRoles = this.reflector.get( + const requireRoles = this.reflector.get( 'requiresTeamRole', context.getHandler(), ); @@ -36,9 +36,8 @@ export class GqlRequestTeamMemberGuard implements CanActivate { const { requestID } = gqlExecCtx.getArgs<{ requestID: string }>(); if (!requestID) throw new Error(BUG_TEAM_REQ_NO_REQ_ID); - const team = await this.teamRequestService.getTeamOfRequestFromID( - requestID, - ); + const team = + await this.teamRequestService.getTeamOfRequestFromID(requestID); if (O.isNone(team)) throw new Error(TEAM_REQ_NOT_FOUND); const member = await this.teamService.getTeamMember( diff --git a/packages/hoppscotch-backend/src/team-request/team-request.module.ts b/packages/hoppscotch-backend/src/team-request/team-request.module.ts index 4b3bd4a6324..78bc5ea6568 100644 --- a/packages/hoppscotch-backend/src/team-request/team-request.module.ts +++ b/packages/hoppscotch-backend/src/team-request/team-request.module.ts @@ -1,21 +1,13 @@ import { Module } from '@nestjs/common'; import { TeamRequestService } from './team-request.service'; import { TeamRequestResolver } from './team-request.resolver'; -import { PrismaModule } from '../prisma/prisma.module'; import { TeamModule } from '../team/team.module'; import { TeamCollectionModule } from '../team-collection/team-collection.module'; import { GqlRequestTeamMemberGuard } from './guards/gql-request-team-member.guard'; import { UserModule } from '../user/user.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; @Module({ - imports: [ - PrismaModule, - TeamModule, - TeamCollectionModule, - UserModule, - PubSubModule, - ], + imports: [TeamModule, TeamCollectionModule, UserModule], providers: [ TeamRequestService, TeamRequestResolver, diff --git a/packages/hoppscotch-backend/src/team-request/team-request.resolver.ts b/packages/hoppscotch-backend/src/team-request/team-request.resolver.ts index 52b652df8bb..da9bfc55886 100644 --- a/packages/hoppscotch-backend/src/team-request/team-request.resolver.ts +++ b/packages/hoppscotch-backend/src/team-request/team-request.resolver.ts @@ -17,7 +17,7 @@ import { MoveTeamRequestArgs, UpdateLookUpRequestOrderArgs, } from './input-type.args'; -import { Team, TeamMemberRole } from '../team/team.model'; +import { Team, TeamAccessRole } from '../team/team.model'; import { TeamRequestService } from './team-request.service'; import { TeamCollection } from '../team-collection/team-collection.model'; import { UseGuards } from '@nestjs/common'; @@ -57,9 +57,8 @@ export class TeamRequestResolver { complexity: 3, }) async collection(@Parent() req: TeamRequest) { - const teamCollection = await this.teamRequestService.getCollectionOfRequest( - req, - ); + const teamCollection = + await this.teamRequestService.getCollectionOfRequest(req); if (E.isLeft(teamCollection)) throwErr(teamCollection.left); return teamCollection.right; } @@ -70,9 +69,9 @@ export class TeamRequestResolver { }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, - TeamMemberRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, + TeamAccessRole.VIEWER, ) async searchForRequest(@Args() args: SearchTeamRequestArgs) { return this.teamRequestService.searchRequest( @@ -89,9 +88,9 @@ export class TeamRequestResolver { }) @UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, - TeamMemberRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, + TeamAccessRole.VIEWER, ) async request( @Args({ @@ -111,9 +110,9 @@ export class TeamRequestResolver { }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, - TeamMemberRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, + TeamAccessRole.VIEWER, ) async requestsInCollection(@Args() input: GetTeamRequestInCollectionArgs) { return this.teamRequestService.getRequestsInCollection( @@ -128,7 +127,7 @@ export class TeamRequestResolver { description: 'Create a team request in the given collection.', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.EDITOR, TeamAccessRole.OWNER) async createRequestInCollection( @Args({ name: 'collectionID', @@ -158,7 +157,7 @@ export class TeamRequestResolver { description: 'Update a request with the given ID', }) @UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.EDITOR, TeamAccessRole.OWNER) async updateRequest( @Args({ name: 'requestID', @@ -187,7 +186,7 @@ export class TeamRequestResolver { description: 'Delete a request with the given ID', }) @UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.EDITOR, TeamAccessRole.OWNER) async deleteRequest( @Args({ name: 'requestID', @@ -196,9 +195,8 @@ export class TeamRequestResolver { }) requestID: string, ) { - const isDeleted = await this.teamRequestService.deleteTeamRequest( - requestID, - ); + const isDeleted = + await this.teamRequestService.deleteTeamRequest(requestID); if (E.isLeft(isDeleted)) throwErr(isDeleted.left); return isDeleted.right; } @@ -207,7 +205,7 @@ export class TeamRequestResolver { description: 'Update the order of requests in the lookup table', }) @UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.EDITOR, TeamAccessRole.OWNER) async updateLookUpRequestOrder( @Args() args: UpdateLookUpRequestOrderArgs, @@ -227,7 +225,7 @@ export class TeamRequestResolver { description: 'Move a request to the given collection', }) @UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.EDITOR, TeamAccessRole.OWNER) async moveRequest(@Args() args: MoveTeamRequestArgs) { const teamRequest = await this.teamRequestService.moveRequest( args.srcCollID, @@ -248,9 +246,9 @@ export class TeamRequestResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) teamRequestAdded( @Args({ @@ -270,9 +268,9 @@ export class TeamRequestResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) teamRequestUpdated( @Args({ @@ -293,9 +291,9 @@ export class TeamRequestResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) teamRequestDeleted( @Args({ @@ -316,9 +314,9 @@ export class TeamRequestResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) requestOrderUpdated( @Args({ @@ -339,9 +337,9 @@ export class TeamRequestResolver { @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) requestMoved( @Args({ diff --git a/packages/hoppscotch-backend/src/team/decorators/requires-team-role.decorator.ts b/packages/hoppscotch-backend/src/team/decorators/requires-team-role.decorator.ts index 9eaf9f40646..56c428692ae 100644 --- a/packages/hoppscotch-backend/src/team/decorators/requires-team-role.decorator.ts +++ b/packages/hoppscotch-backend/src/team/decorators/requires-team-role.decorator.ts @@ -1,5 +1,5 @@ -import { TeamMemberRole } from '@prisma/client'; +import { TeamAccessRole } from '@prisma/client'; import { SetMetadata } from '@nestjs/common'; -export const RequiresTeamRole = (...roles: TeamMemberRole[]) => +export const RequiresTeamRole = (...roles: TeamAccessRole[]) => SetMetadata('requiresTeamRole', roles); diff --git a/packages/hoppscotch-backend/src/team/guards/gql-team-member.guard.ts b/packages/hoppscotch-backend/src/team/guards/gql-team-member.guard.ts index fcd2329d8a9..4fd13e99e44 100644 --- a/packages/hoppscotch-backend/src/team/guards/gql-team-member.guard.ts +++ b/packages/hoppscotch-backend/src/team/guards/gql-team-member.guard.ts @@ -1,7 +1,7 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { TeamService } from '../team.service'; -import { TeamMemberRole } from '../team.model'; +import { TeamAccessRole } from '../team.model'; import { GqlExecutionContext } from '@nestjs/graphql'; import { TEAM_NOT_REQUIRED_ROLE, @@ -19,7 +19,7 @@ export class GqlTeamMemberGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const requireRoles = this.reflector.get( + const requireRoles = this.reflector.get( 'requiresTeamRole', context.getHandler(), ); diff --git a/packages/hoppscotch-backend/src/team/guards/rest-team-member.guard.ts b/packages/hoppscotch-backend/src/team/guards/rest-team-member.guard.ts index f47de81f1ec..4355cb04550 100644 --- a/packages/hoppscotch-backend/src/team/guards/rest-team-member.guard.ts +++ b/packages/hoppscotch-backend/src/team/guards/rest-team-member.guard.ts @@ -1,7 +1,7 @@ import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; import { TeamService } from '../../team/team.service'; -import { TeamMemberRole } from '../../team/team.model'; +import { TeamAccessRole } from '../../team/team.model'; import { BUG_TEAM_NO_REQUIRE_TEAM_ROLE, BUG_AUTH_NO_USER_CTX, @@ -19,7 +19,7 @@ export class RESTTeamMemberGuard implements CanActivate { ) {} async canActivate(context: ExecutionContext): Promise { - const requireRoles = this.reflector.get( + const requireRoles = this.reflector.get( 'requiresTeamRole', context.getHandler(), ); diff --git a/packages/hoppscotch-backend/src/team/team.model.ts b/packages/hoppscotch-backend/src/team/team.model.ts index c370b0ee783..fcae19dd5f9 100644 --- a/packages/hoppscotch-backend/src/team/team.model.ts +++ b/packages/hoppscotch-backend/src/team/team.model.ts @@ -22,18 +22,18 @@ export class TeamMember { userUid: string; - @Field(() => TeamMemberRole, { + @Field(() => TeamAccessRole, { description: 'Role of the given team member in the given team', }) - role: TeamMemberRole; + role: TeamAccessRole; } -export enum TeamMemberRole { +export enum TeamAccessRole { OWNER = 'OWNER', VIEWER = 'VIEWER', EDITOR = 'EDITOR', } -registerEnumType(TeamMemberRole, { - name: 'TeamMemberRole', +registerEnumType(TeamAccessRole, { + name: 'TeamAccessRole', }); diff --git a/packages/hoppscotch-backend/src/team/team.module.ts b/packages/hoppscotch-backend/src/team/team.module.ts index 50bfa619002..40977850d69 100644 --- a/packages/hoppscotch-backend/src/team/team.module.ts +++ b/packages/hoppscotch-backend/src/team/team.module.ts @@ -4,11 +4,9 @@ import { TeamResolver } from './team.resolver'; import { UserModule } from '../user/user.module'; import { TeamMemberResolver } from './team-member.resolver'; import { GqlTeamMemberGuard } from './guards/gql-team-member.guard'; -import { PrismaModule } from '../prisma/prisma.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; @Module({ - imports: [UserModule, PubSubModule, PrismaModule], + imports: [UserModule], providers: [ TeamService, TeamResolver, diff --git a/packages/hoppscotch-backend/src/team/team.resolver.ts b/packages/hoppscotch-backend/src/team/team.resolver.ts index aeb4bc228e3..4760ae0c26b 100644 --- a/packages/hoppscotch-backend/src/team/team.resolver.ts +++ b/packages/hoppscotch-backend/src/team/team.resolver.ts @@ -1,4 +1,4 @@ -import { Team, TeamMember, TeamMemberRole } from './team.model'; +import { Team, TeamMember, TeamAccessRole } from './team.model'; import { Resolver, ResolveField, @@ -59,7 +59,7 @@ export class TeamResolver { return this.teamService.getTeamMembers(team.id); } - @ResolveField(() => TeamMemberRole, { + @ResolveField(() => TeamAccessRole, { description: 'The role of the current user in the team', nullable: true, }) @@ -67,7 +67,7 @@ export class TeamResolver { myRole( @Parent() team: Team, @GqlUser() user: AuthUser, - ): Promise { + ): Promise { return this.teamService.getRoleOfUserInTeam(team.id, user.uid); } @@ -77,7 +77,7 @@ export class TeamResolver { ownersCount(@Parent() team: Team): Promise { return this.teamService.getCountOfUsersWithRoleInTeam( team.id, - TeamMemberRole.OWNER, + TeamAccessRole.OWNER, ); } @@ -87,7 +87,7 @@ export class TeamResolver { editorsCount(@Parent() team: Team): Promise { return this.teamService.getCountOfUsersWithRoleInTeam( team.id, - TeamMemberRole.EDITOR, + TeamAccessRole.EDITOR, ); } @@ -97,7 +97,7 @@ export class TeamResolver { viewersCount(@Parent() team: Team): Promise { return this.teamService.getCountOfUsersWithRoleInTeam( team.id, - TeamMemberRole.VIEWER, + TeamAccessRole.VIEWER, ); } @@ -126,9 +126,9 @@ export class TeamResolver { }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @RequiresTeamRole( - TeamMemberRole.VIEWER, - TeamMemberRole.EDITOR, - TeamMemberRole.OWNER, + TeamAccessRole.VIEWER, + TeamAccessRole.EDITOR, + TeamAccessRole.OWNER, ) team( @Args({ @@ -178,7 +178,7 @@ export class TeamResolver { description: 'Removes the team member from the team', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) async removeTeamMember( @GqlUser() _user: AuthUser, @Args({ @@ -203,7 +203,7 @@ export class TeamResolver { description: 'Renames a team', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) async renameTeam( @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) teamID: string, @@ -219,7 +219,7 @@ export class TeamResolver { description: 'Deletes the team', }) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) async deleteTeam( @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) teamID: string, @@ -232,9 +232,9 @@ export class TeamResolver { @Mutation(() => TeamMember, { description: 'Update role of a team member the executing user owns', }) - @RequiresTeamRole(TeamMemberRole.OWNER) + @RequiresTeamRole(TeamAccessRole.OWNER) @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) - async updateTeamMemberRole( + async updateTeamAccessRole( @Args({ name: 'teamID', description: 'ID of the affected team', @@ -250,11 +250,11 @@ export class TeamResolver { @Args({ name: 'newRole', description: 'Updated role value', - type: () => TeamMemberRole, + type: () => TeamAccessRole, }) - newRole: TeamMemberRole, + newRole: TeamAccessRole, ): Promise { - const teamMember = await this.teamService.updateTeamMemberRole( + const teamMember = await this.teamService.updateTeamAccessRole( teamID, userUid, newRole, @@ -270,9 +270,9 @@ export class TeamResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -293,9 +293,9 @@ export class TeamResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) @@ -316,9 +316,9 @@ export class TeamResolver { resolve: (value) => value, }) @RequiresTeamRole( - TeamMemberRole.OWNER, - TeamMemberRole.EDITOR, - TeamMemberRole.VIEWER, + TeamAccessRole.OWNER, + TeamAccessRole.EDITOR, + TeamAccessRole.VIEWER, ) @SkipThrottle() @UseGuards(GqlAuthGuard, GqlTeamMemberGuard) diff --git a/packages/hoppscotch-backend/src/team/team.service.spec.ts b/packages/hoppscotch-backend/src/team/team.service.spec.ts index 60ee683e361..96ac41fa790 100644 --- a/packages/hoppscotch-backend/src/team/team.service.spec.ts +++ b/packages/hoppscotch-backend/src/team/team.service.spec.ts @@ -1,6 +1,6 @@ import { TeamService } from './team.service'; import { PrismaService } from '../prisma/prisma.service'; -import { Team, TeamMember, TeamMemberRole } from './team.model'; +import { Team, TeamMember, TeamAccessRole } from './team.model'; import { TeamMember as DbTeamMember } from '@prisma/client'; import { USER_NOT_FOUND, @@ -51,13 +51,13 @@ const teams: Team[] = [ ]; const dbTeamMember: DbTeamMember = { id: 'teamMemberID', - role: TeamMemberRole.VIEWER, + role: TeamAccessRole.VIEWER, userUid: 'userUid', teamID: team.id, }; const teamMember: TeamMember = { membershipID: dbTeamMember.id, - role: TeamMemberRole[dbTeamMember.role], + role: TeamAccessRole[dbTeamMember.role], userUid: dbTeamMember.userUid, }; @@ -70,14 +70,14 @@ describe('getCountOfUsersWithRoleInTeam', () => { await expect( teamService.getCountOfUsersWithRoleInTeam( dbTeamMember.teamID, - TeamMemberRole.OWNER, + TeamAccessRole.OWNER, ), ).resolves.toEqual(2); expect(mockPrisma.teamMember.count).toHaveBeenCalledWith({ where: { teamID: dbTeamMember.teamID, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }); }); @@ -88,14 +88,14 @@ describe('getCountOfUsersWithRoleInTeam', () => { await expect( teamService.getCountOfUsersWithRoleInTeam( dbTeamMember.teamID, - TeamMemberRole.VIEWER, + TeamAccessRole.VIEWER, ), ).resolves.toEqual(2); expect(mockPrisma.teamMember.count).toHaveBeenCalledWith({ where: { teamID: dbTeamMember.teamID, - role: TeamMemberRole.VIEWER, + role: TeamAccessRole.VIEWER, }, }); }); @@ -106,14 +106,14 @@ describe('getCountOfUsersWithRoleInTeam', () => { await expect( teamService.getCountOfUsersWithRoleInTeam( dbTeamMember.teamID, - TeamMemberRole.EDITOR, + TeamAccessRole.EDITOR, ), ).resolves.toEqual(2); expect(mockPrisma.teamMember.count).toHaveBeenCalledWith({ where: { teamID: dbTeamMember.teamID, - role: TeamMemberRole.EDITOR, + role: TeamAccessRole.EDITOR, }, }); }); @@ -127,7 +127,7 @@ describe('addMemberToTeam', () => { teamService.addMemberToTeam( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ), ).resolves.toEqual(expect.objectContaining(teamMember)); }); @@ -138,7 +138,7 @@ describe('addMemberToTeam', () => { await teamService.addMemberToTeam( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); expect(mockPrisma.teamMember.create).toHaveBeenCalledWith({ @@ -149,7 +149,7 @@ describe('addMemberToTeam', () => { id: dbTeamMember.teamID, }, }, - role: TeamMemberRole[dbTeamMember.role], + role: TeamAccessRole[dbTeamMember.role], }, }); }); @@ -160,7 +160,7 @@ describe('addMemberToTeam', () => { const member = await teamService.addMemberToTeam( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); expect(mockPubSub.publish).toHaveBeenCalledWith( @@ -188,7 +188,7 @@ describe('addMemberToTeamWithEmail', () => { const result = teamService.addMemberToTeamWithEmail( dbTeamMember.teamID, 'test@hoppscotch.io', - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); return expect(result).resolves.toBeDefined(); }); @@ -199,7 +199,7 @@ describe('addMemberToTeamWithEmail', () => { const result = teamService.addMemberToTeamWithEmail( dbTeamMember.teamID, 'test@hoppscotch.io', - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); return expect(result).resolves.toEqualLeft(USER_NOT_FOUND); }); @@ -215,7 +215,7 @@ describe('addMemberToTeamWithEmail', () => { await teamService.addMemberToTeamWithEmail( dbTeamMember.teamID, 'test@hoppscotch.io', - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); expect(mockPrisma.teamMember.create).toHaveBeenCalledWith({ @@ -226,7 +226,7 @@ describe('addMemberToTeamWithEmail', () => { id: dbTeamMember.teamID, }, }, - role: TeamMemberRole[dbTeamMember.role], + role: TeamAccessRole[dbTeamMember.role], }, }); }); @@ -242,7 +242,7 @@ describe('addMemberToTeamWithEmail', () => { await teamService.addMemberToTeamWithEmail( dbTeamMember.teamID, 'test@hoppscotch.io', - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ); expect(mockPubSub.publish).toHaveBeenCalledWith( @@ -376,7 +376,7 @@ describe('renameTeam', () => { }); }); -describe('updateTeamMemberRole', () => { +describe('updateTeamAccessRole', () => { /** * Test Scenario: * 3 users (testuid1 thru 3) having each of the roles @@ -385,19 +385,19 @@ describe('updateTeamMemberRole', () => { */ test('updates the role', async () => { - const newRole = TeamMemberRole.EDITOR; + const newRole = TeamAccessRole.EDITOR; mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole[dbTeamMember.role], + role: TeamAccessRole[dbTeamMember.role], }); mockPrisma.teamMember.update.mockResolvedValue({ ...dbTeamMember, role: newRole, }); - await teamService.updateTeamMemberRole( + await teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, newRole, @@ -417,7 +417,7 @@ describe('updateTeamMemberRole', () => { }); test('returns the updated details', () => { - const newRole = TeamMemberRole.EDITOR; + const newRole = TeamAccessRole.EDITOR; mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue(dbTeamMember); @@ -427,7 +427,7 @@ describe('updateTeamMemberRole', () => { }); return expect( - teamService.updateTeamMemberRole( + teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, newRole, @@ -439,17 +439,17 @@ describe('updateTeamMemberRole', () => { mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); // Prisma doesn't care if it goes through mockPrisma.teamMember.update.mockResolvedValue(dbTeamMember); return expect( - teamService.updateTeamMemberRole( + teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ), ).resolves.toEqualLeft(TEAM_ONLY_ONE_OWNER); }); @@ -458,18 +458,18 @@ describe('updateTeamMemberRole', () => { mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); mockPrisma.teamMember.update.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); return expect( - teamService.updateTeamMemberRole( + teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[TeamMemberRole.OWNER], + TeamAccessRole[TeamAccessRole.OWNER], ), ).resolves.toBeDefined(); }); @@ -478,28 +478,28 @@ describe('updateTeamMemberRole', () => { mockPrisma.teamMember.count.mockResolvedValue(2); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); mockPrisma.teamMember.update.mockResolvedValue(dbTeamMember); // Set another user as the owner - await teamService.updateTeamMemberRole( + await teamService.updateTeamAccessRole( dbTeamMember.teamID, 'testuid2', - TeamMemberRole.OWNER, + TeamAccessRole.OWNER, ); await expect( - teamService.updateTeamMemberRole( + teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, - TeamMemberRole[dbTeamMember.role], + TeamAccessRole[dbTeamMember.role], ), ).resolves.toBeDefined(); }); test('fires "team//member_updated" pubsub message with correct payload', async () => { - const newRole = TeamMemberRole.EDITOR; + const newRole = TeamAccessRole.EDITOR; mockPrisma.teamMember.count.mockResolvedValue(2); mockPrisma.teamMember.findUnique.mockResolvedValue(dbTeamMember); @@ -508,7 +508,7 @@ describe('updateTeamMemberRole', () => { role: newRole, }); - await teamService.updateTeamMemberRole( + await teamService.updateTeamAccessRole( dbTeamMember.teamID, dbTeamMember.userUid, newRole, @@ -582,13 +582,13 @@ describe('leaveTeam', () => { mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); // Prisma does not care mockPrisma.teamMember.delete.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); return expect( @@ -600,11 +600,11 @@ describe('leaveTeam', () => { mockPrisma.teamMember.count.mockResolvedValue(2); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); mockPrisma.teamMember.delete.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); await expect( @@ -652,7 +652,7 @@ describe('createTeam', () => { members: { create: { userUid: dbTeamMember.userUid, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }, }), @@ -905,7 +905,7 @@ describe('deleteUserFromAllTeams', () => { mockPrisma.teamMember.count.mockResolvedValue(1); mockPrisma.teamMember.findUnique.mockResolvedValue({ ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }); const result = teamService.deleteUserFromAllTeams(dbTeamMember.userUid)(); @@ -922,7 +922,7 @@ describe('deleteUserFromAllTeams', () => { mockPrisma.teamMember.findMany.mockResolvedValue([ { ...dbTeamMember, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, ]); mockPrisma.teamMember.count.mockResolvedValue(2); diff --git a/packages/hoppscotch-backend/src/team/team.service.ts b/packages/hoppscotch-backend/src/team/team.service.ts index 80a2240b134..96d3112e7a0 100644 --- a/packages/hoppscotch-backend/src/team/team.service.ts +++ b/packages/hoppscotch-backend/src/team/team.service.ts @@ -1,5 +1,5 @@ import { Injectable, OnModuleInit } from '@nestjs/common'; -import { TeamMember, TeamMemberRole, Team } from './team.model'; +import { TeamMember, TeamAccessRole, Team } from './team.model'; import { PrismaService } from '../prisma/prisma.service'; import { TeamMember as DbTeamMember } from '@prisma/client'; import { UserService } from '../user/user.service'; @@ -50,7 +50,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { async getCountOfUsersWithRoleInTeam( teamID: string, - role: TeamMemberRole, + role: TeamAccessRole, ): Promise { return await this.prisma.teamMember.count({ where: { @@ -63,7 +63,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { async addMemberToTeamWithEmail( teamID: string, email: string, - role: TeamMemberRole, + role: TeamAccessRole, ): Promise | E.Right> { const user = await this.userService.findUserByEmail(email); if (O.isNone(user)) return E.left(USER_NOT_FOUND); @@ -75,7 +75,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { async addMemberToTeam( teamID: string, uid: string, - role: TeamMemberRole, + role: TeamAccessRole, ): Promise { const teamMember = await this.prisma.teamMember.create({ data: { @@ -92,7 +92,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { const member: TeamMember = { membershipID: teamMember.id, userUid: teamMember.userUid, - role: TeamMemberRole[teamMember.role], + role: TeamAccessRole[teamMember.role], }; this.pubsub.publish(`team/${teamID}/member_added`, member); @@ -151,15 +151,15 @@ export class TeamService implements UserDataHandler, OnModuleInit { } } - async updateTeamMemberRole( + async updateTeamAccessRole( teamID: string, userUid: string, - newRole: TeamMemberRole, + newRole: TeamAccessRole, ): Promise | E.Right> { const ownerCount = await this.prisma.teamMember.count({ where: { teamID, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }); @@ -174,8 +174,8 @@ export class TeamService implements UserDataHandler, OnModuleInit { if (!member) return E.left(TEAM_MEMBER_NOT_FOUND); if ( - member.role === TeamMemberRole.OWNER && - newRole != TeamMemberRole.OWNER && + member.role === TeamAccessRole.OWNER && + newRole != TeamAccessRole.OWNER && ownerCount === 1 ) { return E.left(TEAM_ONLY_ONE_OWNER); @@ -196,7 +196,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { const updatedMember: TeamMember = { membershipID: result.id, userUid: result.userUid, - role: TeamMemberRole[result.role], + role: TeamAccessRole[result.role], }; this.pubsub.publish(`team/${teamID}/member_updated`, updatedMember); @@ -211,14 +211,14 @@ export class TeamService implements UserDataHandler, OnModuleInit { const ownerCount = await this.prisma.teamMember.count({ where: { teamID, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }); const member = await this.getTeamMember(teamID, userUid); if (!member) return E.left(TEAM_INVALID_ID_OR_USER); - if (ownerCount === 1 && member.role === TeamMemberRole.OWNER) { + if (ownerCount === 1 && member.role === TeamAccessRole.OWNER) { return E.left(TEAM_ONLY_ONE_OWNER); } @@ -254,7 +254,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { members: { create: { userUid: creatorUid, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }, }, @@ -368,7 +368,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { return { membershipID: teamMember.id, userUid: userUid, - role: TeamMemberRole[teamMember.role], + role: TeamAccessRole[teamMember.role], }; } catch (e) { return null; @@ -391,7 +391,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { async getRoleOfUserInTeam( teamID: string, userUid: string, - ): Promise { + ): Promise { const teamMember = await this.getTeamMember(teamID, userUid); return teamMember ? teamMember.role : null; } @@ -402,7 +402,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { const userOwnedTeams = await this.prisma.teamMember.findMany({ where: { userUid: uid, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, select: { teamID: true, @@ -413,7 +413,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { const ownerCount = await this.prisma.teamMember.count({ where: { teamID: userOwnedTeam.teamID, - role: TeamMemberRole.OWNER, + role: TeamAccessRole.OWNER, }, }); @@ -460,7 +460,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { { membershipID: entry.id, userUid: entry.userUid, - role: TeamMemberRole[entry.role], + role: TeamAccessRole[entry.role], }, ); @@ -513,7 +513,7 @@ export class TeamService implements UserDataHandler, OnModuleInit { { membershipID: entry.id, userUid: entry.userUid, - role: TeamMemberRole[entry.role], + role: TeamAccessRole[entry.role], }, ); diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.module.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.module.ts index aadc17028af..ee075fe81ef 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.module.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.module.ts @@ -1,12 +1,10 @@ import { Module } from '@nestjs/common'; import { UserCollectionService } from './user-collection.service'; import { UserCollectionResolver } from './user-collection.resolver'; -import { PrismaModule } from 'src/prisma/prisma.module'; import { UserModule } from 'src/user/user.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; @Module({ - imports: [PrismaModule, UserModule, PubSubModule], + imports: [UserModule], providers: [UserCollectionService, UserCollectionResolver], exports: [UserCollectionService], }) diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts index df530e3d793..7302715ee00 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.resolver.ts @@ -139,9 +139,8 @@ export class UserCollectionResolver { }) userCollectionID: string, ) { - const userCollection = await this.userCollectionService.getUserCollection( - userCollectionID, - ); + const userCollection = + await this.userCollectionService.getUserCollection(userCollectionID); if (E.isLeft(userCollection)) throwErr(userCollection.left); return { diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts index 31b218b4075..00153c64096 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.service.spec.ts @@ -23,11 +23,7 @@ import { UserCollection } from './user-collections.model'; const mockPrisma = mockDeep(); const mockPubSub = mockDeep(); -// @ts-ignore -const userCollectionService = new UserCollectionService( - mockPrisma, - mockPubSub as any, -); +const userCollectionService = new UserCollectionService(mockPrisma, mockPubSub); const currentTime = new Date(); @@ -99,36 +95,6 @@ const rootRESTUserCollection_2: DBUserCollection = { data: {}, }; -const rootRESTUserCollection_2Casted: UserCollection = { - id: '4gf', - parentID: null, - title: 'Root Collection 2', - userID: user.uid, - type: ReqType.REST, - data: JSON.stringify(rootRESTUserCollection_2.data), -}; - -const rootGQLUserCollection_2: DBUserCollection = { - id: '4gf', - orderIndex: 2, - parentID: null, - title: 'Root Collection 2', - userUid: user.uid, - type: ReqType.GQL, - createdOn: currentTime, - updatedOn: currentTime, - data: {}, -}; - -const rootGQLUserCollection_2Casted: UserCollection = { - id: '4gf', - parentID: null, - title: 'Root Collection 2', - userID: user.uid, - type: ReqType.GQL, - data: JSON.stringify(rootGQLUserCollection_2.data), -}; - const childRESTUserCollection: DBUserCollection = { id: '234', orderIndex: 1, @@ -192,27 +158,6 @@ const childRESTUserCollection_2Casted: UserCollection = { data: JSON.stringify({}), }; -const childGQLUserCollection_2: DBUserCollection = { - id: '0kn', - orderIndex: 2, - parentID: rootRESTUserCollection_2.id, - title: 'Child Collection 2', - userUid: user.uid, - type: ReqType.GQL, - createdOn: currentTime, - updatedOn: currentTime, - data: {}, -}; - -const childGQLUserCollection_2Casted: UserCollection = { - id: '0kn', - parentID: rootRESTUserCollection_2.id, - title: 'Child Collection 2', - userID: user.uid, - type: ReqType.GQL, - data: JSON.stringify({}), -}; - const childRESTUserCollectionList: DBUserCollection[] = [ { id: '234', @@ -639,9 +584,8 @@ describe('getParentOfUserCollection', () => { childRESTUserCollection, ); - const result = await userCollectionService.getParentOfUserCollection( - 'invalidId', - ); + const result = + await userCollectionService.getParentOfUserCollection('invalidId'); //TODO: check it not null expect(result).toEqual(null); }); @@ -899,7 +843,7 @@ describe('createUserCollection', () => { childRESTUserCollection, ); - const result = await userCollectionService.createUserCollection( + await userCollectionService.createUserCollection( user, childRESTUserCollection.title, JSON.stringify(childRESTUserCollection.data), @@ -924,7 +868,7 @@ describe('createUserCollection', () => { childGQLUserCollection, ); - const result = await userCollectionService.createUserCollection( + await userCollectionService.createUserCollection( user, childGQLUserCollection.title, JSON.stringify(childGQLUserCollection.data), @@ -949,7 +893,7 @@ describe('createUserCollection', () => { rootRESTUserCollection, ); - const result = await userCollectionService.createUserCollection( + await userCollectionService.createUserCollection( user, rootRESTUserCollection.title, JSON.stringify(rootRESTUserCollection.data), @@ -974,7 +918,7 @@ describe('createUserCollection', () => { rootGQLUserCollection, ); - const result = await userCollectionService.createUserCollection( + await userCollectionService.createUserCollection( user, rootGQLUserCollection.title, JSON.stringify(rootGQLUserCollection.data), @@ -1057,7 +1001,7 @@ describe('renameUserCollection', () => { title: 'NewTitle', }); - const result = await userCollectionService.renameUserCollection( + await userCollectionService.renameUserCollection( 'NewTitle', rootRESTUserCollection.id, user.uid, @@ -1162,7 +1106,7 @@ describe('deleteUserCollection', () => { rootRESTUserCollection, ); - const result = await userCollectionService.deleteUserCollection( + await userCollectionService.deleteUserCollection( rootRESTUserCollection.id, user.uid, ); @@ -1384,7 +1328,7 @@ describe('moveUserCollection', () => { orderIndex: 2, }); - const result = await userCollectionService.moveUserCollection( + await userCollectionService.moveUserCollection( childRESTUserCollection.id, null, user.uid, @@ -1507,7 +1451,7 @@ describe('moveUserCollection', () => { orderIndex: 1, }); - const result = await userCollectionService.moveUserCollection( + await userCollectionService.moveUserCollection( rootRESTUserCollection.id, childRESTUserCollection.id, user.uid, @@ -1624,7 +1568,7 @@ describe('updateUserCollectionOrder', () => { orderIndex: childRESTUserCollectionList.length, }); - const result = await userCollectionService.updateUserCollectionOrder( + await userCollectionService.updateUserCollectionOrder( childRESTUserCollectionList[4].id, null, user.uid, @@ -1711,7 +1655,7 @@ describe('updateUserCollectionOrder', () => { .mockResolvedValueOnce(childRESTUserCollectionList[4]) .mockResolvedValueOnce(childRESTUserCollectionList[2]); - const result = await userCollectionService.updateUserCollectionOrder( + await userCollectionService.updateUserCollectionOrder( childRESTUserCollectionList[4].id, childRESTUserCollectionList[2].id, user.uid, @@ -1818,7 +1762,7 @@ describe('updateUserCollection', () => { rootRESTUserCollection, ); - const result = await userCollectionService.updateUserCollection( + await userCollectionService.updateUserCollection( 'new_title', JSON.stringify({ foo: 'bar' }), rootRESTUserCollection.id, diff --git a/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts b/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts index 4366caf43de..d1c24986ff2 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collection.service.ts @@ -744,7 +744,7 @@ export class UserCollectionService { }, }); // Step 2: Update orderIndex of collection to length of list - const updatedUserCollection = await tx.userCollection.update({ + await tx.userCollection.update({ where: { id: collection.right.id }, data: { orderIndex: await this.getCollectionCount( @@ -805,7 +805,7 @@ export class UserCollectionService { }, }); // Step 3: Update OrderIndex of collection - const updatedUserCollection = await tx.userCollection.update({ + await tx.userCollection.update({ where: { id: collection.right.id }, data: { orderIndex: isMovingUp diff --git a/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts b/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts index d0f7717fc21..b522b37b2b5 100644 --- a/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts +++ b/packages/hoppscotch-backend/src/user-collection/user-collections.model.ts @@ -1,5 +1,4 @@ -import { ObjectType, Field, ID, registerEnumType } from '@nestjs/graphql'; -import { User } from '@prisma/client'; +import { ObjectType, Field, ID } from '@nestjs/graphql'; import { ReqType } from 'src/types/RequestTypes'; import { UserRequest } from 'src/user-request/user-request.model'; @@ -59,10 +58,6 @@ export class UserCollectionRemovedData { type: ReqType; } -registerEnumType(ReqType, { - name: 'CollType', -}); - @ObjectType() export class UserCollectionExportJSONData { @Field(() => ID, { @@ -120,7 +115,3 @@ export class UserCollectionDuplicatedData { }) requests: UserRequest[]; } - -registerEnumType(ReqType, { - name: 'CollType', -}); diff --git a/packages/hoppscotch-backend/src/user-environment/user-environments.module.ts b/packages/hoppscotch-backend/src/user-environment/user-environments.module.ts index 04f39168264..fc929d1e67d 100644 --- a/packages/hoppscotch-backend/src/user-environment/user-environments.module.ts +++ b/packages/hoppscotch-backend/src/user-environment/user-environments.module.ts @@ -1,13 +1,11 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from '../prisma/prisma.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; import { UserModule } from '../user/user.module'; import { UserEnvsUserResolver } from './user.resolver'; import { UserEnvironmentsResolver } from './user-environments.resolver'; import { UserEnvironmentsService } from './user-environments.service'; @Module({ - imports: [PrismaModule, PubSubModule, UserModule], + imports: [UserModule], providers: [ UserEnvironmentsResolver, UserEnvironmentsService, diff --git a/packages/hoppscotch-backend/src/user-environment/user.resolver.ts b/packages/hoppscotch-backend/src/user-environment/user.resolver.ts index 0d6d31cf476..c1d8d31d4ec 100644 --- a/packages/hoppscotch-backend/src/user-environment/user.resolver.ts +++ b/packages/hoppscotch-backend/src/user-environment/user.resolver.ts @@ -8,6 +8,7 @@ import { throwErr } from '../utils'; @Resolver(() => User) export class UserEnvsUserResolver { constructor(private userEnvironmentsService: UserEnvironmentsService) {} + @ResolveField(() => [UserEnvironment], { description: 'Returns a list of users personal environments', }) diff --git a/packages/hoppscotch-backend/src/user-history/user-history.module.ts b/packages/hoppscotch-backend/src/user-history/user-history.module.ts index ce2d6884dd6..314d6b8ae18 100644 --- a/packages/hoppscotch-backend/src/user-history/user-history.module.ts +++ b/packages/hoppscotch-backend/src/user-history/user-history.module.ts @@ -1,6 +1,4 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from '../prisma/prisma.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; import { UserModule } from '../user/user.module'; import { UserHistoryUserResolver } from './user.resolver'; import { UserHistoryResolver } from './user-history.resolver'; @@ -8,7 +6,7 @@ import { UserHistoryService } from './user-history.service'; import { InfraConfigModule } from 'src/infra-config/infra-config.module'; @Module({ - imports: [PrismaModule, PubSubModule, UserModule, InfraConfigModule], + imports: [UserModule, InfraConfigModule], providers: [UserHistoryResolver, UserHistoryService, UserHistoryUserResolver], exports: [UserHistoryService], }) diff --git a/packages/hoppscotch-backend/src/user-history/user.resolver.ts b/packages/hoppscotch-backend/src/user-history/user.resolver.ts index 95eab34a690..b6758d159ae 100644 --- a/packages/hoppscotch-backend/src/user-history/user.resolver.ts +++ b/packages/hoppscotch-backend/src/user-history/user.resolver.ts @@ -8,6 +8,7 @@ import { PaginationArgs } from '../types/input-types.args'; @Resolver(() => User) export class UserHistoryUserResolver { constructor(private userHistoryService: UserHistoryService) {} + @ResolveField(() => [UserHistory], { description: 'Returns a users REST history', }) diff --git a/packages/hoppscotch-backend/src/user-request/resolvers/user-request.resolver.ts b/packages/hoppscotch-backend/src/user-request/resolvers/user-request.resolver.ts index cff972dd35e..aaff691af12 100644 --- a/packages/hoppscotch-backend/src/user-request/resolvers/user-request.resolver.ts +++ b/packages/hoppscotch-backend/src/user-request/resolvers/user-request.resolver.ts @@ -1,7 +1,6 @@ import { UseGuards } from '@nestjs/common'; import { Args, - Context, ID, Mutation, Query, diff --git a/packages/hoppscotch-backend/src/user-request/user-request.module.ts b/packages/hoppscotch-backend/src/user-request/user-request.module.ts index e91eacdd89f..b1c465d23e6 100644 --- a/packages/hoppscotch-backend/src/user-request/user-request.module.ts +++ b/packages/hoppscotch-backend/src/user-request/user-request.module.ts @@ -1,13 +1,11 @@ import { Module } from '@nestjs/common'; import { UserCollectionModule } from 'src/user-collection/user-collection.module'; -import { PrismaModule } from '../prisma/prisma.module'; -import { PubSubModule } from '../pubsub/pubsub.module'; import { UserRequestUserCollectionResolver } from './resolvers/user-collection.resolver'; import { UserRequestResolver } from './resolvers/user-request.resolver'; import { UserRequestService } from './user-request.service'; @Module({ - imports: [PrismaModule, PubSubModule, UserCollectionModule], + imports: [UserCollectionModule], providers: [ UserRequestResolver, UserRequestUserCollectionResolver, diff --git a/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts b/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts index c5adb0d278a..2eb26bf9c7c 100644 --- a/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts +++ b/packages/hoppscotch-backend/src/user-request/user-request.service.spec.ts @@ -27,10 +27,9 @@ const mockPrisma = mockDeep(); const mockPubSub = mockDeep(); const mockUserCollectionService = mockDeep(); -// @ts-ignore const userRequestService = new UserRequestService( mockPrisma, - mockPubSub as any, + mockPubSub, mockUserCollectionService, ); @@ -541,7 +540,7 @@ describe('UserRequestService', () => { mockPrisma.userRequest.findFirst.mockResolvedValue(dbUserRequests[0]); mockPrisma.userRequest.delete.mockResolvedValue(null); - const result = await userRequestService.deleteRequest(id, user); + await userRequestService.deleteRequest(id, user); expect(mockPubSub.publish).toHaveBeenCalledWith( `user_request/${dbUserRequests[0].userUid}/deleted`, diff --git a/packages/hoppscotch-backend/src/user-request/user-request.service.ts b/packages/hoppscotch-backend/src/user-request/user-request.service.ts index 5d820a87fe1..e0e952b4885 100644 --- a/packages/hoppscotch-backend/src/user-request/user-request.service.ts +++ b/packages/hoppscotch-backend/src/user-request/user-request.service.ts @@ -120,9 +120,8 @@ export class UserRequestService { const jsonRequest = stringToJson(request); if (E.isLeft(jsonRequest)) return E.left(jsonRequest.left); - const collection = await this.userCollectionService.getUserCollection( - collectionID, - ); + const collection = + await this.userCollectionService.getUserCollection(collectionID); if (E.isLeft(collection)) return E.left(collection.left); if (collection.right.userUid !== user.uid) @@ -132,9 +131,8 @@ export class UserRequestService { return E.left(USER_REQUEST_INVALID_TYPE); try { - const requestCount = await this.getRequestsCountInCollection( - collectionID, - ); + const requestCount = + await this.getRequestsCountInCollection(collectionID); const request = await this.prisma.userRequest.create({ data: { diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts index df3547243f0..ac5204b9cfe 100644 --- a/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts @@ -1,13 +1,11 @@ import { Module } from '@nestjs/common'; -import { PrismaModule } from 'src/prisma/prisma.module'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; import { UserModule } from 'src/user/user.module'; import { UserSettingsResolver } from './user-settings.resolver'; import { UserSettingsService } from './user-settings.service'; import { UserSettingsUserResolver } from './user.resolver'; @Module({ - imports: [PrismaModule, PubSubModule, UserModule], + imports: [UserModule], providers: [ UserSettingsResolver, UserSettingsService, diff --git a/packages/hoppscotch-backend/src/user/user.model.ts b/packages/hoppscotch-backend/src/user/user.model.ts index ffe1e6b6262..4cec5ce23f4 100644 --- a/packages/hoppscotch-backend/src/user/user.model.ts +++ b/packages/hoppscotch-backend/src/user/user.model.ts @@ -79,11 +79,11 @@ export class UserDeletionResult { @Field(() => Boolean, { description: 'Flag to determine if user deletion was successful or not', }) - isDeleted: Boolean; + isDeleted: boolean; @Field({ nullable: true, description: 'Error message if user deletion was not successful', }) - errorMessage: String; + errorMessage: string; } diff --git a/packages/hoppscotch-backend/src/user/user.module.ts b/packages/hoppscotch-backend/src/user/user.module.ts index d02ec68ce11..bdd2ccf1d4c 100644 --- a/packages/hoppscotch-backend/src/user/user.module.ts +++ b/packages/hoppscotch-backend/src/user/user.module.ts @@ -1,11 +1,8 @@ import { Module } from '@nestjs/common'; import { UserResolver } from './user.resolver'; -import { PubSubModule } from 'src/pubsub/pubsub.module'; import { UserService } from './user.service'; -import { PrismaModule } from 'src/prisma/prisma.module'; @Module({ - imports: [PubSubModule, PrismaModule], providers: [UserResolver, UserService], exports: [UserService], }) diff --git a/packages/hoppscotch-backend/src/user/user.service.ts b/packages/hoppscotch-backend/src/user/user.service.ts index c3162e85a73..cb271e22f92 100644 --- a/packages/hoppscotch-backend/src/user/user.service.ts +++ b/packages/hoppscotch-backend/src/user/user.service.ts @@ -21,12 +21,12 @@ import { UserDataHandler } from './user.data.handler'; import { User as DbUser } from '@prisma/client'; import { OffsetPaginationArgs } from 'src/types/input-types.args'; import { GetUserWorkspacesResponse } from 'src/infra-token/request-response.dto'; -import { TeamMemberRole } from 'src/team/team.model'; +import { TeamAccessRole } from 'src/team/team.model'; @Injectable() export class UserService { constructor( - private prisma: PrismaService, + private readonly prisma: PrismaService, private readonly pubsub: PubSubService, ) {} @@ -626,13 +626,13 @@ export class UserService { const workspaces: GetUserWorkspacesResponse[] = []; team.forEach((t) => { const ownerCount = t.members.filter( - (m) => m.role === TeamMemberRole.OWNER, + (m) => m.role === TeamAccessRole.OWNER, ).length; const editorCount = t.members.filter( - (m) => m.role === TeamMemberRole.EDITOR, + (m) => m.role === TeamAccessRole.EDITOR, ).length; const viewerCount = t.members.filter( - (m) => m.role === TeamMemberRole.VIEWER, + (m) => m.role === TeamAccessRole.VIEWER, ).length; const memberCount = t.members.length; diff --git a/packages/hoppscotch-backend/src/utils.ts b/packages/hoppscotch-backend/src/utils.ts index 23afc3fa33a..69844e86a00 100644 --- a/packages/hoppscotch-backend/src/utils.ts +++ b/packages/hoppscotch-backend/src/utils.ts @@ -16,7 +16,7 @@ import { ENV_NOT_SUPPORT_AUTH_PROVIDERS, JSON_INVALID, } from './errors'; -import { TeamMemberRole } from './team/team.model'; +import { TeamAccessRole } from './team/team.model'; import { RESTError } from './types/RESTError'; import * as crypto from 'crypto'; @@ -76,7 +76,7 @@ export const getAnnotatedRequiredRoles = ( context: ExecutionContext, ) => pipe( - reflector.get('requiresTeamRole', context.getHandler()), + reflector.get('requiresTeamRole', context.getHandler()), O.fromNullable, ); diff --git a/packages/hoppscotch-cli/README.md b/packages/hoppscotch-cli/README.md index 28a1efdb57b..aa9f0ec0472 100644 --- a/packages/hoppscotch-cli/README.md +++ b/packages/hoppscotch-cli/README.md @@ -19,54 +19,74 @@ hopp [options or commands] arguments ## **Command Descriptions:** -1. #### **`hopp -v` / `hopp --ver`** +1. #### **`hopp -v` / `hopp --ver`** - - Prints out the current version of the Hoppscotch CLI + - Prints out the current version of the Hoppscotch CLI -2. #### **`hopp -h` / `hopp --help`** +2. #### **`hopp -h` / `hopp --help`** - - Displays the help text + - Displays the help text -3. #### **`hopp test [options] `** +3. #### **`hopp test [options] `** - - Interactive CLI to accept Hoppscotch collection JSON path - - Parses the collection JSON and executes each requests - - Executes pre-request script. - - Outputs the response of each request. - - Executes and outputs test-script response. + - Interactive CLI to accept Hoppscotch collection JSON path + - Parses the collection JSON and executes each requests + - Executes pre-request script. + - Outputs the response of each request. + - Executes and outputs test-script response. - #### Options: + #### Options: - ##### `-e ` / `--env ` + ##### `-e, --env ` - - Accepts path to env.json with contents in below format: + - Accepts path to env.json with contents in below format: - ```json - { - "ENV1": "value1", - "ENV2": "value2" - } - ``` + ```json + { + "ENV1": "value1", + "ENV2": "value2" + } + ``` + + - You can now access those variables using `pw.env.get('')` + + Taking the above example, `pw.env.get("ENV1")` will return `"value1"` + + #### `-d, --delay ` + + - Used to defer the execution of requests in a collection. + + #### `--token ` - - You can now access those variables using `pw.env.get('')` + - Expects a personal access token to be passed for establishing connection with your Hoppscotch account. - Taking the above example, `pw.env.get("ENV1")` will return `"value1"` + #### `--server ` - ##### `--iteration-count ` + - URL of your self-hosted instance, if your collections are on a self-hosted instance. - - Accepts the number of iterations to run the collection + #### `--reporter-junit [path]` - ##### `--iteration-data ` + - Expects a file path to store the JUnit Report. + + ##### `--iteration-count ` + + - Accepts the number of iterations to run the collection + + ##### `--iteration-data ` + + - Accepts the path to a CSV file with contents in the below format: + + ```text + key1,key2,key3 + value1,value2,value3 + value4,value5,value6 + ``` - - Accepts the path to a CSV file with contents in the below format: + For every iteration the values will be replaced with the respective keys in the environment. For iteration 1 the value1,value2,value3 will be replaced and for iteration 2 value4,value5,value6 will be replaced and so on. - ```text - key1,key2,key3 - value1,value2,value3 - value4,value5,value6 - ``` + #### `--legacy-sandbox` - For every iteration the values will be replaced with the respective keys in the environment. For iteration 1 the value1,value2,value3 will be replaced and for iteration 2 value4,value5,value6 will be replaced and so on. + - Opt out from the experimental scripting sandbox. ## Install diff --git a/packages/hoppscotch-cli/package.json b/packages/hoppscotch-cli/package.json index eb5e41cc799..6e2a2dc0726 100644 --- a/packages/hoppscotch-cli/package.json +++ b/packages/hoppscotch-cli/package.json @@ -1,6 +1,6 @@ { "name": "@hoppscotch/cli", - "version": "0.21.0", + "version": "0.22.0", "description": "A CLI to run Hoppscotch test scripts in CI environments.", "homepage": "https://hoppscotch.io", "type": "module", @@ -52,7 +52,7 @@ "qs": "6.13.0", "verzod": "0.2.4", "xmlbuilder2": "3.1.1", - "zod": "3.23.8" + "zod": "3.25.32" }, "devDependencies": { "@hoppscotch/data": "workspace:^", @@ -66,7 +66,7 @@ "qs": "6.11.2", "semver": "7.6.3", "tsup": "8.3.0", - "typescript": "5.6.3", + "typescript": "5.8.3", "vitest": "2.1.2" } } diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap b/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap index 070d36bd289..9c3e21c6e98 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap +++ b/packages/hoppscotch-cli/src/__tests__/e2e/commands/__snapshots__/test.spec.ts.snap @@ -162,7 +162,7 @@ exports[`hopp test [options] > Test\`hopp test + TEST_SCRIPT_ERROR - Script execution failed: ReferenceError: 'status' is not defined]]> > Test\`hopp test + TEST_SCRIPT_ERROR - Script execution failed: ReferenceError: 'status' is not defined]]> ", { timeout: 100000 }, () => { const testFixtures = [ { fileName: "env-v0.json", version: 0 }, { fileName: "env-v1.json", version: 1 }, + { fileName: "env-v2.json", version: 2 }, ]; testFixtures.forEach(({ fileName, version }) => { @@ -179,6 +180,44 @@ describe("hopp test [options] ", { timeout: 100000 }, () => { expect(error).toBeNull(); }); }); + + test("Successfully display console logs and recognizes platform APIs in the experimental scripting sandbox", async () => { + const args = `test ${getTestJsonFilePath( + "test-scripting-sandbox-modes-coll.json", + "collection" + )}`; + const { error, stdout } = await runCLI(args); + + expect(error).toBeNull(); + + const expectedStaticParts = [ + "https://example.com/path?foo=bar&baz=qux", + "'0': 72", + "'12': 33", + "Decoded: Hello, world!", + "Hello after 1s", + ]; + + // Assert that each stable part appears in the output + expectedStaticParts.forEach((part) => { + expect(stdout).toContain(part); + }); + + const every500msCount = (stdout.match(/Every 500ms/g) || []).length; + expect(every500msCount).toBeGreaterThanOrEqual(3); + }); + + test("Fails to display console logs and recognize platform APIs in the legacy scripting sandbox", async () => { + const args = `test ${getTestJsonFilePath( + "test-scripting-sandbox-modes-coll.json", + "collection" + )} --legacy-sandbox`; + const { error, stdout } = await runCLI(args); + + expect(error).toBeTruthy(); + expect(stdout).not.toContain("https://example.com/path?foo=bar&baz=qux"); + expect(stdout).not.toContain("Encoded"); + }); }); test("Ensures tests run in sequence order based on request path", async () => { diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-headers-success-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-headers-success-coll.json new file mode 100644 index 00000000000..38d15711d3c --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-headers-success-coll.json @@ -0,0 +1,41 @@ +{ + "v": 3, + "name": "JWT Auth (headers) - collection", + "folders": [], + "requests": [ + { + "v": "13", + "id": "cm0dm70cw000687bnxi830zz3", + "auth": { + "authType": "jwt", + "authActive": true, + "addTo": "HEADERS", + "algorithm": "<>", + "secret": "<>", + "privateKey": "<>", + "payload": "<>", + "jwtHeaders": "<>", + "headerPrefix": "<>", + "isSecretBase64Encoded": false + }, + "body": { + "body": null, + "contentType": null + }, + "name": "jwt-auth-headers", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Status code is 200\", ()=> { pw.expect(pw.response.status).toBe(200);});", + "preRequestScript": "", + "responses": {}, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-params-success-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-params-success-coll.json new file mode 100644 index 00000000000..25a9a3c1ce4 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/jwt-auth-params-success-coll.json @@ -0,0 +1,41 @@ +{ + "v": 3, + "name": "JWT Auth (params) - collection", + "folders": [], + "requests": [ + { + "v": "13", + "id": "cm0dm70cw000687bnxi830zz4", + "auth": { + "authType": "jwt", + "authActive": true, + "addTo": "QUERY_PARAMS", + "algorithm": "<>", + "secret": "<>", + "privateKey": "<>", + "payload": "<>", + "jwtHeaders": "<>", + "paramName": "<>", + "isSecretBase64Encoded": false + }, + "body": { + "body": null, + "contentType": null + }, + "name": "jwt-auth-params", + "method": "GET", + "params": [], + "headers": [], + "endpoint": "<>", + "testScript": "pw.test(\"Status code is 200\", ()=> { pw.expect(pw.response.status).toBe(200);});", + "preRequestScript": "", + "responses": {}, + "requestVariables": [] + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-scripting-sandbox-modes-coll.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-scripting-sandbox-modes-coll.json new file mode 100644 index 00000000000..548b9cef998 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/collections/test-scripting-sandbox-modes-coll.json @@ -0,0 +1,34 @@ +{ + "v": 7, + "id": "cmb4vtsqh00nxwvqryk6jmnaz", + "name": "test-scripting-sandbox-modes", + "folders": [], + "requests": [ + { + "v": "12", + "name": "sample-req", + "method": "GET", + "endpoint": "https://echo.hoppscotch.io", + "params": [], + "headers": [], + "preRequestScript": "const url = new URL('https://example.com/path?foo=bar');\nurl.searchParams.set('baz', 'qux');\nurl.toString(); // 'https://example.com/path?foo=bar&baz=qux'\n\nconsole.debug(url)\n\nconst encoder = new TextEncoder();\n\nconst text = \"Hello, world!\";\n\nconst encoded = encoder.encode(text);\nconsole.log(\"Encoded:\", encoded);\n\nconst decoder = new TextDecoder();\n\nconst decoded = decoder.decode(encoded);\nconsole.log(\"Decoded:\", decoded);\n\nsetTimeout(() => console.log(\"Hello after 1s\"), 1000);\n \nconst intervalId = setInterval(() => console.log(\"Every 500ms\"), 500);\n\nsetTimeout(() => clearInterval(intervalId), 2000);", + "testScript": "console.log(JSON.stringify(pw, null, 2))\n\n\npw.test(\"Sample assertion\", ()=> {\n pw.expect(pw.response.status).toBeLevel2xx()\n \tconsole.log(\"Status code received is \", pw.response.status)\n});\n\n", + "auth": { + "authType": "inherit", + "authActive": true + }, + "body": { + "contentType": null, + "body": null + }, + "requestVariables": [], + "responses": {} + } + ], + "auth": { + "authType": "inherit", + "authActive": true + }, + "headers": [], + "_ref_id": "coll_mb4vvacd_e015964c-b5c8-4b90-a564-4a8b62bc1631" +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json index 534bf7113fb..486e69c6501 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/aws-signature-auth-envs.json @@ -1,34 +1,42 @@ { - "v": 1, + "v": 2, "id": "cm0dsn3v70004p4qk3l9b7sjm", "name": "AWS Signature - environments", "variables": [ { "key": "awsRegion", - "value": "us-east-1", + "currentValue": "us-east-1", + "initialValue": "us-east-1", "secret": false }, { "key": "serviceName", - "value": "s3", + "currentValue": "s3", + "initialValue": "s3", "secret": false }, { "key": "accessKey", - "value": "test-access-key", + "currentValue": "test-access-key", + "initialValue": "test-access-key", "secret": true }, { "key": "secretKey", + "currentValue": "", + "initialValue": "", "secret": true }, { "key": "url", - "value": "https://echo.hoppscotch.io", + "currentValue": "https://echo.hoppscotch.io", + "initialValue": "https://echo.hoppscotch.io", "secret": false }, { "key": "serviceToken", + "currentValue": "", + "initialValue": "", "secret": true } ] diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json index eacbcbc07ce..63e13d5a22d 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/digest-auth-envs.json @@ -1,21 +1,25 @@ { - "v": 1, + "v": 2, "id": "cm0dsn3v70004p4qk3l9b7sjm", "name": "Digest Auth - environments", "variables": [ { "key": "username", - "value": "admin", + "currentValue": "", + "initialValue": "admin", "secret": true }, { "key": "password", - "value": "admin", + "currentValue": "", + "initialValue": "admin", "secret": true }, { "key": "url", - "value": "https://test.insightres.org/digest/" + "currentValue": "", + "initialValue": "https://test.insightres.org/digest/", + "secret": false } ] } diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json index 4ab5aa65f74..c6f7c9190e2 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v1.json @@ -1,10 +1,10 @@ { - "name": "env-v0", - "variables": [ - { - "key": "baseURL", - "value": "https://echo.hoppscotch.io", - "secret": false - } - ] - } \ No newline at end of file + "name": "env-v1", + "variables": [ + { + "key": "baseURL", + "value": "https://echo.hoppscotch.io", + "secret": false + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v2.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v2.json new file mode 100644 index 00000000000..1c170dbd9ff --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/env-v2.json @@ -0,0 +1,13 @@ +{ + "id": "env-v2", + "v": 2, + "name": "env-v2", + "variables": [ + { + "key": "baseURL", + "initialValue": "https://echo.hoppscotch.io", + "currentValue": "https://echo.hoppscotch.io", + "secret": false + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/jwt-auth-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/jwt-auth-envs.json new file mode 100644 index 00000000000..768bea0ced4 --- /dev/null +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/jwt-auth-envs.json @@ -0,0 +1,43 @@ +{ + "v": 1, + "id": "cm0dsn3v70004p4qk3l9b7sjn", + "name": "JWT Auth - environments", + "variables": [ + { + "key": "url", + "value": "http://localhost:8888/auth/jwt" + }, + { + "key": "algorithm", + "value": "HS256" + }, + { + "key": "secret", + "value": "secret-1" + }, + { + "key": "privateKey", + "value": "" + }, + { + "key": "payload", + "value": "{\"user\":\"test\",\"role\":\"admin\"}" + }, + { + "key": "jwtHeaders", + "value": "{}" + }, + { + "key": "headerPrefix", + "value": "Bearer " + }, + { + "key": "paramName", + "value": "token" + }, + { + "key": "isSecretBase64Encoded", + "value": "false" + } + ] +} diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json index 844af0108b0..ff96dced121 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/request-vars-envs.json @@ -1,33 +1,42 @@ { - "v": 1, + "v": 2, "id": "cm00r7kpb0006mbd2nq1560w6", "name": "Request variables alongside environment variables", "variables": [ { "key": "url", - "value": "https://echo.hoppscotch.io", + "initialValue": "https://echo.hoppscotch.io", + "currentValue": "https://echo.hoppscotch.io", "secret": false }, { "key": "secretBasicAuthPasswordEnvVar", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretBasicAuthUsernameEnvVar", - "value": "username", + "initialValue": "username", + "currentValue": "", "secret": true }, { "key": "username", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "password", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "testHeaderValue", - "value": "test-header-value", + "initialValue": "test-header-value", + "currentValue": "", "secret": false } ] diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json index b03e0508c3e..e1411452f96 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs-persistence-scripting-envs.json @@ -1,26 +1,36 @@ { - "v": 1, + "v": 2, "id": "2", "name": "secret-envs-persistence-scripting-envs", "variables": [ { "key": "preReqVarOne", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "preReqVarTwo", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "postReqVarOne", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "preReqVarTwo", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "customHeaderValueFromSecretVar", + "initialValue": "", + "currentValue": "", "secret": true } ] diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json index 266f821c8a0..1c4ae820b25 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-envs.json @@ -1,44 +1,60 @@ { "id": "2", - "v": 1, + "v": 2, "name": "secret-envs", "variables": [ { "key": "secretBearerToken", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretBasicAuthUsername", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretBasicAuthPassword", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretQueryParamValue", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretBodyValue", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "secretHeaderValue", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "nonExistentValueInSystemEnv", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "echoHoppBaseURL", - "value": "https://echo.hoppscotch.io", + "initialValue": "https://echo.hoppscotch.io", + "currentValue": "", "secret": false }, { "key": "httpbinBaseURL", - "value": "https://httpbin.org", + "initialValue": "https://httpbin.org", + "currentValue": "", "secret": false } ] diff --git a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json index 88155f84e77..9c331bd9a52 100644 --- a/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json +++ b/packages/hoppscotch-cli/src/__tests__/e2e/fixtures/environments/secret-supplied-values-envs.json @@ -1,50 +1,60 @@ { - "v": 1, + "v": 2, "id": "2", "name": "secret-values-envs", "variables": [ { "key": "secretBearerToken", - "value": "test-token", + "initialValue": "test-token", + "currentValue": "test-token", "secret": true }, { "key": "secretBasicAuthUsername", - "value": "test-user", + "initialValue": "test-user", + "currentValue": "test-user", "secret": true }, { "key": "secretBasicAuthPassword", - "value": "test-pass", + "initialValue": "test-pass", + "currentValue": "test-pass", "secret": true }, { "key": "secretQueryParamValue", - "value": "secret-query-param-value", + "initialValue": "secret-query-param-value", + "currentValue": "secret-query-param-value", "secret": true }, { "key": "secretBodyValue", - "value": "secret-body-value", + "initialValue": "secret-body-value", + "currentValue": "secret-body-value", "secret": true }, { "key": "secretHeaderValue", - "value": "secret-header-value", + "initialValue": "secret-header-value", + "currentValue": "secret-header-value", "secret": true }, { "key": "nonExistentValueInSystemEnv", + "initialValue": "", + "currentValue": "", "secret": true }, { "key": "echoHoppBaseURL", - "value": "https://echo.hoppscotch.io", + "initialValue": "https://echo.hoppscotch.io", + "currentValue": "https://echo.hoppscotch.io", "secret": false }, { "key": "httpbinBaseURL", - "value": "https://httpbin.org", + "initialValue": "https://httpbin.org", + "currentValue": "https://httpbin.org", "secret": false } ] diff --git a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts index 6159f923935..86edb4b8d66 100644 --- a/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/functions/mutators/parseCollectionData.spec.ts @@ -20,9 +20,7 @@ describe("parseCollectionData", () => { test("Invalid HoppCollection.", () => { return expect( - parseCollectionData( - "./src/__tests__/samples/malformed-collection2.json" - ) + parseCollectionData("./src/__tests__/samples/malformed-collection2.json") ).rejects.toMatchObject({ code: "MALFORMED_COLLECTION", }); diff --git a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts index d18e8eea1a5..9561317847e 100644 --- a/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts +++ b/packages/hoppscotch-cli/src/__tests__/unit/fixtures/workspace-access.mock.ts @@ -485,17 +485,17 @@ export const WORKSPACE_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Worksp export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: HoppCollection[] = [ { - v: 7, + v: 8, id: "clx1f86hv000010f8szcfya0t", name: "Multiple child collections with authorization & headers set at each level", folders: [ { - v: 7, + v: 8, id: "clx1fjgah000110f8a5bs68gd", name: "folder-1", folders: [ { - v: 7, + v: 8, id: "clx1fjwmm000410f8l1gkkr1a", name: "folder-11", folders: [], @@ -537,7 +537,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fjyxm000510f8pv90dt43", name: "folder-12", folders: [], @@ -595,7 +595,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fk1cv000610f88kc3aupy", name: "folder-13", folders: [], @@ -707,12 +707,12 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fjk9o000210f8j0573pls", name: "folder-2", folders: [ { - v: 7, + v: 8, id: "clx1fk516000710f87sfpw6bo", name: "folder-21", folders: [], @@ -752,7 +752,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fk72t000810f8gfwkpi5y", name: "folder-22", folders: [], @@ -810,7 +810,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fk95g000910f8bunhaoo8", name: "folder-23", folders: [], @@ -915,12 +915,12 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1fjmlq000310f86o4d3w2o", name: "folder-3", folders: [ { - v: 7, + v: 8, id: "clx1iwq0p003e10f8u8zg0p85", name: "folder-31", folders: [], @@ -960,7 +960,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1izut7003m10f894ip59zg", name: "folder-32", folders: [], @@ -1018,7 +1018,7 @@ export const TRANSFORMED_MULTIPLE_CHILD_COLLECTIONS_WITH_AUTH_HEADERS_MOCK: Hopp ], }, { - v: 7, + v: 8, id: "clx1j2ka9003q10f8cdbzpgpg", name: "folder-33", folders: [], @@ -1372,42 +1372,50 @@ export const WORKSPACE_ENVIRONMENT_MOCK: WorkspaceEnvironment = { variables: [ { key: "firstName", - value: "John", + initialValue: "John", + currentValue: "John", secret: false, }, { key: "lastName", - value: "Doe", + initialValue: "Doe", + currentValue: "Doe", secret: false, }, { key: "id", - value: "7", + initialValue: "7", + currentValue: "7", secret: false, }, { key: "fullName", - value: "<> <>", + initialValue: "<> <>", + currentValue: "<> <>", secret: false, }, { key: "recursiveVarX", - value: "<>", + initialValue: "<>", + currentValue: "<>", secret: false, }, { key: "recursiveVarY", - value: "<>", + initialValue: "<>", + currentValue: "<>", secret: false, }, { key: "salutation", - value: "Hello", + initialValue: "Hello", + currentValue: "Hello", secret: false, }, { key: "greetText", - value: "<> <>", + initialValue: "<> <>", + currentValue: "<> <>", secret: false, }, ], @@ -1420,42 +1428,50 @@ export const TRANSFORMED_ENVIRONMENT_MOCK: Environment = { variables: [ { key: "firstName", - value: "John", + initialValue: "John", + currentValue: "John", secret: false, }, { key: "lastName", - value: "Doe", + initialValue: "Doe", + currentValue: "Doe", secret: false, }, { key: "id", - value: "7", + initialValue: "7", + currentValue: "7", secret: false, }, { key: "fullName", - value: "<> <>", + initialValue: "<> <>", + currentValue: "<> <>", secret: false, }, { key: "recursiveVarX", - value: "<>", + initialValue: "<>", + currentValue: "<>", secret: false, }, { key: "recursiveVarY", - value: "<>", + initialValue: "<>", + currentValue: "<>", secret: false, }, { key: "salutation", - value: "Hello", + initialValue: "Hello", + currentValue: "Hello", secret: false, }, { key: "greetText", - value: "<> <>", + initialValue: "<> <>", + currentValue: "<> <>", secret: false, }, ], diff --git a/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts b/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts index 3950a9fe62c..627683eb199 100644 --- a/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts +++ b/packages/hoppscotch-cli/src/__tests__/unit/getters.spec.ts @@ -44,7 +44,12 @@ describe("getters", () => { describe("getEffectiveFinalMetaData", () => { const environmentVariables = [ - { key: "PARAM", value: "parsed_param", secret: false }, + { + key: "PARAM", + initialValue: "parsed_param", + currentValue: "parsed_param", + secret: false, + }, ]; test("Empty list of meta-data", () => { @@ -421,27 +426,32 @@ describe("getters", () => { const environmentVariables = [ { key: "SHARED_KEY_I", - value: "environment-variable-shared-value-I", + initialValue: "environment-variable-shared-value-I", + currentValue: "environment-variable-shared-value-I", secret: false, }, { key: "SHARED_KEY_II", - value: "environment-variable-shared-value-II", + initialValue: "environment-variable-shared-value-II", + currentValue: "environment-variable-shared-value-II", secret: false, }, { key: "ENV_VAR_III", - value: "environment-variable-value-III", + initialValue: "environment-variable-value-III", + currentValue: "environment-variable-value-III", secret: false, }, { key: "ENV_VAR_IV", - value: "environment-variable-value-IV", + initialValue: "environment-variable-value-IV", + currentValue: "environment-variable-value-IV", secret: false, }, { key: "ENV_VAR_V", - value: "environment-variable-value-V", + initialValue: "environment-variable-value-V", + currentValue: "environment-variable-value-V", secret: false, }, ]; @@ -450,32 +460,38 @@ describe("getters", () => { const expected = [ { key: "SHARED_KEY_I", - value: "request-variable-shared-value-I", + currentValue: "request-variable-shared-value-I", + initialValue: "request-variable-shared-value-I", secret: false, }, { key: "REQUEST_VAR_III", - value: "request-variable-value-III", + currentValue: "request-variable-value-III", + initialValue: "request-variable-value-III", secret: false, }, { key: "SHARED_KEY_II", - value: "environment-variable-shared-value-II", + currentValue: "environment-variable-shared-value-II", + initialValue: "environment-variable-shared-value-II", secret: false, }, { key: "ENV_VAR_III", - value: "environment-variable-value-III", + currentValue: "environment-variable-value-III", + initialValue: "environment-variable-value-III", secret: false, }, { key: "ENV_VAR_IV", - value: "environment-variable-value-IV", + currentValue: "environment-variable-value-IV", + initialValue: "environment-variable-value-IV", secret: false, }, { key: "ENV_VAR_V", - value: "environment-variable-value-V", + currentValue: "environment-variable-value-V", + initialValue: "environment-variable-value-V", secret: false, }, ]; diff --git a/packages/hoppscotch-cli/src/commands/test.ts b/packages/hoppscotch-cli/src/commands/test.ts index 0291fa31f36..52fec8bcb4f 100644 --- a/packages/hoppscotch-cli/src/commands/test.ts +++ b/packages/hoppscotch-cli/src/commands/test.ts @@ -20,8 +20,14 @@ import { parseCollectionData } from "../utils/mutators"; export const test = (pathOrId: string, options: TestCmdOptions) => async () => { try { - const { delay, env, iterationCount, iterationData, reporterJunit } = - options; + const { + delay, + env, + iterationCount, + iterationData, + reporterJunit, + legacySandbox, + } = options; if ( iterationCount !== undefined && @@ -75,7 +81,8 @@ export const test = (pathOrId: string, options: TestCmdOptions) => async () => { (key) => { key: key, - value: iterationDataItem[key], + initialValue: iterationDataItem[key], + currentValue: iterationDataItem[key], secret: false, } ) @@ -85,12 +92,15 @@ export const test = (pathOrId: string, options: TestCmdOptions) => async () => { .filter((item) => item.length > 0); } + const resolvedLegacySandbox = Boolean(legacySandbox); + const report = await collectionsRunner({ collections, envs, delay: resolvedDelay, iterationData: transformedIterationData, iterationCount, + legacySandbox: resolvedLegacySandbox, }); const hasSucceeded = collectionsRunnerResult(report, reporterJunit); diff --git a/packages/hoppscotch-cli/src/index.ts b/packages/hoppscotch-cli/src/index.ts index 7b92a47a011..94d70ce31e1 100644 --- a/packages/hoppscotch-cli/src/index.ts +++ b/packages/hoppscotch-cli/src/index.ts @@ -78,6 +78,7 @@ program "--iteration-data ", "path to a CSV file for data-driven testing" ) + .option("--legacy-sandbox", "Opt out from the experimental scripting sandbox") .allowExcessArguments(false) .allowUnknownOption(false) .description("running hoppscotch collection.json file") diff --git a/packages/hoppscotch-cli/src/interfaces/response.ts b/packages/hoppscotch-cli/src/interfaces/response.ts index f85ed820eb8..7c32e912840 100644 --- a/packages/hoppscotch-cli/src/interfaces/response.ts +++ b/packages/hoppscotch-cli/src/interfaces/response.ts @@ -43,6 +43,7 @@ export interface TestScriptParams { testScript: string; response: TestResponse; envs: HoppEnvs; + legacySandbox: boolean; } /** diff --git a/packages/hoppscotch-cli/src/options/test/delay.ts b/packages/hoppscotch-cli/src/options/test/delay.ts index 75be423ed88..e9cf6537cbc 100644 --- a/packages/hoppscotch-cli/src/options/test/delay.ts +++ b/packages/hoppscotch-cli/src/options/test/delay.ts @@ -1,14 +1,14 @@ import { error } from "../../types/errors"; export function parseDelayOption(delay: string): number { - const maybeInt = Number.parseInt(delay) + const maybeInt = Number.parseInt(delay); - if(!Number.isNaN(maybeInt)) { - return maybeInt + if (!Number.isNaN(maybeInt)) { + return maybeInt; } else { throw error({ code: "INVALID_ARGUMENT", data: "Expected '-d, --delay' value to be number", - }) + }); } } diff --git a/packages/hoppscotch-cli/src/options/test/env.ts b/packages/hoppscotch-cli/src/options/test/env.ts index cc6746525a3..8bd3fa7028a 100644 --- a/packages/hoppscotch-cli/src/options/test/env.ts +++ b/packages/hoppscotch-cli/src/options/test/env.ts @@ -60,11 +60,16 @@ export async function parseEnvsData(options: TestCmdEnvironmentOptions) { if (HoppEnvKeyPairResult.success) { for (const [key, value] of Object.entries(HoppEnvKeyPairResult.data)) { - envPairs.push({ key, value, secret: false }); + envPairs.push({ + key, + initialValue: value, + currentValue: value, + secret: false, + }); } } else if (HoppEnvExportObjectResult.type === "ok") { // Original environment variables from the supplied export file - const originalEnvVariables = (contents as NonSecretEnvironment).variables; + const originalEnvVariables = (contents as Environment).variables; // Above environment variables conforming to the latest schema // `value` fields if specified will be omitted for secret environment variables @@ -73,10 +78,10 @@ export async function parseEnvsData(options: TestCmdEnvironmentOptions) { // The values supplied for secret environment variables have to be considered in the CLI // For each secret environment variable, include the value in case supplied const resolvedEnvVariables = migratedEnvVariables.map((variable, idx) => { - if (variable.secret && originalEnvVariables[idx].value) { + if (variable.secret && originalEnvVariables[idx].initialValue) { return { ...variable, - value: originalEnvVariables[idx].value, + initialValue: originalEnvVariables[idx].initialValue, }; } diff --git a/packages/hoppscotch-cli/src/types/collections.ts b/packages/hoppscotch-cli/src/types/collections.ts index 08c6ccb7591..c3d67ebc8b8 100644 --- a/packages/hoppscotch-cli/src/types/collections.ts +++ b/packages/hoppscotch-cli/src/types/collections.ts @@ -7,6 +7,7 @@ export type CollectionRunnerParam = { delay?: number; iterationData?: IterationDataItem[][]; iterationCount?: number; + legacySandbox: boolean; }; export type HoppCollectionFileExt = "json"; diff --git a/packages/hoppscotch-cli/src/types/commands.ts b/packages/hoppscotch-cli/src/types/commands.ts index e353cb7d3ad..f53a12800a7 100644 --- a/packages/hoppscotch-cli/src/types/commands.ts +++ b/packages/hoppscotch-cli/src/types/commands.ts @@ -6,6 +6,7 @@ export type TestCmdOptions = { reporterJunit?: string; iterationCount?: number; iterationData?: string; + legacySandbox?: boolean; }; // Consumed in the collection `file_path_or_id` argument action handler diff --git a/packages/hoppscotch-cli/src/types/request.ts b/packages/hoppscotch-cli/src/types/request.ts index 0f6f780f507..519ae292a4b 100644 --- a/packages/hoppscotch-cli/src/types/request.ts +++ b/packages/hoppscotch-cli/src/types/request.ts @@ -37,4 +37,5 @@ export type ProcessRequestParams = { envs: HoppEnvs; path: string; delay: number; + legacySandbox: boolean; }; diff --git a/packages/hoppscotch-cli/src/utils/collections.ts b/packages/hoppscotch-cli/src/utils/collections.ts index 5c81d5d7dc1..27f9d350874 100644 --- a/packages/hoppscotch-cli/src/utils/collections.ts +++ b/packages/hoppscotch-cli/src/utils/collections.ts @@ -47,7 +47,14 @@ const { WARN, FAIL, INFO } = exceptionColors; export const collectionsRunner = async ( param: CollectionRunnerParam ): Promise => { - const { collections, envs, delay, iterationCount, iterationData } = param; + const { + collections, + envs, + delay, + iterationCount, + iterationData, + legacySandbox, + } = param; const resolvedDelay = delay ?? 0; @@ -87,7 +94,8 @@ export const collectionsRunner = async ( path, envs, resolvedDelay, - requestsReport + requestsReport, + legacySandbox ); } } @@ -100,7 +108,8 @@ const processCollection = async ( path: string, envs: HoppEnvs, delay: number, - requestsReport: RequestReport[] + requestsReport: RequestReport[], + legacySandbox?: boolean ) => { // Process each request in the collection for (const request of collection.requests) { @@ -111,6 +120,7 @@ const processCollection = async ( request: _request, envs, delay, + legacySandbox, }; // Request processing initiated message. @@ -156,7 +166,8 @@ const processCollection = async ( `${path}/${updatedFolder.name}`, envs, delay, - requestsReport + requestsReport, + legacySandbox ); } }; diff --git a/packages/hoppscotch-cli/src/utils/display.ts b/packages/hoppscotch-cli/src/utils/display.ts index 49620e4b11c..564512fdf70 100644 --- a/packages/hoppscotch-cli/src/utils/display.ts +++ b/packages/hoppscotch-cli/src/utils/display.ts @@ -120,7 +120,9 @@ export const printErrorsReport = ( errorsReport: HoppCLIError[] ) => { if (errorsReport.length > 0) { - const REPORTED_ERRORS_TITLE = FAIL(`\n${chalk.bold(path)} reported errors:`); + const REPORTED_ERRORS_TITLE = FAIL( + `\n${chalk.bold(path)} reported errors:` + ); group(REPORTED_ERRORS_TITLE); for (const errorReport of errorsReport) { diff --git a/packages/hoppscotch-cli/src/utils/functions/array.ts b/packages/hoppscotch-cli/src/utils/functions/array.ts index aa944906049..f616d599f7e 100644 --- a/packages/hoppscotch-cli/src/utils/functions/array.ts +++ b/packages/hoppscotch-cli/src/utils/functions/array.ts @@ -28,7 +28,7 @@ export const arrayFlatMap = export const tupleToRecord = < KeyType extends string | number | symbol, - ValueType + ValueType, >( tuples: [KeyType, ValueType][] ): Record => diff --git a/packages/hoppscotch-cli/src/utils/getters.ts b/packages/hoppscotch-cli/src/utils/getters.ts index 07fe43fc20f..c76ff0ea8d9 100644 --- a/packages/hoppscotch-cli/src/utils/getters.ts +++ b/packages/hoppscotch-cli/src/utils/getters.ts @@ -275,9 +275,15 @@ export const getResolvedVariables = ( requestVariables: HoppRESTRequestVariables, environmentVariables: EnvironmentVariable[] ): EnvironmentVariable[] => { + // Transforming request variables to the shape of environment variables const activeRequestVariables = requestVariables .filter(({ active, value }) => active && value) - .map(({ key, value }) => ({ key, value, secret: false })); + .map(({ key, value }) => ({ + key, + initialValue: value, + currentValue: value, + secret: false, + })); const requestVariableKeys = activeRequestVariables.map(({ key }) => key); @@ -286,5 +292,17 @@ export const getResolvedVariables = ( ({ key }) => !requestVariableKeys.includes(key) ); - return [...activeRequestVariables, ...filteredEnvironmentVariables]; + // Setting currentValue to initialValue for environment variables + // because the exported file might not have the currentValue field + const processedEnvironmentVariables = filteredEnvironmentVariables.map( + ({ key, initialValue, currentValue, secret }) => ({ + key, + initialValue, + currentValue: + currentValue && currentValue !== "" ? currentValue : initialValue, + secret, + }) + ); + + return [...activeRequestVariables, ...processedEnvironmentVariables]; }; diff --git a/packages/hoppscotch-cli/src/utils/pre-request.ts b/packages/hoppscotch-cli/src/utils/pre-request.ts index f8bca56e5cf..f6a3d350719 100644 --- a/packages/hoppscotch-cli/src/utils/pre-request.ts +++ b/packages/hoppscotch-cli/src/utils/pre-request.ts @@ -6,6 +6,7 @@ import { parseRawKeyValueEntriesE, parseTemplateString, parseTemplateStringE, + generateJWTToken, } from "@hoppscotch/data"; import { runPreRequestScript } from "@hoppscotch/js-sandbox/node"; import * as A from "fp-ts/Array"; @@ -44,15 +45,18 @@ import { calculateHawkHeader } from "@hoppscotch/data"; */ export const preRequestScriptRunner = ( request: HoppRESTRequest, - envs: HoppEnvs + envs: HoppEnvs, + legacySandbox: boolean ): TE.TaskEither< HoppCLIError, { effectiveRequest: EffectiveHoppRESTRequest } & { updatedEnvs: HoppEnvs } -> => - pipe( +> => { + const experimentalScriptingSandbox = !legacySandbox; + + return pipe( TE.of(request), TE.chain(({ preRequestScript }) => - runPreRequestScript(preRequestScript, envs) + runPreRequestScript(preRequestScript, envs, experimentalScriptingSandbox) ), TE.map( ({ selected, global }) => @@ -77,6 +81,7 @@ export const preRequestScriptRunner = ( }) ) ); +}; /** * Outputs an executable request format with environment variables applied @@ -202,8 +207,11 @@ export async function getEffectiveRESTRequest( const amzDate = currentDate.toISOString().replace(/[:-]|\.\d{3}/g, ""); const { method, endpoint } = request; + const body = getFinalBodyFromRequest(request, resolvedVariables); + const signer = new AwsV4Signer({ method, + body: E.isRight(body) ? body.right?.toString() : undefined, datetime: amzDate, signQuery: addTo === "QUERY_PARAMS", accessKeyId: parseTemplateString( @@ -327,6 +335,50 @@ export async function getEffectiveRESTRequest( value: hawkHeader, description: "", }); + } else if (request.auth.authType === "jwt") { + const { addTo } = request.auth; + + // Generate JWT token + const token = await generateJWTToken({ + algorithm: request.auth.algorithm || "HS256", + secret: parseTemplateString(request.auth.secret, resolvedVariables), + privateKey: parseTemplateString( + request.auth.privateKey, + resolvedVariables + ), + payload: parseTemplateString(request.auth.payload, resolvedVariables), + jwtHeaders: parseTemplateString( + request.auth.jwtHeaders, + resolvedVariables + ), + isSecretBase64Encoded: request.auth.isSecretBase64Encoded, + }); + + if (token) { + if (addTo === "HEADERS") { + const headerPrefix = + parseTemplateString(request.auth.headerPrefix, resolvedVariables) || + "Bearer "; + + effectiveFinalHeaders.push({ + active: true, + key: "Authorization", + value: `${headerPrefix}${token}`, + description: "", + }); + } else if (addTo === "QUERY_PARAMS") { + const paramName = + parseTemplateString(request.auth.paramName, resolvedVariables) || + "token"; + + effectiveFinalParams.push({ + active: true, + key: paramName, + value: token, + description: "", + }); + } + } } } diff --git a/packages/hoppscotch-cli/src/utils/request.ts b/packages/hoppscotch-cli/src/utils/request.ts index a80b8244926..0b78f77c7f1 100644 --- a/packages/hoppscotch-cli/src/utils/request.ts +++ b/packages/hoppscotch-cli/src/utils/request.ts @@ -42,8 +42,10 @@ const processVariables = (variable: Environment["variables"][number]) => { if (variable.secret) { return { ...variable, - value: - "value" in variable ? variable.value : process.env[variable.key] || "", + currentValue: + "currentValue" in variable && variable.currentValue !== "" + ? variable.currentValue + : process.env[variable.key] || "", }; } return variable; @@ -200,7 +202,7 @@ export const processRequest = params: ProcessRequestParams ): T.Task<{ envs: HoppEnvs; report: RequestReport }> => async () => { - const { envs, path, request, delay } = params; + const { envs, path, request, delay, legacySandbox } = params; // Initialising updatedEnvs with given parameter envs, will eventually get updated. const result = { @@ -233,7 +235,8 @@ export const processRequest = // Executing pre-request-script const preRequestRes = await preRequestScriptRunner( request, - processedEnvs + processedEnvs, + legacySandbox )(); if (E.isLeft(preRequestRes)) { printPreRequestRunner.fail(); @@ -285,7 +288,8 @@ export const processRequest = const testScriptParams = getTestScriptParams( _requestRunnerRes, request, - updatedEnvs + updatedEnvs, + legacySandbox ); // Executing test-runner. diff --git a/packages/hoppscotch-cli/src/utils/test.ts b/packages/hoppscotch-cli/src/utils/test.ts index e28609c2670..69815952167 100644 --- a/packages/hoppscotch-cli/src/utils/test.ts +++ b/packages/hoppscotch-cli/src/utils/test.ts @@ -37,9 +37,15 @@ export const testRunner = ( TE.bind("test_response", () => pipe( TE.of(testScriptData), - TE.chain(({ testScript, response, envs }) => - runTestScript(testScript, envs, response) - ) + TE.chain(({ testScript, response, envs, legacySandbox }) => { + const experimentalScriptingSandbox = !legacySandbox; + return runTestScript( + testScript, + envs, + response, + experimentalScriptingSandbox + ); + }) ) ), @@ -137,7 +143,8 @@ export const testDescriptorParser = ( export const getTestScriptParams = ( reqRunnerRes: RequestRunnerResponse, request: HoppRESTRequest, - envs: HoppEnvs + envs: HoppEnvs, + legacySandbox: boolean ) => { const testScriptParams: TestScriptParams = { testScript: request.testScript, @@ -146,7 +153,8 @@ export const getTestScriptParams = ( status: reqRunnerRes.status, headers: reqRunnerRes.headers, }, - envs: envs, + envs, + legacySandbox, }; return testScriptParams; }; diff --git a/packages/hoppscotch-common/assets/icons/apple.svg b/packages/hoppscotch-common/assets/icons/apple.svg new file mode 100644 index 00000000000..6e9303c3e77 --- /dev/null +++ b/packages/hoppscotch-common/assets/icons/apple.svg @@ -0,0 +1 @@ +Apple \ No newline at end of file diff --git a/packages/hoppscotch-common/assets/icons/linux.svg b/packages/hoppscotch-common/assets/icons/linux.svg new file mode 100644 index 00000000000..393e95e8df4 --- /dev/null +++ b/packages/hoppscotch-common/assets/icons/linux.svg @@ -0,0 +1 @@ +Linux \ No newline at end of file diff --git a/packages/hoppscotch-common/assets/icons/windows.svg b/packages/hoppscotch-common/assets/icons/windows.svg new file mode 100644 index 00000000000..92b6d8cbd1c --- /dev/null +++ b/packages/hoppscotch-common/assets/icons/windows.svg @@ -0,0 +1 @@ +Windows \ No newline at end of file diff --git a/packages/hoppscotch-common/locales/en.json b/packages/hoppscotch-common/locales/en.json index cf565fdc3ac..e5775d22cb1 100644 --- a/packages/hoppscotch-common/locales/en.json +++ b/packages/hoppscotch-common/locales/en.json @@ -70,7 +70,8 @@ "yes": "Yes", "verify": "Verify", "enable": "Enable", - "disable": "Disable" + "disable": "Disable", + "assign": "Assign" }, "activity_logs": { "ACTIVITY_LOG_DELETE": "Activity log has been deleted", @@ -118,6 +119,13 @@ "add_pfx_or_pkcs_file": "Add PFX/PKCS12 File" }, "app": { + "additional_links": { + "macOS": "macOS", + "windows": "Windows", + "linux": "Linux", + "web_app": "Web App", + "cli": "CLI" + }, "chat_with_us": "Chat with us", "contact_us": "Contact us", "cookies": "Cookies", @@ -244,6 +252,9 @@ "username": "Username", "advance_config": "Advanced Configuration", "advance_config_description": "Hoppscotch automatically assigns default values to certain fields if no explicit value is provided", + "algorithm": "Algorithm", + "payload": "Payload", + "secret": "Secret", "aws_signature": { "access_key": "Access Key", "secret_key": "Secret Key", @@ -260,6 +271,16 @@ "client_nonce": "Client Nonce", "opaque": "Opaque", "disable_retry": "Disable Retrying Request" + }, + "jwt": { + "params_name": "Params Name", + "header_prefix": "Header Prefix", + "placeholder_request_header": "Request header prefix", + "placeholder_request_param": "Request params name", + "secret_base64_encoded": "Secret Base64 Encoded", + "headers": "JWT Headers", + "private_key": "Private Key", + "placeholder_headers": "JWT Headers" } }, "collection": { @@ -333,11 +354,13 @@ } }, "count": { + "currentValue": "Current value {count}", + "description": "Description {count}", "header": "Header {count}", + "initialValue": "Initial value {count}", + "key": "Key {count}", "message": "Message {count}", "parameter": "Parameter {count}", - "key": "Key {count}", - "description": "Description {count}", "protocol": "Protocol {count}", "value": "Value {count}", "variable": "Variable {count}" @@ -947,6 +970,7 @@ "ai_request_naming_style_pascal_case": "Pascal Case ( PascalCase )", "ai_request_naming_style_custom": "Custom", "ai_request_naming_style_custom_placeholder": "Enter your custom naming style template...", + "experimental_scripting_sandbox": "Experimental scripting sandbox", "sync": "Synchronise", "sync_collections": "Collections", "sync_description": "These settings are synced to cloud.", @@ -1842,5 +1866,9 @@ "update_billing_cycle": "Are you sure you want to update the billing cycle to {newBillingCycle}?" }, "cancel_subscription": "Cancel subscription" + }, + "app_console": { + "entries": "Console entries", + "no_entries": "No entries" } } diff --git a/packages/hoppscotch-common/package.json b/packages/hoppscotch-common/package.json index 5c82ef03b29..d9d66f0fbd7 100644 --- a/packages/hoppscotch-common/package.json +++ b/packages/hoppscotch-common/package.json @@ -1,7 +1,7 @@ { "name": "@hoppscotch/common", "private": true, - "version": "2025.4.2", + "version": "2025.5.0", "scripts": { "dev": "pnpm exec npm-run-all -p -l dev:*", "test": "vitest --run", @@ -34,12 +34,12 @@ "@codemirror/search": "6.5.6", "@codemirror/state": "6.4.1", "@codemirror/view": "6.25.1", - "@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload", "@hoppscotch/codemirror-lang-graphql": "workspace:^", "@hoppscotch/data": "workspace:^", "@hoppscotch/httpsnippet": "3.0.7", "@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/kernel": "workspace:^", + "@hoppscotch/plugin-appload": "github:CuriousCorrelation/tauri-plugin-appload", "@hoppscotch/ui": "0.2.2", "@hoppscotch/vue-toasted": "0.1.0", "@lezer/highlight": "1.2.0", @@ -47,8 +47,8 @@ "@scure/base": "1.1.9", "@shopify/lang-jsonc": "1.0.0", "@tauri-apps/plugin-store": "2.2.0", - "@types/markdown-it": "14.1.2", "@types/hawk": "9.0.6", + "@types/markdown-it": "14.1.2", "@unhead/vue": "1.11.10", "@urql/core": "5.0.6", "@urql/devtools": "2.0.3", @@ -105,6 +105,7 @@ "verzod": "0.2.4", "vue": "3.5.12", "vue-i18n": "10.0.4", + "vue-json-pretty": "2.4.0", "vue-pdf-embed": "2.1.0", "vue-router": "4.4.5", "vue-tippy": "6.5.0", @@ -113,7 +114,7 @@ "workbox-window": "7.1.0", "xml-formatter": "3.6.3", "yargs-parser": "21.1.1", - "zod": "3.23.8" + "zod": "3.25.32" }, "devDependencies": { "@esbuild-plugins/node-globals-polyfill": "0.2.3", @@ -164,7 +165,7 @@ "rollup-plugin-polyfill-node": "0.13.0", "sass": "1.79.5", "tailwindcss": "3.4.14", - "typescript": "5.3.3", + "typescript": "5.8.3", "unplugin-fonts": "1.1.1", "unplugin-icons": "0.19.3", "unplugin-vue-components": "0.27.4", diff --git a/packages/hoppscotch-common/src/components.d.ts b/packages/hoppscotch-common/src/components.d.ts index 4ea41f4adc6..bbd18fe7bce 100644 --- a/packages/hoppscotch-common/src/components.d.ts +++ b/packages/hoppscotch-common/src/components.d.ts @@ -71,6 +71,9 @@ declare module 'vue' { CollectionsRequest: typeof import('./components/collections/Request.vue')['default'] CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default'] CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default'] + ConsoleItem: typeof import('./components/console/Item.vue')['default'] + ConsolePanel: typeof import('./components/console/Panel.vue')['default'] + ConsoleValue: typeof import('./components/console/Value.vue')['default'] CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default'] CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default'] Embeds: typeof import('./components/embeds/index.vue')['default'] @@ -121,6 +124,7 @@ declare module 'vue' { HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default'] HistoryPersonal: typeof import('./components/history/Personal.vue')['default'] HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default'] + HoppAccordion: typeof import('@hoppscotch/ui')['HoppAccordion'] HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] @@ -142,6 +146,7 @@ declare module 'vue' { HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner'] HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab'] HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs'] + HoppSmartTextarea: typeof import('@hoppscotch/ui')['HoppSmartTextarea'] HoppSmartToggle: typeof import('@hoppscotch/ui')['HoppSmartToggle'] HoppSmartTree: typeof import('@hoppscotch/ui')['HoppSmartTree'] HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow'] @@ -154,6 +159,7 @@ declare module 'vue' { HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default'] HttpAuthorizationDigest: typeof import('./components/http/authorization/Digest.vue')['default'] HttpAuthorizationHAWK: typeof import('./components/http/authorization/HAWK.vue')['default'] + HttpAuthorizationJWT: typeof import('./components/http/authorization/JWT.vue')['default'] HttpAuthorizationNTLM: typeof import('./components/http/authorization/NTLM.vue')['default'] HttpAuthorizationOAuth2: typeof import('./components/http/authorization/OAuth2.vue')['default'] HttpBody: typeof import('./components/http/Body.vue')['default'] diff --git a/packages/hoppscotch-common/src/components/app/Header.vue b/packages/hoppscotch-common/src/components/app/Header.vue index 4d528bf019b..6d72a9a32c6 100644 --- a/packages/hoppscotch-common/src/components/app/Header.vue +++ b/packages/hoppscotch-common/src/components/app/Header.vue @@ -59,14 +59,47 @@ class="col-span-2 flex items-center justify-between space-x-2" >
- + + + + + - + + + + + + + + (null) : ref(null) +const downloadableLinksRef = + kernelMode === "web" ? ref(null) : ref(null) const isUserAdmin = ref(false) @@ -351,14 +415,6 @@ const workspaceSelectorFlagEnabled = computed( () => !!platform.platformFeatureFlags.workspaceSwitcherLogin?.value ) -/** - * Once the PWA code is initialized, this holds a method - * that can be called to show the user the installation - * prompt. - */ - -const showInstallButton = computed(() => !!pwaDefferedPrompt.value) - /** * Show the dashboard link if the user is not on the default cloud instance and is an admin */ @@ -391,6 +447,24 @@ const offlineBanner: BannerContent = { dismissible: true, } +const additionalLinks = useService(AdditionalLinksService) + +platform.additionalLinks?.forEach((linkSet) => { + useService(linkSet) +}) + +const downloadableLinks = computed(() => { + if (kernelMode !== "web") return null + + const headerDownloadableLink = additionalLinks?.getLinkSet( + "HEADER_DOWNLOADABLE_LINKS" + ) + + if (!headerDownloadableLink) return null + + return headerDownloadableLink.getLinks().value +}) + // Show the offline banner if the app is offline const network = reactive(useNetwork()) const isOnline = computed(() => network.isOnline) @@ -571,7 +645,7 @@ defineActionHandler( ) defineActionHandler("modals.team.delete", ({ teamId }) => { - if (selectedTeam.value?.myRole !== TeamMemberRole.Owner) return noPermission() + if (selectedTeam.value?.myRole !== TeamAccessRole.Owner) return noPermission() teamID.value = teamId confirmRemove.value = true }) diff --git a/packages/hoppscotch-common/src/components/console/Item.vue b/packages/hoppscotch-common/src/components/console/Item.vue new file mode 100644 index 00000000000..68e803d767c --- /dev/null +++ b/packages/hoppscotch-common/src/components/console/Item.vue @@ -0,0 +1,61 @@ + + + diff --git a/packages/hoppscotch-common/src/components/console/Panel.vue b/packages/hoppscotch-common/src/components/console/Panel.vue new file mode 100644 index 00000000000..363177a91a7 --- /dev/null +++ b/packages/hoppscotch-common/src/components/console/Panel.vue @@ -0,0 +1,55 @@ + + + diff --git a/packages/hoppscotch-common/src/components/console/Value.vue b/packages/hoppscotch-common/src/components/console/Value.vue new file mode 100644 index 00000000000..2a25758947b --- /dev/null +++ b/packages/hoppscotch-common/src/components/console/Value.vue @@ -0,0 +1,101 @@ + + + diff --git a/packages/hoppscotch-common/src/components/environments/Add.vue b/packages/hoppscotch-common/src/components/environments/Add.vue index 5eed9be6362..71dedb1da11 100644 --- a/packages/hoppscotch-common/src/components/environments/Add.vue +++ b/packages/hoppscotch-common/src/components/environments/Add.vue @@ -70,7 +70,6 @@ diff --git a/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/UrlImport.vue b/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/UrlImport.vue index 076b08cd305..920a9cdddd6 100644 --- a/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/UrlImport.vue +++ b/packages/hoppscotch-common/src/components/importExport/ImportExportSteps/UrlImport.vue @@ -23,9 +23,10 @@

@@ -49,7 +50,7 @@ import { KernelInterceptorService } from "~/services/kernel-interceptor.service" import { useService } from "dioc/vue" import * as E from "fp-ts/Either" import * as O from "fp-ts/Option" -import { parseBodyAsJSON } from "~/helpers/functional/json" +import { parseBodyAsJSONOrYAML } from "~/helpers/functional/json" const interceptorService = useService(KernelInterceptorService) @@ -98,7 +99,7 @@ const urlFetchLogic = return E.left("REQUEST_FAILED") } - const responsePayload = parseBodyAsJSON(res.right.body) + const responsePayload = parseBodyAsJSONOrYAML(res.right.body) if (O.isSome(responsePayload)) { // stringify the response payload diff --git a/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue b/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue index cbd7f1cc336..4395530da29 100644 --- a/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue +++ b/packages/hoppscotch-common/src/components/lenses/ResponseBodyRenderer.vue @@ -49,19 +49,29 @@ :is-editable="false" /> + + +