Compare commits

..

5 Commits

7 changed files with 103 additions and 81 deletions

View File

@@ -112,7 +112,7 @@ services:
build: build:
dockerfile: packages/hoppscotch-backend/Dockerfile dockerfile: packages/hoppscotch-backend/Dockerfile
context: . context: .
target: dev target: prod
env_file: env_file:
- ./.env - ./.env
restart: always restart: always
@@ -122,7 +122,7 @@ services:
- PORT=3000 - PORT=3000
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
- /usr/src/app/node_modules/ - /usr/src/app/node_modules/
depends_on: depends_on:
hoppscotch-db: hoppscotch-db:

View File

@@ -1125,7 +1125,7 @@ export class TeamCollectionService {
id: searchResults[i].id, id: searchResults[i].id,
path: !fetchedParentTree path: !fetchedParentTree
? [] ? []
: ([fetchedParentTree.right] as CollectionSearchNode[]), : (fetchedParentTree.right as CollectionSearchNode[]),
}); });
} }
@@ -1151,8 +1151,8 @@ export class TeamCollectionService {
select id,title,'collection' AS type select id,title,'collection' AS type
from "TeamCollection" from "TeamCollection"
where "TeamCollection"."teamID"=${teamID} where "TeamCollection"."teamID"=${teamID}
and titlesearch @@ to_tsquery(${searchQuery}) and titlesearch @@ plainto_tsquery(${searchQuery})
order by ts_rank(titlesearch,to_tsquery(${searchQuery})) order by ts_rank(titlesearch,plainto_tsquery(${searchQuery}))
limit ${take} limit ${take}
OFFSET ${skip === 0 ? 0 : (skip - 1) * take}; OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
`; `;
@@ -1183,8 +1183,8 @@ export class TeamCollectionService {
select id,title,request->>'method' as method,'request' AS type select id,title,request->>'method' as method,'request' AS type
from "TeamRequest" from "TeamRequest"
where "TeamRequest"."teamID"=${teamID} where "TeamRequest"."teamID"=${teamID}
and titlesearch @@ to_tsquery(${searchQuery}) and titlesearch @@ plainto_tsquery(${searchQuery})
order by ts_rank(titlesearch,to_tsquery(${searchQuery})) order by ts_rank(titlesearch,plainto_tsquery(${searchQuery}))
limit ${take} limit ${take}
OFFSET ${skip === 0 ? 0 : (skip - 1) * take}; OFFSET ${skip === 0 ? 0 : (skip - 1) * take};
`; `;
@@ -1250,45 +1250,53 @@ export class TeamCollectionService {
* @returns The parent tree of the parent collections * @returns The parent tree of the parent collections
*/ */
private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) { private generateParentTree(parentCollections: ParentTreeQueryReturnType[]) {
function findChildren(id) { function findChildren(id: string): CollectionSearchNode[] {
const collection = parentCollections.filter((item) => item.id === id)[0]; const collection = parentCollections.filter((item) => item.id === id)[0];
if (collection.parentID == null) { if (collection.parentID == null) {
return { return <CollectionSearchNode[]>[
id: collection.id, {
title: collection.title, id: collection.id,
type: 'collection', title: collection.title,
path: [], type: 'collection' as const,
}; path: [],
},
];
} }
const res = { const res = <CollectionSearchNode[]>[
id: collection.id, {
title: collection.title, id: collection.id,
type: 'collection', title: collection.title,
path: findChildren(collection.parentID), type: 'collection' as const,
}; path: findChildren(collection.parentID),
},
];
return res; return res;
} }
if (parentCollections.length > 0) { if (parentCollections.length > 0) {
if (parentCollections[0].parentID == null) { if (parentCollections[0].parentID == null) {
return { return <CollectionSearchNode[]>[
{
id: parentCollections[0].id,
title: parentCollections[0].title,
type: 'collection',
path: [],
},
];
}
return <CollectionSearchNode[]>[
{
id: parentCollections[0].id, id: parentCollections[0].id,
title: parentCollections[0].title, title: parentCollections[0].title,
type: 'collection', type: 'collection',
path: [], path: findChildren(parentCollections[0].parentID),
}; },
} ];
return {
id: parentCollections[0].id,
title: parentCollections[0].title,
type: 'collection',
path: findChildren(parentCollections[0].parentID),
};
} }
return null; return <CollectionSearchNode[]>[];
} }
/** /**

View File

@@ -4,12 +4,11 @@ import * as O from "fp-ts/Option"
import * as RA from "fp-ts/ReadonlyArray" import * as RA from "fp-ts/ReadonlyArray"
import * as A from "fp-ts/Array" import * as A from "fp-ts/Array"
import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data" import { translateToNewRESTCollection, HoppCollection } from "@hoppscotch/data"
import { isPlainObject as _isPlainObject } from "lodash-es"
import { IMPORTER_INVALID_FILE_FORMAT } from "." import { IMPORTER_INVALID_FILE_FORMAT } from "."
import { safeParseJSON } from "~/helpers/functional/json" import { safeParseJSON } from "~/helpers/functional/json"
import { translateToNewGQLCollection } from "@hoppscotch/data" import { translateToNewGQLCollection } from "@hoppscotch/data"
import { entityReference } from "verzod"
import { z } from "zod"
export const hoppRESTImporter = (content: string[]) => export const hoppRESTImporter = (content: string[]) =>
pipe( pipe(
@@ -27,14 +26,26 @@ export const hoppRESTImporter = (content: string[]) =>
TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT) TE.fromOption(() => IMPORTER_INVALID_FILE_FORMAT)
) )
/**
* checks if a value is a plain object
*/
const isPlainObject = (value: any): value is object => _isPlainObject(value)
/**
* checks if a collection matches the schema for a hoppscotch collection.
* here 2 is the latest version of the schema.
*/
const isValidCollection = (collection: unknown): collection is HoppCollection =>
isPlainObject(collection) && "v" in collection && collection.v === 2
/** /**
* checks if a collection is a valid hoppscotch collection. * checks if a collection is a valid hoppscotch collection.
* else translate it into one. * else translate it into one.
*/ */
const validateCollection = (collection: unknown) => { const validateCollection = (collection: unknown) => {
const result = entityReference(HoppCollection).safeParse(collection) if (isValidCollection(collection)) {
if (result.success) return O.some(result.data) return O.some(collection)
}
return O.some(translateToNewRESTCollection(collection)) return O.some(translateToNewRESTCollection(collection))
} }
@@ -64,9 +75,8 @@ export const hoppGQLImporter = (content: string) =>
* @returns the collection if it is valid, else a translated version of the collection * @returns the collection if it is valid, else a translated version of the collection
*/ */
export const validateGQLCollection = (collection: unknown) => { export const validateGQLCollection = (collection: unknown) => {
const result = z.array(entityReference(HoppCollection)).safeParse(collection) if (isValidCollection(collection)) {
return O.some(collection)
if (result.success) return O.some(result.data) }
return O.some(translateToNewGQLCollection(collection)) return O.some(translateToNewGQLCollection(collection))
} }

View File

@@ -52,6 +52,8 @@ export function makeCollection(x: Omit<HoppCollection, "v">): HoppCollection {
* @returns The proper new collection format * @returns The proper new collection format
*/ */
export function translateToNewRESTCollection(x: any): HoppCollection { export function translateToNewRESTCollection(x: any): HoppCollection {
if (x.v && x.v === CollectionSchemaVersion) return x
// Legacy // Legacy
const name = x.name ?? "Untitled" const name = x.name ?? "Untitled"
const folders = (x.folders ?? []).map(translateToNewRESTCollection) const folders = (x.folders ?? []).map(translateToNewRESTCollection)
@@ -79,6 +81,8 @@ export function translateToNewRESTCollection(x: any): HoppCollection {
* @returns The proper new collection format * @returns The proper new collection format
*/ */
export function translateToNewGQLCollection(x: any): HoppCollection { export function translateToNewGQLCollection(x: any): HoppCollection {
if (x.v && x.v === CollectionSchemaVersion) return x
// Legacy // Legacy
const name = x.name ?? "Untitled" const name = x.name ?? "Untitled"
const folders = (x.folders ?? []).map(translateToNewGQLCollection) const folders = (x.folders ?? []).map(translateToNewGQLCollection)

View File

@@ -18,8 +18,22 @@ export const HoppRESTRequestVariables = z.array(
export type HoppRESTRequestVariables = z.infer<typeof HoppRESTRequestVariables> export type HoppRESTRequestVariables = z.infer<typeof HoppRESTRequestVariables>
const V2_SCHEMA = V1_SCHEMA.extend({ const V2_SCHEMA = z.object({
v: z.literal("2"), v: z.literal("2"),
id: z.optional(z.string()), // Firebase Firestore ID
name: z.string(),
method: z.string(),
endpoint: z.string(),
params: HoppRESTParams,
headers: HoppRESTHeaders,
preRequestScript: z.string().catch(""),
testScript: z.string().catch(""),
auth: HoppRESTAuth,
body: HoppRESTReqBody,
requestVariables: HoppRESTRequestVariables, requestVariables: HoppRESTRequestVariables,
}) })

View File

@@ -112,15 +112,10 @@ function exportedCollectionToHoppCollection(
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: restCollection.requests.map((request) => { requests: restCollection.requests.map(
const requestParsedResult = HoppRESTRequest.safeParse(request) ({
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const {
v,
id, id,
v,
auth, auth,
body, body,
endpoint, endpoint,
@@ -130,23 +125,20 @@ function exportedCollectionToHoppCollection(
params, params,
preRequestScript, preRequestScript,
testScript, testScript,
requestVariables, }) => ({
} = request
return {
v,
id, id,
name, v,
endpoint,
method,
params,
requestVariables: requestVariables,
auth, auth,
headers,
body, body,
endpoint,
headers,
method,
name,
params,
preRequestScript, preRequestScript,
testScript, testScript,
} })
}), ),
} }
} else { } else {
const gqlCollection = collection as ExportedUserCollectionGQL const gqlCollection = collection as ExportedUserCollectionGQL

View File

@@ -120,15 +120,10 @@ function exportedCollectionToHoppCollection(
folders: restCollection.folders.map((folder) => folders: restCollection.folders.map((folder) =>
exportedCollectionToHoppCollection(folder, collectionType) exportedCollectionToHoppCollection(folder, collectionType)
), ),
requests: restCollection.requests.map((request) => { requests: restCollection.requests.map(
const requestParsedResult = HoppRESTRequest.safeParse(request) ({
if (requestParsedResult.type === "ok") {
return requestParsedResult.value
}
const {
v,
id, id,
v,
auth, auth,
body, body,
endpoint, endpoint,
@@ -139,22 +134,21 @@ function exportedCollectionToHoppCollection(
preRequestScript, preRequestScript,
testScript, testScript,
requestVariables, requestVariables,
} = request }) => ({
return {
v,
id, id,
name, v,
endpoint,
method,
params,
requestVariables: requestVariables,
auth, auth,
headers,
body, body,
endpoint,
headers,
method,
name,
params,
preRequestScript, preRequestScript,
testScript, testScript,
} requestVariables,
}), })
),
auth: data.auth, auth: data.auth,
headers: data.headers, headers: data.headers,
} }