Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e869d49e16 | ||
|
|
6496bea846 | ||
|
|
39842559b5 | ||
|
|
51efb35aa6 | ||
|
|
9402bb9285 | ||
|
|
82b6e08d68 | ||
|
|
25177bd635 | ||
|
|
6928eb7992 | ||
|
|
525ba77739 | ||
|
|
6bc748a267 | ||
|
|
b29c04c28d | ||
|
|
b2af353941 |
1
.dockerignore
Normal file
1
.dockerignore
Normal file
@@ -0,0 +1 @@
|
|||||||
|
*/**/node_modules
|
||||||
4
.github/workflows/tests.yml
vendored
4
.github/workflows/tests.yml
vendored
@@ -2,9 +2,9 @@ name: Node.js CI
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [main, staging]
|
branches: [main, staging, "release/**"]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [main, staging]
|
branches: [main, staging, "release/**"]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
|
|||||||
@@ -19,10 +19,12 @@ services:
|
|||||||
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
||||||
- PORT=3000
|
- PORT=3000
|
||||||
volumes:
|
volumes:
|
||||||
- ./packages/hoppscotch-backend/:/usr/src/app
|
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||||
|
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||||
- /usr/src/app/node_modules/
|
- /usr/src/app/node_modules/
|
||||||
depends_on:
|
depends_on:
|
||||||
- hoppscotch-db
|
hoppscotch-db:
|
||||||
|
condition: service_healthy
|
||||||
ports:
|
ports:
|
||||||
- "3170:3000"
|
- "3170:3000"
|
||||||
|
|
||||||
@@ -60,12 +62,20 @@ services:
|
|||||||
# you are using an external postgres instance
|
# you are using an external postgres instance
|
||||||
# This will be exposed at port 5432
|
# This will be exposed at port 5432
|
||||||
hoppscotch-db:
|
hoppscotch-db:
|
||||||
image: postgres
|
image: postgres:15
|
||||||
ports:
|
ports:
|
||||||
- "5432:5432"
|
- "5432:5432"
|
||||||
|
user: postgres
|
||||||
environment:
|
environment:
|
||||||
|
# The default user defined by the docker image
|
||||||
|
POSTGRES_USER: postgres
|
||||||
# NOTE: Please UPDATE THIS PASSWORD!
|
# NOTE: Please UPDATE THIS PASSWORD!
|
||||||
POSTGRES_PASSWORD: testpass
|
POSTGRES_PASSWORD: testpass
|
||||||
POSTGRES_DB: hoppscotch
|
POSTGRES_DB: hoppscotch
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "sh -c 'pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}'"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.8",
|
||||||
"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.getAllTeamInvitations(
|
const invitations = await this.teamInvitationService.getTeamInvitations(
|
||||||
teamID,
|
teamID,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -257,7 +257,7 @@ export class AdminService {
|
|||||||
if (E.isRight(userInvitation)) {
|
if (E.isRight(userInvitation)) {
|
||||||
await this.teamInvitationService.revokeInvitation(
|
await this.teamInvitationService.revokeInvitation(
|
||||||
userInvitation.right.id,
|
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.sendAuthEmail(email, {
|
await this.mailerService.sendEmail(email, {
|
||||||
template: 'code-your-own',
|
template: 'code-your-own',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
|
|||||||
@@ -312,6 +312,13 @@ export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
|
|||||||
*/
|
*/
|
||||||
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)
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ 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';
|
||||||
|
|
||||||
@@ -35,33 +34,14 @@ 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 The email address to be sent to (NOTE: this is not validated)
|
* @param to Receiver's email id
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
sendMail(
|
async sendEmail(
|
||||||
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,15 +1,5 @@
|
|||||||
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,
|
||||||
@@ -19,6 +9,10 @@ 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
|
||||||
@@ -33,50 +27,31 @@ export class GqlTeamEnvTeamGuard implements CanActivate {
|
|||||||
private readonly teamService: TeamService,
|
private readonly teamService: TeamService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
return pipe(
|
const requireRoles = this.reflector.get<TeamMemberRole[]>(
|
||||||
TE.Do,
|
'requiresTeamRole',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
if (!requireRoles) throw new Error(BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES);
|
||||||
|
|
||||||
TE.bindW('requiredRoles', () =>
|
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||||
pipe(
|
|
||||||
getAnnotatedRequiredRoles(this.reflector, context),
|
|
||||||
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.bindW('user', () =>
|
const { user } = gqlExecCtx.getContext().req;
|
||||||
pipe(
|
if (user == undefined) throw new Error(BUG_AUTH_NO_USER_CTX);
|
||||||
getUserFromGQLContext(context),
|
|
||||||
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.bindW('envID', () =>
|
const { id } = gqlExecCtx.getArgs<{ id: string }>();
|
||||||
pipe(
|
if (!id) throwErr(BUG_TEAM_ENV_GUARD_NO_ENV_ID);
|
||||||
getGqlArg('id', context),
|
|
||||||
O.fromPredicate(S.isString),
|
|
||||||
TE.fromOption(() => BUG_TEAM_ENV_GUARD_NO_ENV_ID),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.bindW('membership', ({ envID, user }) =>
|
const teamEnvironment =
|
||||||
pipe(
|
await this.teamEnvironmentService.getTeamEnvironment(id);
|
||||||
this.teamEnvironmentService.getTeamEnvironment(envID),
|
if (E.isLeft(teamEnvironment)) throwErr(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
TE.fromTaskOption(() => TEAM_ENVIRONMENT_NOT_FOUND),
|
|
||||||
TE.chainW((env) =>
|
|
||||||
pipe(
|
|
||||||
this.teamService.getTeamMemberTE(env.teamID, user.uid),
|
|
||||||
TE.mapLeft(() => TEAM_ENVIRONMENT_NOT_TEAM_MEMBER),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.map(({ membership, requiredRoles }) =>
|
const member = await this.teamService.getTeamMember(
|
||||||
requiredRoles.includes(membership.role),
|
teamEnvironment.right.teamID,
|
||||||
),
|
user.uid,
|
||||||
|
);
|
||||||
|
if (!member) throwErr(TEAM_ENVIRONMENT_NOT_TEAM_MEMBER);
|
||||||
|
|
||||||
TE.getOrElse(throwErr),
|
return requireRoles.includes(member.role);
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
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,6 +13,11 @@ 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')
|
||||||
@@ -29,29 +34,18 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
createTeamEnvironment(
|
async createTeamEnvironment(
|
||||||
@Args({
|
@Args() args: CreateTeamEnvironmentArgs,
|
||||||
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> {
|
||||||
return this.teamEnvironmentsService.createTeamEnvironment(
|
const teamEnvironment =
|
||||||
name,
|
await this.teamEnvironmentsService.createTeamEnvironment(
|
||||||
teamID,
|
args.name,
|
||||||
variables,
|
args.teamID,
|
||||||
)();
|
args.variables,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
|
||||||
|
return teamEnvironment.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => Boolean, {
|
@Mutation(() => Boolean, {
|
||||||
@@ -59,7 +53,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
deleteTeamEnvironment(
|
async deleteTeamEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -67,10 +61,12 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return pipe(
|
const isDeleted = await this.teamEnvironmentsService.deleteTeamEnvironment(
|
||||||
this.teamEnvironmentsService.deleteTeamEnvironment(id),
|
id,
|
||||||
TE.getOrElse(throwErr),
|
);
|
||||||
)();
|
|
||||||
|
if (E.isLeft(isDeleted)) throwErr(isDeleted.left);
|
||||||
|
return isDeleted.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -79,28 +75,19 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
updateTeamEnvironment(
|
async updateTeamEnvironment(
|
||||||
@Args({
|
@Args()
|
||||||
name: 'id',
|
args: UpdateTeamEnvironmentArgs,
|
||||||
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> {
|
||||||
return pipe(
|
const updatedTeamEnvironment =
|
||||||
this.teamEnvironmentsService.updateTeamEnvironment(id, name, variables),
|
await this.teamEnvironmentsService.updateTeamEnvironment(
|
||||||
TE.getOrElse(throwErr),
|
args.id,
|
||||||
)();
|
args.name,
|
||||||
|
args.variables,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(updatedTeamEnvironment)) throwErr(updatedTeamEnvironment.left);
|
||||||
|
return updatedTeamEnvironment.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -108,7 +95,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
deleteAllVariablesFromTeamEnvironment(
|
async deleteAllVariablesFromTeamEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -116,10 +103,13 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
return pipe(
|
const teamEnvironment =
|
||||||
this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(id),
|
await this.teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
||||||
TE.getOrElse(throwErr),
|
id,
|
||||||
)();
|
);
|
||||||
|
|
||||||
|
if (E.isLeft(teamEnvironment)) throwErr(teamEnvironment.left);
|
||||||
|
return teamEnvironment.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamEnvironment, {
|
@Mutation(() => TeamEnvironment, {
|
||||||
@@ -127,7 +117,7 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamEnvTeamGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
|
||||||
createDuplicateEnvironment(
|
async createDuplicateEnvironment(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'id',
|
name: 'id',
|
||||||
description: 'ID of the Team Environment',
|
description: 'ID of the Team Environment',
|
||||||
@@ -135,10 +125,12 @@ export class TeamEnvironmentsResolver {
|
|||||||
})
|
})
|
||||||
id: string,
|
id: string,
|
||||||
): Promise<TeamEnvironment> {
|
): Promise<TeamEnvironment> {
|
||||||
return pipe(
|
const res = await this.teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
this.teamEnvironmentsService.createDuplicateEnvironment(id),
|
id,
|
||||||
TE.getOrElse(throwErr),
|
);
|
||||||
)();
|
|
||||||
|
if (E.isLeft(res)) throwErr(res.left);
|
||||||
|
return res.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Subscriptions */
|
/* Subscriptions */
|
||||||
|
|||||||
@@ -2,7 +2,11 @@ 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 { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
|
import {
|
||||||
|
JSON_INVALID,
|
||||||
|
TEAM_ENVIRONMENT_NOT_FOUND,
|
||||||
|
TEAM_ENVIRONMENT_SHORT_NAME,
|
||||||
|
} from 'src/errors';
|
||||||
|
|
||||||
const mockPrisma = mockDeep<PrismaService>();
|
const mockPrisma = mockDeep<PrismaService>();
|
||||||
|
|
||||||
@@ -31,125 +35,81 @@ beforeEach(() => {
|
|||||||
|
|
||||||
describe('TeamEnvironmentsService', () => {
|
describe('TeamEnvironmentsService', () => {
|
||||||
describe('getTeamEnvironment', () => {
|
describe('getTeamEnvironment', () => {
|
||||||
test('queries the db with the id', async () => {
|
test('should successfully return a TeamEnvironment with valid ID', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockResolvedValueOnce(
|
||||||
|
teamEnvironment,
|
||||||
await teamEnvironmentsService.getTeamEnvironment('123')();
|
|
||||||
|
|
||||||
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
where: {
|
|
||||||
id: '123',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
});
|
|
||||||
|
|
||||||
test('requests prisma to reject the query promise if not found', async () => {
|
const result = await teamEnvironmentsService.getTeamEnvironment(
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
teamEnvironment.id,
|
||||||
|
|
||||||
await teamEnvironmentsService.getTeamEnvironment('123')();
|
|
||||||
|
|
||||||
expect(mockPrisma.teamEnvironment.findFirst).toHaveBeenCalledWith(
|
|
||||||
expect.objectContaining({
|
|
||||||
rejectOnNotFound: true,
|
|
||||||
}),
|
|
||||||
);
|
);
|
||||||
|
expect(result).toEqualRight(teamEnvironment);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should return a Some of the correct environment if exists', async () => {
|
test('should throw TEAM_ENVIRONMENT_NOT_FOUND with invalid ID', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValue(teamEnvironment);
|
mockPrisma.teamEnvironment.findFirstOrThrow.mockRejectedValueOnce(
|
||||||
|
'RejectOnNotFound',
|
||||||
|
);
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.getTeamEnvironment('123')();
|
const result = await teamEnvironmentsService.getTeamEnvironment(
|
||||||
|
teamEnvironment.id,
|
||||||
expect(result).toEqualSome(teamEnvironment);
|
);
|
||||||
});
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
|
|
||||||
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 create and return a new team environment given a valid name,variable and team ID', async () => {
|
test('should successfully create and return a new team environment given valid inputs', 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).toEqual(<TeamEnvironment>{
|
expect(result).toEqualRight({
|
||||||
id: teamEnvironment.id,
|
...teamEnvironment,
|
||||||
name: teamEnvironment.name,
|
|
||||||
teamID: teamEnvironment.teamID,
|
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
variables: JSON.stringify(teamEnvironment.variables),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should reject if given team ID is invalid', async () => {
|
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name is invalid', async () => {
|
||||||
mockPrisma.teamEnvironment.create.mockRejectedValue(null as any);
|
const result = await teamEnvironmentsService.createTeamEnvironment(
|
||||||
|
'12',
|
||||||
|
teamEnvironment.teamID,
|
||||||
|
JSON.stringify(teamEnvironment.variables),
|
||||||
|
);
|
||||||
|
|
||||||
await expect(
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_SHORT_NAME);
|
||||||
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.mockResolvedValueOnce(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(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 resolve to true given a valid team environment ID', async () => {
|
test('should successfully delete a TeamEnvironment with a valid 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);
|
||||||
});
|
});
|
||||||
@@ -159,7 +119,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);
|
||||||
});
|
});
|
||||||
@@ -169,7 +129,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`,
|
||||||
@@ -182,7 +142,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('updateVariablesInTeamEnvironment', () => {
|
describe('updateVariablesInTeamEnvironment', () => {
|
||||||
test('should add new variable to a team environment', async () => {
|
test('should successfully add new variable to a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: 'value' }],
|
variables: [{ key: 'value' }],
|
||||||
@@ -192,7 +152,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,
|
||||||
@@ -200,7 +160,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should add new variable to already existing list of variables in a team environment', async () => {
|
test('should successfully 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' }],
|
||||||
@@ -210,7 +170,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,
|
||||||
@@ -218,7 +178,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should edit existing variables in a team environment', async () => {
|
test('should successfully edit existing variables in a team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.update.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
...teamEnvironment,
|
||||||
variables: [{ key: '1234' }],
|
variables: [{ key: '1234' }],
|
||||||
@@ -228,7 +188,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,
|
||||||
@@ -236,22 +196,7 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should delete existing variable in a team environment', async () => {
|
test('should successfully edit name of an existing 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' }],
|
||||||
@@ -261,7 +206,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,
|
||||||
@@ -269,14 +214,24 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should throw TEAM_ENVIRONMENT_SHORT_NAME if input TeamEnvironment name 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);
|
||||||
});
|
});
|
||||||
@@ -288,7 +243,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`,
|
||||||
@@ -301,13 +256,13 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('deleteAllVariablesFromTeamEnvironment', () => {
|
describe('deleteAllVariablesFromTeamEnvironment', () => {
|
||||||
test('should delete all variables in a team environment', async () => {
|
test('should successfully 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,
|
||||||
@@ -315,13 +270,13 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
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 =
|
const result =
|
||||||
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
await teamEnvironmentsService.deleteAllVariablesFromTeamEnvironment(
|
||||||
'invalidid',
|
'invalidid',
|
||||||
)();
|
);
|
||||||
|
|
||||||
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
expect(result).toEqualLeft(TEAM_ENVIRONMENT_NOT_FOUND);
|
||||||
});
|
});
|
||||||
@@ -332,7 +287,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`,
|
||||||
@@ -345,33 +300,33 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('createDuplicateEnvironment', () => {
|
describe('createDuplicateEnvironment', () => {
|
||||||
test('should duplicate an existing team environment', async () => {
|
test('should successfully duplicate an existing team environment', async () => {
|
||||||
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
mockPrisma.teamEnvironment.findFirst.mockResolvedValueOnce(
|
||||||
teamEnvironment,
|
teamEnvironment,
|
||||||
);
|
);
|
||||||
|
|
||||||
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
|
||||||
id: 'newid',
|
id: 'newid',
|
||||||
|
...teamEnvironment,
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
const result = await teamEnvironmentsService.createDuplicateEnvironment(
|
||||||
teamEnvironment.id,
|
teamEnvironment.id,
|
||||||
)();
|
);
|
||||||
|
|
||||||
expect(result).toEqualRight(<TeamEnvironment>{
|
expect(result).toEqualRight(<TeamEnvironment>{
|
||||||
...teamEnvironment,
|
|
||||||
id: 'newid',
|
id: 'newid',
|
||||||
|
...teamEnvironment,
|
||||||
variables: JSON.stringify(teamEnvironment.variables),
|
variables: JSON.stringify(teamEnvironment.variables),
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should reject to TEAM_ENVIRONMMENT_NOT_FOUND if provided id is invalid', async () => {
|
test('should throw 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);
|
||||||
});
|
});
|
||||||
@@ -382,19 +337,19 @@ describe('TeamEnvironmentsService', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
mockPrisma.teamEnvironment.create.mockResolvedValueOnce({
|
||||||
...teamEnvironment,
|
|
||||||
id: 'newid',
|
id: 'newid',
|
||||||
|
...teamEnvironment,
|
||||||
});
|
});
|
||||||
|
|
||||||
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`,
|
||||||
{
|
{
|
||||||
...teamEnvironment,
|
|
||||||
id: 'newid',
|
id: 'newid',
|
||||||
|
...teamEnvironment,
|
||||||
variables: JSON.stringify([{}]),
|
variables: JSON.stringify([{}]),
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { pipe } from 'fp-ts/function';
|
import { TeamEnvironment as DBTeamEnvironment, Prisma } from '@prisma/client';
|
||||||
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 { TEAM_ENVIRONMENT_NOT_FOUND } from 'src/errors';
|
import {
|
||||||
|
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(
|
||||||
@@ -17,219 +16,218 @@ export class TeamEnvironmentsService {
|
|||||||
private readonly pubsub: PubSubService,
|
private readonly pubsub: PubSubService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
getTeamEnvironment(id: string) {
|
TITLE_LENGTH = 3;
|
||||||
return TO.tryCatch(() =>
|
|
||||||
this.prisma.teamEnvironment.findFirst({
|
/**
|
||||||
where: { id },
|
* TeamEnvironments are saved in the DB in the following way
|
||||||
|
* [{ 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) {
|
/**
|
||||||
return pipe(
|
* Fetch all TeamEnvironments of a team.
|
||||||
() =>
|
*
|
||||||
this.prisma.teamEnvironment.create({
|
* @param teamID teamID of new TeamEnvironment
|
||||||
data: {
|
* @returns List of TeamEnvironments
|
||||||
name: name,
|
*/
|
||||||
teamID: teamID,
|
async fetchAllTeamEnvironments(teamID: string) {
|
||||||
variables: JSON.parse(variables),
|
const result = await this.prisma.teamEnvironment.findMany({
|
||||||
},
|
where: {
|
||||||
}),
|
teamID: teamID,
|
||||||
T.chainFirst(
|
},
|
||||||
(environment) => () =>
|
});
|
||||||
this.pubsub.publish(
|
const teamEnvironments = result.map((item) => {
|
||||||
`team_environment/${environment.teamID}/created`,
|
return this.cast(item);
|
||||||
<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),
|
|
||||||
};
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
deleteTeamEnvironment(id: string) {
|
return teamEnvironments;
|
||||||
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
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,15 +12,10 @@ 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 { EmailCodec } from 'src/types/Email';
|
import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors';
|
||||||
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';
|
||||||
@@ -36,6 +31,8 @@ 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)
|
||||||
@@ -79,8 +76,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)
|
||||||
teamInvitation(
|
async teamInvitation(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
description: 'ID of the Team Invitation to lookup',
|
description: 'ID of the Team Invitation to lookup',
|
||||||
@@ -88,17 +85,11 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<TeamInvitation> {
|
): Promise<TeamInvitation> {
|
||||||
return pipe(
|
const teamInvitation = await this.teamInvitationService.getInvitation(
|
||||||
this.teamInvitationService.getInvitation(inviteID),
|
inviteID,
|
||||||
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
);
|
||||||
TE.chainW(
|
if (O.isNone(teamInvitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
||||||
TE.fromPredicate(
|
return teamInvitation.value;
|
||||||
(a) => a.inviteeEmail.toLowerCase() === user.email?.toLowerCase(),
|
|
||||||
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
TE.getOrElse(throwErr),
|
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamInvitation, {
|
@Mutation(() => TeamInvitation, {
|
||||||
@@ -106,56 +97,19 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER)
|
@RequiresTeamRole(TeamMemberRole.OWNER)
|
||||||
createTeamInvitation(
|
async createTeamInvitation(
|
||||||
@GqlUser()
|
@GqlUser() user: AuthUser,
|
||||||
user: User,
|
@Args() args: CreateTeamInvitationArgs,
|
||||||
|
|
||||||
@Args({
|
|
||||||
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> {
|
): Promise<TeamInvitation> {
|
||||||
return pipe(
|
const teamInvitation = await this.teamInvitationService.createInvitation(
|
||||||
TE.Do,
|
user,
|
||||||
|
args.teamID,
|
||||||
|
args.inviteeEmail,
|
||||||
|
args.inviteeRole,
|
||||||
|
);
|
||||||
|
|
||||||
// Validate email
|
if (E.isLeft(teamInvitation)) throwErr(teamInvitation.left);
|
||||||
TE.bindW('email', () =>
|
return teamInvitation.right;
|
||||||
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, {
|
||||||
@@ -163,7 +117,7 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
|
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
|
||||||
@RequiresTeamRole(TeamMemberRole.OWNER)
|
@RequiresTeamRole(TeamMemberRole.OWNER)
|
||||||
revokeTeamInvitation(
|
async revokeTeamInvitation(
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
@@ -171,19 +125,19 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<true> {
|
): Promise<true> {
|
||||||
return pipe(
|
const isRevoked = await this.teamInvitationService.revokeInvitation(
|
||||||
this.teamInvitationService.revokeInvitation(inviteID),
|
inviteID,
|
||||||
TE.map(() => true as const),
|
);
|
||||||
TE.getOrElse(throwErr),
|
if (E.isLeft(isRevoked)) throwErr(isRevoked.left);
|
||||||
)();
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Mutation(() => TeamMember, {
|
@Mutation(() => TeamMember, {
|
||||||
description: 'Accept an Invitation',
|
description: 'Accept an Invitation',
|
||||||
})
|
})
|
||||||
@UseGuards(GqlAuthGuard, TeamInviteeGuard)
|
@UseGuards(GqlAuthGuard, TeamInviteeGuard)
|
||||||
acceptTeamInvitation(
|
async acceptTeamInvitation(
|
||||||
@GqlUser() user: User,
|
@GqlUser() user: AuthUser,
|
||||||
@Args({
|
@Args({
|
||||||
name: 'inviteID',
|
name: 'inviteID',
|
||||||
type: () => ID,
|
type: () => ID,
|
||||||
@@ -191,10 +145,12 @@ export class TeamInvitationResolver {
|
|||||||
})
|
})
|
||||||
inviteID: string,
|
inviteID: string,
|
||||||
): Promise<TeamMember> {
|
): Promise<TeamMember> {
|
||||||
return pipe(
|
const teamMember = await this.teamInvitationService.acceptInvitation(
|
||||||
this.teamInvitationService.acceptInvitation(inviteID, user),
|
inviteID,
|
||||||
TE.getOrElse(throwErr),
|
user,
|
||||||
)();
|
);
|
||||||
|
if (E.isLeft(teamMember)) throwErr(teamMember.left);
|
||||||
|
return teamMember.right;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Subscriptions
|
// Subscriptions
|
||||||
|
|||||||
@@ -1,27 +1,25 @@
|
|||||||
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 TO from 'fp-ts/TaskOption';
|
|
||||||
import * as TE from 'fp-ts/TaskEither';
|
|
||||||
import * as E from 'fp-ts/Either';
|
import * as E from 'fp-ts/Either';
|
||||||
import { pipe, flow, constVoid } from 'fp-ts/function';
|
|
||||||
import { PrismaService } from 'src/prisma/prisma.service';
|
import { PrismaService } from 'src/prisma/prisma.service';
|
||||||
import { Team, TeamMemberRole } from 'src/team/team.model';
|
import { TeamInvitation as DBTeamInvitation } from '@prisma/client';
|
||||||
import { Email } from 'src/types/Email';
|
import { TeamMember, TeamMemberRole } from 'src/team/team.model';
|
||||||
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,
|
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 { validateEmail } from '../utils';
|
||||||
|
import { AuthUser } from 'src/types/AuthUser';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TeamInvitationService {
|
export class TeamInvitationService {
|
||||||
@@ -32,38 +30,37 @@ 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> {
|
/**
|
||||||
return pipe(
|
* Get the team invite
|
||||||
() =>
|
* @param inviteID invite id
|
||||||
this.prisma.teamInvitation.findUnique({
|
* @returns an Option of team invitation or none
|
||||||
where: {
|
*/
|
||||||
id: inviteID,
|
async getInvitation(inviteID: string) {
|
||||||
},
|
try {
|
||||||
}),
|
const dbInvitation = await this.prisma.teamInvitation.findUniqueOrThrow({
|
||||||
TO.fromTask,
|
where: {
|
||||||
TO.chain(flow(O.fromNullable, TO.fromOption)),
|
id: inviteID,
|
||||||
TO.map((x) => x as TeamInvitation),
|
},
|
||||||
);
|
});
|
||||||
}
|
|
||||||
|
|
||||||
getInvitationWithEmail(email: Email, team: Team) {
|
return O.some(this.cast(dbInvitation));
|
||||||
return pipe(
|
} catch (e) {
|
||||||
() =>
|
return O.none;
|
||||||
this.prisma.teamInvitation.findUnique({
|
}
|
||||||
where: {
|
|
||||||
teamID_inviteeEmail: {
|
|
||||||
inviteeEmail: email,
|
|
||||||
teamID: team.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
TO.fromTask,
|
|
||||||
TO.chain(flow(O.fromNullable, TO.fromOption)),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -92,211 +89,162 @@ export class TeamInvitationService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
createInvitation(
|
/**
|
||||||
creator: User,
|
* Create a team invitation
|
||||||
team: Team,
|
* @param creator creator of the invitation
|
||||||
inviteeEmail: Email,
|
* @param teamID team id
|
||||||
|
* @param inviteeEmail invitee email
|
||||||
|
* @param inviteeRole invitee role
|
||||||
|
* @returns an Either of team invitation or error message
|
||||||
|
*/
|
||||||
|
async createInvitation(
|
||||||
|
creator: AuthUser,
|
||||||
|
teamID: string,
|
||||||
|
inviteeEmail: string,
|
||||||
inviteeRole: TeamMemberRole,
|
inviteeRole: TeamMemberRole,
|
||||||
) {
|
) {
|
||||||
return pipe(
|
// validate email
|
||||||
// Perform all validation checks
|
const isEmailValid = validateEmail(inviteeEmail);
|
||||||
TE.sequenceArray([
|
if (!isEmailValid) return E.left(INVALID_EMAIL);
|
||||||
// creator should be a TeamMember
|
|
||||||
pipe(
|
|
||||||
this.teamService.getTeamMemberTE(team.id, creator.uid),
|
|
||||||
TE.map(constVoid),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Invitee should not be a team member
|
// team ID should valid
|
||||||
pipe(
|
const team = await this.teamService.getTeamWithID(teamID);
|
||||||
async () => await this.userService.findUserByEmail(inviteeEmail),
|
if (!team) return E.left(TEAM_INVALID_ID);
|
||||||
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),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Should not have an existing invite
|
// invitation creator should be a TeamMember
|
||||||
pipe(
|
const isTeamMember = await this.teamService.getTeamMember(
|
||||||
this.getInvitationWithEmail(inviteeEmail, team),
|
team.id,
|
||||||
TE.fromTaskOption(() => null),
|
creator.uid,
|
||||||
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);
|
||||||
|
|
||||||
revokeInvitation(inviteID: string) {
|
// Checking to see if the invitee is already part of the team or not
|
||||||
return pipe(
|
const inviteeUser = await this.userService.findUserByEmail(inviteeEmail);
|
||||||
// Make sure invite exists
|
if (O.isSome(inviteeUser)) {
|
||||||
this.getInvitation(inviteID),
|
// invitee should not already a member
|
||||||
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
const isTeamMember = await this.teamService.getTeamMember(
|
||||||
|
team.id,
|
||||||
|
inviteeUser.value.uid,
|
||||||
|
);
|
||||||
|
if (isTeamMember) return E.left(TEAM_INVITE_ALREADY_MEMBER);
|
||||||
|
}
|
||||||
|
|
||||||
// Delete team invitation
|
// check invitee already invited earlier or not
|
||||||
TE.chainTaskK(
|
const teamInvitation = await this.getTeamInviteByEmailAndTeamID(
|
||||||
() => () =>
|
inviteeEmail,
|
||||||
this.prisma.teamInvitation.delete({
|
team.id,
|
||||||
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);
|
||||||
|
|
||||||
getAllInvitationsInTeam(team: Team) {
|
// create the invitation
|
||||||
return pipe(
|
const dbInvitation = await this.prisma.teamInvitation.create({
|
||||||
() =>
|
data: {
|
||||||
this.prisma.teamInvitation.findMany({
|
teamID: team.id,
|
||||||
where: {
|
inviteeEmail,
|
||||||
teamID: team.id,
|
inviteeRole,
|
||||||
},
|
creatorUid: creator.uid,
|
||||||
}),
|
},
|
||||||
T.map((x) => x as TeamInvitation[]),
|
});
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
acceptInvitation(inviteID: string, acceptedBy: User) {
|
await this.mailerService.sendEmail(inviteeEmail, {
|
||||||
return pipe(
|
template: 'team-invitation',
|
||||||
TE.Do,
|
variables: {
|
||||||
|
invitee: creator.displayName ?? 'A Hoppscotch User',
|
||||||
|
action_url: `${process.env.VITE_BASE_URL}/join-team?id=${dbInvitation.id}`,
|
||||||
|
invite_team_name: team.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
// First get the invitation
|
const invitation = this.cast(dbInvitation);
|
||||||
TE.bindW('invitation', () =>
|
this.pubsub.publish(`team/${invitation.teamID}/invite_added`, invitation);
|
||||||
pipe(
|
|
||||||
this.getInvitation(inviteID),
|
|
||||||
TE.fromTaskOption(() => TEAM_INVITE_NO_INVITE_FOUND),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Validation checks
|
return E.right(invitation);
|
||||||
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),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch the count invitations for a given team.
|
* Revoke a team invitation
|
||||||
* @param teamID team id
|
* @param inviteID invite id
|
||||||
* @returns a count team invitations for a team
|
* @returns an Either of true or error message
|
||||||
*/
|
*/
|
||||||
async getAllTeamInvitations(teamID: string) {
|
async revokeInvitation(inviteID: string) {
|
||||||
const invitations = await this.prisma.teamInvitation.findMany({
|
// 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
|
||||||
|
* @returns array of team invitations for a team
|
||||||
|
*/
|
||||||
|
async getTeamInvitations(teamID: string) {
|
||||||
|
const dbInvitations = 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,48 +24,30 @@ export class TeamInviteTeamOwnerGuard implements CanActivate {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
return pipe(
|
// Get GQL context
|
||||||
TE.Do,
|
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||||
|
|
||||||
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
// Get user
|
||||||
|
const { user } = gqlExecCtx.getContext().req;
|
||||||
|
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
||||||
|
|
||||||
// Get the invite
|
// Get the invite
|
||||||
TE.bindW('invite', ({ gqlCtx }) =>
|
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
||||||
pipe(
|
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
||||||
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),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.bindW('user', ({ gqlCtx }) =>
|
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
||||||
pipe(
|
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
||||||
gqlCtx.getContext().req.user,
|
|
||||||
O.fromNullable,
|
|
||||||
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.bindW('userMember', ({ invite, user }) =>
|
// Fetch team member details of this user
|
||||||
this.teamService.getTeamMemberTE(invite.teamID, user.uid),
|
const teamMember = await this.teamService.getTeamMember(
|
||||||
),
|
invitation.value.teamID,
|
||||||
|
user.uid,
|
||||||
|
);
|
||||||
|
|
||||||
TE.chainW(
|
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND);
|
||||||
TE.fromPredicate(
|
if (teamMember.role !== TeamMemberRole.OWNER)
|
||||||
({ userMember }) => userMember.role === TeamMemberRole.OWNER,
|
throwErr(TEAM_NOT_REQUIRED_ROLE);
|
||||||
() => TEAM_NOT_REQUIRED_ROLE,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.fold(
|
return true;
|
||||||
(err) => throwErr(err),
|
|
||||||
() => T.of(true),
|
|
||||||
),
|
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,23 @@
|
|||||||
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(
|
||||||
@@ -23,50 +26,32 @@ export class TeamInviteViewerGuard implements CanActivate {
|
|||||||
) {}
|
) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
return pipe(
|
// Get GQL context
|
||||||
TE.Do,
|
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||||
|
|
||||||
// Get GQL Context
|
// Get user
|
||||||
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
const { user } = gqlExecCtx.getContext().req;
|
||||||
|
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
||||||
|
|
||||||
// Get user
|
// Get the invite
|
||||||
TE.bindW('user', ({ gqlCtx }) =>
|
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
||||||
pipe(
|
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
||||||
O.fromNullable(gqlCtx.getContext().req.user),
|
|
||||||
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Get the invite
|
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
||||||
TE.bindW('invite', ({ gqlCtx }) =>
|
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
||||||
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 we can resolver the user as a team member
|
// Check if the user and the invite email match, else if user is a team member
|
||||||
// any better solution ?
|
if (
|
||||||
TE.chainW(({ user, invite }) =>
|
user.email?.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase()
|
||||||
user.email?.toLowerCase() === invite.inviteeEmail.toLowerCase()
|
) {
|
||||||
? TE.of(true)
|
const teamMember = await this.teamService.getTeamMember(
|
||||||
: pipe(
|
invitation.value.teamID,
|
||||||
this.teamService.getTeamMemberTE(invite.teamID, user.uid),
|
user.uid,
|
||||||
TE.map(() => true),
|
);
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
TE.mapLeft((e) =>
|
if (!teamMember) throwErr(TEAM_MEMBER_NOT_FOUND);
|
||||||
e === 'team/member_not_found' ? TEAM_INVITE_NOT_VALID_VIEWER : e,
|
}
|
||||||
),
|
|
||||||
|
|
||||||
TE.fold(throwErr, () => T.of(true)),
|
return true;
|
||||||
)();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,7 @@
|
|||||||
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,
|
||||||
@@ -24,44 +20,26 @@ 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> {
|
||||||
return pipe(
|
// Get GQL Context
|
||||||
TE.Do,
|
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||||
|
|
||||||
// Get execution context
|
// Get user
|
||||||
TE.bindW('gqlCtx', () => TE.of(GqlExecutionContext.create(context))),
|
const { user } = gqlExecCtx.getContext().req;
|
||||||
|
if (!user) throwErr(BUG_AUTH_NO_USER_CTX);
|
||||||
|
|
||||||
// Get user
|
// Get the invite
|
||||||
TE.bindW('user', ({ gqlCtx }) =>
|
const { inviteID } = gqlExecCtx.getArgs<{ inviteID: string }>();
|
||||||
pipe(
|
if (!inviteID) throwErr(BUG_TEAM_INVITE_NO_INVITE_ID);
|
||||||
O.fromNullable(gqlCtx.getContext().req.user),
|
|
||||||
TE.fromOption(() => BUG_AUTH_NO_USER_CTX),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Get invite
|
const invitation = await this.teamInviteService.getInvitation(inviteID);
|
||||||
TE.bindW('invite', ({ gqlCtx }) =>
|
if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
|
||||||
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 emails match
|
if (
|
||||||
TE.chainW(
|
user.email.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase()
|
||||||
TE.fromPredicate(
|
) {
|
||||||
({ user, invite }) => user.email === invite.inviteeEmail,
|
throwErr(TEAM_INVITE_EMAIL_DO_NOT_MATCH);
|
||||||
() => TEAM_INVITE_EMAIL_DO_NOT_MATCH,
|
}
|
||||||
),
|
|
||||||
),
|
|
||||||
|
|
||||||
// Fold it to a promise
|
return true;
|
||||||
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.getAllInvitationsInTeam(team)();
|
return this.teamInviteService.getTeamInvitations(team.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
1
packages/hoppscotch-common/assets/icons/star-off.svg
Normal file
1
packages/hoppscotch-common/assets/icons/star-off.svg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
<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>
|
||||||
|
After Width: | Height: | Size: 337 B |
@@ -19,7 +19,7 @@
|
|||||||
"edit": "編輯",
|
"edit": "編輯",
|
||||||
"filter": "篩選回應",
|
"filter": "篩選回應",
|
||||||
"go_back": "返回",
|
"go_back": "返回",
|
||||||
"go_forward": "Go forward",
|
"go_forward": "向前",
|
||||||
"group_by": "分組方式",
|
"group_by": "分組方式",
|
||||||
"label": "標籤",
|
"label": "標籤",
|
||||||
"learn_more": "瞭解更多",
|
"learn_more": "瞭解更多",
|
||||||
@@ -117,37 +117,37 @@
|
|||||||
"username": "使用者名稱"
|
"username": "使用者名稱"
|
||||||
},
|
},
|
||||||
"collection": {
|
"collection": {
|
||||||
"created": "組合已建立",
|
"created": "集合已建立",
|
||||||
"different_parent": "Cannot reorder collection with different parent",
|
"different_parent": "無法為父集合不同的集合重新排序",
|
||||||
"edit": "編輯組合",
|
"edit": "編輯集合",
|
||||||
"invalid_name": "請提供有效的組合名稱",
|
"invalid_name": "請提供有效的集合名稱",
|
||||||
"invalid_root_move": "Collection already in the root",
|
"invalid_root_move": "集合已在根目錄",
|
||||||
"moved": "Moved Successfully",
|
"moved": "移動成功",
|
||||||
"my_collections": "我的組合",
|
"my_collections": "我的集合",
|
||||||
"name": "我的新組合",
|
"name": "我的新集合",
|
||||||
"name_length_insufficient": "組合名稱至少要有 3 個字元。",
|
"name_length_insufficient": "集合名稱至少要有 3 個字元。",
|
||||||
"new": "建立組合",
|
"new": "建立集合",
|
||||||
"order_changed": "Collection Order Updated",
|
"order_changed": "集合順序已更新",
|
||||||
"renamed": "組合已重新命名",
|
"renamed": "集合已重新命名",
|
||||||
"request_in_use": "請求正在使用中",
|
"request_in_use": "請求正在使用中",
|
||||||
"save_as": "另存為",
|
"save_as": "另存為",
|
||||||
"select": "選擇一個組合",
|
"select": "選擇一個集合",
|
||||||
"select_location": "選擇位置",
|
"select_location": "選擇位置",
|
||||||
"select_team": "選擇一個團隊",
|
"select_team": "選擇一個團隊",
|
||||||
"team_collections": "團隊組合"
|
"team_collections": "團隊集合"
|
||||||
},
|
},
|
||||||
"confirm": {
|
"confirm": {
|
||||||
"exit_team": "您確定要離開此團隊嗎?",
|
"exit_team": "您確定要離開此團隊嗎?",
|
||||||
"logout": "您確定要登出嗎?",
|
"logout": "您確定要登出嗎?",
|
||||||
"remove_collection": "您確定要永久刪除該組合嗎?",
|
"remove_collection": "您確定要永久刪除該集合嗎?",
|
||||||
"remove_environment": "您確定要永久刪除該環境嗎?",
|
"remove_environment": "您確定要永久刪除該環境嗎?",
|
||||||
"remove_folder": "您確定要永久刪除該資料夾嗎?",
|
"remove_folder": "您確定要永久刪除該資料夾嗎?",
|
||||||
"remove_history": "您確定要永久刪除全部歷史記錄嗎?",
|
"remove_history": "您確定要永久刪除全部歷史記錄嗎?",
|
||||||
"remove_request": "您確定要永久刪除該請求嗎?",
|
"remove_request": "您確定要永久刪除該請求嗎?",
|
||||||
"remove_team": "您確定要刪除該團隊嗎?",
|
"remove_team": "您確定要刪除該團隊嗎?",
|
||||||
"remove_telemetry": "您確定要退出遙測服務嗎?",
|
"remove_telemetry": "您確定要退出遙測服務嗎?",
|
||||||
"request_change": "您確定要捨棄當前請求嗎?未儲存的變更將遺失。",
|
"request_change": "您確定要捨棄目前的請求嗎?未儲存的變更將遺失。",
|
||||||
"save_unsaved_tab": "Do you want to save changes made in this tab?",
|
"save_unsaved_tab": "您要儲存在此分頁做出的改動嗎?",
|
||||||
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
"sync": "您想從雲端恢復您的工作區嗎?這將丟棄您的本地進度。"
|
||||||
},
|
},
|
||||||
"count": {
|
"count": {
|
||||||
@@ -160,13 +160,13 @@
|
|||||||
},
|
},
|
||||||
"documentation": {
|
"documentation": {
|
||||||
"generate": "產生文件",
|
"generate": "產生文件",
|
||||||
"generate_message": "匯入 Hoppscotch 組合以隨時隨地產生 API 文件。"
|
"generate_message": "匯入 Hoppscotch 集合以隨時隨地產生 API 文件。"
|
||||||
},
|
},
|
||||||
"empty": {
|
"empty": {
|
||||||
"authorization": "該請求沒有使用任何授權",
|
"authorization": "該請求沒有使用任何授權",
|
||||||
"body": "該請求沒有任何請求主體",
|
"body": "該請求沒有任何請求主體",
|
||||||
"collection": "組合為空",
|
"collection": "集合為空",
|
||||||
"collections": "組合為空",
|
"collections": "集合為空",
|
||||||
"documentation": "連線到 GraphQL 端點以檢視文件",
|
"documentation": "連線到 GraphQL 端點以檢視文件",
|
||||||
"endpoint": "端點不能留空",
|
"endpoint": "端點不能留空",
|
||||||
"environments": "環境為空",
|
"environments": "環境為空",
|
||||||
@@ -209,7 +209,7 @@
|
|||||||
"browser_support_sse": "此瀏覽器似乎不支援 SSE。",
|
"browser_support_sse": "此瀏覽器似乎不支援 SSE。",
|
||||||
"check_console_details": "檢查控制台日誌以獲悉詳情",
|
"check_console_details": "檢查控制台日誌以獲悉詳情",
|
||||||
"curl_invalid_format": "cURL 格式不正確",
|
"curl_invalid_format": "cURL 格式不正確",
|
||||||
"danger_zone": "Danger zone",
|
"danger_zone": "危險地帶",
|
||||||
"delete_account": "您的帳號目前為這些團隊的擁有者:",
|
"delete_account": "您的帳號目前為這些團隊的擁有者:",
|
||||||
"delete_account_description": "您在刪除帳號前必須先將您自己從團隊中移除、轉移擁有權,或是刪除團隊。",
|
"delete_account_description": "您在刪除帳號前必須先將您自己從團隊中移除、轉移擁有權,或是刪除團隊。",
|
||||||
"empty_req_name": "空請求名稱",
|
"empty_req_name": "空請求名稱",
|
||||||
@@ -277,38 +277,38 @@
|
|||||||
"tests": "編寫測試指令碼以自動除錯。"
|
"tests": "編寫測試指令碼以自動除錯。"
|
||||||
},
|
},
|
||||||
"hide": {
|
"hide": {
|
||||||
"collection": "隱藏組合面板",
|
"collection": "隱藏集合面板",
|
||||||
"more": "隱藏更多",
|
"more": "隱藏更多",
|
||||||
"preview": "隱藏預覽",
|
"preview": "隱藏預覽",
|
||||||
"sidebar": "隱藏側邊欄"
|
"sidebar": "隱藏側邊欄"
|
||||||
},
|
},
|
||||||
"import": {
|
"import": {
|
||||||
"collections": "匯入組合",
|
"collections": "匯入集合",
|
||||||
"curl": "匯入 cURL",
|
"curl": "匯入 cURL",
|
||||||
"failed": "匯入失敗",
|
"failed": "匯入失敗",
|
||||||
"from_gist": "從 Gist 匯入",
|
"from_gist": "從 Gist 匯入",
|
||||||
"from_gist_description": "從 Gist 網址匯入",
|
"from_gist_description": "從 Gist 網址匯入",
|
||||||
"from_insomnia": "從 Insomnia 匯入",
|
"from_insomnia": "從 Insomnia 匯入",
|
||||||
"from_insomnia_description": "從 Insomnia 組合匯入",
|
"from_insomnia_description": "從 Insomnia 集合匯入",
|
||||||
"from_json": "從 Hoppscotch 匯入",
|
"from_json": "從 Hoppscotch 匯入",
|
||||||
"from_json_description": "從 Hoppscotch 組合檔匯入",
|
"from_json_description": "從 Hoppscotch 集合檔匯入",
|
||||||
"from_my_collections": "從我的組合匯入",
|
"from_my_collections": "從我的集合匯入",
|
||||||
"from_my_collections_description": "從我的組合檔匯入",
|
"from_my_collections_description": "從我的集合檔匯入",
|
||||||
"from_openapi": "從 OpenAPI 匯入",
|
"from_openapi": "從 OpenAPI 匯入",
|
||||||
"from_openapi_description": "從 OpenAPI 規格檔 (YML/JSON) 匯入",
|
"from_openapi_description": "從 OpenAPI 規格檔 (YML/JSON) 匯入",
|
||||||
"from_postman": "從 Postman 匯入",
|
"from_postman": "從 Postman 匯入",
|
||||||
"from_postman_description": "從 Postman 組合匯入",
|
"from_postman_description": "從 Postman 集合匯入",
|
||||||
"from_url": "從網址匯入",
|
"from_url": "從網址匯入",
|
||||||
"gist_url": "輸入 Gist 網址",
|
"gist_url": "輸入 Gist 網址",
|
||||||
"import_from_url_invalid_fetch": "無法從網址取得資料",
|
"import_from_url_invalid_fetch": "無法從網址取得資料",
|
||||||
"import_from_url_invalid_file_format": "匯入組合時發生錯誤",
|
"import_from_url_invalid_file_format": "匯入集合時發生錯誤",
|
||||||
"import_from_url_invalid_type": "不支援此類型。可接受的值為 'hoppscotch'、'openapi'、'postman'、'insomnia'",
|
"import_from_url_invalid_type": "不支援此類型。可接受的值為 'hoppscotch'、'openapi'、'postman'、'insomnia'",
|
||||||
"import_from_url_success": "已匯入組合",
|
"import_from_url_success": "已匯入集合",
|
||||||
"json_description": "從 Hoppscotch 組合 JSON 檔匯入組合",
|
"json_description": "從 Hoppscotch 集合 JSON 檔匯入集合",
|
||||||
"title": "匯入"
|
"title": "匯入"
|
||||||
},
|
},
|
||||||
"layout": {
|
"layout": {
|
||||||
"collapse_collection": "隱藏或顯示組合",
|
"collapse_collection": "隱藏或顯示集合",
|
||||||
"collapse_sidebar": "隱藏或顯示側邊欄",
|
"collapse_sidebar": "隱藏或顯示側邊欄",
|
||||||
"column": "垂直版面",
|
"column": "垂直版面",
|
||||||
"name": "配置",
|
"name": "配置",
|
||||||
@@ -316,8 +316,8 @@
|
|||||||
"zen_mode": "專注模式"
|
"zen_mode": "專注模式"
|
||||||
},
|
},
|
||||||
"modal": {
|
"modal": {
|
||||||
"close_unsaved_tab": "You have unsaved changes",
|
"close_unsaved_tab": "您有未儲存的改動",
|
||||||
"collections": "組合",
|
"collections": "集合",
|
||||||
"confirm": "確認",
|
"confirm": "確認",
|
||||||
"edit_request": "編輯請求",
|
"edit_request": "編輯請求",
|
||||||
"import_export": "匯入/匯出"
|
"import_export": "匯入/匯出"
|
||||||
@@ -374,9 +374,9 @@
|
|||||||
"email_verification_mail": "已將驗證信寄送至您的電子郵件地址。請點擊信中連結以驗證您的電子郵件地址。",
|
"email_verification_mail": "已將驗證信寄送至您的電子郵件地址。請點擊信中連結以驗證您的電子郵件地址。",
|
||||||
"no_permission": "您沒有權限執行此操作。",
|
"no_permission": "您沒有權限執行此操作。",
|
||||||
"owner": "擁有者",
|
"owner": "擁有者",
|
||||||
"owner_description": "擁有者可以新增、編輯和刪除請求、組合和團隊成員。",
|
"owner_description": "擁有者可以新增、編輯和刪除請求、集合和團隊成員。",
|
||||||
"roles": "角色",
|
"roles": "角色",
|
||||||
"roles_description": "角色用來控制對共用組合的存取權。",
|
"roles_description": "角色用來控制對共用集合的存取權。",
|
||||||
"updated": "已更新個人檔案",
|
"updated": "已更新個人檔案",
|
||||||
"viewer": "檢視者",
|
"viewer": "檢視者",
|
||||||
"viewer_description": "檢視者只能檢視和使用請求。"
|
"viewer_description": "檢視者只能檢視和使用請求。"
|
||||||
@@ -396,8 +396,8 @@
|
|||||||
"text": "文字"
|
"text": "文字"
|
||||||
},
|
},
|
||||||
"copy_link": "複製連結",
|
"copy_link": "複製連結",
|
||||||
"different_collection": "Cannot reorder requests from different collections",
|
"different_collection": "無法重新排列來自不同集合的請求",
|
||||||
"duplicated": "Request duplicated",
|
"duplicated": "已複製請求",
|
||||||
"duration": "持續時間",
|
"duration": "持續時間",
|
||||||
"enter_curl": "輸入 cURL",
|
"enter_curl": "輸入 cURL",
|
||||||
"generate_code": "產生程式碼",
|
"generate_code": "產生程式碼",
|
||||||
@@ -405,10 +405,10 @@
|
|||||||
"header_list": "請求標頭列表",
|
"header_list": "請求標頭列表",
|
||||||
"invalid_name": "請提供請求名稱",
|
"invalid_name": "請提供請求名稱",
|
||||||
"method": "方法",
|
"method": "方法",
|
||||||
"moved": "Request moved",
|
"moved": "已移動請求",
|
||||||
"name": "請求名稱",
|
"name": "請求名稱",
|
||||||
"new": "新請求",
|
"new": "新請求",
|
||||||
"order_changed": "Request Order Updated",
|
"order_changed": "已更新請求順序",
|
||||||
"override": "覆寫",
|
"override": "覆寫",
|
||||||
"override_help": "在標頭設置 <kbd>Content-Type</kbd>",
|
"override_help": "在標頭設置 <kbd>Content-Type</kbd>",
|
||||||
"overriden": "已覆寫",
|
"overriden": "已覆寫",
|
||||||
@@ -432,7 +432,7 @@
|
|||||||
"view_my_links": "檢視我的連結"
|
"view_my_links": "檢視我的連結"
|
||||||
},
|
},
|
||||||
"response": {
|
"response": {
|
||||||
"audio": "Audio",
|
"audio": "音訊",
|
||||||
"body": "回應本體",
|
"body": "回應本體",
|
||||||
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
|
"filter_response_body": "篩選 JSON 回應本體 (使用 JSONPath 語法)",
|
||||||
"headers": "回應標頭",
|
"headers": "回應標頭",
|
||||||
@@ -446,7 +446,7 @@
|
|||||||
"status": "狀態",
|
"status": "狀態",
|
||||||
"time": "時間",
|
"time": "時間",
|
||||||
"title": "回應",
|
"title": "回應",
|
||||||
"video": "Video",
|
"video": "視訊",
|
||||||
"waiting_for_connection": "等待連線",
|
"waiting_for_connection": "等待連線",
|
||||||
"xml": "XML"
|
"xml": "XML"
|
||||||
},
|
},
|
||||||
@@ -494,7 +494,7 @@
|
|||||||
"short_codes_description": "我們為您打造的快捷碼。",
|
"short_codes_description": "我們為您打造的快捷碼。",
|
||||||
"sidebar_on_left": "左側邊欄",
|
"sidebar_on_left": "左側邊欄",
|
||||||
"sync": "同步",
|
"sync": "同步",
|
||||||
"sync_collections": "組合",
|
"sync_collections": "集合",
|
||||||
"sync_description": "這些設定會同步到雲端。",
|
"sync_description": "這些設定會同步到雲端。",
|
||||||
"sync_environments": "環境",
|
"sync_environments": "環境",
|
||||||
"sync_history": "歷史",
|
"sync_history": "歷史",
|
||||||
@@ -551,7 +551,7 @@
|
|||||||
"previous_method": "選擇上一個方法",
|
"previous_method": "選擇上一個方法",
|
||||||
"put_method": "選擇 PUT 方法",
|
"put_method": "選擇 PUT 方法",
|
||||||
"reset_request": "重置請求",
|
"reset_request": "重置請求",
|
||||||
"save_to_collections": "儲存到組合",
|
"save_to_collections": "儲存到集合",
|
||||||
"send_request": "傳送請求",
|
"send_request": "傳送請求",
|
||||||
"title": "請求"
|
"title": "請求"
|
||||||
},
|
},
|
||||||
@@ -570,7 +570,7 @@
|
|||||||
},
|
},
|
||||||
"show": {
|
"show": {
|
||||||
"code": "顯示程式碼",
|
"code": "顯示程式碼",
|
||||||
"collection": "顯示組合面板",
|
"collection": "顯示集合面板",
|
||||||
"more": "顯示更多",
|
"more": "顯示更多",
|
||||||
"sidebar": "顯示側邊欄"
|
"sidebar": "顯示側邊欄"
|
||||||
},
|
},
|
||||||
@@ -639,9 +639,9 @@
|
|||||||
"tab": {
|
"tab": {
|
||||||
"authorization": "授權",
|
"authorization": "授權",
|
||||||
"body": "請求本體",
|
"body": "請求本體",
|
||||||
"collections": "組合",
|
"collections": "集合",
|
||||||
"documentation": "幫助文件",
|
"documentation": "幫助文件",
|
||||||
"environments": "Environments",
|
"environments": "環境",
|
||||||
"headers": "請求標頭",
|
"headers": "請求標頭",
|
||||||
"history": "歷史記錄",
|
"history": "歷史記錄",
|
||||||
"mqtt": "MQTT",
|
"mqtt": "MQTT",
|
||||||
@@ -666,7 +666,7 @@
|
|||||||
"email_do_not_match": "電子信箱與您的帳號資料不一致。請聯絡您的團隊擁有者。",
|
"email_do_not_match": "電子信箱與您的帳號資料不一致。請聯絡您的團隊擁有者。",
|
||||||
"exit": "退出團隊",
|
"exit": "退出團隊",
|
||||||
"exit_disabled": "團隊擁有者無法退出團隊",
|
"exit_disabled": "團隊擁有者無法退出團隊",
|
||||||
"invalid_coll_id": "Invalid collection ID",
|
"invalid_coll_id": "集合 ID 無效",
|
||||||
"invalid_email_format": "電子信箱格式無效",
|
"invalid_email_format": "電子信箱格式無效",
|
||||||
"invalid_id": "團隊 ID 無效。請聯絡您的團隊擁有者。",
|
"invalid_id": "團隊 ID 無效。請聯絡您的團隊擁有者。",
|
||||||
"invalid_invite_link": "邀請連結無效",
|
"invalid_invite_link": "邀請連結無效",
|
||||||
@@ -690,21 +690,21 @@
|
|||||||
"member_removed": "使用者已移除",
|
"member_removed": "使用者已移除",
|
||||||
"member_role_updated": "使用者角色已更新",
|
"member_role_updated": "使用者角色已更新",
|
||||||
"members": "成員",
|
"members": "成員",
|
||||||
"more_members": "+{count} more",
|
"more_members": "還有 {count} 位",
|
||||||
"name_length_insufficient": "團隊名稱至少為 6 個字元",
|
"name_length_insufficient": "團隊名稱至少為 6 個字元",
|
||||||
"name_updated": "團隊名稱已更新",
|
"name_updated": "團隊名稱已更新",
|
||||||
"new": "新團隊",
|
"new": "新團隊",
|
||||||
"new_created": "已建立新團隊",
|
"new_created": "已建立新團隊",
|
||||||
"new_name": "我的新團隊",
|
"new_name": "我的新團隊",
|
||||||
"no_access": "您沒有編輯組合的許可權",
|
"no_access": "您沒有編輯集合的許可權",
|
||||||
"no_invite_found": "未找到邀請。請聯絡您的團隊擁有者。",
|
"no_invite_found": "未找到邀請。請聯絡您的團隊擁有者。",
|
||||||
"no_request_found": "Request not found.",
|
"no_request_found": "找不到請求。",
|
||||||
"not_found": "找不到團隊。請聯絡您的團隊擁有者。",
|
"not_found": "找不到團隊。請聯絡您的團隊擁有者。",
|
||||||
"not_valid_viewer": "您不是一個有效的檢視者。請聯絡您的團隊擁有者。",
|
"not_valid_viewer": "您不是一個有效的檢視者。請聯絡您的團隊擁有者。",
|
||||||
"parent_coll_move": "Cannot move collection to a child collection",
|
"parent_coll_move": "無法將集合移動至子集合",
|
||||||
"pending_invites": "待定邀請",
|
"pending_invites": "待定邀請",
|
||||||
"permissions": "許可權",
|
"permissions": "許可權",
|
||||||
"same_target_destination": "Same target and destination",
|
"same_target_destination": "目標和目的地相同",
|
||||||
"saved": "團隊已儲存",
|
"saved": "團隊已儲存",
|
||||||
"select_a_team": "選擇團隊",
|
"select_a_team": "選擇團隊",
|
||||||
"title": "團隊",
|
"title": "團隊",
|
||||||
@@ -734,9 +734,9 @@
|
|||||||
"url": "網址"
|
"url": "網址"
|
||||||
},
|
},
|
||||||
"workspace": {
|
"workspace": {
|
||||||
"change": "Change workspace",
|
"change": "切換工作區",
|
||||||
"personal": "My Workspace",
|
"personal": "我的工作區",
|
||||||
"team": "Team Workspace",
|
"team": "團隊工作區",
|
||||||
"title": "Workspaces"
|
"title": "工作區"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.8",
|
||||||
"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",
|
||||||
|
|||||||
34
packages/hoppscotch-common/src/components.d.ts
vendored
34
packages/hoppscotch-common/src/components.d.ts
vendored
@@ -74,27 +74,6 @@ declare module '@vue/runtime-core' {
|
|||||||
History: typeof import('./components/history/index.vue')['default']
|
History: typeof import('./components/history/index.vue')['default']
|
||||||
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
HistoryGraphqlCard: typeof import('./components/history/graphql/Card.vue')['default']
|
||||||
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
HistoryRestCard: typeof import('./components/history/rest/Card.vue')['default']
|
||||||
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
|
|
||||||
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
|
|
||||||
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
|
|
||||||
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
|
|
||||||
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
|
|
||||||
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
|
|
||||||
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
|
|
||||||
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
|
|
||||||
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
|
|
||||||
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
|
|
||||||
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
|
|
||||||
HoppSmartPicture: typeof import('@hoppscotch/ui')['HoppSmartPicture']
|
|
||||||
HoppSmartPlaceholder: typeof import('@hoppscotch/ui')['HoppSmartPlaceholder']
|
|
||||||
HoppSmartProgressRing: typeof import('@hoppscotch/ui')['HoppSmartProgressRing']
|
|
||||||
HoppSmartRadioGroup: typeof import('@hoppscotch/ui')['HoppSmartRadioGroup']
|
|
||||||
HoppSmartSlideOver: typeof import('@hoppscotch/ui')['HoppSmartSlideOver']
|
|
||||||
HoppSmartSpinner: typeof import('@hoppscotch/ui')['HoppSmartSpinner']
|
|
||||||
HoppSmartTab: typeof import('@hoppscotch/ui')['HoppSmartTab']
|
|
||||||
HoppSmartTabs: typeof import('@hoppscotch/ui')['HoppSmartTabs']
|
|
||||||
HoppSmartWindow: typeof import('@hoppscotch/ui')['HoppSmartWindow']
|
|
||||||
HoppSmartWindows: typeof import('@hoppscotch/ui')['HoppSmartWindows']
|
|
||||||
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
HttpAuthorization: typeof import('./components/http/Authorization.vue')['default']
|
||||||
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
HttpAuthorizationApiKey: typeof import('./components/http/authorization/ApiKey.vue')['default']
|
||||||
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
HttpAuthorizationBasic: typeof import('./components/http/authorization/Basic.vue')['default']
|
||||||
@@ -120,19 +99,6 @@ declare module '@vue/runtime-core' {
|
|||||||
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
HttpTestResultReport: typeof import('./components/http/TestResultReport.vue')['default']
|
||||||
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
HttpTests: typeof import('./components/http/Tests.vue')['default']
|
||||||
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
HttpURLEncodedParams: typeof import('./components/http/URLEncodedParams.vue')['default']
|
||||||
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
|
|
||||||
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
|
|
||||||
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
|
|
||||||
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
|
|
||||||
IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
|
|
||||||
IconLucideHelpCircle: typeof import('~icons/lucide/help-circle')['default']
|
|
||||||
IconLucideInbox: typeof import('~icons/lucide/inbox')['default']
|
|
||||||
IconLucideInfo: typeof import('~icons/lucide/info')['default']
|
|
||||||
IconLucideLayers: typeof import('~icons/lucide/layers')['default']
|
|
||||||
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
|
|
||||||
IconLucideMinus: typeof import('~icons/lucide/minus')['default']
|
|
||||||
IconLucideSearch: typeof import('~icons/lucide/search')['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']
|
LensesRenderersAudioLensRenderer: typeof import('./components/lenses/renderers/AudioLensRenderer.vue')['default']
|
||||||
|
|||||||
@@ -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/lucide/star-off"
|
import IconStarOff from "~icons/hopp/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"
|
||||||
|
|||||||
@@ -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/lucide/star-off"
|
import IconStarOff from "~icons/hopp/star-off"
|
||||||
import IconTrash from "~icons/lucide/trash"
|
import IconTrash from "~icons/lucide/trash"
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
|
|||||||
@@ -14,7 +14,13 @@ let keybindingsEnabled = true
|
|||||||
* Alt is also regarded as macOS OPTION (⌥) key
|
* Alt is also regarded as macOS OPTION (⌥) key
|
||||||
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
* Ctrl is also regarded as macOS COMMAND (⌘) key (NOTE: this differs from HTML Keyboard spec where COMMAND is Meta key!)
|
||||||
*/
|
*/
|
||||||
type ModifierKeys = "ctrl" | "alt" | "ctrl-shift" | "alt-shift"
|
type ModifierKeys =
|
||||||
|
| "ctrl"
|
||||||
|
| "alt"
|
||||||
|
| "ctrl-shift"
|
||||||
|
| "alt-shift"
|
||||||
|
| "ctrl-alt"
|
||||||
|
| "ctrl-alt-shift"
|
||||||
|
|
||||||
/* eslint-disable prettier/prettier */
|
/* eslint-disable prettier/prettier */
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
@@ -143,18 +149,19 @@ function getPressedKey(ev: KeyboardEvent): Key | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null {
|
function getActiveModifier(ev: KeyboardEvent): ModifierKeys | null {
|
||||||
const isShiftKey = ev.shiftKey
|
const modifierKeys = {
|
||||||
|
ctrl: isAppleDevice() ? ev.metaKey : ev.ctrlKey,
|
||||||
|
alt: ev.altKey,
|
||||||
|
shift: ev.shiftKey,
|
||||||
|
}
|
||||||
|
|
||||||
// We only allow one modifier key to be pressed (for now)
|
// active modifier: ctrl | alt | ctrl-alt | ctrl-shift | ctrl-alt-shift | alt-shift
|
||||||
// Control key (+ Command) gets priority and if Alt is also pressed, it is ignored
|
// modiferKeys object's keys are sorted to match the above order
|
||||||
if (isAppleDevice() && ev.metaKey) return isShiftKey ? "ctrl-shift" : "ctrl"
|
const activeModifier = Object.keys(modifierKeys)
|
||||||
else if (!isAppleDevice() && ev.ctrlKey)
|
.filter((key) => modifierKeys[key as keyof typeof modifierKeys])
|
||||||
return isShiftKey ? "ctrl-shift" : "ctrl"
|
.join("-")
|
||||||
|
|
||||||
// Test for Alt key
|
return activeModifier === "" ? null : (activeModifier as ModifierKeys)
|
||||||
if (ev.altKey) return isShiftKey ? "alt-shift" : "alt"
|
|
||||||
|
|
||||||
return null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
|||||||
RUN npm i -g pnpm
|
RUN npm i -g pnpm
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm install
|
RUN pnpm install --force --frozen-lockfile
|
||||||
|
|
||||||
WORKDIR /usr/src/app/packages/hoppscotch-selfhost-web/
|
WORKDIR /usr/src/app/packages/hoppscotch-selfhost-web/
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/selfhost-web",
|
"name": "@hoppscotch/selfhost-web",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev:vite": "vite",
|
"dev:vite": "vite",
|
||||||
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\" --watch",
|
"dev:gql-codegen": "graphql-codegen --require dotenv/config --config gql-codegen.yml dotenv_config_path=\"../../.env\" --watch",
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
"build": "node --max_old_space_size=16384 ./node_modules/vite/bin/vite.js build",
|
"build": "node --max_old_space_size=4096 ./node_modules/vite/bin/vite.js build",
|
||||||
"preview": "vite preview",
|
"preview": "vite preview",
|
||||||
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
"lint": "eslint src --ext .ts,.js,.vue --ignore-path .gitignore .",
|
||||||
"lint:ts": "vue-tsc --noEmit",
|
"lint:ts": "vue-tsc --noEmit",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ WORKDIR /usr/src/app
|
|||||||
RUN npm i -g pnpm
|
RUN npm i -g pnpm
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
RUN pnpm install
|
RUN pnpm install --force --frozen-lockfile
|
||||||
|
|
||||||
WORKDIR /usr/src/app/packages/hoppscotch-sh-admin/
|
WORKDIR /usr/src/app/packages/hoppscotch-sh-admin/
|
||||||
RUN pnpm run build
|
RUN pnpm run build
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-sh-admin",
|
"name": "hoppscotch-sh-admin",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.4.7",
|
"version": "2023.4.8",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
"dev": "pnpm exec npm-run-all -p -l dev:*",
|
||||||
|
|||||||
@@ -71,7 +71,7 @@
|
|||||||
"unplugin-vue-components": "^0.21.0",
|
"unplugin-vue-components": "^0.21.0",
|
||||||
"vite": "^3.2.3",
|
"vite": "^3.2.3",
|
||||||
"vite-plugin-checker": "^0.5.1",
|
"vite-plugin-checker": "^0.5.1",
|
||||||
"vite-plugin-dts": "2.0.0-beta.3",
|
"vite-plugin-dts": "3.2.0",
|
||||||
"vite-plugin-fonts": "^0.6.0",
|
"vite-plugin-fonts": "^0.6.0",
|
||||||
"vite-plugin-html-config": "^1.0.10",
|
"vite-plugin-html-config": "^1.0.10",
|
||||||
"vite-plugin-inspect": "^0.7.4",
|
"vite-plugin-inspect": "^0.7.4",
|
||||||
|
|||||||
@@ -1,62 +1,55 @@
|
|||||||
<template>
|
<template>
|
||||||
<HoppSmartLink :to="to" :exact="exact" :blank="blank" class="inline-flex items-center justify-center focus:outline-none"
|
<HoppSmartLink
|
||||||
|
:to="to"
|
||||||
|
:exact="exact"
|
||||||
|
:blank="blank"
|
||||||
|
class="inline-flex items-center justify-center focus:outline-none"
|
||||||
:class="[
|
:class="[
|
||||||
color
|
color
|
||||||
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
? `text-${color}-500 hover:text-${color}-600 focus-visible:text-${color}-600`
|
||||||
: 'hover:text-secondaryDark focus-visible:text-secondaryDark',
|
: 'hover:text-secondaryDark focus-visible:text-secondaryDark',
|
||||||
{ 'opacity-75 cursor-not-allowed': disabled },
|
{ 'opacity-75 cursor-not-allowed': disabled },
|
||||||
{ 'flex-row-reverse': reverse },
|
{ 'flex-row-reverse': reverse },
|
||||||
]" :disabled="disabled" tabindex="0">
|
]"
|
||||||
<component :is="icon" v-if="icon" class="svg-icons" :class="label ? (reverse ? 'ml-2' : 'mr-2') : ''" />
|
:disabled="disabled"
|
||||||
|
tabindex="0"
|
||||||
|
>
|
||||||
|
<component
|
||||||
|
:is="icon"
|
||||||
|
v-if="icon"
|
||||||
|
class="svg-icons"
|
||||||
|
:class="label ? (reverse ? 'ml-2' : 'mr-2') : ''"
|
||||||
|
/>
|
||||||
{{ label }}
|
{{ label }}
|
||||||
</HoppSmartLink>
|
</HoppSmartLink>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script setup lang="ts">
|
||||||
import HoppSmartLink from "./Link.vue";
|
import HoppSmartLink from "./Link.vue"
|
||||||
import { Component, defineComponent, PropType } from "vue"
|
import { Component } from "vue"
|
||||||
|
|
||||||
export default defineComponent({
|
withDefaults(
|
||||||
components: {
|
defineProps<{
|
||||||
HoppSmartLink
|
to: string
|
||||||
},
|
exact: boolean
|
||||||
props: {
|
blank: boolean
|
||||||
to: {
|
label: string
|
||||||
type: String,
|
icon: Component | null
|
||||||
default: "",
|
svg: Component | null
|
||||||
},
|
color: string
|
||||||
exact: {
|
disabled: boolean
|
||||||
type: Boolean,
|
reverse: boolean
|
||||||
default: true,
|
}>(),
|
||||||
},
|
{
|
||||||
blank: {
|
to: "",
|
||||||
type: Boolean,
|
exact: true,
|
||||||
default: false,
|
blank: false,
|
||||||
},
|
label: "",
|
||||||
label: {
|
icon: null,
|
||||||
type: String,
|
svg: null,
|
||||||
default: "",
|
color: "",
|
||||||
},
|
disabled: false,
|
||||||
icon: {
|
reverse: false,
|
||||||
type: Object as PropType<Component | null>,
|
}
|
||||||
default: null,
|
)
|
||||||
},
|
|
||||||
svg: {
|
|
||||||
type: Object as PropType<Component | null>,
|
|
||||||
default: null,
|
|
||||||
},
|
|
||||||
color: {
|
|
||||||
type: String,
|
|
||||||
default: "",
|
|
||||||
},
|
|
||||||
disabled: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
reverse: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -11,14 +11,13 @@ export default defineConfig({
|
|||||||
vue(),
|
vue(),
|
||||||
dts({
|
dts({
|
||||||
insertTypesEntry: true,
|
insertTypesEntry: true,
|
||||||
skipDiagnostics: true,
|
outDir: ["dist"],
|
||||||
outputDir: ['dist']
|
|
||||||
}),
|
}),
|
||||||
WindiCSS({
|
WindiCSS({
|
||||||
root: path.resolve(__dirname),
|
root: path.resolve(__dirname),
|
||||||
}),
|
}),
|
||||||
Icons({
|
Icons({
|
||||||
compiler: "vue3"
|
compiler: "vue3",
|
||||||
}),
|
}),
|
||||||
VitePluginFonts({
|
VitePluginFonts({
|
||||||
google: {
|
google: {
|
||||||
@@ -45,6 +44,6 @@ export default defineConfig({
|
|||||||
exports: "named",
|
exports: "named",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
emptyOutDir: true
|
emptyOutDir: true,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|||||||
284
pnpm-lock.yaml
generated
284
pnpm-lock.yaml
generated
@@ -1274,8 +1274,8 @@ importers:
|
|||||||
specifier: ^0.5.1
|
specifier: ^0.5.1
|
||||||
version: 0.5.1(eslint@8.29.0)(typescript@4.9.3)(vite@3.2.4)
|
version: 0.5.1(eslint@8.29.0)(typescript@4.9.3)(vite@3.2.4)
|
||||||
vite-plugin-dts:
|
vite-plugin-dts:
|
||||||
specifier: 2.0.0-beta.3
|
specifier: 3.2.0
|
||||||
version: 2.0.0-beta.3(@types/node@17.0.45)(rollup@2.79.1)(vite@3.2.4)
|
version: 3.2.0(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)(typescript@4.9.3)
|
||||||
vite-plugin-fonts:
|
vite-plugin-fonts:
|
||||||
specifier: ^0.6.0
|
specifier: ^0.6.0
|
||||||
version: 0.6.0(vite@3.2.4)
|
version: 0.6.0(vite@3.2.4)
|
||||||
@@ -1977,14 +1977,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.18.7
|
'@babel/types': 7.18.7
|
||||||
|
|
||||||
/@babel/parser@7.20.15:
|
|
||||||
resolution: {integrity: sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg==}
|
|
||||||
engines: {node: '>=6.0.0'}
|
|
||||||
hasBin: true
|
|
||||||
dependencies:
|
|
||||||
'@babel/types': 7.21.2
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@babel/parser@7.21.2:
|
/@babel/parser@7.21.2:
|
||||||
resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==}
|
resolution: {integrity: sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==}
|
||||||
engines: {node: '>=6.0.0'}
|
engines: {node: '>=6.0.0'}
|
||||||
@@ -1992,6 +1984,14 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@babel/types': 7.18.7
|
'@babel/types': 7.18.7
|
||||||
|
|
||||||
|
/@babel/parser@7.22.7:
|
||||||
|
resolution: {integrity: sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.21.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.18.6):
|
/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@7.18.6(@babel/core@7.18.6):
|
||||||
resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
|
resolution: {integrity: sha512-Dgxsyg54Fx1d4Nge8UnvTrED63vrwOdPmyvPzlNN/boaliRP54pm3pGzZD1SJUwrBA+Cs/xdG8kXX6Mn/RfISQ==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
@@ -5601,16 +5601,16 @@ packages:
|
|||||||
vue-i18n:
|
vue-i18n:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/message-compiler': 9.3.0-beta.19
|
'@intlify/message-compiler': 9.3.0-beta.24
|
||||||
'@intlify/shared': 9.3.0-beta.19
|
'@intlify/shared': 9.3.0-beta.24
|
||||||
jsonc-eslint-parser: 1.4.1
|
jsonc-eslint-parser: 1.4.1
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
vue-i18n: 9.2.2(vue@3.2.37)
|
vue-i18n: 9.2.2(vue@3.2.37)
|
||||||
yaml-eslint-parser: 0.3.2
|
yaml-eslint-parser: 0.3.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@intlify/bundle-utils@6.0.0:
|
/@intlify/bundle-utils@7.0.0:
|
||||||
resolution: {integrity: sha512-c8nTDgsTrBqVk3LPoF/YEarqeqcW0XAY5Y0UmFl5VKWKRNQh47jzvHRDmeRWhos5bUw1zIdiTixrs99FMJ9j5g==}
|
resolution: {integrity: sha512-+/RBsYWbiZcs97RyVb4mrsSrLmIMaI6evj30jI9f1psjXx+syRbf0ab63I5SIz290EOm6TE80fTst/Xjel+D9w==}
|
||||||
engines: {node: '>= 14.16'}
|
engines: {node: '>= 14.16'}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
petite-vue-i18n: '*'
|
petite-vue-i18n: '*'
|
||||||
@@ -5621,8 +5621,8 @@ packages:
|
|||||||
vue-i18n:
|
vue-i18n:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/message-compiler': 9.3.0-beta.17
|
'@intlify/message-compiler': 9.3.0-beta.20
|
||||||
'@intlify/shared': 9.3.0-beta.17
|
'@intlify/shared': 9.3.0-beta.20
|
||||||
acorn: 8.8.2
|
acorn: 8.8.2
|
||||||
escodegen: 2.0.0
|
escodegen: 2.0.0
|
||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
@@ -5655,33 +5655,33 @@ packages:
|
|||||||
'@intlify/shared': 9.2.2
|
'@intlify/shared': 9.2.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
/@intlify/message-compiler@9.3.0-beta.17:
|
/@intlify/message-compiler@9.3.0-beta.20:
|
||||||
resolution: {integrity: sha512-i7hvVIRk1Ax2uKa9xLRJCT57to08OhFMhFXXjWN07rmx5pWQYQ23MfX1xgggv9drnWTNhqEiD+u4EJeHoS5+Ww==}
|
resolution: {integrity: sha512-hwqQXyTnDzAVZ300SU31jO0+3OJbpOdfVU6iBkrmNpS7t2HRnVACo0EwcEXzJa++4EVDreqz5OeqJbt+PeSGGA==}
|
||||||
engines: {node: '>= 14'}
|
|
||||||
dependencies:
|
|
||||||
'@intlify/shared': 9.3.0-beta.17
|
|
||||||
source-map: 0.6.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@intlify/message-compiler@9.3.0-beta.19:
|
|
||||||
resolution: {integrity: sha512-5RBn5tMOsWh5FqM65IfEJvfpRS8R0lHEUVNDa2rNc9Y7oGEI7swezlbFqU9Kc5FyHy5Kx2jHtdgFIipDwnIYFQ==}
|
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/shared': 9.3.0-beta.19
|
'@intlify/shared': 9.3.0-beta.20
|
||||||
source-map: 0.6.1
|
source-map-js: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@intlify/message-compiler@9.3.0-beta.24:
|
||||||
|
resolution: {integrity: sha512-prhHATkgp0mpPqoVgiAtLmUc1JMvs8fMH6w53AVEBn+VF87dLhzanfmWY5FoZWORG51ag54gBDBOoM/VFv3m3A==}
|
||||||
|
engines: {node: '>= 16'}
|
||||||
|
dependencies:
|
||||||
|
'@intlify/shared': 9.3.0-beta.24
|
||||||
|
source-map-js: 1.0.2
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@intlify/shared@9.2.2:
|
/@intlify/shared@9.2.2:
|
||||||
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
|
resolution: {integrity: sha512-wRwTpsslgZS5HNyM7uDQYZtxnbI12aGiBZURX3BTR9RFIKKRWpllTsgzHWvj3HKm3Y2Sh5LPC1r0PDCKEhVn9Q==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 14'}
|
||||||
|
|
||||||
/@intlify/shared@9.3.0-beta.17:
|
/@intlify/shared@9.3.0-beta.20:
|
||||||
resolution: {integrity: sha512-mscf7RQsUTOil35jTij4KGW1RC9SWQjYScwLxP53Ns6g24iEd5HN7ksbt9O6FvTmlQuX77u+MXpBdfJsGqizLQ==}
|
resolution: {integrity: sha512-RucSPqh8O9FFxlYUysQTerSw0b9HIRpyoN1Zjogpm0qLiHK+lBNSa5sh1nCJ4wSsNcjphzgpLQCyR60GZlRV8g==}
|
||||||
engines: {node: '>= 14'}
|
engines: {node: '>= 16'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@intlify/shared@9.3.0-beta.19:
|
/@intlify/shared@9.3.0-beta.24:
|
||||||
resolution: {integrity: sha512-+lhQggrLvlQ/O5OmIYAc9gadcYXMoaDi0Doef+X/f6TLZFr9PTMjOpBWmpwNNHi026e54jckntUn6GzqDtIN4w==}
|
resolution: {integrity: sha512-AKxJ8s7eKIQWkNaf4wyyoLRwf4puCuQgjSChlDJm5JBEt6T8HGgnYTJLRXu6LD/JACn3Qwu6hM/XRX1c9yvjmQ==}
|
||||||
engines: {node: '>= 16'}
|
engines: {node: '>= 16'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
@@ -5700,8 +5700,8 @@ packages:
|
|||||||
vue-i18n:
|
vue-i18n:
|
||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/bundle-utils': 6.0.0
|
'@intlify/bundle-utils': 7.0.0
|
||||||
'@intlify/shared': 9.3.0-beta.19
|
'@intlify/shared': 9.3.0-beta.24
|
||||||
'@rollup/pluginutils': 4.2.1
|
'@rollup/pluginutils': 4.2.1
|
||||||
debug: 4.3.4(supports-color@9.2.2)
|
debug: 4.3.4(supports-color@9.2.2)
|
||||||
fast-glob: 3.2.11
|
fast-glob: 3.2.11
|
||||||
@@ -5728,7 +5728,7 @@ packages:
|
|||||||
optional: true
|
optional: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
|
'@intlify/bundle-utils': 3.4.0(vue-i18n@9.2.2)
|
||||||
'@intlify/shared': 9.3.0-beta.19
|
'@intlify/shared': 9.3.0-beta.24
|
||||||
'@rollup/pluginutils': 4.2.1
|
'@rollup/pluginutils': 4.2.1
|
||||||
debug: 4.3.4(supports-color@9.2.2)
|
debug: 4.3.4(supports-color@9.2.2)
|
||||||
fast-glob: 3.2.12
|
fast-glob: 3.2.12
|
||||||
@@ -6341,32 +6341,32 @@ packages:
|
|||||||
resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==}
|
resolution: {integrity: sha512-EWUguj2kd7ldmrF9F+vI5hUOralPd+sdsUnYbRy33vZTuZkduC1shE9TtEMEjAQwyfyMb4ole5KtjF8MsnQOlA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@microsoft/api-extractor-model@7.26.4(@types/node@17.0.45):
|
/@microsoft/api-extractor-model@7.27.4(@types/node@17.0.45):
|
||||||
resolution: {integrity: sha512-PDCgCzXDo+SLY5bsfl4bS7hxaeEtnXj7XtuzEE+BtALp7B5mK/NrS2kHWU69pohgsRmEALycQdaQPXoyT2i5MQ==}
|
resolution: {integrity: sha512-HjqQFmuGPOS20rtnu+9Jj0QrqZyR59E+piUWXPMZTTn4jaZI+4UmsHSf3Id8vyueAhOBH2cgwBuRTE5R+MfSMw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/tsdoc': 0.14.2
|
'@microsoft/tsdoc': 0.14.2
|
||||||
'@microsoft/tsdoc-config': 0.16.2
|
'@microsoft/tsdoc-config': 0.16.2
|
||||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@microsoft/api-extractor@7.34.4(@types/node@17.0.45):
|
/@microsoft/api-extractor@7.36.1(@types/node@17.0.45):
|
||||||
resolution: {integrity: sha512-HOdcci2nT40ejhwPC3Xja9G+WSJmWhCUKKryRfQYsmE9cD+pxmBaKBKCbuS9jUcl6bLLb4Gz+h7xEN5r0QiXnQ==}
|
resolution: {integrity: sha512-2SPp1jq6wDY5IOsRLUv/4FxngslctBZJlztAJ3uWpCAwqKQG7ESdL3DhEza+StbYLtBQmu1Pk6q1Vkhl7qD/bg==}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
dependencies:
|
dependencies:
|
||||||
'@microsoft/api-extractor-model': 7.26.4(@types/node@17.0.45)
|
'@microsoft/api-extractor-model': 7.27.4(@types/node@17.0.45)
|
||||||
'@microsoft/tsdoc': 0.14.2
|
'@microsoft/tsdoc': 0.14.2
|
||||||
'@microsoft/tsdoc-config': 0.16.2
|
'@microsoft/tsdoc-config': 0.16.2
|
||||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||||
'@rushstack/rig-package': 0.3.18
|
'@rushstack/rig-package': 0.4.0
|
||||||
'@rushstack/ts-command-line': 4.13.2
|
'@rushstack/ts-command-line': 4.15.1
|
||||||
colors: 1.2.5
|
colors: 1.2.5
|
||||||
lodash: 4.17.21
|
lodash: 4.17.21
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
semver: 7.3.8
|
semver: 7.3.8
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
typescript: 4.8.4
|
typescript: 5.0.4
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6932,8 +6932,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
|
resolution: {integrity: sha512-LwzQKA4vzIct1zNZzBmRKI9QuNpLgTQMEjsQLf3BXuGYb3QPTP4Yjf6mkdX+X1mYttZ808QpOwAzZjv28kq7DA==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rushstack/node-core-library@3.55.2(@types/node@17.0.45):
|
/@rushstack/node-core-library@3.59.5(@types/node@17.0.45):
|
||||||
resolution: {integrity: sha512-SaLe/x/Q/uBVdNFK5V1xXvsVps0y7h1sN7aSJllQyFbugyOaxhNRF25bwEDnicARNEjJw0pk0lYnJQ9Kr6ev0A==}
|
resolution: {integrity: sha512-1IpV7LufrI1EoVO8hYsb3t6L8L+yp40Sa0OaOV2CIu1zx4e6ZeVNaVIEXFgMXBKdGXkAh21MnCaIzlDNpG6ZQw==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@types/node': '*'
|
'@types/node': '*'
|
||||||
peerDependenciesMeta:
|
peerDependenciesMeta:
|
||||||
@@ -6950,15 +6950,15 @@ packages:
|
|||||||
z-schema: 5.0.5
|
z-schema: 5.0.5
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rushstack/rig-package@0.3.18:
|
/@rushstack/rig-package@0.4.0:
|
||||||
resolution: {integrity: sha512-SGEwNTwNq9bI3pkdd01yCaH+gAsHqs0uxfGvtw9b0LJXH52qooWXnrFTRRLG1aL9pf+M2CARdrA9HLHJys3jiQ==}
|
resolution: {integrity: sha512-FnM1TQLJYwSiurP6aYSnansprK5l8WUK8VG38CmAaZs29ZeL1msjK0AP1VS4ejD33G0kE/2cpsPsS9jDenBMxw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
resolve: 1.22.1
|
resolve: 1.22.1
|
||||||
strip-json-comments: 3.1.1
|
strip-json-comments: 3.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@rushstack/ts-command-line@4.13.2:
|
/@rushstack/ts-command-line@4.15.1:
|
||||||
resolution: {integrity: sha512-bCU8qoL9HyWiciltfzg7GqdfODUeda/JpI0602kbN5YH22rzTxyqYvv7aRLENCM7XCQ1VRs7nMkEqgJUOU8Sag==}
|
resolution: {integrity: sha512-EL4jxZe5fhb1uVL/P/wQO+Z8Rc8FMiWJ1G7VgnPDvdIt5GVjRfK7vwzder1CZQiX3x0PY6uxENYLNGTFd1InRQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/argparse': 1.0.38
|
'@types/argparse': 1.0.38
|
||||||
argparse: 1.0.10
|
argparse: 1.0.10
|
||||||
@@ -7220,15 +7220,6 @@ packages:
|
|||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@ts-morph/common@0.18.1:
|
|
||||||
resolution: {integrity: sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA==}
|
|
||||||
dependencies:
|
|
||||||
fast-glob: 3.2.12
|
|
||||||
minimatch: 5.1.0
|
|
||||||
mkdirp: 1.0.4
|
|
||||||
path-browserify: 1.0.1
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/@tsconfig/node10@1.0.9:
|
/@tsconfig/node10@1.0.9:
|
||||||
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
resolution: {integrity: sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==}
|
||||||
|
|
||||||
@@ -8394,6 +8385,12 @@ packages:
|
|||||||
muggle-string: 0.1.0
|
muggle-string: 0.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@volar/language-core@1.8.0:
|
||||||
|
resolution: {integrity: sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==}
|
||||||
|
dependencies:
|
||||||
|
'@volar/source-map': 1.8.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@volar/shared@0.27.24:
|
/@volar/shared@0.27.24:
|
||||||
resolution: {integrity: sha512-Mi8a4GQaiorfb+o4EqOXDZm9E/uBJXgScFgF+NhtcMBOUKHNMKQyLI7YRGumtyJTTdaX7nSDJjGGTkv23tcOtQ==}
|
resolution: {integrity: sha512-Mi8a4GQaiorfb+o4EqOXDZm9E/uBJXgScFgF+NhtcMBOUKHNMKQyLI7YRGumtyJTTdaX7nSDJjGGTkv23tcOtQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8418,6 +8415,12 @@ packages:
|
|||||||
muggle-string: 0.1.0
|
muggle-string: 0.1.0
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@volar/source-map@1.8.0:
|
||||||
|
resolution: {integrity: sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==}
|
||||||
|
dependencies:
|
||||||
|
muggle-string: 0.3.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@volar/transforms@0.27.24:
|
/@volar/transforms@0.27.24:
|
||||||
resolution: {integrity: sha512-sOHi1ZSapFlxn7yPl4MO5TXd9aWC0BVq2CgXAJ2EESb+ddh2uJbGQgLLNocX+MDh419cUuuFT2QAJpuWHhJcng==}
|
resolution: {integrity: sha512-sOHi1ZSapFlxn7yPl4MO5TXd9aWC0BVq2CgXAJ2EESb+ddh2uJbGQgLLNocX+MDh419cUuuFT2QAJpuWHhJcng==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8431,6 +8434,12 @@ packages:
|
|||||||
'@volar/language-core': 1.0.9
|
'@volar/language-core': 1.0.9
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@volar/typescript@1.8.0:
|
||||||
|
resolution: {integrity: sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==}
|
||||||
|
dependencies:
|
||||||
|
'@volar/language-core': 1.8.0
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@volar/vue-code-gen@0.38.2:
|
/@volar/vue-code-gen@0.38.2:
|
||||||
resolution: {integrity: sha512-whLunD6phSGWBUHZKdTxeglrpzQu26ii8CRVapFdjfyMaVhQ7ESNeIAhkTVyg2ovOPc0PiDYPQEPzfWAADIWog==}
|
resolution: {integrity: sha512-whLunD6phSGWBUHZKdTxeglrpzQu26ii8CRVapFdjfyMaVhQ7ESNeIAhkTVyg2ovOPc0PiDYPQEPzfWAADIWog==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8505,6 +8514,15 @@ packages:
|
|||||||
estree-walker: 2.0.2
|
estree-walker: 2.0.2
|
||||||
source-map: 0.6.1
|
source-map: 0.6.1
|
||||||
|
|
||||||
|
/@vue/compiler-core@3.3.4:
|
||||||
|
resolution: {integrity: sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.22.7
|
||||||
|
'@vue/shared': 3.3.4
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
source-map-js: 1.0.2
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@vue/compiler-dom@3.2.37:
|
/@vue/compiler-dom@3.2.37:
|
||||||
resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
|
resolution: {integrity: sha512-yxJLH167fucHKxaqXpYk7x8z7mMEnXOw3G2q62FTkmsvNxu4FQSu5+3UMb+L7fjKa26DEzhrmCxAgFLLIzVfqQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8523,6 +8541,13 @@ packages:
|
|||||||
'@vue/compiler-core': 3.2.45
|
'@vue/compiler-core': 3.2.45
|
||||||
'@vue/shared': 3.2.45
|
'@vue/shared': 3.2.45
|
||||||
|
|
||||||
|
/@vue/compiler-dom@3.3.4:
|
||||||
|
resolution: {integrity: sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==}
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-core': 3.3.4
|
||||||
|
'@vue/shared': 3.3.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@vue/compiler-sfc@2.7.1:
|
/@vue/compiler-sfc@2.7.1:
|
||||||
resolution: {integrity: sha512-YQRE2uYhlvyFgHmKAqySCdLm7O37XZc+yG9dujwD3h8em+rD1qGOthxc0H3XcijOy50gj/pYHgBO6C3MjV+oug==}
|
resolution: {integrity: sha512-YQRE2uYhlvyFgHmKAqySCdLm7O37XZc+yG9dujwD3h8em+rD1qGOthxc0H3XcijOy50gj/pYHgBO6C3MjV+oug==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8636,6 +8661,25 @@ packages:
|
|||||||
- supports-color
|
- supports-color
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@vue/language-core@1.8.4(typescript@4.9.3):
|
||||||
|
resolution: {integrity: sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@volar/language-core': 1.8.0
|
||||||
|
'@volar/source-map': 1.8.0
|
||||||
|
'@vue/compiler-dom': 3.3.4
|
||||||
|
'@vue/reactivity': 3.3.4
|
||||||
|
'@vue/shared': 3.3.4
|
||||||
|
minimatch: 9.0.3
|
||||||
|
muggle-string: 0.3.1
|
||||||
|
typescript: 4.9.3
|
||||||
|
vue-template-compiler: 2.7.14
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@vue/reactivity-transform@3.2.37:
|
/@vue/reactivity-transform@3.2.37:
|
||||||
resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
|
resolution: {integrity: sha512-IWopkKEb+8qpu/1eMKVeXrK0NLw9HicGviJzhJDEyfxTR9e1WtpnnbYkJWurX6WwoFP0sz10xQg8yL8lgskAZg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8679,6 +8723,12 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
'@vue/shared': 3.2.45
|
'@vue/shared': 3.2.45
|
||||||
|
|
||||||
|
/@vue/reactivity@3.3.4:
|
||||||
|
resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==}
|
||||||
|
dependencies:
|
||||||
|
'@vue/shared': 3.3.4
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@vue/runtime-core@3.2.37:
|
/@vue/runtime-core@3.2.37:
|
||||||
resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
|
resolution: {integrity: sha512-JPcd9kFyEdXLl/i0ClS7lwgcs0QpUAWj+SKX2ZC3ANKi1U4DOtiEr6cRqFXsPwY5u1L9fAjkinIdB8Rz3FoYNQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -8739,6 +8789,19 @@ packages:
|
|||||||
/@vue/shared@3.2.45:
|
/@vue/shared@3.2.45:
|
||||||
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
|
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
|
||||||
|
|
||||||
|
/@vue/shared@3.3.4:
|
||||||
|
resolution: {integrity: sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
|
/@vue/typescript@1.8.4(typescript@4.9.3):
|
||||||
|
resolution: {integrity: sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==}
|
||||||
|
dependencies:
|
||||||
|
'@volar/typescript': 1.8.0
|
||||||
|
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- typescript
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@vueuse/core@8.7.5(vue@3.2.37):
|
/@vueuse/core@8.7.5(vue@3.2.37):
|
||||||
resolution: {integrity: sha512-tqgzeZGoZcXzoit4kOGLWJibDMLp0vdm6ZO41SSUQhkhtrPhAg6dbIEPiahhUu6sZAmSYvVrZgEr5aKD51nrLA==}
|
resolution: {integrity: sha512-tqgzeZGoZcXzoit4kOGLWJibDMLp0vdm6ZO41SSUQhkhtrPhAg6dbIEPiahhUu6sZAmSYvVrZgEr5aKD51nrLA==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -9143,7 +9206,7 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
/after@0.8.2:
|
/after@0.8.2:
|
||||||
resolution: {integrity: sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=}
|
resolution: {integrity: sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/agent-base@6.0.2:
|
/agent-base@6.0.2:
|
||||||
@@ -9755,7 +9818,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
|
||||||
|
|
||||||
/base64-arraybuffer@0.1.4:
|
/base64-arraybuffer@0.1.4:
|
||||||
resolution: {integrity: sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=}
|
resolution: {integrity: sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==}
|
||||||
engines: {node: '>= 0.6.0'}
|
engines: {node: '>= 0.6.0'}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
@@ -10302,10 +10365,6 @@ packages:
|
|||||||
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/code-block-writer@11.0.3:
|
|
||||||
resolution: {integrity: sha512-NiujjUFB4SwScJq2bwbYUtXbZhBSlY6vYzm++3Q6oC+U+injTqfPYFK8wS9COOmb2lueqp0ZRB4nK1VYeHgNyw==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/collect-v8-coverage@1.0.1:
|
/collect-v8-coverage@1.0.1:
|
||||||
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
|
resolution: {integrity: sha512-iBPtljfCNcTKNAto0KEtDfZ3qzjJvqE3aTGZsbhjSBlorqpXJlaWWtPO35D+ZImoC3KWejX64o+yPGxhWSTzfg==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -10402,14 +10461,14 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/component-bind@1.0.0:
|
/component-bind@1.0.0:
|
||||||
resolution: {integrity: sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=}
|
resolution: {integrity: sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/component-emitter@1.3.0:
|
/component-emitter@1.3.0:
|
||||||
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==}
|
||||||
|
|
||||||
/component-inherit@0.0.3:
|
/component-inherit@0.0.3:
|
||||||
resolution: {integrity: sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=}
|
resolution: {integrity: sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/concat-map@0.0.1:
|
/concat-map@0.0.1:
|
||||||
@@ -13609,7 +13668,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-cors@1.1.0:
|
/has-cors@1.1.0:
|
||||||
resolution: {integrity: sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=}
|
resolution: {integrity: sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/has-flag@3.0.0:
|
/has-flag@3.0.0:
|
||||||
@@ -14002,7 +14061,7 @@ packages:
|
|||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
/indexof@0.0.1:
|
/indexof@0.0.1:
|
||||||
resolution: {integrity: sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=}
|
resolution: {integrity: sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/inflight@1.0.6:
|
/inflight@1.0.6:
|
||||||
@@ -15863,6 +15922,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ymToLHqL02udwVdbkowNpzjFd6UzozMtshPQKVi5k1EjKRqKqBrOnE9QbLEb0/pV76SAiIT13hdL8R6suc+f3g==}
|
resolution: {integrity: sha512-ymToLHqL02udwVdbkowNpzjFd6UzozMtshPQKVi5k1EjKRqKqBrOnE9QbLEb0/pV76SAiIT13hdL8R6suc+f3g==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/kolorist@1.8.0:
|
||||||
|
resolution: {integrity: sha512-Y+60/zizpJ3HRH8DCss+q95yr6145JXZo46OTpFvDZWLfRCE4qChOyk1b26nMaNpfHHgxagk9dXT5OP0Tfe+dQ==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/leac@0.6.0:
|
/leac@0.6.0:
|
||||||
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
|
||||||
dev: false
|
dev: false
|
||||||
@@ -16231,13 +16294,6 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
sourcemap-codec: 1.4.8
|
sourcemap-codec: 1.4.8
|
||||||
|
|
||||||
/magic-string@0.29.0:
|
|
||||||
resolution: {integrity: sha512-WcfidHrDjMY+eLjlU+8OvwREqHwpgCeKVBUpQ3OhYYuvfaYCUgcbuBzappNzZvg/v8onU3oQj+BYpkOJe9Iw4Q==}
|
|
||||||
engines: {node: '>=12'}
|
|
||||||
dependencies:
|
|
||||||
'@jridgewell/sourcemap-codec': 1.4.14
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/magic-string@0.30.0:
|
/magic-string@0.30.0:
|
||||||
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
resolution: {integrity: sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -16493,6 +16549,13 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
brace-expansion: 2.0.1
|
brace-expansion: 2.0.1
|
||||||
|
|
||||||
|
/minimatch@9.0.3:
|
||||||
|
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
|
||||||
|
engines: {node: '>=16 || 14 >=14.17'}
|
||||||
|
dependencies:
|
||||||
|
brace-expansion: 2.0.1
|
||||||
|
dev: true
|
||||||
|
|
||||||
/minimist-options@4.1.0:
|
/minimist-options@4.1.0:
|
||||||
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
|
resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==}
|
||||||
engines: {node: '>= 6'}
|
engines: {node: '>= 6'}
|
||||||
@@ -16971,6 +17034,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
|
resolution: {integrity: sha512-Tr1knR3d2mKvvWthlk7202rywKbiOm4rVFLsfAaSIhJ6dt9o47W4S+JMtWhd/PW9Wrdew2/S2fSvhz3E2gkfEg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/muggle-string@0.3.1:
|
||||||
|
resolution: {integrity: sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/multer@1.4.4-lts.1:
|
/multer@1.4.4-lts.1:
|
||||||
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
|
resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==}
|
||||||
engines: {node: '>= 6.0.0'}
|
engines: {node: '>= 6.0.0'}
|
||||||
@@ -17569,10 +17636,6 @@ packages:
|
|||||||
utils-merge: 1.0.1
|
utils-merge: 1.0.1
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/path-browserify@1.0.1:
|
|
||||||
resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==}
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/path-case@3.0.4:
|
/path-case@3.0.4:
|
||||||
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
|
resolution: {integrity: sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -19709,7 +19772,7 @@ packages:
|
|||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/to-array@0.1.4:
|
/to-array@0.1.4:
|
||||||
resolution: {integrity: sha1-F+bBH3PdTz10zaek/zI46a2b+JA=}
|
resolution: {integrity: sha512-LhVdShQD/4Mk4zXNroIQZJC+Ap3zgLcDuwEdcmLv9CCO73NWockQDwyUnW/m8VX/EElfL6FcYx7EeutN4HJA6A==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/to-fast-properties@2.0.0:
|
/to-fast-properties@2.0.0:
|
||||||
@@ -19899,13 +19962,6 @@ packages:
|
|||||||
resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==}
|
resolution: {integrity: sha512-DEQrfv6l7IvN2jlzc/VTdZJYsWUnQNCsueYjMkC/iXoEoi5fNan6MjeDqkvhfzbmHgdz9UxDUluX3V5HdjTydQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/ts-morph@17.0.1:
|
|
||||||
resolution: {integrity: sha512-10PkHyXmrtsTvZSL+cqtJLTgFXkU43Gd0JCc0Rw6GchWbqKe0Rwgt1v3ouobTZwQzF1mGhDeAlWYBMGRV7y+3g==}
|
|
||||||
dependencies:
|
|
||||||
'@ts-morph/common': 0.18.1
|
|
||||||
code-block-writer: 11.0.3
|
|
||||||
dev: true
|
|
||||||
|
|
||||||
/ts-node-dev@2.0.0(@types/node@18.11.10)(typescript@4.9.3):
|
/ts-node-dev@2.0.0(@types/node@18.11.10)(typescript@4.9.3):
|
||||||
resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
|
resolution: {integrity: sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w==}
|
||||||
engines: {node: '>=0.8.0'}
|
engines: {node: '>=0.8.0'}
|
||||||
@@ -20213,6 +20269,12 @@ packages:
|
|||||||
hasBin: true
|
hasBin: true
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/typescript@5.0.4:
|
||||||
|
resolution: {integrity: sha512-cW9T5W9xY37cc+jfEnaUvX91foxtHkza3Nw3wkoF4sSlKn0MONdkdEndig/qPBWXNkmplh3NzayQzCiHM4/hqw==}
|
||||||
|
engines: {node: '>=12.20'}
|
||||||
|
hasBin: true
|
||||||
|
dev: true
|
||||||
|
|
||||||
/ua-parser-js@0.7.31:
|
/ua-parser-js@0.7.31:
|
||||||
resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==}
|
resolution: {integrity: sha512-qLK/Xe9E2uzmYI3qLeOmI0tEOt+TBBQyUIAh4aAgU05FVYzeZrKUdkAZfBNVGRaHVgV0TDkdEngJSw/SyQchkQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -20746,27 +20808,31 @@ packages:
|
|||||||
vscode-uri: 3.0.3
|
vscode-uri: 3.0.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-plugin-dts@2.0.0-beta.3(@types/node@17.0.45)(rollup@2.79.1)(vite@3.2.4):
|
/vite-plugin-dts@3.2.0(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)(typescript@4.9.3):
|
||||||
resolution: {integrity: sha512-QrsbTxyt0choSYXPxPfmN9XcSvxcVZk0zticxLrI5DkECs9KhDrSVGok1YP/UPkoKpfF9ThtOJcM5Rjuesxv/w==}
|
resolution: {integrity: sha512-s+dwJvDcb/AWgb49oVbq9JiUSIMwaVpFfV4SVIaBZmv9OZyeyDGxujaq+z4HJ4LB4hUG5c4oRAJyLfV66c763Q==}
|
||||||
engines: {node: ^14.18.0 || >=16.0.0}
|
engines: {node: ^14.18.0 || >=16.0.0}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
vite: '>=2.9.0'
|
typescript: '*'
|
||||||
dependencies:
|
dependencies:
|
||||||
'@babel/parser': 7.20.15
|
'@microsoft/api-extractor': 7.36.1(@types/node@17.0.45)
|
||||||
'@microsoft/api-extractor': 7.34.4(@types/node@17.0.45)
|
|
||||||
'@rollup/pluginutils': 5.0.2(rollup@2.79.1)
|
'@rollup/pluginutils': 5.0.2(rollup@2.79.1)
|
||||||
'@rushstack/node-core-library': 3.55.2(@types/node@17.0.45)
|
'@rushstack/node-core-library': 3.59.5(@types/node@17.0.45)
|
||||||
|
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||||
debug: 4.3.4(supports-color@9.2.2)
|
debug: 4.3.4(supports-color@9.2.2)
|
||||||
fast-glob: 3.2.12
|
kolorist: 1.8.0
|
||||||
fs-extra: 10.1.0
|
typescript: 4.9.3
|
||||||
kolorist: 1.7.0
|
vue-tsc: 1.8.4(typescript@4.9.3)
|
||||||
magic-string: 0.29.0
|
optionalDependencies:
|
||||||
ts-morph: 17.0.1
|
rollup: 2.79.1
|
||||||
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)
|
vite: 3.2.4(@types/node@17.0.45)(sass@1.53.0)(terser@5.14.1)
|
||||||
transitivePeerDependencies:
|
transitivePeerDependencies:
|
||||||
- '@types/node'
|
- '@types/node'
|
||||||
- rollup
|
- less
|
||||||
|
- sass
|
||||||
|
- stylus
|
||||||
|
- sugarss
|
||||||
- supports-color
|
- supports-color
|
||||||
|
- terser
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/vite-plugin-eslint@1.8.1(eslint@8.29.0)(vite@3.2.4):
|
/vite-plugin-eslint@1.8.1(eslint@8.29.0)(vite@3.2.4):
|
||||||
@@ -21500,6 +21566,18 @@ packages:
|
|||||||
typescript: 4.9.3
|
typescript: 4.9.3
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/vue-tsc@1.8.4(typescript@4.9.3):
|
||||||
|
resolution: {integrity: sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
dependencies:
|
||||||
|
'@vue/language-core': 1.8.4(typescript@4.9.3)
|
||||||
|
'@vue/typescript': 1.8.4(typescript@4.9.3)
|
||||||
|
semver: 7.3.8
|
||||||
|
typescript: 4.9.3
|
||||||
|
dev: true
|
||||||
|
|
||||||
/vue@2.7.1:
|
/vue@2.7.1:
|
||||||
resolution: {integrity: sha512-X1YkFddhbTAU2FPK0gBZ/vDOcOMA8ZT4uHoFVor1bUb7BpVGdEswS286YGtODsf/Ghfr1LM1sBMFAY8XT+dVhA==}
|
resolution: {integrity: sha512-X1YkFddhbTAU2FPK0gBZ/vDOcOMA8ZT4uHoFVor1bUb7BpVGdEswS286YGtODsf/Ghfr1LM1sBMFAY8XT+dVhA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -22290,7 +22368,7 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yeast@0.1.2:
|
/yeast@0.1.2:
|
||||||
resolution: {integrity: sha1-AI4G2AlDIMNy28L47XagymyKxBk=}
|
resolution: {integrity: sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/yn@3.1.1:
|
/yn@3.1.1:
|
||||||
|
|||||||
Reference in New Issue
Block a user