Compare commits
1 Commits
refactor/t
...
fix/db-url
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
74933031c6 |
@@ -31,7 +31,6 @@ MICROSOFT_CLIENT_ID="************************************************"
|
|||||||
MICROSOFT_CLIENT_SECRET="************************************************"
|
MICROSOFT_CLIENT_SECRET="************************************************"
|
||||||
MICROSOFT_CALLBACK_URL="http://localhost:3170/v1/auth/microsoft/callback"
|
MICROSOFT_CALLBACK_URL="http://localhost:3170/v1/auth/microsoft/callback"
|
||||||
MICROSOFT_SCOPE="user.read"
|
MICROSOFT_SCOPE="user.read"
|
||||||
MICROSOFT_TENANT="common"
|
|
||||||
|
|
||||||
# Mailer config
|
# Mailer config
|
||||||
MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com"
|
MAILER_SMTP_URL="smtps://user@domain.com:pass@smtp.domain.com"
|
||||||
|
|||||||
42
.github/workflows/ui.yml
vendored
42
.github/workflows/ui.yml
vendored
@@ -1,42 +0,0 @@
|
|||||||
name: Deploy to Netlify (ui)
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
# run this workflow only if an update is made to the ui package
|
|
||||||
paths:
|
|
||||||
- "packages/hoppscotch-ui/**"
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
name: Deploy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Checkout
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Setup environment
|
|
||||||
run: mv .env.example .env
|
|
||||||
|
|
||||||
- name: Setup pnpm
|
|
||||||
uses: pnpm/action-setup@v2.2.4
|
|
||||||
with:
|
|
||||||
version: 8
|
|
||||||
run_install: true
|
|
||||||
|
|
||||||
- name: Setup node
|
|
||||||
uses: actions/setup-node@v3
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node }}
|
|
||||||
cache: pnpm
|
|
||||||
|
|
||||||
- name: Build site
|
|
||||||
run: pnpm run generate-ui
|
|
||||||
|
|
||||||
# Deploy the ui site with netlify-cli
|
|
||||||
- name: Deploy to Netlify (ui)
|
|
||||||
run: npx netlify-cli deploy --dir=packages/hoppscotch-ui/.histoire/dist --prod
|
|
||||||
env:
|
|
||||||
NETLIFY_SITE_ID: ${{ secrets.NETLIFY_UI_SITE_ID }}
|
|
||||||
NETLIFY_AUTH_TOKEN: ${{ secrets.NETLIFY_AUTH_TOKEN }}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.4",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|||||||
@@ -181,7 +181,7 @@ export class AdminService {
|
|||||||
* @returns an array team invitations
|
* @returns an array team invitations
|
||||||
*/
|
*/
|
||||||
async pendingInvitationCountInTeam(teamID: string) {
|
async pendingInvitationCountInTeam(teamID: string) {
|
||||||
const invitations = await this.teamInvitationService.getTeamInvitations(
|
const invitations = await this.teamInvitationService.getAllTeamInvitations(
|
||||||
teamID,
|
teamID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -236,11 +236,11 @@ export class AdminService {
|
|||||||
const user = await this.userService.findUserByEmail(userEmail);
|
const user = await this.userService.findUserByEmail(userEmail);
|
||||||
if (O.isNone(user)) return E.left(USER_NOT_FOUND);
|
if (O.isNone(user)) return E.left(USER_NOT_FOUND);
|
||||||
|
|
||||||
const teamMember = await this.teamService.getTeamMemberTE(
|
const isUserAlreadyMember = await this.teamService.getTeamMemberTE(
|
||||||
teamID,
|
teamID,
|
||||||
user.value.uid,
|
user.value.uid,
|
||||||
)();
|
)();
|
||||||
if (E.isLeft(teamMember)) {
|
if (E.left(isUserAlreadyMember)) {
|
||||||
const addedUser = await this.teamService.addMemberToTeamWithEmail(
|
const addedUser = await this.teamService.addMemberToTeamWithEmail(
|
||||||
teamID,
|
teamID,
|
||||||
userEmail,
|
userEmail,
|
||||||
@@ -248,18 +248,6 @@ export class AdminService {
|
|||||||
);
|
);
|
||||||
if (E.isLeft(addedUser)) return E.left(addedUser.left);
|
if (E.isLeft(addedUser)) return E.left(addedUser.left);
|
||||||
|
|
||||||
const userInvitation =
|
|
||||||
await this.teamInvitationService.getTeamInviteByEmailAndTeamID(
|
|
||||||
userEmail,
|
|
||||||
teamID,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (E.isRight(userInvitation)) {
|
|
||||||
await this.teamInvitationService.revokeInvitation(
|
|
||||||
userInvitation.right.id,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return E.right(addedUser.right);
|
return E.right(addedUser.right);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -228,7 +228,7 @@ export class AuthService {
|
|||||||
url = process.env.VITE_BASE_URL;
|
url = process.env.VITE_BASE_URL;
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.mailerService.sendEmail(email, {
|
await this.mailerService.sendAuthEmail(email, {
|
||||||
template: 'code-your-own',
|
template: 'code-your-own',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ export class MicrosoftStrategy extends PassportStrategy(Strategy) {
|
|||||||
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
clientSecret: process.env.MICROSOFT_CLIENT_SECRET,
|
||||||
callbackURL: process.env.MICROSOFT_CALLBACK_URL,
|
callbackURL: process.env.MICROSOFT_CALLBACK_URL,
|
||||||
scope: [process.env.MICROSOFT_SCOPE],
|
scope: [process.env.MICROSOFT_SCOPE],
|
||||||
tenant: process.env.MICROSOFT_TENANT,
|
passReqToCallback: true,
|
||||||
store: true,
|
store: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const AUTH_FAIL = 'auth/fail';
|
|||||||
export const JSON_INVALID = 'json_invalid';
|
export const JSON_INVALID = 'json_invalid';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tried to delete a user data document from fb firestore but failed.
|
* Tried to delete an user data document from fb firestore but failed.
|
||||||
* (FirebaseService)
|
* (FirebaseService)
|
||||||
*/
|
*/
|
||||||
export const USER_FB_DOCUMENT_DELETION_FAILED =
|
export const USER_FB_DOCUMENT_DELETION_FAILED =
|
||||||
@@ -231,7 +231,7 @@ export const TEAM_COLL_INVALID_JSON = 'team_coll/invalid_json';
|
|||||||
export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const;
|
export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tried to perform an action on a request that doesn't accept their member role level
|
* Tried to perform action on a request that doesn't accept their member role level
|
||||||
* (GqlRequestTeamMemberGuard)
|
* (GqlRequestTeamMemberGuard)
|
||||||
*/
|
*/
|
||||||
export const TEAM_REQ_NOT_REQUIRED_ROLE = 'team_req/not_required_role';
|
export const TEAM_REQ_NOT_REQUIRED_ROLE = 'team_req/not_required_role';
|
||||||
@@ -262,7 +262,7 @@ export const TEAM_REQ_REORDERING_FAILED = 'team_req/reordering_failed' as const;
|
|||||||
export const SENDER_EMAIL_INVALID = 'mailer/sender_email_invalid' as const;
|
export const SENDER_EMAIL_INVALID = 'mailer/sender_email_invalid' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tried to perform an action on a request when the user is not even a member of the team
|
* Tried to perform action on a request when the user is not even member of the team
|
||||||
* (GqlRequestTeamMemberGuard, GqlCollectionTeamMemberGuard)
|
* (GqlRequestTeamMemberGuard, GqlCollectionTeamMemberGuard)
|
||||||
*/
|
*/
|
||||||
export const TEAM_REQ_NOT_MEMBER = 'team_req/not_member';
|
export const TEAM_REQ_NOT_MEMBER = 'team_req/not_member';
|
||||||
@@ -307,18 +307,11 @@ export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
|
|||||||
export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
|
export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invalid or non-existent TEAM ENVIRONMENT ID
|
* Invalid or non-existent TEAM ENVIRONMMENT ID
|
||||||
* (TeamEnvironmentsService)
|
* (TeamEnvironmentsService)
|
||||||
*/
|
*/
|
||||||
export const TEAM_ENVIRONMENT_NOT_FOUND = 'team_environment/not_found' as const;
|
export const TEAM_ENVIRONMENT_NOT_FOUND = 'team_environment/not_found' as const;
|
||||||
|
|
||||||
/**
|
|
||||||
* Invalid TEAM ENVIRONMENT name
|
|
||||||
* (TeamEnvironmentsService)
|
|
||||||
*/
|
|
||||||
export const TEAM_ENVIRONMENT_SHORT_NAME =
|
|
||||||
'team_environment/short_name' as const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The user is not a member of the team of the given environment
|
* The user is not a member of the team of the given environment
|
||||||
* (GqlTeamEnvTeamGuard)
|
* (GqlTeamEnvTeamGuard)
|
||||||
@@ -347,7 +340,7 @@ export const USER_SETTINGS_NULL_SETTINGS =
|
|||||||
'user_settings/null_settings' as const;
|
'user_settings/null_settings' as const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Global environment doesn't exist for the user
|
* Global environment doesnt exists for the user
|
||||||
* (UserEnvironmentsService)
|
* (UserEnvironmentsService)
|
||||||
*/
|
*/
|
||||||
export const USER_ENVIRONMENT_GLOBAL_ENV_DOES_NOT_EXISTS =
|
export const USER_ENVIRONMENT_GLOBAL_ENV_DOES_NOT_EXISTS =
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import {
|
|||||||
UserMagicLinkMailDescription,
|
UserMagicLinkMailDescription,
|
||||||
} from './MailDescriptions';
|
} from './MailDescriptions';
|
||||||
import { throwErr } from 'src/utils';
|
import { throwErr } from 'src/utils';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
import { EMAIL_FAILED } from 'src/errors';
|
import { EMAIL_FAILED } from 'src/errors';
|
||||||
import { MailerService as NestMailerService } from '@nestjs-modules/mailer';
|
import { MailerService as NestMailerService } from '@nestjs-modules/mailer';
|
||||||
|
|
||||||
@@ -34,14 +35,33 @@ export class MailerService {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Sends an email to the given email address given a mail description
|
* Sends an email to the given email address given a mail description
|
||||||
* @param to Receiver's email id
|
* @param to The email address to be sent to (NOTE: this is not validated)
|
||||||
* @param mailDesc Definition of what email to be sent
|
* @param mailDesc Definition of what email to be sent
|
||||||
* @returns Response if email was send successfully or not
|
|
||||||
*/
|
*/
|
||||||
async sendEmail(
|
sendMail(
|
||||||
to: string,
|
to: string,
|
||||||
mailDesc: MailDescription | UserMagicLinkMailDescription,
|
mailDesc: MailDescription | UserMagicLinkMailDescription,
|
||||||
) {
|
) {
|
||||||
|
return TE.tryCatch(
|
||||||
|
async () => {
|
||||||
|
await this.nestMailerService.sendMail({
|
||||||
|
to,
|
||||||
|
template: mailDesc.template,
|
||||||
|
subject: this.resolveSubjectForMailDesc(mailDesc),
|
||||||
|
context: mailDesc.variables,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
() => EMAIL_FAILED,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param to Receiver's email id
|
||||||
|
* @param mailDesc Details of email to be sent for Magic-Link auth
|
||||||
|
* @returns Response if email was send successfully or not
|
||||||
|
*/
|
||||||
|
async sendAuthEmail(to: string, mailDesc: UserMagicLinkMailDescription) {
|
||||||
try {
|
try {
|
||||||
await this.nestMailerService.sendMail({
|
await this.nestMailerService.sendMail({
|
||||||
to,
|
to,
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { Reflector } from '@nestjs/core';
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import * as O from 'fp-ts/Option';
|
||||||
|
import * as S from 'fp-ts/string';
|
||||||
|
import { pipe } from 'fp-ts/function';
|
||||||
|
import {
|
||||||
|
getAnnotatedRequiredRoles,
|
||||||
|
getGqlArg,
|
||||||
|
getUserFromGQLContext,
|
||||||
|
throwErr,
|
||||||
|
} from 'src/utils';
|
||||||
import { TeamEnvironmentsService } from './team-environments.service';
|
import { TeamEnvironmentsService } from './team-environments.service';
|
||||||
import {
|
import {
|
||||||
BUG_AUTH_NO_USER_CTX,
|
BUG_AUTH_NO_USER_CTX,
|
||||||
@@ -9,10 +19,6 @@ import {
|
|||||||
TEAM_ENVIRONMENT_NOT_FOUND,
|
TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
} from 'src/errors';
|
} from 'src/errors';
|
||||||
import { TeamService } from 'src/team/team.service';
|
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 { throwErr } from 'src/utils';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A guard which checks whether the caller of a GQL Operation
|
* A guard which checks whether the caller of a GQL Operation
|
||||||
@@ -27,31 +33,50 @@ export class GqlTeamEnvTeamGuard implements CanActivate {
|
|||||||
private readonly teamService: TeamService,
|
private readonly teamService: TeamService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
const requireRoles = this.reflector.get<TeamMemberRole[]>(
|
return pipe(
|
||||||
'requiresTeamRole',
|
TE.Do,
|
||||||
context.getHandler(),
|
|
||||||
);
|
|
||||||
if (!requireRoles) throw new Error(BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES);
|
|
||||||
|
|
||||||
const gqlExecCtx = GqlExecutionContext.create(context);
|
TE.bindW('requiredRoles', () =>
|
||||||
|
pipe(
|
||||||
|
getAnnotatedRequiredRoles(this.reflector, context),
|
||||||
|
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const { user } = gqlExecCtx.getContext().req;
|
TE.bindW('user', () =>
|
||||||
if (user == undefined) throw new Error(BUG_AUTH_NO_USER_CTX);
|
pipe(
|
||||||
|
getUserFromGQLContext(context),
|
||||||
|
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const { id } = gqlExecCtx.getArgs<{ id: string }>();
|
TE.bindW('envID', () =>
|
||||||
if (!id) throwErr(BUG_TEAM_ENV_GUARD_NO_ENV_ID);
|
pipe(
|
||||||
|
getGqlArg('id', context),
|
||||||
|
O.fromPredicate(S.isString),
|
||||||
|
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_ENV_ID),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const teamEnvironment =
|
TE.bindW('membership', ({ envID, user }) =>
|
||||||
await this.teamEnvironmentService.getTeamEnvironment(id);
|
pipe(
|
||||||
if (E.isLeft(teamEnvironment)) throwErr(TEAM_ENVIRONMENT_NOT_FOUND);
|
this.teamEnvironmentService.getTeamEnvironment(envID),
|
||||||
|
TE.fromTaskOption(() => TEAM_ENVIRONMENT_NOT_FOUND),
|
||||||
|
TE.chainW((env) =>
|
||||||
|
pipe(
|
||||||
|
this.teamService.getTeamMemberTE(env.teamID, user.uid),
|
||||||
|
TE.mapLeft(() => TEAM_ENVIRONMENT_NOT_TEAM_MEMBER),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const member = await this.teamService.getTeamMember(
|
TE.map(({ membership, requiredRoles }) =>
|
||||||
teamEnvironment.right.teamID,
|
requiredRoles.includes(membership.role),
|
||||||
user.uid,
|
),
|
||||||
);
|
|
||||||
if (!member) throwErr(TEAM_ENVIRONMENT_NOT_TEAM_MEMBER);
|
|
||||||
|
|
||||||
return requireRoles.includes(member.role);
|
TE.getOrElse(throwErr),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
import { ArgsType, Field, ID } from '@nestjs/graphql';
|
|
||||||
|
|
||||||
@ArgsType()
|
|
||||||
export class CreateTeamEnvironmentArgs {
|
|
||||||
@Field({
|
|
||||||
name: 'name',
|
|
||||||
description: 'Name of the Team Environment',
|
|
||||||
})
|
|
||||||
name: string;
|
|
||||||
|
|
||||||
@Field(() => ID, {
|
|
||||||
name: 'teamID',
|
|
||||||
description: 'ID of the Team',
|
|
||||||
})
|
|
||||||
teamID: string;
|
|
||||||
|
|
||||||
@Field({
|
|
||||||
name: 'variables',
|
|
||||||
description: 'JSON string of the variables object',
|
|
||||||
})
|
|
||||||
variables: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
@ArgsType()
|
|
||||||
export class UpdateTeamEnvironmentArgs {
|
|
||||||
@Field(() => ID, {
|
|
||||||
name: 'id',
|
|
||||||
description: 'ID of the Team Environment',
|
|
||||||
})
|
|
||||||
id: string;
|
|
||||||
@Field({
|
|
||||||
name: 'name',
|
|
||||||
description: 'Name of the Team Environment',
|
|
||||||
})
|
|
||||||
name: string;
|
|
||||||
@Field({
|
|
||||||
name: 'variables',
|
|
||||||
description: 'JSON string of the variables object',
|
|
||||||
})
|
|
||||||
variables: string;
|
|
||||||
}
|
|
||||||
@@ -13,11 +13,6 @@ import { throwErr } from 'src/utils';
|
|||||||
import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard';
|
import { GqlTeamEnvTeamGuard } from './gql-team-env-team.guard';
|
||||||
import { TeamEnvironment } from './team-environments.model';
|
import { TeamEnvironment } from './team-environments.model';
|
||||||
import { TeamEnvironmentsService } from './team-environments.service';
|
import { TeamEnvironmentsService } from './team-environments.service';
|
||||||
import * as E from 'fp-ts/Either';
|
|
||||||
import {
|
|
||||||
CreateTeamEnvironmentArgs,
|
|
||||||
UpdateTeamEnvironmentArgs,
|
|
||||||
} from './input-type.args';
|
|
||||||
|
|
||||||
@UseGuards(GqlThrottlerGuard)
|
@UseGuards(GqlThrottlerGuard)
|
||||||
@Resolver(() => 'TeamEnvironment')
|
@Resolver(() => 'TeamEnvironment')
|
||||||
@@ -34,18 +29,29 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
async createTeamEnvironment(
|
createTeamEnvironment(
|
||||||
@Args() args: CreateTeamEnvironmentArgs,
|
@Args({
|
||||||
|
name: 'name',
|
||||||
|
description: 'Name of the Team Environment',
|
||||||
|
})
|
||||||
|
name: string,
|
||||||
|
@Args({
|
||||||
|
name: 'teamID',
|
||||||
|
description: 'ID of the Team',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'variables',
|
||||||
|
description: 'JSON string of the variables object',
|
||||||
|
})
|
||||||
|
variables: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
const teamEnvironment =
|
return this.teamEnvironmentsService.createTeamEnvironment(
|
||||||
await this.teamEnvironmentsService.createTeamEnvironment(
|
name,
|
||||||
args.name,
|
teamID,
|
||||||
args.teamID,
|
variables,
|
||||||
args.variables,
|
)();
|
||||||
);
|
|
||||||
|
|
||||||
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
|
|
||||||
return teamEnvironment.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean, {
|
@Mutation(() => Boolean, {
|
||||||
@@ -53,7 +59,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
async deleteTeamEnvironment(
|
deleteTeamEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -61,12 +67,10 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
const isDeleted = await this.teamEnvironmentsService.deleteTeamEnvironment(
|
return pipe(
|
||||||
id,
|
this.teamEnvironmentsService.deleteTeamEnvironment(id),
|
||||||
);
|
TE.getOrElse(throwErr),
|
||||||
|
)();
|
||||||
if (E.isLeft(isDeleted)) throwErr(isDeleted.left);
|
|
||||||
return isDeleted.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -75,19 +79,28 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
async updateTeamEnvironment(
|
updateTeamEnvironment(
|
||||||
@Args()
|
@Args({
|
||||||
args: UpdateTeamEnvironmentArgs,
|
name: 'id',
|
||||||
|
description: 'ID of the Team Environment',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
id: string,
|
||||||
|
@Args({
|
||||||
|
name: 'name',
|
||||||
|
description: 'Name of the Team Environment',
|
||||||
|
})
|
||||||
|
name: string,
|
||||||
|
@Args({
|
||||||
|
name: 'variables',
|
||||||
|
description: 'JSON string of the variables object',
|
||||||
|
})
|
||||||
|
variables: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
const updatedTeamEnvironment =
|
return pipe(
|
||||||
await this.teamEnvironmentsService.updateTeamEnvironment(
|
this.teamEnvironmentsService.updateTeamEnvironment(id, name, variables),
|
||||||
args.id,
|
TE.getOrElse(throwErr),
|
||||||
args.name,
|
)();
|
||||||
args.variables,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (E.isLeft(updatedTeamEnvironment)) throwErr(updatedTeamEnvironment.left);
|
|
||||||
return updatedTeamEnvironment.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -95,7 +108,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
async deleteAllVariablesFromTeamEnvironment(
|
deleteAllVariablesFromTeamEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -103,13 +116,10 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
const teamEnvironment =
|
return pipe(
|
||||||
await this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(id),
|
||||||
id,
|
TE.getOrElse(throwErr),
|
||||||
);
|
)();
|
||||||
|
|
||||||
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
|
|
||||||
return teamEnvironment.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -117,7 +127,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
async createDuplicateEnvironment(
|
createDuplicateEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -125,12 +135,10 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
const res = await this.teamEnvironmentsService.createDuplicateEnvironment(
|
return pipe(
|
||||||
id,
|
this.teamEnvironmentsService.createDuplicateEnvironment(id),
|
||||||
);
|
TE.getOrElse(throwErr),
|
||||||
|
)();
|
||||||
if (E.isLeft(res)) throwErr(res.left);
|
|
||||||
return res.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subscriptions */
|
/* Subscriptions */
|
||||||
|
|||||||
@@ -2,11 +2,7 @@ import { mockDeep, mockReset } from 'jest-mock-extended';
|
|||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { TeamEnvironment } from './team-environments.model';
|
import { TeamEnvironment } from './team-environments.model';
|
||||||
import { TeamEnvironmentsService } from './team-environments.service';
|
import { TeamEnvironmentsService } from './team-environments.service';
|
||||||
import {
|
import { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
|
||||||
JSON_INVALID,
|
|
||||||
TEAM_ENVIRONMENT_NOT_FOUND,
|
|
||||||
TEAM_ENVIRONMENT_SHORT_NAME,
|
|
||||||
} from 'src/errors';
|
|
||||||
|
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
|
|
||||||
@@ -35,81 +31,125 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe('TeamEnvironmentsService', () => {
|
describe('TeamEnvironmentsService', () => {
|
||||||
describe('getTeamEnvironment', () => {
|
describe('getTeamEnvironment', () => {
|
||||||
test('should successfully return a TeamEnvironment with valid ID', async () => {
|
test('queries the db with the id', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
||||||
teamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.getTeamEnvironment(
|
await teamEnvironmentsService.getTeamEnvironment('123')();
|
||||||
teamEnvironment.id,
|
|
||||||
|
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
where: {
|
||||||
|
id: '123',
|
||||||
|
},
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
expect(result).toEqualRight(teamEnvironment);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMENT_NOT_FOUND with invalid ID', async () => {
|
test('requests prisma to reject the query promise if not found', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValueOnce(
|
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
||||||
'RejectOnNotFound',
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.getTeamEnvironment(
|
await teamEnvironmentsService.getTeamEnvironment('123')();
|
||||||
teamEnvironment.id,
|
|
||||||
|
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
|
||||||
|
expect.objectContaining({
|
||||||
|
rejectOnNotFound: true,
|
||||||
|
}),
|
||||||
);
|
);
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
});
|
||||||
|
|
||||||
|
test('should return a Some of the correct environment if exists', async () => {
|
||||||
|
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
||||||
|
|
||||||
|
const result = await teamEnvironmentsService.getTeamEnvironment('123')();
|
||||||
|
|
||||||
|
expect(result).toEqualSome(teamEnvironment);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should return a None if the environment does not exist', async () => {
|
||||||
|
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
||||||
|
|
||||||
|
const result = await teamEnvironmentsService.getTeamEnvironment('123')();
|
||||||
|
|
||||||
|
expect(result).toBeNone();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('createTeamEnvironment', () => {
|
describe('createTeamEnvironment', () => {
|
||||||
test('should successfully create and return a new team environment given valid inputs', async () => {
|
test('should create and return a new team environment given a valid name,variable and team ID', async () => {
|
||||||
mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment);
|
mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createTeamEnvironment(
|
const result = await teamEnvironmentsService.createTeamEnvironment(
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
teamEnvironment.teamID,
|
teamEnvironment.teamID,
|
||||||
JSON.stringify(teamEnvironment.variables),
|
JSON.stringify(teamEnvironment.variables),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight({
|
expect(result).toEqual(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
id: teamEnvironment.id,
|
||||||
|
name: teamEnvironment.name,
|
||||||
|
teamID: teamEnvironment.teamID,
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
variables: JSON.stringify(teamEnvironment.variables),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name is invalid', async () => {
|
test('should reject if given team ID is invalid', async () => {
|
||||||
const result = await teamEnvironmentsService.createTeamEnvironment(
|
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
|
||||||
'12',
|
|
||||||
teamEnvironment.teamID,
|
|
||||||
JSON.stringify(teamEnvironment.variables),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_SHORT_NAME);
|
await expect(
|
||||||
|
teamEnvironmentsService.createTeamEnvironment(
|
||||||
|
teamEnvironment.name,
|
||||||
|
'invalidteamid',
|
||||||
|
JSON.stringify(teamEnvironment.variables),
|
||||||
|
),
|
||||||
|
).rejects.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should reject if provided team environment name is not a string', async () => {
|
||||||
|
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
teamEnvironmentsService.createTeamEnvironment(
|
||||||
|
null as any,
|
||||||
|
teamEnvironment.teamID,
|
||||||
|
JSON.stringify(teamEnvironment.variables),
|
||||||
|
),
|
||||||
|
).rejects.toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should reject if provided variable is not a string', async () => {
|
||||||
|
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
teamEnvironmentsService.createTeamEnvironment(
|
||||||
|
teamEnvironment.name,
|
||||||
|
teamEnvironment.teamID,
|
||||||
|
null as any,
|
||||||
|
),
|
||||||
|
).rejects.toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is created successfully', async () => {
|
test('should send pubsub message to "team_environment/<teamID>/created" if team environment is created successfully', async () => {
|
||||||
mockPrisma.teamEnvironment.create.mockResolvedValue(teamEnvironment);
|
mockPrisma.teamEnvironment.create.mockResolvedValueOnce(teamEnvironment);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createTeamEnvironment(
|
const result = await teamEnvironmentsService.createTeamEnvironment(
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
teamEnvironment.teamID,
|
teamEnvironment.teamID,
|
||||||
JSON.stringify(teamEnvironment.variables),
|
JSON.stringify(teamEnvironment.variables),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`team_environment/${teamEnvironment.teamID}/created`,
|
`team_environment/${teamEnvironment.teamID}/created`,
|
||||||
{
|
result,
|
||||||
...teamEnvironment,
|
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteTeamEnvironment', () => {
|
describe('deleteTeamEnvironment', () => {
|
||||||
test('should successfully delete a TeamEnvironment with a valid ID', async () => {
|
test('should resolve to true given a valid team environment ID', async () => {
|
||||||
mockPrisma.teamEnvironment.delete.mockResolvedValueOnce(teamEnvironment);
|
mockPrisma.teamEnvironment.delete.mockResolvedValueOnce(teamEnvironment);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(true);
|
expect(result).toEqualRight(true);
|
||||||
});
|
});
|
||||||
@@ -119,7 +159,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
||||||
'invalidid',
|
'invalidid',
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
});
|
});
|
||||||
@@ -129,7 +169,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
const result = await teamEnvironmentsService.deleteTeamEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`team_environment/${teamEnvironment.teamID}/deleted`,
|
`team_environment/${teamEnvironment.teamID}/deleted`,
|
||||||
@@ -142,7 +182,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('updateVariablesInTeamEnvironment', () => {
|
describe('updateVariablesInTeamEnvironment', () => {
|
||||||
test('should successfully add new variable to a team environment', async () => {
|
test('should add new variable to a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: 'value' }],
|
variables: [{ key: 'value' }],
|
||||||
@@ -152,7 +192,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify([{ key: 'value' }]),
|
JSON.stringify([{ key: 'value' }]),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
@@ -160,7 +200,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should successfully add new variable to already existing list of variables in a team environment', async () => {
|
test('should add new variable to already existing list of variables in a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: 'value' }, { key_2: 'value_2' }],
|
variables: [{ key: 'value' }, { key_2: 'value_2' }],
|
||||||
@@ -170,7 +210,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify([{ key: 'value' }, { key_2: 'value_2' }]),
|
JSON.stringify([{ key: 'value' }, { key_2: 'value_2' }]),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
@@ -178,7 +218,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should successfully edit existing variables in a team environment', async () => {
|
test('should edit existing variables in a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: '1234' }],
|
variables: [{ key: '1234' }],
|
||||||
@@ -188,7 +228,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify([{ key: '1234' }]),
|
JSON.stringify([{ key: '1234' }]),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
@@ -196,7 +236,22 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should successfully edit name of an existing team environment', async () => {
|
test('should delete existing variable in a team environment', async () => {
|
||||||
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment);
|
||||||
|
|
||||||
|
const result = await teamEnvironmentsService.updateTeamEnvironment(
|
||||||
|
teamEnvironment.id,
|
||||||
|
teamEnvironment.name,
|
||||||
|
JSON.stringify([{}]),
|
||||||
|
)();
|
||||||
|
|
||||||
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
|
...teamEnvironment,
|
||||||
|
variables: JSON.stringify([{}]),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should edit name of an existing team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: '123' }],
|
variables: [{ key: '123' }],
|
||||||
@@ -206,7 +261,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify([{ key: '123' }]),
|
JSON.stringify([{ key: '123' }]),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
@@ -214,24 +269,14 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name is invalid', async () => {
|
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||||
const result = await teamEnvironmentsService.updateTeamEnvironment(
|
|
||||||
teamEnvironment.id,
|
|
||||||
'12',
|
|
||||||
JSON.stringify([{ key: 'value' }]),
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_SHORT_NAME);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
|
||||||
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
|
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.updateTeamEnvironment(
|
const result = await teamEnvironmentsService.updateTeamEnvironment(
|
||||||
'invalidid',
|
'invalidid',
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify(teamEnvironment.variables),
|
JSON.stringify(teamEnvironment.variables),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
});
|
});
|
||||||
@@ -243,7 +288,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
teamEnvironment.name,
|
teamEnvironment.name,
|
||||||
JSON.stringify([{ key: 'value' }]),
|
JSON.stringify([{ key: 'value' }]),
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`team_environment/${teamEnvironment.teamID}/updated`,
|
`team_environment/${teamEnvironment.teamID}/updated`,
|
||||||
@@ -256,13 +301,13 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteAllVariablesFromTeamEnvironment', () => {
|
describe('deleteAllVariablesFromTeamEnvironment', () => {
|
||||||
test('should successfully delete all variables in a team environment', async () => {
|
test('should delete all variables in a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment);
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce(teamEnvironment);
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
@@ -270,13 +315,13 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
|
mockPrisma.teamEnvironment.update.mockRejectedValue('RecordNotFound');
|
||||||
|
|
||||||
const result =
|
const result =
|
||||||
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
||||||
'invalidid',
|
'invalidid',
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
});
|
});
|
||||||
@@ -287,7 +332,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
const result =
|
const result =
|
||||||
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`team_environment/${teamEnvironment.teamID}/updated`,
|
`team_environment/${teamEnvironment.teamID}/updated`,
|
||||||
@@ -300,7 +345,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('createDuplicateEnvironment', () => {
|
describe('createDuplicateEnvironment', () => {
|
||||||
test('should successfully duplicate an existing team environment', async () => {
|
test('should duplicate an existing team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
@@ -312,21 +357,21 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
id: 'newid',
|
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
|
id: 'newid',
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
variables: JSON.stringify(teamEnvironment.variables),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should throw TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
mockPrisma.teamEnvironment.findFirst.mockRejectedValue('NotFoundError');
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
});
|
});
|
||||||
@@ -343,13 +388,13 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
);
|
)();
|
||||||
|
|
||||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||||
`team_environment/${teamEnvironment.teamID}/created`,
|
`team_environment/${teamEnvironment.teamID}/created`,
|
||||||
{
|
{
|
||||||
id: 'newid',
|
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
|
id: 'newid',
|
||||||
variables: JSON.stringify([{}]),
|
variables: JSON.stringify([{}]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,14 +1,15 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { TeamEnvironment as DBTeamEnvironment, Prisma } from '@prisma/client';
|
import { pipe } from 'fp-ts/function';
|
||||||
|
import * as T from 'fp-ts/Task';
|
||||||
|
import * as TO from 'fp-ts/TaskOption';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import * as A from 'fp-ts/Array';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { TeamEnvironment } from './team-environments.model';
|
import { TeamEnvironment } from './team-environments.model';
|
||||||
import {
|
import { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
|
||||||
TEAM_ENVIRONMENT_NOT_FOUND,
|
|
||||||
TEAM_ENVIRONMENT_SHORT_NAME,
|
|
||||||
} from 'src/errors';
|
|
||||||
import * as E from 'fp-ts/Either';
|
|
||||||
import { isValidLength } from 'src/utils';
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamEnvironmentsService {
|
export class TeamEnvironmentsService {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -16,218 +17,219 @@ export class TeamEnvironmentsService {
|
|||||||
private readonly pubsub: PubSubService,
|
private readonly pubsub: PubSubService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
TITLE_LENGTH = 3;
|
getTeamEnvironment(id: string) {
|
||||||
|
return TO.tryCatch(() =>
|
||||||
/**
|
this.prisma.teamEnvironment.findFirst({
|
||||||
* TeamEnvironments are saved in the DB in the following way
|
where: { id },
|
||||||
* [{ key: value }, { key: value },....]
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Typecast a database TeamEnvironment to a TeamEnvironment model
|
|
||||||
* @param teamEnvironment database TeamEnvironment
|
|
||||||
* @returns TeamEnvironment model
|
|
||||||
*/
|
|
||||||
private cast(teamEnvironment: DBTeamEnvironment): TeamEnvironment {
|
|
||||||
return {
|
|
||||||
id: teamEnvironment.id,
|
|
||||||
name: teamEnvironment.name,
|
|
||||||
teamID: teamEnvironment.teamID,
|
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get details of a TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param id TeamEnvironment ID
|
|
||||||
* @returns Either of a TeamEnvironment or error message
|
|
||||||
*/
|
|
||||||
async getTeamEnvironment(id: string) {
|
|
||||||
try {
|
|
||||||
const teamEnvironment =
|
|
||||||
await this.prisma.teamEnvironment.findFirstOrThrow({
|
|
||||||
where: { id },
|
|
||||||
});
|
|
||||||
return E.right(teamEnvironment);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a new TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param name name of new TeamEnvironment
|
|
||||||
* @param teamID teamID of new TeamEnvironment
|
|
||||||
* @param variables JSONified string of contents of new TeamEnvironment
|
|
||||||
* @returns Either of a TeamEnvironment or error message
|
|
||||||
*/
|
|
||||||
async createTeamEnvironment(name: string, teamID: string, variables: string) {
|
|
||||||
const isTitleValid = isValidLength(name, this.TITLE_LENGTH);
|
|
||||||
if (!isTitleValid) return E.left(TEAM_ENVIRONMENT_SHORT_NAME);
|
|
||||||
|
|
||||||
const result = await this.prisma.teamEnvironment.create({
|
|
||||||
data: {
|
|
||||||
name: name,
|
|
||||||
teamID: teamID,
|
|
||||||
variables: JSON.parse(variables),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const createdTeamEnvironment = this.cast(result);
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team_environment/${createdTeamEnvironment.teamID}/created`,
|
|
||||||
createdTeamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(createdTeamEnvironment);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete a TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param id TeamEnvironment ID
|
|
||||||
* @returns Either of boolean or error message
|
|
||||||
*/
|
|
||||||
async deleteTeamEnvironment(id: string) {
|
|
||||||
try {
|
|
||||||
const result = await this.prisma.teamEnvironment.delete({
|
|
||||||
where: {
|
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const deletedTeamEnvironment = this.cast(result);
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team_environment/${deletedTeamEnvironment.teamID}/deleted`,
|
|
||||||
deletedTeamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(true);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Update a TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param id TeamEnvironment ID
|
|
||||||
* @param name TeamEnvironment name
|
|
||||||
* @param variables JSONified string of contents of new TeamEnvironment
|
|
||||||
* @returns Either of a TeamEnvironment or error message
|
|
||||||
*/
|
|
||||||
async updateTeamEnvironment(id: string, name: string, variables: string) {
|
|
||||||
try {
|
|
||||||
const isTitleValid = isValidLength(name, this.TITLE_LENGTH);
|
|
||||||
if (!isTitleValid) return E.left(TEAM_ENVIRONMENT_SHORT_NAME);
|
|
||||||
|
|
||||||
const result = await this.prisma.teamEnvironment.update({
|
|
||||||
where: { id: id },
|
|
||||||
data: {
|
|
||||||
name,
|
|
||||||
variables: JSON.parse(variables),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const updatedTeamEnvironment = this.cast(result);
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team_environment/${updatedTeamEnvironment.teamID}/updated`,
|
|
||||||
updatedTeamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(updatedTeamEnvironment);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Clear contents of a TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param id TeamEnvironment ID
|
|
||||||
* @returns Either of a TeamEnvironment or error message
|
|
||||||
*/
|
|
||||||
async deleteAllVariablesFromTeamEnvironment(id: string) {
|
|
||||||
try {
|
|
||||||
const result = await this.prisma.teamEnvironment.update({
|
|
||||||
where: { id: id },
|
|
||||||
data: {
|
|
||||||
variables: [],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const teamEnvironment = this.cast(result);
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team_environment/${teamEnvironment.teamID}/updated`,
|
|
||||||
teamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(teamEnvironment);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a duplicate of a existing TeamEnvironment.
|
|
||||||
*
|
|
||||||
* @param id TeamEnvironment ID
|
|
||||||
* @returns Either of a TeamEnvironment or error message
|
|
||||||
*/
|
|
||||||
async createDuplicateEnvironment(id: string) {
|
|
||||||
try {
|
|
||||||
const environment = await this.prisma.teamEnvironment.findFirst({
|
|
||||||
where: {
|
|
||||||
id: id,
|
|
||||||
},
|
|
||||||
rejectOnNotFound: true,
|
rejectOnNotFound: true,
|
||||||
});
|
}),
|
||||||
|
);
|
||||||
const result = await this.prisma.teamEnvironment.create({
|
|
||||||
data: {
|
|
||||||
name: environment.name,
|
|
||||||
teamID: environment.teamID,
|
|
||||||
variables: environment.variables as Prisma.JsonArray,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const duplicatedTeamEnvironment = this.cast(result);
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team_environment/${duplicatedTeamEnvironment.teamID}/created`,
|
|
||||||
duplicatedTeamEnvironment,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(duplicatedTeamEnvironment);
|
|
||||||
} catch (error) {
|
|
||||||
return E.left(TEAM_ENVIRONMENT_NOT_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
createTeamEnvironment(name: string, teamID: string, variables: string) {
|
||||||
* Fetch all TeamEnvironments of a team.
|
return pipe(
|
||||||
*
|
() =>
|
||||||
* @param teamID teamID of new TeamEnvironment
|
this.prisma.teamEnvironment.create({
|
||||||
* @returns List of TeamEnvironments
|
data: {
|
||||||
*/
|
name: name,
|
||||||
async fetchAllTeamEnvironments(teamID: string) {
|
teamID: teamID,
|
||||||
const result = await this.prisma.teamEnvironment.findMany({
|
variables: JSON.parse(variables),
|
||||||
where: {
|
},
|
||||||
teamID: teamID,
|
}),
|
||||||
},
|
T.chainFirst(
|
||||||
});
|
(environment) => () =>
|
||||||
const teamEnvironments = result.map((item) => {
|
this.pubsub.publish(
|
||||||
return this.cast(item);
|
`team_environment/${environment.teamID}/created`,
|
||||||
});
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
T.map((data) => {
|
||||||
|
return <TeamEnvironment>{
|
||||||
|
id: data.id,
|
||||||
|
name: data.name,
|
||||||
|
teamID: data.teamID,
|
||||||
|
variables: JSON.stringify(data.variables),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return teamEnvironments;
|
deleteTeamEnvironment(id: string) {
|
||||||
|
return pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
this.prisma.teamEnvironment.delete({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
() => TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
|
),
|
||||||
|
TE.chainFirst((environment) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.pubsub.publish(
|
||||||
|
`team_environment/${environment.teamID}/deleted`,
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.map((data) => true),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateTeamEnvironment(id: string, name: string, variables: string) {
|
||||||
|
return pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
this.prisma.teamEnvironment.update({
|
||||||
|
where: { id: id },
|
||||||
|
data: {
|
||||||
|
name,
|
||||||
|
variables: JSON.parse(variables),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
() => TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
|
),
|
||||||
|
TE.chainFirst((environment) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.pubsub.publish(
|
||||||
|
`team_environment/${environment.teamID}/updated`,
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.map(
|
||||||
|
(environment) =>
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllVariablesFromTeamEnvironment(id: string) {
|
||||||
|
return pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
this.prisma.teamEnvironment.update({
|
||||||
|
where: { id: id },
|
||||||
|
data: {
|
||||||
|
variables: [],
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
() => TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
|
),
|
||||||
|
TE.chainFirst((environment) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.pubsub.publish(
|
||||||
|
`team_environment/${environment.teamID}/updated`,
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.map(
|
||||||
|
(environment) =>
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createDuplicateEnvironment(id: string) {
|
||||||
|
return pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
this.prisma.teamEnvironment.findFirst({
|
||||||
|
where: {
|
||||||
|
id: id,
|
||||||
|
},
|
||||||
|
rejectOnNotFound: true,
|
||||||
|
}),
|
||||||
|
() => TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
|
),
|
||||||
|
TE.chain((environment) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.prisma.teamEnvironment.create({
|
||||||
|
data: {
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: environment.variables as Prisma.JsonArray,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.chainFirst((environment) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.pubsub.publish(
|
||||||
|
`team_environment/${environment.teamID}/created`,
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.map(
|
||||||
|
(environment) =>
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchAllTeamEnvironments(teamID: string) {
|
||||||
|
return pipe(
|
||||||
|
() =>
|
||||||
|
this.prisma.teamEnvironment.findMany({
|
||||||
|
where: {
|
||||||
|
teamID: teamID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
T.map(
|
||||||
|
A.map(
|
||||||
|
(environment) =>
|
||||||
|
<TeamEnvironment>{
|
||||||
|
id: environment.id,
|
||||||
|
name: environment.name,
|
||||||
|
teamID: environment.teamID,
|
||||||
|
variables: JSON.stringify(environment.variables),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -11,6 +11,6 @@ export class TeamEnvsTeamResolver {
|
|||||||
description: 'Returns all Team Environments for the given Team',
|
description: 'Returns all Team Environments for the given Team',
|
||||||
})
|
})
|
||||||
teamEnvironments(@Parent() team: Team): Promise<TeamEnvironment[]> {
|
teamEnvironments(@Parent() team: Team): Promise<TeamEnvironment[]> {
|
||||||
return this.teamEnvironmentService.fetchAllTeamEnvironments(team.id);
|
return this.teamEnvironmentService.fetchAllTeamEnvironments(team.id)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
import { ArgsType, Field, ID } from '@nestjs/graphql';
|
|
||||||
import { TeamMemberRole } from 'src/team/team.model';
|
|
||||||
|
|
||||||
@ArgsType()
|
|
||||||
export class CreateTeamInvitationArgs {
|
|
||||||
@Field(() => ID, {
|
|
||||||
name: 'teamID',
|
|
||||||
description: 'ID of the Team ID to invite from',
|
|
||||||
})
|
|
||||||
teamID: string;
|
|
||||||
|
|
||||||
@Field({ name: 'inviteeEmail', description: 'Email of the user to invite' })
|
|
||||||
inviteeEmail: string;
|
|
||||||
|
|
||||||
@Field(() => TeamMemberRole, {
|
|
||||||
name: 'inviteeRole',
|
|
||||||
description: 'Role to be given to the user',
|
|
||||||
})
|
|
||||||
inviteeRole: TeamMemberRole;
|
|
||||||
}
|
|
||||||
@@ -12,10 +12,15 @@ import { TeamInvitation } from './team-invitation.model';
|
|||||||
import { TeamInvitationService } from './team-invitation.service';
|
import { TeamInvitationService } from './team-invitation.service';
|
||||||
import { pipe } from 'fp-ts/function';
|
import { pipe } from 'fp-ts/function';
|
||||||
import * as TE from 'fp-ts/TaskEither';
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
import * as E from 'fp-ts/Either';
|
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model';
|
import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model';
|
||||||
import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors';
|
import { EmailCodec } from 'src/types/Email';
|
||||||
|
import {
|
||||||
|
INVALID_EMAIL,
|
||||||
|
TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
||||||
|
TEAM_INVITE_NO_INVITE_FOUND,
|
||||||
|
USER_NOT_FOUND,
|
||||||
|
} from 'src/errors';
|
||||||
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
||||||
import { User } from 'src/user/user.model';
|
import { User } from 'src/user/user.model';
|
||||||
import { UseGuards } from '@nestjs/common';
|
import { UseGuards } from '@nestjs/common';
|
||||||
@@ -31,8 +36,6 @@ import { UserService } from 'src/user/user.service';
|
|||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
|
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
|
||||||
import { SkipThrottle } from '@nestjs/throttler';
|
import { SkipThrottle } from '@nestjs/throttler';
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
|
||||||
import { CreateTeamInvitationArgs } from './input-type.args';
|
|
||||||
|
|
||||||
@UseGuards(GqlThrottlerGuard)
|
@UseGuards(GqlThrottlerGuard)
|
||||||
@Resolver(() => TeamInvitation)
|
@Resolver(() => TeamInvitation)
|
||||||
@@ -76,8 +79,8 @@ export class TeamInvitationResolver {
|
|||||||
'Gets the Team Invitation with the given ID, or null if not exists',
|
'Gets the Team Invitation with the given ID, or null if not exists',
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, TeamInviteViewerGuard)
|
@UseGuards(GqlAuthGuard, TeamInviteViewerGuard)
|
||||||
async teamInvitation(
|
teamInvitation(
|
||||||
@GqlUser() user: AuthUser,
|
@GqlUser() user: User,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
description: 'ID of the Team Invitation to lookup',
|
description: 'ID of the Team Invitation to lookup',
|
||||||
@@ -85,11 +88,17 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<TeamInvitation> {
|
): Promise<TeamInvitation> {
|
||||||
const teamInvitation = await this.teamInvitationService.getInvitation(
|
return pipe(
|
||||||
inviteID,
|
this.teamInvitationService.getInvitation(inviteID),
|
||||||
);
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
if (O.isNone(teamInvitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
TE.chainW(
|
||||||
return teamInvitation.value;
|
TE.fromPredicate(
|
||||||
|
(a) => a.inviteeEmail.toLowerCase() === user.email?.toLowerCase(),
|
||||||
|
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.getOrElse(throwErr),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamInvitation, {
|
@Mutation(() => TeamInvitation, {
|
||||||
@@ -97,19 +106,56 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER)
|
@RequiresTeamRole(TeamMemberRole.OWNER)
|
||||||
async createTeamInvitation(
|
createTeamInvitation(
|
||||||
@GqlUser() user: AuthUser,
|
@GqlUser()
|
||||||
@Args() args: CreateTeamInvitationArgs,
|
user: User,
|
||||||
): Promise<TeamInvitation> {
|
|
||||||
const teamInvitation = await this.teamInvitationService.createInvitation(
|
|
||||||
user,
|
|
||||||
args.teamID,
|
|
||||||
args.inviteeEmail,
|
|
||||||
args.inviteeRole,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (E.isLeft(teamInvitation)) throwErr(teamInvitation.left);
|
@Args({
|
||||||
return teamInvitation.right;
|
name: 'teamID',
|
||||||
|
description: 'ID of the Team ID to invite from',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'inviteeEmail',
|
||||||
|
description: 'Email of the user to invite',
|
||||||
|
})
|
||||||
|
inviteeEmail: string,
|
||||||
|
@Args({
|
||||||
|
name: 'inviteeRole',
|
||||||
|
type: () => TeamMemberRole,
|
||||||
|
description: 'Role to be given to the user',
|
||||||
|
})
|
||||||
|
inviteeRole: TeamMemberRole,
|
||||||
|
): Promise<TeamInvitation> {
|
||||||
|
return pipe(
|
||||||
|
TE.Do,
|
||||||
|
|
||||||
|
// Validate email
|
||||||
|
TE.bindW('email', () =>
|
||||||
|
pipe(
|
||||||
|
EmailCodec.decode(inviteeEmail),
|
||||||
|
TE.fromEither,
|
||||||
|
TE.mapLeft(() => INVALID_EMAIL),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Validate and get Team
|
||||||
|
TE.bindW('team', () => this.teamService.getTeamWithIDTE(teamID)),
|
||||||
|
|
||||||
|
// Create team
|
||||||
|
TE.chainW(({ email, team }) =>
|
||||||
|
this.teamInvitationService.createInvitation(
|
||||||
|
user,
|
||||||
|
team,
|
||||||
|
email,
|
||||||
|
inviteeRole,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// If failed, throw err (so the message is passed) else return value
|
||||||
|
TE.getOrElse(throwErr),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean, {
|
@Mutation(() => Boolean, {
|
||||||
@@ -117,7 +163,7 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
|
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER)
|
@RequiresTeamRole(TeamMemberRole.OWNER)
|
||||||
async revokeTeamInvitation(
|
revokeTeamInvitation(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
@@ -125,19 +171,19 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<true> {
|
): Promise<true> {
|
||||||
const isRevoked = await this.teamInvitationService.revokeInvitation(
|
return pipe(
|
||||||
inviteID,
|
this.teamInvitationService.revokeInvitation(inviteID),
|
||||||
);
|
TE.map(() => true as const),
|
||||||
if (E.isLeft(isRevoked)) throwErr(isRevoked.left);
|
TE.getOrElse(throwErr),
|
||||||
return true;
|
)();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamMember, {
|
@Mutation(() => TeamMember, {
|
||||||
description: 'Accept an Invitation',
|
description: 'Accept an Invitation',
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, TeamInviteeGuard)
|
@UseGuards(GqlAuthGuard, TeamInviteeGuard)
|
||||||
async acceptTeamInvitation(
|
acceptTeamInvitation(
|
||||||
@GqlUser() user: AuthUser,
|
@GqlUser() user: User,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
@@ -145,12 +191,10 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<TeamMember> {
|
): Promise<TeamMember> {
|
||||||
const teamMember = await this.teamInvitationService.acceptInvitation(
|
return pipe(
|
||||||
inviteID,
|
this.teamInvitationService.acceptInvitation(inviteID, user),
|
||||||
user,
|
TE.getOrElse(throwErr),
|
||||||
);
|
)();
|
||||||
if (E.isLeft(teamMember)) throwErr(teamMember.left);
|
|
||||||
return teamMember.right;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import * as T from 'fp-ts/Task';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import * as E from 'fp-ts/Either';
|
import * as TO from 'fp-ts/TaskOption';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import { pipe, flow, constVoid } from 'fp-ts/function';
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { TeamInvitation as DBTeamInvitation } from '@prisma/client';
|
import { Team, TeamMemberRole } from 'src/team/team.model';
|
||||||
import { TeamMember, TeamMemberRole } from 'src/team/team.model';
|
import { Email } from 'src/types/Email';
|
||||||
|
import { User } from 'src/user/user.model';
|
||||||
import { TeamService } from 'src/team/team.service';
|
import { TeamService } from 'src/team/team.service';
|
||||||
import {
|
import {
|
||||||
INVALID_EMAIL,
|
|
||||||
TEAM_INVALID_ID,
|
|
||||||
TEAM_INVITE_ALREADY_MEMBER,
|
TEAM_INVITE_ALREADY_MEMBER,
|
||||||
TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
||||||
TEAM_INVITE_MEMBER_HAS_INVITE,
|
TEAM_INVITE_MEMBER_HAS_INVITE,
|
||||||
TEAM_INVITE_NO_INVITE_FOUND,
|
TEAM_INVITE_NO_INVITE_FOUND,
|
||||||
TEAM_MEMBER_NOT_FOUND,
|
|
||||||
} from 'src/errors';
|
} from 'src/errors';
|
||||||
import { TeamInvitation } from './team-invitation.model';
|
import { TeamInvitation } from './team-invitation.model';
|
||||||
import { MailerService } from 'src/mailer/mailer.service';
|
import { MailerService } from 'src/mailer/mailer.service';
|
||||||
import { UserService } from 'src/user/user.service';
|
import { UserService } from 'src/user/user.service';
|
||||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
import { validateEmail } from '../utils';
|
|
||||||
import { AuthUser } from 'src/types/AuthUser';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamInvitationService {
|
export class TeamInvitationService {
|
||||||
@@ -30,221 +29,245 @@ export class TeamInvitationService {
|
|||||||
private readonly mailerService: MailerService,
|
private readonly mailerService: MailerService,
|
||||||
|
|
||||||
private readonly pubsub: PubSubService,
|
private readonly pubsub: PubSubService,
|
||||||
) {}
|
) {
|
||||||
|
this.getInvitation = this.getInvitation.bind(this);
|
||||||
/**
|
|
||||||
* Cast a DBTeamInvitation to a TeamInvitation
|
|
||||||
* @param dbTeamInvitation database TeamInvitation
|
|
||||||
* @returns TeamInvitation model
|
|
||||||
*/
|
|
||||||
cast(dbTeamInvitation: DBTeamInvitation): TeamInvitation {
|
|
||||||
return {
|
|
||||||
...dbTeamInvitation,
|
|
||||||
inviteeRole: TeamMemberRole[dbTeamInvitation.inviteeRole],
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getInvitation(inviteID: string): TO.TaskOption<TeamInvitation> {
|
||||||
* Get the team invite
|
return pipe(
|
||||||
* @param inviteID invite id
|
() =>
|
||||||
* @returns an Option of team invitation or none
|
this.prisma.teamInvitation.findUnique({
|
||||||
*/
|
where: {
|
||||||
async getInvitation(inviteID: string) {
|
id: inviteID,
|
||||||
try {
|
|
||||||
const dbInvitation = await this.prisma.teamInvitation.findUniqueOrThrow({
|
|
||||||
where: {
|
|
||||||
id: inviteID,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return O.some(this.cast(dbInvitation));
|
|
||||||
} catch (e) {
|
|
||||||
return O.none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the team invite for an invitee with email and teamID.
|
|
||||||
* @param inviteeEmail invitee email
|
|
||||||
* @param teamID team id
|
|
||||||
* @returns an Either of team invitation for the invitee or error
|
|
||||||
*/
|
|
||||||
async getTeamInviteByEmailAndTeamID(inviteeEmail: string, teamID: string) {
|
|
||||||
const isEmailValid = validateEmail(inviteeEmail);
|
|
||||||
if (!isEmailValid) return E.left(INVALID_EMAIL);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const teamInvite = await this.prisma.teamInvitation.findUniqueOrThrow({
|
|
||||||
where: {
|
|
||||||
teamID_inviteeEmail: {
|
|
||||||
inviteeEmail: inviteeEmail,
|
|
||||||
teamID: teamID,
|
|
||||||
},
|
},
|
||||||
},
|
}),
|
||||||
});
|
TO.fromTask,
|
||||||
|
TO.chain(flow(O.fromNullable, TO.fromOption)),
|
||||||
return E.right(teamInvite);
|
TO.map((x) => x as TeamInvitation),
|
||||||
} catch (e) {
|
);
|
||||||
return E.left(TEAM_INVITE_NO_INVITE_FOUND);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
getInvitationWithEmail(email: Email, team: Team) {
|
||||||
* Create a team invitation
|
return pipe(
|
||||||
* @param creator creator of the invitation
|
() =>
|
||||||
* @param teamID team id
|
this.prisma.teamInvitation.findUnique({
|
||||||
* @param inviteeEmail invitee email
|
where: {
|
||||||
* @param inviteeRole invitee role
|
teamID_inviteeEmail: {
|
||||||
* @returns an Either of team invitation or error message
|
inviteeEmail: email,
|
||||||
*/
|
teamID: team.id,
|
||||||
async createInvitation(
|
},
|
||||||
creator: AuthUser,
|
},
|
||||||
teamID: string,
|
}),
|
||||||
inviteeEmail: string,
|
TO.fromTask,
|
||||||
|
TO.chain(flow(O.fromNullable, TO.fromOption)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createInvitation(
|
||||||
|
creator: User,
|
||||||
|
team: Team,
|
||||||
|
inviteeEmail: Email,
|
||||||
inviteeRole: TeamMemberRole,
|
inviteeRole: TeamMemberRole,
|
||||||
) {
|
) {
|
||||||
// validate email
|
return pipe(
|
||||||
const isEmailValid = validateEmail(inviteeEmail);
|
// Perform all validation checks
|
||||||
if (!isEmailValid) return E.left(INVALID_EMAIL);
|
TE.sequenceArray([
|
||||||
|
// creator should be a TeamMember
|
||||||
|
pipe(
|
||||||
|
this.teamService.getTeamMemberTE(team.id, creator.uid),
|
||||||
|
TE.map(constVoid),
|
||||||
|
),
|
||||||
|
|
||||||
// team ID should valid
|
// Invitee should not be a team member
|
||||||
const team = await this.teamService.getTeamWithID(teamID);
|
pipe(
|
||||||
if (!team) return E.left(TEAM_INVALID_ID);
|
async () => await this.userService.findUserByEmail(inviteeEmail),
|
||||||
|
TO.foldW(
|
||||||
|
() => TE.right(undefined), // If no user, short circuit to completion
|
||||||
|
(user) =>
|
||||||
|
pipe(
|
||||||
|
// If user is found, check if team member
|
||||||
|
this.teamService.getTeamMemberTE(team.id, user.uid),
|
||||||
|
TE.foldW(
|
||||||
|
() => TE.right(undefined), // Not team-member, this is good
|
||||||
|
() => TE.left(TEAM_INVITE_ALREADY_MEMBER), // Is team member, not good
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TE.map(constVoid),
|
||||||
|
),
|
||||||
|
|
||||||
// invitation creator should be a TeamMember
|
// Should not have an existing invite
|
||||||
const isTeamMember = await this.teamService.getTeamMember(
|
pipe(
|
||||||
team.id,
|
this.getInvitationWithEmail(inviteeEmail, team),
|
||||||
creator.uid,
|
TE.fromTaskOption(() => null),
|
||||||
|
TE.swap,
|
||||||
|
TE.map(constVoid),
|
||||||
|
TE.mapLeft(() => TEAM_INVITE_MEMBER_HAS_INVITE),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
|
||||||
|
// Create the invitation
|
||||||
|
TE.chainTaskK(
|
||||||
|
() => () =>
|
||||||
|
this.prisma.teamInvitation.create({
|
||||||
|
data: {
|
||||||
|
teamID: team.id,
|
||||||
|
inviteeEmail,
|
||||||
|
inviteeRole,
|
||||||
|
creatorUid: creator.uid,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Send email, this is a side effect
|
||||||
|
TE.chainFirstTaskK((invitation) =>
|
||||||
|
pipe(
|
||||||
|
this.mailerService.sendMail(inviteeEmail, {
|
||||||
|
template: 'team-invitation',
|
||||||
|
variables: {
|
||||||
|
invitee: creator.displayName ?? 'A Hoppscotch User',
|
||||||
|
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${invitation.id}`,
|
||||||
|
invite_team_name: team.name,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
|
||||||
|
TE.getOrElseW(() => T.of(undefined)), // This value doesn't matter as we don't mind the return value (chainFirst) as long as the task completes
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Send PubSub topic
|
||||||
|
TE.chainFirstTaskK((invitation) =>
|
||||||
|
TE.fromTask(async () => {
|
||||||
|
const inv: TeamInvitation = {
|
||||||
|
id: invitation.id,
|
||||||
|
teamID: invitation.teamID,
|
||||||
|
creatorUid: invitation.creatorUid,
|
||||||
|
inviteeEmail: invitation.inviteeEmail,
|
||||||
|
inviteeRole: TeamMemberRole[invitation.inviteeRole],
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pubsub.publish(`team/${inv.teamID}/invite_added`, inv);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Map to model type
|
||||||
|
TE.map((x) => x as TeamInvitation),
|
||||||
);
|
);
|
||||||
if (!isTeamMember) return E.left(TEAM_MEMBER_NOT_FOUND);
|
}
|
||||||
|
|
||||||
// Checking to see if the invitee is already part of the team or not
|
revokeInvitation(inviteID: string) {
|
||||||
const inviteeUser = await this.userService.findUserByEmail(inviteeEmail);
|
return pipe(
|
||||||
if (O.isSome(inviteeUser)) {
|
// Make sure invite exists
|
||||||
// invitee should not already a member
|
this.getInvitation(inviteID),
|
||||||
const isTeamMember = await this.teamService.getTeamMember(
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
team.id,
|
|
||||||
inviteeUser.value.uid,
|
|
||||||
);
|
|
||||||
if (isTeamMember) return E.left(TEAM_INVITE_ALREADY_MEMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
// check invitee already invited earlier or not
|
// Delete team invitation
|
||||||
const teamInvitation = await this.getTeamInviteByEmailAndTeamID(
|
TE.chainTaskK(
|
||||||
inviteeEmail,
|
() => () =>
|
||||||
team.id,
|
this.prisma.teamInvitation.delete({
|
||||||
|
where: {
|
||||||
|
id: inviteID,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Emit Pubsub Event
|
||||||
|
TE.chainFirst((invitation) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.pubsub.publish(
|
||||||
|
`team/${invitation.teamID}/invite_removed`,
|
||||||
|
invitation.id,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// We are not returning anything
|
||||||
|
TE.map(constVoid),
|
||||||
);
|
);
|
||||||
if (E.isRight(teamInvitation)) return E.left(TEAM_INVITE_MEMBER_HAS_INVITE);
|
}
|
||||||
|
|
||||||
// create the invitation
|
getAllInvitationsInTeam(team: Team) {
|
||||||
const dbInvitation = await this.prisma.teamInvitation.create({
|
return pipe(
|
||||||
data: {
|
() =>
|
||||||
teamID: team.id,
|
this.prisma.teamInvitation.findMany({
|
||||||
inviteeEmail,
|
where: {
|
||||||
inviteeRole,
|
teamID: team.id,
|
||||||
creatorUid: creator.uid,
|
},
|
||||||
},
|
}),
|
||||||
});
|
T.map((x) => x as TeamInvitation[]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
await this.mailerService.sendEmail(inviteeEmail, {
|
acceptInvitation(inviteID: string, acceptedBy: User) {
|
||||||
template: 'team-invitation',
|
return pipe(
|
||||||
variables: {
|
TE.Do,
|
||||||
invitee: creator.displayName ?? 'A Hoppscotch User',
|
|
||||||
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${dbInvitation.id}`,
|
|
||||||
invite_team_name: team.name,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const invitation = this.cast(dbInvitation);
|
// First get the invitation
|
||||||
this.pubsub.publish(`team/${invitation.teamID}/invite_added`, invitation);
|
TE.bindW('invitation', () =>
|
||||||
|
pipe(
|
||||||
|
this.getInvitation(inviteID),
|
||||||
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
return E.right(invitation);
|
// Validation checks
|
||||||
|
TE.chainFirstW(({ invitation }) =>
|
||||||
|
TE.sequenceArray([
|
||||||
|
// Make sure the invited user is not part of the team
|
||||||
|
pipe(
|
||||||
|
this.teamService.getTeamMemberTE(invitation.teamID, acceptedBy.uid),
|
||||||
|
TE.swap,
|
||||||
|
TE.bimap(
|
||||||
|
() => TEAM_INVITE_ALREADY_MEMBER,
|
||||||
|
constVoid, // The return type is ignored
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Make sure the invited user and accepting user has the same email
|
||||||
|
pipe(
|
||||||
|
undefined,
|
||||||
|
TE.fromPredicate(
|
||||||
|
(a) => acceptedBy.email === invitation.inviteeEmail,
|
||||||
|
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Add the team member
|
||||||
|
// TODO: Somehow bring subscriptions to this ?
|
||||||
|
TE.bindW('teamMember', ({ invitation }) =>
|
||||||
|
pipe(
|
||||||
|
TE.tryCatch(
|
||||||
|
() =>
|
||||||
|
this.teamService.addMemberToTeam(
|
||||||
|
invitation.teamID,
|
||||||
|
acceptedBy.uid,
|
||||||
|
invitation.inviteeRole,
|
||||||
|
),
|
||||||
|
() => TEAM_INVITE_ALREADY_MEMBER, // Can only fail if Team Member already exists, which we checked, but due to async lets assert that here too
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
TE.chainFirstW(({ invitation }) => this.revokeInvitation(invitation.id)),
|
||||||
|
|
||||||
|
TE.map(({ teamMember }) => teamMember),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Revoke a team invitation
|
* Fetch the count invitations for a given team.
|
||||||
* @param inviteID invite id
|
|
||||||
* @returns an Either of true or error message
|
|
||||||
*/
|
|
||||||
async revokeInvitation(inviteID: string) {
|
|
||||||
// check if the invite exists
|
|
||||||
const invitation = await this.getInvitation(inviteID);
|
|
||||||
if (O.isNone(invitation)) return E.left(TEAM_INVITE_NO_INVITE_FOUND);
|
|
||||||
|
|
||||||
// delete the invite
|
|
||||||
await this.prisma.teamInvitation.delete({
|
|
||||||
where: {
|
|
||||||
id: inviteID,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pubsub.publish(
|
|
||||||
`team/${invitation.value.teamID}/invite_removed`,
|
|
||||||
invitation.value.id,
|
|
||||||
);
|
|
||||||
|
|
||||||
return E.right(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Accept a team invitation
|
|
||||||
* @param inviteID invite id
|
|
||||||
* @param acceptedBy user who accepted the invitation
|
|
||||||
* @returns an Either of team member or error message
|
|
||||||
*/
|
|
||||||
async acceptInvitation(inviteID: string, acceptedBy: AuthUser) {
|
|
||||||
// check if the invite exists
|
|
||||||
const invitation = await this.getInvitation(inviteID);
|
|
||||||
if (O.isNone(invitation)) return E.left(TEAM_INVITE_NO_INVITE_FOUND);
|
|
||||||
|
|
||||||
// make sure the user is not already a member of the team
|
|
||||||
const teamMemberInvitee = await this.teamService.getTeamMember(
|
|
||||||
invitation.value.teamID,
|
|
||||||
acceptedBy.uid,
|
|
||||||
);
|
|
||||||
if (teamMemberInvitee) return E.left(TEAM_INVITE_ALREADY_MEMBER);
|
|
||||||
|
|
||||||
// make sure the user is the same as the invitee
|
|
||||||
if (
|
|
||||||
acceptedBy.email.toLowerCase() !==
|
|
||||||
invitation.value.inviteeEmail.toLowerCase()
|
|
||||||
)
|
|
||||||
return E.left(TEAM_INVITE_EMAIL_DO_NOT_MATCH);
|
|
||||||
|
|
||||||
// add the user to the team
|
|
||||||
let teamMember: TeamMember;
|
|
||||||
try {
|
|
||||||
teamMember = await this.teamService.addMemberToTeam(
|
|
||||||
invitation.value.teamID,
|
|
||||||
acceptedBy.uid,
|
|
||||||
invitation.value.inviteeRole,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
|
||||||
return E.left(TEAM_INVITE_ALREADY_MEMBER);
|
|
||||||
}
|
|
||||||
|
|
||||||
// delete the invite
|
|
||||||
await this.revokeInvitation(inviteID);
|
|
||||||
|
|
||||||
return E.right(teamMember);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Fetch all team invitations for a given team.
|
|
||||||
* @param teamID team id
|
* @param teamID team id
|
||||||
* @returns array of team invitations for a team
|
* @returns a count team invitations for a team
|
||||||
*/
|
*/
|
||||||
async getTeamInvitations(teamID: string) {
|
async getAllTeamInvitations(teamID: string) {
|
||||||
const dbInvitations = await this.prisma.teamInvitation.findMany({
|
const invitations = await this.prisma.teamInvitation.findMany({
|
||||||
where: {
|
where: {
|
||||||
teamID: teamID,
|
teamID: teamID,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const invitations: TeamInvitation[] = dbInvitations.map((dbInvitation) =>
|
|
||||||
this.cast(dbInvitation),
|
|
||||||
);
|
|
||||||
|
|
||||||
return invitations;
|
return invitations;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { pipe } from 'fp-ts/function';
|
||||||
import { TeamService } from 'src/team/team.service';
|
import { TeamService } from 'src/team/team.service';
|
||||||
import { TeamInvitationService } from './team-invitation.service';
|
import { TeamInvitationService } from './team-invitation.service';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
|
import * as T from 'fp-ts/Task';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||||
import {
|
import {
|
||||||
BUG_AUTH_NO_USER_CTX,
|
BUG_AUTH_NO_USER_CTX,
|
||||||
BUG_TEAM_INVITE_NO_INVITE_ID,
|
BUG_TEAM_INVITE_NO_INVITE_ID,
|
||||||
TEAM_INVITE_NO_INVITE_FOUND,
|
TEAM_INVITE_NO_INVITE_FOUND,
|
||||||
TEAM_MEMBER_NOT_FOUND,
|
|
||||||
TEAM_NOT_REQUIRED_ROLE,
|
TEAM_NOT_REQUIRED_ROLE,
|
||||||
} from 'src/errors';
|
} from 'src/errors';
|
||||||
|
import { User } from 'src/user/user.model';
|
||||||
import { throwErr } from 'src/utils';
|
import { throwErr } from 'src/utils';
|
||||||
import { TeamMemberRole } from 'src/team/team.model';
|
import { TeamMemberRole } from 'src/team/team.model';
|
||||||
|
|
||||||
/**
|
|
||||||
* This guard only allows team owner to execute the resolver
|
|
||||||
*/
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamInviteTeamOwnerGuard implements CanActivate {
|
export class TeamInviteTeamOwnerGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -24,30 +24,48 @@ export class TeamInviteTeamOwnerGuard implements CanActivate {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
// Get GQL context
|
return pipe(
|
||||||
const gqlExecCtx = GqlExecutionContext.create(context);
|
TE.Do,
|
||||||
|
|
||||||
// Get user
|
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
||||||
const { user } = gqlExecCtx.getContext().req;
|
|
||||||
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
|
||||||
|
|
||||||
// Get the invite
|
// Get the invite
|
||||||
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
TE.bindW('invite', ({ gqlCtx }) =>
|
||||||
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
pipe(
|
||||||
|
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
|
||||||
|
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
|
||||||
|
TE.chainW((inviteID) =>
|
||||||
|
pipe(
|
||||||
|
this.teamInviteService.getInvitation(inviteID),
|
||||||
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
TE.bindW('user', ({ gqlCtx }) =>
|
||||||
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
pipe(
|
||||||
|
gqlCtx.getContext().req.user,
|
||||||
|
O.fromNullable,
|
||||||
|
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Fetch team member details of this user
|
TE.bindW('userMember', ({ invite, user }) =>
|
||||||
const teamMember = await this.teamService.getTeamMember(
|
this.teamService.getTeamMemberTE(invite.teamID, user.uid),
|
||||||
invitation.value.teamID,
|
),
|
||||||
user.uid,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND);
|
TE.chainW(
|
||||||
if (teamMember.role !== TeamMemberRole.OWNER)
|
TE.fromPredicate(
|
||||||
throwErr(TEAM_NOT_REQUIRED_ROLE);
|
({ userMember }) => userMember.role === TeamMemberRole.OWNER,
|
||||||
|
() => TEAM_NOT_REQUIRED_ROLE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
return true;
|
TE.fold(
|
||||||
|
(err) => throwErr(err),
|
||||||
|
() => T.of(true),
|
||||||
|
),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { TeamInvitationService } from './team-invitation.service';
|
import { TeamInvitationService } from './team-invitation.service';
|
||||||
|
import { pipe, flow } from 'fp-ts/function';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import * as T from 'fp-ts/Task';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||||
import {
|
import {
|
||||||
BUG_AUTH_NO_USER_CTX,
|
BUG_AUTH_NO_USER_CTX,
|
||||||
BUG_TEAM_INVITE_NO_INVITE_ID,
|
BUG_TEAM_INVITE_NO_INVITE_ID,
|
||||||
|
TEAM_INVITE_NOT_VALID_VIEWER,
|
||||||
TEAM_INVITE_NO_INVITE_FOUND,
|
TEAM_INVITE_NO_INVITE_FOUND,
|
||||||
TEAM_MEMBER_NOT_FOUND,
|
|
||||||
} from 'src/errors';
|
} from 'src/errors';
|
||||||
|
import { User } from 'src/user/user.model';
|
||||||
import { throwErr } from 'src/utils';
|
import { throwErr } from 'src/utils';
|
||||||
import { TeamService } from 'src/team/team.service';
|
import { TeamService } from 'src/team/team.service';
|
||||||
|
|
||||||
/**
|
|
||||||
* This guard only allows user to execute the resolver
|
|
||||||
* 1. If user is invitee, allow
|
|
||||||
* 2. Or else, if user is team member, allow
|
|
||||||
*
|
|
||||||
* TLDR: Allow if user is invitee or team member
|
|
||||||
*/
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamInviteViewerGuard implements CanActivate {
|
export class TeamInviteViewerGuard implements CanActivate {
|
||||||
constructor(
|
constructor(
|
||||||
@@ -26,32 +23,50 @@ export class TeamInviteViewerGuard implements CanActivate {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
// Get GQL context
|
return pipe(
|
||||||
const gqlExecCtx = GqlExecutionContext.create(context);
|
TE.Do,
|
||||||
|
|
||||||
// Get user
|
// Get GQL Context
|
||||||
const { user } = gqlExecCtx.getContext().req;
|
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
||||||
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
|
||||||
|
|
||||||
// Get the invite
|
// Get user
|
||||||
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
TE.bindW('user', ({ gqlCtx }) =>
|
||||||
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
pipe(
|
||||||
|
O.fromNullable(gqlCtx.getContext<{ user?: User }>().user),
|
||||||
|
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
// Get the invite
|
||||||
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
TE.bindW('invite', ({ gqlCtx }) =>
|
||||||
|
pipe(
|
||||||
|
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
|
||||||
|
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
|
||||||
|
TE.chainW(
|
||||||
|
flow(
|
||||||
|
this.teamInviteService.getInvitation,
|
||||||
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
// Check if the user and the invite email match, else if user is a team member
|
// Check if the user and the invite email match, else if we can resolver the user as a team member
|
||||||
if (
|
// any better solution ?
|
||||||
user.email?.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase()
|
TE.chainW(({ user, invite }) =>
|
||||||
) {
|
user.email?.toLowerCase() === invite.inviteeEmail.toLowerCase()
|
||||||
const teamMember = await this.teamService.getTeamMember(
|
? TE.of(true)
|
||||||
invitation.value.teamID,
|
: pipe(
|
||||||
user.uid,
|
this.teamService.getTeamMemberTE(invite.teamID, user.uid),
|
||||||
);
|
TE.map(() => true),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND);
|
TE.mapLeft((e) =>
|
||||||
}
|
e === 'team/member_not_found' ? TEAM_INVITE_NOT_VALID_VIEWER : e,
|
||||||
|
),
|
||||||
|
|
||||||
return true;
|
TE.fold(throwErr, () => T.of(true)),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
import { TeamInvitationService } from './team-invitation.service';
|
import { TeamInvitationService } from './team-invitation.service';
|
||||||
|
import { pipe, flow } from 'fp-ts/function';
|
||||||
import * as O from 'fp-ts/Option';
|
import * as O from 'fp-ts/Option';
|
||||||
|
import * as T from 'fp-ts/Task';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||||
|
import { User } from 'src/user/user.model';
|
||||||
import {
|
import {
|
||||||
BUG_AUTH_NO_USER_CTX,
|
BUG_AUTH_NO_USER_CTX,
|
||||||
BUG_TEAM_INVITE_NO_INVITE_ID,
|
BUG_TEAM_INVITE_NO_INVITE_ID,
|
||||||
@@ -20,26 +24,44 @@ export class TeamInviteeGuard implements CanActivate {
|
|||||||
constructor(private readonly teamInviteService: TeamInvitationService) {}
|
constructor(private readonly teamInviteService: TeamInvitationService) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
// Get GQL Context
|
return pipe(
|
||||||
const gqlExecCtx = GqlExecutionContext.create(context);
|
TE.Do,
|
||||||
|
|
||||||
// Get user
|
// Get execution context
|
||||||
const { user } = gqlExecCtx.getContext().req;
|
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
||||||
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
|
||||||
|
|
||||||
// Get the invite
|
// Get user
|
||||||
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
TE.bindW('user', ({ gqlCtx }) =>
|
||||||
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
pipe(
|
||||||
|
O.fromNullable(gqlCtx.getContext<{ user?: User }>().user),
|
||||||
|
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
// Get invite
|
||||||
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
TE.bindW('invite', ({ gqlCtx }) =>
|
||||||
|
pipe(
|
||||||
|
O.fromNullable(gqlCtx.getArgs<{ inviteID?: string }>().inviteID),
|
||||||
|
TE.fromOption(() => BUG_TEAM_INVITE_NO_INVITE_ID),
|
||||||
|
TE.chainW(
|
||||||
|
flow(
|
||||||
|
this.teamInviteService.getInvitation,
|
||||||
|
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
if (
|
// Check if the emails match
|
||||||
user.email.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase()
|
TE.chainW(
|
||||||
) {
|
TE.fromPredicate(
|
||||||
throwErr(TEAM_INVITE_EMAIL_DO_NOT_MATCH);
|
({ user, invite }) => user.email === invite.inviteeEmail,
|
||||||
}
|
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
return true;
|
// Fold it to a promise
|
||||||
|
TE.fold(throwErr, () => T.of(true)),
|
||||||
|
)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,6 @@ export class TeamTeamInviteExtResolver {
|
|||||||
complexity: 10,
|
complexity: 10,
|
||||||
})
|
})
|
||||||
teamInvitations(@Parent() team: Team): Promise<TeamInvitation[]> {
|
teamInvitations(@Parent() team: Team): Promise<TeamInvitation[]> {
|
||||||
return this.teamInviteService.getTeamInvitations(team.id);
|
return this.teamInviteService.getAllInvitationsInTeam(team)();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { HttpStatus } from '@nestjs/common';
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
** Custom interface to handle errors specific to Auth module
|
** Custom interface to handle errors specific to Auth module
|
||||||
** Since its REST we need to return the HTTP status code along with the error message
|
** Since its REST we need to return HTTP status code along with error message
|
||||||
*/
|
*/
|
||||||
export type AuthError = {
|
export type AuthError = {
|
||||||
message: string;
|
message: string;
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="currentColor" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-star"><polygon points="12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"/></svg>
|
|
||||||
|
Before Width: | Height: | Size: 337 B |
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Reaksie liggaam",
|
"body": "Reaksie liggaam",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Opskrifte",
|
"headers": "Opskrifte",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tyd",
|
"time": "Tyd",
|
||||||
"title": "Reaksie",
|
"title": "Reaksie",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "wag vir verbinding",
|
"waiting_for_connection": "wag vir verbinding",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "هيئة الاستجابة",
|
"body": "هيئة الاستجابة",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "الرؤوس",
|
"headers": "الرؤوس",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "حالة",
|
"status": "حالة",
|
||||||
"time": "وقت",
|
"time": "وقت",
|
||||||
"title": "إجابة",
|
"title": "إجابة",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "في انتظار الاتصال",
|
"waiting_for_connection": "في انتظار الاتصال",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Visualitzar els meus enllaços"
|
"view_my_links": "Visualitzar els meus enllaços"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Cos de resposta",
|
"body": "Cos de resposta",
|
||||||
"filter_response_body": "Filtrar el cos de la resposta JSON (utilitza la sintaxi JSONPath)",
|
"filter_response_body": "Filtrar el cos de la resposta JSON (utilitza la sintaxi JSONPath)",
|
||||||
"headers": "Capçaleres",
|
"headers": "Capçaleres",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Estat",
|
"status": "Estat",
|
||||||
"time": "Temps",
|
"time": "Temps",
|
||||||
"title": "Resposta",
|
"title": "Resposta",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "esperant la connexió",
|
"waiting_for_connection": "esperant la connexió",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "查看我的链接"
|
"view_my_links": "查看我的链接"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "响应体",
|
"body": "响应体",
|
||||||
"filter_response_body": "筛选JSON响应本体(使用JSONPath语法)",
|
"filter_response_body": "筛选JSON响应本体(使用JSONPath语法)",
|
||||||
"headers": "响应头",
|
"headers": "响应头",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "状态",
|
"status": "状态",
|
||||||
"time": "时间",
|
"time": "时间",
|
||||||
"title": "响应",
|
"title": "响应",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "等待连接",
|
"waiting_for_connection": "等待连接",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Odpovědní orgán",
|
"body": "Odpovědní orgán",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Záhlaví",
|
"headers": "Záhlaví",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Postavení",
|
"status": "Postavení",
|
||||||
"time": "Čas",
|
"time": "Čas",
|
||||||
"title": "Odezva",
|
"title": "Odezva",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "čekání na připojení",
|
"waiting_for_connection": "čekání na připojení",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Svarorgan",
|
"body": "Svarorgan",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Overskrifter",
|
"headers": "Overskrifter",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tid",
|
"time": "Tid",
|
||||||
"title": "Respons",
|
"title": "Respons",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "venter på forbindelse",
|
"waiting_for_connection": "venter på forbindelse",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Antworttext",
|
"body": "Antworttext",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Header",
|
"headers": "Header",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Zeit",
|
"time": "Zeit",
|
||||||
"title": "Antwort",
|
"title": "Antwort",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "auf Verbindung warten",
|
"waiting_for_connection": "auf Verbindung warten",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Προβολή των links μου"
|
"view_my_links": "Προβολή των links μου"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Σώμα απόκρισης",
|
"body": "Σώμα απόκρισης",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Κεφαλίδες",
|
"headers": "Κεφαλίδες",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Κατάσταση",
|
"status": "Κατάσταση",
|
||||||
"time": "χρόνος",
|
"time": "χρόνος",
|
||||||
"title": "Απάντηση",
|
"title": "Απάντηση",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "περιμένοντας τη σύνδεση",
|
"waiting_for_connection": "περιμένοντας τη σύνδεση",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Ver mis enlaces"
|
"view_my_links": "Ver mis enlaces"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Cuerpo de respuesta",
|
"body": "Cuerpo de respuesta",
|
||||||
"filter_response_body": "Filtrar el cuerpo de la respuesta JSON (utiliza la sintaxis JSONPath)",
|
"filter_response_body": "Filtrar el cuerpo de la respuesta JSON (utiliza la sintaxis JSONPath)",
|
||||||
"headers": "Encabezados",
|
"headers": "Encabezados",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Estado",
|
"status": "Estado",
|
||||||
"time": "Tiempo",
|
"time": "Tiempo",
|
||||||
"title": "Respuesta",
|
"title": "Respuesta",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "esperando la conexión",
|
"waiting_for_connection": "esperando la conexión",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Vastauselin",
|
"body": "Vastauselin",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Otsikot",
|
"headers": "Otsikot",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Tila",
|
"status": "Tila",
|
||||||
"time": "Aika",
|
"time": "Aika",
|
||||||
"title": "Vastaus",
|
"title": "Vastaus",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "yhteyttä odotellessa",
|
"waiting_for_connection": "yhteyttä odotellessa",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Voir mes liens"
|
"view_my_links": "Voir mes liens"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Corps de réponse",
|
"body": "Corps de réponse",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "En-têtes",
|
"headers": "En-têtes",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"time": "Temps",
|
"time": "Temps",
|
||||||
"title": "Réponse",
|
"title": "Réponse",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "En attente de connexion",
|
"waiting_for_connection": "En attente de connexion",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "גוף תגובה",
|
"body": "גוף תגובה",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "כותרות",
|
"headers": "כותרות",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "סטָטוּס",
|
"status": "סטָטוּס",
|
||||||
"time": "זְמַן",
|
"time": "זְמַן",
|
||||||
"title": "תְגוּבָה",
|
"title": "תְגוּבָה",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "מחכה לחיבור",
|
"waiting_for_connection": "מחכה לחיבור",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -433,7 +433,6 @@
|
|||||||
"view_my_links": "मेरे लिंक देखें"
|
"view_my_links": "मेरे लिंक देखें"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "प्रतिक्रिया निकाय",
|
"body": "प्रतिक्रिया निकाय",
|
||||||
"filter_response_body": "फ़िल्टर JSON रिस्पांस बॉडी (JSONPATH सिंटैक्स का उपयोग करता है)",
|
"filter_response_body": "फ़िल्टर JSON रिस्पांस बॉडी (JSONPATH सिंटैक्स का उपयोग करता है)",
|
||||||
"headers": "हेडर",
|
"headers": "हेडर",
|
||||||
@@ -447,7 +446,6 @@
|
|||||||
"status": "दर्जा",
|
"status": "दर्जा",
|
||||||
"time": "समय",
|
"time": "समय",
|
||||||
"title": "जवाब",
|
"title": "जवाब",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "जुडने के लिए इंतजार",
|
"waiting_for_connection": "जुडने के लिए इंतजार",
|
||||||
"xml": "एक्सएमएल"
|
"xml": "एक्सएमएल"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -5,29 +5,29 @@
|
|||||||
"choose_file": "Válasszon egy fájlt",
|
"choose_file": "Válasszon egy fájlt",
|
||||||
"clear": "Törlés",
|
"clear": "Törlés",
|
||||||
"clear_all": "Összes törlése",
|
"clear_all": "Összes törlése",
|
||||||
"close": "Bezárás",
|
"close": "Close",
|
||||||
"connect": "Kapcsolódás",
|
"connect": "Kapcsolódás",
|
||||||
"connecting": "Kapcsolódás",
|
"connecting": "Connecting",
|
||||||
"copy": "Másolás",
|
"copy": "Másolás",
|
||||||
"delete": "Törlés",
|
"delete": "Törlés",
|
||||||
"disconnect": "Leválasztás",
|
"disconnect": "Leválasztás",
|
||||||
"dismiss": "Eltüntetés",
|
"dismiss": "Eltüntetés",
|
||||||
"dont_save": "Ne mentse",
|
"dont_save": "Ne mentse",
|
||||||
"download_file": "Fájl letöltése",
|
"download_file": "Fájl letöltése",
|
||||||
"drag_to_reorder": "Húzza az átrendezéshez",
|
"drag_to_reorder": "Drag to reorder",
|
||||||
"duplicate": "Kettőzés",
|
"duplicate": "Kettőzés",
|
||||||
"edit": "Szerkesztés",
|
"edit": "Szerkesztés",
|
||||||
"filter": "Szűrő",
|
"filter": "Filter",
|
||||||
"go_back": "Vissza",
|
"go_back": "Vissza",
|
||||||
"go_forward": "Előre",
|
"go_forward": "Go forward",
|
||||||
"group_by": "Csoportosítás",
|
"group_by": "Group by",
|
||||||
"label": "Címke",
|
"label": "Címke",
|
||||||
"learn_more": "Tudjon meg többet",
|
"learn_more": "Tudjon meg többet",
|
||||||
"less": "Kevesebb",
|
"less": "Kevesebb",
|
||||||
"more": "Több",
|
"more": "Több",
|
||||||
"new": "Új",
|
"new": "Új",
|
||||||
"no": "Nem",
|
"no": "Nem",
|
||||||
"open_workspace": "Munkaterület megnyitása",
|
"open_workspace": "Open workspace",
|
||||||
"paste": "Beillesztés",
|
"paste": "Beillesztés",
|
||||||
"prettify": "Csinosítás",
|
"prettify": "Csinosítás",
|
||||||
"remove": "Eltávolítás",
|
"remove": "Eltávolítás",
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
"search": "Keresés",
|
"search": "Keresés",
|
||||||
"send": "Küldés",
|
"send": "Küldés",
|
||||||
"start": "Indítás",
|
"start": "Indítás",
|
||||||
"starting": "Indítás",
|
"starting": "Starting",
|
||||||
"stop": "Leállítás",
|
"stop": "Leállítás",
|
||||||
"to_close": "a bezáráshoz",
|
"to_close": "a bezáráshoz",
|
||||||
"to_navigate": "a navigáláshoz",
|
"to_navigate": "a navigáláshoz",
|
||||||
@@ -118,16 +118,16 @@
|
|||||||
},
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"created": "Gyűjtemény létrehozva",
|
"created": "Gyűjtemény létrehozva",
|
||||||
"different_parent": "Nem lehet átrendezni a különböző szülővel rendelkező gyűjteményt",
|
"different_parent": "Cannot reorder collection with different parent",
|
||||||
"edit": "Gyűjtemény szerkesztése",
|
"edit": "Gyűjtemény szerkesztése",
|
||||||
"invalid_name": "Adjon nevet a gyűjteménynek",
|
"invalid_name": "Adjon nevet a gyűjteménynek",
|
||||||
"invalid_root_move": "A gyűjtemény már a gyökérben van",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Sikeresen áthelyezve",
|
"moved": "Moved Successfully",
|
||||||
"my_collections": "Saját gyűjtemények",
|
"my_collections": "Saját gyűjtemények",
|
||||||
"name": "Saját új gyűjtemény",
|
"name": "Saját új gyűjtemény",
|
||||||
"name_length_insufficient": "A gyűjtemény nevének legalább 3 karakter hosszúságúnak kell lennie",
|
"name_length_insufficient": "A gyűjtemény nevének legalább 3 karakter hosszúságúnak kell lennie",
|
||||||
"new": "Új gyűjtemény",
|
"new": "Új gyűjtemény",
|
||||||
"order_changed": "Gyűjtemény sorrendje frissítve",
|
"order_changed": "Collection Order Updated",
|
||||||
"renamed": "Gyűjtemény átnevezve",
|
"renamed": "Gyűjtemény átnevezve",
|
||||||
"request_in_use": "A kérés használatban",
|
"request_in_use": "A kérés használatban",
|
||||||
"save_as": "Mentés másként",
|
"save_as": "Mentés másként",
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
"remove_team": "Biztosan törölni szeretné ezt a csapatot?",
|
"remove_team": "Biztosan törölni szeretné ezt a csapatot?",
|
||||||
"remove_telemetry": "Biztosan ki szeretné kapcsolni a telemetriát?",
|
"remove_telemetry": "Biztosan ki szeretné kapcsolni a telemetriát?",
|
||||||
"request_change": "Biztosan el szeretné vetni a jelenlegi kérést? Minden mentetlen változtatás el fog veszni.",
|
"request_change": "Biztosan el szeretné vetni a jelenlegi kérést? Minden mentetlen változtatás el fog veszni.",
|
||||||
"save_unsaved_tab": "Szeretné menteni az ezen a lapon elvégzett változtatásokat?",
|
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||||
"sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát."
|
"sync": "Szeretné visszaállítani a munkaterületét a felhőből? Ez el fogja vetni a helyi folyamatát."
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -180,8 +180,8 @@
|
|||||||
"profile": "Jelentkezzen be a profilja megtekintéséhez",
|
"profile": "Jelentkezzen be a profilja megtekintéséhez",
|
||||||
"protocols": "A protokollok üresek",
|
"protocols": "A protokollok üresek",
|
||||||
"schema": "Kapcsolódjon egy GraphQL-végponthoz a séma megtekintéséhez",
|
"schema": "Kapcsolódjon egy GraphQL-végponthoz a séma megtekintéséhez",
|
||||||
"shortcodes": "A rövid kódok üresek",
|
"shortcodes": "Shortcodes are empty",
|
||||||
"subscription": "A feliratkozások üresek",
|
"subscription": "Subscriptions are empty",
|
||||||
"team_name": "A csapat neve üres",
|
"team_name": "A csapat neve üres",
|
||||||
"teams": "Ön nem tartozik semmilyen csapathoz",
|
"teams": "Ön nem tartozik semmilyen csapathoz",
|
||||||
"tests": "Nincsenek tesztek ehhez a kéréshez"
|
"tests": "Nincsenek tesztek ehhez a kéréshez"
|
||||||
@@ -194,13 +194,13 @@
|
|||||||
"deleted": "Környezet törlése",
|
"deleted": "Környezet törlése",
|
||||||
"edit": "Környezet szerkesztése",
|
"edit": "Környezet szerkesztése",
|
||||||
"invalid_name": "Adjon nevet a környezetnek",
|
"invalid_name": "Adjon nevet a környezetnek",
|
||||||
"my_environments": "Saját környezetek",
|
"my_environments": "My Environments",
|
||||||
"nested_overflow": "az egymásba ágyazott környezeti változók 10 szintre vannak korlátozva",
|
"nested_overflow": "az egymásba ágyazott környezeti változók 10 szintre vannak korlátozva",
|
||||||
"new": "Új környezet",
|
"new": "Új környezet",
|
||||||
"no_environment": "Nincs környezet",
|
"no_environment": "Nincs környezet",
|
||||||
"no_environment_description": "Nem lettek környezetek kiválasztva. Válassza ki, hogy mit kell tenni a következő változókkal.",
|
"no_environment_description": "Nem lettek környezetek kiválasztva. Válassza ki, hogy mit kell tenni a következő változókkal.",
|
||||||
"select": "Környezet kiválasztása",
|
"select": "Környezet kiválasztása",
|
||||||
"team_environments": "Csapatkörnyezetek",
|
"team_environments": "Team Environments",
|
||||||
"title": "Környezetek",
|
"title": "Környezetek",
|
||||||
"updated": "Környezet frissítve",
|
"updated": "Környezet frissítve",
|
||||||
"variable_list": "Változólista"
|
"variable_list": "Változólista"
|
||||||
@@ -209,9 +209,9 @@
|
|||||||
"browser_support_sse": "Úgy tűnik, hogy ez a böngésző nem támogatja a kiszolgáló által küldött eseményeket.",
|
"browser_support_sse": "Úgy tűnik, hogy ez a böngésző nem támogatja a kiszolgáló által küldött eseményeket.",
|
||||||
"check_console_details": "Nézze meg a konzolnaplót a részletekért.",
|
"check_console_details": "Nézze meg a konzolnaplót a részletekért.",
|
||||||
"curl_invalid_format": "A cURL nincs megfelelően formázva",
|
"curl_invalid_format": "A cURL nincs megfelelően formázva",
|
||||||
"danger_zone": "Veszélyes zóna",
|
"danger_zone": "Danger zone",
|
||||||
"delete_account": "Az Ön fiókja jelenleg tulajdonos ezekben a csapatokban:",
|
"delete_account": "Your account is currently an owner in these teams:",
|
||||||
"delete_account_description": "El kell távolítani magát, át kell adnia a tulajdonjogot vagy törölnie kell ezeket a csapatokat, mielőtt törölhetné a fiókját.",
|
"delete_account_description": "You must either remove yourself, transfer ownership, or delete these teams before you can delete your account.",
|
||||||
"empty_req_name": "Üres kérésnév",
|
"empty_req_name": "Üres kérésnév",
|
||||||
"f12_details": "(F12 a részletekért)",
|
"f12_details": "(F12 a részletekért)",
|
||||||
"gql_prettify_invalid_query": "Nem sikerült csinosítani egy érvénytelen lekérdezést, oldja meg a lekérdezés szintaktikai hibáit, és próbálja újra",
|
"gql_prettify_invalid_query": "Nem sikerült csinosítani egy érvénytelen lekérdezést, oldja meg a lekérdezés szintaktikai hibáit, és próbálja újra",
|
||||||
@@ -219,13 +219,13 @@
|
|||||||
"incorrect_email": "Hibás e-mail",
|
"incorrect_email": "Hibás e-mail",
|
||||||
"invalid_link": "Érvénytelen hivatkozás",
|
"invalid_link": "Érvénytelen hivatkozás",
|
||||||
"invalid_link_description": "A kattintott hivatkozás érvénytelen vagy lejárt.",
|
"invalid_link_description": "A kattintott hivatkozás érvénytelen vagy lejárt.",
|
||||||
"json_parsing_failed": "Érvénytelen JSON",
|
"json_parsing_failed": "Invalid JSON",
|
||||||
"json_prettify_invalid_body": "Nem sikerült csinosítani egy érvénytelen törzset, oldja meg a JSON szintaktikai hibáit, és próbálja újra",
|
"json_prettify_invalid_body": "Nem sikerült csinosítani egy érvénytelen törzset, oldja meg a JSON szintaktikai hibáit, és próbálja újra",
|
||||||
"network_error": "Úgy tűnik, hogy hálózati hiba van. Próbálja újra.",
|
"network_error": "Úgy tűnik, hogy hálózati hiba van. Próbálja újra.",
|
||||||
"network_fail": "Nem sikerült elküldeni a kérést",
|
"network_fail": "Nem sikerült elküldeni a kérést",
|
||||||
"no_duration": "Nincs időtartam",
|
"no_duration": "Nincs időtartam",
|
||||||
"no_results_found": "Nincs találat",
|
"no_results_found": "No matches found",
|
||||||
"page_not_found": "Ez az oldal nem található",
|
"page_not_found": "This page could not be found",
|
||||||
"script_fail": "Nem sikerült végrehajtani a kérés előtti parancsfájlt",
|
"script_fail": "Nem sikerült végrehajtani a kérés előtti parancsfájlt",
|
||||||
"something_went_wrong": "Valami elromlott",
|
"something_went_wrong": "Valami elromlott",
|
||||||
"test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt"
|
"test_script_fail": "Nem sikerült végrehajtani a kérés utáni parancsfájlt"
|
||||||
@@ -238,9 +238,9 @@
|
|||||||
"title": "Exportálás"
|
"title": "Exportálás"
|
||||||
},
|
},
|
||||||
"filter": {
|
"filter": {
|
||||||
"all": "Összes",
|
"all": "All",
|
||||||
"none": "Nincs",
|
"none": "None",
|
||||||
"starred": "Csillagozott"
|
"starred": "Starred"
|
||||||
},
|
},
|
||||||
"folder": {
|
"folder": {
|
||||||
"created": "Mappa létrehozva",
|
"created": "Mappa létrehozva",
|
||||||
@@ -256,7 +256,7 @@
|
|||||||
"subscriptions": "Feliratkozások"
|
"subscriptions": "Feliratkozások"
|
||||||
},
|
},
|
||||||
"group": {
|
"group": {
|
||||||
"time": "Idő",
|
"time": "Time",
|
||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"header": {
|
"header": {
|
||||||
@@ -316,32 +316,32 @@
|
|||||||
"zen_mode": "Zen mód"
|
"zen_mode": "Zen mód"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close_unsaved_tab": "Elmentetlen változtatásai vannak",
|
"close_unsaved_tab": "You have unsaved changes",
|
||||||
"collections": "Gyűjtemények",
|
"collections": "Gyűjtemények",
|
||||||
"confirm": "Megerősítés",
|
"confirm": "Megerősítés",
|
||||||
"edit_request": "Kérés szerkesztése",
|
"edit_request": "Kérés szerkesztése",
|
||||||
"import_export": "Importálás és exportálás"
|
"import_export": "Importálás és exportálás"
|
||||||
},
|
},
|
||||||
"mqtt": {
|
"mqtt": {
|
||||||
"already_subscribed": "Ön már feliratkozott erre a témára.",
|
"already_subscribed": "You are already subscribed to this topic.",
|
||||||
"clean_session": "Munkamenet törlése",
|
"clean_session": "Clean Session",
|
||||||
"clear_input": "Bevitel törlése",
|
"clear_input": "Clear input",
|
||||||
"clear_input_on_send": "Bevitel törlése küldéskor",
|
"clear_input_on_send": "Clear input on send",
|
||||||
"client_id": "Ügyfél-azonosító",
|
"client_id": "Client ID",
|
||||||
"color": "Válasszon színt",
|
"color": "Pick a color",
|
||||||
"communication": "Kommunikáció",
|
"communication": "Kommunikáció",
|
||||||
"connection_config": "Kapcsolat beállításai",
|
"connection_config": "Connection Config",
|
||||||
"connection_not_authorized": "Ez az MQTT-kapcsolat nem használ semmilyen hitelesítést.",
|
"connection_not_authorized": "This MQTT connection does not use any authentication.",
|
||||||
"invalid_topic": "Adjon témát a feliratkozáshoz",
|
"invalid_topic": "Please provide a topic for the subscription",
|
||||||
"keep_alive": "Életben tartás",
|
"keep_alive": "Keep Alive",
|
||||||
"log": "Napló",
|
"log": "Napló",
|
||||||
"lw_message": "Utolsó kívánság üzenet",
|
"lw_message": "Last-Will Message",
|
||||||
"lw_qos": "Utolsó kívánság QoS",
|
"lw_qos": "Last-Will QoS",
|
||||||
"lw_retain": "Utolsó kívánság megtartás",
|
"lw_retain": "Last-Will Retain",
|
||||||
"lw_topic": "Utolsó kívánság téma",
|
"lw_topic": "Last-Will Topic",
|
||||||
"message": "Üzenet",
|
"message": "Üzenet",
|
||||||
"new": "Új feliratkozás",
|
"new": "New Subscription",
|
||||||
"not_connected": "Először indítson egy MQTT-kapcsolatot.",
|
"not_connected": "Please start a MQTT connection first.",
|
||||||
"publish": "Közzététel",
|
"publish": "Közzététel",
|
||||||
"qos": "QoS",
|
"qos": "QoS",
|
||||||
"ssl": "SSL",
|
"ssl": "SSL",
|
||||||
@@ -368,7 +368,7 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"app_settings": "Alkalmazás beállításai",
|
"app_settings": "Alkalmazás beállításai",
|
||||||
"default_hopp_displayname": "Névtelen felhasználó",
|
"default_hopp_displayname": "Unnamed User",
|
||||||
"editor": "Szerkesztő",
|
"editor": "Szerkesztő",
|
||||||
"editor_description": "A szerkesztők hozzáadhatnak, szerkeszthetnek és törölhetnek kéréseket.",
|
"editor_description": "A szerkesztők hozzáadhatnak, szerkeszthetnek és törölhetnek kéréseket.",
|
||||||
"email_verification_mail": "Egy ellenőrző e-mail el lett küldve az e-mail-címére. Kattintson a hivatkozásra az e-mail-címe ellenőrzéséhez.",
|
"email_verification_mail": "Egy ellenőrző e-mail el lett küldve az e-mail-címére. Kattintson a hivatkozásra az e-mail-címe ellenőrzéséhez.",
|
||||||
@@ -391,26 +391,26 @@
|
|||||||
"choose_language": "Nyelv kiválasztása",
|
"choose_language": "Nyelv kiválasztása",
|
||||||
"content_type": "Tartalom típusa",
|
"content_type": "Tartalom típusa",
|
||||||
"content_type_titles": {
|
"content_type_titles": {
|
||||||
"others": "Egyebek",
|
"others": "Others",
|
||||||
"structured": "Szerkesztett",
|
"structured": "Structured",
|
||||||
"text": "Szöveg"
|
"text": "Text"
|
||||||
},
|
},
|
||||||
"copy_link": "Hivatkozás másolása",
|
"copy_link": "Hivatkozás másolása",
|
||||||
"different_collection": "Nem lehet átrendezni a különböző gyűjteményekből érkező kéréseket",
|
"different_collection": "Cannot reorder requests from different collections",
|
||||||
"duplicated": "Kérés megkettőzve",
|
"duplicated": "Request duplicated",
|
||||||
"duration": "Időtartam",
|
"duration": "Időtartam",
|
||||||
"enter_curl": "cURL-parancs megadása",
|
"enter_curl": "cURL megadása",
|
||||||
"generate_code": "Kód előállítása",
|
"generate_code": "Kód előállítása",
|
||||||
"generated_code": "Előállított kód",
|
"generated_code": "Előállított kód",
|
||||||
"header_list": "Fejléclista",
|
"header_list": "Fejléclista",
|
||||||
"invalid_name": "Adjon nevet a kérésnek",
|
"invalid_name": "Adjon nevet a kérésnek",
|
||||||
"method": "Módszer",
|
"method": "Módszer",
|
||||||
"moved": "Kérés áthelyezve",
|
"moved": "Request moved",
|
||||||
"name": "Kérés neve",
|
"name": "Kérés neve",
|
||||||
"new": "Új kérés",
|
"new": "Új kérés",
|
||||||
"order_changed": "Kérés sorrendje frissítve",
|
"order_changed": "Request Order Updated",
|
||||||
"override": "Felülbírálás",
|
"override": "Felülbírálás",
|
||||||
"override_help": "<kbd>Content-Type</kbd> beállítása a fejlécekben",
|
"override_help": "A <kbd>Content-Type</kbd> beállítása a fejlécekben",
|
||||||
"overriden": "Felülbírálva",
|
"overriden": "Felülbírálva",
|
||||||
"parameter_list": "Lekérdezési paraméterek",
|
"parameter_list": "Lekérdezési paraméterek",
|
||||||
"parameters": "Paraméterek",
|
"parameters": "Paraméterek",
|
||||||
@@ -429,12 +429,11 @@
|
|||||||
"type": "Kérés típusa",
|
"type": "Kérés típusa",
|
||||||
"url": "URL",
|
"url": "URL",
|
||||||
"variables": "Változók",
|
"variables": "Változók",
|
||||||
"view_my_links": "Saját hivatkozások megtekintése"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Hang",
|
|
||||||
"body": "Válasz törzse",
|
"body": "Válasz törzse",
|
||||||
"filter_response_body": "JSON-válasz törzsének szűrése (JSONPath szintaxist használ)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Fejlécek",
|
"headers": "Fejlécek",
|
||||||
"html": "HTML",
|
"html": "HTML",
|
||||||
"image": "Kép",
|
"image": "Kép",
|
||||||
@@ -446,14 +445,13 @@
|
|||||||
"status": "Állapot",
|
"status": "Állapot",
|
||||||
"time": "Idő",
|
"time": "Idő",
|
||||||
"title": "Válasz",
|
"title": "Válasz",
|
||||||
"video": "Videó",
|
|
||||||
"waiting_for_connection": "várakozás kapcsolódásra",
|
"waiting_for_connection": "várakozás kapcsolódásra",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
"settings": {
|
"settings": {
|
||||||
"accent_color": "Kiemelőszín",
|
"accent_color": "Kiemelőszín",
|
||||||
"account": "Fiók",
|
"account": "Fiók",
|
||||||
"account_deleted": "A fiókja törölve lett",
|
"account_deleted": "Your account has been deleted",
|
||||||
"account_description": "A fiókbeállítások személyre szabása.",
|
"account_description": "A fiókbeállítások személyre szabása.",
|
||||||
"account_email_description": "Az Ön elsődleges e-mail-címe.",
|
"account_email_description": "Az Ön elsődleges e-mail-címe.",
|
||||||
"account_name_description": "Ez a megjelenített neve.",
|
"account_name_description": "Ez a megjelenített neve.",
|
||||||
@@ -462,8 +460,8 @@
|
|||||||
"change_font_size": "Betűméret megváltoztatása",
|
"change_font_size": "Betűméret megváltoztatása",
|
||||||
"choose_language": "Nyelv kiválasztása",
|
"choose_language": "Nyelv kiválasztása",
|
||||||
"dark_mode": "Sötét",
|
"dark_mode": "Sötét",
|
||||||
"delete_account": "Fiók törlése",
|
"delete_account": "Delete account",
|
||||||
"delete_account_description": "Ha törli a fiókját, akkor az összes adata véglegesen törlésre kerül. Ezt a műveletet nem lehet visszavonni.",
|
"delete_account_description": "Once you delete your account, all your data will be permanently deleted. This action cannot be undone.",
|
||||||
"expand_navigation": "Navigáció kinyitása",
|
"expand_navigation": "Navigáció kinyitása",
|
||||||
"experiments": "Kísérletek",
|
"experiments": "Kísérletek",
|
||||||
"experiments_notice": "Ez olyan kísérletek gyűjteménye, amelyeken dolgozunk, és amelyek hasznosak, szórakoztatóak lehetnek, mindkettő, vagy egyik sem. Ezek nem véglegesek és nem stabilak, ezért ha valami túl furcsa dolog történik, ne essen pánikba. Egyszerűen kapcsolja ki a hibás dolgot. Viccet félretéve, ",
|
"experiments_notice": "Ez olyan kísérletek gyűjteménye, amelyeken dolgozunk, és amelyek hasznosak, szórakoztatóak lehetnek, mindkettő, vagy egyik sem. Ezek nem véglegesek és nem stabilak, ezért ha valami túl furcsa dolog történik, ne essen pánikba. Egyszerűen kapcsolja ki a hibás dolgot. Viccet félretéve, ",
|
||||||
@@ -490,8 +488,8 @@
|
|||||||
"proxy_use_toggle": "A proxy középprogram használata a kérések küldéséhez",
|
"proxy_use_toggle": "A proxy középprogram használata a kérések küldéséhez",
|
||||||
"read_the": "Olvassa el:",
|
"read_the": "Olvassa el:",
|
||||||
"reset_default": "Visszaállítás az alapértelmezettre",
|
"reset_default": "Visszaállítás az alapértelmezettre",
|
||||||
"short_codes": "Rövid kódok",
|
"short_codes": "Short codes",
|
||||||
"short_codes_description": "Az Ön által létrehozott rövid kódok.",
|
"short_codes_description": "Short codes which were created by you.",
|
||||||
"sidebar_on_left": "Oldalsáv a bal oldalon",
|
"sidebar_on_left": "Oldalsáv a bal oldalon",
|
||||||
"sync": "Szinkronizálás",
|
"sync": "Szinkronizálás",
|
||||||
"sync_collections": "Gyűjtemények",
|
"sync_collections": "Gyűjtemények",
|
||||||
@@ -505,16 +503,16 @@
|
|||||||
"theme_description": "Az alkalmazás témájának személyre szabása.",
|
"theme_description": "Az alkalmazás témájának személyre szabása.",
|
||||||
"use_experimental_url_bar": "Kísérleti URL-sáv használata a környezet kiemelésével",
|
"use_experimental_url_bar": "Kísérleti URL-sáv használata a környezet kiemelésével",
|
||||||
"user": "Felhasználó",
|
"user": "Felhasználó",
|
||||||
"verified_email": "Ellenőrzött e-mail-cím",
|
"verified_email": "Verified email",
|
||||||
"verify_email": "E-mail-cím ellenőrzése"
|
"verify_email": "E-mail-cím ellenőrzése"
|
||||||
},
|
},
|
||||||
"shortcodes": {
|
"shortcodes": {
|
||||||
"actions": "Műveletek",
|
"actions": "Actions",
|
||||||
"created_on": "Létrehozva",
|
"created_on": "Created on",
|
||||||
"deleted": "Rövid kód törölve",
|
"deleted": "Shortcode deleted",
|
||||||
"method": "Módszer",
|
"method": "Method",
|
||||||
"not_found": "A rövid kód nem található",
|
"not_found": "Shortcode not found",
|
||||||
"short_code": "Rövid kód",
|
"short_code": "Short code",
|
||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"shortcut": {
|
"shortcut": {
|
||||||
@@ -556,9 +554,9 @@
|
|||||||
"title": "Kérés"
|
"title": "Kérés"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"copy": "Válasz másolása a vágólapra",
|
"copy": "Copy response to clipboard",
|
||||||
"download": "Válasz letöltés fájlként",
|
"download": "Download response as file",
|
||||||
"title": "Válasz"
|
"title": "Response"
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
"black": "Téma átváltása fekete módra",
|
"black": "Téma átváltása fekete módra",
|
||||||
@@ -576,8 +574,8 @@
|
|||||||
},
|
},
|
||||||
"socketio": {
|
"socketio": {
|
||||||
"communication": "Kommunikáció",
|
"communication": "Kommunikáció",
|
||||||
"connection_not_authorized": "Ez a SocketIO-kapcsolat nem használ semmilyen hitelesítést.",
|
"connection_not_authorized": "This SocketIO connection does not use any authentication.",
|
||||||
"event_name": "Esemény vagy téma neve",
|
"event_name": "Esemény neve",
|
||||||
"events": "Események",
|
"events": "Események",
|
||||||
"log": "Napló",
|
"log": "Napló",
|
||||||
"url": "URL"
|
"url": "URL"
|
||||||
@@ -594,9 +592,9 @@
|
|||||||
"connected": "Kapcsolódva",
|
"connected": "Kapcsolódva",
|
||||||
"connected_to": "Kapcsolódva ehhez: {name}",
|
"connected_to": "Kapcsolódva ehhez: {name}",
|
||||||
"connecting_to": "Kapcsolódás ehhez: {name}…",
|
"connecting_to": "Kapcsolódás ehhez: {name}…",
|
||||||
"connection_error": "Nem sikerült kapcsolódni",
|
"connection_error": "Failed to connect",
|
||||||
"connection_failed": "A kapcsolódás sikertelen",
|
"connection_failed": "Connection failed",
|
||||||
"connection_lost": "A kapcsolat elveszett",
|
"connection_lost": "Connection lost",
|
||||||
"copied_to_clipboard": "Vágólapra másolva",
|
"copied_to_clipboard": "Vágólapra másolva",
|
||||||
"deleted": "Törölve",
|
"deleted": "Törölve",
|
||||||
"deprecated": "ELAVULT",
|
"deprecated": "ELAVULT",
|
||||||
@@ -611,17 +609,17 @@
|
|||||||
"history_deleted": "Előzmények törölve",
|
"history_deleted": "Előzmények törölve",
|
||||||
"linewrap": "Sorok tördelése",
|
"linewrap": "Sorok tördelése",
|
||||||
"loading": "Betöltés…",
|
"loading": "Betöltés…",
|
||||||
"message_received": "Üzenet: {message} érkezett ehhez a témához: {topic}",
|
"message_received": "Message: {message} arrived on topic: {topic}",
|
||||||
"mqtt_subscription_failed": "Valami elromlott a következő témára való feliratkozás során: {topic}",
|
"mqtt_subscription_failed": "Something went wrong while subscribing to topic: {topic}",
|
||||||
"none": "Nincs",
|
"none": "Nincs",
|
||||||
"nothing_found": "Semmi sem található ehhez:",
|
"nothing_found": "Semmi sem található ehhez:",
|
||||||
"published_error": "Valami elromlott a következő üzenet közzététele során: {topic}, ehhez a témához: {message}",
|
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||||
"published_message": "Közzétett üzenet: {message}, ehhez a témához: {topic}",
|
"published_message": "Published message: {message} to topic: {topic}",
|
||||||
"reconnection_error": "Nem sikerült újrakapcsolódni",
|
"reconnection_error": "Failed to reconnect",
|
||||||
"subscribed_failed": "Nem sikerült feliratkozni erre a témára: {topic}",
|
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||||
"subscribed_success": "Sikeresen feliratkozott erre a témára: {topic}",
|
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||||
"unsubscribed_failed": "Nem sikerült leiratkozni erről a témáról: {topic}",
|
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||||
"unsubscribed_success": "Sikeresen leiratkozott erről a témáról: {topic}",
|
"unsubscribed_success": "Successfully unsubscribed from topic: {topic}",
|
||||||
"waiting_send_request": "Várakozás a kérés elküldésére"
|
"waiting_send_request": "Várakozás a kérés elküldésére"
|
||||||
},
|
},
|
||||||
"support": {
|
"support": {
|
||||||
@@ -641,7 +639,7 @@
|
|||||||
"body": "Törzs",
|
"body": "Törzs",
|
||||||
"collections": "Gyűjtemények",
|
"collections": "Gyűjtemények",
|
||||||
"documentation": "Dokumentáció",
|
"documentation": "Dokumentáció",
|
||||||
"environments": "Környezetek",
|
"environments": "Environments",
|
||||||
"headers": "Fejlécek",
|
"headers": "Fejlécek",
|
||||||
"history": "Előzmények",
|
"history": "Előzmények",
|
||||||
"mqtt": "MQTT",
|
"mqtt": "MQTT",
|
||||||
@@ -666,7 +664,7 @@
|
|||||||
"email_do_not_match": "Az e-mail-cím nem egyezik a fiókja részleteivel. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
"email_do_not_match": "Az e-mail-cím nem egyezik a fiókja részleteivel. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
||||||
"exit": "Kilépés a csapatból",
|
"exit": "Kilépés a csapatból",
|
||||||
"exit_disabled": "Csak a tulajdonos nem léphet ki a csapatból",
|
"exit_disabled": "Csak a tulajdonos nem léphet ki a csapatból",
|
||||||
"invalid_coll_id": "Érvénytelen gyűjteményazonosító",
|
"invalid_coll_id": "Invalid collection ID",
|
||||||
"invalid_email_format": "Az e-mail formátuma érvénytelen",
|
"invalid_email_format": "Az e-mail formátuma érvénytelen",
|
||||||
"invalid_id": "Érvénytelen csapatazonosító. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
"invalid_id": "Érvénytelen csapatazonosító. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
||||||
"invalid_invite_link": "Érvénytelen meghívási hivatkozás",
|
"invalid_invite_link": "Érvénytelen meghívási hivatkozás",
|
||||||
@@ -690,7 +688,7 @@
|
|||||||
"member_removed": "Felhasználó eltávolítva",
|
"member_removed": "Felhasználó eltávolítva",
|
||||||
"member_role_updated": "Felhasználói szerepek frissítve",
|
"member_role_updated": "Felhasználói szerepek frissítve",
|
||||||
"members": "Tagok",
|
"members": "Tagok",
|
||||||
"more_members": "+{count} további",
|
"more_members": "+{count} more",
|
||||||
"name_length_insufficient": "A csapat nevének legalább 6 karakter hosszúságúnak kell lennie",
|
"name_length_insufficient": "A csapat nevének legalább 6 karakter hosszúságúnak kell lennie",
|
||||||
"name_updated": "Csapatnév frissítve",
|
"name_updated": "Csapatnév frissítve",
|
||||||
"new": "Új csapat",
|
"new": "Új csapat",
|
||||||
@@ -698,13 +696,13 @@
|
|||||||
"new_name": "Saját új csapat",
|
"new_name": "Saját új csapat",
|
||||||
"no_access": "Nincs szerkesztési jogosultsága ezekhez a gyűjteményekhez",
|
"no_access": "Nincs szerkesztési jogosultsága ezekhez a gyűjteményekhez",
|
||||||
"no_invite_found": "A meghívás nem található. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
"no_invite_found": "A meghívás nem található. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
||||||
"no_request_found": "A kérés nem található.",
|
"no_request_found": "Request not found.",
|
||||||
"not_found": "A csapat nem található. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
"not_found": "A csapat nem található. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
||||||
"not_valid_viewer": "Ön nem érvényes megtekintő. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
"not_valid_viewer": "Ön nem érvényes megtekintő. Vegye fel a kapcsolatot a csapat tulajdonosával.",
|
||||||
"parent_coll_move": "Nem lehet áthelyezni a gyűjteményt egy gyermekgyűjteménybe",
|
"parent_coll_move": "Cannot move collection to a child collection",
|
||||||
"pending_invites": "Függőben lévő meghívások",
|
"pending_invites": "Függőben lévő meghívások",
|
||||||
"permissions": "Jogosultságok",
|
"permissions": "Jogosultságok",
|
||||||
"same_target_destination": "Ugyanaz a cél és célhely",
|
"same_target_destination": "Same target and destination",
|
||||||
"saved": "Csapat elmentve",
|
"saved": "Csapat elmentve",
|
||||||
"select_a_team": "Csapat kiválasztása",
|
"select_a_team": "Csapat kiválasztása",
|
||||||
"title": "Csapatok",
|
"title": "Csapatok",
|
||||||
@@ -712,9 +710,9 @@
|
|||||||
"we_sent_invite_link_description": "Kérje meg az összes meghívottat, hogy nézzék meg a beérkező leveleiket. Kattintsanak a hivatkozásra a csapathoz való csatlakozáshoz."
|
"we_sent_invite_link_description": "Kérje meg az összes meghívottat, hogy nézzék meg a beérkező leveleiket. Kattintsanak a hivatkozásra a csapathoz való csatlakozáshoz."
|
||||||
},
|
},
|
||||||
"team_environment": {
|
"team_environment": {
|
||||||
"deleted": "Környezet törölve",
|
"deleted": "Environment Deleted",
|
||||||
"duplicate": "Környezet megkettőzve",
|
"duplicate": "Environment Duplicated",
|
||||||
"not_found": "A környezet nem található."
|
"not_found": "Environment not found."
|
||||||
},
|
},
|
||||||
"test": {
|
"test": {
|
||||||
"failed": "teszt sikertelen",
|
"failed": "teszt sikertelen",
|
||||||
@@ -734,9 +732,9 @@
|
|||||||
"url": "URL"
|
"url": "URL"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"change": "Munkaterület váltása",
|
"change": "Change workspace",
|
||||||
"personal": "Saját munkaterület",
|
"personal": "My Workspace",
|
||||||
"team": "Csapat-munkaterület",
|
"team": "Team Workspace",
|
||||||
"title": "Munkaterületek"
|
"title": "Workspaces"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Lihat tautan saya"
|
"view_my_links": "Lihat tautan saya"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Response Body",
|
"body": "Response Body",
|
||||||
"filter_response_body": "Filter body respons JSON (menggunakan sintaks JSONPath)",
|
"filter_response_body": "Filter body respons JSON (menggunakan sintaks JSONPath)",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Waktu",
|
"time": "Waktu",
|
||||||
"title": "Response",
|
"title": "Response",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "Menunggu koneksi",
|
"waiting_for_connection": "Menunggu koneksi",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Corpo della risposta",
|
"body": "Corpo della risposta",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Intestazioni",
|
"headers": "Intestazioni",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Stato",
|
"status": "Stato",
|
||||||
"time": "Tempo impiegato",
|
"time": "Tempo impiegato",
|
||||||
"title": "Risposta",
|
"title": "Risposta",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "In attesa di connessione",
|
"waiting_for_connection": "In attesa di connessione",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "自分のリンクを見る"
|
"view_my_links": "自分のリンクを見る"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "レスポンスボディ",
|
"body": "レスポンスボディ",
|
||||||
"filter_response_body": "JSONレスポンスボディをフィルタ (JSONPathシンタックスを使用)",
|
"filter_response_body": "JSONレスポンスボディをフィルタ (JSONPathシンタックスを使用)",
|
||||||
"headers": "ヘッダー",
|
"headers": "ヘッダー",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "ステータス",
|
"status": "ステータス",
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
"title": "レスポンス",
|
"title": "レスポンス",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "接続を待っています",
|
"waiting_for_connection": "接続を待っています",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "내 링크 보기"
|
"view_my_links": "내 링크 보기"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "응답 본문",
|
"body": "응답 본문",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "헤더",
|
"headers": "헤더",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "상태",
|
"status": "상태",
|
||||||
"time": "시간",
|
"time": "시간",
|
||||||
"title": "제목",
|
"title": "제목",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "연결 대기 중",
|
"waiting_for_connection": "연결 대기 중",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Reactie inhoud",
|
"body": "Reactie inhoud",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Headers",
|
"headers": "Headers",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tijd",
|
"time": "Tijd",
|
||||||
"title": "Antwoord",
|
"title": "Antwoord",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "wachten op verbinding",
|
"waiting_for_connection": "wachten op verbinding",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Svarkropp",
|
"body": "Svarkropp",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Overskrifter",
|
"headers": "Overskrifter",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tid",
|
"time": "Tid",
|
||||||
"title": "Respons",
|
"title": "Respons",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "venter på tilkobling",
|
"waiting_for_connection": "venter på tilkobling",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Ciało odpowiedzi",
|
"body": "Ciało odpowiedzi",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Nagłówki",
|
"headers": "Nagłówki",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Czas",
|
"time": "Czas",
|
||||||
"title": "Odpowiedź",
|
"title": "Odpowiedź",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "oczekiwanie na połączenie",
|
"waiting_for_connection": "oczekiwanie na połączenie",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Corpo de Resposta",
|
"body": "Corpo de Resposta",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Cabeçalhos",
|
"headers": "Cabeçalhos",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tempo",
|
"time": "Tempo",
|
||||||
"title": "Resposta",
|
"title": "Resposta",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "aguardando conexão",
|
"waiting_for_connection": "aguardando conexão",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Corpo de Resposta",
|
"body": "Corpo de Resposta",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Cabeçalhos",
|
"headers": "Cabeçalhos",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tempo",
|
"time": "Tempo",
|
||||||
"title": "Resposta",
|
"title": "Resposta",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "aguardando conexão",
|
"waiting_for_connection": "aguardando conexão",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Vizualizare link-uri"
|
"view_my_links": "Vizualizare link-uri"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Corpul de răspuns",
|
"body": "Corpul de răspuns",
|
||||||
"filter_response_body": "Filtrează corpul răspunsului JSON (folosește sintaxa JSONPath)",
|
"filter_response_body": "Filtrează corpul răspunsului JSON (folosește sintaxa JSONPath)",
|
||||||
"headers": "Anteturi",
|
"headers": "Anteturi",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Stare",
|
"status": "Stare",
|
||||||
"time": "Timp",
|
"time": "Timp",
|
||||||
"title": "Raspuns",
|
"title": "Raspuns",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "Așteptând conexiunea",
|
"waiting_for_connection": "Așteptând conexiunea",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Тело ответа",
|
"body": "Тело ответа",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Заголовки",
|
"headers": "Заголовки",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"time": "Время",
|
"time": "Время",
|
||||||
"title": "Ответ",
|
"title": "Ответ",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "Ожидание соединения",
|
"waiting_for_connection": "Ожидание соединения",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Тело за одговор",
|
"body": "Тело за одговор",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Заглавља",
|
"headers": "Заглавља",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"time": "време",
|
"time": "време",
|
||||||
"title": "Одговор",
|
"title": "Одговор",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "чека везу",
|
"waiting_for_connection": "чека везу",
|
||||||
"xml": "КСМЛ"
|
"xml": "КСМЛ"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Svarskommitté",
|
"body": "Svarskommitté",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Rubriker",
|
"headers": "Rubriker",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Status",
|
"status": "Status",
|
||||||
"time": "Tid",
|
"time": "Tid",
|
||||||
"title": "Svar",
|
"title": "Svar",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "väntar på anslutning",
|
"waiting_for_connection": "väntar på anslutning",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -122,7 +122,7 @@
|
|||||||
"edit": "Koleksiyonu düzenle",
|
"edit": "Koleksiyonu düzenle",
|
||||||
"invalid_name": "Lütfen koleksiyon için geçerli bir ad girin",
|
"invalid_name": "Lütfen koleksiyon için geçerli bir ad girin",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "Collection already in the root",
|
||||||
"moved": "Başarıyla taşındı",
|
"moved": "Moved Successfully",
|
||||||
"my_collections": "Koleksiyonlarım",
|
"my_collections": "Koleksiyonlarım",
|
||||||
"name": "Yeni Koleksiyonum",
|
"name": "Yeni Koleksiyonum",
|
||||||
"name_length_insufficient": "Koleksiyon adı en az 3 karakter uzunluğunda olmalıdır",
|
"name_length_insufficient": "Koleksiyon adı en az 3 karakter uzunluğunda olmalıdır",
|
||||||
@@ -147,7 +147,7 @@
|
|||||||
"remove_team": "Bu takımı silmek istediğinizden emin misiniz?",
|
"remove_team": "Bu takımı silmek istediğinizden emin misiniz?",
|
||||||
"remove_telemetry": "Telemetriden çıkmak istediğinizden emin misiniz?",
|
"remove_telemetry": "Telemetriden çıkmak istediğinizden emin misiniz?",
|
||||||
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
"request_change": "Are you sure you want to discard current request, unsaved changes will be lost.",
|
||||||
"save_unsaved_tab": "Bu sekmede yapılan değişiklikleri kaydetmek istiyor musunuz?",
|
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
||||||
"sync": "Bu çalışma alanını senkronize etmek istediğinizden emin misiniz?"
|
"sync": "Bu çalışma alanını senkronize etmek istediğinizden emin misiniz?"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -368,9 +368,9 @@
|
|||||||
},
|
},
|
||||||
"profile": {
|
"profile": {
|
||||||
"app_settings": "Uygulama ayarları",
|
"app_settings": "Uygulama ayarları",
|
||||||
"default_hopp_displayname": "Adsız Kullanıcı",
|
"default_hopp_displayname": "Unnamed User",
|
||||||
"editor": "Editör",
|
"editor": "Düzenleyici",
|
||||||
"editor_description": "Editörler istekleri ekleyebilir, düzenleyebilir ve silebilir.",
|
"editor_description": "Editors can add, edit, and delete requests.",
|
||||||
"email_verification_mail": "Doğrulama bağlantısı e-postanıza gönderildi. E-postanızı doğrulamak için gelen bağlantıya tıklayınız.",
|
"email_verification_mail": "Doğrulama bağlantısı e-postanıza gönderildi. E-postanızı doğrulamak için gelen bağlantıya tıklayınız.",
|
||||||
"no_permission": "Bu eylemi gerçekleştirmek için gerekli yetkiniz yok.",
|
"no_permission": "Bu eylemi gerçekleştirmek için gerekli yetkiniz yok.",
|
||||||
"owner": "Kurucu",
|
"owner": "Kurucu",
|
||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Yanıt gövdesi",
|
"body": "Yanıt gövdesi",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Başlıklar",
|
"headers": "Başlıklar",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Durum",
|
"status": "Durum",
|
||||||
"time": "Zaman",
|
"time": "Zaman",
|
||||||
"title": "Cevap",
|
"title": "Cevap",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "Bağlantı için bekleniyor",
|
"waiting_for_connection": "Bağlantı için bekleniyor",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "檢視我的連結"
|
"view_my_links": "檢視我的連結"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "回應本體",
|
"body": "回應本體",
|
||||||
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
|
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
|
||||||
"headers": "回應標頭",
|
"headers": "回應標頭",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
"title": "回應",
|
"title": "回應",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "等待連線",
|
"waiting_for_connection": "等待連線",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "Переглянути мої посилання"
|
"view_my_links": "Переглянути мої посилання"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Орган реагування",
|
"body": "Орган реагування",
|
||||||
"filter_response_body": "Фільтр тіла відповідей JSON (використовує синтаксис JSONPath)",
|
"filter_response_body": "Фільтр тіла відповідей JSON (використовує синтаксис JSONPath)",
|
||||||
"headers": "Заголовки",
|
"headers": "Заголовки",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Статус",
|
"status": "Статус",
|
||||||
"time": "Час",
|
"time": "Час",
|
||||||
"title": "Відповідь",
|
"title": "Відповідь",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "очікування підключення",
|
"waiting_for_connection": "очікування підключення",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
"cancel": "Hủy bỏ",
|
"cancel": "Hủy bỏ",
|
||||||
"choose_file": "Chọn một tệp",
|
"choose_file": "Chọn một tệp",
|
||||||
"clear": "Thông thoáng",
|
"clear": "Thông thoáng",
|
||||||
"clear_all": "Quet sạch tất cả",
|
"clear_all": "Quet sạch tât cả",
|
||||||
"close": "Close",
|
"close": "Close",
|
||||||
"connect": "Liên kết",
|
"connect": "Liên kết",
|
||||||
"connecting": "Connecting",
|
"connecting": "Connecting",
|
||||||
@@ -432,7 +432,6 @@
|
|||||||
"view_my_links": "View my links"
|
"view_my_links": "View my links"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
|
||||||
"body": "Cơ quan phản hồi",
|
"body": "Cơ quan phản hồi",
|
||||||
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
"filter_response_body": "Filter JSON response body (uses JSONPath syntax)",
|
||||||
"headers": "Tiêu đề",
|
"headers": "Tiêu đề",
|
||||||
@@ -446,7 +445,6 @@
|
|||||||
"status": "Tình trạng",
|
"status": "Tình trạng",
|
||||||
"time": "Thời gian",
|
"time": "Thời gian",
|
||||||
"title": "Phản ứng",
|
"title": "Phản ứng",
|
||||||
"video": "Video",
|
|
||||||
"waiting_for_connection": "Đang đợi kết nối",
|
"waiting_for_connection": "Đang đợi kết nối",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.4",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 12 KiB After Width: | Height: | Size: 7.9 KiB |
@@ -86,7 +86,6 @@ declare module '@vue/runtime-core' {
|
|||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
||||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
|
||||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
||||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
||||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
||||||
@@ -135,13 +134,11 @@ declare module '@vue/runtime-core' {
|
|||||||
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
IconLucideUsers: typeof import('~icons/lucide/users')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
|
||||||
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
LensesRenderersHTMLLensRenderer: typeof import('./components/lenses/renderers/HTMLLensRenderer.vue')['default']
|
||||||
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
|
LensesRenderersImageLensRenderer: typeof import('./components/lenses/renderers/ImageLensRenderer.vue')['default']
|
||||||
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
|
LensesRenderersJSONLensRenderer: typeof import('./components/lenses/renderers/JSONLensRenderer.vue')['default']
|
||||||
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
|
LensesRenderersPDFLensRenderer: typeof import('./components/lenses/renderers/PDFLensRenderer.vue')['default']
|
||||||
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
|
LensesRenderersRawLensRenderer: typeof import('./components/lenses/renderers/RawLensRenderer.vue')['default']
|
||||||
LensesRenderersVideoLensRenderer: typeof import('./components/lenses/renderers/VideoLensRenderer.vue')['default']
|
|
||||||
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
LensesRenderersXMLLensRenderer: typeof import('./components/lenses/renderers/XMLLensRenderer.vue')['default']
|
||||||
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
LensesResponseBodyRenderer: typeof import('./components/lenses/ResponseBodyRenderer.vue')['default']
|
||||||
ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default']
|
ProfileShortcode: typeof import('./components/profile/Shortcode.vue')['default']
|
||||||
|
|||||||
@@ -44,9 +44,8 @@
|
|||||||
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
class="flex flex-col items-center justify-center p-4 text-secondaryLight"
|
||||||
>
|
>
|
||||||
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
<icon-lucide-search class="pb-2 opacity-75 svg-icons" />
|
||||||
<span class="my-2 text-center flex flex-col">
|
<span class="my-2 text-center">
|
||||||
{{ t("state.nothing_found") }}
|
{{ t("state.nothing_found") }} "{{ filterText }}"
|
||||||
<span class="break-all">"{{ filterText }}"</span>
|
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -284,14 +284,6 @@ const importerAction = async (stepResults: StepReturnValue[]) => {
|
|||||||
emit("import-to-teams", result)
|
emit("import-to-teams", result)
|
||||||
} else {
|
} else {
|
||||||
appendRESTCollections(result)
|
appendRESTCollections(result)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_IMPORT_COLLECTION",
|
|
||||||
importer: importerModule.value!.name,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
fileImported()
|
fileImported()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1 bg-primary">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||||
:style="
|
:style="
|
||||||
|
|||||||
@@ -89,7 +89,6 @@ import {
|
|||||||
import { GQLError } from "~/helpers/backend/GQLClient"
|
import { GQLError } from "~/helpers/backend/GQLClient"
|
||||||
import { computedWithControl } from "@vueuse/core"
|
import { computedWithControl } from "@vueuse/core"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -224,13 +223,6 @@ const saveRequestAs = async () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "my-folder") {
|
} else if (picked.value.pickedType === "my-folder") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
@@ -251,13 +243,6 @@ const saveRequestAs = async () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "my-request") {
|
} else if (picked.value.pickedType === "my-request") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
@@ -279,38 +264,17 @@ const saveRequestAs = async () => {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "teams-collection") {
|
} else if (picked.value.pickedType === "teams-collection") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
updateTeamCollectionOrFolder(picked.value.collectionID, requestUpdated)
|
updateTeamCollectionOrFolder(picked.value.collectionID, requestUpdated)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
} else if (picked.value.pickedType === "teams-folder") {
|
} else if (picked.value.pickedType === "teams-folder") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
|
|
||||||
updateTeamCollectionOrFolder(picked.value.folderID, requestUpdated)
|
updateTeamCollectionOrFolder(picked.value.folderID, requestUpdated)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
} else if (picked.value.pickedType === "teams-request") {
|
} else if (picked.value.pickedType === "teams-request") {
|
||||||
if (!isHoppRESTRequest(requestUpdated))
|
if (!isHoppRESTRequest(requestUpdated))
|
||||||
throw new Error("requestUpdated is not a REST Request")
|
throw new Error("requestUpdated is not a REST Request")
|
||||||
@@ -328,13 +292,6 @@ const saveRequestAs = async () => {
|
|||||||
title: requestUpdated.name,
|
title: requestUpdated.name,
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
updateTeamRequest(picked.value.requestID, data),
|
updateTeamRequest(picked.value.requestID, data),
|
||||||
TE.match(
|
TE.match(
|
||||||
@@ -356,13 +313,6 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated as HoppGQLRequest
|
requestUpdated as HoppGQLRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: false,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "gql-my-folder") {
|
} else if (picked.value.pickedType === "gql-my-folder") {
|
||||||
// TODO: Check for GQL request ?
|
// TODO: Check for GQL request ?
|
||||||
@@ -371,13 +321,6 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated as HoppGQLRequest
|
requestUpdated as HoppGQLRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
} else if (picked.value.pickedType === "gql-my-collection") {
|
} else if (picked.value.pickedType === "gql-my-collection") {
|
||||||
// TODO: Check for GQL request ?
|
// TODO: Check for GQL request ?
|
||||||
@@ -386,13 +329,6 @@ const saveRequestAs = async () => {
|
|||||||
requestUpdated as HoppGQLRequest
|
requestUpdated as HoppGQLRequest
|
||||||
)
|
)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
requestSaved()
|
requestSaved()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col flex-1">
|
<div class="flex flex-col flex-1 bg-primary">
|
||||||
<div
|
<div
|
||||||
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
|
||||||
:style="
|
:style="
|
||||||
|
|||||||
@@ -46,7 +46,6 @@ import { useToast } from "@composables/toast"
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { HoppGQLRequest, makeCollection } from "@hoppscotch/data"
|
import { HoppGQLRequest, makeCollection } from "@hoppscotch/data"
|
||||||
import { addGraphqlCollection } from "~/newstore/collections"
|
import { addGraphqlCollection } from "~/newstore/collections"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -80,13 +79,6 @@ export default defineComponent({
|
|||||||
)
|
)
|
||||||
|
|
||||||
this.hideModal()
|
this.hideModal()
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
isRootCollection: true,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
hideModal() {
|
hideModal() {
|
||||||
this.name = null
|
this.name = null
|
||||||
|
|||||||
@@ -244,14 +244,6 @@ const importFromJSON = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
appendGraphqlCollections(collections)
|
appendGraphqlCollections(collections)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_IMPORT_COLLECTION",
|
|
||||||
importer: "json",
|
|
||||||
workspaceType: "personal",
|
|
||||||
platform: "gql",
|
|
||||||
})
|
|
||||||
|
|
||||||
fileImported()
|
fileImported()
|
||||||
}
|
}
|
||||||
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
reader.readAsText(inputChooseFileToImportFrom.value.files[0])
|
||||||
@@ -265,12 +257,6 @@ const exportJSON = () => {
|
|||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
a.href = url
|
a.href = url
|
||||||
|
|
||||||
platform?.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "json",
|
|
||||||
platform: "gql",
|
|
||||||
})
|
|
||||||
|
|
||||||
// TODO: get uri from meta
|
// TODO: get uri from meta
|
||||||
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a)
|
||||||
|
|||||||
@@ -153,7 +153,6 @@ import IconArchive from "~icons/lucide/archive"
|
|||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
props: {
|
props: {
|
||||||
@@ -286,13 +285,6 @@ export default defineComponent({
|
|||||||
response: "",
|
response: "",
|
||||||
})
|
})
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
platform: "gql",
|
|
||||||
createdNow: true,
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
this.displayModalAddRequest(false)
|
this.displayModalAddRequest(false)
|
||||||
},
|
},
|
||||||
addRequest(payload) {
|
addRequest(payload) {
|
||||||
@@ -302,14 +294,6 @@ export default defineComponent({
|
|||||||
},
|
},
|
||||||
onAddFolder({ name, path }) {
|
onAddFolder({ name, path }) {
|
||||||
addGraphqlFolder(name, path)
|
addGraphqlFolder(name, path)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
isRootCollection: false,
|
|
||||||
platform: "gql",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
this.displayModalAddFolder(false)
|
this.displayModalAddFolder(false)
|
||||||
},
|
},
|
||||||
addFolder(payload) {
|
addFolder(payload) {
|
||||||
|
|||||||
@@ -125,8 +125,8 @@
|
|||||||
@hide-modal="displayModalEditFolder(false)"
|
@hide-modal="displayModalEditFolder(false)"
|
||||||
/>
|
/>
|
||||||
<CollectionsEditRequest
|
<CollectionsEditRequest
|
||||||
v-model="editingRequestName"
|
|
||||||
:show="showModalEditRequest"
|
:show="showModalEditRequest"
|
||||||
|
:model-value="editingRequest ? editingRequest.name : ''"
|
||||||
:loading-state="modalLoadingState"
|
:loading-state="modalLoadingState"
|
||||||
@submit="updateEditingRequest"
|
@submit="updateEditingRequest"
|
||||||
@hide-modal="displayModalEditRequest(false)"
|
@hide-modal="displayModalEditRequest(false)"
|
||||||
@@ -157,7 +157,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, nextTick, PropType, ref, watch } from "vue"
|
import { computed, PropType, ref, watch } from "vue"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { Picked } from "~/helpers/types/HoppPicked"
|
import { Picked } from "~/helpers/types/HoppPicked"
|
||||||
@@ -288,7 +288,6 @@ const editingFolder = ref<
|
|||||||
const editingFolderName = ref<string | null>(null)
|
const editingFolderName = ref<string | null>(null)
|
||||||
const editingFolderPath = ref<string | null>(null)
|
const editingFolderPath = ref<string | null>(null)
|
||||||
const editingRequest = ref<HoppRESTRequest | null>(null)
|
const editingRequest = ref<HoppRESTRequest | null>(null)
|
||||||
const editingRequestName = ref("")
|
|
||||||
const editingRequestIndex = ref<number | null>(null)
|
const editingRequestIndex = ref<number | null>(null)
|
||||||
const editingRequestID = ref<string | null>(null)
|
const editingRequestID = ref<string | null>(null)
|
||||||
|
|
||||||
@@ -599,25 +598,11 @@ const addNewRootCollection = (name: string) => {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
isRootCollection: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
displayModalAdd(false)
|
displayModalAdd(false)
|
||||||
} else if (hasTeamWriteAccess.value) {
|
} else if (hasTeamWriteAccess.value) {
|
||||||
if (!collectionsType.value.selectedTeam) return
|
if (!collectionsType.value.selectedTeam) return
|
||||||
modalLoadingState.value = true
|
modalLoadingState.value = true
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
isRootCollection: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
createNewRootCollection(name, collectionsType.value.selectedTeam.id),
|
createNewRootCollection(name, collectionsType.value.selectedTeam.id),
|
||||||
TE.match(
|
TE.match(
|
||||||
@@ -666,13 +651,6 @@ const onAddRequest = (requestName: string) => {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
workspaceType: "personal",
|
|
||||||
createdNow: true,
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
displayModalAddRequest(false)
|
displayModalAddRequest(false)
|
||||||
} else if (hasTeamWriteAccess.value) {
|
} else if (hasTeamWriteAccess.value) {
|
||||||
const folder = editingFolder.value
|
const folder = editingFolder.value
|
||||||
@@ -688,13 +666,6 @@ const onAddRequest = (requestName: string) => {
|
|||||||
title: requestName,
|
title: requestName,
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
workspaceType: "team",
|
|
||||||
platform: "rest",
|
|
||||||
createdNow: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
createRequestInCollection(folder.id, data),
|
createRequestInCollection(folder.id, data),
|
||||||
TE.match(
|
TE.match(
|
||||||
@@ -740,14 +711,6 @@ const onAddFolder = (folderName: string) => {
|
|||||||
if (collectionsType.value.type === "my-collections") {
|
if (collectionsType.value.type === "my-collections") {
|
||||||
if (!path) return
|
if (!path) return
|
||||||
addRESTFolder(folderName, path)
|
addRESTFolder(folderName, path)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
workspaceType: "personal",
|
|
||||||
isRootCollection: false,
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
displayModalAddFolder(false)
|
displayModalAddFolder(false)
|
||||||
} else if (hasTeamWriteAccess.value) {
|
} else if (hasTeamWriteAccess.value) {
|
||||||
const folder = editingFolder.value
|
const folder = editingFolder.value
|
||||||
@@ -755,13 +718,6 @@ const onAddFolder = (folderName: string) => {
|
|||||||
|
|
||||||
modalLoadingState.value = true
|
modalLoadingState.value = true
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_COLLECTION",
|
|
||||||
workspaceType: "personal",
|
|
||||||
isRootCollection: false,
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
createChildCollection(folderName, folder.id),
|
createChildCollection(folderName, folder.id),
|
||||||
TE.match(
|
TE.match(
|
||||||
@@ -904,7 +860,6 @@ const editRequest = (payload: {
|
|||||||
}) => {
|
}) => {
|
||||||
const { folderPath, requestIndex, request } = payload
|
const { folderPath, requestIndex, request } = payload
|
||||||
editingRequest.value = request
|
editingRequest.value = request
|
||||||
editingRequestName.value = request.name ?? ""
|
|
||||||
if (collectionsType.value.type === "my-collections" && folderPath) {
|
if (collectionsType.value.type === "my-collections" && folderPath) {
|
||||||
editingFolderPath.value = folderPath
|
editingFolderPath.value = folderPath
|
||||||
editingRequestIndex.value = parseInt(requestIndex)
|
editingRequestIndex.value = parseInt(requestIndex)
|
||||||
@@ -938,9 +893,6 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
|
|
||||||
if (possibleActiveTab) {
|
if (possibleActiveTab) {
|
||||||
possibleActiveTab.value.document.request.name = requestUpdated.name
|
possibleActiveTab.value.document.request.name = requestUpdated.name
|
||||||
nextTick(() => {
|
|
||||||
possibleActiveTab.value.document.isDirty = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
displayModalEditRequest(false)
|
displayModalEditRequest(false)
|
||||||
@@ -979,9 +931,6 @@ const updateEditingRequest = (newName: string) => {
|
|||||||
|
|
||||||
if (possibleTab) {
|
if (possibleTab) {
|
||||||
possibleTab.value.document.request.name = requestName
|
possibleTab.value.document.request.name = requestName
|
||||||
nextTick(() => {
|
|
||||||
possibleTab.value.document.isDirty = false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1927,12 +1876,6 @@ const exportData = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const exportJSONCollection = async () => {
|
const exportJSONCollection = async () => {
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "json",
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
await getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
initializeDownloadCollection(collectionJSON.value, null)
|
initializeDownloadCollection(collectionJSON.value, null)
|
||||||
@@ -1944,12 +1887,6 @@ const createCollectionGist = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "gist",
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
creatingGistCollection.value = true
|
creatingGistCollection.value = true
|
||||||
await getJSONCollection()
|
await getJSONCollection()
|
||||||
|
|
||||||
@@ -1980,12 +1917,6 @@ const importToTeams = async (collection: HoppCollection<HoppRESTRequest>[]) => {
|
|||||||
|
|
||||||
importingMyCollections.value = true
|
importingMyCollections.value = true
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
|
||||||
exporter: "import-to-teams",
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
pipe(
|
pipe(
|
||||||
importJSONToTeam(
|
importJSONToTeam(
|
||||||
JSON.stringify(collection),
|
JSON.stringify(collection),
|
||||||
|
|||||||
@@ -190,12 +190,6 @@ const createEnvironmentGist = async () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
toast.success(t("export.gist_created").toString())
|
toast.success(t("export.gist_created").toString())
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_EXPORT_ENVIRONMENT",
|
|
||||||
platform: "rest",
|
|
||||||
})
|
|
||||||
|
|
||||||
window.open(res.data.html_url)
|
window.open(res.data.html_url)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(t("error.something_went_wrong").toString())
|
toast.error(t("error.something_went_wrong").toString())
|
||||||
@@ -255,13 +249,6 @@ const openDialogChooseFileToImportFrom = () => {
|
|||||||
|
|
||||||
const importToTeams = async (content: Environment[]) => {
|
const importToTeams = async (content: Environment[]) => {
|
||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
for (const [i, env] of content.entries()) {
|
for (const [i, env] of content.entries()) {
|
||||||
if (i === content.length - 1) {
|
if (i === content.length - 1) {
|
||||||
await pipe(
|
await pipe(
|
||||||
@@ -314,12 +301,6 @@ const importFromJSON = () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT",
|
|
||||||
platform: "rest",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
const reader = new FileReader()
|
const reader = new FileReader()
|
||||||
|
|
||||||
reader.onload = ({ target }) => {
|
reader.onload = ({ target }) => {
|
||||||
@@ -371,7 +352,6 @@ const importFromPostman = ({
|
|||||||
const environment: Environment = { name, variables: [] }
|
const environment: Environment = { name, variables: [] }
|
||||||
values.forEach(({ key, value }) => environment.variables.push({ key, value }))
|
values.forEach(({ key, value }) => environment.variables.push({ key, value }))
|
||||||
const environments = [environment]
|
const environments = [environment]
|
||||||
|
|
||||||
importFromHoppscotch(environments)
|
importFromHoppscotch(environments)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -49,7 +49,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedEnvTab"
|
v-model="selectedEnvTab"
|
||||||
styles="sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary"
|
styles="sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary"
|
||||||
render-inactive-tabs
|
render-inactive-tabs
|
||||||
>
|
>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -97,7 +97,7 @@
|
|||||||
<HoppSmartSpinner class="my-4" />
|
<HoppSmartSpinner class="my-4" />
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="isTeamSelected" class="flex flex-col">
|
<div v-if="isTeamSelected" class="flex flex-col">
|
||||||
<HoppSmartItem
|
<HoppSmartItem
|
||||||
v-for="(gen, index) in teamEnvironmentList"
|
v-for="(gen, index) in teamEnvironmentList"
|
||||||
:key="`gen-team-${index}`"
|
:key="`gen-team-${index}`"
|
||||||
@@ -161,14 +161,10 @@ import {
|
|||||||
selectedEnvironmentIndex$,
|
selectedEnvironmentIndex$,
|
||||||
setSelectedEnvironmentIndex,
|
setSelectedEnvironmentIndex,
|
||||||
} from "~/newstore/environments"
|
} from "~/newstore/environments"
|
||||||
import { changeWorkspace, workspaceStatus$ } from "~/newstore/workspace"
|
import { workspaceStatus$ } from "~/newstore/workspace"
|
||||||
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
import TeamEnvironmentAdapter from "~/helpers/teams/TeamEnvironmentAdapter"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
|
||||||
import TeamListAdapter from "~/helpers/teams/TeamListAdapter"
|
|
||||||
import { useLocalState } from "~/newstore/localstate"
|
|
||||||
import { onLoggedIn } from "~/composables/auth"
|
|
||||||
import { GetMyTeamsQuery } from "~/helpers/backend/graphql"
|
|
||||||
|
|
||||||
const breakpoints = useBreakpoints(breakpointsTailwind)
|
const breakpoints = useBreakpoints(breakpointsTailwind)
|
||||||
const mdAndLarger = breakpoints.greater("md")
|
const mdAndLarger = breakpoints.greater("md")
|
||||||
@@ -217,38 +213,6 @@ watch(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// TeamList-Adapter
|
|
||||||
const teamListAdapter = new TeamListAdapter(true)
|
|
||||||
const myTeams = useReadonlyStream(teamListAdapter.teamList$, null)
|
|
||||||
const teamListFetched = ref(false)
|
|
||||||
const REMEMBERED_TEAM_ID = useLocalState("REMEMBERED_TEAM_ID")
|
|
||||||
|
|
||||||
onLoggedIn(() => {
|
|
||||||
!teamListAdapter.isInitialized && teamListAdapter.initialize()
|
|
||||||
})
|
|
||||||
|
|
||||||
const switchToTeamWorkspace = (team: GetMyTeamsQuery["myTeams"][number]) => {
|
|
||||||
REMEMBERED_TEAM_ID.value = team.id
|
|
||||||
changeWorkspace({
|
|
||||||
teamID: team.id,
|
|
||||||
teamName: team.name,
|
|
||||||
type: "team",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => myTeams.value,
|
|
||||||
(newTeams) => {
|
|
||||||
if (newTeams && !teamListFetched.value) {
|
|
||||||
teamListFetched.value = true
|
|
||||||
if (REMEMBERED_TEAM_ID.value) {
|
|
||||||
const team = newTeams.find((t) => t.id === REMEMBERED_TEAM_ID.value)
|
|
||||||
if (team) switchToTeamWorkspace(team)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const selectedEnv = computed(() => {
|
const selectedEnv = computed(() => {
|
||||||
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
if (selectedEnvironmentIndex.value.type === "MY_ENV") {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -148,7 +148,6 @@ import { useToast } from "@composables/toast"
|
|||||||
import { useReadonlyStream } from "@composables/stream"
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useColorMode } from "@composables/theming"
|
import { useColorMode } from "@composables/theming"
|
||||||
import { environmentsStore } from "~/newstore/environments"
|
import { environmentsStore } from "~/newstore/environments"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
id: number
|
id: number
|
||||||
@@ -312,11 +311,6 @@ const saveEnvironment = () => {
|
|||||||
index: envList.value.length - 1,
|
index: envList.value.length - 1,
|
||||||
})
|
})
|
||||||
toast.success(`${t("environment.created")}`)
|
toast.success(`${t("environment.created")}`)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_ENVIRONMENT",
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
} else if (props.editingEnvironmentIndex === "Global") {
|
} else if (props.editingEnvironmentIndex === "Global") {
|
||||||
// Editing the Global environment
|
// Editing the Global environment
|
||||||
setGlobalEnvVariables(environmentUpdated.variables)
|
setGlobalEnvVariables(environmentUpdated.variables)
|
||||||
|
|||||||
@@ -156,7 +156,6 @@ import IconTrash from "~icons/lucide/trash"
|
|||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import IconDone from "~icons/lucide/check"
|
import IconDone from "~icons/lucide/check"
|
||||||
import IconPlus from "~icons/lucide/plus"
|
import IconPlus from "~icons/lucide/plus"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
type EnvironmentVariable = {
|
type EnvironmentVariable = {
|
||||||
id: number
|
id: number
|
||||||
@@ -295,11 +294,6 @@ const saveEnvironment = async () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
if (props.action === "new") {
|
if (props.action === "new") {
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_ENVIRONMENT",
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
await pipe(
|
await pipe(
|
||||||
createTeamEnvironment(
|
createTeamEnvironment(
|
||||||
JSON.stringify(filterdVariables),
|
JSON.stringify(filterdVariables),
|
||||||
|
|||||||
@@ -143,51 +143,33 @@
|
|||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="basicUsername"
|
v-model="basicUsername"
|
||||||
:environment-highlights="false"
|
|
||||||
:placeholder="t('authorization.username')"
|
:placeholder="t('authorization.username')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput
|
||||||
v-model="basicPassword"
|
v-model="basicPassword"
|
||||||
:environment-highlights="false"
|
|
||||||
:placeholder="t('authorization.password')"
|
:placeholder="t('authorization.password')"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="authType === 'bearer'">
|
<div v-if="authType === 'bearer'">
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput v-model="bearerToken" placeholder="Token" />
|
||||||
v-model="bearerToken"
|
|
||||||
:environment-highlights="false"
|
|
||||||
placeholder="Token"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="authType === 'oauth-2'">
|
<div v-if="authType === 'oauth-2'">
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput v-model="oauth2Token" placeholder="Token" />
|
||||||
v-model="oauth2Token"
|
|
||||||
:environment-highlights="false"
|
|
||||||
placeholder="Token"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<HttpOAuth2Authorization />
|
<HttpOAuth2Authorization />
|
||||||
</div>
|
</div>
|
||||||
<div v-if="authType === 'api-key'">
|
<div v-if="authType === 'api-key'">
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput v-model="apiKey" placeholder="Key" />
|
||||||
v-model="apiKey"
|
|
||||||
:environment-highlights="false"
|
|
||||||
placeholder="Key"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 border-b border-dividerLight">
|
<div class="flex flex-1 border-b border-dividerLight">
|
||||||
<SmartEnvInput
|
<SmartEnvInput v-model="apiValue" placeholder="Value" />
|
||||||
v-model="apiValue"
|
|
||||||
:environment-highlights="false"
|
|
||||||
placeholder="Value"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center border-b border-dividerLight">
|
<div class="flex items-center border-b border-dividerLight">
|
||||||
<span class="flex items-center">
|
<span class="flex items-center">
|
||||||
|
|||||||
@@ -17,7 +17,6 @@
|
|||||||
<HoppButtonPrimary
|
<HoppButtonPrimary
|
||||||
id="get"
|
id="get"
|
||||||
name="get"
|
name="get"
|
||||||
:loading="isLoading"
|
|
||||||
:label="!connected ? t('action.connect') : t('action.disconnect')"
|
:label="!connected ? t('action.connect') : t('action.disconnect')"
|
||||||
class="w-32"
|
class="w-32"
|
||||||
@click="onConnectClick"
|
@click="onConnectClick"
|
||||||
@@ -32,12 +31,7 @@ import { GQLConnection } from "~/helpers/GQLConnection"
|
|||||||
import { getCurrentStrategyID } from "~/helpers/network"
|
import { getCurrentStrategyID } from "~/helpers/network"
|
||||||
import { useReadonlyStream, useStream } from "@composables/stream"
|
import { useReadonlyStream, useStream } from "@composables/stream"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import {
|
import { gqlHeaders$, gqlURL$, setGQLURL } from "~/newstore/GQLSession"
|
||||||
gqlAuth$,
|
|
||||||
gqlHeaders$,
|
|
||||||
gqlURL$,
|
|
||||||
setGQLURL,
|
|
||||||
} from "~/newstore/GQLSession"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -46,21 +40,15 @@ const props = defineProps<{
|
|||||||
}>()
|
}>()
|
||||||
|
|
||||||
const connected = useReadonlyStream(props.conn.connected$, false)
|
const connected = useReadonlyStream(props.conn.connected$, false)
|
||||||
const isLoading = useReadonlyStream(props.conn.isLoading$, false)
|
|
||||||
const headers = useReadonlyStream(gqlHeaders$, [])
|
const headers = useReadonlyStream(gqlHeaders$, [])
|
||||||
const auth = useReadonlyStream(gqlAuth$, {
|
|
||||||
authType: "none",
|
|
||||||
authActive: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
const url = useStream(gqlURL$, "", setGQLURL)
|
const url = useStream(gqlURL$, "", setGQLURL)
|
||||||
|
|
||||||
const onConnectClick = () => {
|
const onConnectClick = () => {
|
||||||
if (!connected.value) {
|
if (!connected.value) {
|
||||||
props.conn.connect(url.value, headers.value as any, auth.value)
|
props.conn.connect(url.value, headers.value as any)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "graphql-schema",
|
platform: "graphql-schema",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: getCurrentStrategyID(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -748,8 +748,7 @@ const runQuery = async () => {
|
|||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "graphql-query",
|
platform: "graphql-query",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: getCurrentStrategyID(),
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ import { GQLHistoryEntry } from "~/newstore/history"
|
|||||||
import { shortDateTime } from "~/helpers/utils/date"
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
|
||||||
import IconStar from "~icons/lucide/star"
|
import IconStar from "~icons/lucide/star"
|
||||||
import IconStarOff from "~icons/hopp/star-off"
|
import IconStarOff from "~icons/lucide/star-off"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
import IconMinimize2 from "~icons/lucide/minimize-2"
|
import IconMinimize2 from "~icons/lucide/minimize-2"
|
||||||
import IconMaximize2 from "~icons/lucide/maximize-2"
|
import IconMaximize2 from "~icons/lucide/maximize-2"
|
||||||
|
|||||||
@@ -72,11 +72,9 @@
|
|||||||
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary truncate"
|
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary"
|
||||||
>
|
>
|
||||||
<icon-lucide-chevron-right
|
<icon-lucide-chevron-right class="mr-2 indicator" />
|
||||||
class="mr-2 indicator flex flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<span
|
<span
|
||||||
:class="[
|
:class="[
|
||||||
{ 'capitalize-first': groupSelection === 'TIME' },
|
{ 'capitalize-first': groupSelection === 'TIME' },
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import { RESTHistoryEntry } from "~/newstore/history"
|
|||||||
import { shortDateTime } from "~/helpers/utils/date"
|
import { shortDateTime } from "~/helpers/utils/date"
|
||||||
|
|
||||||
import IconStar from "~icons/lucide/star"
|
import IconStar from "~icons/lucide/star"
|
||||||
import IconStarOff from "~icons/hopp/star-off"
|
import IconStarOff from "~icons/lucide/star-off"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -165,7 +165,6 @@ import IconCheck from "~icons/lucide/check"
|
|||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
import IconWrapText from "~icons/lucide/wrap-text"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||||
import cloneDeep from "lodash-es/cloneDeep"
|
import cloneDeep from "lodash-es/cloneDeep"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -249,10 +248,6 @@ watch(
|
|||||||
(goingToShow) => {
|
(goingToShow) => {
|
||||||
if (goingToShow) {
|
if (goingToShow) {
|
||||||
request.value = cloneDeep(currentActiveTab.value.document.request)
|
request.value = cloneDeep(currentActiveTab.value.document.request)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_REST_CODEGEN_OPENED",
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -338,7 +338,7 @@ watch(workingHeaders, (headersList) => {
|
|||||||
|
|
||||||
// Sync logic between headers and working/bulk headers
|
// Sync logic between headers and working/bulk headers
|
||||||
watch(
|
watch(
|
||||||
() => request.value.headers,
|
request.value.headers,
|
||||||
(newHeadersList) => {
|
(newHeadersList) => {
|
||||||
// Sync should overwrite working headers
|
// Sync should overwrite working headers
|
||||||
const filteredWorkingHeaders = pipe(
|
const filteredWorkingHeaders = pipe(
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ import IconClipboard from "~icons/lucide/clipboard"
|
|||||||
import IconCheck from "~icons/lucide/check"
|
import IconCheck from "~icons/lucide/check"
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
import IconTrash2 from "~icons/lucide/trash-2"
|
||||||
import { currentActiveTab } from "~/helpers/rest/tab"
|
import { currentActiveTab } from "~/helpers/rest/tab"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -145,10 +144,6 @@ const handleImport = () => {
|
|||||||
try {
|
try {
|
||||||
const req = parseCurlToHoppRESTReq(text)
|
const req = parseCurlToHoppRESTReq(text)
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_REST_IMPORT_CURL",
|
|
||||||
})
|
|
||||||
|
|
||||||
currentActiveTab.value.document.request = req
|
currentActiveTab.value.document.request = req
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
|
|||||||
@@ -217,7 +217,6 @@
|
|||||||
@hide-modal="showCodegenModal = false"
|
@hide-modal="showCodegenModal = false"
|
||||||
/>
|
/>
|
||||||
<CollectionsSaveRequest
|
<CollectionsSaveRequest
|
||||||
v-if="showSaveRequestModal"
|
|
||||||
mode="rest"
|
mode="rest"
|
||||||
:show="showSaveRequestModal"
|
:show="showSaveRequestModal"
|
||||||
@hide-modal="showSaveRequestModal = false"
|
@hide-modal="showSaveRequestModal = false"
|
||||||
@@ -324,8 +323,7 @@ const newSendRequest = async () => {
|
|||||||
loading.value = true
|
loading.value = true
|
||||||
|
|
||||||
// Log the request run into analytics
|
// Log the request run into analytics
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "rest",
|
platform: "rest",
|
||||||
strategy: getCurrentStrategyID(),
|
strategy: getCurrentStrategyID(),
|
||||||
})
|
})
|
||||||
@@ -447,11 +445,6 @@ const copyRequest = async () => {
|
|||||||
shareLink.value = ""
|
shareLink.value = ""
|
||||||
fetchingShareLink.value = true
|
fetchingShareLink.value = true
|
||||||
const shortcodeResult = await createShortcode(tab.value.document.request)()
|
const shortcodeResult = await createShortcode(tab.value.document.request)()
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SHORTCODE_CREATED",
|
|
||||||
})
|
|
||||||
|
|
||||||
if (E.isLeft(shortcodeResult)) {
|
if (E.isLeft(shortcodeResult)) {
|
||||||
toast.error(`${shortcodeResult.left.error}`)
|
toast.error(`${shortcodeResult.left.error}`)
|
||||||
shareLink.value = `${t("error.something_went_wrong")}`
|
shareLink.value = `${t("error.something_went_wrong")}`
|
||||||
@@ -522,14 +515,6 @@ const saveRequest = () => {
|
|||||||
editRESTRequest(saveCtx.folderPath, saveCtx.requestIndex, req)
|
editRESTRequest(saveCtx.folderPath, saveCtx.requestIndex, req)
|
||||||
|
|
||||||
tab.value.document.isDirty = false
|
tab.value.document.isDirty = false
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
platform: "rest",
|
|
||||||
createdNow: false,
|
|
||||||
workspaceType: "personal",
|
|
||||||
})
|
|
||||||
|
|
||||||
toast.success(`${t("request.saved")}`)
|
toast.success(`${t("request.saved")}`)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
tab.value.document.saveContext = undefined
|
tab.value.document.saveContext = undefined
|
||||||
@@ -540,13 +525,6 @@ const saveRequest = () => {
|
|||||||
|
|
||||||
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
// TODO: handle error case (NOTE: overwriteRequestTeams is async)
|
||||||
try {
|
try {
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SAVE_REQUEST",
|
|
||||||
platform: "rest",
|
|
||||||
createdNow: false,
|
|
||||||
workspaceType: "team",
|
|
||||||
})
|
|
||||||
|
|
||||||
runMutation(UpdateRequestDocument, {
|
runMutation(UpdateRequestDocument, {
|
||||||
requestID: saveCtx.requestID,
|
requestID: saveCtx.requestID,
|
||||||
data: {
|
data: {
|
||||||
|
|||||||
@@ -54,8 +54,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { useVModel } from "@vueuse/core"
|
import { computed, ref, watch } from "vue"
|
||||||
import { computed, ref } from "vue"
|
|
||||||
|
|
||||||
export type RequestOptionTabs =
|
export type RequestOptionTabs =
|
||||||
| "params"
|
| "params"
|
||||||
@@ -71,7 +70,15 @@ const emit = defineEmits<{
|
|||||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const request = useVModel(props, "modelValue", emit)
|
const request = ref(props.modelValue)
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => request.value,
|
||||||
|
(newVal) => {
|
||||||
|
emit("update:modelValue", newVal)
|
||||||
|
},
|
||||||
|
{ deep: true }
|
||||||
|
)
|
||||||
|
|
||||||
const selectedRealtimeTab = ref<RequestOptionTabs>("params")
|
const selectedRealtimeTab = ref<RequestOptionTabs>("params")
|
||||||
|
|
||||||
|
|||||||
@@ -28,11 +28,9 @@
|
|||||||
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
class="flex items-center justify-between flex-1 min-w-0 transition cursor-pointer focus:outline-none text-secondaryLight text-tiny group"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary truncate"
|
class="inline-flex items-center justify-center px-4 py-2 transition group-hover:text-secondary"
|
||||||
>
|
>
|
||||||
<icon-lucide-chevron-right
|
<icon-lucide-chevron-right class="mr-2 indicator" />
|
||||||
class="mr-2 indicator flex flex-shrink-0"
|
|
||||||
/>
|
|
||||||
<span class="truncate capitalize-first">
|
<span class="truncate capitalize-first">
|
||||||
{{ t("environment.title") }}
|
{{ t("environment.title") }}
|
||||||
</span>
|
</span>
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ const props = withDefaults(
|
|||||||
envs?: { key: string; value: string; source: string }[] | null
|
envs?: { key: string; value: string; source: string }[] | null
|
||||||
focus?: boolean
|
focus?: boolean
|
||||||
selectTextOnMount?: boolean
|
selectTextOnMount?: boolean
|
||||||
environmentHighlights?: boolean
|
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
}>(),
|
}>(),
|
||||||
{
|
{
|
||||||
@@ -54,7 +53,6 @@ const props = withDefaults(
|
|||||||
envs: null,
|
envs: null,
|
||||||
focus: false,
|
focus: false,
|
||||||
readonly: false,
|
readonly: false,
|
||||||
environmentHighlights: true,
|
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -144,7 +142,7 @@ const initView = (el: any) => {
|
|||||||
tooltips({
|
tooltips({
|
||||||
position: "absolute",
|
position: "absolute",
|
||||||
}),
|
}),
|
||||||
props.environmentHighlights ? envTooltipPlugin : [],
|
envTooltipPlugin,
|
||||||
placeholderExt(props.placeholder),
|
placeholderExt(props.placeholder),
|
||||||
EditorView.domEventHandlers({
|
EditorView.domEventHandlers({
|
||||||
paste(ev) {
|
paste(ev) {
|
||||||
|
|||||||
@@ -44,7 +44,6 @@ import { createTeam } from "~/helpers/backend/mutations/Team"
|
|||||||
import { TeamNameCodec } from "~/helpers/backend/types/TeamName"
|
import { TeamNameCodec } from "~/helpers/backend/types/TeamName"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -69,12 +68,6 @@ const addNewTeam = async () => {
|
|||||||
TE.fromEither,
|
TE.fromEither,
|
||||||
TE.mapLeft(() => "invalid_name" as const),
|
TE.mapLeft(() => "invalid_name" as const),
|
||||||
TE.chainW(createTeam),
|
TE.chainW(createTeam),
|
||||||
TE.chainFirstIOK(
|
|
||||||
() => () =>
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_CREATE_TEAM",
|
|
||||||
})
|
|
||||||
),
|
|
||||||
TE.match(
|
TE.match(
|
||||||
(err) => {
|
(err) => {
|
||||||
// err is of type "invalid_name" | GQLError<Err>
|
// err is of type "invalid_name" | GQLError<Err>
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ export class GQLConnection {
|
|||||||
|
|
||||||
private timeoutSubscription: any
|
private timeoutSubscription: any
|
||||||
|
|
||||||
public connect(url: string, headers: GQLHeader[], auth: HoppGQLAuth) {
|
public connect(url: string, headers: GQLHeader[]) {
|
||||||
if (this.connected$.value) {
|
if (this.connected$.value) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"A connection is already running. Close it before starting another."
|
"A connection is already running. Close it before starting another."
|
||||||
@@ -110,7 +110,7 @@ export class GQLConnection {
|
|||||||
this.connected$.next(true)
|
this.connected$.next(true)
|
||||||
|
|
||||||
const poll = async () => {
|
const poll = async () => {
|
||||||
await this.getSchema(url, headers, auth)
|
await this.getSchema(url, headers)
|
||||||
this.timeoutSubscription = setTimeout(() => {
|
this.timeoutSubscription = setTimeout(() => {
|
||||||
poll()
|
poll()
|
||||||
}, GQL_SCHEMA_POLL_INTERVAL)
|
}, GQL_SCHEMA_POLL_INTERVAL)
|
||||||
@@ -135,11 +135,7 @@ export class GQLConnection {
|
|||||||
this.schema$.next(null)
|
this.schema$.next(null)
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getSchema(
|
private async getSchema(url: string, headers: GQLHeader[]) {
|
||||||
url: string,
|
|
||||||
reqHeaders: GQLHeader[],
|
|
||||||
auth: HoppGQLAuth
|
|
||||||
) {
|
|
||||||
try {
|
try {
|
||||||
this.isLoading$.next(true)
|
this.isLoading$.next(true)
|
||||||
|
|
||||||
@@ -147,38 +143,10 @@ export class GQLConnection {
|
|||||||
query: getIntrospectionQuery(),
|
query: getIntrospectionQuery(),
|
||||||
})
|
})
|
||||||
|
|
||||||
const headers = reqHeaders.filter((x) => x.active && x.key !== "")
|
|
||||||
|
|
||||||
// TODO: Support a better b64 implementation than btoa ?
|
|
||||||
if (auth.authType === "basic") {
|
|
||||||
const username = auth.username
|
|
||||||
const password = auth.password
|
|
||||||
|
|
||||||
headers.push({
|
|
||||||
active: true,
|
|
||||||
key: "Authorization",
|
|
||||||
value: `Basic ${btoa(`${username}:${password}`)}`,
|
|
||||||
})
|
|
||||||
} else if (auth.authType === "bearer" || auth.authType === "oauth-2") {
|
|
||||||
headers.push({
|
|
||||||
active: true,
|
|
||||||
key: "Authorization",
|
|
||||||
value: `Bearer ${auth.token}`,
|
|
||||||
})
|
|
||||||
} else if (auth.authType === "api-key") {
|
|
||||||
const { key, value, addTo } = auth
|
|
||||||
|
|
||||||
if (addTo === "Headers") {
|
|
||||||
headers.push({
|
|
||||||
active: true,
|
|
||||||
key,
|
|
||||||
value,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const finalHeaders: Record<string, string> = {}
|
const finalHeaders: Record<string, string> = {}
|
||||||
headers.forEach((x) => (finalHeaders[x.key] = x.value))
|
headers
|
||||||
|
.filter((x) => x.active && x.key !== "")
|
||||||
|
.forEach((x) => (finalHeaders[x.key] = x.value))
|
||||||
|
|
||||||
const reqOptions = {
|
const reqOptions = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
|||||||
@@ -6,18 +6,14 @@ import { isJSONContentType } from "./utils/contenttypes"
|
|||||||
* Handles translations for all the hopp.io REST Shareable URL params
|
* Handles translations for all the hopp.io REST Shareable URL params
|
||||||
*/
|
*/
|
||||||
export function translateExtURLParams(
|
export function translateExtURLParams(
|
||||||
urlParams: Record<string, any>,
|
urlParams: Record<string, any>
|
||||||
initialReq?: HoppRESTRequest
|
|
||||||
): HoppRESTRequest {
|
): HoppRESTRequest {
|
||||||
if (urlParams.v) return parseV1ExtURL(urlParams, initialReq)
|
if (urlParams.v) return parseV1ExtURL(urlParams)
|
||||||
else return parseV0ExtURL(urlParams, initialReq)
|
else return parseV0ExtURL(urlParams)
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseV0ExtURL(
|
function parseV0ExtURL(urlParams: Record<string, any>): HoppRESTRequest {
|
||||||
urlParams: Record<string, any>,
|
const resolvedReq = getDefaultRESTRequest()
|
||||||
initialReq?: HoppRESTRequest
|
|
||||||
): HoppRESTRequest {
|
|
||||||
const resolvedReq = initialReq ?? getDefaultRESTRequest()
|
|
||||||
|
|
||||||
if (urlParams.method && typeof urlParams.method === "string") {
|
if (urlParams.method && typeof urlParams.method === "string") {
|
||||||
resolvedReq.method = urlParams.method
|
resolvedReq.method = urlParams.method
|
||||||
@@ -93,11 +89,8 @@ function parseV0ExtURL(
|
|||||||
return resolvedReq
|
return resolvedReq
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseV1ExtURL(
|
function parseV1ExtURL(urlParams: Record<string, any>): HoppRESTRequest {
|
||||||
urlParams: Record<string, any>,
|
const resolvedReq = getDefaultRESTRequest()
|
||||||
initialReq?: HoppRESTRequest
|
|
||||||
): HoppRESTRequest {
|
|
||||||
const resolvedReq = initialReq ?? getDefaultRESTRequest()
|
|
||||||
|
|
||||||
if (urlParams.headers && typeof urlParams.headers === "string") {
|
if (urlParams.headers && typeof urlParams.headers === "string") {
|
||||||
resolvedReq.headers = JSON.parse(urlParams.headers)
|
resolvedReq.headers = JSON.parse(urlParams.headers)
|
||||||
|
|||||||
@@ -71,11 +71,9 @@ const parseURL = (urlText: string | number) =>
|
|||||||
* @returns URL object
|
* @returns URL object
|
||||||
*/
|
*/
|
||||||
export function getURLObject(parsedArguments: parser.Arguments) {
|
export function getURLObject(parsedArguments: parser.Arguments) {
|
||||||
const location = parsedArguments.location ?? undefined
|
|
||||||
|
|
||||||
return pipe(
|
return pipe(
|
||||||
// contains raw url strings
|
// contains raw url strings
|
||||||
[...parsedArguments._.slice(1), location],
|
parsedArguments._.slice(1),
|
||||||
A.findFirstMap(parseURL),
|
A.findFirstMap(parseURL),
|
||||||
// no url found
|
// no url found
|
||||||
O.getOrElse(() => new URL(defaultRESTReq.endpoint))
|
O.getOrElse(() => new URL(defaultRESTReq.endpoint))
|
||||||
|
|||||||
@@ -105,8 +105,7 @@ export class MQTTConnection {
|
|||||||
this.handleError(e)
|
this.handleError(e)
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "mqtt",
|
platform: "mqtt",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,8 +113,7 @@ export class SIOConnection {
|
|||||||
this.handleError(error, "CONNECTION")
|
this.handleError(error, "CONNECTION")
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "socketio",
|
platform: "socketio",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -63,8 +63,7 @@ export class SSEConnection {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "sse",
|
platform: "sse",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,8 +71,7 @@ export class WSConnection {
|
|||||||
this.handleError(error as SyntaxError)
|
this.handleError(error as SyntaxError)
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logHoppRequestRunToAnalytics({
|
||||||
type: "HOPP_REQUEST_RUN",
|
|
||||||
platform: "wss",
|
platform: "wss",
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import { refWithControl } from "@vueuse/core"
|
|||||||
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
import { HoppRESTResponse } from "../types/HoppRESTResponse"
|
||||||
import { getDefaultRESTRequest } from "./default"
|
import { getDefaultRESTRequest } from "./default"
|
||||||
import { HoppTestResult } from "../types/HoppTestResult"
|
import { HoppTestResult } from "../types/HoppTestResult"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
export type HoppRESTTab = {
|
export type HoppRESTTab = {
|
||||||
id: string
|
id: string
|
||||||
@@ -148,10 +147,6 @@ export function createNewTab(document: HoppRESTDocument, switchToIt = true) {
|
|||||||
currentTabID.value = id
|
currentTabID.value = id
|
||||||
}
|
}
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_REST_NEW_TAB_OPENED",
|
|
||||||
})
|
|
||||||
|
|
||||||
return tab
|
return tab
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -275,4 +275,7 @@ export const gqlResponse$ = gqlSessionStore.subject$.pipe(
|
|||||||
distinctUntilChanged()
|
distinctUntilChanged()
|
||||||
)
|
)
|
||||||
|
|
||||||
export const gqlAuth$ = gqlSessionStore.subject$.pipe(pluck("request", "auth"))
|
export const gqlAuth$ = gqlSessionStore.subject$.pipe(
|
||||||
|
pluck("request", "auth"),
|
||||||
|
distinctUntilChanged()
|
||||||
|
)
|
||||||
|
|||||||
@@ -47,8 +47,6 @@ import {
|
|||||||
loadTabsFromPersistedState,
|
loadTabsFromPersistedState,
|
||||||
persistableTabState,
|
persistableTabState,
|
||||||
} from "~/helpers/rest/tab"
|
} from "~/helpers/rest/tab"
|
||||||
import { debounceTime } from "rxjs"
|
|
||||||
import { gqlSessionStore, setGQLSession } from "./GQLSession"
|
|
||||||
|
|
||||||
function checkAndMigrateOldSettings() {
|
function checkAndMigrateOldSettings() {
|
||||||
if (window.localStorage.getItem("selectedEnvIndex")) {
|
if (window.localStorage.getItem("selectedEnvIndex")) {
|
||||||
@@ -335,35 +333,12 @@ export function setupRESTTabsPersistence() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
// temporary persistence for GQL session
|
|
||||||
export function setupGQLPersistence() {
|
|
||||||
try {
|
|
||||||
const state = window.localStorage.getItem("gqlState")
|
|
||||||
if (state) {
|
|
||||||
const data = JSON.parse(state)
|
|
||||||
data["schema"] = ""
|
|
||||||
data["response"] = ""
|
|
||||||
setGQLSession(data)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(
|
|
||||||
`Failed parsing persisted GraphQL state, state:`,
|
|
||||||
window.localStorage.getItem("gqlState")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
gqlSessionStore.subject$.pipe(debounceTime(500)).subscribe((state) => {
|
|
||||||
window.localStorage.setItem("gqlState", JSON.stringify(state))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export function setupLocalPersistence() {
|
export function setupLocalPersistence() {
|
||||||
checkAndMigrateOldSettings()
|
checkAndMigrateOldSettings()
|
||||||
|
|
||||||
setupLocalStatePersistence()
|
setupLocalStatePersistence()
|
||||||
setupSettingsPersistence()
|
setupSettingsPersistence()
|
||||||
setupRESTTabsPersistence()
|
setupRESTTabsPersistence()
|
||||||
setupGQLPersistence()
|
|
||||||
setupHistoryPersistence()
|
setupHistoryPersistence()
|
||||||
setupCollectionsPersistence()
|
setupCollectionsPersistence()
|
||||||
setupGlobalEnvsPersistence()
|
setupGlobalEnvsPersistence()
|
||||||
|
|||||||
@@ -14,10 +14,12 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { usePageHead } from "@composables/head"
|
import { computed, onBeforeUnmount, watch } from "vue"
|
||||||
|
import { useReadonlyStream } from "@composables/stream"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
|
import { usePageHead } from "@composables/head"
|
||||||
|
import { startPageProgress, completePageProgress } from "@modules/loadingbar"
|
||||||
import { GQLConnection } from "@helpers/GQLConnection"
|
import { GQLConnection } from "@helpers/GQLConnection"
|
||||||
import { computed, onBeforeUnmount } from "vue"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
@@ -26,10 +28,16 @@ usePageHead({
|
|||||||
})
|
})
|
||||||
|
|
||||||
const gqlConn = new GQLConnection()
|
const gqlConn = new GQLConnection()
|
||||||
|
const isLoading = useReadonlyStream(gqlConn.isLoading$, false)
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
if (gqlConn.connected$.value) {
|
if (gqlConn.connected$.value) {
|
||||||
gqlConn.disconnect()
|
gqlConn.disconnect()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
watch(isLoading, () => {
|
||||||
|
if (isLoading.value) startPageProgress()
|
||||||
|
else completePageProgress()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -79,9 +79,8 @@
|
|||||||
@resolve="onResolveConfirmSaveTab"
|
@resolve="onResolveConfirmSaveTab"
|
||||||
/>
|
/>
|
||||||
<CollectionsSaveRequest
|
<CollectionsSaveRequest
|
||||||
v-if="savingRequest"
|
|
||||||
mode="rest"
|
|
||||||
:show="savingRequest"
|
:show="savingRequest"
|
||||||
|
:mode="'rest'"
|
||||||
@hide-modal="onSaveModalClose"
|
@hide-modal="onSaveModalClose"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -154,11 +153,8 @@ function bindRequestToURLParams() {
|
|||||||
// If query params are empty, or contains code or error param (these are from Oauth Redirect)
|
// If query params are empty, or contains code or error param (these are from Oauth Redirect)
|
||||||
// We skip URL params parsing
|
// We skip URL params parsing
|
||||||
if (Object.keys(query).length === 0 || query.code || query.error) return
|
if (Object.keys(query).length === 0 || query.code || query.error) return
|
||||||
|
|
||||||
const request = currentActiveTab.value.document.request
|
|
||||||
|
|
||||||
currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
currentActiveTab.value.document.request = safelyExtractRESTRequest(
|
||||||
translateExtURLParams(query, request),
|
translateExtURLParams(query),
|
||||||
getDefaultRESTRequest()
|
getDefaultRESTRequest()
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -11,14 +11,6 @@
|
|||||||
<p class="mt-2 text-center">
|
<p class="mt-2 text-center">
|
||||||
{{ t("error.invalid_link_description") }}
|
{{ t("error.invalid_link_description") }}
|
||||||
</p>
|
</p>
|
||||||
<p class="mt-4">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
to="/"
|
|
||||||
:icon="IconHome"
|
|
||||||
filled
|
|
||||||
:label="t('app.home')"
|
|
||||||
/>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<div v-else class="flex flex-col items-center justify-center flex-1 p-4">
|
<div v-else class="flex flex-col items-center justify-center flex-1 p-4">
|
||||||
<div
|
<div
|
||||||
@@ -84,7 +76,6 @@ import IconHome from "~icons/lucide/home"
|
|||||||
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
import IconRefreshCW from "~icons/lucide/refresh-cw"
|
||||||
import { createNewTab } from "~/helpers/rest/tab"
|
import { createNewTab } from "~/helpers/rest/tab"
|
||||||
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
import { getDefaultRESTRequest } from "~/helpers/rest/default"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@@ -116,15 +107,6 @@ const addRequestToTab = () => {
|
|||||||
const data = shortcodeDetails.data
|
const data = shortcodeDetails.data
|
||||||
|
|
||||||
if (E.isRight(data)) {
|
if (E.isRight(data)) {
|
||||||
if (!data.right.shortcode?.request) {
|
|
||||||
invalidLink.value = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
platform.analytics?.logEvent({
|
|
||||||
type: "HOPP_SHORTCODE_RESOLVED",
|
|
||||||
})
|
|
||||||
|
|
||||||
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
const request: unknown = JSON.parse(data.right.shortcode?.request as string)
|
||||||
|
|
||||||
createNewTab({
|
createNewTab({
|
||||||
|
|||||||
@@ -5,50 +5,8 @@ export type HoppRequestEvent =
|
|||||||
}
|
}
|
||||||
| { platform: "wss" | "sse" | "socketio" | "mqtt" }
|
| { platform: "wss" | "sse" | "socketio" | "mqtt" }
|
||||||
|
|
||||||
export type AnalyticsEvent =
|
|
||||||
| ({ type: "HOPP_REQUEST_RUN" } & HoppRequestEvent)
|
|
||||||
| {
|
|
||||||
type: "HOPP_CREATE_ENVIRONMENT"
|
|
||||||
workspaceType: "personal" | "team"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "HOPP_CREATE_COLLECTION"
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
isRootCollection: boolean
|
|
||||||
workspaceType: "personal" | "team"
|
|
||||||
}
|
|
||||||
| { type: "HOPP_CREATE_TEAM" }
|
|
||||||
| {
|
|
||||||
type: "HOPP_SAVE_REQUEST"
|
|
||||||
createdNow: boolean
|
|
||||||
workspaceType: "personal" | "team"
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
}
|
|
||||||
| { type: "HOPP_SHORTCODE_CREATED" }
|
|
||||||
| { type: "HOPP_SHORTCODE_RESOLVED" }
|
|
||||||
| { type: "HOPP_REST_NEW_TAB_OPENED" }
|
|
||||||
| {
|
|
||||||
type: "HOPP_IMPORT_COLLECTION"
|
|
||||||
importer: string
|
|
||||||
workspaceType: "personal" | "team"
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "HOPP_IMPORT_ENVIRONMENT"
|
|
||||||
workspaceType: "personal" | "team"
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
type: "HOPP_EXPORT_COLLECTION"
|
|
||||||
exporter: string
|
|
||||||
platform: "rest" | "gql"
|
|
||||||
}
|
|
||||||
| { type: "HOPP_EXPORT_ENVIRONMENT"; platform: "rest" | "gql" }
|
|
||||||
| { type: "HOPP_REST_CODEGEN_OPENED" }
|
|
||||||
| { type: "HOPP_REST_IMPORT_CURL" }
|
|
||||||
|
|
||||||
export type AnalyticsPlatformDef = {
|
export type AnalyticsPlatformDef = {
|
||||||
initAnalytics: () => void
|
initAnalytics: () => void
|
||||||
logEvent: (ev: AnalyticsEvent) => void
|
logHoppRequestRunToAnalytics: (ev: HoppRequestEvent) => void
|
||||||
logPageView: (pagePath: string) => void
|
logPageView: (pagePath: string) => void
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/selfhost-web",
|
"name": "@hoppscotch/selfhost-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-sh-admin",
|
"name": "hoppscotch-sh-admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.4",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user