Compare commits
1 Commits
feat/colle
...
feature/up
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e20904e896 |
@@ -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
@@ -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"
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -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
|
|
||||||
}
|
|
||||||
@@ -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
@@ -0,0 +1,11 @@
|
|||||||
|
:3000 {
|
||||||
|
try_files {path} /
|
||||||
|
root * /site/selfhost-web
|
||||||
|
file_server
|
||||||
|
}
|
||||||
|
|
||||||
|
:3100 {
|
||||||
|
try_files {path} /
|
||||||
|
root * /site/sh-admin
|
||||||
|
file_server
|
||||||
|
}
|
||||||
@@ -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) => {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -17,12 +17,12 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"sideEffects": false,
|
"sideEffects": false,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@codemirror/language": "6.9.0",
|
"@codemirror/language": "^6.9.0",
|
||||||
"@lezer/highlight": "1.1.4",
|
"@lezer/highlight": "^1.1.6",
|
||||||
"@lezer/lr": "^1.3.13"
|
"@lezer/lr": "^1.3.10"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@lezer/generator": "^1.5.1",
|
"@lezer/generator": "^1.5.0",
|
||||||
"mocha": "^9.2.2",
|
"mocha": "^9.2.2",
|
||||||
"rollup": "^3.29.3",
|
"rollup": "^3.29.3",
|
||||||
"rollup-plugin-dts": "^6.0.2",
|
"rollup-plugin-dts": "^6.0.2",
|
||||||
|
|||||||
@@ -1,3 +0,0 @@
|
|||||||
:80 :3170 {
|
|
||||||
reverse_proxy localhost:8080
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hoppscotch-backend",
|
"name": "hoppscotch-backend",
|
||||||
"version": "2023.8.4-1",
|
"version": "2023.8.2",
|
||||||
"description": "",
|
"description": "",
|
||||||
"author": "",
|
"author": "",
|
||||||
"private": true,
|
"private": true,
|
||||||
@@ -46,6 +46,7 @@
|
|||||||
"graphql-query-complexity": "^0.12.0",
|
"graphql-query-complexity": "^0.12.0",
|
||||||
"graphql-redis-subscriptions": "^2.6.0",
|
"graphql-redis-subscriptions": "^2.6.0",
|
||||||
"graphql-subscriptions": "^2.0.0",
|
"graphql-subscriptions": "^2.0.0",
|
||||||
|
"graphql-ws": "^5.14.2",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
"io-ts": "^2.2.16",
|
"io-ts": "^2.2.16",
|
||||||
"luxon": "^3.2.1",
|
"luxon": "^3.2.1",
|
||||||
@@ -66,7 +67,6 @@
|
|||||||
"@nestjs/schematics": "^10.0.2",
|
"@nestjs/schematics": "^10.0.2",
|
||||||
"@nestjs/testing": "^10.2.6",
|
"@nestjs/testing": "^10.2.6",
|
||||||
"@relmify/jest-fp-ts": "^2.0.2",
|
"@relmify/jest-fp-ts": "^2.0.2",
|
||||||
"@types/argon2": "^0.15.0",
|
|
||||||
"@types/bcrypt": "^5.0.0",
|
"@types/bcrypt": "^5.0.0",
|
||||||
"@types/cookie": "^0.5.1",
|
"@types/cookie": "^0.5.1",
|
||||||
"@types/cookie-parser": "^1.4.3",
|
"@types/cookie-parser": "^1.4.3",
|
||||||
|
|||||||
@@ -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;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
-- AlterTable
|
|
||||||
ALTER TABLE "TeamCollection" ADD COLUMN "data" JSONB;
|
|
||||||
|
|
||||||
-- AlterTable
|
|
||||||
ALTER TABLE "UserCollection" ADD COLUMN "data" JSONB;
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
@@ -11,7 +11,6 @@ import { TeamEnvironmentsModule } from '../team-environments/team-environments.m
|
|||||||
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 { InfraResolver } from './infra.resolver';
|
||||||
import { ShortcodeModule } from 'src/shortcode/shortcode.module';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -24,7 +23,6 @@ import { ShortcodeModule } from 'src/shortcode/shortcode.module';
|
|||||||
TeamEnvironmentsModule,
|
TeamEnvironmentsModule,
|
||||||
TeamCollectionModule,
|
TeamCollectionModule,
|
||||||
TeamRequestModule,
|
TeamRequestModule,
|
||||||
ShortcodeModule,
|
|
||||||
],
|
],
|
||||||
providers: [InfraResolver, AdminResolver, AdminService],
|
providers: [InfraResolver, AdminResolver, AdminService],
|
||||||
exports: [AdminService],
|
exports: [AdminService],
|
||||||
|
|||||||
@@ -443,23 +443,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, {
|
||||||
|
|||||||
@@ -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[] = [
|
||||||
|
|||||||
@@ -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,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -76,7 +74,7 @@ export class AdminService {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
|
await this.mailerService.sendUserInvitationEmail(inviteeEmail, {
|
||||||
template: 'user-invitation',
|
template: 'code-your-own',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: inviteeEmail,
|
inviteeEmail: inviteeEmail,
|
||||||
magicLink: `${process.env.VITE_BASE_URL}`,
|
magicLink: `${process.env.VITE_BASE_URL}`,
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import { InvitedUser } from './invited-user.model';
|
|||||||
import { Team } from 'src/team/team.model';
|
import { Team } from 'src/team/team.model';
|
||||||
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
||||||
import { GqlAdmin } from './decorators/gql-admin.decorator';
|
import { GqlAdmin } from './decorators/gql-admin.decorator';
|
||||||
import { ShortcodeWithUserEmail } from 'src/shortcode/shortcode.model';
|
|
||||||
|
|
||||||
@UseGuards(GqlThrottlerGuard)
|
@UseGuards(GqlThrottlerGuard)
|
||||||
@Resolver(() => Infra)
|
@Resolver(() => Infra)
|
||||||
@@ -203,23 +202,4 @@ export class InfraResolver {
|
|||||||
async teamRequestsCount() {
|
async teamRequestsCount() {
|
||||||
return this.adminService.getTeamRequestsCount();
|
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,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { ForbiddenException, HttpException, Module } from '@nestjs/common';
|
import { HttpException, Module } from '@nestjs/common';
|
||||||
import { GraphQLModule } from '@nestjs/graphql';
|
import { GraphQLModule } from '@nestjs/graphql';
|
||||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||||
import { UserModule } from './user/user.module';
|
import { UserModule } from './user/user.module';
|
||||||
@@ -20,28 +20,35 @@ import { ShortcodeModule } from './shortcode/shortcode.module';
|
|||||||
import { COOKIES_NOT_FOUND } from './errors';
|
import { COOKIES_NOT_FOUND } from './errors';
|
||||||
import { ThrottlerModule } from '@nestjs/throttler';
|
import { ThrottlerModule } from '@nestjs/throttler';
|
||||||
import { AppController } from './app.controller';
|
import { AppController } from './app.controller';
|
||||||
|
import { Context } from 'graphql-ws';
|
||||||
|
import {
|
||||||
|
ApolloServerPluginLandingPageLocalDefault,
|
||||||
|
ApolloServerPluginLandingPageProductionDefault,
|
||||||
|
} from '@apollo/server/plugin/landingPage/default';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
GraphQLModule.forRoot<ApolloDriverConfig>({
|
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||||
|
driver: ApolloDriver,
|
||||||
buildSchemaOptions: {
|
buildSchemaOptions: {
|
||||||
numberScalarMode: 'integer',
|
numberScalarMode: 'integer',
|
||||||
},
|
},
|
||||||
playground: process.env.PRODUCTION !== 'true',
|
playground: false,
|
||||||
|
plugins: [
|
||||||
|
process.env.PRODUCTION !== 'true'
|
||||||
|
? ApolloServerPluginLandingPageLocalDefault()
|
||||||
|
: ApolloServerPluginLandingPageProductionDefault(),
|
||||||
|
],
|
||||||
autoSchemaFile: true,
|
autoSchemaFile: true,
|
||||||
installSubscriptionHandlers: true,
|
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
'subscriptions-transport-ws': {
|
'graphql-ws': {
|
||||||
path: '/graphql',
|
path: '/graphql',
|
||||||
onConnect: (_, websocket) => {
|
onConnect: (context: Context<any, any>) => {
|
||||||
try {
|
try {
|
||||||
const cookies = subscriptionContextCookieParser(
|
const cookies = subscriptionContextCookieParser(
|
||||||
websocket.upgradeReq.headers.cookie,
|
context.extra.request.headers.cookie,
|
||||||
);
|
);
|
||||||
|
context['cookies'] = cookies;
|
||||||
return {
|
|
||||||
headers: { ...websocket?.upgradeReq?.headers, cookies },
|
|
||||||
};
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new HttpException(COOKIES_NOT_FOUND, 400, {
|
throw new HttpException(COOKIES_NOT_FOUND, 400, {
|
||||||
cause: new Error(COOKIES_NOT_FOUND),
|
cause: new Error(COOKIES_NOT_FOUND),
|
||||||
@@ -55,7 +62,6 @@ import { AppController } from './app.controller';
|
|||||||
res,
|
res,
|
||||||
connection,
|
connection,
|
||||||
}),
|
}),
|
||||||
driver: ApolloDriver,
|
|
||||||
}),
|
}),
|
||||||
ThrottlerModule.forRoot([
|
ThrottlerModule.forRoot([
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -229,7 +229,7 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await this.mailerService.sendEmail(email, {
|
await this.mailerService.sendEmail(email, {
|
||||||
template: 'user-invitation',
|
template: 'code-your-own',
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: email,
|
inviteeEmail: email,
|
||||||
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
magicLink: `${url}/enter?token=${generatedTokens.token}`,
|
||||||
|
|||||||
@@ -60,13 +60,13 @@ export const authCookieHandler = (
|
|||||||
res.cookie(AuthTokenType.ACCESS_TOKEN, authTokens.access_token, {
|
res.cookie(AuthTokenType.ACCESS_TOKEN, authTokens.access_token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'lax',
|
sameSite: process.env.PRODUCTION !== 'true' ? 'none' : 'lax',
|
||||||
maxAge: accessTokenValidity,
|
maxAge: accessTokenValidity,
|
||||||
});
|
});
|
||||||
res.cookie(AuthTokenType.REFRESH_TOKEN, authTokens.refresh_token, {
|
res.cookie(AuthTokenType.REFRESH_TOKEN, authTokens.refresh_token, {
|
||||||
httpOnly: true,
|
httpOnly: true,
|
||||||
secure: true,
|
secure: true,
|
||||||
sameSite: 'lax',
|
sameSite: process.env.PRODUCTION !== 'true' ? 'none' : 'lax',
|
||||||
maxAge: refreshTokenValidity,
|
maxAge: refreshTokenValidity,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ export type MailDescription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type UserMagicLinkMailDescription = {
|
export type UserMagicLinkMailDescription = {
|
||||||
template: 'user-invitation';
|
template: 'code-your-own';
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: string;
|
inviteeEmail: string;
|
||||||
magicLink: string;
|
magicLink: string;
|
||||||
@@ -16,7 +16,7 @@ export type UserMagicLinkMailDescription = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export type AdminUserInvitationMailDescription = {
|
export type AdminUserInvitationMailDescription = {
|
||||||
template: 'user-invitation';
|
template: 'code-your-own';
|
||||||
variables: {
|
variables: {
|
||||||
inviteeEmail: string;
|
inviteeEmail: string;
|
||||||
magicLink: string;
|
magicLink: string;
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export class MailerService {
|
|||||||
case 'team-invitation':
|
case 'team-invitation':
|
||||||
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
return `${mailDesc.variables.invitee} invited you to join ${mailDesc.variables.invite_team_name} in Hoppscotch`;
|
||||||
|
|
||||||
case 'user-invitation':
|
case 'code-your-own':
|
||||||
return 'Sign in to Hoppscotch';
|
return 'Sign in to Hoppscotch';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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, {
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(),
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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',
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -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",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
{
|
{
|
||||||
"action": {
|
"action": {
|
||||||
"add": "Add",
|
|
||||||
"autoscroll": "Autoscroll",
|
"autoscroll": "Autoscroll",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"choose_file": "Choose a file",
|
"choose_file": "Choose a file",
|
||||||
@@ -55,28 +54,9 @@
|
|||||||
"new": "Add new",
|
"new": "Add new",
|
||||||
"star": "Add star"
|
"star": "Add star"
|
||||||
},
|
},
|
||||||
"cookies": {
|
|
||||||
"modal": {
|
|
||||||
"new_domain_name": "New domain name",
|
|
||||||
"set": "Set a cookie",
|
|
||||||
"cookie_string": "Cookie string",
|
|
||||||
"enter_cookie_string": "Enter cookie string",
|
|
||||||
"cookie_name": "Name",
|
|
||||||
"cookie_value": "Value",
|
|
||||||
"cookie_path": "Path",
|
|
||||||
"cookie_expires": "Expires",
|
|
||||||
"managed_tab": "Managed",
|
|
||||||
"raw_tab": "Raw",
|
|
||||||
"interceptor_no_support": "Your currently selected interceptor does not support cookies. Select a different Interceptor and try again.",
|
|
||||||
"empty_domains": "Domain list is empty",
|
|
||||||
"empty_domain": "Domain is empty",
|
|
||||||
"no_cookies_in_domain": "No cookies set for this domain"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"app": {
|
"app": {
|
||||||
"chat_with_us": "Chat with us",
|
"chat_with_us": "Chat with us",
|
||||||
"contact_us": "Contact us",
|
"contact_us": "Contact us",
|
||||||
"cookies": "Cookies",
|
|
||||||
"copy": "Copy",
|
"copy": "Copy",
|
||||||
"copy_user_id": "Copy User Auth Token",
|
"copy_user_id": "Copy User Auth Token",
|
||||||
"developer_option": "Developer options",
|
"developer_option": "Developer options",
|
||||||
@@ -139,21 +119,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",
|
||||||
@@ -271,7 +237,6 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
"browser_support_sse": "This browser doesn't seems to have Server Sent Events support.",
|
||||||
"check_console_details": "Check console log for details.",
|
"check_console_details": "Check console log for details.",
|
||||||
"check_how_to_add_origin": "Check how you can add an origin",
|
|
||||||
"curl_invalid_format": "cURL is not formatted properly",
|
"curl_invalid_format": "cURL is not formatted properly",
|
||||||
"danger_zone": "Danger zone",
|
"danger_zone": "Danger zone",
|
||||||
"delete_account": "Your account is currently an owner in these teams:",
|
"delete_account": "Your account is currently an owner in these teams:",
|
||||||
@@ -292,7 +257,6 @@
|
|||||||
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
"no_environments_to_export": "No environments to export. Please create an environment to get started.",
|
||||||
"no_results_found": "No matches found",
|
"no_results_found": "No matches found",
|
||||||
"page_not_found": "This page could not be found",
|
"page_not_found": "This page could not be found",
|
||||||
"please_install_extension": "Please install the extension and add origin to the extension.",
|
|
||||||
"proxy_error": "Proxy error",
|
"proxy_error": "Proxy error",
|
||||||
"script_fail": "Could not execute pre-request script",
|
"script_fail": "Could not execute pre-request script",
|
||||||
"something_went_wrong": "Something went wrong",
|
"something_went_wrong": "Something went wrong",
|
||||||
@@ -497,8 +461,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 +563,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": {
|
||||||
@@ -802,7 +764,7 @@
|
|||||||
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
"published_error": "Something went wrong while publishing msg: {topic} to topic: {message}",
|
||||||
"published_message": "Published message: {message} to topic: {topic}",
|
"published_message": "Published message: {message} to topic: {topic}",
|
||||||
"reconnection_error": "Failed to reconnect",
|
"reconnection_error": "Failed to reconnect",
|
||||||
"show": "Show",
|
"show":"Show",
|
||||||
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
"subscribed_failed": "Failed to subscribe to topic: {topic}",
|
||||||
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
"subscribed_success": "Successfully subscribed to topic: {topic}",
|
||||||
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
"unsubscribed_failed": "Failed to unsubscribe from topic: {topic}",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@hoppscotch/common",
|
"name": "@hoppscotch/common",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "2023.8.4-1",
|
"version": "2023.8.2",
|
||||||
"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",
|
||||||
@@ -17,22 +17,22 @@
|
|||||||
"postinstall": "pnpm run gql-codegen",
|
"postinstall": "pnpm run gql-codegen",
|
||||||
"do-test": "pnpm run test",
|
"do-test": "pnpm run test",
|
||||||
"do-lint": "pnpm run prod-lint",
|
"do-lint": "pnpm run prod-lint",
|
||||||
"do-typecheck": "node type-check.mjs",
|
"do-typecheck": "pnpm run lint",
|
||||||
"do-lintfix": "pnpm run lintfix"
|
"do-lintfix": "pnpm run lintfix"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@apidevtools/swagger-parser": "^10.1.0",
|
"@apidevtools/swagger-parser": "^10.1.0",
|
||||||
"@codemirror/autocomplete": "^6.10.2",
|
"@codemirror/autocomplete": "^6.9.0",
|
||||||
"@codemirror/commands": "^6.3.0",
|
"@codemirror/commands": "^6.2.4",
|
||||||
"@codemirror/lang-javascript": "^6.2.1",
|
"@codemirror/lang-javascript": "^6.1.9",
|
||||||
"@codemirror/lang-json": "^6.0.1",
|
"@codemirror/lang-json": "^6.0.1",
|
||||||
"@codemirror/lang-xml": "^6.0.2",
|
"@codemirror/lang-xml": "^6.0.2",
|
||||||
"@codemirror/language": "6.9.0",
|
"@codemirror/language": "^6.9.0",
|
||||||
"@codemirror/legacy-modes": "^6.3.3",
|
"@codemirror/legacy-modes": "^6.3.3",
|
||||||
"@codemirror/lint": "^6.4.2",
|
"@codemirror/lint": "^6.4.0",
|
||||||
"@codemirror/search": "^6.5.4",
|
"@codemirror/search": "^6.5.1",
|
||||||
"@codemirror/state": "^6.3.1",
|
"@codemirror/state": "^6.2.1",
|
||||||
"@codemirror/view": "^6.22.0",
|
"@codemirror/view": "^6.16.0",
|
||||||
"@fontsource-variable/inter": "^5.0.8",
|
"@fontsource-variable/inter": "^5.0.8",
|
||||||
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
"@fontsource-variable/material-symbols-rounded": "^5.0.7",
|
||||||
"@fontsource-variable/roboto-mono": "^5.0.9",
|
"@fontsource-variable/roboto-mono": "^5.0.9",
|
||||||
@@ -41,7 +41,9 @@
|
|||||||
"@hoppscotch/js-sandbox": "workspace:^",
|
"@hoppscotch/js-sandbox": "workspace:^",
|
||||||
"@hoppscotch/ui": "workspace:^",
|
"@hoppscotch/ui": "workspace:^",
|
||||||
"@hoppscotch/vue-toasted": "^0.1.0",
|
"@hoppscotch/vue-toasted": "^0.1.0",
|
||||||
"@lezer/highlight": "1.1.4",
|
"@lezer/highlight": "^1.1.6",
|
||||||
|
"@sentry/tracing": "^7.64.0",
|
||||||
|
"@sentry/vue": "^7.64.0",
|
||||||
"@urql/core": "^4.1.1",
|
"@urql/core": "^4.1.1",
|
||||||
"@urql/devtools": "^2.0.3",
|
"@urql/devtools": "^2.0.3",
|
||||||
"@urql/exchange-auth": "^2.1.6",
|
"@urql/exchange-auth": "^2.1.6",
|
||||||
@@ -52,7 +54,6 @@
|
|||||||
"acorn-walk": "^8.2.0",
|
"acorn-walk": "^8.2.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"cookie-es": "^1.0.0",
|
|
||||||
"dioc": "workspace:^",
|
"dioc": "workspace:^",
|
||||||
"esprima": "^4.0.1",
|
"esprima": "^4.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
@@ -77,8 +78,6 @@
|
|||||||
"process": "^0.11.10",
|
"process": "^0.11.10",
|
||||||
"qs": "^6.11.2",
|
"qs": "^6.11.2",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"set-cookie-parser": "^2.6.0",
|
|
||||||
"set-cookie-parser-es": "^1.0.5",
|
|
||||||
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
"socket.io-client-v2": "npm:socket.io-client@^2.4.0",
|
||||||
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
"socket.io-client-v3": "npm:socket.io-client@^3.1.3",
|
||||||
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
"socket.io-client-v4": "npm:socket.io-client@^4.4.1",
|
||||||
@@ -92,7 +91,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",
|
||||||
@@ -102,8 +100,7 @@
|
|||||||
"wonka": "^6.3.4",
|
"wonka": "^6.3.4",
|
||||||
"workbox-window": "^7.0.0",
|
"workbox-window": "^7.0.0",
|
||||||
"xml-formatter": "^3.5.0",
|
"xml-formatter": "^3.5.0",
|
||||||
"yargs-parser": "^21.1.1",
|
"yargs-parser": "^21.1.1"
|
||||||
"zod": "^3.22.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
"@esbuild-plugins/node-globals-polyfill": "^0.2.3",
|
||||||
@@ -142,21 +139,20 @@
|
|||||||
"eslint": "^8.47.0",
|
"eslint": "^8.47.0",
|
||||||
"eslint-plugin-prettier": "^5.0.0",
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
"eslint-plugin-vue": "^9.17.0",
|
"eslint-plugin-vue": "^9.17.0",
|
||||||
"glob": "^10.3.10",
|
|
||||||
"npm-run-all": "^4.1.5",
|
"npm-run-all": "^4.1.5",
|
||||||
"openapi-types": "^12.1.3",
|
|
||||||
"postcss": "^8.4.23",
|
"postcss": "^8.4.23",
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
|
"tailwindcss": "^3.3.2",
|
||||||
|
"vite-plugin-fonts": "^0.6.0",
|
||||||
|
"openapi-types": "^12.1.3",
|
||||||
"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",
|
||||||
|
|||||||
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="32" fill="none"><rect width="156" height="32" fill="#000" rx="4"/><text xmlns="http://www.w3.org/2000/svg" x="50%" y="50%" fill="#fff" dominant-baseline="central" font-family="Helvetica,sans-serif" font-size="12" font-weight="bold" text-anchor="middle" text-rendering="geometricPrecision">▶ Run in Hoppscotch</text></svg>
|
|
||||||
|
Before Width: | Height: | Size: 386 B |
@@ -1 +0,0 @@
|
|||||||
<svg xmlns="http://www.w3.org/2000/svg" width="156" height="32" fill="none"><rect width="156" height="32" fill="#fff" rx="4"/><text xmlns="http://www.w3.org/2000/svg" x="50%" y="50%" fill="#000" dominant-baseline="central" font-family="Helvetica,sans-serif" font-size="12" font-weight="bold" text-anchor="middle" text-rendering="geometricPrecision">▶ Run in Hoppscotch</text></svg>
|
|
||||||
|
Before Width: | Height: | Size: 386 B |
|
Before Width: | Height: | Size: 354 KiB After Width: | Height: | Size: 926 KiB |
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 462 KiB After Width: | Height: | Size: 88 KiB |
|
Before Width: | Height: | Size: 11 KiB After Width: | Height: | Size: 4.6 KiB |
|
Before Width: | Height: | Size: 624 B After Width: | Height: | Size: 400 B |
|
Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 871 B |
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 360 KiB After Width: | Height: | Size: 510 KiB |
|
Before Width: | Height: | Size: 385 KiB After Width: | Height: | Size: 535 KiB |
|
Before Width: | Height: | Size: 178 KiB After Width: | Height: | Size: 60 KiB |
@@ -1,50 +1 @@
|
|||||||
<svg width="824" height="824" viewBox="0 0 824 824" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512" fill="none"><path fill="#10B981" d="M0 0h512v512H0z"/><circle cx="197.76" cy="157.84" r="10" fill="#fff" fill-opacity=".75"/><circle cx="259.76" cy="161.84" r="12" fill="#fff" fill-opacity=".75"/><circle cx="319.76" cy="177.84" r="10" fill="#fff" fill-opacity=".75"/><path d="M344.963 235.676c2.075-12.698-38.872-29.804-90.967-38.094-52.09-8.296-96.404-4.665-98.48 8.033-.257 1.035 0 1.812.263 2.853-1.298-.521-76.714 211.212-76.714 211.212H364.14s-17.621-181.414-20.211-181.414c.515-.772 1.035-1.549 1.035-2.59Z" fill="url(#a)"/><path d="M314.902 227.386c-1.298 8.033-30.839 9.845-66.343 4.402-35.247-5.7-62.982-16.843-61.684-24.618.521-2.59 3.888-4.665 9.331-5.7-18.141.777-30.062 4.145-31.096 9.845-1.555 10.628 34.726 25.139 81.373 32.657 46.647 7.512 85.782 4.665 87.594-5.7 1.041-6.226-9.33-12.961-26.431-19.439 4.923 2.847 7.513 5.957 7.256 8.553Z" fill="#A7F3D0" fill-opacity=".5"/><path d="M333.557 157.413c-3.104-32.137-27.729-59.351-60.9-64.53-33.172-5.186-64.531 12.954-77.749 42.238 21.251 1.298 44.057 3.631 67.904 7.518 25.396 3.888 49.237 9.074 70.745 14.774Z" fill="url(#b)"/><path d="M74.142 158.002c-2.59 15.808 30.319 35.247 81.894 51.055-.257-1.04-.257-1.818-.257-2.853 2.07-12.698 46.127-16.328 98.48-8.032 52.347 8.29 93.037 25.396 90.961 38.094-.257 1.04-.514 1.818-1.035 2.589 53.645.778 90.968-7.512 93.557-23.32 3.625-24.104-74.638-56.498-174.93-72.306-100.555-15.808-185.045-9.331-188.67 14.773Zm115.586-1.298c.778-4.145 4.665-7.255 8.81-6.477 4.145.777 7.256 4.665 6.478 8.81-.52 4.145-4.665 6.998-8.81 6.478-4.145-.778-7.255-4.666-6.478-8.811Zm59.866 4.145c.777-5.7 6.22-9.587 11.92-8.547 5.7.778 9.588 6.215 8.553 11.921-1.041 5.442-6.478 9.33-11.92 8.553-5.706-.778-9.594-6.221-8.553-11.927Zm62.975 15.294c.778-4.145 4.665-7.255 8.81-6.478 4.145.778 7.255 4.666 6.478 8.811-.515 4.145-4.665 7.255-8.81 6.477-4.145-.777-7.256-4.665-6.478-8.81Z" fill="url(#c)"/><defs><radialGradient id="b" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0 32.7063 -69.3245 0 264.232 124.706)"><stop stop-color="#047857"/><stop offset="1" stop-color="#064E3B"/></radialGradient><radialGradient id="c" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(255.837 186.754) scale(1389.61)"><stop stop-color="#047857"/><stop offset=".115" stop-color="#064E3B"/></radialGradient><linearGradient id="a" x1="224.998" y1="157.606" x2="224.998" y2="403.696" gradientUnits="userSpaceOnUse"><stop stop-color="#86EFAC" stop-opacity=".75"/><stop offset=".635" stop-color="#fff" stop-opacity=".2"/><stop offset="1" stop-color="#fff" stop-opacity="0"/></linearGradient></defs></svg>
|
||||||
<rect width="824" height="824" rx="184" fill="#08110F"/>
|
|
||||||
<rect width="824" height="824" rx="184" fill="url(#paint0_radial_0_21)" fill-opacity="0.5"/>
|
|
||||||
<path d="M435.425 463.217C429.441 476.657 411.033 481.515 394.309 474.07C377.585 466.624 368.879 449.693 374.863 436.253C380.846 422.813 399.254 417.954 415.978 425.4C432.702 432.846 441.409 449.777 435.425 463.217Z" fill="url(#paint1_linear_0_21)"/>
|
|
||||||
<path d="M435.425 463.217C429.441 476.657 411.033 481.515 394.309 474.07C377.585 466.624 368.879 449.693 374.863 436.253C380.846 422.813 399.254 417.954 415.978 425.4C432.702 432.846 441.409 449.777 435.425 463.217Z" fill="url(#paint2_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
|
||||||
<path d="M535.563 521.172C553.071 526.191 570.536 518.856 574.571 504.789C578.606 490.722 567.684 475.251 550.175 470.232C532.666 465.213 515.201 472.548 511.166 486.615C507.131 500.682 518.054 516.153 535.563 521.172Z" fill="url(#paint3_linear_0_21)"/>
|
|
||||||
<path d="M535.563 521.172C553.071 526.191 570.536 518.856 574.571 504.789C578.606 490.722 567.684 475.251 550.175 470.232C532.666 465.213 515.201 472.548 511.166 486.615C507.131 500.682 518.054 516.153 535.563 521.172Z" fill="url(#paint4_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
|
||||||
<path d="M292.782 355.633C308.227 365.286 314.462 383.173 306.709 395.584C298.955 407.995 280.149 410.231 264.704 400.578C249.258 390.924 243.023 373.037 250.777 360.626C258.53 348.215 277.337 345.98 292.782 355.633Z" fill="url(#paint5_linear_0_21)"/>
|
|
||||||
<path d="M292.782 355.633C308.227 365.286 314.462 383.173 306.709 395.584C298.955 407.995 280.149 410.231 264.704 400.578C249.258 390.924 243.023 373.037 250.777 360.626C258.53 348.215 277.337 345.98 292.782 355.633Z" fill="url(#paint6_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M502.355 231.325C581.373 266.506 632.095 343.263 634.119 429.03C680.633 465.639 726.858 516.883 705.36 565.168C681.25 619.319 595.382 617.091 497.781 589.689C450.767 615.718 392.444 620.168 339.689 596.68C286.934 573.192 251.229 526.908 239.1 474.517C153.428 420.321 94.3151 357.999 118.425 303.847C139.923 255.562 208.935 255.626 267.265 265.697C332.356 209.81 423.338 196.144 502.355 231.325ZM159.38 322.082C147.667 348.389 210.578 423.052 382.845 499.751C555.111 576.449 652.693 573.241 664.405 546.934C674.099 525.16 634.213 483.308 588.537 450.878C553.009 425.484 504.344 397.494 440.864 369.231C423.586 361.538 416.839 341.008 424.104 324.691C431.369 308.374 447.329 297.463 480.93 295.91C496.747 295.862 498.823 291.476 499.546 287.716C500.442 281.915 492.401 276.002 484.108 272.31C418.17 242.953 337.453 255.265 281.503 314.178C226.84 301.933 169.074 300.309 159.38 322.082Z" fill="url(#paint7_linear_0_21)"/>
|
|
||||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M502.355 231.325C581.373 266.506 632.095 343.263 634.119 429.03C680.633 465.639 726.858 516.883 705.36 565.168C681.25 619.319 595.382 617.091 497.781 589.689C450.767 615.718 392.444 620.168 339.689 596.68C286.934 573.192 251.229 526.908 239.1 474.517C153.428 420.321 94.3151 357.999 118.425 303.847C139.923 255.562 208.935 255.626 267.265 265.697C332.356 209.81 423.338 196.144 502.355 231.325ZM159.38 322.082C147.667 348.389 210.578 423.052 382.845 499.751C555.111 576.449 652.693 573.241 664.405 546.934C674.099 525.16 634.213 483.308 588.537 450.878C553.009 425.484 504.344 397.494 440.864 369.231C423.586 361.538 416.839 341.008 424.104 324.691C431.369 308.374 447.329 297.463 480.93 295.91C496.747 295.862 498.823 291.476 499.546 287.716C500.442 281.915 492.401 276.002 484.108 272.31C418.17 242.953 337.453 255.265 281.503 314.178C226.84 301.933 169.074 300.309 159.38 322.082Z" fill="url(#paint8_radial_0_21)" style="mix-blend-mode:soft-light"/>
|
|
||||||
<defs>
|
|
||||||
<radialGradient id="paint0_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(814.524 12.36) rotate(125.613) scale(1089.59 1210.34)">
|
|
||||||
<stop stop-color="#00D196" stop-opacity="0.5"/>
|
|
||||||
<stop offset="0.996771" stop-color="#00D196" stop-opacity="0"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="paint1_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#00D196"/>
|
|
||||||
<stop offset="1" stop-color="#00B381"/>
|
|
||||||
</linearGradient>
|
|
||||||
<radialGradient id="paint2_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="paint3_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#00D196"/>
|
|
||||||
<stop offset="1" stop-color="#00B381"/>
|
|
||||||
</linearGradient>
|
|
||||||
<radialGradient id="paint4_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="paint5_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#00D196"/>
|
|
||||||
<stop offset="1" stop-color="#00B381"/>
|
|
||||||
</linearGradient>
|
|
||||||
<radialGradient id="paint6_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</radialGradient>
|
|
||||||
<linearGradient id="paint7_linear_0_21" x1="411.893" y1="212" x2="411.893" y2="612" gradientUnits="userSpaceOnUse">
|
|
||||||
<stop stop-color="#00D196"/>
|
|
||||||
<stop offset="1" stop-color="#00B381"/>
|
|
||||||
</linearGradient>
|
|
||||||
<radialGradient id="paint8_radial_0_21" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(644.721 344.481) rotate(159.984) scale(631.37 385.135)">
|
|
||||||
<stop stop-color="white"/>
|
|
||||||
<stop offset="1" stop-color="white" stop-opacity="0"/>
|
|
||||||
</radialGradient>
|
|
||||||
</defs>
|
|
||||||
</svg>
|
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 5.9 KiB After Width: | Height: | Size: 2.6 KiB |
@@ -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']
|
||||||
@@ -59,8 +58,6 @@ declare module 'vue' {
|
|||||||
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
CollectionsRequest: typeof import('./components/collections/Request.vue')['default']
|
||||||
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
CollectionsSaveRequest: typeof import('./components/collections/SaveRequest.vue')['default']
|
||||||
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
CollectionsTeamCollections: typeof import('./components/collections/TeamCollections.vue')['default']
|
||||||
CookiesAllModal: typeof import('./components/cookies/AllModal.vue')['default']
|
|
||||||
CookiesEditCookie: typeof import('./components/cookies/EditCookie.vue')['default']
|
|
||||||
Environments: typeof import('./components/environments/index.vue')['default']
|
Environments: typeof import('./components/environments/index.vue')['default']
|
||||||
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
EnvironmentsAdd: typeof import('./components/environments/Add.vue')['default']
|
||||||
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
EnvironmentsImportExport: typeof import('./components/environments/ImportExport.vue')['default']
|
||||||
@@ -143,7 +140,6 @@ 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']
|
||||||
@@ -160,7 +156,7 @@ declare module 'vue' {
|
|||||||
IconLucideRss: typeof import('~icons/lucide/rss')['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']
|
IconLucideVerified: typeof import('~icons/lucide/verified')['default']
|
||||||
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
InterceptorsExtensionSubtitle: typeof import('./components/interceptors/ExtensionSubtitle.vue')['default']
|
||||||
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
LensesHeadersRenderer: typeof import('./components/lenses/HeadersRenderer.vue')['default']
|
||||||
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
LensesHeadersRendererEntry: typeof import('./components/lenses/HeadersRendererEntry.vue')['default']
|
||||||
@@ -206,7 +202,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']
|
||||||
|
|||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<template>
|
||||||
|
<div
|
||||||
|
class="group relative flex items-center bg-error px-4 py-2 text-tiny transition"
|
||||||
|
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>
|
||||||
@@ -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>
|
|
||||||
@@ -20,12 +20,6 @@
|
|||||||
<AppInterceptor />
|
<AppInterceptor />
|
||||||
</template>
|
</template>
|
||||||
</tippy>
|
</tippy>
|
||||||
<HoppButtonSecondary
|
|
||||||
v-if="platform.platformFeatureFlags.cookiesEnabled ?? false"
|
|
||||||
:label="t('app.cookies')"
|
|
||||||
:icon="IconCookie"
|
|
||||||
@click="showCookiesModal = true"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<tippy
|
<tippy
|
||||||
@@ -201,17 +195,12 @@
|
|||||||
:show="showDeveloperOptions"
|
:show="showDeveloperOptions"
|
||||||
@hide-modal="showDeveloperOptions = false"
|
@hide-modal="showDeveloperOptions = false"
|
||||||
/>
|
/>
|
||||||
<CookiesAllModal
|
|
||||||
:show="showCookiesModal"
|
|
||||||
@hide-modal="showCookiesModal = false"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from "vue"
|
import { ref } from "vue"
|
||||||
import { version } from "~/../package.json"
|
import { version } from "~/../package.json"
|
||||||
import IconCookie from "~icons/lucide/cookie"
|
|
||||||
import IconSidebar from "~icons/lucide/sidebar"
|
import IconSidebar from "~icons/lucide/sidebar"
|
||||||
import IconZap from "~icons/lucide/zap"
|
import IconZap from "~icons/lucide/zap"
|
||||||
import IconShare2 from "~icons/lucide/share-2"
|
import IconShare2 from "~icons/lucide/share-2"
|
||||||
@@ -234,9 +223,7 @@ import { invokeAction } from "@helpers/actions"
|
|||||||
import { HoppSmartItem } from "@hoppscotch/ui"
|
import { HoppSmartItem } from "@hoppscotch/ui"
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const showDeveloperOptions = ref(false)
|
const showDeveloperOptions = ref(false)
|
||||||
const showCookiesModal = ref(false)
|
|
||||||
|
|
||||||
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
const EXPAND_NAVIGATION = useSetting("EXPAND_NAVIGATION")
|
||||||
const SIDEBAR = useSetting("SIDEBAR")
|
const SIDEBAR = useSetting("SIDEBAR")
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<header
|
<header
|
||||||
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 flex-1 flex-shrink-0 items-center justify-between space-x-2 overflow-x-auto overflow-y-hidden px-2 py-2"
|
||||||
@mousedown.prevent="platform.ui?.appHeader?.onHeaderAreaClick?.()"
|
|
||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
class="inline-flex flex-1 items-center justify-start space-x-2"
|
class="inline-flex flex-1 items-center justify-start space-x-2"
|
||||||
@@ -217,7 +215,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 +264,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 +281,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(),
|
||||||
|
|||||||
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -258,7 +258,7 @@ const importFromJSON = () => {
|
|||||||
inputChooseFileToImportFrom.value.value = ""
|
inputChooseFileToImportFrom.value.value = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportJSON = async () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = collectionJson.value
|
const dataToWrite = collectionJson.value
|
||||||
|
|
||||||
const parsedCollections = JSON.parse(dataToWrite)
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
@@ -268,32 +268,24 @@ const exportJSON = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
|
|
||||||
const result = await platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/json",
|
|
||||||
suggestedFilename: filename,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "Hoppscotch Collection JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
|
||||||
platform?.analytics?.logEvent({
|
platform?.analytics?.logEvent({
|
||||||
type: "HOPP_EXPORT_COLLECTION",
|
type: "HOPP_EXPORT_COLLECTION",
|
||||||
exporter: "json",
|
exporter: "json",
|
||||||
platform: "gql",
|
platform: "gql",
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// TODO: get uri from meta
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
toast.success(t("state.download_started").toString())
|
toast.success(t("state.download_started").toString())
|
||||||
}
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -18,12 +18,13 @@
|
|||||||
"
|
"
|
||||||
>
|
>
|
||||||
<WorkspaceCurrent :section="t('tab.collections')" />
|
<WorkspaceCurrent :section="t('tab.collections')" />
|
||||||
<input
|
|
||||||
|
<HoppSmartInput
|
||||||
v-model="filterTexts"
|
v-model="filterTexts"
|
||||||
type="search"
|
|
||||||
autocomplete="off"
|
|
||||||
class="flex h-8 w-full bg-transparent p-4 py-2"
|
|
||||||
:placeholder="t('action.search')"
|
:placeholder="t('action.search')"
|
||||||
|
input-styles="py-2 pl-4 pr-2 bg-transparent !border-0"
|
||||||
|
type="search"
|
||||||
|
:autofocus="false"
|
||||||
:disabled="collectionsType.type === 'team-collections'"
|
:disabled="collectionsType.type === 'team-collections'"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -1866,25 +1867,28 @@ const getJSONCollection = async () => {
|
|||||||
* @param collectionJSON - JSON string of the collection
|
* @param collectionJSON - JSON string of the collection
|
||||||
* @param name - Name of the collection set as the file name
|
* @param name - Name of the collection set as the file name
|
||||||
*/
|
*/
|
||||||
const initializeDownloadCollection = async (
|
const initializeDownloadCollection = (
|
||||||
collectionJSON: string,
|
collectionJSON: string,
|
||||||
name: string | null
|
name: string | null
|
||||||
) => {
|
) => {
|
||||||
const result = await platform.io.saveFileWithDialog({
|
const file = new Blob([collectionJSON], { type: "application/json" })
|
||||||
data: collectionJSON,
|
const a = document.createElement("a")
|
||||||
contentType: "application/json",
|
const url = URL.createObjectURL(file)
|
||||||
suggestedFilename: `${name ?? "collection"}.json`,
|
a.href = url
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "Hoppscotch Collection JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
if (name) {
|
||||||
toast.success(t("state.download_started").toString())
|
a.download = `${name}.json`
|
||||||
|
} else {
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
|
toast.success(t("state.download_started").toString())
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -1913,14 +1917,11 @@ const exportData = async (
|
|||||||
exportLoading.value = false
|
exportLoading.value = false
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
async (coll) => {
|
(coll) => {
|
||||||
const hoppColl = teamCollToHoppRESTColl(coll)
|
const hoppColl = teamCollToHoppRESTColl(coll)
|
||||||
const collectionJSONString = JSON.stringify(hoppColl)
|
const collectionJSONString = JSON.stringify(hoppColl)
|
||||||
|
|
||||||
await initializeDownloadCollection(
|
initializeDownloadCollection(collectionJSONString, hoppColl.name)
|
||||||
collectionJSONString,
|
|
||||||
hoppColl.name
|
|
||||||
)
|
|
||||||
exportLoading.value = false
|
exportLoading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,269 +0,0 @@
|
|||||||
<template>
|
|
||||||
<HoppSmartModal
|
|
||||||
v-if="show"
|
|
||||||
dialog
|
|
||||||
:title="t('app.cookies')"
|
|
||||||
aria-modal="true"
|
|
||||||
@close="hideModal"
|
|
||||||
>
|
|
||||||
<template #body>
|
|
||||||
<HoppSmartPlaceholder
|
|
||||||
v-if="!currentInterceptorSupportsCookies"
|
|
||||||
:text="t('cookies.modal.interceptor_no_support')"
|
|
||||||
>
|
|
||||||
<AppInterceptor class="rounded border border-dividerLight p-2" />
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div v-else class="flex flex-col">
|
|
||||||
<div
|
|
||||||
class="sticky -mx-4 -mt-4 flex space-x-2 border-b border-dividerLight bg-primary px-4 py-4"
|
|
||||||
style="top: calc(-1 * var(--line-height-body))"
|
|
||||||
>
|
|
||||||
<HoppSmartInput
|
|
||||||
v-model="newDomainText"
|
|
||||||
class="flex-1"
|
|
||||||
:placeholder="t('cookies.modal.new_domain_name')"
|
|
||||||
@keyup.enter="addNewDomain"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
outline
|
|
||||||
filled
|
|
||||||
:label="t('action.add')"
|
|
||||||
@click="addNewDomain"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-4">
|
|
||||||
<HoppSmartPlaceholder
|
|
||||||
v-if="workingCookieJar.size === 0"
|
|
||||||
:src="`/images/states/${colorMode.value}/blockchain.svg`"
|
|
||||||
:alt="`${t('cookies.modal.empty_domains')}`"
|
|
||||||
:text="t('cookies.modal.empty_domains')"
|
|
||||||
class="mt-6"
|
|
||||||
>
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
<div
|
|
||||||
v-for="[domain, entries] in workingCookieJar.entries()"
|
|
||||||
v-else
|
|
||||||
:key="domain"
|
|
||||||
class="flex flex-col"
|
|
||||||
>
|
|
||||||
<div class="flex flex-1 items-center justify-between">
|
|
||||||
<label for="cookiesList" class="p-4">
|
|
||||||
{{ domain }}
|
|
||||||
</label>
|
|
||||||
<div class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.delete')"
|
|
||||||
:icon="IconTrash2"
|
|
||||||
@click="deleteDomain(domain)"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('add.new')"
|
|
||||||
:icon="IconPlus"
|
|
||||||
@click="addCookieToDomain(domain)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="rounded border border-divider">
|
|
||||||
<div class="divide-y divide-dividerLight">
|
|
||||||
<div
|
|
||||||
v-if="entries.length === 0"
|
|
||||||
class="flex flex-col items-center gap-2 p-4"
|
|
||||||
>
|
|
||||||
{{ t("cookies.modal.no_cookies_in_domain") }}
|
|
||||||
</div>
|
|
||||||
<template v-else>
|
|
||||||
<div
|
|
||||||
v-for="(entry, entryIndex) in entries"
|
|
||||||
:key="`${entry}-${entryIndex}`"
|
|
||||||
class="flex divide-x divide-dividerLight"
|
|
||||||
>
|
|
||||||
<input
|
|
||||||
class="flex flex-1 bg-transparent px-4 py-2"
|
|
||||||
:value="entry"
|
|
||||||
readonly
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.edit')"
|
|
||||||
:icon="IconEdit"
|
|
||||||
@click="editCookie(domain, entryIndex, entry)"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.remove')"
|
|
||||||
:icon="IconTrash"
|
|
||||||
color="red"
|
|
||||||
@click="deleteCookie(domain, entryIndex)"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template v-if="currentInterceptorSupportsCookies" #footer>
|
|
||||||
<span class="flex space-x-2">
|
|
||||||
<HoppButtonPrimary
|
|
||||||
v-focus
|
|
||||||
:label="t('action.save')"
|
|
||||||
outline
|
|
||||||
@click="saveCookieChanges"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('action.cancel')"
|
|
||||||
outline
|
|
||||||
filled
|
|
||||||
@click="cancelCookieChanges"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('action.clear_all')"
|
|
||||||
outline
|
|
||||||
filled
|
|
||||||
@click="clearAllDomains"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</HoppSmartModal>
|
|
||||||
<CookiesEditCookie
|
|
||||||
:show="!!showEditModalFor"
|
|
||||||
:entry="showEditModalFor"
|
|
||||||
@save-cookie="saveCookie"
|
|
||||||
@hide-modal="showEditModalFor = null"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { CookieJarService } from "~/services/cookie-jar.service"
|
|
||||||
import IconTrash from "~icons/lucide/trash"
|
|
||||||
import IconEdit from "~icons/lucide/edit"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
import IconPlus from "~icons/lucide/plus"
|
|
||||||
import { cloneDeep } from "lodash-es"
|
|
||||||
import { ref, watch, computed } from "vue"
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
|
||||||
import { EditCookieConfig } from "./EditCookie.vue"
|
|
||||||
import { useColorMode } from "@composables/theming"
|
|
||||||
import { useToast } from "@composables/toast"
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
show: boolean
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "hide-modal"): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
const colorMode = useColorMode()
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const newDomainText = ref("")
|
|
||||||
|
|
||||||
const interceptorService = useService(InterceptorService)
|
|
||||||
const cookieJarService = useService(CookieJarService)
|
|
||||||
|
|
||||||
const workingCookieJar = ref(cloneDeep(cookieJarService.cookieJar.value))
|
|
||||||
|
|
||||||
const currentInterceptorSupportsCookies = computed(() => {
|
|
||||||
const currentInterceptor = interceptorService.currentInterceptor.value
|
|
||||||
|
|
||||||
if (!currentInterceptor) return true
|
|
||||||
|
|
||||||
return currentInterceptor.supportsCookies ?? false
|
|
||||||
})
|
|
||||||
|
|
||||||
function addNewDomain() {
|
|
||||||
if (newDomainText.value === "" || /^\s+$/.test(newDomainText.value)) {
|
|
||||||
toast.error(`${t("cookies.modal.empty_domain")}`)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
workingCookieJar.value.set(newDomainText.value, [])
|
|
||||||
newDomainText.value = ""
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteDomain(domain: string) {
|
|
||||||
workingCookieJar.value.delete(domain)
|
|
||||||
}
|
|
||||||
|
|
||||||
function addCookieToDomain(domain: string) {
|
|
||||||
showEditModalFor.value = { type: "create", domain }
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearAllDomains() {
|
|
||||||
workingCookieJar.value = new Map()
|
|
||||||
toast.success(`${t("state.cleared")}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.show,
|
|
||||||
(show) => {
|
|
||||||
if (show) {
|
|
||||||
workingCookieJar.value = cloneDeep(cookieJarService.cookieJar.value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
const showEditModalFor = ref<EditCookieConfig | null>(null)
|
|
||||||
|
|
||||||
function saveCookieChanges() {
|
|
||||||
cookieJarService.cookieJar.value = workingCookieJar.value
|
|
||||||
hideModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelCookieChanges() {
|
|
||||||
hideModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
function editCookie(domain: string, entryIndex: number, cookieEntry: string) {
|
|
||||||
showEditModalFor.value = {
|
|
||||||
type: "edit",
|
|
||||||
domain,
|
|
||||||
entryIndex,
|
|
||||||
currentCookieEntry: cookieEntry,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function deleteCookie(domain: string, entryIndex: number) {
|
|
||||||
const entry = workingCookieJar.value.get(domain)
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
entry.splice(entryIndex, 1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveCookie(cookie: string) {
|
|
||||||
if (showEditModalFor.value?.type === "create") {
|
|
||||||
const { domain } = showEditModalFor.value
|
|
||||||
|
|
||||||
const entry = workingCookieJar.value.get(domain)!
|
|
||||||
entry.push(cookie)
|
|
||||||
|
|
||||||
showEditModalFor.value = null
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showEditModalFor.value?.type !== "edit") return
|
|
||||||
|
|
||||||
const { domain, entryIndex } = showEditModalFor.value!
|
|
||||||
|
|
||||||
const entry = workingCookieJar.value.get(domain)
|
|
||||||
|
|
||||||
if (entry) {
|
|
||||||
entry[entryIndex] = cookie
|
|
||||||
}
|
|
||||||
|
|
||||||
showEditModalFor.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
const hideModal = () => {
|
|
||||||
emit("hide-modal")
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -1,195 +0,0 @@
|
|||||||
<template>
|
|
||||||
<HoppSmartModal
|
|
||||||
v-if="show"
|
|
||||||
dialog
|
|
||||||
:title="t('cookies.modal.set')"
|
|
||||||
@close="hideModal"
|
|
||||||
>
|
|
||||||
<template #body>
|
|
||||||
<div class="rounded border border-dividerLight">
|
|
||||||
<div class="flex flex-col">
|
|
||||||
<div class="flex items-center justify-between pl-4">
|
|
||||||
<label class="truncate font-semibold text-secondaryLight">
|
|
||||||
{{ t("cookies.modal.cookie_string") }}
|
|
||||||
</label>
|
|
||||||
<div class="flex items-center">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('action.clear_all')"
|
|
||||||
:icon="IconTrash2"
|
|
||||||
@click="clearContent()"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
|
||||||
:title="t('state.linewrap')"
|
|
||||||
:class="{ '!text-accent': linewrapEnabled }"
|
|
||||||
:icon="IconWrapText"
|
|
||||||
@click.prevent="linewrapEnabled = !linewrapEnabled"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
|
||||||
:title="t('action.download_file')"
|
|
||||||
:icon="downloadIcon"
|
|
||||||
@click="downloadResponse"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
v-tippy="{ theme: 'tooltip', allowHTML: true }"
|
|
||||||
:title="t('action.copy')"
|
|
||||||
:icon="copyIcon"
|
|
||||||
@click="copyResponse"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="h-46">
|
|
||||||
<div
|
|
||||||
ref="cookieEditor"
|
|
||||||
class="h-full rounded-b border-t border-dividerLight"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #footer>
|
|
||||||
<div class="flex space-x-2">
|
|
||||||
<HoppButtonPrimary
|
|
||||||
v-focus
|
|
||||||
:label="t('action.save')"
|
|
||||||
outline
|
|
||||||
@click="saveCookieChange"
|
|
||||||
/>
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:label="t('action.cancel')"
|
|
||||||
outline
|
|
||||||
filled
|
|
||||||
@click="cancelCookieChange"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<span class="flex">
|
|
||||||
<HoppButtonSecondary
|
|
||||||
:icon="pasteIcon"
|
|
||||||
:label="`${t('action.paste')}`"
|
|
||||||
filled
|
|
||||||
outline
|
|
||||||
@click="handlePaste"
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</template>
|
|
||||||
</HoppSmartModal>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts">
|
|
||||||
export type EditCookieConfig =
|
|
||||||
| { type: "create"; domain: string }
|
|
||||||
| {
|
|
||||||
type: "edit"
|
|
||||||
domain: string
|
|
||||||
entryIndex: number
|
|
||||||
currentCookieEntry: string
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { useCodemirror } from "~/composables/codemirror"
|
|
||||||
import { watch, ref, reactive } from "vue"
|
|
||||||
import { refAutoReset } from "@vueuse/core"
|
|
||||||
import IconWrapText from "~icons/lucide/wrap-text"
|
|
||||||
import IconClipboard from "~icons/lucide/clipboard"
|
|
||||||
import IconCheck from "~icons/lucide/check"
|
|
||||||
import IconTrash2 from "~icons/lucide/trash-2"
|
|
||||||
import { useToast } from "~/composables/toast"
|
|
||||||
import {
|
|
||||||
useCopyResponse,
|
|
||||||
useDownloadResponse,
|
|
||||||
} from "~/composables/lens-actions"
|
|
||||||
|
|
||||||
// TODO: Build Managed Mode!
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
show: boolean
|
|
||||||
|
|
||||||
entry: EditCookieConfig | null
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: "save-cookie", cookie: string): void
|
|
||||||
(e: "hide-modal"): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
const toast = useToast()
|
|
||||||
|
|
||||||
const cookieEditor = ref<HTMLElement>()
|
|
||||||
const rawCookieString = ref("")
|
|
||||||
const linewrapEnabled = ref(true)
|
|
||||||
|
|
||||||
useCodemirror(
|
|
||||||
cookieEditor,
|
|
||||||
rawCookieString,
|
|
||||||
reactive({
|
|
||||||
extendedEditorConfig: {
|
|
||||||
mode: "text/plain",
|
|
||||||
placeholder: `${t("cookies.modal.enter_cookie_string")}`,
|
|
||||||
lineWrapping: linewrapEnabled,
|
|
||||||
},
|
|
||||||
linter: null,
|
|
||||||
completer: null,
|
|
||||||
environmentHighlights: false,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
|
|
||||||
const pasteIcon = refAutoReset<typeof IconClipboard | typeof IconCheck>(
|
|
||||||
IconClipboard,
|
|
||||||
1000
|
|
||||||
)
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.entry,
|
|
||||||
() => {
|
|
||||||
if (!props.entry) return
|
|
||||||
|
|
||||||
if (props.entry.type === "create") {
|
|
||||||
rawCookieString.value = ""
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
rawCookieString.value = props.entry.currentCookieEntry
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function hideModal() {
|
|
||||||
emit("hide-modal")
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancelCookieChange() {
|
|
||||||
hideModal()
|
|
||||||
}
|
|
||||||
|
|
||||||
async function handlePaste() {
|
|
||||||
try {
|
|
||||||
const text = await navigator.clipboard.readText()
|
|
||||||
if (text) {
|
|
||||||
rawCookieString.value = text
|
|
||||||
pasteIcon.value = IconCheck
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to copy: ", e)
|
|
||||||
toast.error(t("profile.no_permission").toString())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function saveCookieChange() {
|
|
||||||
emit("save-cookie", rawCookieString.value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const { copyIcon, copyResponse } = useCopyResponse(rawCookieString)
|
|
||||||
const { downloadIcon, downloadResponse } = useDownloadResponse(
|
|
||||||
"",
|
|
||||||
rawCookieString
|
|
||||||
)
|
|
||||||
|
|
||||||
function clearContent() {
|
|
||||||
rawCookieString.value = ""
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
@@ -375,7 +375,7 @@ const importFromPostman = ({
|
|||||||
importFromHoppscotch(environments)
|
importFromHoppscotch(environments)
|
||||||
}
|
}
|
||||||
|
|
||||||
const exportJSON = async () => {
|
const exportJSON = () => {
|
||||||
const dataToWrite = environmentJson.value
|
const dataToWrite = environmentJson.value
|
||||||
|
|
||||||
const parsedCollections = JSON.parse(dataToWrite)
|
const parsedCollections = JSON.parse(dataToWrite)
|
||||||
@@ -385,27 +385,19 @@ const exportJSON = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
// TODO: get uri from meta
|
||||||
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
URL.revokeObjectURL(url)
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
const result = await platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/json",
|
|
||||||
suggestedFilename: filename,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
|
||||||
toast.success(t("state.download_started").toString())
|
toast.success(t("state.download_started").toString())
|
||||||
}
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const getErrorMessage = (err: GQLError<string>) => {
|
const getErrorMessage = (err: GQLError<string>) => {
|
||||||
|
|||||||
@@ -66,7 +66,7 @@
|
|||||||
/>
|
/>
|
||||||
<HoppSmartTabs
|
<HoppSmartTabs
|
||||||
v-model="selectedEnvTab"
|
v-model="selectedEnvTab"
|
||||||
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-10 top-0 bg-primary ${
|
:styles="`sticky overflow-x-auto my-2 border border-divider rounded flex-shrink-0 z-0 top-0 bg-primary ${
|
||||||
!isTeamSelected || workspace.type === 'personal'
|
!isTeamSelected || workspace.type === 'personal'
|
||||||
? 'bg-primaryLight'
|
? 'bg-primaryLight'
|
||||||
: ''
|
: ''
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex justify-between gap-2">
|
<div class="field-title" :class="{ 'field-highlighted': isHighlighted }">
|
||||||
<div
|
|
||||||
class="field-title flex-1"
|
|
||||||
:class="{ 'field-highlighted': isHighlighted }"
|
|
||||||
>
|
|
||||||
{{ fieldName }}
|
{{ fieldName }}
|
||||||
<span v-if="fieldArgs.length > 0">
|
<span v-if="fieldArgs.length > 0">
|
||||||
(
|
(
|
||||||
@@ -12,23 +8,16 @@
|
|||||||
{{ field.name }}:
|
{{ field.name }}:
|
||||||
<GraphqlTypeLink
|
<GraphqlTypeLink
|
||||||
:gql-type="field.type"
|
:gql-type="field.type"
|
||||||
@jump-to-type="jumpToType"
|
:jump-type-callback="jumpTypeCallback"
|
||||||
/>
|
/>
|
||||||
<span v-if="index !== fieldArgs.length - 1">, </span>
|
<span v-if="index !== fieldArgs.length - 1">, </span>
|
||||||
</span>
|
</span>
|
||||||
) </span
|
) </span
|
||||||
>:
|
>:
|
||||||
<GraphqlTypeLink :gql-type="gqlField.type" @jump-to-type="jumpToType" />
|
<GraphqlTypeLink
|
||||||
</div>
|
:gql-type="gqlField.type"
|
||||||
<div v-if="gqlField.deprecationReason">
|
:jump-type-callback="jumpTypeCallback"
|
||||||
<span
|
/>
|
||||||
v-tippy="{ theme: 'tomato' }"
|
|
||||||
class="flex cursor-pointer items-center gap-2 text-xs !text-red-500 hover:!text-red-600"
|
|
||||||
:title="gqlField.deprecationReason"
|
|
||||||
>
|
|
||||||
<IconAlertTriangle /> {{ t("state.deprecated") }}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
v-if="gqlField.description"
|
v-if="gqlField.description"
|
||||||
@@ -36,6 +25,12 @@
|
|||||||
>
|
>
|
||||||
{{ gqlField.description }}
|
{{ gqlField.description }}
|
||||||
</div>
|
</div>
|
||||||
|
<div
|
||||||
|
v-if="gqlField.isDeprecated"
|
||||||
|
class="field-deprecated my-1 inline-block rounded bg-yellow-200 px-2 py-1 text-black"
|
||||||
|
>
|
||||||
|
{{ t("state.deprecated") }}
|
||||||
|
</div>
|
||||||
<div v-if="fieldArgs.length > 0">
|
<div v-if="fieldArgs.length > 0">
|
||||||
<h5 class="my-2">Arguments:</h5>
|
<h5 class="my-2">Arguments:</h5>
|
||||||
<div class="border-l-2 border-divider pl-4">
|
<div class="border-l-2 border-divider pl-4">
|
||||||
@@ -44,7 +39,7 @@
|
|||||||
{{ field.name }}:
|
{{ field.name }}:
|
||||||
<GraphqlTypeLink
|
<GraphqlTypeLink
|
||||||
:gql-type="field.type"
|
:gql-type="field.type"
|
||||||
@jump-to-type="jumpToType"
|
:jump-type-callback="jumpTypeCallback"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
<div
|
<div
|
||||||
@@ -59,36 +54,32 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script>
|
||||||
|
// TypeScript + Script Setup this :)
|
||||||
|
import { defineComponent } from "vue"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { GraphQLType } from "graphql"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import IconAlertTriangle from "~icons/lucide/alert-triangle"
|
|
||||||
|
|
||||||
const t = useI18n()
|
export default defineComponent({
|
||||||
|
props: {
|
||||||
const props = withDefaults(
|
gqlField: { type: Object, default: () => ({}) },
|
||||||
defineProps<{
|
jumpTypeCallback: { type: Function, default: () => ({}) },
|
||||||
gqlField: any
|
isHighlighted: { type: Boolean, default: false },
|
||||||
isHighlighted: boolean
|
},
|
||||||
}>(),
|
setup() {
|
||||||
{
|
return {
|
||||||
gqlField: {},
|
t: useI18n(),
|
||||||
isHighlighted: false,
|
|
||||||
}
|
}
|
||||||
)
|
},
|
||||||
|
computed: {
|
||||||
|
fieldName() {
|
||||||
|
return this.gqlField.name
|
||||||
|
},
|
||||||
|
|
||||||
const emit = defineEmits<{
|
fieldArgs() {
|
||||||
(e: "jump-to-type", type: GraphQLType): void
|
return this.gqlField.args || []
|
||||||
}>()
|
},
|
||||||
|
},
|
||||||
const fieldName = computed(() => props.gqlField.name)
|
})
|
||||||
|
|
||||||
const fieldArgs = computed(() => props.gqlField.args || [])
|
|
||||||
|
|
||||||
const jumpToType = (type: GraphQLType) => {
|
|
||||||
emit("jump-to-type", type)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
@@ -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 !cursor-grab group-hover:text-secondaryLight':
|
||||||
index !== workingHeaders?.length - 1,
|
index !== workingHeaders?.length - 1,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
|||||||
@@ -51,7 +51,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { completePageProgress, startPageProgress } from "~/modules/loadingbar"
|
import { completePageProgress, startPageProgress } from "@modules/loadingbar"
|
||||||
import * as gql from "graphql"
|
import * as gql from "graphql"
|
||||||
import { clone } from "lodash-es"
|
import { clone } from "lodash-es"
|
||||||
import { computed, ref, watch } from "vue"
|
import { computed, ref, watch } from "vue"
|
||||||
@@ -63,7 +63,6 @@ import {
|
|||||||
GQLResponseEvent,
|
GQLResponseEvent,
|
||||||
runGQLOperation,
|
runGQLOperation,
|
||||||
gqlMessageEvent,
|
gqlMessageEvent,
|
||||||
connection,
|
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
import { useService } from "dioc/vue"
|
import { useService } from "dioc/vue"
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
import { InterceptorService } from "~/services/interceptor.service"
|
||||||
@@ -153,7 +152,13 @@ const runQuery = async (
|
|||||||
toast.success(t("authorization.graphql_headers"))
|
toast.success(t("authorization.graphql_headers"))
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
|
console.log(e)
|
||||||
|
// response.value = [`${e}`]
|
||||||
completePageProgress()
|
completePageProgress()
|
||||||
|
toast.error(
|
||||||
|
`${t("error.something_went_wrong")}. ${t("error.check_console_details")}`,
|
||||||
|
{}
|
||||||
|
)
|
||||||
console.error(e)
|
console.error(e)
|
||||||
}
|
}
|
||||||
platform.analytics?.logEvent({
|
platform.analytics?.logEvent({
|
||||||
@@ -172,10 +177,7 @@ watch(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (
|
if (event?.operationType !== "subscription") {
|
||||||
event?.type === "response" &&
|
|
||||||
event?.operationType !== "subscription"
|
|
||||||
) {
|
|
||||||
// response.value = [event]
|
// response.value = [event]
|
||||||
emit("update:response", [event])
|
emit("update:response", [event])
|
||||||
} else {
|
} else {
|
||||||
@@ -190,26 +192,6 @@ watch(
|
|||||||
{ deep: true }
|
{ deep: true }
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(
|
|
||||||
() => connection,
|
|
||||||
(newVal) => {
|
|
||||||
if (newVal.error && newVal.state === "DISCONNECTED") {
|
|
||||||
const response = [
|
|
||||||
{
|
|
||||||
type: "error",
|
|
||||||
error: {
|
|
||||||
message: newVal.error.message(t),
|
|
||||||
type: newVal.error.type,
|
|
||||||
component: newVal.error.component,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
emit("update:response", response)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ deep: true }
|
|
||||||
)
|
|
||||||
|
|
||||||
const hideRequestModal = () => {
|
const hideRequestModal = () => {
|
||||||
showSaveRequestModal.value = false
|
showSaveRequestModal.value = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-1 flex-col overflow-auto whitespace-nowrap">
|
<div class="flex flex-1 flex-col overflow-auto whitespace-nowrap">
|
||||||
<div
|
<div v-if="response?.length === 1" class="flex flex-1 flex-col">
|
||||||
v-if="
|
|
||||||
response && response.length === 1 && response[0].type === 'response'
|
|
||||||
"
|
|
||||||
class="flex flex-1 flex-col"
|
|
||||||
>
|
|
||||||
<div
|
<div
|
||||||
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
class="sticky top-0 z-10 flex flex-shrink-0 items-center justify-between overflow-x-auto border-b border-dividerLight bg-primary pl-4"
|
||||||
>
|
>
|
||||||
@@ -40,13 +35,6 @@
|
|||||||
</div>
|
</div>
|
||||||
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
<div ref="schemaEditor" class="flex flex-1 flex-col"></div>
|
||||||
</div>
|
</div>
|
||||||
<component
|
|
||||||
:is="response[0].error.component"
|
|
||||||
v-else-if="
|
|
||||||
response && response[0].type === 'error' && response[0].error.component
|
|
||||||
"
|
|
||||||
class="flex-1"
|
|
||||||
/>
|
|
||||||
<div
|
<div
|
||||||
v-else-if="response && response?.length > 1"
|
v-else-if="response && response?.length > 1"
|
||||||
class="flex flex-1 flex-col"
|
class="flex flex-1 flex-col"
|
||||||
@@ -71,7 +59,6 @@ import { useToast } from "@composables/toast"
|
|||||||
import { defineActionHandler } from "~/helpers/actions"
|
import { defineActionHandler } from "~/helpers/actions"
|
||||||
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
import { getPlatformSpecialKey as getSpecialKey } from "~/helpers/platformutils"
|
||||||
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
import { GQLResponseEvent } from "~/helpers/graphql/connection"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -86,16 +73,8 @@ const props = withDefaults(
|
|||||||
)
|
)
|
||||||
|
|
||||||
const responseString = computed(() => {
|
const responseString = computed(() => {
|
||||||
const response = props.response
|
if (props.response?.length === 1) {
|
||||||
if (response && response[0].type === "error") {
|
return JSON.stringify(JSON.parse(props.response[0].data), null, 2)
|
||||||
return ""
|
|
||||||
} else if (
|
|
||||||
response &&
|
|
||||||
response.length === 1 &&
|
|
||||||
response[0].type === "response" &&
|
|
||||||
response[0].data
|
|
||||||
) {
|
|
||||||
return JSON.stringify(JSON.parse(response[0].data), null, 2)
|
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
})
|
})
|
||||||
@@ -132,31 +111,21 @@ const copyResponse = (str: string) => {
|
|||||||
toast.success(`${t("state.copied_to_clipboard")}`)
|
toast.success(`${t("state.copied_to_clipboard")}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadResponse = async (str: string) => {
|
const downloadResponse = (str: string) => {
|
||||||
const dataToWrite = str
|
const dataToWrite = str
|
||||||
const file = new Blob([dataToWrite!], { type: "application/json" })
|
const file = new Blob([dataToWrite!], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
const filename = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}`
|
||||||
|
document.body.appendChild(a)
|
||||||
URL.revokeObjectURL(url)
|
a.click()
|
||||||
|
|
||||||
const result = await platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/json",
|
|
||||||
suggestedFilename: filename,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "JSON file",
|
|
||||||
extensions: ["json"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
|
||||||
downloadResponseIcon.value = IconCheck
|
downloadResponseIcon.value = IconCheck
|
||||||
toast.success(`${t("state.download_started")}`)
|
toast.success(`${t("state.download_started")}`)
|
||||||
}
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
defineActionHandler(
|
defineActionHandler(
|
||||||
|
|||||||
@@ -30,8 +30,8 @@
|
|||||||
v-model="graphqlFieldsFilterText"
|
v-model="graphqlFieldsFilterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex h-8 w-full bg-transparent p-4 py-2"
|
|
||||||
:placeholder="`${t('action.search')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
|
class="flex flex-1 bg-transparent p-4 py-2"
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
@@ -58,8 +58,8 @@
|
|||||||
v-for="(field, index) in filteredQueryFields"
|
v-for="(field, index) in filteredQueryFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
|
:jump-type-callback="handleJumpToType"
|
||||||
class="p-4"
|
class="p-4"
|
||||||
@jump-to-type="handleJumpToType"
|
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -72,8 +72,8 @@
|
|||||||
v-for="(field, index) in filteredMutationFields"
|
v-for="(field, index) in filteredMutationFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
|
:jump-type-callback="handleJumpToType"
|
||||||
class="p-4"
|
class="p-4"
|
||||||
@jump-to-type="handleJumpToType"
|
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -86,8 +86,8 @@
|
|||||||
v-for="(field, index) in filteredSubscriptionFields"
|
v-for="(field, index) in filteredSubscriptionFields"
|
||||||
:key="`field-${index}`"
|
:key="`field-${index}`"
|
||||||
:gql-field="field"
|
:gql-field="field"
|
||||||
|
:jump-type-callback="handleJumpToType"
|
||||||
class="p-4"
|
class="p-4"
|
||||||
@jump-to-type="handleJumpToType"
|
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
<HoppSmartTab
|
<HoppSmartTab
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
:gql-types="graphqlTypes"
|
:gql-types="graphqlTypes"
|
||||||
:is-highlighted="isGqlTypeHighlighted(type)"
|
:is-highlighted="isGqlTypeHighlighted(type)"
|
||||||
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
:highlighted-fields="getGqlTypeHighlightedFields(type)"
|
||||||
@jump-to-type="handleJumpToType"
|
:jump-type-callback="handleJumpToType"
|
||||||
/>
|
/>
|
||||||
</HoppSmartTab>
|
</HoppSmartTab>
|
||||||
</HoppSmartTabs>
|
</HoppSmartTabs>
|
||||||
@@ -202,7 +202,6 @@ import {
|
|||||||
schemaString,
|
schemaString,
|
||||||
subscriptionFields,
|
subscriptionFields,
|
||||||
} from "~/helpers/graphql/connection"
|
} from "~/helpers/graphql/connection"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
type NavigationTabs = "history" | "collection" | "docs" | "schema"
|
||||||
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
type GqlTabs = "queries" | "mutations" | "subscriptions" | "types"
|
||||||
@@ -373,33 +372,21 @@ useCodemirror(
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
const downloadSchema = async () => {
|
const downloadSchema = () => {
|
||||||
const dataToWrite = schemaString.value
|
const dataToWrite = JSON.stringify(schemaString.value, null, 2)
|
||||||
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
const file = new Blob([dataToWrite], { type: "application/graphql" })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
const filename = `${
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.graphql`
|
||||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
document.body.appendChild(a)
|
||||||
}.graphql`
|
a.click()
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
|
|
||||||
const result = await platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/graphql",
|
|
||||||
suggestedFilename: filename,
|
|
||||||
filters: [
|
|
||||||
{
|
|
||||||
name: "GraphQL Schema File",
|
|
||||||
extensions: ["graphql"],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
|
||||||
downloadSchemaIcon.value = IconCheck
|
downloadSchemaIcon.value = IconCheck
|
||||||
toast.success(`${t("state.download_started")}`)
|
toast.success(`${t("state.download_started")}`)
|
||||||
}
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
|
||||||
const copySchema = () => {
|
const copySchema = () => {
|
||||||
|
|||||||
@@ -49,11 +49,7 @@
|
|||||||
v-for="(entry, index) in log"
|
v-for="(entry, index) in log"
|
||||||
:key="`entry-${index}`"
|
:key="`entry-${index}`"
|
||||||
:is-open="log.length - 1 === index"
|
:is-open="log.length - 1 === index"
|
||||||
:entry="{
|
:entry="{ ts: entry.time, source: 'info', payload: entry.data }"
|
||||||
ts: entry.type === 'response' ? entry.time : undefined,
|
|
||||||
source: 'info',
|
|
||||||
payload: entry.type === 'response' ? entry.data : '',
|
|
||||||
}"
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,31 +7,38 @@
|
|||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script lang="ts">
|
||||||
import { GraphQLScalarType, GraphQLType } from "graphql"
|
import { defineComponent } from "vue"
|
||||||
import { computed } from "vue"
|
import { GraphQLScalarType } from "graphql"
|
||||||
|
|
||||||
const props = defineProps<{
|
export default defineComponent({
|
||||||
gqlType: GraphQLType
|
props: {
|
||||||
}>()
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
gqlType: null,
|
||||||
|
// (typeName: string) => void
|
||||||
|
// eslint-disable-next-line vue/require-default-prop
|
||||||
|
jumpTypeCallback: Function,
|
||||||
|
},
|
||||||
|
|
||||||
const emit = defineEmits<{
|
computed: {
|
||||||
(e: "jump-to-type", type: GraphQLType): void
|
typeString() {
|
||||||
}>()
|
return `${this.gqlType}`
|
||||||
|
},
|
||||||
|
isScalar() {
|
||||||
|
return this.resolveRootType(this.gqlType) instanceof GraphQLScalarType
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
const typeString = computed(() => `${props.gqlType}`)
|
methods: {
|
||||||
const isScalar = computed(() => {
|
jumpToType() {
|
||||||
return resolveRootType(props.gqlType) instanceof GraphQLScalarType
|
if (this.isScalar) return
|
||||||
})
|
this.jumpTypeCallback(this.gqlType)
|
||||||
|
},
|
||||||
function resolveRootType(type: GraphQLType) {
|
resolveRootType(type) {
|
||||||
let t = type as any
|
let t = type
|
||||||
while (t.ofType != null) t = t.ofType
|
while (t.ofType != null) t = t.ofType
|
||||||
return t
|
return t
|
||||||
}
|
},
|
||||||
|
},
|
||||||
function jumpToType() {
|
})
|
||||||
if (isScalar.value) return
|
|
||||||
emit("jump-to-type", props.gqlType)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
v-model="filterText"
|
v-model="filterText"
|
||||||
type="search"
|
type="search"
|
||||||
autocomplete="off"
|
autocomplete="off"
|
||||||
class="flex h-8 w-full bg-transparent p-4 py-2"
|
class="flex flex-1 bg-transparent p-4 py-2"
|
||||||
:placeholder="`${t('action.search')}`"
|
:placeholder="`${t('action.search')}`"
|
||||||
/>
|
/>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
|
|||||||
@@ -54,9 +54,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 !cursor-grab group-hover:text-secondaryLight':
|
||||||
index !== workingParams?.length - 1,
|
index !== workingParams?.length - 1,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -75,7 +75,7 @@
|
|||||||
"
|
"
|
||||||
/>
|
/>
|
||||||
<div v-if="entry.isFile" class="file-chips-container">
|
<div v-if="entry.isFile" class="file-chips-container">
|
||||||
<div class="file-chips-wrapper space-x-1">
|
<div class="file-chips-wrapper space-x-2">
|
||||||
<HoppSmartFileChip
|
<HoppSmartFileChip
|
||||||
v-for="(file, fileIndex) in entry.value"
|
v-for="(file, fileIndex) in entry.value"
|
||||||
:key="`param-${index}-file-${fileIndex}`"
|
:key="`param-${index}-file-${fileIndex}`"
|
||||||
|
|||||||
@@ -71,9 +71,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 !cursor-grab group-hover:text-secondaryLight':
|
||||||
index !== workingHeaders?.length - 1,
|
index !== workingHeaders?.length - 1,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
@@ -190,13 +190,19 @@
|
|||||||
:icon="masking ? IconEye : IconEyeOff"
|
:icon="masking ? IconEye : IconEyeOff"
|
||||||
@click="toggleMask()"
|
@click="toggleMask()"
|
||||||
/>
|
/>
|
||||||
<div v-else class="aspect-square w-8"></div>
|
<HoppButtonSecondary
|
||||||
|
v-else
|
||||||
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
|
:icon="IconArrowUpRight"
|
||||||
|
:title="t('request.go_to_authorization_tab')"
|
||||||
|
class="cursor-auto text-primary hover:text-primary"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
<span>
|
<span>
|
||||||
<HoppButtonSecondary
|
<HoppButtonSecondary
|
||||||
v-tippy="{ theme: 'tooltip' }"
|
v-tippy="{ theme: 'tooltip' }"
|
||||||
:icon="IconArrowUpRight"
|
:icon="IconArrowUpRight"
|
||||||
:title="changeTabTooltip(header.source)"
|
:title="t('request.go_to_authorization_tab')"
|
||||||
@click="changeTab(header.source)"
|
@click="changeTab(header.source)"
|
||||||
/>
|
/>
|
||||||
</span>
|
</span>
|
||||||
@@ -250,7 +256,7 @@ import * as E from "fp-ts/Either"
|
|||||||
import * as O from "fp-ts/Option"
|
import * as O from "fp-ts/Option"
|
||||||
import * as A from "fp-ts/Array"
|
import * as A from "fp-ts/Array"
|
||||||
import draggable from "vuedraggable-es"
|
import draggable from "vuedraggable-es"
|
||||||
import { RESTOptionTabs } from "./RequestOptions.vue"
|
import { RequestOptionTabs } from "./RequestOptions.vue"
|
||||||
import { useCodemirror } from "@composables/codemirror"
|
import { useCodemirror } from "@composables/codemirror"
|
||||||
import { commonHeaders } from "~/helpers/headers"
|
import { commonHeaders } from "~/helpers/headers"
|
||||||
import { useI18n } from "@composables/i18n"
|
import { useI18n } from "@composables/i18n"
|
||||||
@@ -289,7 +295,7 @@ const deletionToast = ref<{ goAway: (delay: number) => void } | null>(null)
|
|||||||
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
const props = defineProps<{ modelValue: HoppRESTRequest }>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
(e: "change-tab", value: RESTOptionTabs): void
|
(e: "change-tab", value: RequestOptionTabs): void
|
||||||
(e: "update:modelValue", value: HoppRESTRequest): void
|
(e: "update:modelValue", value: HoppRESTRequest): void
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
@@ -504,15 +510,6 @@ const mask = (header: ComputedHeader) => {
|
|||||||
return header.header.value
|
return header.header.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const changeTabTooltip = (tab: ComputedHeader["source"]) => {
|
|
||||||
switch (tab) {
|
|
||||||
case "auth":
|
|
||||||
return t("request.go_to_authorization_tab")
|
|
||||||
case "body":
|
|
||||||
return t("request.go_to_body_tab")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const changeTab = (tab: ComputedHeader["source"]) => {
|
const changeTab = (tab: ComputedHeader["source"]) => {
|
||||||
if (tab === "auth") emit("change-tab", "authorization")
|
if (tab === "auth") emit("change-tab", "authorization")
|
||||||
else emit("change-tab", "bodyParams")
|
else emit("change-tab", "bodyParams")
|
||||||
|
|||||||
@@ -43,7 +43,6 @@ import { useI18n } from "@composables/i18n"
|
|||||||
import { useToast } from "@composables/toast"
|
import { useToast } from "@composables/toast"
|
||||||
import { tokenRequest } from "~/helpers/oauth"
|
import { tokenRequest } from "~/helpers/oauth"
|
||||||
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
import { getCombinedEnvVariables } from "~/helpers/preRequest"
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -78,15 +77,6 @@ const clientSecret = pluckRef(auth, "clientSecret" as any)
|
|||||||
|
|
||||||
const scope = pluckRef(auth, "scope")
|
const scope = pluckRef(auth, "scope")
|
||||||
|
|
||||||
function translateTokenRequestError(error: string) {
|
|
||||||
switch (error) {
|
|
||||||
case "OIDC_DISCOVERY_FAILED":
|
|
||||||
return t("authorization.oauth.token_generation_oidc_discovery_failed")
|
|
||||||
default:
|
|
||||||
return t("authorization.oauth.something_went_wrong_on_token_generation")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleAccessTokenRequest = async () => {
|
const handleAccessTokenRequest = async () => {
|
||||||
if (
|
if (
|
||||||
oidcDiscoveryURL.value === "" &&
|
oidcDiscoveryURL.value === "" &&
|
||||||
@@ -108,11 +98,7 @@ const handleAccessTokenRequest = async () => {
|
|||||||
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
clientSecret: parseTemplateString(clientSecret.value, envVars),
|
||||||
scope: parseTemplateString(scope.value, envVars),
|
scope: parseTemplateString(scope.value, envVars),
|
||||||
}
|
}
|
||||||
const res = await tokenRequest(tokenReqParams)
|
await tokenRequest(tokenReqParams)
|
||||||
|
|
||||||
if (res && E.isLeft(res)) {
|
|
||||||
toast.error(translateTokenRequestError(res.left))
|
|
||||||
}
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
toast.error(`${e}`)
|
toast.error(`${e}`)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,9 +71,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 !cursor-grab group-hover:text-secondaryLight':
|
||||||
index !== workingParams?.length - 1,
|
index !== workingParams?.length - 1,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ const newSendRequest = async () => {
|
|||||||
const streamResult = await streamPromise
|
const streamResult = await streamPromise
|
||||||
|
|
||||||
requestCancelFunc.value = cancel
|
requestCancelFunc.value = cancel
|
||||||
|
|
||||||
if (E.isRight(streamResult)) {
|
if (E.isRight(streamResult)) {
|
||||||
subscribeToStream(
|
subscribeToStream(
|
||||||
streamResult.right,
|
streamResult.right,
|
||||||
@@ -364,20 +365,6 @@ const newSendRequest = async () => {
|
|||||||
loading.value = false
|
loading.value = false
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
// TODO: Change this any to a proper type
|
|
||||||
const result = (streamResult.right as any).value
|
|
||||||
if (
|
|
||||||
result.type === "network_fail" &&
|
|
||||||
result.error?.error === "NO_PW_EXT_HOOK"
|
|
||||||
) {
|
|
||||||
const errorResponse: HoppRESTResponse = {
|
|
||||||
type: "extension_error",
|
|
||||||
error: result.error.humanMessage.heading,
|
|
||||||
component: result.error.component,
|
|
||||||
req: result.req,
|
|
||||||
}
|
|
||||||
updateRESTResponse(errorResponse)
|
|
||||||
}
|
|
||||||
loading.value = false
|
loading.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -11,12 +11,6 @@
|
|||||||
<HoppSmartSpinner class="my-4" />
|
<HoppSmartSpinner class="my-4" />
|
||||||
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
<span class="text-secondaryLight">{{ t("state.loading") }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<component
|
|
||||||
:is="response.component"
|
|
||||||
v-if="response.type === 'extension_error'"
|
|
||||||
class="flex-1"
|
|
||||||
/>
|
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="response.type === 'network_fail'"
|
v-if="response.type === 'network_fail'"
|
||||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
||||||
|
|||||||
@@ -71,9 +71,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 !cursor-grab group-hover:text-secondaryLight':
|
||||||
index !== workingUrlEncodedParams?.length - 1,
|
index !== workingUrlEncodedParams?.length - 1,
|
||||||
}"
|
}"
|
||||||
tabindex="-1"
|
tabindex="-1"
|
||||||
|
|||||||
@@ -1,98 +0,0 @@
|
|||||||
<template>
|
|
||||||
<HoppSmartPlaceholder
|
|
||||||
:src="`/images/states/${colorMode.value}/youre_lost.svg`"
|
|
||||||
:alt="`${t('error.network_fail')}`"
|
|
||||||
:heading="t('error.network_fail')"
|
|
||||||
large
|
|
||||||
>
|
|
||||||
<div class="my-1 flex flex-col items-center text-secondaryLight">
|
|
||||||
<span>
|
|
||||||
{{ t("error.please_install_extension") }}
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
{{ t("error.check_how_to_add_origin") }}
|
|
||||||
<HoppSmartLink
|
|
||||||
blank
|
|
||||||
to="https://docs.hoppscotch.io/documentation/features/interceptor#browser-extension"
|
|
||||||
class="text-accent hover:text-accentDark"
|
|
||||||
>
|
|
||||||
here
|
|
||||||
</HoppSmartLink>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="flex flex-col space-y-2 py-4">
|
|
||||||
<span>
|
|
||||||
<HoppSmartItem
|
|
||||||
to="https://chrome.google.com/webstore/detail/hoppscotch-browser-extens/amknoiejhlmhancpahfcfcfhllgkpbld"
|
|
||||||
blank
|
|
||||||
:icon="IconChrome"
|
|
||||||
label="Chrome"
|
|
||||||
:info-icon="hasChromeExtInstalled ? IconCheckCircle : null"
|
|
||||||
:active-info-icon="hasChromeExtInstalled"
|
|
||||||
outline
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
<span>
|
|
||||||
<HoppSmartItem
|
|
||||||
to="https://addons.mozilla.org/en-US/firefox/addon/hoppscotch"
|
|
||||||
blank
|
|
||||||
:icon="IconFirefox"
|
|
||||||
label="Firefox"
|
|
||||||
:info-icon="hasFirefoxExtInstalled ? IconCheckCircle : null"
|
|
||||||
:active-info-icon="hasFirefoxExtInstalled"
|
|
||||||
outline
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div class="space-y-4 py-4">
|
|
||||||
<div class="flex items-center">
|
|
||||||
<HoppSmartToggle
|
|
||||||
:on="extensionEnabled"
|
|
||||||
@change="extensionEnabled = !extensionEnabled"
|
|
||||||
>
|
|
||||||
{{ t("settings.extensions_use_toggle") }}
|
|
||||||
</HoppSmartToggle>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</HoppSmartPlaceholder>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import IconChrome from "~icons/brands/chrome"
|
|
||||||
import IconFirefox from "~icons/brands/firefox"
|
|
||||||
import IconCheckCircle from "~icons/lucide/check-circle"
|
|
||||||
import { useI18n } from "@composables/i18n"
|
|
||||||
import { ExtensionInterceptorService } from "~/platform/std/interceptors/extension"
|
|
||||||
import { useService } from "dioc/vue"
|
|
||||||
import { computed } from "vue"
|
|
||||||
import { InterceptorService } from "~/services/interceptor.service"
|
|
||||||
import { platform } from "~/platform"
|
|
||||||
import { useColorMode } from "~/composables/theming"
|
|
||||||
|
|
||||||
const colorMode = useColorMode()
|
|
||||||
const t = useI18n()
|
|
||||||
|
|
||||||
const interceptorService = useService(InterceptorService)
|
|
||||||
const extensionService = useService(ExtensionInterceptorService)
|
|
||||||
|
|
||||||
const hasChromeExtInstalled = extensionService.chromeExtensionInstalled
|
|
||||||
const hasFirefoxExtInstalled = extensionService.firefoxExtensionInstalled
|
|
||||||
|
|
||||||
const extensionEnabled = computed({
|
|
||||||
get() {
|
|
||||||
return (
|
|
||||||
interceptorService.currentInterceptorID.value ===
|
|
||||||
extensionService.interceptorID
|
|
||||||
)
|
|
||||||
},
|
|
||||||
set(active) {
|
|
||||||
if (active) {
|
|
||||||
interceptorService.currentInterceptorID.value =
|
|
||||||
extensionService.interceptorID
|
|
||||||
} else {
|
|
||||||
interceptorService.currentInterceptorID.value =
|
|
||||||
platform.interceptors.default
|
|
||||||
}
|
|
||||||
},
|
|
||||||
})
|
|
||||||
</script>
|
|
||||||
@@ -35,12 +35,12 @@
|
|||||||
v-if="
|
v-if="
|
||||||
!teamDetails.loading &&
|
!teamDetails.loading &&
|
||||||
E.isRight(teamDetails.data) &&
|
E.isRight(teamDetails.data) &&
|
||||||
teamDetails.data.right.team?.teamMembers
|
teamDetails.data.right.team.teamMembers
|
||||||
"
|
"
|
||||||
class="rounded border border-divider"
|
class="rounded border border-divider"
|
||||||
>
|
>
|
||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="teamDetails.data.right.team.teamMembers.length === 0"
|
v-if="teamDetails.data.right.team.teamMembers === 0"
|
||||||
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
:src="`/images/states/${colorMode.value}/add_group.svg`"
|
||||||
:alt="`${t('empty.members')}`"
|
:alt="`${t('empty.members')}`"
|
||||||
:text="t('empty.members')"
|
:text="t('empty.members')"
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
>
|
>
|
||||||
<div
|
<div
|
||||||
v-for="(invitee, index) in pendingInvites.data.right.team
|
v-for="(invitee, index) in pendingInvites.data.right.team
|
||||||
?.teamInvitations"
|
.teamInvitations"
|
||||||
:key="`invitee-${index}`"
|
:key="`invitee-${index}`"
|
||||||
class="flex divide-x divide-dividerLight"
|
class="flex divide-x divide-dividerLight"
|
||||||
>
|
>
|
||||||
@@ -122,7 +122,7 @@
|
|||||||
<HoppSmartPlaceholder
|
<HoppSmartPlaceholder
|
||||||
v-if="
|
v-if="
|
||||||
E.isRight(pendingInvites.data) &&
|
E.isRight(pendingInvites.data) &&
|
||||||
pendingInvites.data.right.team?.teamInvitations.length === 0
|
pendingInvites.data.right.team.teamInvitations.length === 0
|
||||||
"
|
"
|
||||||
:text="t('empty.pending_invites')"
|
:text="t('empty.pending_invites')"
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -44,8 +44,6 @@ import { invokeAction } from "~/helpers/actions"
|
|||||||
import { useDebounceFn } from "@vueuse/core"
|
import { useDebounceFn } from "@vueuse/core"
|
||||||
// TODO: Migrate from legacy mode
|
// TODO: Migrate from legacy mode
|
||||||
|
|
||||||
import * as E from "fp-ts/Either"
|
|
||||||
|
|
||||||
type ExtendedEditorConfig = {
|
type ExtendedEditorConfig = {
|
||||||
mode: string
|
mode: string
|
||||||
placeholder: string
|
placeholder: string
|
||||||
@@ -162,21 +160,6 @@ const getLanguage = (langMime: string): Language | null => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const formatXML = (doc: string) => {
|
|
||||||
try {
|
|
||||||
const formatted = xmlFormat(doc, {
|
|
||||||
indentation: " ",
|
|
||||||
collapseContent: true,
|
|
||||||
lineSeparator: "\n",
|
|
||||||
whiteSpaceAtEndOfSelfclosingTag: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
return E.right(formatted)
|
|
||||||
} catch (e) {
|
|
||||||
return E.left(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uses xml-formatter to format the XML document
|
* Uses xml-formatter to format the XML document
|
||||||
* @param doc Document to parse
|
* @param doc Document to parse
|
||||||
@@ -188,11 +171,14 @@ const parseDoc = (
|
|||||||
langMime: string
|
langMime: string
|
||||||
): string | undefined => {
|
): string | undefined => {
|
||||||
if (langMime === "application/xml" && doc) {
|
if (langMime === "application/xml" && doc) {
|
||||||
const xmlFormatingResult = formatXML(doc)
|
return xmlFormat(doc, {
|
||||||
if (E.isRight(xmlFormatingResult)) return xmlFormatingResult.right
|
indentation: " ",
|
||||||
}
|
collapseContent: true,
|
||||||
|
lineSeparator: "\n",
|
||||||
|
})
|
||||||
|
} else {
|
||||||
return doc
|
return doc
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const getEditorLanguage = (
|
const getEditorLanguage = (
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import { useI18n } from "./i18n"
|
|||||||
import { refAutoReset } from "@vueuse/core"
|
import { refAutoReset } from "@vueuse/core"
|
||||||
import { copyToClipboard } from "@helpers/utils/clipboard"
|
import { copyToClipboard } from "@helpers/utils/clipboard"
|
||||||
import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
|
import { HoppRESTResponse } from "@helpers/types/HoppRESTResponse"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
export function useCopyResponse(responseBodyText: Ref<any>) {
|
export function useCopyResponse(responseBodyText: Ref<any>) {
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
@@ -41,14 +40,15 @@ export function useDownloadResponse(
|
|||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const t = useI18n()
|
const t = useI18n()
|
||||||
|
|
||||||
const downloadResponse = async () => {
|
const downloadResponse = () => {
|
||||||
const dataToWrite = responseBody.value
|
const dataToWrite = responseBody.value
|
||||||
|
|
||||||
// Guess extension and filename
|
|
||||||
const file = new Blob([dataToWrite], { type: contentType })
|
const file = new Blob([dataToWrite], { type: contentType })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
const filename = pipe(
|
// TODO: get uri from meta
|
||||||
|
a.download = pipe(
|
||||||
url,
|
url,
|
||||||
S.split("/"),
|
S.split("/"),
|
||||||
RNEA.last,
|
RNEA.last,
|
||||||
@@ -58,24 +58,15 @@ export function useDownloadResponse(
|
|||||||
RNEA.head
|
RNEA.head
|
||||||
)
|
)
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
document.body.appendChild(a)
|
||||||
|
a.click()
|
||||||
console.log(filename)
|
|
||||||
|
|
||||||
// TODO: Look at the mime type and determine extension ?
|
|
||||||
const result = await platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: contentType,
|
|
||||||
suggestedFilename: filename,
|
|
||||||
})
|
|
||||||
|
|
||||||
// Assume success if unknown as we cannot determine
|
|
||||||
if (result.type === "unknown" || result.type === "saved") {
|
|
||||||
downloadIcon.value = IconCheck
|
downloadIcon.value = IconCheck
|
||||||
toast.success(`${t("state.download_started")}`)
|
toast.success(`${t("state.download_started")}`)
|
||||||
|
setTimeout(() => {
|
||||||
|
document.body.removeChild(a)
|
||||||
|
URL.revokeObjectURL(url)
|
||||||
|
}, 1000)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
downloadIcon,
|
downloadIcon,
|
||||||
downloadResponse,
|
downloadResponse,
|
||||||
|
|||||||
@@ -152,14 +152,12 @@ export function useStreamSubscriber(): {
|
|||||||
error?: (e: any) => void,
|
error?: (e: any) => void,
|
||||||
complete?: () => void
|
complete?: () => void
|
||||||
) => {
|
) => {
|
||||||
let sub: Subscription | null = null
|
const sub = stream.subscribe({
|
||||||
|
|
||||||
sub = stream.subscribe({
|
|
||||||
next,
|
next,
|
||||||
error,
|
error,
|
||||||
complete: () => {
|
complete: () => {
|
||||||
if (complete) complete()
|
if (complete) complete()
|
||||||
if (sub) subs.splice(subs.indexOf(sub), 1)
|
subs.splice(subs.indexOf(sub), 1)
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import { Ref, onBeforeUnmount, onMounted, reactive, watch } from "vue"
|
|||||||
import { BehaviorSubject } from "rxjs"
|
import { BehaviorSubject } from "rxjs"
|
||||||
import { HoppRESTDocument } from "./rest/document"
|
import { HoppRESTDocument } from "./rest/document"
|
||||||
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
import { HoppGQLRequest, HoppRESTRequest } from "@hoppscotch/data"
|
||||||
import { RESTOptionTabs } from "~/components/http/RequestOptions.vue"
|
import { RequestOptionTabs } from "~/components/http/RequestOptions.vue"
|
||||||
import { HoppGQLSaveContext } from "./graphql/document"
|
import { HoppGQLSaveContext } from "./graphql/document"
|
||||||
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
|
||||||
import { computed } from "vue"
|
import { computed } from "vue"
|
||||||
@@ -113,7 +113,7 @@ type HoppActionArgsMap = {
|
|||||||
request: HoppGQLRequest
|
request: HoppGQLRequest
|
||||||
}
|
}
|
||||||
"request.open-tab": {
|
"request.open-tab": {
|
||||||
tab: RESTOptionTabs | GQLOptionTabs
|
tab: RequestOptionTabs | GQLOptionTabs
|
||||||
}
|
}
|
||||||
|
|
||||||
"tab.duplicate-tab": {
|
"tab.duplicate-tab": {
|
||||||
|
|||||||
@@ -11,9 +11,8 @@ import {
|
|||||||
getIntrospectionQuery,
|
getIntrospectionQuery,
|
||||||
printSchema,
|
printSchema,
|
||||||
} from "graphql"
|
} from "graphql"
|
||||||
import { Component, computed, reactive, ref } from "vue"
|
import { computed, reactive, ref } from "vue"
|
||||||
import { getService } from "~/modules/dioc"
|
import { getService } from "~/modules/dioc"
|
||||||
import { getI18n } from "~/modules/i18n"
|
|
||||||
|
|
||||||
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
import { addGraphqlHistoryEntry, makeGQLHistoryEntry } from "~/newstore/history"
|
||||||
|
|
||||||
@@ -33,23 +32,13 @@ type RunQueryOptions = {
|
|||||||
operationType: OperationType
|
operationType: OperationType
|
||||||
}
|
}
|
||||||
|
|
||||||
export type GQLResponseEvent =
|
export type GQLResponseEvent = {
|
||||||
| {
|
|
||||||
type: "response"
|
|
||||||
time: number
|
time: number
|
||||||
operationName: string | undefined
|
operationName: string | undefined
|
||||||
operationType: OperationType
|
operationType: OperationType
|
||||||
data: string
|
data: string
|
||||||
rawQuery?: RunQueryOptions
|
rawQuery?: RunQueryOptions
|
||||||
}
|
}
|
||||||
| {
|
|
||||||
type: "error"
|
|
||||||
error: {
|
|
||||||
type: string
|
|
||||||
message: string
|
|
||||||
component?: Component
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
|
export type ConnectionState = "CONNECTING" | "CONNECTED" | "DISCONNECTED"
|
||||||
export type SubscriptionState = "SUBSCRIBING" | "SUBSCRIBED" | "UNSUBSCRIBED"
|
export type SubscriptionState = "SUBSCRIBING" | "SUBSCRIBED" | "UNSUBSCRIBED"
|
||||||
@@ -72,11 +61,6 @@ type Connection = {
|
|||||||
subscriptionState: Map<string, SubscriptionState>
|
subscriptionState: Map<string, SubscriptionState>
|
||||||
socket: WebSocket | undefined
|
socket: WebSocket | undefined
|
||||||
schema: GraphQLSchema | null
|
schema: GraphQLSchema | null
|
||||||
error?: {
|
|
||||||
type: string
|
|
||||||
message: (t: ReturnType<typeof getI18n>) => string
|
|
||||||
component?: Component
|
|
||||||
} | null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = getService(GQLTabService)
|
const tabs = getService(GQLTabService)
|
||||||
@@ -87,7 +71,6 @@ export const connection = reactive<Connection>({
|
|||||||
subscriptionState: new Map<string, SubscriptionState>(),
|
subscriptionState: new Map<string, SubscriptionState>(),
|
||||||
socket: undefined,
|
socket: undefined,
|
||||||
schema: null,
|
schema: null,
|
||||||
error: null,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
export const schema = computed(() => connection.schema)
|
export const schema = computed(() => connection.schema)
|
||||||
@@ -219,19 +202,7 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
|
|||||||
const res = await interceptorService.runRequest(reqOptions).response
|
const res = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
if (E.isLeft(res)) {
|
if (E.isLeft(res)) {
|
||||||
if (
|
console.error(res.left)
|
||||||
res.left !== "cancellation" &&
|
|
||||||
res.left.error === "NO_PW_EXT_HOOK" &&
|
|
||||||
res.left.humanMessage
|
|
||||||
) {
|
|
||||||
connection.error = {
|
|
||||||
type: res.left.error,
|
|
||||||
message: (t: ReturnType<typeof getI18n>) =>
|
|
||||||
res.left.humanMessage.description(t),
|
|
||||||
component: res.left.component,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(res.left.toString())
|
throw new Error(res.left.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -247,7 +218,6 @@ const getSchema = async (url: string, headers: GQLHeader[]) => {
|
|||||||
const schema = buildClientSchema(introspectResponse.data)
|
const schema = buildClientSchema(introspectResponse.data)
|
||||||
|
|
||||||
connection.schema = schema
|
connection.schema = schema
|
||||||
connection.error = null
|
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e)
|
console.error(e)
|
||||||
disconnect()
|
disconnect()
|
||||||
@@ -310,18 +280,7 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
const result = await interceptorService.runRequest(reqOptions).response
|
const result = await interceptorService.runRequest(reqOptions).response
|
||||||
|
|
||||||
if (E.isLeft(result)) {
|
if (E.isLeft(result)) {
|
||||||
if (
|
console.error(result.left)
|
||||||
result.left !== "cancellation" &&
|
|
||||||
result.left.error === "NO_PW_EXT_HOOK" &&
|
|
||||||
result.left.humanMessage
|
|
||||||
) {
|
|
||||||
connection.error = {
|
|
||||||
type: result.left.error,
|
|
||||||
message: (t: ReturnType<typeof getI18n>) =>
|
|
||||||
result.left.humanMessage.description(t),
|
|
||||||
component: result.left.component,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
throw new Error(result.left.toString())
|
throw new Error(result.left.toString())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -333,7 +292,6 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
.replace(/\0+$/, "")
|
.replace(/\0+$/, "")
|
||||||
|
|
||||||
gqlMessageEvent.value = {
|
gqlMessageEvent.value = {
|
||||||
type: "response",
|
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
operationName: operationName ?? "query",
|
operationName: operationName ?? "query",
|
||||||
data: responseText,
|
data: responseText,
|
||||||
@@ -341,10 +299,6 @@ export const runGQLOperation = async (options: RunQueryOptions) => {
|
|||||||
operationType,
|
operationType,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection.state !== "CONNECTED") {
|
|
||||||
connection.state = "CONNECTED"
|
|
||||||
}
|
|
||||||
|
|
||||||
addQueryToHistory(options, responseText)
|
addQueryToHistory(options, responseText)
|
||||||
|
|
||||||
return responseText
|
return responseText
|
||||||
@@ -398,7 +352,6 @@ export const runSubscription = (
|
|||||||
}
|
}
|
||||||
case GQL.DATA: {
|
case GQL.DATA: {
|
||||||
gqlMessageEvent.value = {
|
gqlMessageEvent.value = {
|
||||||
type: "response",
|
|
||||||
time: Date.now(),
|
time: Date.now(),
|
||||||
operationName,
|
operationName,
|
||||||
data: JSON.stringify(data.payload),
|
data: JSON.stringify(data.payload),
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { Environment } from "@hoppscotch/data"
|
import { Environment } from "@hoppscotch/data"
|
||||||
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
import { TeamEnvironment } from "~/helpers/teams/TeamEnvironment"
|
||||||
import { cloneDeep } from "lodash-es"
|
import { cloneDeep } from "lodash-es"
|
||||||
import { platform } from "~/platform"
|
|
||||||
|
|
||||||
const getEnvironmentJson = (
|
const getEnvironmentJson = (
|
||||||
environmentObj: TeamEnvironment | Environment,
|
environmentObj: TeamEnvironment | Environment,
|
||||||
@@ -33,24 +32,17 @@ export const exportAsJSON = (
|
|||||||
if (!dataToWrite) return false
|
if (!dataToWrite) return false
|
||||||
|
|
||||||
const file = new Blob([dataToWrite], { type: "application/json" })
|
const file = new Blob([dataToWrite], { type: "application/json" })
|
||||||
|
const a = document.createElement("a")
|
||||||
const url = URL.createObjectURL(file)
|
const url = URL.createObjectURL(file)
|
||||||
|
a.href = url
|
||||||
|
|
||||||
URL.revokeObjectURL(url)
|
|
||||||
|
|
||||||
platform.io.saveFileWithDialog({
|
|
||||||
data: dataToWrite,
|
|
||||||
contentType: "application/json",
|
|
||||||
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
|
// Extracts the path from url, removes fragment identifier and query parameters if any, appends the ".json" extension, and assigns it
|
||||||
suggestedFilename: `${
|
a.download = `${url.split("/").pop()!.split("#")[0].split("?")[0]}.json`
|
||||||
url.split("/").pop()!.split("#")[0].split("?")[0]
|
document.body.appendChild(a)
|
||||||
}.json`,
|
a.click()
|
||||||
filters: [
|
setTimeout(() => {
|
||||||
{
|
document.body.removeChild(a)
|
||||||
name: "JSON file",
|
window.URL.revokeObjectURL(url)
|
||||||
extensions: ["json"],
|
}, 0)
|
||||||
},
|
|
||||||
],
|
|
||||||
})
|
|
||||||
|
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||