From 623f3958d9f85b84c264a1e3d699ba78eb146b82 Mon Sep 17 00:00:00 2001 From: ozkeisar Date: Fri, 3 Jan 2025 00:51:06 +0200 Subject: [PATCH] fix import swagger --- README.md | 52 ++-- deployment.yaml | 52 ++++ package-lock.json | 37 ++- package.json | 2 + src/backend/actions/parents.ts | 80 +++-- src/backend/actions/route.ts | 112 +++---- src/backend/events/general.ts | 10 - src/backend/internalServer.ts | 8 +- src/backend/routers/server.ts | 44 +-- src/backend/run.ts | 2 +- src/backend/utils/analytics.ts | 93 ++++++ src/backend/utils/general.ts | 20 -- src/backend/utils/git.ts | 19 +- src/backend/utils/index.ts | 2 +- src/backend/utils/swaggerBuilder.ts | 274 ++++++++++-------- src/consts/analytics.ts | 6 +- src/consts/events.ts | 4 + src/main/main.ts | 13 +- .../components/cloneProject/cloneProject.tsx | 11 +- .../details/serverDetails/serverDetails.tsx | 49 ++-- .../components/devTools/terminal/terminal.tsx | 11 +- .../importSwaggerDialog.tsx | 92 +++--- .../ipChangedDialog/ipChangedDialog.tsx | 4 +- .../dialogs/settingsDialog/settingsDialog.tsx | 10 +- .../components/globalEvents/globalEvents.tsx | 3 +- src/renderer/const/general.ts | 6 +- src/renderer/index.tsx | 5 +- src/renderer/screens/main/main.css | 2 - src/renderer/screens/main/main.tsx | 15 +- src/renderer/utils/analytics.ts | 19 +- src/renderer/utils/electron.ts | 6 +- 31 files changed, 625 insertions(+), 438 deletions(-) create mode 100644 deployment.yaml create mode 100644 src/backend/utils/analytics.ts diff --git a/README.md b/README.md index 9b1228a..ea45bdd 100644 --- a/README.md +++ b/README.md @@ -1,57 +1,51 @@ -

- - # Mockingbird -Mockingbird is a tool designed to help software developers test their apps without needing the real server or backend ready. Imagine you’re building a new app, but the part of the system that handles data isn’t finished yet. Instead of waiting around, you can use Mockingbird to create a pretend version of that system. This allows you to keep working and testing your app as if everything is already set up, saving time and frustration. - +Mockingbird is a tool designed to help software developers test their apps without needing the real server or backend ready. Imagine you’re building a new app, but the part of the system that handles data isn’t finished yet. Instead of waiting around, you can use Mockingbird to create a pretend version of that system. This allows you to keep working and testing your app as if everything is already set up, saving time and frustration.

- ## Main Features -1. **Multiple Responses for Each Route**: +1. **Multiple Responses for Each Route**: Create different responses for the same API route, allowing you to simulate various scenarios like successful requests or errors. This helps you test how your app handles different situations. -2. **Presets**: +2. **Presets**: Easily switch between different sets of responses with just one click. This feature allows you to test how your app behaves in various scenarios without manually changing the responses each time. -3. **Git Integration**: +3. **Git Integration**: All mock data is stored in a Git repository, providing version control and security without the need for additional servers. You can track changes over time and collaborate more effectively. -4. **Proxy Functionality**: +4. **Proxy Functionality**: Mockingbird can listen to actual API requests and responses, helping you quickly create mocks based on real API calls. This saves time by automating part of the setup process. -5. **API Call Monitoring**: +5. **API Call Monitoring**: Monitor all incoming API calls to see exactly what requests are being made by your app and how they’re handled by Mockingbird. -6. **GraphQL Support**: +6. **GraphQL Support**: Full support for creating and managing GraphQL mocks, making it easy to work with GraphQL APIs during development. -7. **Multiple Projects and Servers**: +7. **Multiple Projects and Servers**: Manage different projects and servers within Mockingbird, making it a versatile tool for teams working on multiple applications. -8. **Built-in API for Testing**: +8. **Built-in API for Testing**: Includes a built-in API that allows you to control Mockingbird during automated tests, helping you seamlessly integrate it into your testing workflow. -9. **Docker image**: mockingbird have a docker image that let's you set it up as part of your ci/cd, make it suitable for your e2e testing. +9. **Docker image**: mockingbird have a docker image that let's you set it up as part of your ci/cd, make it suitable for your e2e testing. -## Mockingbird Guides +## Mockingbird Guides 1. [Mockingbird: New Tool for Your Mock Environments](https://dev.to/ozkeisar/mockingbird-new-tool-for-your-mock-environments-49j) 2. [Setting Up Your Mock Server with Mockingbird](https://dev.to/ozkeisar/setting-up-your-mock-server-with-mockingbird-1b72) 3. [Mockingbird Presets: Optimizing API Development Workflows](https://dev.to/ozkeisar/optimizing-api-development-workflows-with-mockingbird-presets-17hc) 4. [Creating and Managing Multiple Projects and Servers with Mockingbird](https://dev.to/ozkeisar/creating-and-managing-multiple-projects-and-servers-with-mockingbird-a7b) 5. [I built a new way of mocking GraphQL server](https://dev.to/ozkeisar/i-built-a-new-way-of-mocking-graphql-server-i94) -6. [Dockerize Your Mockingbird Setup: A Quickstart Guide](https://dev.to/ozkeisar/how-to-use-the-mockingbird-docker-image-29mf) - +6. [Dockerize Your Mockingbird Setup: A Quickstart Guide](https://dev.to/ozkeisar/how-to-use-the-mockingbird-docker-image-29mf) ## creating Docker image @@ -61,6 +55,11 @@ Docker folder contains scripts to run the project as a standalone server. Then use `docker build . -t {username}/mockingbird:{version}` to create the image. + `docker buildx build \ + --platform linux/amd64,linux/arm64 \ + -t ozkeisar/mockingbird:latest \ + --push .` + ## Licensing Mockingbird is dual-licensed under the GNU Affero General Public License v3.0 (AGPLv3) for open-source use and a commercial license for proprietary use. For more details, see [LICENSE](./LICENSE) and [COMMERCIAL_LICENSE](./COMMERCIAL_LICENSE). @@ -69,19 +68,18 @@ Mockingbird is dual-licensed under the GNU Affero General Public License v3.0 (A We welcome contributions to Mockingbird! Please read and agree to our Contributor License Agreement (CLA) before contributing. For more details, see [CONTRIBUTING](./CONTRIBUTING.md). - ## Troubleshooting + ### Error - Developer Cannot be Verified (Mac Users) - - **Open terminal and Run Command:** - - `xattr -c /Applications/Mockingbird.app` - - - **Verify Mockingbird:** - - - Once the command has been executed successfully, try running Mockingbird again. +- **Open terminal and Run Command:** + + `xattr -c /Applications/Mockingbird.app` + +- **Verify Mockingbird:** + + - Once the command has been executed successfully, try running Mockingbird again. For support or inquiries, contact us at ozkeisar@gmail.com Spread your wings with Mockingbird! 🚀 - diff --git a/deployment.yaml b/deployment.yaml new file mode 100644 index 0000000..33bca77 --- /dev/null +++ b/deployment.yaml @@ -0,0 +1,52 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: mockingbird +spec: + replicas: 1 + selector: + matchLabels: + app: mockingbird + template: + metadata: + labels: + app: mockingbird + spec: + containers: + - name: mockingbird + image: ozkeisar/mockingbird:latest + ports: + - containerPort: 1511 + - containerPort: 3000 + - containerPort: 3001 + - containerPort: 3050 + imagePullPolicy: Always + env: + - name: NODE_ENV + value: "production" +--- +apiVersion: v1 +kind: Service +metadata: + name: mockingbird-service +spec: + selector: + app: mockingbird + ports: + - name: api-port + protocol: TCP + port: 1511 + targetPort: 1511 + - name: web-port-3000 + protocol: TCP + port: 3000 + targetPort: 3000 + - name: web-port-3001 + protocol: TCP + port: 3001 + targetPort: 3001 + - name: web-port-3050 + protocol: TCP + port: 3050 + targetPort: 3050 + type: LoadBalancer diff --git a/package-lock.json b/package-lock.json index 086cb07..f944b3c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,7 @@ "hasInstallScript": true, "dependencies": { "@amplitude/analytics-browser": "^2.9.3", + "@amplitude/analytics-node": "^1.3.6", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@monaco-editor/react": "^4.6.0", @@ -194,6 +195,33 @@ "tslib": "^2.4.1" } }, + "node_modules/@amplitude/analytics-node": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-node/-/analytics-node-1.3.6.tgz", + "integrity": "sha512-yZQKe2JR6UWIWAOF19OfKSGkJ/Fn9cqLOVtp5MVKMGL45T4pXdFwCQdw9L5gspmL2B4/MtergdBKq0WDRIRvCw==", + "license": "MIT", + "dependencies": { + "@amplitude/analytics-core": "^1.2.5", + "@amplitude/analytics-types": "^1.3.4", + "tslib": "^2.4.1" + } + }, + "node_modules/@amplitude/analytics-node/node_modules/@amplitude/analytics-core": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-core/-/analytics-core-1.2.5.tgz", + "integrity": "sha512-V7CVlHVN+1diKiOpdp2bCPZ0mbS4CmUYF+v+eXDwVfJL3M/t3sVcT1apXnmVYGYi14cGu9hQOD11rD6qKbUOsw==", + "license": "MIT", + "dependencies": { + "@amplitude/analytics-types": "^1.3.4", + "tslib": "^2.4.1" + } + }, + "node_modules/@amplitude/analytics-node/node_modules/@amplitude/analytics-types": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-1.3.4.tgz", + "integrity": "sha512-tR70gzqFkEzX9QpxvWYMfLCledT7vMhgd3d4/bkp3nnGXTOORaVUOCcSgOyxyuFdSx84T61aP/eZPKIcZcaP+A==", + "license": "MIT" + }, "node_modules/@amplitude/analytics-types": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/@amplitude/analytics-types/-/analytics-types-2.6.0.tgz", @@ -8446,9 +8474,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001619", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001619.tgz", - "integrity": "sha512-SfolDuyDxyza6EUm112ugVI3gKIvUY/6+iddjvy00Ri7SCp34ZqM1p+U357VTLvOQ5FUo4MIFxbst03nKkDHUw==", + "version": "1.0.30001690", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001690.tgz", + "integrity": "sha512-5ExiE3qQN6oF8Clf8ifIDcMRCRE/dMGcETG/XGMD8/XiXm6HXQgQTh1yZYLXXpSOsEUlJm1Xr7kGULZTuGtP/w==", "dev": true, "funding": [ { @@ -8463,7 +8491,8 @@ "type": "github", "url": "https://github.com/sponsors/ai" } - ] + ], + "license": "CC-BY-4.0" }, "node_modules/chalk": { "version": "4.1.2", diff --git a/package.json b/package.json index f38272d..17b3771 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "start:renderer": "cross-env NODE_ENV=development TS_NODE_TRANSPILE_ONLY=true webpack serve --config ./.erb/configs/webpack.config.renderer.dev.ts", "start:backend": "ts-node ./src/backend/run.ts", "generateSwagger": "ts-node src/backend/swagger.ts", + "tsc": "tsc", "test": "jest" }, "browserslist": [], @@ -88,6 +89,7 @@ }, "dependencies": { "@amplitude/analytics-browser": "^2.9.3", + "@amplitude/analytics-node": "^1.3.6", "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.0", "@monaco-editor/react": "^4.6.0", diff --git a/src/backend/actions/parents.ts b/src/backend/actions/parents.ts index 7ec390f..db51e52 100644 --- a/src/backend/actions/parents.ts +++ b/src/backend/actions/parents.ts @@ -1,46 +1,45 @@ -import { RouteParent } from "../../types"; -import { EVENT_KEYS } from "../../types/events"; -import { isParentExist, parentsProperties } from "../../utils/parent"; -import { projectsManager } from "../managers"; -import { emitGlobalSocketMessage } from "../socket"; -import { hasUncommittedChanges, updateRouteParentFile } from "../utils"; - - -export const createParent = async (projectName: string, serverName: string, parent: RouteParent)=>{ - - const serversHash = await projectsManager.getProjectServersHash(projectName) - const server = serversHash[serverName] - - if(!server){ - throw new Error("server not exist"); +import { RouteParent } from '../../types'; +import { EVENT_KEYS } from '../../types/events'; +import { isParentExist, parentsProperties } from '../../utils/parent'; +import { projectsManager } from '../managers'; +import { emitGlobalSocketMessage } from '../socket'; +import { updateRouteParentFile } from '../utils/files'; +import { hasUncommittedChanges } from '../utils/git'; + +export const createParent = async ( + projectName: string, + serverName: string, + parent: RouteParent, +) => { + const serversHash = await projectsManager.getProjectServersHash(projectName); + const server = serversHash[serverName]; + + if (!server) { + throw new Error('server not exist'); } - const { - filenames, - paths, - graphQlNames, - restPaths, - graphQlPaths - } = parentsProperties(server) - - - const { - parentExist, - } = isParentExist(server, { - filenames, - paths, - graphQlNames, - restPaths, - graphQlPaths - }, parent) - - if(parentExist){ - throw new Error("parent already exist"); + const { filenames, paths, graphQlNames, restPaths, graphQlPaths } = + parentsProperties(server); + + const { parentExist } = isParentExist( + server, + { + filenames, + paths, + graphQlNames, + restPaths, + graphQlPaths, + }, + parent, + ); + + if (parentExist) { + throw new Error('parent already exist'); } await updateRouteParentFile(projectName, serverName, parent); - - projectsManager.setProjectChanged(projectName) + + projectsManager.setProjectChanged(projectName); const hasDiffs = await hasUncommittedChanges(projectName); @@ -53,6 +52,5 @@ export const createParent = async (projectName: string, serverName: string, pare hasDiffs, }); - return parent - -} \ No newline at end of file + return parent; +}; diff --git a/src/backend/actions/route.ts b/src/backend/actions/route.ts index 8721bb7..90814d6 100644 --- a/src/backend/actions/route.ts +++ b/src/backend/actions/route.ts @@ -1,54 +1,58 @@ -import { Route } from "../../types"; -import { EVENT_KEYS } from "../../types/events"; -import { isRouteExist } from "../../utils/route"; -import { projectsManager } from "../managers"; -import { emitGlobalSocketMessage } from "../socket"; -import { hasUncommittedChanges, updateRouteParentFile } from "../utils"; - - -export const createRoute = async(projectName: string, serverName: string, parentId: string, route: Route)=>{ - - const serverHash = await projectsManager.getProjectServersHash(projectName); - const server = serverHash[serverName] - - if(!server){ - throw new Error("server not exist"); - } - - const parent = server.parentRoutesHash[parentId] - - if(!parent){ - throw new Error("parent not exist"); - } - - const routeExist = isRouteExist(route, parent) - - if(routeExist){ - throw new Error("route already exist"); - } - - if(parent.routesHash){ - parent.routesHash[route.id] = route - }else{ - parent.routesHash = { - [route.id]: route - } - } - - await updateRouteParentFile(projectName, serverName, parent); - - projectsManager.setProjectChanged(projectName) - - const hasDiffs = await hasUncommittedChanges(projectName); - - emitGlobalSocketMessage(EVENT_KEYS.UPDATE_ROUTES_FILE, { - success: true, - content: parent, - filename: parent.filename, - projectName, - serverName, - hasDiffs, - }); - - return route -} \ No newline at end of file +import { Route } from '../../types'; +import { EVENT_KEYS } from '../../types/events'; +import { isRouteExist } from '../../utils/route'; +import { projectsManager } from '../managers'; +import { emitGlobalSocketMessage } from '../socket'; +import { updateRouteParentFile } from '../utils/files'; +import { hasUncommittedChanges } from '../utils/git'; + +export const createRoute = async ( + projectName: string, + serverName: string, + parentId: string, + route: Route, +) => { + const serverHash = await projectsManager.getProjectServersHash(projectName); + const server = serverHash[serverName]; + + if (!server) { + throw new Error('server not exist'); + } + + const parent = server.parentRoutesHash[parentId]; + + if (!parent) { + throw new Error('parent not exist'); + } + + const routeExist = isRouteExist(route, parent); + + if (routeExist) { + throw new Error('route already exist'); + } + + if (parent.routesHash) { + parent.routesHash[route.id] = route; + } else { + parent.routesHash = { + [route.id]: route, + }; + } + + await updateRouteParentFile(projectName, serverName, parent); + + projectsManager.setProjectChanged(projectName); + + const hasDiffs = await hasUncommittedChanges(projectName); + + emitGlobalSocketMessage(EVENT_KEYS.UPDATE_ROUTES_FILE, { + success: true, + content: parent, + filename: parent.filename, + projectName, + serverName, + hasDiffs, + }); + + return route; +}; diff --git a/src/backend/events/general.ts b/src/backend/events/general.ts index be13a3b..8aaa1db 100644 --- a/src/backend/events/general.ts +++ b/src/backend/events/general.ts @@ -2,7 +2,6 @@ import { execSync } from 'child_process'; import { Socket } from 'socket.io'; import { checkIServerUp, closeProjectServers } from '../server'; import { - activateProgram, getActiveProjectName, getProjectPath, getProjectsNameList, @@ -139,15 +138,6 @@ export const generalEvents = (socket: Socket) => { } }); - socket.on(EVENT_KEYS.ACTIVATE, async (arg) => { - try { - const success = await activateProgram(arg.activationKey); - emitSocketMessage(socket, EVENT_KEYS.ACTIVATE, { success }); - } catch (error) { - emitSocketMessage(socket, EVENT_KEYS.ACTIVATE, { success: false }); - } - }); - socket.on(EVENT_KEYS.RELOAD, async (arg) => { const { projectName } = arg; diff --git a/src/backend/internalServer.ts b/src/backend/internalServer.ts index c0b5a15..1773578 100644 --- a/src/backend/internalServer.ts +++ b/src/backend/internalServer.ts @@ -1,7 +1,13 @@ import { getCurrentIPAddresses } from './utils'; import { server } from './app'; +import { initAnalytics } from './utils/analytics'; + +export const startInternalServer = async ( + port: number, + { platform }: { platform: 'docker' | 'electron' }, +) => { + await initAnalytics({ platform }); -export const startInternalServer = (port?: number) => { const iPAddresses = getCurrentIPAddresses(); const host = iPAddresses[0]; const _port = port ?? 1511; diff --git a/src/backend/routers/server.ts b/src/backend/routers/server.ts index 49385e2..c581dec 100644 --- a/src/backend/routers/server.ts +++ b/src/backend/routers/server.ts @@ -102,36 +102,39 @@ serverRouter.post('/restart', async (req: Request, res: Response) => { } }); - - serverRouter.post('/load-swagger', async (req: Request, res: Response) => { try { - const { projectName, serverName, type, swaggerUrl, swaggerJson } = req.body as { - projectName: string, - serverName: string, - type: 'json' | 'url', - swaggerUrl: string, - swaggerJson: any - }; - - const iServerUp = checkIServerUp() + const { projectName, serverName, type, swaggerUrl, swaggerJson } = + req.body as { + projectName: string; + serverName: string; + type: 'json' | 'url'; + swaggerUrl: string; + swaggerJson: any; + }; + + const iServerUp = checkIServerUp(); /// check if server is running - if(iServerUp){ + if (iServerUp) { handleCloseServer(); } logger('start load from swagger', { projectName, serverName }); - if(type === 'url'){ - await addRoutesFromSwaggerUrl(projectName, serverName, swaggerUrl) - } else if(type === 'json'){ - await addRoutesFromSwaggerJson(projectName, serverName, JSON.parse(swaggerJson)) - }else { + if (type === 'url') { + await addRoutesFromSwaggerUrl(projectName, serverName, swaggerUrl); + } else if (type === 'json') { + await addRoutesFromSwaggerJson( + projectName, + serverName, + JSON.parse(swaggerJson), + ); + } else { throw new Error("type must be type 'url' | 'json' "); } /// check if server was running - if(iServerUp){ + if (iServerUp) { await handleStartServer(projectName); } @@ -140,7 +143,10 @@ serverRouter.post('/load-swagger', async (req: Request, res: Response) => { console.log(error); logger('Error load from swagger', error?.message); - res.status(500).send({ success: false, message: error?.message || 'fail to load from swagger' }); + res.status(500).send({ + success: false, + message: error?.message || 'fail to load from swagger', + }); } }); diff --git a/src/backend/run.ts b/src/backend/run.ts index 5ecc963..ecee9db 100644 --- a/src/backend/run.ts +++ b/src/backend/run.ts @@ -1,3 +1,3 @@ import { startInternalServer } from './index'; -startInternalServer(1511); +startInternalServer(1511, { platform: 'docker' }); diff --git a/src/backend/utils/analytics.ts b/src/backend/utils/analytics.ts new file mode 100644 index 0000000..05d242f --- /dev/null +++ b/src/backend/utils/analytics.ts @@ -0,0 +1,93 @@ +import * as amplitude from '@amplitude/analytics-node'; +// import { BUTTONS, COMMANDS, ELEMENTS } from '../../consts/analytics'; +// import { EVENT_KEYS } from '../../types/events'; +import { init, identify, Identify } from '@amplitude/analytics-node'; +import pj from '../../../package.json'; + +let aliveInterval: any | null = null; +const second = 60 * 1000; +const minutes = second * 60; + +const reportEvent = ( + event: string, + args: Record, +) => { + amplitude.track(event, args); +}; + +export const initAnalytics = async ({ + platform, +}: { + platform: 'docker' | 'electron'; +}) => { + const isDev = !process.env.NODE_ENV || process.env.NODE_ENV === 'development'; + + init( + isDev + ? '16543070a2c829f8a27f46c21fc0f708' + : 'ed51d61371d63ef1136b842900ebdae', + {}, + ); + + const identifyObj = new Identify(); + identify(identifyObj, { + // user_id: 'user@amplitude.com', + app_version: pj.version, + platform, + }); + + if (aliveInterval !== null) { + clearInterval(aliveInterval); + } + + aliveInterval = setInterval(() => { + reportEvent('alive', {}); + }, minutes * 30); +}; + +// export const reportCommandExecuted = ( +// event: COMMANDS, +// args?: Record, +// ) => { +// reportEvent(`command executed`, { event, ...args }); +// }; + +// export const reportButtonClick = ( +// event: BUTTONS, +// args?: Record, +// ) => { +// reportEvent(`button click`, { event, ...args }); +// }; + +// export const reportElementClick = ( +// event: ELEMENTS, +// args?: Record, +// ) => { +// reportEvent(`element click`, { event, ...args }); +// }; + +// export const reportSendEvent = ( +// event: EVENT_KEYS, +// args?: Record, +// ) => { +// reportEvent(`send event`, { event, ...args }); +// }; + +// export const reportEventReceived = ( +// event: EVENT_KEYS, +// success?: boolean, +// args?: Record, +// ) => { +// let eventName = `${event}`; + +// if (success === true) { +// eventName += ' success'; +// } else if (success === false) { +// eventName += ' failed'; +// } +// reportEvent(`received event`, { +// event: eventName, +// success: `${success}`, +// ...args, +// }); +// }; diff --git a/src/backend/utils/general.ts b/src/backend/utils/general.ts index e17cbfe..c8aaa1e 100644 --- a/src/backend/utils/general.ts +++ b/src/backend/utils/general.ts @@ -1,5 +1,4 @@ import os from 'os'; -import jwt from 'jsonwebtoken'; import { DEFAULT_SERVER_SETTINGS } from '../../consts'; import { getProjectPath, @@ -13,13 +12,11 @@ import { readAppSettings, readServerData, readServerSettings, - updateAppSettings, updatePresetFile, updateRouteParentFile, updateServerSettings, verifyProjectFoldersExist, } from './files'; -import pjson from '../../../package.json'; import { ExampleGraphqlNestedData, ExampleGraphqlParent, @@ -129,23 +126,6 @@ export const isFirstVersionGreater = (version1: string, version2: string) => { return true; }; -const verifyJWT = (jwtToken: string, username: string, secretKey: string) => { - try { - const decoded = jwt.verify(jwtToken, secretKey) as any; - - if (decoded.untilVersion && decoded.username) { - return ( - decoded.username === username && - isFirstVersionGreater(decoded.untilVersion, pjson.version) - ); - } - return false; - } catch (error) { - console.log('verifyJWT decoded error', error); - return false; - } -}; - export const getActiveProjectName = async () => { const appSettings = await readAppSettings(); diff --git a/src/backend/utils/git.ts b/src/backend/utils/git.ts index e6e5565..81aa629 100644 --- a/src/backend/utils/git.ts +++ b/src/backend/utils/git.ts @@ -15,8 +15,8 @@ async function isGitInstalled(): Promise { export const isGitRepository = async (projectName: string) => { try { - if(!await isGitInstalled()){ - return false + if (!(await isGitInstalled())) { + return false; } const currentRepoFolderPath = await getProjectPath(projectName); @@ -35,12 +35,11 @@ export const isGitRepository = async (projectName: string) => { } }; - export async function checkGitConnection(projectName: string) { const currentRepoFolderPath = await getProjectPath(projectName); - if(!await isGitRepository(projectName)){ - return false + if (!(await isGitRepository(projectName))) { + return false; } const git = simpleGit(currentRepoFolderPath); @@ -54,8 +53,6 @@ export async function checkGitConnection(projectName: string) { } } - - export const isCurrentBranchWithoutRemote = async (projectName: string) => { try { const currentRepoFolderPath = await getProjectPath(projectName); @@ -78,8 +75,8 @@ export const isCurrentBranchWithoutRemote = async (projectName: string) => { export const hasUncommittedChanges = async (projectName: string) => { try { - if(!await isGitRepository(projectName)){ - return false + if (!(await isGitRepository(projectName))) { + return false; } const currentRepoFolderPath = await getProjectPath(projectName); @@ -173,8 +170,8 @@ export const checkoutToBranch = async ( export const getCurrentBranch = async (projectName: string) => { try { - if(!await isGitRepository(projectName)){ - return null + if (!(await isGitRepository(projectName))) { + return null; } const currentRepoFolderPath = await getProjectPath(projectName); diff --git a/src/backend/utils/index.ts b/src/backend/utils/index.ts index 0427019..3f74b6e 100644 --- a/src/backend/utils/index.ts +++ b/src/backend/utils/index.ts @@ -3,4 +3,4 @@ export * from './events'; export * from './files'; export * from './git'; export * from './general'; -export * from './swaggerBuilder' \ No newline at end of file +export * from './swaggerBuilder'; diff --git a/src/backend/utils/swaggerBuilder.ts b/src/backend/utils/swaggerBuilder.ts index 5719785..15d3d94 100644 --- a/src/backend/utils/swaggerBuilder.ts +++ b/src/backend/utils/swaggerBuilder.ts @@ -1,13 +1,14 @@ +/* eslint-disable guard-for-in */ import axios from 'axios'; -import { createParent } from '../actions/parents'; import { v4 as uuid } from 'uuid'; +import { METHODS } from 'http'; +import { createParent } from '../actions/parents'; import { formatFileName } from '../../utils/utils'; import { createRoute } from '../actions/route'; import { Method, RouteParent } from '../../types'; import { findParent } from '../../utils/parent'; import { projectsManager } from '../managers'; - interface Endpoint { path: string; method: string; @@ -15,7 +16,7 @@ interface Endpoint { summary: string; tags: string[]; } - + /** * Fetches the Swagger JSON from the given URL and extracts a list of endpoints. * @param swaggerUrl - The URL of the Swagger JSON. @@ -27,24 +28,22 @@ async function fetchSwaggerEndpoints(swaggerUrl: string): Promise { const response = await axios.get(swaggerUrl); const swaggerData = response.data; - return swaggerData - + return swaggerData; } catch (error) { console.error('Error fetching Swagger endpoints:', error); throw new Error('Failed to fetch endpoints from Swagger URL'); } } - -function groupRoutes(routes: Endpoint[]): {[key: string]: Endpoint[]} { +function groupRoutes(routes: Endpoint[]): { [key: string]: Endpoint[] } { const groups: Map[] = []; routes.forEach((route) => { // Split the route into segments and filter out empty strings const segments = [...route.path.split('/').filter(Boolean), '']; // Use the first segment as the group key - segments.reduce((groupKey, segment, i)=>{ - if(!groups[i]){ + segments.reduce((groupKey, segment, i) => { + if (!groups[i]) { groups[i] = new Map(); } @@ -53,149 +52,160 @@ function groupRoutes(routes: Endpoint[]): {[key: string]: Endpoint[]} { } groups[i].get(groupKey)!.push(route); - - return groupKey + '/' + segment - }, '') - + return `${groupKey}/${segment}`; + }, ''); }); - - const result = routes.map((route) => { // Split the route into segments and filter out empty strings const segments = [...route.path.split('/').filter(Boolean), '']; // Use the first segment as the group key + const routeGrouping = segments.reduce( + (acc, segment, i) => { + const group = groups[i]; + const amountInGroup = group.get(acc.groupKey)?.length || 0; - const routeGrouping = segments.reduce((acc, segment, i)=>{ - const group = groups[i] - const amountInGroup = group.get(acc.groupKey)?.length || 0 + let score = amountInGroup ** i + i; - let score = Math.pow(amountInGroup, i) + i; - - if(route.tags.includes(segments[i-1])){ - score = score * 4 - } - if(amountInGroup === 1 && i !== 0 || segment === 'api' && segments[i+1]?.startsWith('v')){ - score = 0 - } + if (route.tags?.includes(segments[i - 1])) { + score *= 4; + } + if ( + (amountInGroup === 1 && i !== 0) || + (segment === 'api' && segments[i + 1]?.startsWith('v')) + ) { + score = 0; + } - if(score >= acc.selected.score){ - acc.selected = { - groupIndex: i, - amountInGroup, - group: acc.groupKey, - score, - route, + if (score >= acc.selected.score) { + acc.selected = { + groupIndex: i, + amountInGroup, + group: acc.groupKey, + score, + route, + }; } - } - /// remove from arrays - const arr = group.get(acc.groupKey); - if (arr) { - const index = arr.indexOf(route); - if (index > -1) { - arr.splice(index, 1); + /// remove from arrays + const arr = group.get(acc.groupKey); + if (arr) { + const index = arr.indexOf(route); + if (index > -1) { + arr.splice(index, 1); + } } - } + acc.groupKey = `${acc.groupKey}/${segment}`; - acc.groupKey = acc.groupKey + '/' + segment + return acc; + }, + { groupKey: '', selected: { score: 0 } as any }, + ); - return acc - },{groupKey: '', selected: {score: 0} as any}) - - groups[routeGrouping.selected.groupIndex].get(routeGrouping.selected.group)!.push(route) + groups[routeGrouping.selected.groupIndex] + .get(routeGrouping.selected.group)! + .push(route); - return routeGrouping.selected + return routeGrouping.selected; }); - - return result.reduce((acc,item)=>{ - if(!!acc[item.group]){ - acc[item.group].push(item.route) - }else{ - acc[item.group] = [item.route] + return result.reduce((acc, item) => { + if (acc[item.group]) { + acc[item.group].push(item.route); + } else { + acc[item.group] = [item.route]; } - return acc - }, {}) + return acc; + }, {}); } - -const handleCreateParent = async (projectName: string, serverName: string, parentPath: string)=>{ +const handleCreateParent = async ( + projectName: string, + serverName: string, + parentPath: string, +) => { const id = uuid(); let parent: RouteParent | undefined; - const projectData = await projectsManager.getProjectServersHash(projectName) + const projectData = await projectsManager.getProjectServersHash(projectName); - const server = projectData[serverName] + const server = projectData[serverName]; const newParent: RouteParent = { id, - type:'Rest', - filename: formatFileName(parentPath) + '-' + id, + type: 'Rest', + filename: `${formatFileName(parentPath)}-${id}`, name: null, - routesHash:{}, - graphQlRouteHash:{}, + routesHash: {}, + graphQlRouteHash: {}, graphqlQueriesType: null, path: parentPath, - schemaPath: null - } + schemaPath: null, + }; try { - parent = await createParent(projectName,serverName, newParent) - + parent = await createParent(projectName, serverName, newParent); } catch (error) { - console.log('fail to create parent ' + parentPath) - parent = await findParent(server, serverName, newParent) - console.log('find parent ' ,parent?.path) - + console.log(`fail to create parent ${parentPath}`); + parent = await findParent(server, serverName, newParent); + console.log('find parent ', parent?.path); } - return parent -} - + return parent; +}; -export const buildRoutesFromSwaggerData = async (projectName: string, serverName: string, swaggerEndpoints: Endpoint[]) => { +export const buildRoutesFromSwaggerData = async ( + projectName: string, + serverName: string, + swaggerEndpoints: Endpoint[], +) => { const grouped = groupRoutes(swaggerEndpoints); - await Promise.all(Object.keys(grouped).map(async(parentPath)=>{ - - const parent = await handleCreateParent(projectName, serverName, parentPath) + await Promise.all( + Object.keys(grouped).map(async (parentPath) => { + const parent = await handleCreateParent( + projectName, + serverName, + parentPath, + ); - if(!parent?.id){ + if (!parent?.id) { return; } - await Promise.all(grouped[parentPath].map(async (routeData)=>{ - try { - - const route = await createRoute(projectName,serverName, parent.id, { - id: uuid(), - description: routeData.description || routeData.summary, - routePath: routeData.path, - method: routeData.method.toLocaleLowerCase() as Method, - activeResponseId: '', - responsesHash: {}, - withParams: false, - paramKey: null, - paramType: 'body', - paramValue:null - }); - return route; - - - } catch (error: any) { - console.log('create route fail' + routeData.path, error.message) - return null - } - })) - - })) -} - - -export const pareseSwaggerJson = (swaggerData: any)=>{ - + await Promise.all( + grouped[parentPath].map(async (routeData) => { + try { + const route = await createRoute( + projectName, + serverName, + parent.id, + { + id: uuid(), + description: routeData.description || routeData.summary, + routePath: routeData.path, + method: routeData.method.toLocaleLowerCase() as Method, + activeResponseId: '', + responsesHash: {}, + withParams: false, + paramKey: null, + paramType: 'body', + paramValue: null, + }, + ); + return route; + } catch (error: any) { + console.log(`create route fail${routeData.path}`, error.message); + return null; + } + }), + ); + }), + ); +}; + +export const pareseSwaggerJson = (swaggerData: any) => { const endpoints: Endpoint[] = []; // Iterate through the paths and methods @@ -205,27 +215,37 @@ export const pareseSwaggerJson = (swaggerData: any)=>{ // Iterate through each method (e.g., get, post, put, delete) for (const method in pathItem) { const methodDetails = pathItem[method]; - endpoints.push({ - path, - method: method.toUpperCase(), - description: methodDetails.description, - summary: methodDetails.summary, - tags: methodDetails.tags - }); + if (METHODS.includes(method.toUpperCase())) { + endpoints.push({ + path, + method: method.toUpperCase(), + description: methodDetails.description, + summary: methodDetails.summary, + tags: methodDetails.tags, + }); + } } } return endpoints; -} +}; -export const addRoutesFromSwaggerUrl = async(projectName: string, serverName: string, swaggerUrl: string)=>{ +export const addRoutesFromSwaggerUrl = async ( + projectName: string, + serverName: string, + swaggerUrl: string, +) => { const swaggerJson = await fetchSwaggerEndpoints(swaggerUrl); - const endpoints = pareseSwaggerJson(swaggerJson) - await buildRoutesFromSwaggerData(projectName, serverName, endpoints) -} - -export const addRoutesFromSwaggerJson = async(projectName: string, serverName: string, swaggerJson: any)=>{ - const endpoints = pareseSwaggerJson(swaggerJson) - await buildRoutesFromSwaggerData(projectName, serverName, endpoints) - -} + const endpoints = pareseSwaggerJson(swaggerJson); + + await buildRoutesFromSwaggerData(projectName, serverName, endpoints); +}; + +export const addRoutesFromSwaggerJson = async ( + projectName: string, + serverName: string, + swaggerJson: any, +) => { + const endpoints = pareseSwaggerJson(swaggerJson); + await buildRoutesFromSwaggerData(projectName, serverName, endpoints); +}; diff --git a/src/consts/analytics.ts b/src/consts/analytics.ts index b1e135d..dbbded3 100644 --- a/src/consts/analytics.ts +++ b/src/consts/analytics.ts @@ -50,8 +50,8 @@ export enum BUTTONS { DELETE_ROUTE_DIALOG_CANCEL = 'delete route dialog cancel', DELETE_SERVER_DIALOG_CANCEL = 'delete server dialog cancel', DELETE_SERVER_DIALOG_DELETE = 'delete server dialog delete', - DELETE_IP_CHANGED_DIALOG_RESTART = 'delete ip changed dialog restart', - DELETE_IP_CHANGED_DIALOG_CLOSE = 'delete ip changed dialog close', + IP_CHANGED_DIALOG_RESTART = 'ip changed dialog restart', + IP_CHANGED_DIALOG_CLOSE = 'ip changed dialog close', PARENT_DIALOG_CLOSE = 'parent dialog close', PARENT_DIALOG_SAVE = 'parent dialog save', PRESET_DIALOG_SAVE = 'preset dialog save', @@ -99,7 +99,7 @@ export enum BUTTONS { START_MOCKING = 'welcome dialog start mocking', IMPORT_SWAGGER_DIALOG_IMPORT = 'import swagger dialog import', IMPORT_SWAGGER_DIALOG_CLOSE = 'import swagger dialog close', - SERVER_DETAILS_IMPORT = 'server details import' + SERVER_DETAILS_IMPORT = 'server details import', } export enum ELEMENTS { diff --git a/src/consts/events.ts b/src/consts/events.ts index bb40fcb..5a94fdf 100644 --- a/src/consts/events.ts +++ b/src/consts/events.ts @@ -166,4 +166,8 @@ export const EVENTS_SNACKBAR: EventsSnackbarType = { success: null, fail: 'failed to open project folder', }, + [EVENT_KEYS.SERVERS_CONSOLE]: { + success: null, + fail: null, + }, }; diff --git a/src/main/main.ts b/src/main/main.ts index 9ecd42d..6cf58d5 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -29,7 +29,6 @@ const { promisify } = require('util'); const execAsync = promisify(exec); - // eslint-disable-next-line import/no-mutable-exports let mainWindow: BrowserWindow | null = null; @@ -95,7 +94,7 @@ const createWindow = async () => { if (isDebug) { await installExtensions(); } - startInternalServer(); + startInternalServer(1511, { platform: 'electron' }); const RESOURCES_PATH = app.isPackaged ? path.join(process.resourcesPath, 'assets') @@ -151,7 +150,7 @@ const createWindow = async () => { // Remove this if your app does not use auto updates // eslint-disable-next-line - autoUpdater.on('update-available',(e)=>{ + autoUpdater.on('update-available', (e) => { mainWindow?.webContents.send(EVENT_KEYS.DEBUG_LOG, { e, event: 'update-available', @@ -248,14 +247,14 @@ ipcMain.on('selectDirectory', async (event) => { ipcMain.on('openProjectDirectory', async (event, args) => { try { - const {platform} = args; - + const { platform } = args; + const activeProjectName = await getActiveProjectName(); if (activeProjectName) { const projectPath = await getProjectPath(activeProjectName); - if(platform === 'vscode'){ + if (platform === 'vscode') { await execAsync('code .', { cwd: projectPath }); - }else{ + } else { shell.showItemInFolder(projectPath); // Show the given file in a file manager. If possible, select the file. } } else { diff --git a/src/renderer/components/cloneProject/cloneProject.tsx b/src/renderer/components/cloneProject/cloneProject.tsx index bc458d0..307c8fe 100644 --- a/src/renderer/components/cloneProject/cloneProject.tsx +++ b/src/renderer/components/cloneProject/cloneProject.tsx @@ -13,9 +13,10 @@ import CloseIcon from '@mui/icons-material/Close'; import styles from './cloneProject.module.css'; import { emitSocketEvent, reportButtonClick, socket } from '../../utils'; import { useGeneralStore } from '../../state'; -import { ElectronEvents, isElectronEnabled } from '../../utils/electron'; +import { ElectronEvents } from '../../utils/electron'; import { EVENT_KEYS } from '../../../types/events'; import { BUTTONS } from '../../../consts/analytics'; +import { isElectronEnabled } from '../../const/general'; const httpsExample = 'https://github.com/ozkeisar/mockingbird.git'; const hssExample = 'git@github.com:ozkeisar/mockingbird.git'; @@ -158,9 +159,11 @@ export function CloneProject({ LOCAL - {isElectronEnabled && - OPEN - } + {isElectronEnabled && ( + + OPEN + + )} {cloneType === 'HTTPS' && ( diff --git a/src/renderer/components/details/serverDetails/serverDetails.tsx b/src/renderer/components/details/serverDetails/serverDetails.tsx index 1c9899f..fa4f79e 100644 --- a/src/renderer/components/details/serverDetails/serverDetails.tsx +++ b/src/renderer/components/details/serverDetails/serverDetails.tsx @@ -1,6 +1,5 @@ import Button from '@mui/material/Button'; import { - Avatar, Divider, FormControlLabel, IconButton, @@ -21,7 +20,7 @@ import { EVENT_KEYS } from '../../../../types/events'; import { emitSocketEvent, reportButtonClick, socket } from '../../../utils'; import { BUTTONS } from '../../../../consts/analytics'; import { ServerSettings } from '../../../../types'; -import { ReactComponent as SwaggerIcon } from './../../../../../assets/svg/Swagger.svg'; +import { ReactComponent as SwaggerIcon } from '../../../../../assets/svg/Swagger.svg'; import { ImportSwaggerDialog } from '../../dialogs/importSwaggerDialog'; export function ServerDetails() { @@ -30,7 +29,7 @@ export function ServerDetails() { const { isServerUp, host } = useGeneralStore(); const { updateServerSettings, activeProjectName, setHasDiffs } = useProjectStore(); - const [isImportSwaggerOpen, setIsImportSwaggerOpen] = useState(false) + const [isImportSwaggerOpen, setIsImportSwaggerOpen] = useState(false); const [_proxyBaseUrl, setProxyBaseUrl] = useState( server?.settings.proxyBaseUrl || '', ); @@ -150,24 +149,28 @@ export function ServerDetails() {

@@ -296,13 +299,13 @@ export function ServerDetails() { onClose={() => setIsDeleteServerDialogOpen(false)} /> )} - { - isImportSwaggerOpen && setIsImportSwaggerOpen(false)} + onClose={() => setIsImportSwaggerOpen(false)} /> - } + )}
); } diff --git a/src/renderer/components/devTools/terminal/terminal.tsx b/src/renderer/components/devTools/terminal/terminal.tsx index 10f7f16..3dfbdbd 100644 --- a/src/renderer/components/devTools/terminal/terminal.tsx +++ b/src/renderer/components/devTools/terminal/terminal.tsx @@ -7,12 +7,12 @@ import { useProjectStore } from '../../../state/project'; import { ElectronEvents, emitSocketEvent, - isElectronEnabled, openInNewTab, reportCommandExecuted, socket, } from '../../../utils'; import styles from './terminal.module.css'; +import { isElectronEnabled } from '../../../const/general'; type CommandFunc = ( args: string[], @@ -128,13 +128,12 @@ export function CommandsTerminal() { // }, swagger: () => { reportCommandExecuted(COMMANDS.SWAGGER); - if(isElectronEnabled){ + if (isElectronEnabled) { openInNewTab('http://localhost:1511/api-docs'); - }else{ - const baseURl = window.location.href - openInNewTab(baseURl+'api-docs'); + } else { + const baseURl = window.location.href; + openInNewTab(`${baseURl}api-docs`); } - }, git: (args, print) => { reportCommandExecuted(COMMANDS.GIT, { diff --git a/src/renderer/components/dialogs/importSwaggerDialog/importSwaggerDialog.tsx b/src/renderer/components/dialogs/importSwaggerDialog/importSwaggerDialog.tsx index 2f73373..2c30ed7 100644 --- a/src/renderer/components/dialogs/importSwaggerDialog/importSwaggerDialog.tsx +++ b/src/renderer/components/dialogs/importSwaggerDialog/importSwaggerDialog.tsx @@ -11,28 +11,28 @@ import { Typography, } from '@mui/material'; import { useState } from 'react'; +import axios from 'axios'; import { BUTTONS } from '../../../../consts/analytics'; import { useProjectStore } from '../../../state/project'; import { reportButtonClick } from '../../../utils'; -import axios from 'axios' import { BASE_URL } from '../../../const/general'; export function ImportSwaggerDialog({ open, onClose, - serverName + serverName, }: { open: boolean; onClose: () => void; - serverName: string + serverName: string; }) { const { activeProjectName } = useProjectStore(); const [swaggerUrl, setSwaggerUrl] = useState(''); const [swaggerJson, setSwaggerJson] = useState(''); - const [type, setType] = useState<'url' | 'json'>('url') + const [type, setType] = useState<'url' | 'json'>('url'); const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(false) - const isJson = type === 'json' + const [error, setError] = useState(false); + const isJson = type === 'json'; const handleImport = async () => { reportButtonClick(BUTTONS.IMPORT_SWAGGER_DIALOG_IMPORT); @@ -40,25 +40,23 @@ export function ImportSwaggerDialog({ setIsLoading(true); try { - const res = await axios.post(BASE_URL+'/servers/load-swagger', { - "projectName": activeProjectName, - "serverName": serverName, - "type": type, - "swaggerUrl": isJson ? '' : swaggerUrl, - "swaggerJson": isJson ? swaggerJson: '' - }) + const res = await axios.post(`${BASE_URL}/servers/load-swagger`, { + projectName: activeProjectName, + serverName, + type, + swaggerUrl: isJson ? '' : swaggerUrl, + swaggerJson: isJson ? swaggerJson : '', + }); setIsLoading(false); - if(res.status === 200){ + if (res.status === 200) { onClose(); - }else{ - setError(true) + } else { + setError(true); } - - } catch (error) { + } catch (_) { setIsLoading(false); - setError(true) + setError(true); } - }; const handleClose = () => { @@ -75,31 +73,29 @@ export function ImportSwaggerDialog({ > import swagger - - - { - if(_type){ - setType(_type) - } - }} - aria-label="Platform" - > - url - json - + { + if (_type) { + setType(_type); + } + }} + aria-label="Platform" + > + url + json + { - setError(false) - if(isJson){ - setSwaggerJson(e.target.value) - } else{ + setError(false); + if (isJson) { + setSwaggerJson(e.target.value); + } else { setSwaggerUrl(e.target.value); } }} @@ -108,13 +104,13 @@ export function ImportSwaggerDialog({ maxRows={10} error={error} className="activation key" - label={isJson ? "swagger json" :"swagger json url"} - placeholder={isJson ? '{...}' : 'https://your.swagger.domain/swagger.json'} + label={isJson ? 'swagger json' : 'swagger json url'} + placeholder={ + isJson ? '{...}' : 'https://your.swagger.domain/swagger.json' + } variant="filled" /> - {error && ( - something went wrong - )} + {error && something went wrong}