Compare commits

..

1 Commits

Author SHA1 Message Date
nivedin
00588bcc0a fix: interceptor error from extension issue 2023-11-14 17:17:07 +05:30
281 changed files with 3984 additions and 10117 deletions

View File

@@ -12,8 +12,8 @@ SESSION_SECRET='add some secret here'
# Hoppscotch App Domain Config # Hoppscotch App Domain Config
REDIRECT_URL="http://localhost:3000" REDIRECT_URL="http://localhost:3000"
WHITELISTED_ORIGINS="http://localhost:3170,http://localhost:3000,http://localhost:3100" WHITELISTED_ORIGINS = "http://localhost:3170,http://localhost:3000,http://localhost:3100"
VITE_ALLOWED_AUTH_PROVIDERS=GOOGLE,GITHUB,MICROSOFT,EMAIL VITE_ALLOWED_AUTH_PROVIDERS = GOOGLE,GITHUB,MICROSOFT,EMAIL
# Google Auth Config # Google Auth Config
GOOGLE_CLIENT_ID="************************************************" GOOGLE_CLIENT_ID="************************************************"
@@ -59,6 +59,3 @@ VITE_BACKEND_API_URL=http://localhost:3170/v1
# Terms Of Service And Privacy Policy Links (Optional) # Terms Of Service And Privacy Policy Links (Optional)
VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms
VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy
# Set to `true` for subpath based access
ENABLE_SUBPATH_BASED_ACCESS=false

14
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,14 @@
{
"recommendations": [
"antfu.iconify",
"vue.volar",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"editorconfig.editorconfig",
"csstools.postcss",
"folke.vscode-monorepo-workspace"
],
"unwantedRecommendations": [
"octref.vetur"
]
}

View File

@@ -1,19 +0,0 @@
:3000 {
try_files {path} /
root * /site/selfhost-web
file_server
}
:3100 {
try_files {path} /
root * /site/sh-admin-multiport-setup
file_server
}
:3170 {
reverse_proxy localhost:8080
}
:80 {
respond 404
}

View File

@@ -1,37 +0,0 @@
:3000 {
respond 404
}
:3100 {
respond 404
}
:3170 {
reverse_proxy localhost:8080
}
:80 {
# Serve the `selfhost-web` SPA by default
root * /site/selfhost-web
file_server
handle_path /admin* {
root * /site/sh-admin-subpath-access
file_server
# Ensures any non-existent file in the server is routed to the SPA
try_files {path} /
}
# Handle requests under `/backend*` path
handle_path /backend* {
reverse_proxy localhost:8080
}
# Catch-all route for unknown paths, serves `selfhost-web` SPA
handle {
root * /site/selfhost-web
file_server
try_files {path} /
}
}

11
aio.Caddyfile Normal file
View File

@@ -0,0 +1,11 @@
:3000 {
try_files {path} /
root * /site/selfhost-web
file_server
}
:3100 {
try_files {path} /
root * /site/sh-admin
file_server
}

View File

@@ -49,8 +49,7 @@ execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
fs.rmSync("build.env") fs.rmSync("build.env")
const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'aio-subpath-access.Caddyfile' : 'aio-multiport-setup.Caddyfile' const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server") const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
caddyProcess.on("exit", (code) => { caddyProcess.on("exit", (code) => {

View File

@@ -17,7 +17,7 @@ services:
environment: environment:
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well) # Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
- 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=8080 - PORT=3170
volumes: volumes:
# Uncomment the line below when modifying code. Only applicable when using the "dev" target. # Uncomment the line below when modifying code. Only applicable when using the "dev" target.
# - ./packages/hoppscotch-backend/:/usr/src/app # - ./packages/hoppscotch-backend/:/usr/src/app
@@ -26,7 +26,6 @@ services:
hoppscotch-db: hoppscotch-db:
condition: service_healthy condition: service_healthy
ports: ports:
- "3180:80"
- "3170:3170" - "3170:3170"
# The main hoppscotch app. This will be hosted at port 3000 # The main hoppscotch app. This will be hosted at port 3000
@@ -43,8 +42,7 @@ services:
depends_on: depends_on:
- hoppscotch-backend - hoppscotch-backend
ports: ports:
- "3080:80" - "3000:8080"
- "3000:3000"
# The Self Host dashboard for managing the app. This will be hosted at port 3100 # The Self Host dashboard for managing the app. This will be hosted at port 3100
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for # NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
@@ -60,8 +58,7 @@ services:
depends_on: depends_on:
- hoppscotch-backend - hoppscotch-backend
ports: ports:
- "3280:80" - "3100:8080"
- "3100:3100"
# The service that spins up all 3 services at once in one container # The service that spins up all 3 services at once in one container
hoppscotch-aio: hoppscotch-aio:
@@ -79,7 +76,6 @@ services:
- "3000:3000" - "3000:3000"
- "3100:3100" - "3100:3100"
- "3170:3170" - "3170:3170"
- "3080:80"
# The preset DB service, you can delete/comment the below lines if # The preset DB service, you can delete/comment the below lines if
# you are using an external postgres instance # you are using an external postgres instance

View File

@@ -1,3 +0,0 @@
:80 :3170 {
reverse_proxy localhost:8080
}

View File

@@ -1,6 +1,6 @@
{ {
"name": "hoppscotch-backend", "name": "hoppscotch-backend",
"version": "2023.8.4-1", "version": "2023.8.3-1",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,

View File

@@ -1,15 +0,0 @@
/*
Warnings:
- A unique constraint covering the columns `[id]` on the table `Shortcode` will be added. If there are existing duplicate values, this will fail.
*/
-- AlterTable
ALTER TABLE "Shortcode" ADD COLUMN "embedProperties" JSONB,
ADD COLUMN "updatedOn" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
-- CreateIndex
CREATE UNIQUE INDEX "Shortcode_id_key" ON "Shortcode"("id");
-- AddForeignKey
ALTER TABLE "Shortcode" ADD CONSTRAINT "Shortcode_creatorUid_fkey" FOREIGN KEY ("creatorUid") REFERENCES "User"("uid") ON DELETE SET NULL ON UPDATE CASCADE;

View File

@@ -1,5 +0,0 @@
-- AlterTable
ALTER TABLE "TeamCollection" ADD COLUMN "data" JSONB;
-- AlterTable
ALTER TABLE "UserCollection" ADD COLUMN "data" JSONB;

View File

@@ -43,7 +43,6 @@ model TeamInvitation {
model TeamCollection { model TeamCollection {
id String @id @default(cuid()) id String @id @default(cuid())
parentID String? parentID String?
data Json?
parent TeamCollection? @relation("TeamCollectionChildParent", fields: [parentID], references: [id]) parent TeamCollection? @relation("TeamCollectionChildParent", fields: [parentID], references: [id])
children TeamCollection[] @relation("TeamCollectionChildParent") children TeamCollection[] @relation("TeamCollectionChildParent")
requests TeamRequest[] requests TeamRequest[]
@@ -69,13 +68,10 @@ model TeamRequest {
} }
model Shortcode { model Shortcode {
id String @id @unique id String @id
request Json request Json
embedProperties Json?
creatorUid String? creatorUid String?
User User? @relation(fields: [creatorUid], references: [uid])
createdOn DateTime @default(now()) createdOn DateTime @default(now())
updatedOn DateTime @default(now()) @updatedAt
@@unique(fields: [id, creatorUid], name: "creator_uid_shortcode_unique") @@unique(fields: [id, creatorUid], name: "creator_uid_shortcode_unique")
} }
@@ -106,7 +102,6 @@ model User {
currentGQLSession Json? currentGQLSession Json?
createdOn DateTime @default(now()) @db.Timestamp(3) createdOn DateTime @default(now()) @db.Timestamp(3)
invitedUsers InvitedUsers[] invitedUsers InvitedUsers[]
shortcodes Shortcode[]
} }
model Account { model Account {
@@ -197,7 +192,6 @@ model UserCollection {
userUid String userUid String
user User @relation(fields: [userUid], references: [uid], onDelete: Cascade) user User @relation(fields: [userUid], references: [uid], onDelete: Cascade)
title String title String
data Json?
orderIndex Int orderIndex Int
type ReqType type ReqType
createdOn DateTime @default(now()) @db.Timestamp(3) createdOn DateTime @default(now()) @db.Timestamp(3)

View File

@@ -1,66 +0,0 @@
#!/usr/local/bin/node
// @ts-check
import { spawn } from 'child_process';
import process from 'process';
function runChildProcessWithPrefix(command, args, prefix) {
const childProcess = spawn(command, args);
childProcess.stdout.on('data', (data) => {
const output = data.toString().trim().split('\n');
output.forEach((line) => {
console.log(`${prefix} | ${line}`);
});
});
childProcess.stderr.on('data', (data) => {
const error = data.toString().trim().split('\n');
error.forEach((line) => {
console.error(`${prefix} | ${line}`);
});
});
childProcess.on('close', (code) => {
console.log(`${prefix} Child process exited with code ${code}`);
});
childProcess.on('error', (stuff) => {
console.error('error');
console.error(stuff);
});
return childProcess;
}
const caddyProcess = runChildProcessWithPrefix(
'caddy',
['run', '--config', '/etc/caddy/backend.Caddyfile', '--adapter', 'caddyfile'],
'App/Admin Dashboard Caddy',
);
const backendProcess = runChildProcessWithPrefix(
'pnpm',
['run', 'start:prod'],
'Backend Server',
);
caddyProcess.on('exit', (code) => {
console.log(`Exiting process because Caddy Server exited with code ${code}`);
process.exit(code);
});
backendProcess.on('exit', (code) => {
console.log(
`Exiting process because Backend Server exited with code ${code}`,
);
process.exit(code);
});
process.on('SIGINT', () => {
console.log('SIGINT received, exiting...');
caddyProcess.kill('SIGINT');
backendProcess.kill('SIGINT');
process.exit(0);
});

View File

@@ -1,9 +1,4 @@
import { ObjectType, OmitType } from '@nestjs/graphql'; import { ObjectType } from '@nestjs/graphql';
import { User } from 'src/user/user.model';
@ObjectType() @ObjectType()
export class Admin extends OmitType(User, [ export class Admin {}
'isAdmin',
'currentRESTSession',
'currentGQLSession',
]) {}

View File

@@ -10,8 +10,6 @@ import { TeamInvitationModule } from '../team-invitation/team-invitation.module'
import { TeamEnvironmentsModule } from '../team-environments/team-environments.module'; import { TeamEnvironmentsModule } from '../team-environments/team-environments.module';
import { TeamCollectionModule } from '../team-collection/team-collection.module'; import { TeamCollectionModule } from '../team-collection/team-collection.module';
import { TeamRequestModule } from '../team-request/team-request.module'; import { TeamRequestModule } from '../team-request/team-request.module';
import { InfraResolver } from './infra.resolver';
import { ShortcodeModule } from 'src/shortcode/shortcode.module';
@Module({ @Module({
imports: [ imports: [
@@ -24,9 +22,8 @@ import { ShortcodeModule } from 'src/shortcode/shortcode.module';
TeamEnvironmentsModule, TeamEnvironmentsModule,
TeamCollectionModule, TeamCollectionModule,
TeamRequestModule, TeamRequestModule,
ShortcodeModule,
], ],
providers: [InfraResolver, AdminResolver, AdminService], providers: [AdminResolver, AdminService],
exports: [AdminService], exports: [AdminService],
}) })
export class AdminModule {} export class AdminModule {}

View File

@@ -21,15 +21,15 @@ import { InvitedUser } from './invited-user.model';
import { GqlUser } from '../decorators/gql-user.decorator'; import { GqlUser } from '../decorators/gql-user.decorator';
import { PubSubService } from '../pubsub/pubsub.service'; import { PubSubService } from '../pubsub/pubsub.service';
import { Team, TeamMember } from '../team/team.model'; import { Team, TeamMember } from '../team/team.model';
import { User } from '../user/user.model';
import { TeamInvitation } from '../team-invitation/team-invitation.model';
import { PaginationArgs } from '../types/input-types.args';
import { import {
AddUserToTeamArgs, AddUserToTeamArgs,
ChangeUserRoleInTeamArgs, ChangeUserRoleInTeamArgs,
} from './input-types.args'; } from './input-types.args';
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 { User } from 'src/user/user.model';
import { PaginationArgs } from 'src/types/input-types.args';
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
@UseGuards(GqlThrottlerGuard) @UseGuards(GqlThrottlerGuard)
@Resolver(() => Admin) @Resolver(() => Admin)
@@ -51,7 +51,6 @@ export class AdminResolver {
@ResolveField(() => [User], { @ResolveField(() => [User], {
description: 'Returns a list of all admin users in infra', description: 'Returns a list of all admin users in infra',
deprecationReason: 'Use `infra` query instead',
}) })
@UseGuards(GqlAuthGuard, GqlAdminGuard) @UseGuards(GqlAuthGuard, GqlAdminGuard)
async admins() { async admins() {
@@ -60,7 +59,6 @@ export class AdminResolver {
} }
@ResolveField(() => User, { @ResolveField(() => User, {
description: 'Returns a user info by UID', description: 'Returns a user info by UID',
deprecationReason: 'Use `infra` query instead',
}) })
@UseGuards(GqlAuthGuard, GqlAdminGuard) @UseGuards(GqlAuthGuard, GqlAdminGuard)
async userInfo( async userInfo(
@@ -78,7 +76,6 @@ export class AdminResolver {
@ResolveField(() => [User], { @ResolveField(() => [User], {
description: 'Returns a list of all the users in infra', description: 'Returns a list of all the users in infra',
deprecationReason: 'Use `infra` query instead',
}) })
@UseGuards(GqlAuthGuard, GqlAdminGuard) @UseGuards(GqlAuthGuard, GqlAdminGuard)
async allUsers( async allUsers(
@@ -91,7 +88,6 @@ export class AdminResolver {
@ResolveField(() => [InvitedUser], { @ResolveField(() => [InvitedUser], {
description: 'Returns a list of all the invited users', description: 'Returns a list of all the invited users',
deprecationReason: 'Use `infra` query instead',
}) })
async invitedUsers(@Parent() admin: Admin): Promise<InvitedUser[]> { async invitedUsers(@Parent() admin: Admin): Promise<InvitedUser[]> {
const users = await this.adminService.fetchInvitedUsers(); const users = await this.adminService.fetchInvitedUsers();
@@ -100,7 +96,6 @@ export class AdminResolver {
@ResolveField(() => [Team], { @ResolveField(() => [Team], {
description: 'Returns a list of all the teams in the infra', description: 'Returns a list of all the teams in the infra',
deprecationReason: 'Use `infra` query instead',
}) })
async allTeams( async allTeams(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -111,7 +106,6 @@ export class AdminResolver {
} }
@ResolveField(() => Team, { @ResolveField(() => Team, {
description: 'Returns a team info by ID when requested by Admin', description: 'Returns a team info by ID when requested by Admin',
deprecationReason: 'Use `infra` query instead',
}) })
async teamInfo( async teamInfo(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -129,7 +123,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return count of all the members in a team', description: 'Return count of all the members in a team',
deprecationReason: 'Use `infra` query instead',
}) })
async membersCountInTeam( async membersCountInTeam(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -147,7 +140,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return count of all the stored collections in a team', description: 'Return count of all the stored collections in a team',
deprecationReason: 'Use `infra` query instead',
}) })
async collectionCountInTeam( async collectionCountInTeam(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -163,7 +155,6 @@ export class AdminResolver {
} }
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return count of all the stored requests in a team', description: 'Return count of all the stored requests in a team',
deprecationReason: 'Use `infra` query instead',
}) })
async requestCountInTeam( async requestCountInTeam(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -180,7 +171,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return count of all the stored environments in a team', description: 'Return count of all the stored environments in a team',
deprecationReason: 'Use `infra` query instead',
}) })
async environmentCountInTeam( async environmentCountInTeam(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -197,7 +187,6 @@ export class AdminResolver {
@ResolveField(() => [TeamInvitation], { @ResolveField(() => [TeamInvitation], {
description: 'Return all the pending invitations in a team', description: 'Return all the pending invitations in a team',
deprecationReason: 'Use `infra` query instead',
}) })
async pendingInvitationCountInTeam( async pendingInvitationCountInTeam(
@Parent() admin: Admin, @Parent() admin: Admin,
@@ -216,7 +205,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return total number of Users in organization', description: 'Return total number of Users in organization',
deprecationReason: 'Use `infra` query instead',
}) })
async usersCount() { async usersCount() {
return this.adminService.getUsersCount(); return this.adminService.getUsersCount();
@@ -224,7 +212,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return total number of Teams in organization', description: 'Return total number of Teams in organization',
deprecationReason: 'Use `infra` query instead',
}) })
async teamsCount() { async teamsCount() {
return this.adminService.getTeamsCount(); return this.adminService.getTeamsCount();
@@ -232,7 +219,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return total number of Team Collections in organization', description: 'Return total number of Team Collections in organization',
deprecationReason: 'Use `infra` query instead',
}) })
async teamCollectionsCount() { async teamCollectionsCount() {
return this.adminService.getTeamCollectionsCount(); return this.adminService.getTeamCollectionsCount();
@@ -240,7 +226,6 @@ export class AdminResolver {
@ResolveField(() => Number, { @ResolveField(() => Number, {
description: 'Return total number of Team Requests in organization', description: 'Return total number of Team Requests in organization',
deprecationReason: 'Use `infra` query instead',
}) })
async teamRequestsCount() { async teamRequestsCount() {
return this.adminService.getTeamRequestsCount(); return this.adminService.getTeamRequestsCount();
@@ -443,23 +428,6 @@ export class AdminResolver {
return true; return true;
} }
@Mutation(() => Boolean, {
description: 'Revoke Shortcode by ID',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async revokeShortcodeByAdmin(
@Args({
name: 'code',
description: 'The shortcode to delete',
type: () => ID,
})
code: string,
): Promise<boolean> {
const res = await this.adminService.deleteShortcode(code);
if (E.isLeft(res)) throwErr(res.left);
return true;
}
/* Subscriptions */ /* Subscriptions */
@Subscription(() => InvitedUser, { @Subscription(() => InvitedUser, {

View File

@@ -15,7 +15,6 @@ import {
INVALID_EMAIL, INVALID_EMAIL,
USER_ALREADY_INVITED, USER_ALREADY_INVITED,
} from '../errors'; } from '../errors';
import { ShortcodeService } from 'src/shortcode/shortcode.service';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
const mockPubSub = mockDeep<PubSubService>(); const mockPubSub = mockDeep<PubSubService>();
@@ -26,7 +25,6 @@ const mockTeamRequestService = mockDeep<TeamRequestService>();
const mockTeamInvitationService = mockDeep<TeamInvitationService>(); const mockTeamInvitationService = mockDeep<TeamInvitationService>();
const mockTeamCollectionService = mockDeep<TeamCollectionService>(); const mockTeamCollectionService = mockDeep<TeamCollectionService>();
const mockMailerService = mockDeep<MailerService>(); const mockMailerService = mockDeep<MailerService>();
const mockShortcodeService = mockDeep<ShortcodeService>();
const adminService = new AdminService( const adminService = new AdminService(
mockUserService, mockUserService,
@@ -38,7 +36,6 @@ const adminService = new AdminService(
mockPubSub as any, mockPubSub as any,
mockPrisma as any, mockPrisma as any,
mockMailerService, mockMailerService,
mockShortcodeService,
); );
const invitedUsers: InvitedUsers[] = [ const invitedUsers: InvitedUsers[] = [

View File

@@ -24,7 +24,6 @@ import { TeamRequestService } from '../team-request/team-request.service';
import { TeamEnvironmentsService } from '../team-environments/team-environments.service'; import { TeamEnvironmentsService } from '../team-environments/team-environments.service';
import { TeamInvitationService } from '../team-invitation/team-invitation.service'; import { TeamInvitationService } from '../team-invitation/team-invitation.service';
import { TeamMemberRole } from '../team/team.model'; import { TeamMemberRole } from '../team/team.model';
import { ShortcodeService } from 'src/shortcode/shortcode.service';
@Injectable() @Injectable()
export class AdminService { export class AdminService {
@@ -38,7 +37,6 @@ export class AdminService {
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
private readonly prisma: PrismaService, private readonly prisma: PrismaService,
private readonly mailerService: MailerService, private readonly mailerService: MailerService,
private readonly shortcodeService: ShortcodeService,
) {} ) {}
/** /**
@@ -434,35 +432,4 @@ export class AdminService {
return E.right(teamInvite.right); return E.right(teamInvite.right);
} }
/**
* Fetch all created ShortCodes
*
* @param args Pagination arguments
* @param userEmail User email
* @returns ShortcodeWithUserEmail
*/
async fetchAllShortcodes(
cursorID: string,
take: number,
userEmail: string = null,
) {
return this.shortcodeService.fetchAllShortcodes(
{ cursor: cursorID, take },
userEmail,
);
}
/**
* Delete a Shortcode
*
* @param shortcodeID ID of Shortcode being deleted
* @returns Boolean on successful deletion
*/
async deleteShortcode(shortcodeID: string) {
const result = await this.shortcodeService.deleteShortcode(shortcodeID);
if (E.isLeft(result)) return E.left(result.left);
return E.right(result.right);
}
} }

View File

@@ -1,10 +0,0 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { Admin } from './admin.model';
@ObjectType()
export class Infra {
@Field(() => Admin, {
description: 'Admin who executed the action',
})
executedBy: Admin;
}

View File

@@ -1,225 +0,0 @@
import { UseGuards } from '@nestjs/common';
import { Args, ID, Query, ResolveField, Resolver } from '@nestjs/graphql';
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
import { Infra } from './infra.model';
import { AdminService } from './admin.service';
import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
import { GqlAdminGuard } from './guards/gql-admin.guard';
import { User } from 'src/user/user.model';
import { AuthUser } from 'src/types/AuthUser';
import { throwErr } from 'src/utils';
import * as E from 'fp-ts/Either';
import { Admin } from './admin.model';
import { PaginationArgs } from 'src/types/input-types.args';
import { InvitedUser } from './invited-user.model';
import { Team } from 'src/team/team.model';
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
import { GqlAdmin } from './decorators/gql-admin.decorator';
import { ShortcodeWithUserEmail } from 'src/shortcode/shortcode.model';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => Infra)
export class InfraResolver {
constructor(private adminService: AdminService) {}
@Query(() => Infra, {
description: 'Fetch details of the Infrastructure',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
infra(@GqlAdmin() admin: Admin) {
const infra: Infra = { executedBy: admin };
return infra;
}
@ResolveField(() => [User], {
description: 'Returns a list of all admin users in infra',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async admins() {
const admins = await this.adminService.fetchAdmins();
return admins;
}
@ResolveField(() => User, {
description: 'Returns a user info by UID',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async userInfo(
@Args({
name: 'userUid',
type: () => ID,
description: 'The user UID',
})
userUid: string,
): Promise<AuthUser> {
const user = await this.adminService.fetchUserInfo(userUid);
if (E.isLeft(user)) throwErr(user.left);
return user.right;
}
@ResolveField(() => [User], {
description: 'Returns a list of all the users in infra',
})
@UseGuards(GqlAuthGuard, GqlAdminGuard)
async allUsers(@Args() args: PaginationArgs): Promise<AuthUser[]> {
const users = await this.adminService.fetchUsers(args.cursor, args.take);
return users;
}
@ResolveField(() => [InvitedUser], {
description: 'Returns a list of all the invited users',
})
async invitedUsers(): Promise<InvitedUser[]> {
const users = await this.adminService.fetchInvitedUsers();
return users;
}
@ResolveField(() => [Team], {
description: 'Returns a list of all the teams in the infra',
})
async allTeams(@Args() args: PaginationArgs): Promise<Team[]> {
const teams = await this.adminService.fetchAllTeams(args.cursor, args.take);
return teams;
}
@ResolveField(() => Team, {
description: 'Returns a team info by ID when requested by Admin',
})
async teamInfo(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which info to fetch',
})
teamID: string,
): Promise<Team> {
const team = await this.adminService.getTeamInfo(teamID);
if (E.isLeft(team)) throwErr(team.left);
return team.right;
}
@ResolveField(() => Number, {
description: 'Return count of all the members in a team',
})
async membersCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
nullable: false,
})
teamID: string,
): Promise<number> {
const teamMembersCount = await this.adminService.membersCountInTeam(teamID);
return teamMembersCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored collections in a team',
})
async collectionCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const teamCollCount = await this.adminService.collectionCountInTeam(teamID);
return teamCollCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored requests in a team',
})
async requestCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const teamReqCount = await this.adminService.requestCountInTeam(teamID);
return teamReqCount;
}
@ResolveField(() => Number, {
description: 'Return count of all the stored environments in a team',
})
async environmentCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
): Promise<number> {
const envsCount = await this.adminService.environmentCountInTeam(teamID);
return envsCount;
}
@ResolveField(() => [TeamInvitation], {
description: 'Return all the pending invitations in a team',
})
async pendingInvitationCountInTeam(
@Args({
name: 'teamID',
type: () => ID,
description: 'Team ID for which team members to fetch',
})
teamID: string,
) {
const invitations = await this.adminService.pendingInvitationCountInTeam(
teamID,
);
return invitations;
}
@ResolveField(() => Number, {
description: 'Return total number of Users in organization',
})
async usersCount() {
return this.adminService.getUsersCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Teams in organization',
})
async teamsCount() {
return this.adminService.getTeamsCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Team Collections in organization',
})
async teamCollectionsCount() {
return this.adminService.getTeamCollectionsCount();
}
@ResolveField(() => Number, {
description: 'Return total number of Team Requests in organization',
})
async teamRequestsCount() {
return this.adminService.getTeamRequestsCount();
}
@ResolveField(() => [ShortcodeWithUserEmail], {
description: 'Returns a list of all the shortcodes in the infra',
})
async allShortcodes(
@Args() args: PaginationArgs,
@Args({
name: 'userEmail',
nullable: true,
description: 'Users email to filter shortcodes by',
})
userEmail: string,
) {
return await this.adminService.fetchAllShortcodes(
args.cursor,
args.take,
userEmail,
);
}
}

View File

@@ -254,13 +254,6 @@ export const TEAM_COLL_INVALID_JSON = 'team_coll/invalid_json';
*/ */
export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const; export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const;
/**
* The Team Collection data is not valid
* (TeamCollectionService)
*/
export const TEAM_COLL_DATA_INVALID =
'team_coll/team_coll_data_invalid' as const;
/** /**
* Tried to perform an action on a request that doesn't accept their member role level * Tried to perform an action on a request that doesn't accept their member role level
* (GqlRequestTeamMemberGuard) * (GqlRequestTeamMemberGuard)
@@ -325,6 +318,18 @@ export const TEAM_INVITATION_NOT_FOUND =
*/ */
export const SHORTCODE_NOT_FOUND = 'shortcode/not_found' as const; export const SHORTCODE_NOT_FOUND = 'shortcode/not_found' as const;
/**
* Invalid ShortCode format
* (ShortcodeService)
*/
export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
/**
* ShortCode already exists in DB
* (ShortcodeService)
*/
export const SHORTCODE_ALREADY_EXISTS = 'shortcode/already_exists' as const;
/** /**
* Invalid or non-existent TEAM ENVIRONMENT ID * Invalid or non-existent TEAM ENVIRONMENT ID
* (TeamEnvironmentsService) * (TeamEnvironmentsService)
@@ -592,13 +597,6 @@ export const USER_COLL_REORDERING_FAILED =
export const USER_COLL_SAME_NEXT_COLL = export const USER_COLL_SAME_NEXT_COLL =
'user_coll/user_collection_and_next_user_collection_are_same' as const; 'user_coll/user_collection_and_next_user_collection_are_same' as const;
/**
* The User Collection data is not valid
* (UserCollectionService)
*/
export const USER_COLL_DATA_INVALID =
'user_coll/user_coll_data_invalid' as const;
/** /**
* The User Collection does not belong to the logged-in user * The User Collection does not belong to the logged-in user
* (UserCollectionService) * (UserCollectionService)
@@ -623,24 +621,3 @@ export const MAILER_SMTP_URL_UNDEFINED = 'mailer/smtp_url_undefined' as const;
*/ */
export const MAILER_FROM_ADDRESS_UNDEFINED = export const MAILER_FROM_ADDRESS_UNDEFINED =
'mailer/from_address_undefined' as const; 'mailer/from_address_undefined' as const;
/**
* SharedRequest invalid request JSON format
* (ShortcodeService)
*/
export const SHORTCODE_INVALID_REQUEST_JSON =
'shortcode/request_invalid_format' as const;
/**
* SharedRequest invalid properties JSON format
* (ShortcodeService)
*/
export const SHORTCODE_INVALID_PROPERTIES_JSON =
'shortcode/properties_invalid_format' as const;
/**
* SharedRequest invalid properties not found
* (ShortcodeService)
*/
export const SHORTCODE_PROPERTIES_NOT_FOUND =
'shortcode/properties_not_found' as const;

View File

@@ -27,7 +27,6 @@ import { UserRequestUserCollectionResolver } from './user-request/resolvers/user
import { UserEnvsUserResolver } from './user-environment/user.resolver'; import { UserEnvsUserResolver } from './user-environment/user.resolver';
import { UserHistoryUserResolver } from './user-history/user.resolver'; import { UserHistoryUserResolver } from './user-history/user.resolver';
import { UserSettingsUserResolver } from './user-settings/user.resolver'; import { UserSettingsUserResolver } from './user-settings/user.resolver';
import { InfraResolver } from './admin/infra.resolver';
/** /**
* All the resolvers present in the application. * All the resolvers present in the application.
@@ -35,7 +34,6 @@ import { InfraResolver } from './admin/infra.resolver';
* NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate * NOTE: This needs to be KEPT UP-TO-DATE to keep the schema accurate
*/ */
const RESOLVERS = [ const RESOLVERS = [
InfraResolver,
AdminResolver, AdminResolver,
ShortcodeResolver, ShortcodeResolver,
TeamResolver, TeamResolver,

View File

@@ -21,8 +21,8 @@ import {
} from 'src/team-request/team-request.model'; } from 'src/team-request/team-request.model';
import { TeamInvitation } from 'src/team-invitation/team-invitation.model'; import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
import { InvitedUser } from '../admin/invited-user.model'; import { InvitedUser } from '../admin/invited-user.model';
import { UserCollection } from '@prisma/client';
import { import {
UserCollection,
UserCollectionRemovedData, UserCollectionRemovedData,
UserCollectionReorderData, UserCollectionReorderData,
} from 'src/user-collection/user-collections.model'; } from 'src/user-collection/user-collections.model';
@@ -69,7 +69,5 @@ export type TopicDef = {
[topic: `team_req/${string}/req_deleted`]: string; [topic: `team_req/${string}/req_deleted`]: string;
[topic: `team/${string}/invite_added`]: TeamInvitation; [topic: `team/${string}/invite_added`]: TeamInvitation;
[topic: `team/${string}/invite_removed`]: string; [topic: `team/${string}/invite_removed`]: string;
[ [topic: `shortcode/${string}/${'created' | 'revoked'}`]: Shortcode;
topic: `shortcode/${string}/${'created' | 'revoked' | 'updated'}`
]: Shortcode;
}; };

View File

@@ -1,10 +1,9 @@
import { Field, ID, ObjectType } from '@nestjs/graphql'; import { Field, ID, ObjectType } from '@nestjs/graphql';
import { User } from 'src/user/user.model';
@ObjectType() @ObjectType()
export class Shortcode { export class Shortcode {
@Field(() => ID, { @Field(() => ID, {
description: 'The 12 digit alphanumeric code', description: 'The shortcode. 12 digit alphanumeric.',
}) })
id: string; id: string;
@@ -13,57 +12,8 @@ export class Shortcode {
}) })
request: string; request: string;
@Field({
description: 'JSON string representing the properties for an embed',
nullable: true,
})
properties: string;
@Field({ @Field({
description: 'Timestamp of when the Shortcode was created', description: 'Timestamp of when the Shortcode was created',
}) })
createdOn: Date; createdOn: Date;
} }
@ObjectType()
export class ShortcodeCreator {
@Field({
description: 'Uid of user who created the shortcode',
})
uid: string;
@Field({
description: 'Email of user who created the shortcode',
})
email: string;
}
@ObjectType()
export class ShortcodeWithUserEmail {
@Field(() => ID, {
description: 'The 12 digit alphanumeric code',
})
id: string;
@Field({
description: 'JSON string representing the request data',
})
request: string;
@Field({
description: 'JSON string representing the properties for an embed',
nullable: true,
})
properties: string;
@Field({
description: 'Timestamp of when the Shortcode was created',
})
createdOn: Date;
@Field({
description: 'Details of user who created the shortcode',
nullable: true,
})
creator: ShortcodeCreator;
}

View File

@@ -1,4 +1,5 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PrismaModule } from 'src/prisma/prisma.module'; import { PrismaModule } from 'src/prisma/prisma.module';
import { PubSubModule } from 'src/pubsub/pubsub.module'; import { PubSubModule } from 'src/pubsub/pubsub.module';
import { UserModule } from 'src/user/user.module'; import { UserModule } from 'src/user/user.module';
@@ -6,7 +7,14 @@ import { ShortcodeResolver } from './shortcode.resolver';
import { ShortcodeService } from './shortcode.service'; import { ShortcodeService } from './shortcode.service';
@Module({ @Module({
imports: [PrismaModule, UserModule, PubSubModule], imports: [
PrismaModule,
UserModule,
PubSubModule,
JwtModule.register({
secret: process.env.JWT_SECRET,
}),
],
providers: [ShortcodeService, ShortcodeResolver], providers: [ShortcodeService, ShortcodeResolver],
exports: [ShortcodeService], exports: [ShortcodeService],
}) })

View File

@@ -1,5 +1,6 @@
import { import {
Args, Args,
Context,
ID, ID,
Mutation, Mutation,
Query, Query,
@@ -8,25 +9,28 @@ import {
} from '@nestjs/graphql'; } from '@nestjs/graphql';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model'; import { Shortcode } from './shortcode.model';
import { ShortcodeService } from './shortcode.service'; import { ShortcodeService } from './shortcode.service';
import { UserService } from 'src/user/user.service';
import { throwErr } from 'src/utils'; import { throwErr } from 'src/utils';
import { GqlUser } from 'src/decorators/gql-user.decorator'; import { GqlUser } from 'src/decorators/gql-user.decorator';
import { GqlAuthGuard } from 'src/guards/gql-auth.guard'; import { GqlAuthGuard } from 'src/guards/gql-auth.guard';
import { User } from 'src/user/user.model'; import { User } from 'src/user/user.model';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from '../types/AuthUser'; import { AuthUser } from '../types/AuthUser';
import { JwtService } from '@nestjs/jwt';
import { PaginationArgs } from 'src/types/input-types.args'; import { PaginationArgs } from 'src/types/input-types.args';
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 { GqlAdminGuard } from 'src/admin/guards/gql-admin.guard';
@UseGuards(GqlThrottlerGuard) @UseGuards(GqlThrottlerGuard)
@Resolver(() => Shortcode) @Resolver(() => Shortcode)
export class ShortcodeResolver { export class ShortcodeResolver {
constructor( constructor(
private readonly shortcodeService: ShortcodeService, private readonly shortcodeService: ShortcodeService,
private readonly userService: UserService,
private readonly pubsub: PubSubService, private readonly pubsub: PubSubService,
private jwtService: JwtService,
) {} ) {}
/* Queries */ /* Queries */
@@ -60,53 +64,20 @@ export class ShortcodeResolver {
@Mutation(() => Shortcode, { @Mutation(() => Shortcode, {
description: 'Create a shortcode for the given request.', description: 'Create a shortcode for the given request.',
}) })
@UseGuards(GqlAuthGuard)
async createShortcode( async createShortcode(
@GqlUser() user: AuthUser,
@Args({ @Args({
name: 'request', name: 'request',
description: 'JSON string of the request object', description: 'JSON string of the request object',
}) })
request: string, request: string,
@Args({ @Context() ctx: any,
name: 'properties',
description: 'JSON string of the properties of the embed',
nullable: true,
})
properties: string,
) { ) {
const decodedAccessToken = this.jwtService.verify(
ctx.req.cookies['access_token'],
);
const result = await this.shortcodeService.createShortcode( const result = await this.shortcodeService.createShortcode(
request, request,
properties, decodedAccessToken?.sub,
user,
);
if (E.isLeft(result)) throwErr(result.left);
return result.right;
}
@Mutation(() => Shortcode, {
description: 'Update a user generated Shortcode',
})
@UseGuards(GqlAuthGuard)
async updateEmbedProperties(
@GqlUser() user: AuthUser,
@Args({
name: 'code',
type: () => ID,
description: 'The Shortcode to update',
})
code: string,
@Args({
name: 'properties',
description: 'JSON string of the properties of the embed',
})
properties: string,
) {
const result = await this.shortcodeService.updateEmbedProperties(
code,
user.uid,
properties,
); );
if (E.isLeft(result)) throwErr(result.left); if (E.isLeft(result)) throwErr(result.left);
@@ -122,7 +93,7 @@ export class ShortcodeResolver {
@Args({ @Args({
name: 'code', name: 'code',
type: () => ID, type: () => ID,
description: 'The shortcode to remove', description: 'The shortcode to resolve',
}) })
code: string, code: string,
) { ) {
@@ -143,16 +114,6 @@ export class ShortcodeResolver {
return this.pubsub.asyncIterator(`shortcode/${user.uid}/created`); return this.pubsub.asyncIterator(`shortcode/${user.uid}/created`);
} }
@Subscription(() => Shortcode, {
description: 'Listen for Shortcode updates',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard)
myShortcodesUpdated(@GqlUser() user: AuthUser) {
return this.pubsub.asyncIterator(`shortcode/${user.uid}/updated`);
}
@Subscription(() => Shortcode, { @Subscription(() => Shortcode, {
description: 'Listen for shortcode deletion', description: 'Listen for shortcode deletion',
resolve: (value) => value, resolve: (value) => value,

View File

@@ -1,16 +1,13 @@
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mockDeep, mockReset } from 'jest-mock-extended';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { import {
INVALID_EMAIL, SHORTCODE_ALREADY_EXISTS,
SHORTCODE_INVALID_PROPERTIES_JSON, SHORTCODE_INVALID_JSON,
SHORTCODE_INVALID_REQUEST_JSON,
SHORTCODE_NOT_FOUND, SHORTCODE_NOT_FOUND,
SHORTCODE_PROPERTIES_NOT_FOUND,
} from 'src/errors'; } from 'src/errors';
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model'; import { Shortcode } from './shortcode.model';
import { ShortcodeService } from './shortcode.service'; import { ShortcodeService } from './shortcode.service';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
import { AuthUser } from 'src/types/AuthUser';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
@@ -25,7 +22,7 @@ const mockFB = {
doc: mockDocFunc, doc: mockDocFunc,
}, },
}; };
const mockUserService = new UserService(mockPrisma as any, mockPubSub as any); const mockUserService = new UserService(mockFB as any, mockPubSub as any);
// eslint-disable-next-line @typescript-eslint/ban-ts-comment // eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore // @ts-ignore
@@ -41,34 +38,18 @@ beforeEach(() => {
}); });
const createdOn = new Date(); const createdOn = new Date();
const user: AuthUser = { const shortCodeWithOutUser = {
uid: '123344',
email: 'dwight@dundermifflin.com',
displayName: 'Dwight Schrute',
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
isAdmin: false,
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
createdOn: createdOn,
currentGQLSession: {},
currentRESTSession: {},
};
const mockEmbed = {
id: '123', id: '123',
request: '{}', request: '{}',
embedProperties: '{}',
createdOn: createdOn, createdOn: createdOn,
creatorUid: user.uid, creatorUid: null,
updatedOn: createdOn,
}; };
const mockShortcode = { const shortCodeWithUser = {
id: '123', id: '123',
request: '{}', request: '{}',
embedProperties: null,
createdOn: createdOn, createdOn: createdOn,
creatorUid: user.uid, creatorUid: 'user_uid_1',
updatedOn: createdOn,
}; };
const shortcodes = [ const shortcodes = [
@@ -77,67 +58,33 @@ const shortcodes = [
request: { request: {
hello: 'there', hello: 'there',
}, },
embedProperties: { creatorUid: 'testuser',
foo: 'bar',
},
creatorUid: user.uid,
createdOn: new Date(), createdOn: new Date(),
updatedOn: createdOn,
}, },
{ {
id: 'blablabla1', id: 'blablabla1',
request: { request: {
hello: 'there', hello: 'there',
}, },
embedProperties: { creatorUid: 'testuser',
foo: 'bar',
},
creatorUid: user.uid,
createdOn: new Date(), createdOn: new Date(),
updatedOn: createdOn,
},
];
const shortcodesWithUserEmail = [
{
id: 'blablabla',
request: {
hello: 'there',
},
embedProperties: {
foo: 'bar',
},
creatorUid: user.uid,
createdOn: new Date(),
updatedOn: createdOn,
User: user,
},
{
id: 'blablabla1',
request: {
hello: 'there',
},
embedProperties: {
foo: 'bar',
},
creatorUid: user.uid,
createdOn: new Date(),
updatedOn: createdOn,
User: user,
}, },
]; ];
describe('ShortcodeService', () => { describe('ShortcodeService', () => {
describe('getShortCode', () => { describe('getShortCode', () => {
test('should return a valid Shortcode with valid Shortcode ID', async () => { test('should return a valid shortcode with valid shortcode ID', async () => {
mockPrisma.shortcode.findFirstOrThrow.mockResolvedValueOnce(mockEmbed); mockPrisma.shortcode.findFirstOrThrow.mockResolvedValueOnce(
shortCodeWithOutUser,
);
const result = await shortcodeService.getShortCode(mockEmbed.id); const result = await shortcodeService.getShortCode(
shortCodeWithOutUser.id,
);
expect(result).toEqualRight(<Shortcode>{ expect(result).toEqualRight(<Shortcode>{
id: mockEmbed.id, id: shortCodeWithOutUser.id,
createdOn: mockEmbed.createdOn, createdOn: shortCodeWithOutUser.createdOn,
request: JSON.stringify(mockEmbed.request), request: JSON.stringify(shortCodeWithOutUser.request),
properties: JSON.stringify(mockEmbed.embedProperties),
}); });
}); });
@@ -152,10 +99,10 @@ describe('ShortcodeService', () => {
}); });
describe('fetchUserShortCodes', () => { describe('fetchUserShortCodes', () => {
test('should return list of Shortcode with valid inputs and no cursor', async () => { test('should return list of shortcodes with valid inputs and no cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodes); mockPrisma.shortcode.findMany.mockResolvedValueOnce(shortcodes);
const result = await shortcodeService.fetchUserShortCodes(user.uid, { const result = await shortcodeService.fetchUserShortCodes('testuser', {
cursor: null, cursor: null,
take: 10, take: 10,
}); });
@@ -163,22 +110,20 @@ describe('ShortcodeService', () => {
{ {
id: shortcodes[0].id, id: shortcodes[0].id,
request: JSON.stringify(shortcodes[0].request), request: JSON.stringify(shortcodes[0].request),
properties: JSON.stringify(shortcodes[0].embedProperties),
createdOn: shortcodes[0].createdOn, createdOn: shortcodes[0].createdOn,
}, },
{ {
id: shortcodes[1].id, id: shortcodes[1].id,
request: JSON.stringify(shortcodes[1].request), request: JSON.stringify(shortcodes[1].request),
properties: JSON.stringify(shortcodes[1].embedProperties),
createdOn: shortcodes[1].createdOn, createdOn: shortcodes[1].createdOn,
}, },
]); ]);
}); });
test('should return list of Shortcode with valid inputs and cursor', async () => { test('should return list of shortcodes with valid inputs and cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValue([shortcodes[1]]); mockPrisma.shortcode.findMany.mockResolvedValue([shortcodes[1]]);
const result = await shortcodeService.fetchUserShortCodes(user.uid, { const result = await shortcodeService.fetchUserShortCodes('testuser', {
cursor: 'blablabla', cursor: 'blablabla',
take: 10, take: 10,
}); });
@@ -186,7 +131,6 @@ describe('ShortcodeService', () => {
{ {
id: shortcodes[1].id, id: shortcodes[1].id,
request: JSON.stringify(shortcodes[1].request), request: JSON.stringify(shortcodes[1].request),
properties: JSON.stringify(shortcodes[1].embedProperties),
createdOn: shortcodes[1].createdOn, createdOn: shortcodes[1].createdOn,
}, },
]); ]);
@@ -195,7 +139,7 @@ describe('ShortcodeService', () => {
test('should return an empty array for an invalid cursor', async () => { test('should return an empty array for an invalid cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValue([]); mockPrisma.shortcode.findMany.mockResolvedValue([]);
const result = await shortcodeService.fetchUserShortCodes(user.uid, { const result = await shortcodeService.fetchUserShortCodes('testuser', {
cursor: 'invalidcursor', cursor: 'invalidcursor',
take: 10, take: 10,
}); });
@@ -227,111 +171,77 @@ describe('ShortcodeService', () => {
}); });
describe('createShortcode', () => { describe('createShortcode', () => {
test('should throw SHORTCODE_INVALID_REQUEST_JSON error if incoming request data is invalid', async () => { test('should throw SHORTCODE_INVALID_JSON error if incoming request data is invalid', async () => {
const result = await shortcodeService.createShortcode( const result = await shortcodeService.createShortcode(
'invalidRequest', 'invalidRequest',
null, 'user_uid_1',
user,
); );
expect(result).toEqualLeft(SHORTCODE_INVALID_REQUEST_JSON); expect(result).toEqualLeft(SHORTCODE_INVALID_JSON);
}); });
test('should throw SHORTCODE_INVALID_PROPERTIES_JSON error if incoming properties data is invalid', async () => { test('should successfully create a new shortcode with valid user uid', async () => {
const result = await shortcodeService.createShortcode( // generateUniqueShortCodeID --> getShortCode
'{}',
'invalid_data',
user,
);
expect(result).toEqualLeft(SHORTCODE_INVALID_PROPERTIES_JSON);
});
test('should successfully create a new Embed with valid user uid', async () => {
// generateUniqueShortCodeID --> getShortcode
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce( mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
'NotFoundError', 'NotFoundError',
); );
mockPrisma.shortcode.create.mockResolvedValueOnce(mockEmbed); mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
const result = await shortcodeService.createShortcode('{}', '{}', user); const result = await shortcodeService.createShortcode('{}', 'user_uid_1');
expect(result).toEqualRight(<Shortcode>{ expect(result).toEqualRight({
id: mockEmbed.id, id: shortCodeWithUser.id,
createdOn: mockEmbed.createdOn, createdOn: shortCodeWithUser.createdOn,
request: JSON.stringify(mockEmbed.request), request: JSON.stringify(shortCodeWithUser.request),
properties: JSON.stringify(mockEmbed.embedProperties),
}); });
}); });
test('should successfully create a new ShortCode with valid user uid', async () => { test('should successfully create a new shortcode with null user uid', async () => {
// generateUniqueShortCodeID --> getShortcode // generateUniqueShortCodeID --> getShortCode
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce( mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
'NotFoundError', 'NotFoundError',
); );
mockPrisma.shortcode.create.mockResolvedValueOnce(mockShortcode); mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
const result = await shortcodeService.createShortcode('{}', null, user); const result = await shortcodeService.createShortcode('{}', null);
expect(result).toEqualRight(<Shortcode>{ expect(result).toEqualRight({
id: mockShortcode.id, id: shortCodeWithUser.id,
createdOn: mockShortcode.createdOn, createdOn: shortCodeWithUser.createdOn,
request: JSON.stringify(mockShortcode.request), request: JSON.stringify(shortCodeWithOutUser.request),
properties: mockShortcode.embedProperties,
}); });
}); });
test('should send pubsub message to `shortcode/{uid}/created` on successful creation of a Shortcode', async () => { test('should send pubsub message to `shortcode/{uid}/created` on successful creation of shortcode', async () => {
// generateUniqueShortCodeID --> getShortcode // generateUniqueShortCodeID --> getShortCode
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce( mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
'NotFoundError', 'NotFoundError',
); );
mockPrisma.shortcode.create.mockResolvedValueOnce(mockShortcode); mockPrisma.shortcode.create.mockResolvedValueOnce(shortCodeWithUser);
const result = await shortcodeService.createShortcode('{}', null, user);
const result = await shortcodeService.createShortcode('{}', 'user_uid_1');
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`shortcode/${mockShortcode.creatorUid}/created`, `shortcode/${shortCodeWithUser.creatorUid}/created`,
<Shortcode>{ {
id: mockShortcode.id, id: shortCodeWithUser.id,
createdOn: mockShortcode.createdOn, createdOn: shortCodeWithUser.createdOn,
request: JSON.stringify(mockShortcode.request), request: JSON.stringify(shortCodeWithUser.request),
properties: mockShortcode.embedProperties,
},
);
});
test('should send pubsub message to `shortcode/{uid}/created` on successful creation of an Embed', async () => {
// generateUniqueShortCodeID --> getShortcode
mockPrisma.shortcode.findFirstOrThrow.mockRejectedValueOnce(
'NotFoundError',
);
mockPrisma.shortcode.create.mockResolvedValueOnce(mockEmbed);
const result = await shortcodeService.createShortcode('{}', '{}', user);
expect(mockPubSub.publish).toHaveBeenCalledWith(
`shortcode/${mockEmbed.creatorUid}/created`,
<Shortcode>{
id: mockEmbed.id,
createdOn: mockEmbed.createdOn,
request: JSON.stringify(mockEmbed.request),
properties: JSON.stringify(mockEmbed.embedProperties),
}, },
); );
}); });
}); });
describe('revokeShortCode', () => { describe('revokeShortCode', () => {
test('should return true on successful deletion of Shortcode with valid inputs', async () => { test('should return true on successful deletion of shortcode with valid inputs', async () => {
mockPrisma.shortcode.delete.mockResolvedValueOnce(mockEmbed); mockPrisma.shortcode.delete.mockResolvedValueOnce(shortCodeWithUser);
const result = await shortcodeService.revokeShortCode( const result = await shortcodeService.revokeShortCode(
mockEmbed.id, shortCodeWithUser.id,
mockEmbed.creatorUid, shortCodeWithUser.creatorUid,
); );
expect(mockPrisma.shortcode.delete).toHaveBeenCalledWith({ expect(mockPrisma.shortcode.delete).toHaveBeenCalledWith({
where: { where: {
creator_uid_shortcode_unique: { creator_uid_shortcode_unique: {
creatorUid: mockEmbed.creatorUid, creatorUid: shortCodeWithUser.creatorUid,
id: mockEmbed.id, id: shortCodeWithUser.id,
}, },
}, },
}); });
@@ -339,53 +249,52 @@ describe('ShortcodeService', () => {
expect(result).toEqualRight(true); expect(result).toEqualRight(true);
}); });
test('should return SHORTCODE_NOT_FOUND error when Shortcode is invalid and user uid is valid', async () => { test('should return SHORTCODE_NOT_FOUND error when shortcode is invalid and user uid is valid', async () => {
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound'); mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
expect( expect(
shortcodeService.revokeShortCode('invalid', 'testuser'), shortcodeService.revokeShortCode('invalid', 'testuser'),
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND); ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
}); });
test('should return SHORTCODE_NOT_FOUND error when Shortcode is valid and user uid is invalid', async () => { test('should return SHORTCODE_NOT_FOUND error when shortcode is valid and user uid is invalid', async () => {
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound'); mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
expect( expect(
shortcodeService.revokeShortCode('blablablabla', 'invalidUser'), shortcodeService.revokeShortCode('blablablabla', 'invalidUser'),
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND); ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
}); });
test('should return SHORTCODE_NOT_FOUND error when both Shortcode and user uid are invalid', async () => { test('should return SHORTCODE_NOT_FOUND error when both shortcode and user uid are invalid', async () => {
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound'); mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
expect( expect(
shortcodeService.revokeShortCode('invalid', 'invalid'), shortcodeService.revokeShortCode('invalid', 'invalid'),
).resolves.toEqualLeft(SHORTCODE_NOT_FOUND); ).resolves.toEqualLeft(SHORTCODE_NOT_FOUND);
}); });
test('should send pubsub message to `shortcode/{uid}/revoked` on successful deletion of Shortcode', async () => { test('should send pubsub message to `shortcode/{uid}/revoked` on successful deletion of shortcode', async () => {
mockPrisma.shortcode.delete.mockResolvedValueOnce(mockEmbed); mockPrisma.shortcode.delete.mockResolvedValueOnce(shortCodeWithUser);
const result = await shortcodeService.revokeShortCode( const result = await shortcodeService.revokeShortCode(
mockEmbed.id, shortCodeWithUser.id,
mockEmbed.creatorUid, shortCodeWithUser.creatorUid,
); );
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`shortcode/${mockEmbed.creatorUid}/revoked`, `shortcode/${shortCodeWithUser.creatorUid}/revoked`,
{ {
id: mockEmbed.id, id: shortCodeWithUser.id,
createdOn: mockEmbed.createdOn, createdOn: shortCodeWithUser.createdOn,
request: JSON.stringify(mockEmbed.request), request: JSON.stringify(shortCodeWithUser.request),
properties: JSON.stringify(mockEmbed.embedProperties),
}, },
); );
}); });
}); });
describe('deleteUserShortCodes', () => { describe('deleteUserShortCodes', () => {
test('should successfully delete all users Shortcodes with valid user uid', async () => { test('should successfully delete all users shortcodes with valid user uid', async () => {
mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 1 }); mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 1 });
const result = await shortcodeService.deleteUserShortCodes( const result = await shortcodeService.deleteUserShortCodes(
mockEmbed.creatorUid, shortCodeWithUser.creatorUid,
); );
expect(result).toEqual(1); expect(result).toEqual(1);
}); });
@@ -394,176 +303,9 @@ describe('ShortcodeService', () => {
mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 0 }); mockPrisma.shortcode.deleteMany.mockResolvedValueOnce({ count: 0 });
const result = await shortcodeService.deleteUserShortCodes( const result = await shortcodeService.deleteUserShortCodes(
mockEmbed.creatorUid, shortCodeWithUser.creatorUid,
); );
expect(result).toEqual(0); expect(result).toEqual(0);
}); });
}); });
describe('updateShortcode', () => {
test('should return SHORTCODE_PROPERTIES_NOT_FOUND error when updatedProps in invalid', async () => {
const result = await shortcodeService.updateEmbedProperties(
mockEmbed.id,
user.uid,
'',
);
expect(result).toEqualLeft(SHORTCODE_PROPERTIES_NOT_FOUND);
});
test('should return SHORTCODE_PROPERTIES_NOT_FOUND error when updatedProps in invalid JSON format', async () => {
const result = await shortcodeService.updateEmbedProperties(
mockEmbed.id,
user.uid,
'{kk',
);
expect(result).toEqualLeft(SHORTCODE_INVALID_PROPERTIES_JSON);
});
test('should return SHORTCODE_NOT_FOUND error when Shortcode ID is invalid', async () => {
mockPrisma.shortcode.update.mockRejectedValue('RecordNotFound');
const result = await shortcodeService.updateEmbedProperties(
'invalidID',
user.uid,
'{}',
);
expect(result).toEqualLeft(SHORTCODE_NOT_FOUND);
});
test('should successfully update a Shortcodes with valid inputs', async () => {
mockPrisma.shortcode.update.mockResolvedValueOnce({
...mockEmbed,
embedProperties: '{"foo":"bar"}',
});
const result = await shortcodeService.updateEmbedProperties(
mockEmbed.id,
user.uid,
'{"foo":"bar"}',
);
expect(result).toEqualRight({
id: mockEmbed.id,
createdOn: mockEmbed.createdOn,
request: JSON.stringify(mockEmbed.request),
properties: JSON.stringify('{"foo":"bar"}'),
});
});
test('should send pubsub message to `shortcode/{uid}/updated` on successful Update of Shortcode', async () => {
mockPrisma.shortcode.update.mockResolvedValueOnce({
...mockEmbed,
embedProperties: '{"foo":"bar"}',
});
const result = await shortcodeService.updateEmbedProperties(
mockEmbed.id,
user.uid,
'{"foo":"bar"}',
);
expect(mockPubSub.publish).toHaveBeenCalledWith(
`shortcode/${mockEmbed.creatorUid}/updated`,
{
id: mockEmbed.id,
createdOn: mockEmbed.createdOn,
request: JSON.stringify(mockEmbed.request),
properties: JSON.stringify('{"foo":"bar"}'),
},
);
});
});
describe('deleteShortcode', () => {
test('should return true on successful deletion of Shortcode with valid inputs', async () => {
mockPrisma.shortcode.delete.mockResolvedValueOnce(mockEmbed);
const result = await shortcodeService.deleteShortcode(mockEmbed.id);
expect(result).toEqualRight(true);
});
test('should return SHORTCODE_NOT_FOUND error when Shortcode is invalid', async () => {
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
expect(shortcodeService.deleteShortcode('invalid')).resolves.toEqualLeft(
SHORTCODE_NOT_FOUND,
);
});
});
describe('fetchAllShortcodes', () => {
test('should return list of Shortcodes with valid inputs and no cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValueOnce(
shortcodesWithUserEmail,
);
const result = await shortcodeService.fetchAllShortcodes(
{
cursor: null,
take: 10,
},
user.email,
);
expect(result).toEqual(<ShortcodeWithUserEmail[]>[
{
id: shortcodes[0].id,
request: JSON.stringify(shortcodes[0].request),
properties: JSON.stringify(shortcodes[0].embedProperties),
createdOn: shortcodes[0].createdOn,
creator: {
uid: user.uid,
email: user.email,
},
},
{
id: shortcodes[1].id,
request: JSON.stringify(shortcodes[1].request),
properties: JSON.stringify(shortcodes[1].embedProperties),
createdOn: shortcodes[1].createdOn,
creator: {
uid: user.uid,
email: user.email,
},
},
]);
});
test('should return list of Shortcode with valid inputs and cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValue([
shortcodesWithUserEmail[1],
]);
const result = await shortcodeService.fetchAllShortcodes(
{
cursor: 'blablabla',
take: 10,
},
user.email,
);
expect(result).toEqual(<ShortcodeWithUserEmail[]>[
{
id: shortcodes[1].id,
request: JSON.stringify(shortcodes[1].request),
properties: JSON.stringify(shortcodes[1].embedProperties),
createdOn: shortcodes[1].createdOn,
creator: {
uid: user.uid,
email: user.email,
},
},
]);
});
test('should return an empty array for an invalid cursor', async () => {
mockPrisma.shortcode.findMany.mockResolvedValue([]);
const result = await shortcodeService.fetchAllShortcodes(
{
cursor: 'invalidcursor',
take: 10,
},
user.email,
);
expect(result).toHaveLength(0);
});
});
}); });

View File

@@ -1,16 +1,12 @@
import { Injectable, OnModuleInit } from '@nestjs/common'; import { Injectable, OnModuleInit } from '@nestjs/common';
import * as T from 'fp-ts/Task'; import * as T from 'fp-ts/Task';
import * as O from 'fp-ts/Option';
import * as TO from 'fp-ts/TaskOption'; import * as TO from 'fp-ts/TaskOption';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { import { SHORTCODE_INVALID_JSON, SHORTCODE_NOT_FOUND } from 'src/errors';
SHORTCODE_INVALID_PROPERTIES_JSON,
SHORTCODE_INVALID_REQUEST_JSON,
SHORTCODE_NOT_FOUND,
SHORTCODE_PROPERTIES_NOT_FOUND,
} from 'src/errors';
import { UserDataHandler } from 'src/user/user.data.handler'; import { UserDataHandler } from 'src/user/user.data.handler';
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model'; import { Shortcode } from './shortcode.model';
import { Shortcode as DBShortCode } from '@prisma/client'; import { Shortcode as DBShortCode } from '@prisma/client';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { UserService } from 'src/user/user.service'; import { UserService } from 'src/user/user.service';
@@ -50,14 +46,10 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
* @param shortcodeInfo Prisma Shortcode type * @param shortcodeInfo Prisma Shortcode type
* @returns GQL Shortcode * @returns GQL Shortcode
*/ */
private cast(shortcodeInfo: DBShortCode): Shortcode { private returnShortCode(shortcodeInfo: DBShortCode): Shortcode {
return <Shortcode>{ return <Shortcode>{
id: shortcodeInfo.id, id: shortcodeInfo.id,
request: JSON.stringify(shortcodeInfo.request), request: JSON.stringify(shortcodeInfo.request),
properties:
shortcodeInfo.embedProperties != null
? JSON.stringify(shortcodeInfo.embedProperties)
: null,
createdOn: shortcodeInfo.createdOn, createdOn: shortcodeInfo.createdOn,
}; };
} }
@@ -102,7 +94,7 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
const shortcodeInfo = await this.prisma.shortcode.findFirstOrThrow({ const shortcodeInfo = await this.prisma.shortcode.findFirstOrThrow({
where: { id: shortcode }, where: { id: shortcode },
}); });
return E.right(this.cast(shortcodeInfo)); return E.right(this.returnShortCode(shortcodeInfo));
} catch (error) { } catch (error) {
return E.left(SHORTCODE_NOT_FOUND); return E.left(SHORTCODE_NOT_FOUND);
} }
@@ -112,22 +104,14 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
* Create a new ShortCode * Create a new ShortCode
* *
* @param request JSON string of request details * @param request JSON string of request details
* @param userInfo user UI * @param userUID user UID, if present
* @param properties JSON string of embed properties, if present
* @returns Either of ShortCode or error * @returns Either of ShortCode or error
*/ */
async createShortcode( async createShortcode(request: string, userUID: string | null) {
request: string, const shortcodeData = stringToJson(request);
properties: string | null = null, if (E.isLeft(shortcodeData)) return E.left(SHORTCODE_INVALID_JSON);
userInfo: AuthUser,
) {
const requestData = stringToJson(request);
if (E.isLeft(requestData) || !requestData.right)
return E.left(SHORTCODE_INVALID_REQUEST_JSON);
const parsedProperties = stringToJson(properties); const user = await this.userService.findUserById(userUID);
if (E.isLeft(parsedProperties))
return E.left(SHORTCODE_INVALID_PROPERTIES_JSON);
const generatedShortCode = await this.generateUniqueShortCodeID(); const generatedShortCode = await this.generateUniqueShortCodeID();
if (E.isLeft(generatedShortCode)) return E.left(generatedShortCode.left); if (E.isLeft(generatedShortCode)) return E.left(generatedShortCode.left);
@@ -135,9 +119,8 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
const createdShortCode = await this.prisma.shortcode.create({ const createdShortCode = await this.prisma.shortcode.create({
data: { data: {
id: generatedShortCode.right, id: generatedShortCode.right,
request: requestData.right, request: shortcodeData.right,
embedProperties: parsedProperties.right ?? undefined, creatorUid: O.isNone(user) ? null : user.value.uid,
creatorUid: userInfo.uid,
}, },
}); });
@@ -145,11 +128,11 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
if (createdShortCode.creatorUid) { if (createdShortCode.creatorUid) {
this.pubsub.publish( this.pubsub.publish(
`shortcode/${createdShortCode.creatorUid}/created`, `shortcode/${createdShortCode.creatorUid}/created`,
this.cast(createdShortCode), this.returnShortCode(createdShortCode),
); );
} }
return E.right(this.cast(createdShortCode)); return E.right(this.returnShortCode(createdShortCode));
} }
/** /**
@@ -173,14 +156,14 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
}); });
const fetchedShortCodes: Shortcode[] = shortCodes.map((code) => const fetchedShortCodes: Shortcode[] = shortCodes.map((code) =>
this.cast(code), this.returnShortCode(code),
); );
return fetchedShortCodes; return fetchedShortCodes;
} }
/** /**
* Delete a ShortCode created by User of uid * Delete a ShortCode
* *
* @param shortcode ShortCode * @param shortcode ShortCode
* @param uid User Uid * @param uid User Uid
@@ -199,7 +182,7 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
this.pubsub.publish( this.pubsub.publish(
`shortcode/${deletedShortCodes.creatorUid}/revoked`, `shortcode/${deletedShortCodes.creatorUid}/revoked`,
this.cast(deletedShortCodes), this.returnShortCode(deletedShortCodes),
); );
return E.right(true); return E.right(true);
@@ -222,118 +205,4 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
return deletedShortCodes.count; return deletedShortCodes.count;
} }
/**
* Delete a Shortcode
*
* @param shortcodeID ID of Shortcode being deleted
* @returns Boolean on successful deletion
*/
async deleteShortcode(shortcodeID: string) {
try {
await this.prisma.shortcode.delete({
where: {
id: shortcodeID,
},
});
return E.right(true);
} catch (error) {
return E.left(SHORTCODE_NOT_FOUND);
}
}
/**
* Update a created Shortcode
* @param shortcodeID Shortcode ID
* @param uid User Uid
* @returns Updated Shortcode
*/
async updateEmbedProperties(
shortcodeID: string,
uid: string,
updatedProps: string,
) {
if (!updatedProps) return E.left(SHORTCODE_PROPERTIES_NOT_FOUND);
const parsedProperties = stringToJson(updatedProps);
if (E.isLeft(parsedProperties) || !parsedProperties.right)
return E.left(SHORTCODE_INVALID_PROPERTIES_JSON);
try {
const updatedShortcode = await this.prisma.shortcode.update({
where: {
creator_uid_shortcode_unique: {
creatorUid: uid,
id: shortcodeID,
},
},
data: {
embedProperties: parsedProperties.right,
},
});
this.pubsub.publish(
`shortcode/${updatedShortcode.creatorUid}/updated`,
this.cast(updatedShortcode),
);
return E.right(this.cast(updatedShortcode));
} catch (error) {
return E.left(SHORTCODE_NOT_FOUND);
}
}
/**
* Fetch all created ShortCodes
*
* @param args Pagination arguments
* @param userEmail User email
* @returns ShortcodeWithUserEmail
*/
async fetchAllShortcodes(
args: PaginationArgs,
userEmail: string | null = null,
) {
const shortCodes = await this.prisma.shortcode.findMany({
where: userEmail
? {
User: {
email: userEmail,
},
}
: undefined,
orderBy: {
createdOn: 'desc',
},
skip: args.cursor ? 1 : 0,
take: args.take,
cursor: args.cursor ? { id: args.cursor } : undefined,
include: {
User: true,
},
});
const fetchedShortCodes: ShortcodeWithUserEmail[] = shortCodes.map(
(code) => {
return <ShortcodeWithUserEmail>{
id: code.id,
request: JSON.stringify(code.request),
properties:
code.embedProperties != null
? JSON.stringify(code.embedProperties)
: null,
createdOn: code.createdOn,
creator: code.User
? {
uid: code.User.uid,
email: code.User.email,
}
: null,
};
},
);
return fetchedShortCodes;
}
} }

View File

@@ -14,13 +14,6 @@ export class CreateRootTeamCollectionArgs {
@Field({ name: 'title', description: 'Title of the new collection' }) @Field({ name: 'title', description: 'Title of the new collection' })
title: string; title: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
} }
@ArgsType() @ArgsType()
@@ -33,13 +26,6 @@ export class CreateChildTeamCollectionArgs {
@Field({ name: 'childTitle', description: 'Title of the new collection' }) @Field({ name: 'childTitle', description: 'Title of the new collection' })
childTitle: string; childTitle: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
} }
@ArgsType() @ArgsType()
@@ -47,14 +33,12 @@ export class RenameTeamCollectionArgs {
@Field(() => ID, { @Field(() => ID, {
name: 'collectionID', name: 'collectionID',
description: 'ID of the collection', description: 'ID of the collection',
deprecationReason: 'Switch to updateTeamCollection mutation instead',
}) })
collectionID: string; collectionID: string;
@Field({ @Field({
name: 'newTitle', name: 'newTitle',
description: 'The updated title of the collection', description: 'The updated title of the collection',
deprecationReason: 'Switch to updateTeamCollection mutation instead',
}) })
newTitle: string; newTitle: string;
} }
@@ -114,26 +98,3 @@ export class ReplaceTeamCollectionArgs {
}) })
parentCollectionID?: string; parentCollectionID?: string;
} }
@ArgsType()
export class UpdateTeamCollectionArgs {
@Field(() => ID, {
name: 'collectionID',
description: 'ID of the collection',
})
collectionID: string;
@Field({
name: 'newTitle',
description: 'The updated title of the collection',
nullable: true,
})
newTitle: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
}

View File

@@ -12,17 +12,12 @@ export class TeamCollection {
}) })
title: string; title: string;
@Field({
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
@Field(() => ID, { @Field(() => ID, {
description: 'ID of the collection', description: 'ID of the collection',
nullable: true, nullable: true,
}) })
parentID: string; parentID: string;
teamID: string;
} }
@ObjectType() @ObjectType()

View File

@@ -25,7 +25,6 @@ import {
MoveTeamCollectionArgs, MoveTeamCollectionArgs,
RenameTeamCollectionArgs, RenameTeamCollectionArgs,
ReplaceTeamCollectionArgs, ReplaceTeamCollectionArgs,
UpdateTeamCollectionArgs,
UpdateTeamCollectionOrderArgs, UpdateTeamCollectionOrderArgs,
} from './input-type.args'; } from './input-type.args';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
@@ -142,14 +141,7 @@ export class TeamCollectionResolver {
); );
if (E.isLeft(teamCollections)) throwErr(teamCollections.left); if (E.isLeft(teamCollections)) throwErr(teamCollections.left);
return <TeamCollection>{ return teamCollections.right;
id: teamCollections.right.id,
title: teamCollections.right.title,
parentID: teamCollections.right.parentID,
data: !teamCollections.right.data
? null
: JSON.stringify(teamCollections.right.data),
};
} }
// Mutations // Mutations
@@ -163,7 +155,6 @@ export class TeamCollectionResolver {
const teamCollection = await this.teamCollectionService.createCollection( const teamCollection = await this.teamCollectionService.createCollection(
args.teamID, args.teamID,
args.title, args.title,
args.data,
null, null,
); );
@@ -239,7 +230,6 @@ export class TeamCollectionResolver {
const teamCollection = await this.teamCollectionService.createCollection( const teamCollection = await this.teamCollectionService.createCollection(
team.right.id, team.right.id,
args.childTitle, args.childTitle,
args.data,
args.collectionID, args.collectionID,
); );
@@ -249,7 +239,6 @@ export class TeamCollectionResolver {
@Mutation(() => TeamCollection, { @Mutation(() => TeamCollection, {
description: 'Rename a collection', description: 'Rename a collection',
deprecationReason: 'Switch to updateTeamCollection mutation instead',
}) })
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
@@ -314,23 +303,6 @@ export class TeamCollectionResolver {
return request.right; return request.right;
} }
@Mutation(() => TeamCollection, {
description: 'Update Team Collection details',
})
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async updateTeamCollection(@Args() args: UpdateTeamCollectionArgs) {
const updatedTeamCollection =
await this.teamCollectionService.updateTeamCollection(
args.collectionID,
args.data,
args.newTitle,
);
if (E.isLeft(updatedTeamCollection)) throwErr(updatedTeamCollection.left);
return updatedTeamCollection.right;
}
// Subscriptions // Subscriptions
@Subscription(() => TeamCollection, { @Subscription(() => TeamCollection, {

View File

@@ -1,7 +1,6 @@
import { Team, TeamCollection as DBTeamCollection } from '@prisma/client'; import { Team, TeamCollection as DBTeamCollection } from '@prisma/client';
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mockDeep, mockReset } from 'jest-mock-extended';
import { import {
TEAM_COLL_DATA_INVALID,
TEAM_COLL_DEST_SAME, TEAM_COLL_DEST_SAME,
TEAM_COLL_INVALID_JSON, TEAM_COLL_INVALID_JSON,
TEAM_COLL_IS_PARENT_COLL, TEAM_COLL_IS_PARENT_COLL,
@@ -18,7 +17,6 @@ import { PrismaService } from 'src/prisma/prisma.service';
import { PubSubService } from 'src/pubsub/pubsub.service'; import { PubSubService } from 'src/pubsub/pubsub.service';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
import { TeamCollectionService } from './team-collection.service'; import { TeamCollectionService } from './team-collection.service';
import { TeamCollection } from './team-collection.model';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
const mockPubSub = mockDeep<PubSubService>(); const mockPubSub = mockDeep<PubSubService>();
@@ -53,60 +51,35 @@ const rootTeamCollection: DBTeamCollection = {
id: '123', id: '123',
orderIndex: 1, orderIndex: 1,
parentID: null, parentID: null,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}; };
const rootTeamCollectionsCasted: TeamCollection = {
id: rootTeamCollection.id,
title: rootTeamCollection.title,
parentID: rootTeamCollection.parentID,
data: JSON.stringify(rootTeamCollection.data),
};
const rootTeamCollection_2: DBTeamCollection = { const rootTeamCollection_2: DBTeamCollection = {
id: 'erv', id: 'erv',
orderIndex: 2, orderIndex: 2,
parentID: null, parentID: null,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}; };
const rootTeamCollection_2Casted: TeamCollection = {
id: 'erv',
parentID: null,
data: JSON.stringify(rootTeamCollection_2.data),
title: 'Root Collection 1',
};
const childTeamCollection: DBTeamCollection = { const childTeamCollection: DBTeamCollection = {
id: 'rfe', id: 'rfe',
orderIndex: 1, orderIndex: 1,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Child Collection 1', title: 'Child Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}; };
const childTeamCollectionCasted: TeamCollection = {
id: 'rfe',
parentID: rootTeamCollection.id,
data: JSON.stringify(childTeamCollection.data),
title: 'Child Collection 1',
};
const childTeamCollection_2: DBTeamCollection = { const childTeamCollection_2: DBTeamCollection = {
id: 'bgdz', id: 'bgdz',
orderIndex: 1, orderIndex: 1,
data: {},
parentID: rootTeamCollection_2.id, parentID: rootTeamCollection_2.id,
title: 'Child Collection 1', title: 'Child Collection 1',
teamID: team.id, teamID: team.id,
@@ -114,20 +87,11 @@ const childTeamCollection_2: DBTeamCollection = {
updatedOn: currentTime, updatedOn: currentTime,
}; };
const childTeamCollection_2Casted: TeamCollection = {
id: 'bgdz',
data: JSON.stringify(childTeamCollection_2.data),
parentID: rootTeamCollection_2.id,
title: 'Child Collection 1',
};
const rootTeamCollectionList: DBTeamCollection[] = [ const rootTeamCollectionList: DBTeamCollection[] = [
{ {
id: 'fdv', id: 'fdv',
orderIndex: 1, orderIndex: 1,
parentID: null, parentID: null,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -138,8 +102,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
orderIndex: 2, orderIndex: 2,
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -149,8 +111,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
orderIndex: 3, orderIndex: 3,
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -159,8 +119,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
id: 'bre3', id: 'bre3',
orderIndex: 4, orderIndex: 4,
parentID: null, parentID: null,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -171,8 +129,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
orderIndex: 5, orderIndex: 5,
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -183,8 +139,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
data: {},
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}, },
@@ -194,8 +148,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
data: {},
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}, },
@@ -204,7 +156,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
orderIndex: 8, orderIndex: 8,
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -214,7 +165,6 @@ const rootTeamCollectionList: DBTeamCollection[] = [
orderIndex: 9, orderIndex: 9,
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -225,83 +175,17 @@ const rootTeamCollectionList: DBTeamCollection[] = [
parentID: null, parentID: null,
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
data: {},
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
}, },
]; ];
const rootTeamCollectionListCasted: TeamCollection[] = [
{
id: 'fdv',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: 'fbbg',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: 'fgbfg',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: 'bre3',
parentID: null,
data: JSON.stringify(rootTeamCollection.data),
title: 'Root Collection 1',
},
{
id: 'hghgf',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: '123',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: '54tyh',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: '234re',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: '34rtg',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
{
id: '45tgh',
parentID: null,
title: 'Root Collection 1',
data: JSON.stringify(rootTeamCollection.data),
},
];
const childTeamCollectionList: DBTeamCollection[] = [ const childTeamCollectionList: DBTeamCollection[] = [
{ {
id: '123', id: '123',
orderIndex: 1, orderIndex: 1,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -311,8 +195,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
orderIndex: 2, orderIndex: 2,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -322,8 +204,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
orderIndex: 3, orderIndex: 3,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
title: 'Root Collection 1', title: 'Root Collection 1',
data: {},
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
updatedOn: currentTime, updatedOn: currentTime,
@@ -332,8 +212,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '567', id: '567',
orderIndex: 4, orderIndex: 4,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -343,8 +221,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '123', id: '123',
orderIndex: 5, orderIndex: 5,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -354,8 +230,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '678', id: '678',
orderIndex: 6, orderIndex: 6,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -365,8 +239,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '789', id: '789',
orderIndex: 7, orderIndex: 7,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -376,8 +248,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '890', id: '890',
orderIndex: 8, orderIndex: 8,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -387,7 +257,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '012', id: '012',
orderIndex: 9, orderIndex: 9,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -397,8 +266,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
id: '0bhu', id: '0bhu',
orderIndex: 10, orderIndex: 10,
parentID: rootTeamCollection.id, parentID: rootTeamCollection.id,
data: {},
title: 'Root Collection 1', title: 'Root Collection 1',
teamID: team.id, teamID: team.id,
createdOn: currentTime, createdOn: currentTime,
@@ -406,75 +273,6 @@ const childTeamCollectionList: DBTeamCollection[] = [
}, },
]; ];
const childTeamCollectionListCasted: TeamCollection[] = [
{
id: '123',
parentID: rootTeamCollection.id,
title: 'Root Collection 1',
data: JSON.stringify({}),
},
{
id: '345',
parentID: rootTeamCollection.id,
title: 'Root Collection 1',
data: JSON.stringify({}),
},
{
id: '456',
parentID: rootTeamCollection.id,
title: 'Root Collection 1',
data: JSON.stringify({}),
},
{
id: '567',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '123',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '678',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '789',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '890',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '012',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
{
id: '0bhu',
parentID: rootTeamCollection.id,
data: JSON.stringify({}),
title: 'Root Collection 1',
},
];
beforeEach(() => { beforeEach(() => {
mockReset(mockPrisma); mockReset(mockPrisma);
mockPubSub.publish.mockClear(); mockPubSub.publish.mockClear();
@@ -513,7 +311,7 @@ describe('getParentOfCollection', () => {
const result = await teamCollectionService.getParentOfCollection( const result = await teamCollectionService.getParentOfCollection(
childTeamCollection.id, childTeamCollection.id,
); );
expect(result).toEqual(rootTeamCollectionsCasted); expect(result).toEqual(rootTeamCollection);
}); });
test('should return null successfully for a root collection with valid collectionID', async () => { test('should return null successfully for a root collection with valid collectionID', async () => {
@@ -549,7 +347,7 @@ describe('getChildrenOfCollection', () => {
null, null,
10, 10,
); );
expect(result).toEqual(childTeamCollectionListCasted); expect(result).toEqual(childTeamCollectionList);
}); });
test('should return a list of 3 child collections successfully with cursor being equal to the 7th item in the list', async () => { test('should return a list of 3 child collections successfully with cursor being equal to the 7th item in the list', async () => {
@@ -565,9 +363,9 @@ describe('getChildrenOfCollection', () => {
10, 10,
); );
expect(result).toEqual([ expect(result).toEqual([
{ ...childTeamCollectionListCasted[7] }, { ...childTeamCollectionList[7] },
{ ...childTeamCollectionListCasted[8] }, { ...childTeamCollectionList[8] },
{ ...childTeamCollectionListCasted[9] }, { ...childTeamCollectionList[9] },
]); ]);
}); });
@@ -594,7 +392,7 @@ describe('getTeamRootCollections', () => {
null, null,
10, 10,
); );
expect(result).toEqual(rootTeamCollectionListCasted); expect(result).toEqual(rootTeamCollectionList);
}); });
test('should return a list of 3 root collections successfully with cursor being equal to the 7th item in the list', async () => { test('should return a list of 3 root collections successfully with cursor being equal to the 7th item in the list', async () => {
@@ -610,9 +408,9 @@ describe('getTeamRootCollections', () => {
10, 10,
); );
expect(result).toEqual([ expect(result).toEqual([
{ ...rootTeamCollectionListCasted[7] }, { ...rootTeamCollectionList[7] },
{ ...rootTeamCollectionListCasted[8] }, { ...rootTeamCollectionList[8] },
{ ...rootTeamCollectionListCasted[9] }, { ...rootTeamCollectionList[9] },
]); ]);
}); });
@@ -666,7 +464,6 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
rootTeamCollection.teamID, rootTeamCollection.teamID,
'ab', 'ab',
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(result).toEqualLeft(TEAM_COLL_SHORT_TITLE); expect(result).toEqualLeft(TEAM_COLL_SHORT_TITLE);
@@ -681,27 +478,11 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
rootTeamCollection.teamID, rootTeamCollection.teamID,
'abcd', 'abcd',
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(result).toEqualLeft(TEAM_NOT_OWNER); expect(result).toEqualLeft(TEAM_NOT_OWNER);
}); });
test('should throw TEAM_COLL_DATA_INVALID when parent TeamCollection does not belong to the team', async () => {
// isOwnerCheck
mockPrisma.teamCollection.findFirstOrThrow.mockResolvedValueOnce(
rootTeamCollection,
);
const result = await teamCollectionService.createCollection(
rootTeamCollection.teamID,
'abcd',
'{',
rootTeamCollection.id,
);
expect(result).toEqualLeft(TEAM_COLL_DATA_INVALID);
});
test('should successfully create a new root TeamCollection with valid inputs', async () => { test('should successfully create a new root TeamCollection with valid inputs', async () => {
// isOwnerCheck // isOwnerCheck
mockPrisma.teamCollection.findFirstOrThrow.mockResolvedValueOnce( mockPrisma.teamCollection.findFirstOrThrow.mockResolvedValueOnce(
@@ -715,10 +496,9 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
rootTeamCollection.teamID, rootTeamCollection.teamID,
'abcdefg', 'abcdefg',
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(result).toEqualRight(rootTeamCollectionsCasted); expect(result).toEqualRight(rootTeamCollection);
}); });
test('should successfully create a new child TeamCollection with valid inputs', async () => { test('should successfully create a new child TeamCollection with valid inputs', async () => {
@@ -734,10 +514,9 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
childTeamCollection.teamID, childTeamCollection.teamID,
childTeamCollection.title, childTeamCollection.title,
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(result).toEqualRight(childTeamCollectionCasted); expect(result).toEqualRight(childTeamCollection);
}); });
test('should send pubsub message to "team_coll/<teamID>/coll_added" if child TeamCollection is created successfully', async () => { test('should send pubsub message to "team_coll/<teamID>/coll_added" if child TeamCollection is created successfully', async () => {
@@ -753,13 +532,11 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
childTeamCollection.teamID, childTeamCollection.teamID,
childTeamCollection.title, childTeamCollection.title,
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollection.teamID}/coll_added`, `team_coll/${childTeamCollection.teamID}/coll_added`,
childTeamCollectionCasted, childTeamCollection,
); );
}); });
@@ -776,13 +553,11 @@ describe('createCollection', () => {
const result = await teamCollectionService.createCollection( const result = await teamCollectionService.createCollection(
rootTeamCollection.teamID, rootTeamCollection.teamID,
'abcdefg', 'abcdefg',
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.id, rootTeamCollection.id,
); );
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${rootTeamCollection.teamID}/coll_added`, `team_coll/${rootTeamCollection.teamID}/coll_added`,
rootTeamCollectionsCasted, rootTeamCollection,
); );
}); });
}); });
@@ -812,7 +587,7 @@ describe('renameCollection', () => {
'NewTitle', 'NewTitle',
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...rootTeamCollectionsCasted, ...rootTeamCollection,
title: 'NewTitle', title: 'NewTitle',
}); });
}); });
@@ -850,7 +625,7 @@ describe('renameCollection', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${rootTeamCollection.teamID}/coll_updated`, `team_coll/${rootTeamCollection.teamID}/coll_updated`,
{ {
...rootTeamCollectionsCasted, ...rootTeamCollection,
title: 'NewTitle', title: 'NewTitle',
}, },
); );
@@ -1057,8 +832,9 @@ describe('moveCollection', () => {
null, null,
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...childTeamCollectionCasted, ...childTeamCollection,
parentID: null, parentID: null,
orderIndex: 2,
}); });
}); });
@@ -1114,8 +890,9 @@ describe('moveCollection', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollection.teamID}/coll_moved`, `team_coll/${childTeamCollection.teamID}/coll_moved`,
{ {
...childTeamCollectionCasted, ...childTeamCollection,
parentID: null, parentID: null,
orderIndex: 2,
}, },
); );
}); });
@@ -1154,8 +931,9 @@ describe('moveCollection', () => {
childTeamCollection_2.id, childTeamCollection_2.id,
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...rootTeamCollectionsCasted, ...rootTeamCollection,
parentID: childTeamCollection_2Casted.id, parentID: childTeamCollection_2.id,
orderIndex: 1,
}); });
}); });
@@ -1195,8 +973,9 @@ describe('moveCollection', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollection_2.teamID}/coll_moved`, `team_coll/${childTeamCollection_2.teamID}/coll_moved`,
{ {
...rootTeamCollectionsCasted, ...rootTeamCollection,
parentID: childTeamCollection_2Casted.id, parentID: childTeamCollection_2.id,
orderIndex: 1,
}, },
); );
}); });
@@ -1235,8 +1014,9 @@ describe('moveCollection', () => {
childTeamCollection_2.id, childTeamCollection_2.id,
); );
expect(result).toEqualRight({ expect(result).toEqualRight({
...childTeamCollectionCasted, ...childTeamCollection,
parentID: childTeamCollection_2Casted.id, parentID: childTeamCollection_2.id,
orderIndex: 1,
}); });
}); });
@@ -1276,8 +1056,9 @@ describe('moveCollection', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollection.teamID}/coll_moved`, `team_coll/${childTeamCollection.teamID}/coll_moved`,
{ {
...childTeamCollectionCasted, ...childTeamCollection,
parentID: childTeamCollection_2Casted.id, parentID: childTeamCollection_2.id,
orderIndex: 1,
}, },
); );
}); });
@@ -1373,7 +1154,7 @@ describe('updateCollectionOrder', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollectionList[4].teamID}/coll_order_updated`, `team_coll/${childTeamCollectionList[4].teamID}/coll_order_updated`,
{ {
collection: rootTeamCollectionListCasted[4], collection: rootTeamCollectionList[4],
nextCollection: null, nextCollection: null,
}, },
); );
@@ -1454,8 +1235,8 @@ describe('updateCollectionOrder', () => {
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${childTeamCollectionList[2].teamID}/coll_order_updated`, `team_coll/${childTeamCollectionList[2].teamID}/coll_order_updated`,
{ {
collection: childTeamCollectionListCasted[4], collection: childTeamCollectionList[4],
nextCollection: childTeamCollectionListCasted[2], nextCollection: childTeamCollectionList[2],
}, },
); );
}); });
@@ -1521,7 +1302,7 @@ describe('importCollectionsFromJSON', () => {
); );
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${rootTeamCollection.teamID}/coll_added`, `team_coll/${rootTeamCollection.teamID}/coll_added`,
rootTeamCollectionsCasted, rootTeamCollection,
); );
}); });
}); });
@@ -1640,7 +1421,7 @@ describe('replaceCollectionsWithJSON', () => {
); );
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${rootTeamCollection.teamID}/coll_added`, `team_coll/${rootTeamCollection.teamID}/coll_added`,
rootTeamCollectionsCasted, rootTeamCollection,
); );
}); });
}); });
@@ -1677,64 +1458,4 @@ describe('totalCollectionsInTeam', () => {
}); });
}); });
describe('updateTeamCollection', () => {
test('should throw TEAM_COLL_SHORT_TITLE if title is invalid', async () => {
const result = await teamCollectionService.updateTeamCollection(
rootTeamCollection.id,
JSON.stringify(rootTeamCollection.data),
'de',
);
expect(result).toEqualLeft(TEAM_COLL_SHORT_TITLE);
});
test('should throw TEAM_COLL_DATA_INVALID is collection data is invalid', async () => {
const result = await teamCollectionService.updateTeamCollection(
rootTeamCollection.id,
'{',
rootTeamCollection.title,
);
expect(result).toEqualLeft(TEAM_COLL_DATA_INVALID);
});
test('should throw TEAM_COLL_NOT_FOUND is collectionID is invalid', async () => {
mockPrisma.teamCollection.update.mockRejectedValueOnce('RecordNotFound');
const result = await teamCollectionService.updateTeamCollection(
'invalid_id',
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.title,
);
expect(result).toEqualLeft(TEAM_COLL_NOT_FOUND);
});
test('should successfully update a collection', async () => {
mockPrisma.teamCollection.update.mockResolvedValueOnce(rootTeamCollection);
const result = await teamCollectionService.updateTeamCollection(
rootTeamCollection.id,
JSON.stringify({ foo: 'bar' }),
'new_title',
);
expect(result).toEqualRight({
data: JSON.stringify({ foo: 'bar' }),
title: 'new_title',
...rootTeamCollectionsCasted,
});
});
test('should send pubsub message to "team_coll/<teamID>/coll_updated" if TeamCollection is updated successfully', async () => {
mockPrisma.teamCollection.update.mockResolvedValueOnce(rootTeamCollection);
const result = await teamCollectionService.updateTeamCollection(
rootTeamCollection.id,
JSON.stringify(rootTeamCollection.data),
rootTeamCollection.title,
);
expect(mockPubSub.publish).toHaveBeenCalledWith(
`team_coll/${rootTeamCollection.teamID}/coll_updated`,
rootTeamCollectionsCasted,
);
});
});
//ToDo: write test cases for exportCollectionsToJSON //ToDo: write test cases for exportCollectionsToJSON

View File

@@ -13,7 +13,6 @@ import {
TEAM_COLL_IS_PARENT_COLL, TEAM_COLL_IS_PARENT_COLL,
TEAM_COL_SAME_NEXT_COLL, TEAM_COL_SAME_NEXT_COLL,
TEAM_COL_REORDERING_FAILED, TEAM_COL_REORDERING_FAILED,
TEAM_COLL_DATA_INVALID,
} from '../errors'; } from '../errors';
import { PubSubService } from '../pubsub/pubsub.service'; import { PubSubService } from '../pubsub/pubsub.service';
import { isValidLength } from 'src/utils'; import { isValidLength } from 'src/utils';
@@ -70,7 +69,6 @@ export class TeamCollectionService {
this.generatePrismaQueryObjForFBCollFolder(f, teamID, index + 1), this.generatePrismaQueryObjForFBCollFolder(f, teamID, index + 1),
), ),
}, },
data: folder.data ?? undefined,
}; };
} }
@@ -120,7 +118,6 @@ export class TeamCollectionService {
name: collection.right.title, name: collection.right.title,
folders: childrenCollectionObjects, folders: childrenCollectionObjects,
requests: requests.map((x) => x.request), requests: requests.map((x) => x.request),
data: JSON.stringify(collection.right.data),
}; };
return E.right(result); return E.right(result);
@@ -201,11 +198,8 @@ export class TeamCollectionService {
), ),
); );
teamCollections.forEach((collection) => teamCollections.forEach((x) =>
this.pubsub.publish( this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x),
`team_coll/${destTeamID}/coll_added`,
this.cast(collection),
),
); );
return E.right(true); return E.right(true);
@@ -274,11 +268,8 @@ export class TeamCollectionService {
), ),
); );
teamCollections.forEach((collections) => teamCollections.forEach((x) =>
this.pubsub.publish( this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x),
`team_coll/${destTeamID}/coll_added`,
this.cast(collections),
),
); );
return E.right(true); return E.right(true);
@@ -286,17 +277,11 @@ export class TeamCollectionService {
/** /**
* Typecast a database TeamCollection to a TeamCollection model * Typecast a database TeamCollection to a TeamCollection model
*
* @param teamCollection database TeamCollection * @param teamCollection database TeamCollection
* @returns TeamCollection model * @returns TeamCollection model
*/ */
private cast(teamCollection: DBTeamCollection): TeamCollection { private cast(teamCollection: DBTeamCollection): TeamCollection {
return <TeamCollection>{ return <TeamCollection>{ ...teamCollection };
id: teamCollection.id,
title: teamCollection.title,
parentID: teamCollection.parentID,
data: !teamCollection.data ? null : JSON.stringify(teamCollection.data),
};
} }
/** /**
@@ -339,7 +324,7 @@ export class TeamCollectionService {
}); });
if (!teamCollection) return null; if (!teamCollection) return null;
return !teamCollection.parent ? null : this.cast(teamCollection.parent); return teamCollection.parent;
} }
/** /**
@@ -350,12 +335,12 @@ export class TeamCollectionService {
* @param take Number of items we want returned * @param take Number of items we want returned
* @returns A list of child collections * @returns A list of child collections
*/ */
async getChildrenOfCollection( getChildrenOfCollection(
collectionID: string, collectionID: string,
cursor: string | null, cursor: string | null,
take: number, take: number,
) { ) {
const res = await this.prisma.teamCollection.findMany({ return this.prisma.teamCollection.findMany({
where: { where: {
parentID: collectionID, parentID: collectionID,
}, },
@@ -366,12 +351,6 @@ export class TeamCollectionService {
skip: cursor ? 1 : 0, skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
}); });
const childCollections = res.map((teamCollection) =>
this.cast(teamCollection),
);
return childCollections;
} }
/** /**
@@ -387,7 +366,7 @@ export class TeamCollectionService {
cursor: string | null, cursor: string | null,
take: number, take: number,
) { ) {
const res = await this.prisma.teamCollection.findMany({ return this.prisma.teamCollection.findMany({
where: { where: {
teamID, teamID,
parentID: null, parentID: null,
@@ -399,12 +378,6 @@ export class TeamCollectionService {
skip: cursor ? 1 : 0, skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
}); });
const teamCollections = res.map((teamCollection) =>
this.cast(teamCollection),
);
return teamCollections;
} }
/** /**
@@ -497,7 +470,6 @@ export class TeamCollectionService {
async createCollection( async createCollection(
teamID: string, teamID: string,
title: string, title: string,
data: string | null = null,
parentTeamCollectionID: string | null, parentTeamCollectionID: string | null,
) { ) {
const isTitleValid = isValidLength(title, this.TITLE_LENGTH); const isTitleValid = isValidLength(title, this.TITLE_LENGTH);
@@ -509,13 +481,6 @@ export class TeamCollectionService {
if (O.isNone(isOwner)) return E.left(TEAM_NOT_OWNER); if (O.isNone(isOwner)) return E.left(TEAM_NOT_OWNER);
} }
if (data === '') return E.left(TEAM_COLL_DATA_INVALID);
if (data) {
const jsonReq = stringToJson(data);
if (E.isLeft(jsonReq)) return E.left(TEAM_COLL_DATA_INVALID);
data = jsonReq.right;
}
const isParent = parentTeamCollectionID const isParent = parentTeamCollectionID
? { ? {
connect: { connect: {
@@ -533,23 +498,18 @@ export class TeamCollectionService {
}, },
}, },
parent: isParent, parent: isParent,
data: data ?? undefined,
orderIndex: !parentTeamCollectionID orderIndex: !parentTeamCollectionID
? (await this.getRootCollectionsCount(teamID)) + 1 ? (await this.getRootCollectionsCount(teamID)) + 1
: (await this.getChildCollectionsCount(parentTeamCollectionID)) + 1, : (await this.getChildCollectionsCount(parentTeamCollectionID)) + 1,
}, },
}); });
this.pubsub.publish( this.pubsub.publish(`team_coll/${teamID}/coll_added`, teamCollection);
`team_coll/${teamID}/coll_added`,
this.cast(teamCollection),
);
return E.right(this.cast(teamCollection)); return E.right(this.cast(teamCollection));
} }
/** /**
* @deprecated Use updateTeamCollection method instead
* Update the title of a TeamCollection * Update the title of a TeamCollection
* *
* @param collectionID The Collection ID * @param collectionID The Collection ID
@@ -572,10 +532,10 @@ export class TeamCollectionService {
this.pubsub.publish( this.pubsub.publish(
`team_coll/${updatedTeamCollection.teamID}/coll_updated`, `team_coll/${updatedTeamCollection.teamID}/coll_updated`,
this.cast(updatedTeamCollection), updatedTeamCollection,
); );
return E.right(this.cast(updatedTeamCollection)); return E.right(updatedTeamCollection);
} catch (error) { } catch (error) {
return E.left(TEAM_COLL_NOT_FOUND); return E.left(TEAM_COLL_NOT_FOUND);
} }
@@ -734,8 +694,8 @@ export class TeamCollectionService {
* @returns An Option of boolean, is parent or not * @returns An Option of boolean, is parent or not
*/ */
private async isParent( private async isParent(
collection: DBTeamCollection, collection: TeamCollection,
destCollection: DBTeamCollection, destCollection: TeamCollection,
): Promise<O.Option<boolean>> { ): Promise<O.Option<boolean>> {
//* Recursively check if collection is a parent by going up the tree of child-parent collections until we reach a root collection i.e parentID === null //* Recursively check if collection is a parent by going up the tree of child-parent collections until we reach a root collection i.e parentID === null
//* Valid condition, isParent returns false //* Valid condition, isParent returns false
@@ -1011,49 +971,4 @@ export class TeamCollectionService {
const teamCollectionsCount = this.prisma.teamCollection.count(); const teamCollectionsCount = this.prisma.teamCollection.count();
return teamCollectionsCount; return teamCollectionsCount;
} }
/**
* Update Team Collection details
*
* @param collectionID Collection ID
* @param collectionData new header data in a JSONified string form
* @param newTitle New title of the collection
* @returns Updated TeamCollection
*/
async updateTeamCollection(
collectionID: string,
collectionData: string = null,
newTitle: string = null,
) {
try {
if (newTitle != null) {
const isTitleValid = isValidLength(newTitle, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(TEAM_COLL_SHORT_TITLE);
}
if (collectionData === '') return E.left(TEAM_COLL_DATA_INVALID);
if (collectionData) {
const jsonReq = stringToJson(collectionData);
if (E.isLeft(jsonReq)) return E.left(TEAM_COLL_DATA_INVALID);
collectionData = jsonReq.right;
}
const updatedTeamCollection = await this.prisma.teamCollection.update({
where: { id: collectionID },
data: {
data: collectionData ?? undefined,
title: newTitle ?? undefined,
},
});
this.pubsub.publish(
`team_coll/${updatedTeamCollection.teamID}/coll_updated`,
this.cast(updatedTeamCollection),
);
return E.right(this.cast(updatedTeamCollection));
} catch (e) {
return E.left(TEAM_COLL_NOT_FOUND);
}
}
} }

View File

@@ -42,7 +42,6 @@ const teamCollection: DbTeamCollection = {
id: 'team-coll-1', id: 'team-coll-1',
parentID: null, parentID: null,
teamID: team.id, teamID: team.id,
data: {},
title: 'Team Collection 1', title: 'Team Collection 1',
orderIndex: 1, orderIndex: 1,
createdOn: new Date(), createdOn: new Date(),

View File

@@ -1,8 +1,6 @@
// This interface defines how data will be received from the app when we are importing Hoppscotch collections
export interface CollectionFolder { export interface CollectionFolder {
id?: string; id?: string;
folders: CollectionFolder[]; folders: CollectionFolder[];
requests: any[]; requests: any[];
name: string; name: string;
data?: string;
} }

View File

@@ -6,13 +6,6 @@ import { PaginationArgs } from 'src/types/input-types.args';
export class CreateRootUserCollectionArgs { export class CreateRootUserCollectionArgs {
@Field({ name: 'title', description: 'Title of the new user collection' }) @Field({ name: 'title', description: 'Title of the new user collection' })
title: string; title: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
} }
@ArgsType() @ArgsType()
export class CreateChildUserCollectionArgs { export class CreateChildUserCollectionArgs {
@@ -24,13 +17,6 @@ export class CreateChildUserCollectionArgs {
description: 'ID of the parent to the new user collection', description: 'ID of the parent to the new user collection',
}) })
parentUserCollectionID: string; parentUserCollectionID: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
} }
@ArgsType() @ArgsType()
@@ -109,26 +95,3 @@ export class ImportUserCollectionsFromJSONArgs {
}) })
parentCollectionID?: string; parentCollectionID?: string;
} }
@ArgsType()
export class UpdateUserCollectionsArgs {
@Field(() => ID, {
name: 'userCollectionID',
description: 'ID of the user collection',
})
userCollectionID: string;
@Field({
name: 'newTitle',
description: 'The updated title of the user collection',
nullable: true,
})
newTitle: string;
@Field({
name: 'data',
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
}

View File

@@ -30,7 +30,6 @@ import {
MoveUserCollectionArgs, MoveUserCollectionArgs,
RenameUserCollectionsArgs, RenameUserCollectionsArgs,
UpdateUserCollectionArgs, UpdateUserCollectionArgs,
UpdateUserCollectionsArgs,
} from './input-type.args'; } from './input-type.args';
import { ReqType } from 'src/types/RequestTypes'; import { ReqType } from 'src/types/RequestTypes';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
@@ -143,13 +142,7 @@ export class UserCollectionResolver {
); );
if (E.isLeft(userCollection)) throwErr(userCollection.left); if (E.isLeft(userCollection)) throwErr(userCollection.left);
return <UserCollection>{ return userCollection.right;
...userCollection.right,
userID: userCollection.right.userUid,
data: !userCollection.right.data
? null
: JSON.stringify(userCollection.right.data),
};
} }
@Query(() => UserCollectionExportJSONData, { @Query(() => UserCollectionExportJSONData, {
@@ -198,7 +191,6 @@ export class UserCollectionResolver {
await this.userCollectionService.createUserCollection( await this.userCollectionService.createUserCollection(
user, user,
args.title, args.title,
args.data,
null, null,
ReqType.REST, ReqType.REST,
); );
@@ -220,7 +212,6 @@ export class UserCollectionResolver {
await this.userCollectionService.createUserCollection( await this.userCollectionService.createUserCollection(
user, user,
args.title, args.title,
args.data,
null, null,
ReqType.GQL, ReqType.GQL,
); );
@@ -241,7 +232,6 @@ export class UserCollectionResolver {
await this.userCollectionService.createUserCollection( await this.userCollectionService.createUserCollection(
user, user,
args.title, args.title,
args.data,
args.parentUserCollectionID, args.parentUserCollectionID,
ReqType.GQL, ReqType.GQL,
); );
@@ -262,7 +252,6 @@ export class UserCollectionResolver {
await this.userCollectionService.createUserCollection( await this.userCollectionService.createUserCollection(
user, user,
args.title, args.title,
args.data,
args.parentUserCollectionID, args.parentUserCollectionID,
ReqType.REST, ReqType.REST,
); );
@@ -370,26 +359,6 @@ export class UserCollectionResolver {
return importedCollection.right; return importedCollection.right;
} }
@Mutation(() => UserCollection, {
description: 'Update a UserCollection',
})
@UseGuards(GqlAuthGuard)
async updateUserCollection(
@GqlUser() user: AuthUser,
@Args() args: UpdateUserCollectionsArgs,
) {
const updatedUserCollection =
await this.userCollectionService.updateUserCollection(
args.newTitle,
args.data,
args.userCollectionID,
user.uid,
);
if (E.isLeft(updatedUserCollection)) throwErr(updatedUserCollection.left);
return updatedUserCollection.right;
}
// Subscriptions // Subscriptions
@Subscription(() => UserCollection, { @Subscription(() => UserCollection, {
description: 'Listen for User Collection Creation', description: 'Listen for User Collection Creation',

View File

@@ -12,7 +12,6 @@ import {
USER_NOT_FOUND, USER_NOT_FOUND,
USER_NOT_OWNER, USER_NOT_OWNER,
USER_COLL_INVALID_JSON, USER_COLL_INVALID_JSON,
USER_COLL_DATA_INVALID,
} from 'src/errors'; } from 'src/errors';
import { PrismaService } from 'src/prisma/prisma.service'; import { PrismaService } from 'src/prisma/prisma.service';
import { AuthUser } from 'src/types/AuthUser'; import { AuthUser } from 'src/types/AuthUser';
@@ -44,12 +43,8 @@ export class UserCollectionService {
*/ */
private cast(collection: UserCollection) { private cast(collection: UserCollection) {
return <UserCollectionModel>{ return <UserCollectionModel>{
id: collection.id, ...collection,
title: collection.title,
type: collection.type,
parentID: collection.parentID,
userID: collection.userUid, userID: collection.userUid,
data: !collection.data ? null : JSON.stringify(collection.data),
}; };
} }
@@ -151,7 +146,7 @@ export class UserCollectionService {
}, },
}); });
return !parent ? null : this.cast(parent); return parent;
} }
/** /**
@@ -169,7 +164,7 @@ export class UserCollectionService {
take: number, take: number,
type: ReqType, type: ReqType,
) { ) {
const res = await this.prisma.userCollection.findMany({ return this.prisma.userCollection.findMany({
where: { where: {
parentID: collectionID, parentID: collectionID,
type: type, type: type,
@@ -181,12 +176,6 @@ export class UserCollectionService {
skip: cursor ? 1 : 0, skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
}); });
const childCollections = res.map((childCollection) =>
this.cast(childCollection),
);
return childCollections;
} }
/** /**
@@ -222,20 +211,12 @@ export class UserCollectionService {
async createUserCollection( async createUserCollection(
user: AuthUser, user: AuthUser,
title: string, title: string,
data: string | null = null,
parentUserCollectionID: string | null, parentUserCollectionID: string | null,
type: ReqType, type: ReqType,
) { ) {
const isTitleValid = isValidLength(title, this.TITLE_LENGTH); const isTitleValid = isValidLength(title, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(USER_COLL_SHORT_TITLE); if (!isTitleValid) return E.left(USER_COLL_SHORT_TITLE);
if (data === '') return E.left(USER_COLL_DATA_INVALID);
if (data) {
const jsonReq = stringToJson(data);
if (E.isLeft(jsonReq)) return E.left(USER_COLL_DATA_INVALID);
data = jsonReq.right;
}
// If creating a child collection // If creating a child collection
if (parentUserCollectionID !== null) { if (parentUserCollectionID !== null) {
const parentCollection = await this.getUserCollection( const parentCollection = await this.getUserCollection(
@@ -270,19 +251,15 @@ export class UserCollectionService {
}, },
}, },
parent: isParent, parent: isParent,
data: data ?? undefined,
orderIndex: !parentUserCollectionID orderIndex: !parentUserCollectionID
? (await this.getRootCollectionsCount(user.uid)) + 1 ? (await this.getRootCollectionsCount(user.uid)) + 1
: (await this.getChildCollectionsCount(parentUserCollectionID)) + 1, : (await this.getChildCollectionsCount(parentUserCollectionID)) + 1,
}, },
}); });
await this.pubsub.publish( await this.pubsub.publish(`user_coll/${user.uid}/created`, userCollection);
`user_coll/${user.uid}/created`,
this.cast(userCollection),
);
return E.right(this.cast(userCollection)); return E.right(userCollection);
} }
/** /**
@@ -299,7 +276,7 @@ export class UserCollectionService {
take: number, take: number,
type: ReqType, type: ReqType,
) { ) {
const res = await this.prisma.userCollection.findMany({ return this.prisma.userCollection.findMany({
where: { where: {
userUid: user.uid, userUid: user.uid,
parentID: null, parentID: null,
@@ -312,12 +289,6 @@ export class UserCollectionService {
skip: cursor ? 1 : 0, skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
}); });
const userCollections = res.map((childCollection) =>
this.cast(childCollection),
);
return userCollections;
} }
/** /**
@@ -336,7 +307,7 @@ export class UserCollectionService {
take: number, take: number,
type: ReqType, type: ReqType,
) { ) {
const res = await this.prisma.userCollection.findMany({ return this.prisma.userCollection.findMany({
where: { where: {
userUid: user.uid, userUid: user.uid,
parentID: userCollectionID, parentID: userCollectionID,
@@ -346,16 +317,9 @@ export class UserCollectionService {
skip: cursor ? 1 : 0, skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined, cursor: cursor ? { id: cursor } : undefined,
}); });
const childCollections = res.map((childCollection) =>
this.cast(childCollection),
);
return childCollections;
} }
/** /**
* @deprecated Use updateUserCollection method instead
* Update the title of a UserCollection * Update the title of a UserCollection
* *
* @param newTitle The new title of collection * @param newTitle The new title of collection
@@ -387,10 +351,10 @@ export class UserCollectionService {
this.pubsub.publish( this.pubsub.publish(
`user_coll/${updatedUserCollection.userUid}/updated`, `user_coll/${updatedUserCollection.userUid}/updated`,
this.cast(updatedUserCollection), updatedUserCollection,
); );
return E.right(this.cast(updatedUserCollection)); return E.right(updatedUserCollection);
} catch (error) { } catch (error) {
return E.left(USER_COLL_NOT_FOUND); return E.left(USER_COLL_NOT_FOUND);
} }
@@ -627,10 +591,10 @@ export class UserCollectionService {
this.pubsub.publish( this.pubsub.publish(
`user_coll/${collection.right.userUid}/moved`, `user_coll/${collection.right.userUid}/moved`,
this.cast(updatedCollection.right), updatedCollection.right,
); );
return E.right(this.cast(updatedCollection.right)); return E.right(updatedCollection.right);
} }
// destCollectionID != null i.e move into another collection // destCollectionID != null i.e move into another collection
@@ -678,10 +642,10 @@ export class UserCollectionService {
this.pubsub.publish( this.pubsub.publish(
`user_coll/${collection.right.userUid}/moved`, `user_coll/${collection.right.userUid}/moved`,
this.cast(updatedCollection.right), updatedCollection.right,
); );
return E.right(this.cast(updatedCollection.right)); return E.right(updatedCollection.right);
} }
/** /**
@@ -882,7 +846,6 @@ export class UserCollectionService {
...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread ...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread
}; };
}), }),
data: JSON.stringify(collection.right.data),
}; };
return E.right(result); return E.right(result);
@@ -955,7 +918,6 @@ export class UserCollectionService {
...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread ...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread
}; };
}), }),
data: JSON.stringify(parentCollection.right.data),
}), }),
collectionType: parentCollection.right.type, collectionType: parentCollection.right.type,
}); });
@@ -1009,7 +971,6 @@ export class UserCollectionService {
this.generatePrismaQueryObj(f, userID, index + 1, reqType), this.generatePrismaQueryObj(f, userID, index + 1, reqType),
), ),
}, },
data: folder.data ?? undefined,
}; };
} }
@@ -1079,63 +1040,10 @@ export class UserCollectionService {
), ),
); );
userCollections.forEach((collection) => userCollections.forEach((x) =>
this.pubsub.publish(`user_coll/${userID}/created`, this.cast(collection)), this.pubsub.publish(`user_coll/${userID}/created`, x),
); );
return E.right(true); return E.right(true);
} }
/**
* Update a UserCollection
*
* @param newTitle The new title of collection
* @param userCollectionID The Collection Id
* @param userID The User UID
* @returns An Either of the updated UserCollection
*/
async updateUserCollection(
newTitle: string = null,
collectionData: string | null = null,
userCollectionID: string,
userID: string,
) {
if (collectionData === '') return E.left(USER_COLL_DATA_INVALID);
if (collectionData) {
const jsonReq = stringToJson(collectionData);
if (E.isLeft(jsonReq)) return E.left(USER_COLL_DATA_INVALID);
collectionData = jsonReq.right;
}
if (newTitle != null) {
const isTitleValid = isValidLength(newTitle, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(USER_COLL_SHORT_TITLE);
}
// Check to see is the collection belongs to the user
const isOwner = await this.isOwnerCheck(userCollectionID, userID);
if (O.isNone(isOwner)) return E.left(USER_NOT_OWNER);
try {
const updatedUserCollection = await this.prisma.userCollection.update({
where: {
id: userCollectionID,
},
data: {
data: collectionData ?? undefined,
title: newTitle ?? undefined,
},
});
this.pubsub.publish(
`user_coll/${updatedUserCollection.userUid}/updated`,
this.cast(updatedUserCollection),
);
return E.right(this.cast(updatedUserCollection));
} catch (error) {
return E.left(USER_COLL_NOT_FOUND);
}
}
} }

View File

@@ -13,12 +13,6 @@ export class UserCollection {
}) })
title: string; title: string;
@Field({
description: 'JSON string representing the collection data',
nullable: true,
})
data: string;
@Field(() => ReqType, { @Field(() => ReqType, {
description: 'Type of the user collection', description: 'Type of the user collection',
}) })

View File

@@ -1,6 +1,6 @@
{ {
"name": "@hoppscotch/cli", "name": "@hoppscotch/cli",
"version": "0.4.0", "version": "0.3.3",
"description": "A CLI to run Hoppscotch test scripts in CI environments.", "description": "A CLI to run Hoppscotch test scripts in CI environments.",
"homepage": "https://hoppscotch.io", "homepage": "https://hoppscotch.io",
"main": "dist/index.js", "main": "dist/index.js",
@@ -10,9 +10,6 @@
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"engines": {
"node": ">=18"
},
"scripts": { "scripts": {
"build": "pnpm exec tsup", "build": "pnpm exec tsup",
"dev": "pnpm exec tsup --watch", "dev": "pnpm exec tsup --watch",
@@ -41,24 +38,24 @@
"devDependencies": { "devDependencies": {
"@hoppscotch/data": "workspace:^", "@hoppscotch/data": "workspace:^",
"@hoppscotch/js-sandbox": "workspace:^", "@hoppscotch/js-sandbox": "workspace:^",
"@relmify/jest-fp-ts": "^2.1.1", "@relmify/jest-fp-ts": "^2.0.2",
"@swc/core": "^1.3.92", "@swc/core": "^1.2.181",
"@types/jest": "^29.5.5", "@types/jest": "^27.4.1",
"@types/lodash": "^4.14.199", "@types/lodash": "^4.14.181",
"@types/qs": "^6.9.8", "@types/qs": "^6.9.7",
"axios": "^0.21.4", "axios": "^0.21.4",
"chalk": "^4.1.2", "chalk": "^4.1.1",
"commander": "^11.0.0", "commander": "^8.0.0",
"esm": "^3.2.25", "esm": "^3.2.25",
"fp-ts": "^2.16.1", "fp-ts": "^2.12.1",
"io-ts": "^2.2.20", "io-ts": "^2.2.16",
"jest": "^29.7.0", "jest": "^27.5.1",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"prettier": "^3.0.3", "prettier": "^2.8.4",
"qs": "^6.11.2", "qs": "^6.10.3",
"ts-jest": "^29.1.1", "ts-jest": "^27.1.4",
"tsup": "^7.2.0", "tsup": "^5.12.7",
"typescript": "^5.2.2", "typescript": "^4.6.4",
"zod": "^3.22.4" "zod": "^3.22.2"
} }
} }

View File

@@ -57,7 +57,7 @@ module.exports = {
{ {
name: "localStorage", name: "localStorage",
message: message:
"Do not use 'localStorage' directly. Please use the PersistenceService", "Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
}, },
], ],
// window.localStorage block // window.localStorage block
@@ -66,7 +66,7 @@ module.exports = {
{ {
selector: "CallExpression[callee.object.property.name='localStorage']", selector: "CallExpression[callee.object.property.name='localStorage']",
message: message:
"Do not use 'localStorage' directly. Please use the PersistenceService", "Do not use 'localStorage' directly. Please use localpersistence.ts functions or stores",
}, },
], ],
}, },

View File

@@ -4,6 +4,5 @@ module.exports = {
singleQuote: false, singleQuote: false,
printWidth: 80, printWidth: 80,
useTabs: false, useTabs: false,
tabWidth: 2, tabWidth: 2
plugins: ["prettier-plugin-tailwindcss"],
} }

View File

@@ -1,25 +1,7 @@
/*
* Write hoppscotch-common related custom styles in this file.
* If styles are sharable across all package then write into hoppscotch-ui/assets/scss/styles.scss file.
*/
* { * {
backface-visibility: hidden; @apply backface-hidden;
-moz-backface-visibility: hidden; @apply before:backface-hidden;
-webkit-backface-visibility: hidden; @apply after:backface-hidden;
&::before {
backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
&::after {
backface-visibility: hidden;
-moz-backface-visibility: hidden;
-webkit-backface-visibility: hidden;
}
@apply selection:bg-accentDark; @apply selection:bg-accentDark;
@apply selection:text-accentContrast; @apply selection:text-accentContrast;
@apply overscroll-none; @apply overscroll-none;
@@ -29,25 +11,17 @@
@apply antialiased; @apply antialiased;
accent-color: var(--accent-color); accent-color: var(--accent-color);
font-variant-ligatures: common-ligatures; font-variant-ligatures: common-ligatures;
// Colors
--info-color: #ec4899;
--success-color: #10b981;
--blue-color: #3b82f6;
--warning-color: #f59e0b;
--cl-error-color: #ef4444;
--sv-error-color: #dc2626;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@apply bg-transparent; @apply bg-transparent;
@apply border-b-0 border-l border-r-0 border-t-0 border-solid border-dividerLight; @apply border-solid border-l border-dividerLight border-t-0 border-b-0 border-r-0;
} }
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
@apply bg-divider bg-clip-content; @apply bg-divider bg-clip-content;
@apply rounded-full; @apply rounded-full;
@apply border-4 border-solid border-transparent; @apply border-solid border-transparent border-4;
@apply hover:bg-dividerDark; @apply hover:bg-dividerDark;
@apply hover:bg-clip-content; @apply hover:bg-clip-content;
} }
@@ -80,7 +54,7 @@ html {
body { body {
@apply bg-primary; @apply bg-primary;
@apply text-body text-secondary; @apply text-secondary text-body;
@apply font-medium; @apply font-medium;
@apply select-none; @apply select-none;
@apply overflow-x-hidden; @apply overflow-x-hidden;
@@ -150,8 +124,8 @@ a {
&.link { &.link {
@apply items-center; @apply items-center;
@apply px-1 py-0.5; @apply py-0.5 px-1;
@apply -mx-1 -my-0.5; @apply -my-0.5 -mx-1;
@apply text-accent; @apply text-accent;
@apply rounded; @apply rounded;
@apply hover:text-accentDark; @apply hover:text-accentDark;
@@ -166,7 +140,7 @@ a {
@apply shadow-none #{!important}; @apply shadow-none #{!important};
@apply fixed; @apply fixed;
@apply inline-flex; @apply inline-flex;
@apply -mt-8; @apply -mt-7.5;
} }
} }
@@ -180,7 +154,7 @@ a {
@apply flex; @apply flex;
@apply text-tiny text-primary; @apply text-tiny text-primary;
@apply font-semibold; @apply font-semibold;
@apply px-2 py-1; @apply py-1 px-2;
@apply truncate; @apply truncate;
@apply leading-normal; @apply leading-normal;
@apply items-center; @apply items-center;
@@ -188,7 +162,7 @@ a {
kbd { kbd {
@apply hidden; @apply hidden;
@apply font-sans; @apply font-sans;
background-color: rgba(107, 114, 128, 0.45); @apply bg-gray-500/45;
@apply text-primaryLight; @apply text-primaryLight;
@apply rounded-sm; @apply rounded-sm;
@apply px-1; @apply px-1;
@@ -196,12 +170,6 @@ a {
@apply truncate; @apply truncate;
@apply sm:inline-flex; @apply sm:inline-flex;
} }
.env-icon {
@apply transition;
@apply inline-flex;
@apply items-center;
}
} }
.tippy-svg-arrow { .tippy-svg-arrow {
@@ -227,7 +195,7 @@ a {
@apply max-h-[45vh]; @apply max-h-[45vh];
@apply items-stretch; @apply items-stretch;
@apply overflow-y-auto; @apply overflow-y-auto;
@apply text-body text-secondary; @apply text-secondary text-body;
@apply p-2; @apply p-2;
@apply leading-normal; @apply leading-normal;
@apply focus:outline-none; @apply focus:outline-none;
@@ -266,7 +234,7 @@ hr {
.heading { .heading {
@apply font-bold; @apply font-bold;
@apply text-lg text-secondaryDark; @apply text-secondaryDark text-lg;
@apply tracking-tight; @apply tracking-tight;
} }
@@ -275,7 +243,7 @@ hr {
.textarea { .textarea {
@apply flex; @apply flex;
@apply w-full; @apply w-full;
@apply px-4 py-2; @apply py-2 px-4;
@apply bg-transparent; @apply bg-transparent;
@apply rounded; @apply rounded;
@apply text-secondaryDark; @apply text-secondaryDark;
@@ -316,7 +284,7 @@ button {
@apply transform; @apply transform;
@apply origin-top-left; @apply origin-top-left;
@apply scale-75; @apply scale-75;
@apply -translate-y-4 translate-x-1; @apply translate-x-1 -translate-y-4;
} }
.floating-input:focus-within ~ label { .floating-input:focus-within ~ label {
@@ -325,7 +293,7 @@ button {
.floating-input ~ .end-actions { .floating-input ~ .end-actions {
@apply absolute; @apply absolute;
@apply right-[.05rem]; @apply right-0.2;
@apply inset-y-0; @apply inset-y-0;
@apply flex; @apply flex;
@apply items-center; @apply items-center;
@@ -367,23 +335,23 @@ pre.ace_editor {
} }
.info-response { .info-response {
color: var(--info-color); @apply text-pink-500;
} }
.success-response { .success-response {
color: var(--success-color); @apply text-green-500;
} }
.redir-response { .redir-response {
color: var(--warning-color); @apply text-yellow-500;
} }
.cl-error-response { .cl-error-response {
color: var(--cl-error-color); @apply text-red-500;
} }
.sv-error-response { .sv-error-response {
color: var(--sv-error-color); @apply text-red-600;
} }
.missing-data-response { .missing-data-response {
@@ -398,7 +366,7 @@ pre.ace_editor {
@apply px-4 py-2; @apply px-4 py-2;
@apply bg-tooltip; @apply bg-tooltip;
@apply border-secondaryDark; @apply border-secondaryDark;
@apply text-body text-primary; @apply text-primary text-body;
@apply justify-between; @apply justify-between;
@apply shadow-lg; @apply shadow-lg;
@apply font-semibold; @apply font-semibold;
@@ -426,7 +394,7 @@ pre.ace_editor {
@apply before:opacity-10; @apply before:opacity-10;
@apply before:inset-0; @apply before:inset-0;
@apply before:transition; @apply before:transition;
@apply before:content-['']; @apply before:content-DEFAULT;
@apply hover:no-underline; @apply hover:no-underline;
@apply hover:before:opacity-20; @apply hover:before:opacity-20;
} }
@@ -460,7 +428,7 @@ pre.ace_editor {
@apply before:opacity-0; @apply before:opacity-0;
@apply before:z-20; @apply before:z-20;
@apply before:transition; @apply before:transition;
@apply before:content-['']; @apply before:content-DEFAULT;
@apply hover:before:opacity-100; @apply hover:before:opacity-100;
} }
@@ -533,6 +501,22 @@ pre.ace_editor {
} }
} }
.cm-panel.cm-search [name="close"] {
@apply flex;
@apply items-center;
@apply justify-center;
@apply min-h-5;
@apply min-w-5;
@apply bg-primaryDark #{!important};
@apply sticky #{!important};
@apply right-0 #{!important};
@apply ml-auto #{!important};
@apply my-auto #{!important};
@apply rounded #{!important};
@apply outline #{!important};
@apply outline-divider #{!important};
}
.shortcut-key { .shortcut-key {
@apply inline-flex; @apply inline-flex;
@apply font-sans; @apply font-sans;

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -0,0 +1,274 @@
@mixin base-theme {
--font-sans: "Inter Variable", sans-serif;
--font-icon: "Material Symbols Rounded Variable";
--font-mono: "Roboto Mono Variable", monospace;
--font-size-body: 0.75rem;
--font-size-tiny: 0.688rem;
--line-height-body: 1rem;
--upper-primary-sticky-fold: 4.125rem;
--upper-secondary-sticky-fold: 6.188rem;
--upper-tertiary-sticky-fold: 8.25rem;
--upper-fourth-sticky-fold: 10.2rem;
--upper-mobile-primary-sticky-fold: 6.625rem;
--upper-mobile-secondary-sticky-fold: 8.688rem;
--upper-mobile-sticky-fold: 10.75rem;
--upper-mobile-tertiary-sticky-fold: 8.25rem;
--lower-primary-sticky-fold: 3rem;
--lower-secondary-sticky-fold: 5.063rem;
--lower-tertiary-sticky-fold: 7.125rem;
--lower-fourth-sticky-fold: 9.188rem;
--sidebar-primary-sticky-fold: 2rem;
}
@mixin dark-theme {
--primary-color: theme("colors.dark.800");
--primary-light-color: theme("colors.dark.600");
--primary-dark-color: theme("colors.neutral.800");
--primary-contrast-color: theme("colors.neutral.900");
--secondary-color: theme("colors.neutral.400");
--secondary-light-color: theme("colors.neutral.500");
--secondary-dark-color: theme("colors.neutral.50");
--divider-color: theme("colors.neutral.800");
--divider-light-color: theme("colors.dark.500");
--divider-dark-color: theme("colors.dark.300");
--error-color: theme("colors.stone.800");
--tooltip-color: theme("colors.neutral.100");
--popover-color: theme("colors.dark.700");
--editor-theme: "merbivore_soft";
}
@mixin light-theme {
--primary-color: theme("colors.white");
--primary-light-color: theme("colors.gray.50");
--primary-dark-color: theme("colors.gray.100");
--primary-contrast-color: theme("colors.light.50");
--secondary-color: theme("colors.gray.500");
--secondary-light-color: theme("colors.gray.400");
--secondary-dark-color: theme("colors.gray.900");
--divider-color: theme("colors.gray.100");
--divider-light-color: theme("colors.gray.100");
--divider-dark-color: theme("colors.gray.300");
--error-color: theme("colors.yellow.100");
--tooltip-color: theme("colors.neutral.800");
--popover-color: theme("colors.white");
--editor-theme: "textmate";
}
@mixin black-theme {
--primary-color: theme("colors.dark.900");
--primary-light-color: theme("colors.neutral.900");
--primary-dark-color: theme("colors.dark.800");
--primary-contrast-color: theme("colors.dark.900");
--secondary-color: theme("colors.neutral.400");
--secondary-light-color: theme("colors.neutral.500");
--secondary-dark-color: theme("colors.neutral.100");
--divider-color: theme("colors.dark.600");
--divider-light-color: theme("colors.dark.800");
--divider-dark-color: theme("colors.dark.200");
--error-color: theme("colors.stone.900");
--tooltip-color: theme("colors.neutral.100");
--popover-color: theme("colors.dark.900");
--editor-theme: "twilight";
}
@mixin dark-editor-theme {
--editor-type-color: theme("colors.purple.400");
--editor-name-color: theme("colors.blue.400");
--editor-operator-color: theme("colors.indigo.400");
--editor-invalid-color: theme("colors.red.400");
--editor-separator-color: theme("colors.gray.400");
--editor-meta-color: theme("colors.gray.400");
--editor-variable-color: theme("colors.green.400");
--editor-link-color: theme("colors.cyan.400");
--editor-process-color: theme("colors.fuchsia.400");
--editor-constant-color: theme("colors.violet.400");
--editor-keyword-color: theme("colors.pink.400");
}
@mixin light-editor-theme {
--editor-type-color: theme("colors.purple.600");
--editor-name-color: theme("colors.red.600");
--editor-operator-color: theme("colors.indigo.600");
--editor-invalid-color: theme("colors.red.600");
--editor-separator-color: theme("colors.gray.600");
--editor-meta-color: theme("colors.gray.600");
--editor-variable-color: theme("colors.green.600");
--editor-link-color: theme("colors.cyan.600");
--editor-process-color: theme("colors.blue.600");
--editor-constant-color: theme("colors.fuchsia.600");
--editor-keyword-color: theme("colors.pink.600");
}
@mixin black-editor-theme {
--editor-type-color: theme("colors.purple.400");
--editor-name-color: theme("colors.fuchsia.400");
--editor-operator-color: theme("colors.indigo.400");
--editor-invalid-color: theme("colors.red.400");
--editor-separator-color: theme("colors.gray.400");
--editor-meta-color: theme("colors.gray.400");
--editor-variable-color: theme("colors.green.400");
--editor-link-color: theme("colors.cyan.400");
--editor-process-color: theme("colors.violet.400");
--editor-constant-color: theme("colors.blue.400");
--editor-keyword-color: theme("colors.pink.400");
}
@mixin green-theme {
--accent-color: theme("colors.green.500");
--accent-light-color: theme("colors.green.400");
--accent-dark-color: theme("colors.green.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.green.200");
--gradient-via-color: theme("colors.green.400");
--gradient-to-color: theme("colors.green.600");
}
@mixin teal-theme {
--accent-color: theme("colors.teal.500");
--accent-light-color: theme("colors.teal.400");
--accent-dark-color: theme("colors.teal.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.teal.200");
--gradient-via-color: theme("colors.teal.400");
--gradient-to-color: theme("colors.teal.600");
}
@mixin blue-theme {
--accent-color: theme("colors.blue.500");
--accent-light-color: theme("colors.blue.400");
--accent-dark-color: theme("colors.blue.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.blue.200");
--gradient-via-color: theme("colors.blue.400");
--gradient-to-color: theme("colors.blue.600");
}
@mixin indigo-theme {
--accent-color: theme("colors.indigo.500");
--accent-light-color: theme("colors.indigo.400");
--accent-dark-color: theme("colors.indigo.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.indigo.200");
--gradient-via-color: theme("colors.indigo.400");
--gradient-to-color: theme("colors.indigo.600");
}
@mixin purple-theme {
--accent-color: theme("colors.purple.500");
--accent-light-color: theme("colors.purple.400");
--accent-dark-color: theme("colors.purple.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.purple.200");
--gradient-via-color: theme("colors.purple.400");
--gradient-to-color: theme("colors.purple.600");
}
@mixin yellow-theme {
--accent-color: theme("colors.yellow.500");
--accent-light-color: theme("colors.yellow.400");
--accent-dark-color: theme("colors.yellow.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.yellow.200");
--gradient-via-color: theme("colors.yellow.400");
--gradient-to-color: theme("colors.yellow.600");
}
@mixin orange-theme {
--accent-color: theme("colors.orange.500");
--accent-light-color: theme("colors.orange.400");
--accent-dark-color: theme("colors.orange.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.orange.200");
--gradient-via-color: theme("colors.orange.400");
--gradient-to-color: theme("colors.orange.600");
}
@mixin red-theme {
--accent-color: theme("colors.red.500");
--accent-light-color: theme("colors.red.400");
--accent-dark-color: theme("colors.red.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.red.200");
--gradient-via-color: theme("colors.red.400");
--gradient-to-color: theme("colors.red.600");
}
@mixin pink-theme {
--accent-color: theme("colors.pink.500");
--accent-light-color: theme("colors.pink.400");
--accent-dark-color: theme("colors.pink.600");
--accent-contrast-color: theme("colors.white");
--gradient-from-color: theme("colors.pink.200");
--gradient-via-color: theme("colors.pink.400");
--gradient-to-color: theme("colors.pink.600");
}
:root {
@include base-theme;
@include dark-theme;
@include dark-editor-theme;
@include green-theme;
}
:root.light {
@include light-theme;
@include light-editor-theme;
color-scheme: light;
}
:root.dark {
@include dark-theme;
@include dark-editor-theme;
color-scheme: dark;
}
:root.black {
@include black-theme;
@include black-editor-theme;
color-scheme: dark;
}
:root[data-accent="blue"] {
@include blue-theme;
}
:root[data-accent="green"] {
@include green-theme;
}
:root[data-accent="teal"] {
@include teal-theme;
}
:root[data-accent="indigo"] {
@include indigo-theme;
}
:root[data-accent="purple"] {
@include purple-theme;
}
:root[data-accent="orange"] {
@include orange-theme;
}
:root[data-accent="pink"] {
@include pink-theme;
}
:root[data-accent="red"] {
@include red-theme;
}
:root[data-accent="yellow"] {
@include yellow-theme;
}

View File

@@ -1,89 +0,0 @@
@mixin green-theme {
--accent-color: #10b981;
--accent-light-color: #34d399;
--accent-dark-color: #059669;
--accent-contrast-color: #fff;
--gradient-from-color: #a7f3d0;
--gradient-via-color: #34d399;
--gradient-to-color: #059669;
}
@mixin teal-theme {
--accent-color: #14b8a6;
--accent-light-color: #2dd4bf;
--accent-dark-color: #0d9488;
--accent-contrast-color: #fff;
--gradient-from-color: #99f6e4;
--gradient-via-color: #2dd4bf;
--gradient-to-color: #0d9488;
}
@mixin blue-theme {
--accent-color: #3b82f6;
--accent-light-color: #60a5fa;
--accent-dark-color: #2563eb;
--accent-contrast-color: #fff;
--gradient-from-color: #bfdbfe;
--gradient-via-color: #60a5fa;
--gradient-to-color: #2563eb;
}
@mixin indigo-theme {
--accent-color: #6366f1;
--accent-light-color: #818cf8;
--accent-dark-color: #4f46e5;
--accent-contrast-color: #fff;
--gradient-from-color: #c7d2fe;
--gradient-via-color: #818cf8;
--gradient-to-color: #4f46e5;
}
@mixin purple-theme {
--accent-color: #8b5cf6;
--accent-light-color: #a78bfa;
--accent-dark-color: #7c3aed;
--accent-contrast-color: #fff;
--gradient-from-color: #ddd6fe;
--gradient-via-color: #a78bfa;
--gradient-to-color: #7c3aed;
}
@mixin yellow-theme {
--accent-color: #f59e0b;
--accent-light-color: #fbbf24;
--accent-dark-color: #d97706;
--accent-contrast-color: #fff;
--gradient-from-color: #fde68a;
--gradient-via-color: #fbbf24;
--gradient-to-color: #d97706;
}
@mixin orange-theme {
--accent-color: #f97316;
--accent-light-color: #fb923c;
--accent-dark-color: #ea580c;
--accent-contrast-color: #fff;
--gradient-from-color: #fed7aa;
--gradient-via-color: #fb923c;
--gradient-to-color: #ea580c;
}
@mixin red-theme {
--accent-color: #ef4444;
--accent-light-color: #f87171;
--accent-dark-color: #dc2626;
--accent-contrast-color: #fff;
--gradient-from-color: #fecaca;
--gradient-via-color: #f87171;
--gradient-to-color: #dc2626;
}
@mixin pink-theme {
--accent-color: #ec4899;
--accent-light-color: #f472b6;
--accent-dark-color: #db2777;
--accent-contrast-color: #fff;
--gradient-from-color: #fbcfe8;
--gradient-via-color: #f472b6;
--gradient-to-color: #db2777;
}

View File

@@ -1,81 +0,0 @@
@mixin base-theme {
--font-sans: "Inter Variable", sans-serif;
--font-icon: "Material Symbols Rounded Variable";
--font-mono: "Roboto Mono Variable", monospace;
--font-size-body: 0.75rem;
--font-size-tiny: 0.688rem;
--line-height-body: 1rem;
--upper-primary-sticky-fold: 4.125rem;
--upper-secondary-sticky-fold: 6.188rem;
--upper-tertiary-sticky-fold: 8.25rem;
--upper-fourth-sticky-fold: 10.2rem;
--upper-mobile-primary-sticky-fold: 6.625rem;
--upper-mobile-secondary-sticky-fold: 8.688rem;
--upper-mobile-sticky-fold: 10.75rem;
--upper-mobile-tertiary-sticky-fold: 8.25rem;
--lower-primary-sticky-fold: 3rem;
--lower-secondary-sticky-fold: 5.063rem;
--lower-tertiary-sticky-fold: 7.125rem;
--lower-fourth-sticky-fold: 9.188rem;
--sidebar-primary-sticky-fold: 2rem;
}
@mixin dark-theme {
--primary-color: #181818;
--primary-light-color: #1c1c1e;
--primary-dark-color: #262626;
--primary-contrast-color: #171717;
--secondary-color: #a3a3a3;
--secondary-light-color: #737373;
--secondary-dark-color: #fafafa;
--divider-color: #262626;
--divider-light-color: #1f1f1f;
--divider-dark-color: #2d2d2d;
--error-color: #292524;
--tooltip-color: #f5f5f5;
--popover-color: #1b1b1b;
--editor-theme: "merbivore_soft";
}
@mixin light-theme {
--primary-color: #ffffff;
--primary-light-color: #f9fafb;
--primary-dark-color: #f3f4f6;
--primary-contrast-color: #fdfdfd;
--secondary-color: #6b7280;
--secondary-light-color: #9ca3af;
--secondary-dark-color: #111827;
--divider-color: #f3f4f6;
--divider-light-color: #f3f4f6;
--divider-dark-color: #d1d5db;
--error-color: #fef3c7;
--tooltip-color: #262626;
--popover-color: #ffffff;
--editor-theme: "textmate";
}
@mixin black-theme {
--primary-color: #0f0f0f;
--primary-light-color: #171717;
--primary-dark-color: #181818;
--primary-contrast-color: #0f0f0f;
--secondary-color: #a3a3a3;
--secondary-light-color: #737373;
--secondary-dark-color: #f5f5f5;
--divider-color: #1c1c1e;
--divider-light-color: #181818;
--divider-dark-color: #323232;
--error-color: #1c1917;
--tooltip-color: #f5f5f5;
--popover-color: #0f0f0f;
--editor-theme: "twilight";
}

View File

@@ -1,41 +0,0 @@
@mixin dark-editor-theme {
--editor-type-color: #a78bfa;
--editor-name-color: #60a5fa;
--editor-operator-color: #818cf8;
--editor-invalid-color: #f87171;
--editor-separator-color: #9ca3af;
--editor-meta-color: #9ca3af;
--editor-variable-color: #34d399;
--editor-link-color: #22d3ee;
--editor-process-color: #e879f9;
--editor-constant-color: #a78bfa;
--editor-keyword-color: #f472b6;
}
@mixin light-editor-theme {
--editor-type-color: #7c3aed;
--editor-name-color: #dc2626;
--editor-operator-color: #4f46e5;
--editor-invalid-color: #dc2626;
--editor-separator-color: #4b5563;
--editor-meta-color: #4b5563;
--editor-variable-color: #059669;
--editor-link-color: #0891b2;
--editor-process-color: #2563eb;
--editor-constant-color: #c026d3;
--editor-keyword-color: #db2777;
}
@mixin black-editor-theme {
--editor-type-color: #a78bfa;
--editor-name-color: #e879f9;
--editor-operator-color: #818cf8;
--editor-invalid-color: #f87171;
--editor-separator-color: #9ca3af;
--editor-meta-color: #9ca3af;
--editor-variable-color: #34d399;
--editor-link-color: #22d3ee;
--editor-process-color: #a78bfa;
--editor-constant-color: #60a5fa;
--editor-keyword-color: #f472b6;
}

View File

@@ -1,64 +0,0 @@
@import "./base-themes.scss";
@import "./editor-themes.scss";
@import "./accent-themes.scss";
:root {
@include base-theme;
@include dark-theme;
@include green-theme;
@include dark-editor-theme;
}
:root.light {
@include light-theme;
@include light-editor-theme;
color-scheme: light;
}
:root.dark {
@include dark-theme;
@include dark-editor-theme;
color-scheme: dark;
}
:root.black {
@include black-theme;
@include black-editor-theme;
color-scheme: dark;
}
:root[data-accent="blue"] {
@include blue-theme;
}
:root[data-accent="green"] {
@include green-theme;
}
:root[data-accent="teal"] {
@include teal-theme;
}
:root[data-accent="indigo"] {
@include indigo-theme;
}
:root[data-accent="purple"] {
@include purple-theme;
}
:root[data-accent="orange"] {
@include orange-theme;
}
:root[data-accent="pink"] {
@include pink-theme;
}
:root[data-accent="red"] {
@include red-theme;
}
:root[data-accent="yellow"] {
@include yellow-theme;
}

View File

@@ -139,21 +139,7 @@
"password": "Password", "password": "Password",
"token": "Token", "token": "Token",
"type": "Authorization Type", "type": "Authorization Type",
"username": "Username", "username": "Username"
"oauth": {
"token_generation_oidc_discovery_failed": "Failure on token generation: OpenID Connect Discovery Failed",
"something_went_wrong_on_token_generation": "Something went wrong on token generation",
"redirect_auth_server_returned_error": "Auth Server returned an error state",
"redirect_no_auth_code": "No Authorization Code present in the redirect",
"redirect_invalid_state": "Invalid State value present in the redirect",
"redirect_no_token_endpoint": "No Token Endpoint Defined",
"redirect_no_client_id": "No Client ID defined",
"redirect_no_client_secret": "No Client Secret Defined",
"redirect_no_code_verifier": "No Code Verifier Defined",
"redirect_auth_token_request_failed": "Request to get the auth token failed",
"redirect_auth_token_request_invalid_response": "Invalid Response from the Token Endpoint when requesting for an auth token",
"something_went_wrong_on_oauth_redirect": "Something went wrong during OAuth Redirect"
}
}, },
"collection": { "collection": {
"created": "Collection created", "created": "Collection created",
@@ -497,8 +483,7 @@
"enter_curl": "Enter cURL command", "enter_curl": "Enter cURL command",
"generate_code": "Generate code", "generate_code": "Generate code",
"generated_code": "Generated code", "generated_code": "Generated code",
"go_to_authorization_tab": "Go to Authorization tab", "go_to_authorization_tab": "Go to Authorization",
"go_to_body_tab": "Go to Body tab",
"header_list": "Header List", "header_list": "Header List",
"invalid_name": "Please provide a name for the request", "invalid_name": "Please provide a name for the request",
"method": "Method", "method": "Method",
@@ -600,7 +585,6 @@
"use_experimental_url_bar": "Use experimental URL bar with environment highlighting", "use_experimental_url_bar": "Use experimental URL bar with environment highlighting",
"user": "User", "user": "User",
"verified_email": "Verified email", "verified_email": "Verified email",
"additional": "Additional Settings",
"verify_email": "Verify email" "verify_email": "Verify email"
}, },
"shortcodes": { "shortcodes": {

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/common", "name": "@hoppscotch/common",
"private": true, "private": true,
"version": "2023.8.4-1", "version": "2023.8.3-1",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run", "test": "vitest --run",
@@ -92,7 +92,6 @@
"url": "^0.11.1", "url": "^0.11.1",
"util": "^0.12.5", "util": "^0.12.5",
"uuid": "^9.0.0", "uuid": "^9.0.0",
"verzod": "^0.2.0",
"vue": "^3.3.4", "vue": "^3.3.4",
"vue-i18n": "^9.2.2", "vue-i18n": "^9.2.2",
"vue-pdf-embed": "^1.1.6", "vue-pdf-embed": "^1.1.6",
@@ -136,7 +135,6 @@
"@vue/compiler-sfc": "^3.3.4", "@vue/compiler-sfc": "^3.3.4",
"@vue/eslint-config-typescript": "^11.0.3", "@vue/eslint-config-typescript": "^11.0.3",
"@vue/runtime-core": "^3.3.4", "@vue/runtime-core": "^3.3.4",
"autoprefixer": "^10.4.14",
"cross-env": "^7.0.3", "cross-env": "^7.0.3",
"dotenv": "^16.3.1", "dotenv": "^16.3.1",
"eslint": "^8.47.0", "eslint": "^8.47.0",
@@ -145,25 +143,23 @@
"glob": "^10.3.10", "glob": "^10.3.10",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"openapi-types": "^12.1.3", "openapi-types": "^12.1.3",
"postcss": "^8.4.23",
"prettier-plugin-tailwindcss": "^0.5.6",
"rollup-plugin-polyfill-node": "^0.12.0", "rollup-plugin-polyfill-node": "^0.12.0",
"sass": "^1.66.0", "sass": "^1.66.0",
"tailwindcss": "^3.3.2",
"typescript": "^5.1.6", "typescript": "^5.1.6",
"unplugin-fonts": "^1.0.3", "unplugin-fonts": "^1.0.3",
"unplugin-icons": "^0.16.5", "unplugin-icons": "^0.16.5",
"unplugin-vue-components": "^0.25.1", "unplugin-vue-components": "^0.25.1",
"vite": "^4.4.9", "vite": "^4.4.9",
"vite-plugin-checker": "^0.6.1", "vite-plugin-checker": "^0.6.1",
"vite-plugin-fonts": "^0.6.0",
"vite-plugin-html-config": "^1.0.11", "vite-plugin-html-config": "^1.0.11",
"vite-plugin-inspect": "^0.7.38", "vite-plugin-inspect": "^0.7.38",
"vite-plugin-pages": "^0.31.0", "vite-plugin-pages": "^0.31.0",
"vite-plugin-pages-sitemap": "^1.6.1", "vite-plugin-pages-sitemap": "^1.6.1",
"vite-plugin-pwa": "^0.16.4", "vite-plugin-pwa": "^0.16.4",
"vite-plugin-vue-layouts": "^0.8.0", "vite-plugin-vue-layouts": "^0.8.0",
"vite-plugin-windicss": "^1.9.1",
"vitest": "^0.34.2", "vitest": "^0.34.2",
"vue-tsc": "^1.8.8" "vue-tsc": "^1.8.8",
"windicss": "^3.5.6"
} }
} }

View File

@@ -2,7 +2,7 @@
<div> <div>
<div <div
v-if="isLoadingInitialRoute" v-if="isLoadingInitialRoute"
class="flex min-h-screen flex-col items-center justify-center" class="flex flex-col items-center justify-center min-h-screen"
> >
<HoppSmartSpinner /> <HoppSmartSpinner />
</div> </div>

View File

@@ -9,7 +9,6 @@ declare module 'vue' {
export interface GlobalComponents { export interface GlobalComponents {
AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default'] AppActionHandler: typeof import('./components/app/ActionHandler.vue')['default']
AppAnnouncement: typeof import('./components/app/Announcement.vue')['default'] AppAnnouncement: typeof import('./components/app/Announcement.vue')['default']
AppBanner: typeof import('./components/app/Banner.vue')['default']
AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default'] AppContextMenu: typeof import('./components/app/ContextMenu.vue')['default']
AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default'] AppDeveloperOptions: typeof import('./components/app/DeveloperOptions.vue')['default']
AppFooter: typeof import('./components/app/Footer.vue')['default'] AppFooter: typeof import('./components/app/Footer.vue')['default']
@@ -93,13 +92,11 @@ declare module 'vue' {
HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary'] HoppButtonPrimary: typeof import('@hoppscotch/ui')['HoppButtonPrimary']
HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary'] HoppButtonSecondary: typeof import('@hoppscotch/ui')['HoppButtonSecondary']
HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor'] HoppSmartAnchor: typeof import('@hoppscotch/ui')['HoppSmartAnchor']
HoppSmartAutoComplete: typeof import('@hoppscotch/ui')['HoppSmartAutoComplete']
HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox'] HoppSmartCheckbox: typeof import('@hoppscotch/ui')['HoppSmartCheckbox']
HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal'] HoppSmartConfirmModal: typeof import('@hoppscotch/ui')['HoppSmartConfirmModal']
HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand'] HoppSmartExpand: typeof import('@hoppscotch/ui')['HoppSmartExpand']
HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip'] HoppSmartFileChip: typeof import('@hoppscotch/ui')['HoppSmartFileChip']
HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput'] HoppSmartInput: typeof import('@hoppscotch/ui')['HoppSmartInput']
HoppSmartIntersection: typeof import('@hoppscotch/ui')['HoppSmartIntersection']
HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem'] HoppSmartItem: typeof import('@hoppscotch/ui')['HoppSmartItem']
HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink'] HoppSmartLink: typeof import('@hoppscotch/ui')['HoppSmartLink']
HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal'] HoppSmartModal: typeof import('@hoppscotch/ui')['HoppSmartModal']
@@ -143,11 +140,9 @@ declare module 'vue' {
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']
IconLucideActivity: typeof import('~icons/lucide/activity')['default'] IconLucideActivity: typeof import('~icons/lucide/activity')['default']
IconLucideAlertCircle: typeof import('~icons/lucide/alert-circle')['default']
IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default'] IconLucideAlertTriangle: typeof import('~icons/lucide/alert-triangle')['default']
IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default'] IconLucideArrowLeft: typeof import('~icons/lucide/arrow-left')['default']
IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default'] IconLucideArrowUpRight: typeof import('~icons/lucide/arrow-up-right')['default']
IconLucideBrush: typeof import('~icons/lucide/brush')['default']
IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default'] IconLucideCheckCircle: typeof import('~icons/lucide/check-circle')['default']
IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default'] IconLucideChevronRight: typeof import('~icons/lucide/chevron-right')['default']
IconLucideGlobe: typeof import('~icons/lucide/globe')['default'] IconLucideGlobe: typeof import('~icons/lucide/globe')['default']
@@ -157,7 +152,6 @@ declare module 'vue' {
IconLucideLayers: typeof import('~icons/lucide/layers')['default'] IconLucideLayers: typeof import('~icons/lucide/layers')['default']
IconLucideListEnd: typeof import('~icons/lucide/list-end')['default'] IconLucideListEnd: typeof import('~icons/lucide/list-end')['default']
IconLucideMinus: typeof import('~icons/lucide/minus')['default'] IconLucideMinus: typeof import('~icons/lucide/minus')['default']
IconLucideRss: typeof import('~icons/lucide/rss')['default']
IconLucideSearch: typeof import('~icons/lucide/search')['default'] IconLucideSearch: typeof import('~icons/lucide/search')['default']
IconLucideUsers: typeof import('~icons/lucide/users')['default'] IconLucideUsers: typeof import('~icons/lucide/users')['default']
InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default'] InterceptorsErrorPlaceholder: typeof import('./components/interceptors/ErrorPlaceholder.vue')['default']
@@ -206,7 +200,6 @@ declare module 'vue' {
SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default'] SmartSlideOver: typeof import('./../../hoppscotch-ui/src/components/smart/SlideOver.vue')['default']
SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default'] SmartSpinner: typeof import('./../../hoppscotch-ui/src/components/smart/Spinner.vue')['default']
SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default'] SmartTab: typeof import('./../../hoppscotch-ui/src/components/smart/Tab.vue')['default']
SmartTable: typeof import('./../../hoppscotch-ui/src/components/smart/Table.vue')['default']
SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default'] SmartTabs: typeof import('./../../hoppscotch-ui/src/components/smart/Tabs.vue')['default']
SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default'] SmartToggle: typeof import('./../../hoppscotch-ui/src/components/smart/Toggle.vue')['default']
SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default'] SmartTree: typeof import('./../../hoppscotch-ui/src/components/smart/Tree.vue')['default']

View File

@@ -0,0 +1,22 @@
<template>
<div
class="relative flex items-center px-4 py-2 transition bg-error text-tiny group"
role="alert"
>
<icon-lucide-info class="mr-2" />
<span class="text-secondaryDark">
<span class="md:hidden">
{{ t("helpers.offline_short") }}
</span>
<span class="<md:hidden">
{{ t("helpers.offline") }}
</span>
</span>
</div>
</template>
<script setup lang="ts">
import { useI18n } from "~/composables/i18n"
const t = useI18n()
</script>

View File

@@ -1,56 +0,0 @@
<template>
<div
:role="bannerRole"
class="flex items-center px-4 py-2 text-tiny"
:class="bannerColor"
>
<component :is="bannerIcon" class="mr-2 text-white" />
<span class="text-white">
<span v-if="banner.alternateText" class="md:hidden">
{{ banner.alternateText(t) }}
</span>
<span :class="banner.alternateText ? '<md:hidden' : ''">
{{ banner.text(t) }}
</span>
</span>
</div>
</template>
<script setup lang="ts">
import { computed } from "vue"
import { BannerContent, BannerType } from "~/services/banner.service"
import { useI18n } from "@composables/i18n"
import IconAlertCircle from "~icons/lucide/alert-circle"
import IconAlertTriangle from "~icons/lucide/alert-triangle"
import IconInfo from "~icons/lucide/info"
const props = defineProps<{
banner: BannerContent
}>()
const t = useI18n()
const ariaRoles: Record<BannerType, string> = {
error: "alert",
warning: "status",
info: "status",
}
const bgColors: Record<BannerType, string> = {
error: "bg-red-700",
warning: "bg-yellow-700",
info: "bg-stone-800",
}
const icons = {
info: IconInfo,
warning: IconAlertCircle,
error: IconAlertTriangle,
}
const bannerColor = computed(() => bgColors[props.banner.type])
const bannerIcon = computed(() => icons[props.banner.type])
const bannerRole = computed(() => ariaRoles[props.banner.type])
</script>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div <div
ref="contextMenuRef" ref="contextMenuRef"
class="fixed translate-y-8 transform rounded border border-dividerDark bg-popover p-2 shadow-lg" class="fixed bg-popover shadow-lg transform translate-y-8 border border-dividerDark p-2 rounded"
:style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`" :style="`top: ${position.top}px; left: ${position.left}px; z-index: 1000;`"
> >
<div v-if="contextMenuOptions" class="flex flex-col"> <div v-if="contextMenuOptions" class="flex flex-col">

View File

@@ -6,7 +6,7 @@
@close="hideModal" @close="hideModal"
> >
<template #body> <template #body>
<p class="mb-4 px-2 text-secondaryLight"> <p class="px-2 mb-4 text-secondaryLight">
{{ t("app.developer_option_description") }} {{ t("app.developer_option_description") }}
</p> </p>
<div class="flex flex-1"> <div class="flex flex-1">

View File

@@ -181,7 +181,7 @@
@click="COLUMN_LAYOUT = !COLUMN_LAYOUT" @click="COLUMN_LAYOUT = !COLUMN_LAYOUT"
/> />
<span <span
class="transform transition" class="transition transform"
:class="{ :class="{
'rotate-180': SIDEBAR_ON_LEFT, 'rotate-180': SIDEBAR_ON_LEFT,
}" }"

View File

@@ -2,29 +2,29 @@
<div> <div>
<header <header
ref="headerRef" ref="headerRef"
class="flex flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2" class="flex items-center justify-between flex-1 flex-shrink-0 px-2 py-2 space-x-2 overflow-x-auto overflow-y-hidden"
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()" @mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
> >
<div <div
class="inline-flex flex-1 items-center justify-start space-x-2" class="inline-flex items-center justify-start flex-1 space-x-2"
:style="{ :style="{
paddingTop: platform.ui?.appHeader?.paddingTop?.value, paddingTop: platform.ui?.appHeader?.paddingTop?.value,
paddingLeft: platform.ui?.appHeader?.paddingLeft?.value, paddingLeft: platform.ui?.appHeader?.paddingLeft?.value,
}" }"
> >
<HoppButtonSecondary <HoppButtonSecondary
class="!font-bold uppercase tracking-wide !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark" class="tracking-wide !font-bold !text-secondaryDark hover:bg-primaryDark focus-visible:bg-primaryDark uppercase"
:label="t('app.name')" :label="t('app.name')"
to="/" to="/"
/> />
</div> </div>
<div class="inline-flex flex-1 items-center justify-center space-x-2"> <div class="inline-flex items-center justify-center flex-1 space-x-2">
<button <button
class="flex max-w-[15rem] flex-1 cursor-text items-center justify-between self-stretch rounded border border-dividerDark bg-primaryDark px-2 py-1 text-secondaryLight transition hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary" class="flex flex-1 items-center justify-between px-2 py-1 self-stretch bg-primaryDark transition text-secondaryLight cursor-text rounded border border-dividerDark max-w-60 hover:border-dividerDark hover:bg-primaryLight hover:text-secondary focus-visible:border-dividerDark focus-visible:bg-primaryLight focus-visible:text-secondary"
@click="invokeAction('modals.search.toggle')" @click="invokeAction('modals.search.toggle')"
> >
<span class="inline-flex flex-1 items-center"> <span class="inline-flex flex-1 items-center">
<icon-lucide-search class="svg-icons mr-2" /> <icon-lucide-search class="mr-2 svg-icons" />
{{ t("app.search") }} {{ t("app.search") }}
</span> </span>
<span class="flex space-x-1"> <span class="flex space-x-1">
@@ -50,7 +50,7 @@
@click="invokeAction('modals.support.toggle')" @click="invokeAction('modals.support.toggle')"
/> />
</div> </div>
<div class="inline-flex flex-1 items-center justify-end space-x-2"> <div class="inline-flex items-center justify-end flex-1 space-x-2">
<div <div
v-if="currentUser === null" v-if="currentUser === null"
class="inline-flex items-center space-x-2" class="inline-flex items-center space-x-2"
@@ -58,7 +58,7 @@
<HoppButtonSecondary <HoppButtonSecondary
:icon="IconUploadCloud" :icon="IconUploadCloud"
:label="t('header.save_workspace')" :label="t('header.save_workspace')"
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 hidden border border-green-600/25 bg-green-500/[.15] !text-green-500 hover:border-green-800/50 hover:bg-green-400/10 focus-visible:border-green-800/50 focus-visible:bg-green-400/10 md:flex" class="hidden md:flex bg-green-500/15 py-1.75 border border-green-600/25 !text-green-500 hover:bg-green-400/10 focus-visible:bg-green-400/10 focus-visible:border-green-800/50 !focus-visible:text-green-600 hover:border-green-800/50 !hover:text-green-600"
@click="invokeAction('modals.login.toggle')" @click="invokeAction('modals.login.toggle')"
/> />
<HoppButtonPrimary <HoppButtonPrimary
@@ -79,13 +79,13 @@
@handle-click="handleTeamEdit()" @handle-click="handleTeamEdit()"
/> />
<div <div
class="flex divide-x divide-green-600/25 rounded border border-green-600/25 bg-green-500/[.15] focus-within:divide-green-800/50 focus-within:border-green-800/50 focus-within:bg-green-400/10 hover:divide-green-800/50 hover:border-green-800/50 hover:bg-green-400/10" class="flex border divide-x rounded bg-green-500/15 divide-green-600/25 border-green-600/25 focus-within:bg-green-400/10 focus-within:border-green-800/50 focus-within:divide-green-800/50 hover:bg-green-400/10 hover:border-green-800/50 hover:divide-green-800/50"
> >
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('team.invite_tooltip')" :title="t('team.invite_tooltip')"
:icon="IconUserPlus" :icon="IconUserPlus"
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500" class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
@click="handleInvite()" @click="handleInvite()"
/> />
<HoppButtonSecondary <HoppButtonSecondary
@@ -97,7 +97,7 @@
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:title="t('team.edit')" :title="t('team.edit')"
:icon="IconSettings" :icon="IconSettings"
class="py-1.75 !focus-visible:text-green-600 !hover:text-green-600 !text-green-500" class="py-1.75 !text-green-500 !focus-visible:text-green-600 !hover:text-green-600"
@click="handleTeamEdit()" @click="handleTeamEdit()"
/> />
</div> </div>
@@ -112,7 +112,7 @@
:title="t('workspace.change')" :title="t('workspace.change')"
:label="mdAndLarger ? workspaceName : ``" :label="mdAndLarger ? workspaceName : ``"
:icon="workspace.type === 'personal' ? IconUser : IconUsers" :icon="workspace.type === 'personal' ? IconUser : IconUsers"
class="select-wrapper !focus-visible:text-blue-600 !hover:text-blue-600 rounded border border-blue-600/25 bg-blue-500/[.15] py-[0.4375rem] pr-8 !text-blue-500 hover:border-blue-800/50 hover:bg-blue-400/10 focus-visible:border-blue-800/50 focus-visible:bg-blue-400/10" class="pr-8 select-wrapper rounded bg-blue-500/15 py-1.75 border border-blue-600/25 !text-blue-500 focus-visible:bg-blue-400/10 focus-visible:border-blue-800/50 !focus-visible:text-blue-600 hover:bg-blue-400/10 hover:border-blue-800/50 !hover:text-blue-600"
/> />
<template #content="{ hide }"> <template #content="{ hide }">
<div <div
@@ -178,7 +178,7 @@
@keyup.escape="hide()" @keyup.escape="hide()"
> >
<div class="flex flex-col px-2 text-tiny"> <div class="flex flex-col px-2 text-tiny">
<span class="inline-flex truncate font-semibold"> <span class="inline-flex font-semibold truncate">
{{ {{
currentUser.displayName || currentUser.displayName ||
t("profile.default_hopp_displayname") t("profile.default_hopp_displayname")
@@ -217,7 +217,7 @@
</div> </div>
</div> </div>
</header> </header>
<AppBanner v-if="bannerContent" :banner="bannerContent" /> <AppAnnouncement v-if="!network.isOnline" />
<TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" /> <TeamsModal :show="showTeamsModal" @hide-modal="showTeamsModal = false" />
<TeamsInvite <TeamsInvite
v-if="workspace.type === 'team' && workspace.teamID" v-if="workspace.type === 'team' && workspace.teamID"
@@ -266,11 +266,6 @@ import IconUsers from "~icons/lucide/users"
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 { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team" import { deleteTeam as backendDeleteTeam } from "~/helpers/backend/mutations/Team"
import {
BannerService,
BannerContent,
BANNER_PRIORITY_HIGH,
} from "~/services/banner.service"
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
@@ -288,31 +283,7 @@ const showTeamsModal = ref(false)
const breakpoints = useBreakpoints(breakpointsTailwind) const breakpoints = useBreakpoints(breakpointsTailwind)
const mdAndLarger = breakpoints.greater("md") const mdAndLarger = breakpoints.greater("md")
const banner = useService(BannerService)
const bannerContent = computed(() => banner.content.value?.content)
let bannerID: number | null = null
const offlineBanner: BannerContent = {
type: "info",
text: (t) => t("helpers.offline"),
alternateText: (t) => t("helpers.offline_short"),
score: BANNER_PRIORITY_HIGH,
}
const network = reactive(useNetwork()) const network = reactive(useNetwork())
const isOnline = computed(() => network.isOnline)
// Show the offline banner if the user is offline
watch(isOnline, () => {
if (!isOnline.value) {
bannerID = banner.showBanner(offlineBanner)
return
} else {
if (banner.content && bannerID) {
banner.removeBanner(bannerID)
}
}
})
const currentUser = useReadonlyStream( const currentUser = useReadonlyStream(
platform.auth.getProbableUserStream(), platform.auth.getProbableUserStream(),

View File

@@ -1,7 +1,7 @@
<template> <template>
<div v-if="inspectionResults && inspectionResults.length > 0"> <div v-if="inspectionResults && inspectionResults.length > 0">
<tippy interactive trigger="click" theme="popover"> <tippy interactive trigger="click" theme="popover">
<div class="flex flex-1 flex-col items-center justify-center"> <div class="flex justify-center items-center flex-1 flex-col">
<HoppButtonSecondary <HoppButtonSecondary
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
:icon="IconAlertTriangle" :icon="IconAlertTriangle"
@@ -10,12 +10,12 @@
/> />
</div> </div>
<template #content="{ hide }"> <template #content="{ hide }">
<div class="flex flex-1 flex-col items-start space-y-2"> <div class="flex flex-col space-y-2 items-start flex-1">
<div <div
class="sticky top-0 flex justify-between self-stretch rounded border border-divider bg-popover pl-2" class="flex justify-between border rounded pl-2 border-divider bg-popover sticky top-0 self-stretch"
> >
<span class="flex flex-1 items-center"> <span class="flex items-center flex-1">
<icon-lucide-activity class="svg-icons mr-2 text-accent" /> <icon-lucide-activity class="mr-2 svg-icons text-accent" />
<span class="font-bold"> <span class="font-bold">
{{ t("inspections.title") }} {{ t("inspections.title") }}
</span> </span>
@@ -31,10 +31,10 @@
<div <div
v-for="(inspector, index) in inspectionResults" v-for="(inspector, index) in inspectionResults"
:key="index" :key="index"
class="flex w-full max-w-md self-stretch" class="flex self-stretch max-w-md w-full"
> >
<div <div
class="flex flex-1 flex-col divide-y divide-dashed divide-dividerDark rounded border border-dashed border-dividerDark" class="flex flex-col flex-1 rounded border border-dashed border-dividerDark divide-y divide-dashed divide-dividerDark"
> >
<span <span
v-if="inspector.text.type === 'text'" v-if="inspector.text.type === 'text'"
@@ -44,13 +44,13 @@
<HoppSmartLink <HoppSmartLink
blank blank
:to="inspector.doc.link" :to="inspector.doc.link"
class="text-accent transition hover:text-accentDark" class="text-accent hover:text-accentDark transition"
> >
{{ inspector.doc.text }} {{ inspector.doc.text }}
<icon-lucide-arrow-up-right class="svg-icons" /> <icon-lucide-arrow-up-right class="svg-icons" />
</HoppSmartLink> </HoppSmartLink>
</span> </span>
<span v-if="inspector.action" class="flex space-x-2 p-2"> <span v-if="inspector.action" class="flex p-2 space-x-2">
<HoppButtonSecondary <HoppButtonSecondary
:label="inspector.action.text" :label="inspector.action.text"
outline outline

View File

@@ -8,7 +8,7 @@
> >
<template #body> <template #body>
<div class="flex flex-col space-y-2"> <div class="flex flex-col space-y-2">
<h2 class="p-4 font-bold font-semibold text-secondaryDark"> <h2 class="p-4 font-semibold font-bold text-secondaryDark">
{{ t("layout.name") }} {{ t("layout.name") }}
</h2> </h2>
<HoppSmartItem <HoppSmartItem
@@ -27,7 +27,7 @@
active active
@click="expandCollection" @click="expandCollection"
/> />
<h2 class="p-4 font-bold font-semibold text-secondaryDark"> <h2 class="p-4 font-semibold font-bold text-secondaryDark">
{{ t("support.title") }} {{ t("support.title") }}
</h2> </h2>
<template <template

View File

@@ -47,15 +47,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { Pane, Splitpanes } from "splitpanes" import { Splitpanes, Pane } from "splitpanes"
import "splitpanes/dist/splitpanes.css" import "splitpanes/dist/splitpanes.css"
import { useSetting } from "@composables/settings"
import { breakpointsTailwind, useBreakpoints } from "@vueuse/core" import { breakpointsTailwind, useBreakpoints } from "@vueuse/core"
import { useService } from "dioc/vue" import { computed, useSlots, ref } from "vue"
import { computed, ref, useSlots } from "vue" import { useSetting } from "@composables/settings"
import { PersistenceService } from "~/services/persistence" import { setLocalConfig, getLocalConfig } from "~/newstore/localpersistence"
const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT") const SIDEBAR_ON_LEFT = useSetting("SIDEBAR_ON_LEFT")
@@ -68,8 +67,6 @@ const SIDEBAR = useSetting("SIDEBAR")
const slots = useSlots() const slots = useSlots()
const persistenceService = useService(PersistenceService)
const hasSidebar = computed(() => !!slots.sidebar) const hasSidebar = computed(() => !!slots.sidebar)
const hasSecondary = computed(() => !!slots.secondary) const hasSecondary = computed(() => !!slots.secondary)
@@ -99,7 +96,7 @@ if (!COLUMN_LAYOUT.value) {
function setPaneEvent(event: PaneEvent[], type: "vertical" | "horizontal") { function setPaneEvent(event: PaneEvent[], type: "vertical" | "horizontal") {
if (!props.layoutId) return if (!props.layoutId) return
const storageKey = `${props.layoutId}-pane-config-${type}` const storageKey = `${props.layoutId}-pane-config-${type}`
persistenceService.setLocalConfig(storageKey, JSON.stringify(event)) setLocalConfig(storageKey, JSON.stringify(event))
} }
function populatePaneEvent() { function populatePaneEvent() {
@@ -122,7 +119,7 @@ function populatePaneEvent() {
function getPaneData(type: "vertical" | "horizontal"): PaneEvent[] | null { function getPaneData(type: "vertical" | "horizontal"): PaneEvent[] | null {
const storageKey = `${props.layoutId}-pane-config-${type}` const storageKey = `${props.layoutId}-pane-config-${type}`
const paneEvent = persistenceService.getLocalConfig(storageKey) const paneEvent = getLocalConfig(storageKey)
if (!paneEvent) return null if (!paneEvent) return null
return JSON.parse(paneEvent) return JSON.parse(paneEvent)
} }

View File

@@ -16,13 +16,13 @@
class="share-link" class="share-link"
tabindex="0" tabindex="0"
> >
<component :is="platform.icon" class="h-6 w-6" /> <component :is="platform.icon" class="w-6 h-6" />
<span class="mt-3"> <span class="mt-3">
{{ platform.name }} {{ platform.name }}
</span> </span>
</a> </a>
<button class="share-link" @click="copyAppLink"> <button class="share-link" @click="copyAppLink">
<component :is="copyIcon" class="h-6 w-6 text-xl" /> <component :is="copyIcon" class="w-6 h-6 text-xl" />
<span class="mt-3"> <span class="mt-3">
{{ t("app.copy") }} {{ t("app.copy") }}
</span> </span>
@@ -119,14 +119,14 @@ const hideModal = () => {
.share-link { .share-link {
@apply border border-dividerLight; @apply border border-dividerLight;
@apply rounded; @apply rounded;
@apply flex flex-col; @apply flex-col flex;
@apply p-4; @apply p-4;
@apply items-center; @apply items-center;
@apply justify-center; @apply justify-center;
@apply font-semibold; @apply font-semibold;
@apply hover:bg-primaryLight hover:text-secondaryDark; @apply hover: (bg-primaryLight text-secondaryDark);
@apply focus:outline-none; @apply focus: outline-none;
@apply focus-visible:border-divider; @apply focus-visible: border-divider;
svg { svg {
@apply opacity-80; @apply opacity-80;

View File

@@ -2,7 +2,7 @@
<HoppSmartSlideOver :show="show" :title="t('app.shortcuts')" @close="close()"> <HoppSmartSlideOver :show="show" :title="t('app.shortcuts')" @close="close()">
<template #content> <template #content>
<div <div
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto bg-primary" class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
> >
<HoppSmartInput <HoppSmartInput
v-model="filterText" v-model="filterText"
@@ -17,7 +17,7 @@
v-if="isEmpty(shortcutsResults)" v-if="isEmpty(shortcutsResults)"
:text="`${t('state.nothing_found')} ‟${filterText}”`" :text="`${t('state.nothing_found')} ‟${filterText}”`"
> >
<icon-lucide-search class="svg-icons pb-2 opacity-75" /> <icon-lucide-search class="pb-2 opacity-75 svg-icons" />
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<details <details
@@ -28,16 +28,16 @@
open open
> >
<summary <summary
class="flex min-w-0 flex-1 cursor-pointer items-center px-6 py-4 font-semibold text-secondaryLight transition hover:text-secondaryDark focus:outline-none" class="flex items-center flex-1 min-w-0 px-6 py-4 font-semibold transition cursor-pointer focus:outline-none text-secondaryLight hover:text-secondaryDark"
> >
<icon-lucide-chevron-right class="indicator mr-2" /> <icon-lucide-chevron-right class="mr-2 indicator" />
<span <span
class="capitalize-first truncate font-semibold text-secondaryDark" class="font-semibold truncate capitalize-first text-secondaryDark"
> >
{{ sectionTitle }} {{ sectionTitle }}
</span> </span>
</summary> </summary>
<div class="flex flex-col space-y-2 px-6 pb-4"> <div class="flex flex-col px-6 pb-4 space-y-2">
<AppShortcutsEntry <AppShortcutsEntry
v-for="(shortcut, index) in sectionResults" v-for="(shortcut, index) in sectionResults"
:key="`shortcut-${index}`" :key="`shortcut-${index}`"

View File

@@ -1,6 +1,6 @@
<template> <template>
<div class="flex items-center py-1"> <div class="flex items-center py-1">
<span class="mr-4 flex flex-1"> <span class="flex flex-1 mr-4">
{{ shortcut.label }} {{ shortcut.label }}
</span> </span>
<kbd <kbd

View File

@@ -1,17 +1,17 @@
<template> <template>
<div class="flex flex-col items-center justify-center text-secondaryLight"> <div class="flex flex-col items-center justify-center text-secondaryLight">
<div class="mb-4 flex space-x-2"> <div class="flex mb-4 space-x-2">
<div class="flex flex-col items-end space-y-4 text-right"> <div class="flex flex-col items-end space-y-4 text-right">
<span class="flex flex-1 items-center"> <span class="flex items-center flex-1">
{{ t("shortcut.request.send_request") }} {{ t("shortcut.request.send_request") }}
</span> </span>
<span class="flex flex-1 items-center"> <span class="flex items-center flex-1">
{{ t("shortcut.general.show_all") }} {{ t("shortcut.general.show_all") }}
</span> </span>
<span class="flex flex-1 items-center"> <span class="flex items-center flex-1">
{{ t("shortcut.general.command_menu") }} {{ t("shortcut.general.command_menu") }}
</span> </span>
<span class="flex flex-1 items-center"> <span class="flex items-center flex-1">
{{ t("shortcut.general.help_menu") }} {{ t("shortcut.general.help_menu") }}
</span> </span>
</div> </div>

View File

@@ -1,6 +1,6 @@
<template> <template>
<aside class="flex h-full justify-between md:flex-col"> <aside class="flex justify-between h-full md:flex-col">
<nav class="flex flex-1 flex-nowrap bg-primary md:flex-none md:flex-col"> <nav class="flex flex-1 flex-nowrap md:flex-col md:flex-none bg-primary">
<HoppSmartLink <HoppSmartLink
v-for="(navigation, index) in primaryNavigation" v-for="(navigation, index) in primaryNavigation"
:key="`navigation-${index}`" :key="`navigation-${index}`"
@@ -73,25 +73,25 @@ const primaryNavigation = [
.nav-link { .nav-link {
@apply relative; @apply relative;
@apply p-4; @apply p-4;
@apply flex flex-1 flex-col; @apply flex flex-col flex-1;
@apply items-center; @apply items-center;
@apply justify-center; @apply justify-center;
@apply hover:bg-primaryDark hover:text-secondaryDark; @apply hover: (bg-primaryDark text-secondaryDark);
@apply focus-visible:text-secondaryDark; @apply focus-visible: text-secondaryDark;
@apply after:absolute; @apply after:absolute;
@apply after:inset-x-0; @apply after:inset-x-0;
@apply after:md:inset-x-auto; @apply after:md: inset-x-auto;
@apply after:md:inset-y-0; @apply after:md: inset-y-0;
@apply after:bottom-0; @apply after:bottom-0;
@apply after:md:bottom-auto; @apply after:md: bottom-auto;
@apply after:md:left-0; @apply after:md: left-0;
@apply after:z-10; @apply after:z-2;
@apply after:h-0.5; @apply after:h-0.5;
@apply after:md:h-full; @apply after:md: h-full;
@apply after:w-full; @apply after:w-full;
@apply after:md:w-0.5; @apply after:md: w-0.5;
@apply after:content-[""]; @apply after:content-DEFAULT;
@apply focus:after:bg-divider; @apply focus: after: bg-divider;
.svg-icons { .svg-icons {
@apply opacity-75; @apply opacity-75;
@@ -105,7 +105,7 @@ const primaryNavigation = [
&.router-link-active { &.router-link-active {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primaryLight; @apply bg-primaryLight;
@apply hover:text-secondaryDark; @apply hover: text-secondaryDark;
@apply after:bg-accent; @apply after:bg-accent;
.svg-icons { .svg-icons {
@@ -116,7 +116,7 @@ const primaryNavigation = [
&.exact-active-link { &.exact-active-link {
@apply text-secondaryDark; @apply text-secondaryDark;
@apply bg-primaryLight; @apply bg-primaryLight;
@apply hover:text-secondaryDark; @apply hover: text-secondaryDark;
@apply after:bg-accent; @apply after:bg-accent;
.svg-icons { .svg-icons {

View File

@@ -1,7 +1,7 @@
<template> <template>
<button <button
ref="el" ref="el"
class="search-entry relative flex flex-1 cursor-pointer items-center space-x-4 px-6 py-4 font-medium transition focus:outline-none" class="flex items-center flex-1 px-6 py-4 font-medium space-x-4 transition cursor-pointer relative search-entry focus:outline-none"
:class="{ 'active bg-primaryLight text-secondaryDark': active }" :class="{ 'active bg-primaryLight text-secondaryDark': active }"
tabindex="-1" tabindex="-1"
@click="emit('action')" @click="emit('action')"
@@ -9,7 +9,7 @@
> >
<component <component
:is="entry.icon" :is="entry.icon"
class="svg-icons opacity-50" class="opacity-50 svg-icons"
:class="{ 'opacity-100': active }" :class="{ 'opacity-100': active }"
/> />
<template <template
@@ -112,9 +112,9 @@ watch(
@apply after:left-0; @apply after:left-0;
@apply after:bottom-0; @apply after:bottom-0;
@apply after:bg-transparent; @apply after:bg-transparent;
@apply after:z-10; @apply after:z-2;
@apply after:w-0.5; @apply after:w-0.5;
@apply after:content-['']; @apply after:content-DEFAULT;
&.active { &.active {
@apply after:bg-accentLight; @apply after:bg-accentLight;

View File

@@ -8,7 +8,7 @@
{{ historyEntry.request.url }} {{ historyEntry.request.url }}
</span> </span>
<span <span
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold" class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
> >
{{ historyEntry.request.query.split("\n")[0] }} {{ historyEntry.request.query.split("\n")[0] }}
</span> </span>

View File

@@ -1,5 +1,5 @@
<template> <template>
<span class="flex flex-1 items-center space-x-2"> <span class="flex flex-1 space-x-2 items-center">
<template v-for="(folder, index) in pathFolders" :key="index"> <template v-for="(folder, index) in pathFolders" :key="index">
<span class="block" :class="{ truncate: index !== 0 }"> <span class="block" :class="{ truncate: index !== 0 }">
{{ folder.name }} {{ folder.name }}

View File

@@ -5,7 +5,7 @@
</span> </span>
<icon-lucide-chevron-right class="flex flex-shrink-0" /> <icon-lucide-chevron-right class="flex flex-shrink-0" />
<span <span
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold" class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
:class="entryStatus.className" :class="entryStatus.className"
> >
{{ historyEntry.request.method }} {{ historyEntry.request.method }}

View File

@@ -8,8 +8,8 @@
</template> </template>
<span <span
v-if="request" v-if="request"
class="flex flex-shrink-0 truncate rounded-md border border-dividerDark px-1 text-tiny font-semibold" class="font-semibold truncate text-tiny flex flex-shrink-0 border border-dividerDark rounded-md px-1"
:style="{ color: getMethodLabelColorClassOf(request) }" :class="getMethodLabelColorClassOf(request)"
> >
{{ request.method.toUpperCase() }} {{ request.method.toUpperCase() }}
</span> </span>

View File

@@ -6,7 +6,7 @@
@close="emit('hide-modal')" @close="emit('hide-modal')"
> >
<template #body> <template #body>
<div class="flex flex-col border-b border-divider transition"> <div class="flex flex-col border-b transition border-divider">
<div class="flex items-center"> <div class="flex items-center">
<input <input
id="command" id="command"
@@ -16,14 +16,14 @@
autocomplete="off" autocomplete="off"
name="command" name="command"
:placeholder="`${t('app.type_a_command_search')}`" :placeholder="`${t('app.type_a_command_search')}`"
class="flex flex-1 bg-transparent px-6 py-5 text-base text-secondaryDark" class="flex flex-1 text-base bg-transparent text-secondaryDark px-6 py-5"
/> />
<HoppSmartSpinner v-if="searchSession?.loading" class="mr-6" /> <HoppSmartSpinner v-if="searchSession?.loading" class="mr-6" />
</div> </div>
</div> </div>
<div <div
v-if="searchSession && search.length > 0" v-if="searchSession && search.length > 0"
class="flex flex-1 flex-col divide-y divide-dividerLight overflow-y-auto border-b border-divider" class="flex flex-col flex-1 overflow-y-auto border-b border-divider divide-y divide-dividerLight"
> >
<div <div
v-for="([sectionID, sectionResult], sectionIndex) in scoredResults" v-for="([sectionID, sectionResult], sectionIndex) in scoredResults"
@@ -31,7 +31,7 @@
class="flex flex-col" class="flex flex-col"
> >
<h5 <h5
class="sticky top-0 z-10 bg-primaryContrast px-6 py-2 text-secondaryLight" class="px-6 py-2 bg-primaryContrast z-10 text-secondaryLight sticky top-0"
> >
{{ sectionResult.title }} {{ sectionResult.title }}
</h5> </h5>
@@ -49,7 +49,7 @@
:text="`${t('state.nothing_found')} ‟${search}”`" :text="`${t('state.nothing_found')} ‟${search}”`"
> >
<template #icon> <template #icon>
<icon-lucide-search class="svg-icons pb-2 opacity-75" /> <icon-lucide-search class="pb-2 opacity-75 svg-icons" />
</template> </template>
<HoppButtonSecondary <HoppButtonSecondary
:label="t('action.clear')" :label="t('action.clear')"
@@ -59,7 +59,7 @@
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
</div> </div>
<div <div
class="flex flex-shrink-0 justify-between overflow-auto whitespace-nowrap p-4 text-tiny text-secondaryLight <sm:hidden" class="flex flex-shrink-0 text-tiny text-secondaryLight p-4 justify-between whitespace-nowrap overflow-auto <sm:hidden"
> >
<div class="flex items-center"> <div class="flex items-center">
<kbd class="shortcut-key"></kbd> <kbd class="shortcut-key"></kbd>

View File

@@ -12,16 +12,16 @@
@dragleave="ordering = false" @dragleave="ordering = false"
@dragend="resetDragState" @dragend="resetDragState"
></div> ></div>
<div class="relative flex flex-col"> <div class="flex flex-col relative">
<div <div
class="z-1 pointer-events-none absolute inset-0 bg-accent opacity-0 transition" class="absolute bg-accent opacity-0 pointer-events-none inset-0 z-1 transition"
:class="{ :class="{
'opacity-25': 'opacity-25':
dragging && notSameDestination && notSameParentDestination, dragging && notSameDestination && notSameParentDestination,
}" }"
></div> ></div>
<div <div
class="z-3 group pointer-events-auto relative flex cursor-pointer items-stretch" class="flex items-stretch group relative z-3 cursor-pointer pointer-events-auto"
:draggable="!hasNoTeamAccess" :draggable="!hasNoTeamAccess"
@dragstart="dragStart" @dragstart="dragStart"
@drop="handelDrop($event)" @drop="handelDrop($event)"
@@ -36,11 +36,11 @@
@contextmenu.prevent="options?.tippy.show()" @contextmenu.prevent="options?.tippy.show()"
> >
<div <div
class="flex min-w-0 flex-1 items-center justify-center" class="flex items-center justify-center flex-1 min-w-0"
@click="emit('toggle-children')" @click="emit('toggle-children')"
> >
<span <span
class="pointer-events-none flex items-center justify-center px-4" class="flex items-center justify-center px-4 pointer-events-none"
> >
<HoppSmartSpinner v-if="isCollLoading" /> <HoppSmartSpinner v-if="isCollLoading" />
<component <component
@@ -51,7 +51,7 @@
/> />
</span> </span>
<span <span
class="pointer-events-none flex min-w-0 flex-1 py-2 pr-2 transition group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 transition pointer-events-none group-hover:text-secondaryDark"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
{{ collectionName }} {{ collectionName }}

View File

@@ -26,7 +26,7 @@
<div v-if="step.name === 'FILE_IMPORT'" class="space-y-4"> <div v-if="step.name === 'FILE_IMPORT'" class="space-y-4">
<p class="flex items-center"> <p class="flex items-center">
<span <span
class="mr-4 inline-flex flex-shrink-0 items-center justify-center rounded-full border-4 border-primary text-dividerDark" class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
:class="{ :class="{
'!text-green-500': hasFile, '!text-green-500': hasFile,
}" }"
@@ -38,14 +38,14 @@
</span> </span>
</p> </p>
<p <p
class="ml-10 flex flex-col rounded border border-dashed border-dividerDark" class="flex flex-col ml-10 border border-dashed rounded border-dividerDark"
> >
<input <input
id="inputChooseFileToImportFrom" id="inputChooseFileToImportFrom"
ref="inputChooseFileToImportFrom" ref="inputChooseFileToImportFrom"
name="inputChooseFileToImportFrom" name="inputChooseFileToImportFrom"
type="file" type="file"
class="cursor-pointer p-4 text-secondary transition file:mr-2 file:cursor-pointer file:rounded file:border-0 file:bg-primaryLight file:px-4 file:py-2 file:text-secondary file:transition hover:text-secondaryDark hover:file:bg-primaryDark hover:file:text-secondaryDark" class="p-4 cursor-pointer transition file:transition file:cursor-pointer text-secondary hover:text-secondaryDark file:mr-2 file:py-2 file:px-4 file:rounded file:border-0 file:text-secondary hover:file:text-secondaryDark file:bg-primaryLight hover:file:bg-primaryDark"
:accept="step.metadata.acceptedFileTypes" :accept="step.metadata.acceptedFileTypes"
@change="onFileChange" @change="onFileChange"
/> />
@@ -54,7 +54,7 @@
<div v-else-if="step.name === 'URL_IMPORT'" class="space-y-4"> <div v-else-if="step.name === 'URL_IMPORT'" class="space-y-4">
<p class="flex items-center"> <p class="flex items-center">
<span <span
class="mr-4 inline-flex flex-shrink-0 items-center justify-center rounded-full border-4 border-primary text-dividerDark" class="inline-flex items-center justify-center flex-shrink-0 mr-4 border-4 rounded-full border-primary text-dividerDark"
:class="{ :class="{
'!text-green-500': hasGist, '!text-green-500': hasGist,
}" }"
@@ -65,7 +65,7 @@
{{ t(`${step.metadata.caption}`) }} {{ t(`${step.metadata.caption}`) }}
</span> </span>
</p> </p>
<p class="ml-10 flex flex-col"> <p class="flex flex-col ml-10">
<input <input
v-model="inputChooseGistToImportFrom" v-model="inputChooseGistToImportFrom"
type="url" type="url"

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-1 flex-col"> <div class="flex flex-col flex-1">
<div <div
class="sticky z-10 flex flex-1 justify-between border-b border-dividerLight bg-primary" class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
:style=" :style="
saveRequest saveRequest
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))' ? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
@@ -31,7 +31,7 @@
/> />
</span> </span>
</div> </div>
<div class="flex flex-1 flex-col"> <div class="flex flex-col flex-1">
<HoppSmartTree :adapter="myAdapter"> <HoppSmartTree :adapter="myAdapter">
<template <template
#content="{ node, toggleChildren, isOpen, highlightChildren }" #content="{ node, toggleChildren, isOpen, highlightChildren }"
@@ -248,7 +248,7 @@
:text="`${t('state.nothing_found')}${filterText}`" :text="`${t('state.nothing_found')}${filterText}`"
> >
<template #icon> <template #icon>
<icon-lucide-search class="svg-icons pb-2 opacity-75" /> <icon-lucide-search class="pb-2 opacity-75 svg-icons" />
</template> </template>
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<HoppSmartPlaceholder <HoppSmartPlaceholder
@@ -258,10 +258,10 @@
:text="t('empty.collections')" :text="t('empty.collections')"
> >
<div class="flex flex-col items-center space-y-4"> <div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight"> <span class="text-secondaryLight text-center">
{{ t("collection.import_or_create") }} {{ t("collection.import_or_create") }}
</span> </span>
<div class="flex flex-col items-stretch gap-4"> <div class="flex gap-4 flex-col items-stretch">
<HoppButtonPrimary <HoppButtonPrimary
:icon="IconImport" :icon="IconImport"
:label="t('import.title')" :label="t('import.title')"

View File

@@ -13,7 +13,7 @@
@dragend="resetDragState" @dragend="resetDragState"
></div> ></div>
<div <div
class="group flex items-stretch" class="flex items-stretch group"
:draggable="!hasNoTeamAccess" :draggable="!hasNoTeamAccess"
@drop="handelDrop" @drop="handelDrop"
@dragstart="dragStart" @dragstart="dragStart"
@@ -23,13 +23,12 @@
@contextmenu.prevent="options?.tippy.show()" @contextmenu.prevent="options?.tippy.show()"
> >
<div <div
class="pointer-events-auto flex min-w-0 flex-1 cursor-pointer items-center justify-center" class="flex items-center justify-center flex-1 min-w-0 cursor-pointer pointer-events-auto"
@click="selectRequest()" @click="selectRequest()"
> >
<span <span
class="pointer-events-none flex w-16 items-center justify-center truncate px-2" class="flex items-center justify-center w-16 px-2 truncate pointer-events-none"
:class="requestLabelColor" :class="requestLabelColor"
:style="{ color: requestLabelColor }"
> >
<component <component
:is="IconCheckCircle" :is="IconCheckCircle"
@@ -38,12 +37,12 @@
:class="{ 'text-accent': isSelected }" :class="{ 'text-accent': isSelected }"
/> />
<HoppSmartSpinner v-else-if="isRequestLoading" /> <HoppSmartSpinner v-else-if="isRequestLoading" />
<span v-else class="truncate text-tiny font-semibold"> <span v-else class="font-semibold truncate text-tiny">
{{ request.method }} {{ request.method }}
</span> </span>
</span> </span>
<span <span
class="pointer-events-none flex min-w-0 flex-1 items-center py-2 pr-2 transition group-hover:text-secondaryDark" class="flex items-center flex-1 min-w-0 py-2 pr-2 pointer-events-none transition group-hover:text-secondaryDark"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
{{ request.name }} {{ request.name }}
@@ -51,15 +50,15 @@
<span <span
v-if="isActive" v-if="isActive"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0" class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
:title="`${t('collection.request_in_use')}`" :title="`${t('collection.request_in_use')}`"
> >
<span <span
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75" class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
> >
</span> </span>
<span <span
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500" class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
></span> ></span>
</span> </span>
</span> </span>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-1 flex-col"> <div class="flex flex-col flex-1">
<div <div
class="sticky z-10 flex flex-1 justify-between border-b border-dividerLight bg-primary" class="sticky z-10 flex justify-between flex-1 border-b bg-primary border-dividerLight"
:style=" :style="
saveRequest saveRequest
? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))' ? 'top: calc(var(--upper-primary-sticky-fold) - var(--line-height-body))'
@@ -269,10 +269,10 @@
@drop.stop @drop.stop
> >
<div class="flex flex-col items-center space-y-4"> <div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight"> <span class="text-secondaryLight text-center">
{{ t("collection.import_or_create") }} {{ t("collection.import_or_create") }}
</span> </span>
<div class="flex flex-col items-stretch gap-4"> <div class="flex gap-4 flex-col items-stretch">
<HoppButtonPrimary <HoppButtonPrimary
:icon="IconImport" :icon="IconImport"
:label="t('import.title')" :label="t('import.title')"

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]"> <div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
<div <div
class="group flex items-stretch" class="flex items-stretch group"
@dragover.prevent @dragover.prevent
@drop.prevent="dropEvent" @drop.prevent="dropEvent"
@dragover="dragging = true" @dragover="dragging = true"
@@ -11,7 +11,7 @@
@contextmenu.prevent="options.tippy.show()" @contextmenu.prevent="options.tippy.show()"
> >
<span <span
class="flex cursor-pointer items-center justify-center px-4" class="flex items-center justify-center px-4 cursor-pointer"
@click="toggleShowChildren()" @click="toggleShowChildren()"
> >
<component <component
@@ -21,7 +21,7 @@
/> />
</span> </span>
<span <span
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="toggleShowChildren()" @click="toggleShowChildren()"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
@@ -136,10 +136,10 @@
</div> </div>
<div v-if="showChildren || isFiltered" class="flex"> <div v-if="showChildren || isFiltered" class="flex">
<div <div
class="ml-[1.375rem] flex w-0.5 transform cursor-nsResize bg-dividerLight transition hover:scale-x-125 hover:bg-dividerDark" class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-0.5 hover:bg-dividerDark hover:scale-x-125"
@click="toggleShowChildren()" @click="toggleShowChildren()"
></div> ></div>
<div class="flex flex-1 flex-col truncate"> <div class="flex flex-col flex-1 truncate">
<CollectionsGraphqlFolder <CollectionsGraphqlFolder
v-for="(folder, index) in collection.folders" v-for="(folder, index) in collection.folders"
:key="`folder-${String(index)}`" :key="`folder-${String(index)}`"

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]"> <div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
<div <div
class="group flex items-stretch" class="flex items-stretch group"
@dragover.prevent @dragover.prevent
@drop.prevent="dropEvent" @drop.prevent="dropEvent"
@dragover="dragging = true" @dragover="dragging = true"
@@ -11,7 +11,7 @@
@contextmenu.prevent="options.tippy.show()" @contextmenu.prevent="options.tippy.show()"
> >
<span <span
class="flex cursor-pointer items-center justify-center px-4" class="flex items-center justify-center px-4 cursor-pointer"
@click="toggleShowChildren()" @click="toggleShowChildren()"
> >
<component <component
@@ -21,7 +21,7 @@
/> />
</span> </span>
<span <span
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="toggleShowChildren()" @click="toggleShowChildren()"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
@@ -128,10 +128,10 @@
</div> </div>
<div v-if="showChildren || isFiltered" class="flex"> <div v-if="showChildren || isFiltered" class="flex">
<div <div
class="ml-[1.375rem] flex w-0.5 transform cursor-nsResize bg-dividerLight transition hover:scale-x-125 hover:bg-dividerDark" class="bg-dividerLight cursor-nsResize flex ml-5.5 transform transition w-0.5 hover:bg-dividerDark hover:scale-x-125"
@click="toggleShowChildren()" @click="toggleShowChildren()"
></div> ></div>
<div class="flex flex-1 flex-col truncate"> <div class="flex flex-col flex-1 truncate">
<!-- Referring to this component only (this is recursive) --> <!-- Referring to this component only (this is recursive) -->
<Folder <Folder
v-for="(subFolder, subFolderIndex) in folder.folders" v-for="(subFolder, subFolderIndex) in folder.folders"

View File

@@ -1,7 +1,7 @@
<template> <template>
<div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]"> <div class="flex flex-col" :class="[{ 'bg-primaryLight': dragging }]">
<div <div
class="group flex items-stretch" class="flex items-stretch group"
draggable="true" draggable="true"
@dragstart="dragStart" @dragstart="dragStart"
@dragover.stop @dragover.stop
@@ -10,7 +10,7 @@
@contextmenu.prevent="options.tippy.show()" @contextmenu.prevent="options.tippy.show()"
> >
<span <span
class="flex w-16 cursor-pointer items-center justify-center truncate px-2" class="flex items-center justify-center w-16 px-2 truncate cursor-pointer"
@click="selectRequest()" @click="selectRequest()"
> >
<component <component
@@ -20,7 +20,7 @@
/> />
</span> </span>
<span <span
class="flex min-w-0 flex-1 cursor-pointer items-center py-2 pr-2 transition group-hover:text-secondaryDark" class="flex items-center flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="selectRequest()" @click="selectRequest()"
> >
<span class="truncate" :class="{ 'text-accent': isSelected }"> <span class="truncate" :class="{ 'text-accent': isSelected }">
@@ -29,15 +29,15 @@
<span <span
v-if="isActive" v-if="isActive"
v-tippy="{ theme: 'tooltip' }" v-tippy="{ theme: 'tooltip' }"
class="relative mx-3 flex h-1.5 w-1.5 flex-shrink-0" class="relative h-1.5 w-1.5 flex flex-shrink-0 mx-3"
:title="`${t('collection.request_in_use')}`" :title="`${t('collection.request_in_use')}`"
> >
<span <span
class="absolute inline-flex h-full w-full flex-shrink-0 animate-ping rounded-full bg-green-500 opacity-75" class="absolute inline-flex flex-shrink-0 w-full h-full bg-green-500 rounded-full opacity-75 animate-ping"
> >
</span> </span>
<span <span
class="relative inline-flex h-1.5 w-1.5 flex-shrink-0 rounded-full bg-green-500" class="relative inline-flex flex-shrink-0 rounded-full h-1.5 w-1.5 bg-green-500"
></span> ></span>
</span> </span>
</span> </span>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div :class="{ 'rounded border border-divider': saveRequest }"> <div :class="{ 'rounded border border-divider': saveRequest }">
<div <div
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto rounded-t bg-primary" class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto rounded-t bg-primary"
:style=" :style="
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0' saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
" "
@@ -11,10 +11,10 @@
type="search" type="search"
autocomplete="off" autocomplete="off"
:placeholder="t('action.search')" :placeholder="t('action.search')"
class="!border-0 bg-transparent py-2 pl-4 pr-2" class="py-2 pl-4 pr-2 bg-transparent !border-0"
/> />
<div <div
class="flex flex-1 flex-shrink-0 justify-between border-y border-dividerLight bg-primary" class="flex justify-between flex-1 flex-shrink-0 border-y bg-primary border-dividerLight"
> >
<HoppButtonSecondary <HoppButtonSecondary
:icon="IconPlus" :icon="IconPlus"
@@ -67,10 +67,10 @@
:text="t('empty.collections')" :text="t('empty.collections')"
> >
<div class="flex flex-col items-center space-y-4"> <div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight"> <span class="text-secondaryLight text-center">
{{ t("collection.import_or_create") }} {{ t("collection.import_or_create") }}
</span> </span>
<div class="flex flex-col items-stretch gap-4"> <div class="flex gap-4 flex-col items-stretch">
<HoppButtonPrimary <HoppButtonPrimary
:icon="IconImport" :icon="IconImport"
:label="t('import.title')" :label="t('import.title')"
@@ -93,7 +93,7 @@
:text="`${t('state.nothing_found')}${filterText}`" :text="`${t('state.nothing_found')}${filterText}`"
> >
<template #icon> <template #icon>
<icon-lucide-search class="svg-icons pb-2 opacity-75" /> <icon-lucide-search class="pb-2 opacity-75 svg-icons" />
</template> </template>
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<CollectionsGraphqlAdd <CollectionsGraphqlAdd

View File

@@ -11,7 +11,7 @@
@dragend="draggingToRoot = false" @dragend="draggingToRoot = false"
> >
<div <div
class="sticky z-10 flex flex-shrink-0 flex-col overflow-x-auto border-b border-dividerLight bg-primary" class="sticky z-10 flex flex-col flex-shrink-0 overflow-x-auto border-b bg-primary border-dividerLight"
:class="{ 'rounded-t': saveRequest }" :class="{ 'rounded-t': saveRequest }"
:style=" :style="
saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0' saveRequest ? 'top: calc(-1 * var(--line-height-body))' : 'top: 0'
@@ -22,7 +22,7 @@
v-model="filterTexts" v-model="filterTexts"
type="search" type="search"
autocomplete="off" autocomplete="off"
class="flex h-8 w-full bg-transparent p-4 py-2" class="flex w-full p-4 py-2 bg-transparent h-8"
:placeholder="t('action.search')" :placeholder="t('action.search')"
:disabled="collectionsType.type === 'team-collections'" :disabled="collectionsType.type === 'team-collections'"
/> />
@@ -85,12 +85,12 @@
@display-modal-import-export="displayModalImportExport(true)" @display-modal-import-export="displayModalImportExport(true)"
/> />
<div <div
class="py-15 hidden flex-1 flex-col items-center justify-center bg-primaryDark px-4 text-secondaryLight" class="hidden bg-primaryDark flex-col flex-1 items-center py-15 justify-center px-4 text-secondaryLight"
:class="{ :class="{
'!flex': draggingToRoot && currentReorderingStatus.type !== 'request', '!flex': draggingToRoot && currentReorderingStatus.type !== 'request',
}" }"
> >
<icon-lucide-list-end class="svg-icons !h-8 !w-8" /> <icon-lucide-list-end class="svg-icons !w-8 !h-8" />
</div> </div>
<CollectionsAdd <CollectionsAdd
:show="showModalAdd" :show="showModalAdd"

View File

@@ -11,11 +11,11 @@
v-if="!currentInterceptorSupportsCookies" v-if="!currentInterceptorSupportsCookies"
:text="t('cookies.modal.interceptor_no_support')" :text="t('cookies.modal.interceptor_no_support')"
> >
<AppInterceptor class="rounded border border-dividerLight p-2" /> <AppInterceptor class="p-2 border rounded border-dividerLight" />
</HoppSmartPlaceholder> </HoppSmartPlaceholder>
<div v-else class="flex flex-col"> <div v-else class="flex flex-col">
<div <div
class="sticky -mx-4 -mt-4 flex space-x-2 border-b border-dividerLight bg-primary px-4 py-4" class="flex bg-primary space-x-2 border-b sticky border-dividerLight -mx-4 px-4 py-4 -mt-4"
style="top: calc(-1 * var(--line-height-body))" style="top: calc(-1 * var(--line-height-body))"
> >
<HoppSmartInput <HoppSmartInput
@@ -46,7 +46,7 @@
:key="domain" :key="domain"
class="flex flex-col" class="flex flex-col"
> >
<div class="flex flex-1 items-center justify-between"> <div class="flex items-center justify-between flex-1">
<label for="cookiesList" class="p-4"> <label for="cookiesList" class="p-4">
{{ domain }} {{ domain }}
</label> </label>
@@ -65,11 +65,11 @@
/> />
</div> </div>
</div> </div>
<div class="rounded border border-divider"> <div class="border rounded border-divider">
<div class="divide-y divide-dividerLight"> <div class="divide-y divide-dividerLight">
<div <div
v-if="entries.length === 0" v-if="entries.length === 0"
class="flex flex-col items-center gap-2 p-4" class="flex flex-col gap-2 p-4 items-center"
> >
{{ t("cookies.modal.no_cookies_in_domain") }} {{ t("cookies.modal.no_cookies_in_domain") }}
</div> </div>
@@ -80,7 +80,7 @@
class="flex divide-x divide-dividerLight" class="flex divide-x divide-dividerLight"
> >
<input <input
class="flex flex-1 bg-transparent px-4 py-2" class="flex flex-1 px-4 py-2 bg-transparent"
:value="entry" :value="entry"
readonly readonly
/> />

View File

@@ -6,10 +6,10 @@
@close="hideModal" @close="hideModal"
> >
<template #body> <template #body>
<div class="rounded border border-dividerLight"> <div class="border rounded border-dividerLight">
<div class="flex flex-col"> <div class="flex flex-col">
<div class="flex items-center justify-between pl-4"> <div class="flex items-center justify-between pl-4">
<label class="truncate font-semibold text-secondaryLight"> <label class="font-semibold truncate text-secondaryLight">
{{ t("cookies.modal.cookie_string") }} {{ t("cookies.modal.cookie_string") }}
</label> </label>
<div class="flex items-center"> <div class="flex items-center">
@@ -43,7 +43,7 @@
<div class="h-46"> <div class="h-46">
<div <div
ref="cookieEditor" ref="cookieEditor"
class="h-full rounded-b border-t border-dividerLight" class="h-full border-t rounded-b border-dividerLight"
></div> ></div>
</div> </div>
</div> </div>

View File

@@ -5,9 +5,9 @@
@close="hideModal" @close="hideModal"
> >
<template #body> <template #body>
<div class="flex flex-1 flex-col space-y-4"> <div class="flex space-y-4 flex-1 flex-col">
<div class="ml-2 flex items-center space-x-8"> <div class="flex items-center space-x-8 ml-2">
<label for="name" class="min-w-10 font-semibold">{{ <label for="name" class="font-semibold min-w-10">{{
t("environment.name") t("environment.name")
}}</label> }}</label>
<input <input
@@ -17,8 +17,8 @@
class="input" class="input"
/> />
</div> </div>
<div class="ml-2 flex items-center space-x-8"> <div class="flex items-center space-x-8 ml-2">
<label for="value" class="min-w-10 font-semibold">{{ <label for="value" class="font-semibold min-w-10">{{
t("environment.value") t("environment.value")
}}</label> }}</label>
<input <input
@@ -28,17 +28,17 @@
:placeholder="t('environment.value')" :placeholder="t('environment.value')"
/> />
</div> </div>
<div class="ml-2 flex items-center space-x-8"> <div class="flex items-center space-x-8 ml-2">
<label for="scope" class="min-w-10 font-semibold"> <label for="scope" class="font-semibold min-w-10">
{{ t("environment.scope") }} {{ t("environment.scope") }}
</label> </label>
<div <div
class="relative flex flex-1 flex-col rounded border border-divider focus-visible:border-dividerDark" class="relative flex flex-1 flex-col border border-divider rounded focus-visible:border-dividerDark"
> >
<EnvironmentsSelector v-model="scope" :is-scope-selector="true" /> <EnvironmentsSelector v-model="scope" :is-scope-selector="true" />
</div> </div>
</div> </div>
<div v-if="replaceWithVariable" class="mt-3 flex space-x-2"> <div v-if="replaceWithVariable" class="flex space-x-2 mt-3">
<div class="min-w-18" /> <div class="min-w-18" />
<HoppSmartCheckbox <HoppSmartCheckbox
:on="replaceWithVariable" :on="replaceWithVariable"

View File

@@ -20,7 +20,7 @@
: `${t('environment.select')}` : `${t('environment.select')}`
: '' : ''
" "
class="flex-1 !justify-start rounded-none pr-8" class="flex-1 !justify-start pr-8 rounded-none"
/> />
</span> </span>
<template #content="{ hide }"> <template #content="{ hide }">
@@ -101,7 +101,7 @@
<img <img
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"
loading="lazy" loading="lazy"
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center" class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
:alt="`${t('empty.environments')}`" :alt="`${t('empty.environments')}`"
/> />
<span class="pb-2 text-center"> <span class="pb-2 text-center">
@@ -148,7 +148,7 @@
<img <img
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"
loading="lazy" loading="lazy"
class="mb-2 inline-flex h-16 w-16 flex-col object-contain object-center" class="inline-flex flex-col object-contain object-center w-16 h-16 mb-2"
:alt="`${t('empty.environments')}`" :alt="`${t('empty.environments')}`"
/> />
<span class="pb-2 text-center"> <span class="pb-2 text-center">
@@ -160,7 +160,7 @@
v-if="!teamListLoading && teamAdapterError" v-if="!teamListLoading && teamAdapterError"
class="flex flex-col items-center py-4" class="flex flex-col items-center py-4"
> >
<icon-lucide-help-circle class="svg-icons mb-4" /> <icon-lucide-help-circle class="mb-4 svg-icons" />
{{ getErrorMessage(teamAdapterError) }} {{ getErrorMessage(teamAdapterError) }}
</div> </div>
</HoppSmartTab> </HoppSmartTab>
@@ -190,7 +190,7 @@
@keyup.escape="hide()" @keyup.escape="hide()"
> >
<div <div
class="sticky top-0 flex items-center justify-between truncate rounded border border-divider bg-primary pl-4 font-semibold text-secondaryDark" class="sticky top-0 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
> >
{{ t("environment.global_variables") }} {{ t("environment.global_variables") }}
<HoppButtonSecondary <HoppButtonSecondary
@@ -205,12 +205,12 @@
" "
/> />
</div> </div>
<div class="my-2 flex flex-1 flex-col space-y-2 pl-4 pr-2"> <div class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
<div class="flex flex-1 space-x-4"> <div class="flex flex-1 space-x-4">
<span class="min-w-32 w-1/4 truncate text-tiny font-semibold"> <span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
{{ t("environment.name") }} {{ t("environment.name") }}
</span> </span>
<span class="min-w-32 w-full truncate text-tiny font-semibold"> <span class="w-full min-w-32 truncate text-tiny font-semibold">
{{ t("environment.value") }} {{ t("environment.value") }}
</span> </span>
</div> </div>
@@ -219,10 +219,10 @@
:key="index" :key="index"
class="flex flex-1 space-x-4" class="flex flex-1 space-x-4"
> >
<span class="min-w-32 w-1/4 truncate text-secondaryLight"> <span class="text-secondaryLight w-1/4 min-w-32 truncate">
{{ variable.key }} {{ variable.key }}
</span> </span>
<span class="min-w-32 w-full truncate text-secondaryLight"> <span class="text-secondaryLight w-full min-w-32 truncate">
{{ variable.value }} {{ variable.value }}
</span> </span>
</div> </div>
@@ -231,7 +231,7 @@
</div> </div>
</div> </div>
<div <div
class="sticky top-0 mt-2 flex items-center justify-between truncate rounded border border-divider bg-primary pl-4 font-semibold text-secondaryDark" class="sticky top-0 mt-2 font-semibold truncate flex items-center justify-between text-secondaryDark bg-primary border border-divider rounded pl-4"
:class="{ :class="{
'bg-primaryLight': !selectedEnv.variables, 'bg-primaryLight': !selectedEnv.variables,
}" }"
@@ -252,16 +252,16 @@
</div> </div>
<div <div
v-if="selectedEnv.type === 'NO_ENV_SELECTED'" v-if="selectedEnv.type === 'NO_ENV_SELECTED'"
class="my-2 flex flex-1 flex-col pl-4 text-secondaryLight" class="text-secondaryLight my-2 flex flex-col flex-1 pl-4"
> >
{{ t("environment.no_active_environment") }} {{ t("environment.no_active_environment") }}
</div> </div>
<div v-else class="my-2 flex flex-1 flex-col space-y-2 pl-4 pr-2"> <div v-else class="my-2 flex flex-col flex-1 space-y-2 pl-4 pr-2">
<div class="flex flex-1 space-x-4"> <div class="flex flex-1 space-x-4">
<span class="min-w-32 w-1/4 truncate text-tiny font-semibold"> <span class="w-1/4 min-w-32 truncate text-tiny font-semibold">
{{ t("environment.name") }} {{ t("environment.name") }}
</span> </span>
<span class="min-w-32 w-full truncate text-tiny font-semibold"> <span class="w-full min-w-32 truncate text-tiny font-semibold">
{{ t("environment.value") }} {{ t("environment.value") }}
</span> </span>
</div> </div>
@@ -270,10 +270,10 @@
:key="index" :key="index"
class="flex flex-1 space-x-4" class="flex flex-1 space-x-4"
> >
<span class="min-w-32 w-1/4 truncate text-secondaryLight"> <span class="text-secondaryLight w-1/4 min-w-32 truncate">
{{ variable.key }} {{ variable.key }}
</span> </span>
<span class="min-w-32 w-full truncate text-secondaryLight"> <span class="text-secondaryLight w-full min-w-32 truncate">
{{ variable.value }} {{ variable.value }}
</span> </span>
</div> </div>

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div <div
class="sticky top-0 z-10 flex flex-shrink-0 flex-col overflow-x-auto bg-primary" class="sticky top-0 z-10 flex flex-col flex-shrink-0 overflow-x-auto bg-primary"
> >
<WorkspaceCurrent :section="t('tab.environments')" /> <WorkspaceCurrent :section="t('tab.environments')" />
<EnvironmentsMyEnvironment <EnvironmentsMyEnvironment

View File

@@ -16,7 +16,7 @@
@submit="saveEnvironment" @submit="saveEnvironment"
/> />
<div class="flex flex-1 items-center justify-between"> <div class="flex items-center justify-between flex-1">
<label for="variableList" class="p-4"> <label for="variableList" class="p-4">
{{ t("environment.variable_list") }} {{ t("environment.variable_list") }}
</label> </label>
@@ -37,11 +37,11 @@
</div> </div>
<div <div
v-if="evnExpandError" v-if="evnExpandError"
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400" class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
> >
{{ t("environment.nested_overflow") }} {{ t("environment.nested_overflow") }}
</div> </div>
<div class="divide-y divide-dividerLight rounded border border-divider"> <div class="border rounded divide-y divide-dividerLight border-divider">
<div <div
v-for="({ id, env }, index) in vars" v-for="({ id, env }, index) in vars"
:key="`variable-${id}-${index}`" :key="`variable-${id}-${index}`"
@@ -50,7 +50,7 @@
<input <input
v-model="env.key" v-model="env.key"
v-focus v-focus
class="flex flex-1 bg-transparent px-4 py-2" class="flex flex-1 px-4 py-2 bg-transparent"
:placeholder="`${t('count.variable', { count: index + 1 })}`" :placeholder="`${t('count.variable', { count: index + 1 })}`"
:name="'param' + index" :name="'param' + index"
/> />

View File

@@ -1,24 +1,24 @@
<template> <template>
<div <div
class="group flex items-stretch" class="flex items-stretch group"
@contextmenu.prevent="options!.tippy.show()" @contextmenu.prevent="options!.tippy.show()"
> >
<span <span
v-if="environmentIndex === 'Global'" v-if="environmentIndex === 'Global'"
class="flex cursor-pointer items-center justify-center px-4" class="flex items-center justify-center px-4 cursor-pointer"
@click="emit('edit-environment')" @click="emit('edit-environment')"
> >
<icon-lucide-globe class="svg-icons" /> <icon-lucide-globe class="svg-icons" />
</span> </span>
<span <span
v-else v-else
class="flex cursor-pointer items-center justify-center px-4" class="flex items-center justify-center px-4 cursor-pointer"
@click="emit('edit-environment')" @click="emit('edit-environment')"
> >
<icon-lucide-layers class="svg-icons" /> <icon-lucide-layers class="svg-icons" />
</span> </span>
<span <span
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="emit('edit-environment')" @click="emit('edit-environment')"
> >
<span class="truncate"> <span class="truncate">

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div <div
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary" class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperPrimaryStickyFold border-dividerLight bg-primary"
> >
<HoppButtonSecondary <HoppButtonSecondary
:icon="IconPlus" :icon="IconPlus"
@@ -39,10 +39,10 @@
:text="t('empty.environments')" :text="t('empty.environments')"
> >
<div class="flex flex-col items-center space-y-4"> <div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight"> <span class="text-secondaryLight text-center">
{{ t("environment.import_or_create") }} {{ t("environment.import_or_create") }}
</span> </span>
<div class="flex flex-col items-stretch gap-4"> <div class="flex gap-4 flex-col items-stretch">
<HoppButtonPrimary <HoppButtonPrimary
:icon="IconImport" :icon="IconImport"
:label="t('import.title')" :label="t('import.title')"

View File

@@ -16,7 +16,7 @@
@submit="saveEnvironment" @submit="saveEnvironment"
/> />
<div class="flex flex-1 items-center justify-between"> <div class="flex items-center justify-between flex-1">
<label for="variableList" class="p-4"> <label for="variableList" class="p-4">
{{ t("environment.variable_list") }} {{ t("environment.variable_list") }}
</label> </label>
@@ -37,11 +37,11 @@
</div> </div>
<div <div
v-if="evnExpandError" v-if="evnExpandError"
class="mb-2 w-full overflow-auto whitespace-normal rounded bg-primaryLight px-4 py-2 font-mono text-red-400" class="w-full px-4 py-2 mb-2 overflow-auto font-mono text-red-400 whitespace-normal rounded bg-primaryLight"
> >
{{ t("environment.nested_overflow") }} {{ t("environment.nested_overflow") }}
</div> </div>
<div class="divide-y divide-dividerLight rounded border border-divider"> <div class="border rounded divide-y divide-dividerLight border-divider">
<div <div
v-for="({ id, env }, index) in vars" v-for="({ id, env }, index) in vars"
:key="`variable-${id}-${index}`" :key="`variable-${id}-${index}`"
@@ -50,7 +50,7 @@
<input <input
v-model="env.key" v-model="env.key"
v-focus v-focus
class="flex flex-1 bg-transparent px-4 py-2" class="flex flex-1 px-4 py-2 bg-transparent"
:class="isViewer && 'opacity-25'" :class="isViewer && 'opacity-25'"
:placeholder="`${t('count.variable', { count: index + 1 })}`" :placeholder="`${t('count.variable', { count: index + 1 })}`"
:name="'param' + index" :name="'param' + index"

View File

@@ -1,16 +1,16 @@
<template> <template>
<div <div
class="group flex items-stretch" class="flex items-stretch group"
@contextmenu.prevent="options!.tippy.show()" @contextmenu.prevent="options!.tippy.show()"
> >
<span <span
class="flex cursor-pointer items-center justify-center px-4" class="flex items-center justify-center px-4 cursor-pointer"
@click="emit('edit-environment')" @click="emit('edit-environment')"
> >
<icon-lucide-layers class="svg-icons" /> <icon-lucide-layers class="svg-icons" />
</span> </span>
<span <span
class="flex min-w-0 flex-1 cursor-pointer py-2 pr-2 transition group-hover:text-secondaryDark" class="flex flex-1 min-w-0 py-2 pr-2 cursor-pointer transition group-hover:text-secondaryDark"
@click="emit('edit-environment')" @click="emit('edit-environment')"
> >
<span class="truncate"> <span class="truncate">

View File

@@ -1,7 +1,7 @@
<template> <template>
<div> <div>
<div <div
class="sticky top-upperPrimaryStickyFold z-10 flex flex-1 flex-shrink-0 justify-between overflow-x-auto border-b border-dividerLight bg-primary" class="sticky z-10 flex justify-between flex-1 flex-shrink-0 overflow-x-auto border-b top-upperPrimaryStickyFold border-dividerLight bg-primary"
> >
<HoppButtonSecondary <HoppButtonSecondary
v-if="team === undefined || team.myRole === 'VIEWER'" v-if="team === undefined || team.myRole === 'VIEWER'"
@@ -50,10 +50,10 @@
:text="t('empty.environments')" :text="t('empty.environments')"
> >
<div class="flex flex-col items-center space-y-4"> <div class="flex flex-col items-center space-y-4">
<span class="text-center text-secondaryLight"> <span class="text-secondaryLight text-center">
{{ t("environment.import_or_create") }} {{ t("environment.import_or_create") }}
</span> </span>
<div class="flex flex-col items-stretch gap-4"> <div class="flex gap-4 flex-col items-stretch">
<HoppButtonPrimary <HoppButtonPrimary
:icon="IconImport" :icon="IconImport"
:label="t('import.title')" :label="t('import.title')"
@@ -94,7 +94,7 @@
v-if="!loading && adapterError" v-if="!loading && adapterError"
class="flex flex-col items-center py-4" class="flex flex-col items-center py-4"
> >
<icon-lucide-help-circle class="svg-icons mb-4" /> <icon-lucide-help-circle class="mb-4 svg-icons" />
{{ getErrorMessage(adapterError) }} {{ getErrorMessage(adapterError) }}
</div> </div>
<EnvironmentsTeamsDetails <EnvironmentsTeamsDetails

View File

@@ -47,9 +47,9 @@
/> />
</form> </form>
<div v-if="mode === 'email-sent'" class="flex flex-col px-4"> <div v-if="mode === 'email-sent'" class="flex flex-col px-4">
<div class="flex max-w-md flex-col items-center justify-center"> <div class="flex flex-col items-center justify-center max-w-md">
<icon-lucide-inbox class="h-6 w-6 text-accent" /> <icon-lucide-inbox class="w-6 h-6 text-accent" />
<h3 class="my-2 text-center text-lg"> <h3 class="my-2 text-lg text-center">
{{ t("auth.we_sent_magic_link") }} {{ t("auth.we_sent_magic_link") }}
</h3> </h3>
<p class="text-center"> <p class="text-center">
@@ -63,7 +63,7 @@
<template #footer> <template #footer>
<div <div
v-if="mode === 'sign-in' && tosLink && privacyPolicyLink" v-if="mode === 'sign-in' && tosLink && privacyPolicyLink"
class="text-tiny text-secondaryLight" class="text-secondaryLight text-tiny"
> >
By signing in, you are agreeing to our By signing in, you are agreeing to our
<HoppSmartAnchor <HoppSmartAnchor
@@ -90,7 +90,7 @@
</div> </div>
<div <div
v-if="mode === 'email-sent'" v-if="mode === 'email-sent'"
class="flex flex-1 justify-between text-secondaryLight" class="flex justify-between flex-1 text-secondaryLight"
> >
<HoppSmartAnchor <HoppSmartAnchor
class="link" class="link"
@@ -111,21 +111,20 @@
<script setup lang="ts"> <script setup lang="ts">
import { Ref, computed, onMounted, ref } from "vue" import { Ref, computed, onMounted, ref } from "vue"
import { useI18n } from "@composables/i18n"
import { useStreamSubscriber } from "@composables/stream" import { useStreamSubscriber } from "@composables/stream"
import { useToast } from "@composables/toast" import { useToast } from "@composables/toast"
import { useI18n } from "@composables/i18n"
import { platform } from "~/platform" import { platform } from "~/platform"
import { setLocalConfig } from "~/newstore/localpersistence"
import IconEmail from "~icons/auth/email"
import IconGithub from "~icons/auth/github" import IconGithub from "~icons/auth/github"
import IconGoogle from "~icons/auth/google" import IconGoogle from "~icons/auth/google"
import IconEmail from "~icons/auth/email"
import IconMicrosoft from "~icons/auth/microsoft" import IconMicrosoft from "~icons/auth/microsoft"
import IconArrowLeft from "~icons/lucide/arrow-left" import IconArrowLeft from "~icons/lucide/arrow-left"
import { useService } from "dioc/vue"
import { LoginItemDef } from "~/platform/auth" import { LoginItemDef } from "~/platform/auth"
import { PersistenceService } from "~/services/persistence"
defineProps<{ defineProps<{
show: boolean show: boolean
@@ -139,8 +138,6 @@ const { subscribeToStream } = useStreamSubscriber()
const t = useI18n() const t = useI18n()
const toast = useToast() const toast = useToast()
const persistenceService = useService(PersistenceService)
const form = { const form = {
email: "", email: "",
} }
@@ -263,7 +260,7 @@ const signInWithEmail = async () => {
.signInWithEmail(form.email) .signInWithEmail(form.email)
.then(() => { .then(() => {
mode.value = "email-sent" mode.value = "email-sent"
persistenceService.setLocalConfig("emailForSignIn", form.email) setLocalConfig("emailForSignIn", form.email)
}) })
.catch((e) => { .catch((e) => {
console.error(e) console.error(e)

View File

@@ -1,10 +1,10 @@
<template> <template>
<div class="flex flex-1 flex-col"> <div class="flex flex-col flex-1">
<div <div
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4" class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
> >
<span class="flex items-center"> <span class="flex items-center">
<label class="truncate font-semibold text-secondaryLight"> <label class="font-semibold truncate text-secondaryLight">
{{ t("authorization.type") }} {{ t("authorization.type") }}
</label> </label>
<tippy <tippy
@@ -15,7 +15,7 @@
> >
<span class="select-wrapper"> <span class="select-wrapper">
<HoppButtonSecondary <HoppButtonSecondary
class="ml-2 rounded-none pr-8" class="pr-8 ml-2 rounded-none"
:label="authName" :label="authName"
/> />
</span> </span>
@@ -171,7 +171,7 @@
</div> </div>
</div> </div>
<div <div
class="z-9 sticky top-upperTertiaryStickyFold h-full min-w-46 max-w-1/3 flex-shrink-0 overflow-auto overflow-x-auto bg-primary p-4" class="sticky flex-shrink-0 h-full p-4 overflow-auto overflow-x-auto bg-primary top-upperTertiaryStickyFold min-w-46 max-w-1/3 z-9"
> >
<div class="pb-2 text-secondaryLight"> <div class="pb-2 text-secondaryLight">
{{ t("helpers.authorization") }} {{ t("helpers.authorization") }}

View File

@@ -23,7 +23,7 @@
<div v-if="gqlField.deprecationReason"> <div v-if="gqlField.deprecationReason">
<span <span
v-tippy="{ theme: 'tomato' }" v-tippy="{ theme: 'tomato' }"
class="flex cursor-pointer items-center gap-2 text-xs !text-red-500 hover:!text-red-600" class="!text-red-500 hover:!text-red-600 text-xs flex items-center gap-2 cursor-pointer"
:title="gqlField.deprecationReason" :title="gqlField.deprecationReason"
> >
<IconAlertTriangle /> {{ t("state.deprecated") }} <IconAlertTriangle /> {{ t("state.deprecated") }}

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4" class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
> >
<label class="font-semibold text-secondaryLight"> <label class="font-semibold text-secondaryLight">
{{ t("tab.headers") }} {{ t("tab.headers") }}
@@ -42,7 +42,7 @@
/> />
</div> </div>
</div> </div>
<div v-if="bulkMode" ref="bulkEditor" class="flex flex-1 flex-col"></div> <div v-if="bulkMode" ref="bulkEditor" class="flex flex-col flex-1"></div>
<div v-else> <div v-else>
<draggable <draggable
v-model="workingHeaders" v-model="workingHeaders"
@@ -56,7 +56,7 @@
> >
<template #item="{ element: header, index }"> <template #item="{ element: header, index }">
<div <div
class="draggable-content group flex divide-x divide-dividerLight border-b border-dividerLight" class="flex border-b divide-x divide-dividerLight border-dividerLight draggable-content group"
> >
<span> <span>
<HoppButtonSecondary <HoppButtonSecondary
@@ -69,9 +69,9 @@
: null, : null,
}" }"
:icon="IconGripVertical" :icon="IconGripVertical"
class="opacity-0" class="cursor-auto text-primary hover:text-primary"
:class="{ :class="{
'draggable-handle cursor-grab group-hover:opacity-100': 'draggable-handle group-hover:text-secondaryLight !cursor-grab':
index !== workingHeaders?.length - 1, index !== workingHeaders?.length - 1,
}" }"
tabindex="-1" tabindex="-1"
@@ -91,7 +91,7 @@
px-4 px-4
truncate truncate
" "
class="!flex flex-1" class="flex-1 !flex"
@input=" @input="
updateHeader(index, { updateHeader(index, {
id: header.id, id: header.id,
@@ -102,7 +102,7 @@
" "
/> />
<input <input
class="flex flex-1 bg-transparent px-4 py-2" class="flex flex-1 px-4 py-2 bg-transparent"
:placeholder="`${t('count.value', { count: index + 1 })}`" :placeholder="`${t('count.value', { count: index + 1 })}`"
:name="`value ${String(index)}`" :name="`value ${String(index)}`"
:value="header.value" :value="header.value"

View File

@@ -1,6 +1,6 @@
<template> <template>
<div <div
class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between border-y border-dividerLight bg-primary pl-4" class="sticky top-sidebarPrimaryStickyFold z-10 flex items-center justify-between pl-4 border-y bg-primary border-dividerLight"
> >
<label class="font-semibold text-secondaryLight"> <label class="font-semibold text-secondaryLight">
{{ t("request.query") }} {{ t("request.query") }}
@@ -16,7 +16,7 @@
:title="`${t('request.stop')}`" :title="`${t('request.stop')}`"
:label="`${t('request.stop')}`" :label="`${t('request.stop')}`"
:icon="IconStop" :icon="IconStop"
class="!hover:text-accentDark rounded-none !text-accent" class="rounded-none !text-accent !hover:text-accentDark"
@click="unsubscribe()" @click="unsubscribe()"
/> />
@@ -31,7 +31,7 @@
:label="`${selectedOperation.name?.value ?? t('request.run')}`" :label="`${selectedOperation.name?.value ?? t('request.run')}`"
:icon="IconPlay" :icon="IconPlay"
:disabled="!selectedOperation" :disabled="!selectedOperation"
class="!hover:text-accentDark rounded-none !text-accent" class="rounded-none !text-accent !hover:text-accentDark"
@click="runQuery(selectedOperation)" @click="runQuery(selectedOperation)"
/> />
@@ -79,7 +79,7 @@
/> />
</div> </div>
</div> </div>
<div ref="queryEditor" class="flex flex-1 flex-col"></div> <div ref="queryEditor" class="flex flex-col flex-1"></div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">

Some files were not shown because too many files have changed in this diff Show More