├── .editorconfig ├── .erb ├── configs │ ├── .eslintrc │ ├── webpack.config.base.ts │ ├── webpack.config.eslint.ts │ ├── webpack.config.main.prod.ts │ ├── webpack.config.preload.dev.ts │ ├── webpack.config.renderer.dev.dll.ts │ ├── webpack.config.renderer.dev.ts │ ├── webpack.config.renderer.prod.ts │ └── webpack.paths.ts ├── img │ ├── erb-banner.svg │ ├── erb-logo.png │ └── palette-sponsor-banner.svg ├── mocks │ └── fileMock.js └── scripts │ ├── .eslintrc │ ├── check-build-exists.ts │ ├── check-native-dep.js │ ├── check-node-env.js │ ├── check-port-in-use.js │ ├── clean.js │ ├── delete-source-maps.js │ ├── electron-rebuild.js │ ├── link-modules.ts │ └── notarize.js ├── .eslintignore ├── .eslintrc.js ├── .gitattributes ├── .gitignore ├── .vscode ├── extensions.json ├── launch.json └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTIONS.md ├── LICENSE ├── README.md ├── assets ├── assets.d.ts ├── code-templates │ ├── express │ │ ├── endpoint_init.txt │ │ ├── general │ │ │ ├── index_old.txt │ │ │ └── init_modules.txt │ │ ├── modules │ │ │ ├── firebase │ │ │ │ ├── auth │ │ │ │ │ ├── fbAuthStarter.txt │ │ │ │ │ └── firebaseAuthMiddleware.txt │ │ │ │ ├── firebaseStarter.txt │ │ │ │ ├── firestore │ │ │ │ │ └── firestoreStarter.txt │ │ │ │ ├── initFirebase.txt │ │ │ │ └── initFirebaseOld.txt │ │ │ ├── gpt │ │ │ │ ├── createChatCompletion.txt │ │ │ │ ├── gptStarter.txt │ │ │ │ └── initGpt.txt │ │ │ ├── jwt │ │ │ │ ├── generateToken.txt │ │ │ │ ├── jwtAuthMiddleware.txt │ │ │ │ ├── jwtStarter.txt │ │ │ │ └── parseToken.txt │ │ │ ├── mongo │ │ │ │ ├── initMongo.txt │ │ │ │ └── mongoStarter.txt │ │ │ ├── resend │ │ │ │ ├── htmlTemplate.txt │ │ │ │ ├── initResend.txt │ │ │ │ ├── reactEmailTemplate.txt │ │ │ │ ├── resendStarter.txt │ │ │ │ └── sendEmail.txt │ │ │ ├── stripe │ │ │ │ ├── createCheckout.txt │ │ │ │ ├── createCustomerPortal.txt │ │ │ │ ├── initStripe.txt │ │ │ │ ├── stripeStarter.txt │ │ │ │ ├── stripeWebhookHandler.txt │ │ │ │ └── webhook-templates │ │ │ │ │ ├── subDeleted.txt │ │ │ │ │ ├── subInvoicePaid.txt │ │ │ │ │ ├── subInvoicePaymentFailed.txt │ │ │ │ │ ├── subUpdated.txt │ │ │ │ │ └── subWebhooksHandler.txt │ │ │ └── supabase │ │ │ │ ├── auth │ │ │ │ └── supabaseAuthMiddleware.txt │ │ │ │ ├── initSupabase.txt │ │ │ │ └── supabaseStarter.txt │ │ ├── router.txt │ │ └── router_init.txt │ └── fastapi │ │ ├── endpoint_init.txt │ │ ├── general │ │ ├── index_old.txt │ │ └── init_modules.txt │ │ ├── modules │ │ ├── firebase │ │ │ ├── auth │ │ │ │ └── firebase_auth_middleware.txt │ │ │ ├── firebase_starter.txt │ │ │ └── init_firebase.txt │ │ ├── gpt │ │ │ ├── create_chat_completion.txt │ │ │ ├── gpt_starter.txt │ │ │ └── init_gpt.txt │ │ ├── jwt │ │ │ ├── generate_token.txt │ │ │ ├── jwt_auth_middleware.txt │ │ │ ├── jwt_starter.txt │ │ │ └── parse_token.txt │ │ ├── mongo │ │ │ ├── init_mongo.txt │ │ │ └── mongo_starter.txt │ │ ├── resend │ │ │ ├── html_template.txt │ │ │ ├── init_resend.txt │ │ │ ├── resend_starter.txt │ │ │ └── send_email.txt │ │ ├── stripe │ │ │ ├── create_checkout.txt │ │ │ ├── create_customer_portal.txt │ │ │ ├── init_stripe.txt │ │ │ ├── stripe_starter.txt │ │ │ └── webhook-templates │ │ │ │ ├── sub_deleted.txt │ │ │ │ ├── sub_invoice_paid.txt │ │ │ │ ├── sub_invoice_payment_failed.txt │ │ │ │ ├── sub_updated.txt │ │ │ │ └── sub_webhooks_handler.txt │ │ └── supabase │ │ │ ├── auth │ │ │ └── supabase_auth_middleware.txt │ │ │ ├── init_supabase.txt │ │ │ └── supabase_starter.txt │ │ ├── router.txt │ │ └── router_init.txt ├── entitlements.mac.plist ├── express-template │ ├── .dockerignore │ ├── .gitignore │ ├── Dockerfile │ ├── cloudbuild.yaml │ ├── dist │ │ ├── api │ │ │ ├── Router.js │ │ │ └── Router.js.map │ │ ├── index.js │ │ ├── index.js.map │ │ └── models │ │ │ ├── project.js │ │ │ ├── project.js.map │ │ │ ├── user.js │ │ │ └── user.js.map │ ├── nodemon.json │ ├── package-lock.json │ ├── package.json │ ├── src │ │ ├── api │ │ │ └── root_router.ts │ │ ├── index.ts │ │ └── initModules.ts │ └── tsconfig.json ├── fastapi-template │ ├── .gitignore │ ├── Dockerfile │ ├── requirements.txt │ ├── run.py │ └── src │ │ ├── api │ │ └── root_router.py │ │ ├── init_modules.py │ │ └── main.py ├── github-images │ ├── demo_screenshot.png │ ├── github_banner.png │ ├── module_screenshot.png │ ├── routes_screenshot.png │ └── stripe_screenshot.png ├── icon.icns ├── icon.ico ├── icon.png ├── icon.svg └── icons │ ├── icon-128x128.png │ ├── icon-16x16.png │ ├── icon-256x256.png │ ├── icon-32x32.png │ ├── icon-512x512.png │ └── icon-64x64.png ├── docs ├── CODE_STRUCTURE.md └── HOW_IT_WORKS.md ├── package-lock.json ├── package.json ├── release └── app │ ├── package-lock.json │ └── package.json ├── src ├── __tests__ │ └── App.test.tsx ├── archives │ ├── CreateFirebaseModule.tsx │ └── FirebaseFirestore │ │ └── FirestoreManager.tsx ├── main │ ├── actions.ts │ ├── db │ │ ├── connect.ts │ │ ├── funcs │ │ │ └── funcQueries.ts │ │ ├── modules │ │ │ └── moduleQueries.ts │ │ └── route │ │ │ └── routeQueries.ts │ ├── generate │ │ ├── cloudbuild.ts │ │ ├── endpoints.ts │ │ ├── env.ts │ │ ├── fastapi │ │ │ ├── pyEndpointsGen.ts │ │ │ └── pythonInstall.ts │ │ ├── func.ts │ │ ├── general.ts │ │ ├── install.ts │ │ └── modules │ │ │ └── firebase │ │ │ └── firebaseGen.ts │ ├── helpers │ │ ├── binFuncs.ts │ │ └── fileFuncs.ts │ ├── ipc │ │ ├── auth │ │ │ ├── authFuncs.ts │ │ │ └── subscriptionFuncs.ts │ │ ├── home │ │ │ └── homeFuncs.ts │ │ ├── project │ │ │ ├── editorFuncs.ts │ │ │ ├── envFuncs.ts │ │ │ ├── fastapi │ │ │ │ ├── pyRouteFuncs.ts │ │ │ │ └── pythonProjectFuncs.ts │ │ │ ├── modules │ │ │ │ ├── firebase │ │ │ │ │ ├── firebaseAuthFuncs.ts │ │ │ │ │ └── firebaseFuncs.ts │ │ │ │ ├── funcFuncs.ts │ │ │ │ ├── helpers.ts │ │ │ │ ├── moduleFuncs.ts │ │ │ │ ├── mongodb │ │ │ │ │ └── mongoFuncs.ts │ │ │ │ ├── resend │ │ │ │ │ └── resendFuncs.ts │ │ │ │ ├── stripe │ │ │ │ │ └── stripeFuncs.ts │ │ │ │ └── supabase │ │ │ │ │ └── supabaseFuncs.ts │ │ │ ├── packageFuncs.ts │ │ │ ├── projectFuncs.ts │ │ │ └── routeFuncs.ts │ │ └── terminal │ │ │ └── terminalFuncs.ts │ ├── main.ts │ ├── menu.ts │ ├── preload.ts │ ├── services │ │ ├── GptService.ts │ │ ├── ProjectService.ts │ │ ├── StripeService.ts │ │ ├── UserService.ts │ │ └── config.ts │ └── util.ts ├── renderer │ ├── App.scss │ ├── App.tsx │ ├── components │ │ └── general │ │ │ └── Margin.tsx │ ├── declaration.d.ts │ ├── fonts │ │ ├── CascadiaCode.woff2 │ │ ├── CascadiaCodeItalic.woff2 │ │ ├── CascadiaCodePL.woff2 │ │ ├── CascadiaCodePLItalic.woff2 │ │ ├── CascadiaMono.woff2 │ │ ├── CascadiaMonoItalic.woff2 │ │ ├── CascadiaMonoPL.woff2 │ │ └── CascadiaMonoPLItalic.woff2 │ ├── hooks │ │ ├── shortcuts.tsx │ │ ├── useClickOutside.tsx │ │ ├── useEscClicked.tsx │ │ ├── useKeyPress.tsx │ │ ├── useShortcut.tsx │ │ └── useShortcutShift.tsx │ ├── index.ejs │ ├── index.tsx │ ├── misc │ │ └── constants.ts │ ├── preload.d.ts │ ├── redux │ │ ├── app │ │ │ ├── appReducer.ts │ │ │ └── appSlice.ts │ │ ├── cloud │ │ │ ├── cloudReducer.ts │ │ │ └── cloudSlice.ts │ │ ├── editor │ │ │ ├── editorReducer.ts │ │ │ └── editorSlice.ts │ │ ├── module │ │ │ ├── moduleReducer.tsx │ │ │ └── moduleSlice.tsx │ │ ├── project │ │ │ ├── projectReducers.ts │ │ │ └── projectSlice.ts │ │ ├── routes │ │ │ ├── routesReducer.ts │ │ │ └── routesSlice.ts │ │ └── store.ts │ ├── screens │ │ ├── Auth │ │ │ ├── AuthScreen.tsx │ │ │ ├── LoginContainer.tsx │ │ │ └── SignupContainer.tsx │ │ ├── Home │ │ │ ├── Home.tsx │ │ │ ├── HomeSidebar.tsx │ │ │ ├── Modals │ │ │ │ ├── CreateProjectModal.tsx │ │ │ │ ├── OpenWithExternalEditorModal.tsx │ │ │ │ └── UpgradeModal.tsx │ │ │ ├── NewPremiumModal.tsx │ │ │ └── RequireUpgradeModal.tsx │ │ └── Project │ │ │ ├── DeleteProjectModal.tsx │ │ │ ├── EditorScreen │ │ │ └── EditorScreen.tsx │ │ │ ├── ProjectScreen.tsx │ │ │ ├── SearchFuncsModal.tsx │ │ │ ├── SectionManager │ │ │ ├── EnvManager │ │ │ │ └── EnvManager.tsx │ │ │ ├── HostingScreen │ │ │ │ └── HostingManager.tsx │ │ │ ├── Modules │ │ │ │ ├── CreateModule │ │ │ │ │ ├── CreateModuleModal.tsx │ │ │ │ │ └── SelectModule.tsx │ │ │ │ ├── FirebaseModule │ │ │ │ │ ├── CreateFirebase.tsx │ │ │ │ │ ├── CreateFsColModal.tsx │ │ │ │ │ └── FirebaseManager.tsx │ │ │ │ ├── General │ │ │ │ │ ├── BasicModuleManager.tsx │ │ │ │ │ ├── CreateFuncModal.tsx │ │ │ │ │ ├── CreateModalHeader.tsx │ │ │ │ │ ├── DeleteModuleModal.tsx │ │ │ │ │ ├── FuncButton.tsx │ │ │ │ │ └── FuncSection.tsx │ │ │ │ ├── GptModule │ │ │ │ │ └── CreateGpt.tsx │ │ │ │ ├── JwtModule │ │ │ │ │ └── CreateJwt.tsx │ │ │ │ ├── ModuleScreen.tsx │ │ │ │ ├── MongoModule │ │ │ │ │ ├── CreateMongoDB.tsx │ │ │ │ │ ├── CreateMongoFuncModal.tsx │ │ │ │ │ └── MongoManager.tsx │ │ │ │ ├── ResendModule │ │ │ │ │ ├── CreateEmailTemplate.tsx │ │ │ │ │ ├── CreateResend.tsx │ │ │ │ │ ├── EmailTemplatesSection.tsx │ │ │ │ │ └── ResendManager.tsx │ │ │ │ ├── StripeModule │ │ │ │ │ ├── AddWebhookTemplate.tsx │ │ │ │ │ ├── CreateStripe.tsx │ │ │ │ │ └── StripeManager.tsx │ │ │ │ └── SupabaseModule │ │ │ │ │ ├── CreateSbTableModal.tsx │ │ │ │ │ ├── CreateSupabase.tsx │ │ │ │ │ └── SupabaseManager.tsx │ │ │ ├── PackageManager │ │ │ │ └── PackageManager.tsx │ │ │ ├── RoutesManager │ │ │ │ ├── CreateRouteModal.tsx │ │ │ │ ├── RouteRow.tsx │ │ │ │ └── RoutesManager.tsx │ │ │ └── SectionManager.tsx │ │ │ ├── Sidebar │ │ │ └── Sidebar.tsx │ │ │ └── Terminal │ │ │ └── Terminal.tsx │ ├── services │ │ ├── ProjectService.ts │ │ ├── UserService.ts │ │ └── config.ts │ └── styles │ │ ├── Auth │ │ └── AuthScreen.scss │ │ ├── Home │ │ ├── CreateProjectModal.scss │ │ ├── Home.scss │ │ ├── HomeSidebar.scss │ │ ├── NewPremiumModal.scss │ │ ├── OpenWithVsModal.scss │ │ └── UpgradeModal.scss │ │ ├── Project │ │ ├── CreateModule │ │ │ ├── CreateFirebase.scss │ │ │ ├── CreateModule.scss │ │ │ ├── CreateModuleModal.scss │ │ │ ├── ModuleTypes │ │ │ │ └── CreateMongoDB.scss │ │ │ └── SelectModule.scss │ │ ├── CreateRouteModal.scss │ │ ├── DeleteProjectModal.scss │ │ ├── EditorScreen │ │ │ └── EditorScreen.scss │ │ ├── Endpoint │ │ │ └── EndpointScreen.scss │ │ ├── EnvManager │ │ │ └── EnvManager.scss │ │ ├── Modules │ │ │ ├── CreateFuncModal.scss │ │ │ ├── FirebaseModule │ │ │ │ ├── FirebaseAuthManager.scss │ │ │ │ └── FirestoreManager.scss │ │ │ ├── ModuleFuncScreen.scss │ │ │ ├── ModuleSection.scss │ │ │ ├── MongoModule │ │ │ │ ├── CreateMongoFuncModal.scss │ │ │ │ ├── ManageMongo.scss │ │ │ │ └── funcScreen.scss │ │ │ └── StripeModule │ │ │ │ └── AddWebhookTemplateModal.scss │ │ ├── PackageManager │ │ │ └── PackageManager.scss │ │ ├── ProjectScreen.scss │ │ ├── RouteRow.scss │ │ ├── Routes │ │ │ └── RoutesScreen.scss │ │ ├── SearchFuncsModal.scss │ │ ├── SectionManager │ │ │ ├── HostingManager │ │ │ │ └── HostingManager.scss │ │ │ └── SectionManager.scss │ │ ├── Sidebar │ │ │ └── Sidebar.scss │ │ ├── Terminal │ │ │ └── Terminal.scss │ │ └── _projectConfig.scss │ │ ├── _config.scss │ │ └── index.scss └── shared │ ├── assets │ └── images │ │ ├── chatgpt.png │ │ ├── fb-auth.png │ │ ├── fb-firestore.png │ │ ├── firebase.png │ │ ├── jwt.png │ │ ├── logo.png │ │ ├── mongodb-logo.png │ │ ├── resend.png │ │ ├── sendgrid.png │ │ ├── stripe.png │ │ ├── supabase.png │ │ └── vscode.png │ ├── db │ ├── main.db │ └── test-project-gen.db │ ├── models │ ├── BFunc.ts │ ├── BModule.tsx │ ├── Editor.ts │ ├── GBuild.ts │ ├── NodeType.ts │ ├── User.ts │ ├── dbConn.ts │ ├── project.ts │ └── route.ts │ └── utils │ ├── GenFuncs.ts │ ├── MainFuncs.ts │ └── RenFuncs.ts ├── tsconfig.json └── zip.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.erb/configs/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.base.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Base webpack config used across other specific configs 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import TsconfigPathsPlugins from 'tsconfig-paths-webpack-plugin'; 7 | import webpackPaths from './webpack.paths'; 8 | import { dependencies as externals } from '../../release/app/package.json'; 9 | 10 | const configuration: webpack.Configuration = { 11 | externals: [...Object.keys(externals || {})], 12 | 13 | stats: 'errors-only', 14 | 15 | module: { 16 | rules: [ 17 | { 18 | test: /\.[jt]sx?$/, 19 | exclude: /node_modules/, 20 | use: { 21 | loader: 'ts-loader', 22 | options: { 23 | // Remove this line to enable type checking in webpack builds 24 | transpileOnly: true, 25 | compilerOptions: { 26 | module: 'esnext', 27 | }, 28 | }, 29 | }, 30 | }, 31 | ], 32 | }, 33 | 34 | output: { 35 | path: webpackPaths.srcPath, 36 | // https://github.com/webpack/webpack/issues/1114 37 | library: { 38 | type: 'commonjs2', 39 | }, 40 | }, 41 | 42 | /** 43 | * Determine the array of extensions that should be used to resolve modules. 44 | */ 45 | resolve: { 46 | extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'], 47 | modules: [webpackPaths.srcPath, 'node_modules'], 48 | // There is no need to add aliases here, the paths in tsconfig get mirrored 49 | plugins: [new TsconfigPathsPlugins()], 50 | }, 51 | 52 | plugins: [ 53 | new webpack.EnvironmentPlugin({ 54 | NODE_ENV: 'production', 55 | }), 56 | ], 57 | }; 58 | 59 | export default configuration; 60 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.eslint.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/no-unresolved: off, import/no-self-import: off */ 2 | 3 | module.exports = require('./webpack.config.renderer.dev').default; 4 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.main.prod.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Webpack config for production electron main process 3 | */ 4 | 5 | import path from 'path'; 6 | import webpack from 'webpack'; 7 | import { merge } from 'webpack-merge'; 8 | import TerserPlugin from 'terser-webpack-plugin'; 9 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 10 | import baseConfig from './webpack.config.base'; 11 | import webpackPaths from './webpack.paths'; 12 | import checkNodeEnv from '../scripts/check-node-env'; 13 | import deleteSourceMaps from '../scripts/delete-source-maps'; 14 | 15 | checkNodeEnv('production'); 16 | deleteSourceMaps(); 17 | 18 | const configuration: webpack.Configuration = { 19 | devtool: 'source-map', 20 | 21 | mode: 'production', 22 | 23 | target: 'electron-main', 24 | 25 | entry: { 26 | main: path.join(webpackPaths.srcMainPath, 'main.ts'), 27 | preload: path.join(webpackPaths.srcMainPath, 'preload.ts'), 28 | }, 29 | 30 | output: { 31 | path: webpackPaths.distMainPath, 32 | filename: '[name].js', 33 | library: { 34 | type: 'umd', 35 | }, 36 | }, 37 | 38 | optimization: { 39 | minimizer: [ 40 | new TerserPlugin({ 41 | parallel: true, 42 | }), 43 | ], 44 | }, 45 | 46 | plugins: [ 47 | new BundleAnalyzerPlugin({ 48 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 49 | analyzerPort: 8888, 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'production', 63 | DEBUG_PROD: false, 64 | START_MINIMIZED: false, 65 | }), 66 | 67 | new webpack.DefinePlugin({ 68 | 'process.type': '"browser"', 69 | }), 70 | ], 71 | 72 | /** 73 | * Disables webpack processing of __dirname and __filename. 74 | * If you run the bundle in node.js it falls back to these values of node.js. 75 | * https://github.com/webpack/webpack/issues/2010 76 | */ 77 | node: { 78 | __dirname: false, 79 | __filename: false, 80 | }, 81 | }; 82 | 83 | export default merge(baseConfig, configuration); 84 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.preload.dev.ts: -------------------------------------------------------------------------------- 1 | import path from 'path'; 2 | import webpack from 'webpack'; 3 | import { merge } from 'webpack-merge'; 4 | import { BundleAnalyzerPlugin } from 'webpack-bundle-analyzer'; 5 | import baseConfig from './webpack.config.base'; 6 | import webpackPaths from './webpack.paths'; 7 | import checkNodeEnv from '../scripts/check-node-env'; 8 | 9 | // When an ESLint server is running, we can't set the NODE_ENV so we'll check if it's 10 | // at the dev webpack config is not accidentally run in a production environment 11 | if (process.env.NODE_ENV === 'production') { 12 | checkNodeEnv('development'); 13 | } 14 | 15 | const configuration: webpack.Configuration = { 16 | devtool: 'inline-source-map', 17 | 18 | mode: 'development', 19 | 20 | target: 'electron-preload', 21 | 22 | entry: path.join(webpackPaths.srcMainPath, 'preload.ts'), 23 | 24 | output: { 25 | path: webpackPaths.dllPath, 26 | filename: 'preload.js', 27 | library: { 28 | type: 'umd', 29 | }, 30 | }, 31 | 32 | plugins: [ 33 | new BundleAnalyzerPlugin({ 34 | analyzerMode: process.env.ANALYZE === 'true' ? 'server' : 'disabled', 35 | }), 36 | 37 | /** 38 | * Create global constants which can be configured at compile time. 39 | * 40 | * Useful for allowing different behaviour between development builds and 41 | * release builds 42 | * 43 | * NODE_ENV should be production so that modules do not perform certain 44 | * development checks 45 | * 46 | * By default, use 'development' as NODE_ENV. This can be overriden with 47 | * 'staging', for example, by changing the ENV variables in the npm scripts 48 | */ 49 | new webpack.EnvironmentPlugin({ 50 | NODE_ENV: 'development', 51 | }), 52 | 53 | new webpack.LoaderOptionsPlugin({ 54 | debug: true, 55 | }), 56 | ], 57 | 58 | /** 59 | * Disables webpack processing of __dirname and __filename. 60 | * If you run the bundle in node.js it falls back to these values of node.js. 61 | * https://github.com/webpack/webpack/issues/2010 62 | */ 63 | node: { 64 | __dirname: false, 65 | __filename: false, 66 | }, 67 | 68 | watch: true, 69 | }; 70 | 71 | export default merge(baseConfig, configuration); 72 | -------------------------------------------------------------------------------- /.erb/configs/webpack.config.renderer.dev.dll.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Builds the DLL for development electron renderer process 3 | */ 4 | 5 | import webpack from 'webpack'; 6 | import path from 'path'; 7 | import { merge } from 'webpack-merge'; 8 | import baseConfig from './webpack.config.base'; 9 | import webpackPaths from './webpack.paths'; 10 | import { dependencies } from '../../package.json'; 11 | import checkNodeEnv from '../scripts/check-node-env'; 12 | 13 | checkNodeEnv('development'); 14 | 15 | const dist = webpackPaths.dllPath; 16 | 17 | const configuration: webpack.Configuration = { 18 | context: webpackPaths.rootPath, 19 | 20 | devtool: 'eval', 21 | 22 | mode: 'development', 23 | 24 | target: 'electron-renderer', 25 | 26 | externals: ['fsevents', 'crypto-browserify'], 27 | 28 | /** 29 | * Use `module` from `webpack.config.renderer.dev.js` 30 | */ 31 | module: require('./webpack.config.renderer.dev').default.module, 32 | 33 | entry: { 34 | renderer: Object.keys(dependencies || {}), 35 | }, 36 | 37 | output: { 38 | path: dist, 39 | filename: '[name].dev.dll.js', 40 | library: { 41 | name: 'renderer', 42 | type: 'var', 43 | }, 44 | }, 45 | 46 | plugins: [ 47 | new webpack.DllPlugin({ 48 | path: path.join(dist, '[name].json'), 49 | name: '[name]', 50 | }), 51 | 52 | /** 53 | * Create global constants which can be configured at compile time. 54 | * 55 | * Useful for allowing different behaviour between development builds and 56 | * release builds 57 | * 58 | * NODE_ENV should be production so that modules do not perform certain 59 | * development checks 60 | */ 61 | new webpack.EnvironmentPlugin({ 62 | NODE_ENV: 'development', 63 | }), 64 | 65 | new webpack.LoaderOptionsPlugin({ 66 | debug: true, 67 | options: { 68 | context: webpackPaths.srcPath, 69 | output: { 70 | path: webpackPaths.dllPath, 71 | }, 72 | }, 73 | }), 74 | ], 75 | }; 76 | 77 | export default merge(baseConfig, configuration); 78 | -------------------------------------------------------------------------------- /.erb/configs/webpack.paths.ts: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | const rootPath = path.join(__dirname, '../..'); 4 | 5 | const dllPath = path.join(__dirname, '../dll'); 6 | 7 | const srcPath = path.join(rootPath, 'src'); 8 | const srcMainPath = path.join(srcPath, 'main'); 9 | const srcRendererPath = path.join(srcPath, 'renderer'); 10 | 11 | const releasePath = path.join(rootPath, 'release'); 12 | const appPath = path.join(releasePath, 'app'); 13 | const appPackagePath = path.join(appPath, 'package.json'); 14 | const appNodeModulesPath = path.join(appPath, 'node_modules'); 15 | const srcNodeModulesPath = path.join(srcPath, 'node_modules'); 16 | 17 | const distPath = path.join(appPath, 'dist'); 18 | const distMainPath = path.join(distPath, 'main'); 19 | const distRendererPath = path.join(distPath, 'renderer'); 20 | 21 | const buildPath = path.join(releasePath, 'build'); 22 | 23 | export default { 24 | rootPath, 25 | dllPath, 26 | srcPath, 27 | srcMainPath, 28 | srcRendererPath, 29 | releasePath, 30 | appPath, 31 | appPackagePath, 32 | appNodeModulesPath, 33 | srcNodeModulesPath, 34 | distPath, 35 | distMainPath, 36 | distRendererPath, 37 | buildPath, 38 | }; 39 | -------------------------------------------------------------------------------- /.erb/img/erb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/.erb/img/erb-logo.png -------------------------------------------------------------------------------- /.erb/mocks/fileMock.js: -------------------------------------------------------------------------------- 1 | export default 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /.erb/scripts/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-console": "off", 4 | "global-require": "off", 5 | "import/no-dynamic-require": "off", 6 | "import/no-extraneous-dependencies": "off" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.erb/scripts/check-build-exists.ts: -------------------------------------------------------------------------------- 1 | // Check if the renderer and main bundles are built 2 | import path from 'path'; 3 | import chalk from 'chalk'; 4 | import fs from 'fs'; 5 | import webpackPaths from '../configs/webpack.paths'; 6 | 7 | const mainPath = path.join(webpackPaths.distMainPath, 'main.js'); 8 | const rendererPath = path.join(webpackPaths.distRendererPath, 'renderer.js'); 9 | 10 | if (!fs.existsSync(mainPath)) { 11 | throw new Error( 12 | chalk.whiteBright.bgRed.bold( 13 | 'The main process is not built yet. Build it by running "npm run build:main"' 14 | ) 15 | ); 16 | } 17 | 18 | if (!fs.existsSync(rendererPath)) { 19 | throw new Error( 20 | chalk.whiteBright.bgRed.bold( 21 | 'The renderer process is not built yet. Build it by running "npm run build:renderer"' 22 | ) 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /.erb/scripts/check-native-dep.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import chalk from 'chalk'; 3 | import { execSync } from 'child_process'; 4 | import { dependencies } from '../../package.json'; 5 | 6 | if (dependencies) { 7 | const dependenciesKeys = Object.keys(dependencies); 8 | const nativeDeps = fs 9 | .readdirSync('node_modules') 10 | .filter((folder) => fs.existsSync(`node_modules/${folder}/binding.gyp`)); 11 | if (nativeDeps.length === 0) { 12 | process.exit(0); 13 | } 14 | try { 15 | // Find the reason for why the dependency is installed. If it is installed 16 | // because of a devDependency then that is okay. Warn when it is installed 17 | // because of a dependency 18 | const { dependencies: dependenciesObject } = JSON.parse( 19 | execSync(`npm ls ${nativeDeps.join(' ')} --json`).toString() 20 | ); 21 | const rootDependencies = Object.keys(dependenciesObject); 22 | const filteredRootDependencies = rootDependencies.filter((rootDependency) => 23 | dependenciesKeys.includes(rootDependency) 24 | ); 25 | if (filteredRootDependencies.length > 0) { 26 | const plural = filteredRootDependencies.length > 1; 27 | console.log(` 28 | ${chalk.whiteBright.bgYellow.bold( 29 | 'Webpack does not work with native dependencies.' 30 | )} 31 | ${chalk.bold(filteredRootDependencies.join(', '))} ${ 32 | plural ? 'are native dependencies' : 'is a native dependency' 33 | } and should be installed inside of the "./release/app" folder. 34 | First, uninstall the packages from "./package.json": 35 | ${chalk.whiteBright.bgGreen.bold('npm uninstall your-package')} 36 | ${chalk.bold( 37 | 'Then, instead of installing the package to the root "./package.json":' 38 | )} 39 | ${chalk.whiteBright.bgRed.bold('npm install your-package')} 40 | ${chalk.bold('Install the package to "./release/app/package.json"')} 41 | ${chalk.whiteBright.bgGreen.bold( 42 | 'cd ./release/app && npm install your-package' 43 | )} 44 | Read more about native dependencies at: 45 | ${chalk.bold( 46 | 'https://electron-react-boilerplate.js.org/docs/adding-dependencies/#module-structure' 47 | )} 48 | `); 49 | process.exit(1); 50 | } 51 | } catch (e) { 52 | console.log('Native dependencies could not be checked'); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /.erb/scripts/check-node-env.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | 3 | export default function checkNodeEnv(expectedEnv) { 4 | if (!expectedEnv) { 5 | throw new Error('"expectedEnv" not set'); 6 | } 7 | 8 | if (process.env.NODE_ENV !== expectedEnv) { 9 | console.log( 10 | chalk.whiteBright.bgRed.bold( 11 | `"process.env.NODE_ENV" must be "${expectedEnv}" to use this webpack config` 12 | ) 13 | ); 14 | process.exit(2); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.erb/scripts/check-port-in-use.js: -------------------------------------------------------------------------------- 1 | import chalk from 'chalk'; 2 | import detectPort from 'detect-port'; 3 | 4 | const port = process.env.PORT || '1212'; 5 | 6 | detectPort(port, (err, availablePort) => { 7 | if (port !== String(availablePort)) { 8 | throw new Error( 9 | chalk.whiteBright.bgRed.bold( 10 | `Port "${port}" on "localhost" is already in use. Please use another port. ex: PORT=4343 npm start` 11 | ) 12 | ); 13 | } else { 14 | process.exit(0); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /.erb/scripts/clean.js: -------------------------------------------------------------------------------- 1 | import { rimrafSync } from 'rimraf'; 2 | import fs from 'fs'; 3 | import webpackPaths from '../configs/webpack.paths'; 4 | 5 | const foldersToRemove = [ 6 | webpackPaths.distPath, 7 | webpackPaths.buildPath, 8 | webpackPaths.dllPath, 9 | ]; 10 | 11 | foldersToRemove.forEach((folder) => { 12 | if (fs.existsSync(folder)) rimrafSync(folder); 13 | }); 14 | -------------------------------------------------------------------------------- /.erb/scripts/delete-source-maps.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import { rimrafSync } from 'rimraf'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | export default function deleteSourceMaps() { 7 | if (fs.existsSync(webpackPaths.distMainPath)) 8 | rimrafSync(path.join(webpackPaths.distMainPath, '*.js.map'), { 9 | glob: true, 10 | }); 11 | if (fs.existsSync(webpackPaths.distRendererPath)) 12 | rimrafSync(path.join(webpackPaths.distRendererPath, '*.js.map'), { 13 | glob: true, 14 | }); 15 | } 16 | -------------------------------------------------------------------------------- /.erb/scripts/electron-rebuild.js: -------------------------------------------------------------------------------- 1 | import { execSync } from 'child_process'; 2 | import fs from 'fs'; 3 | import { dependencies } from '../../release/app/package.json'; 4 | import webpackPaths from '../configs/webpack.paths'; 5 | 6 | if ( 7 | Object.keys(dependencies || {}).length > 0 && 8 | fs.existsSync(webpackPaths.appNodeModulesPath) 9 | ) { 10 | const electronRebuildCmd = 11 | '../../node_modules/.bin/electron-rebuild --force --types prod,dev,optional --module-dir .'; 12 | const cmd = 13 | process.platform === 'win32' 14 | ? electronRebuildCmd.replace(/\//g, '\\') 15 | : electronRebuildCmd; 16 | execSync(cmd, { 17 | cwd: webpackPaths.appPath, 18 | stdio: 'inherit', 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /.erb/scripts/link-modules.ts: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import webpackPaths from '../configs/webpack.paths'; 3 | 4 | const { srcNodeModulesPath } = webpackPaths; 5 | const { appNodeModulesPath } = webpackPaths; 6 | 7 | if (!fs.existsSync(srcNodeModulesPath) && fs.existsSync(appNodeModulesPath)) { 8 | fs.symlinkSync(appNodeModulesPath, srcNodeModulesPath, 'junction'); 9 | } 10 | -------------------------------------------------------------------------------- /.erb/scripts/notarize.js: -------------------------------------------------------------------------------- 1 | const { notarize } = require('@electron/notarize'); 2 | const { build } = require('../../package.json'); 3 | require('dotenv').config(); 4 | 5 | exports.default = async function notarizeMacos(context) { 6 | return; 7 | const { electronPlatformName, appOutDir } = context; 8 | if (electronPlatformName !== 'darwin') { 9 | return; 10 | } 11 | 12 | if (process.env.CI !== 'true') { 13 | console.warn('Skipping notarizing step. Packaging is not running in CI'); 14 | return; 15 | } 16 | 17 | if (!('APPLE_ID' in process.env && 'APPLE_ID_PASS' in process.env)) { 18 | console.warn( 19 | 'Skipping notarizing step. APPLE_ID and APPLE_ID_PASS env variables must be set' 20 | ); 21 | return; 22 | } 23 | 24 | const appName = context.packager.appInfo.productFilename; 25 | 26 | await notarize({ 27 | appBundleId: build.appId, 28 | appPath: `${appOutDir}/${appName}.app`, 29 | appleId: process.env.APPLE_ID, 30 | appleIdPassword: process.env.APPLE_APP_SPECIFIC_PASSWORD, 31 | }); 32 | }; 33 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Coverage directory used by tools like istanbul 11 | coverage 12 | .eslintcache 13 | 14 | # Dependency directory 15 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 16 | node_modules 17 | 18 | # OSX 19 | .DS_Store 20 | 21 | release/app/dist 22 | release/build 23 | .erb/dll 24 | 25 | .idea 26 | npm-debug.log.* 27 | *.css.d.ts 28 | *.sass.d.ts 29 | *.scss.d.ts 30 | 31 | # eslint ignores hidden directories by default: 32 | # https://github.com/eslint/eslint/issues/8429 33 | !.erb 34 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: 'erb', 3 | plugins: ['@typescript-eslint'], 4 | rules: { 5 | // A temporary hack related to IDE not resolving correct package.json 6 | 'import/no-extraneous-dependencies': 'off', 7 | 'react/react-in-jsx-scope': 'off', 8 | 'react/jsx-filename-extension': 'off', 9 | 'import/extensions': 'off', 10 | 'import/no-unresolved': 'off', 11 | 'import/no-import-module-exports': 'off', 12 | 'no-shadow': 'off', 13 | '@typescript-eslint/no-shadow': 'error', 14 | 'no-unused-vars': 'off', 15 | '@typescript-eslint/no-unused-vars': 'error', 16 | }, 17 | parserOptions: { 18 | ecmaVersion: 2020, 19 | sourceType: 'module', 20 | project: './tsconfig.json', 21 | tsconfigRootDir: __dirname, 22 | createDefaultProgram: true, 23 | }, 24 | settings: { 25 | 'import/resolver': { 26 | // See https://github.com/benmosher/eslint-plugin-import/issues/1396#issuecomment-575727774 for line below 27 | node: {}, 28 | webpack: { 29 | config: require.resolve('./.erb/configs/webpack.config.eslint.ts'), 30 | }, 31 | typescript: {}, 32 | }, 33 | 'import/parsers': { 34 | '@typescript-eslint/parser': ['.ts', '.tsx'], 35 | }, 36 | }, 37 | }; 38 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text eol=lf 2 | *.exe binary 3 | *.png binary 4 | *.jpg binary 5 | *.jpeg binary 6 | *.ico binary 7 | *.icns binary 8 | *.eot binary 9 | *.otf binary 10 | *.ttf binary 11 | *.woff binary 12 | *.woff2 binary 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .env 2 | zip.js 3 | 4 | assets/node-v18.18.2-darwin-arm64.tar.gz 5 | assets/node-v18.18.2-darwin-x64.tar.gz 6 | assets/node-v18.18.2-win-x64.zip 7 | assets/node-v18.18.2-win-x86.zip 8 | assets/node-lts 9 | 10 | # Logs 11 | logs 12 | *.log 13 | 14 | # Runtime data 15 | pids 16 | *.pid 17 | *.seed 18 | 19 | # Coverage directory used by tools like istanbul 20 | coverage 21 | .eslintcache 22 | 23 | # Dependency directory 24 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 25 | node_modules 26 | 27 | # OSX 28 | .DS_Store 29 | 30 | release/app/dist 31 | release/build 32 | .erb/dll 33 | 34 | .idea 35 | npm-debug.log.* 36 | *.css.d.ts 37 | *.sass.d.ts 38 | *.scss.d.ts 39 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": ["dbaeumer.vscode-eslint", "EditorConfig.EditorConfig"] 3 | } 4 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Electron: Main", 6 | "type": "node", 7 | "request": "launch", 8 | "protocol": "inspector", 9 | "runtimeExecutable": "npm", 10 | "runtimeArgs": ["run", "start"], 11 | "env": { 12 | "MAIN_ARGS": "--inspect=5858 --remote-debugging-port=9223" 13 | } 14 | }, 15 | { 16 | "name": "Electron: Renderer", 17 | "type": "chrome", 18 | "request": "attach", 19 | "port": 9223, 20 | "webRoot": "${workspaceFolder}", 21 | "timeout": 15000 22 | } 23 | ], 24 | "compounds": [ 25 | { 26 | "name": "Electron: All", 27 | "configurations": ["Electron: Main", "Electron: Renderer"] 28 | } 29 | ] 30 | } 31 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | ".eslintrc": "jsonc", 4 | ".prettierrc": "jsonc", 5 | ".eslintignore": "ignore" 6 | }, 7 | 8 | "eslint.validate": [ 9 | "javascript", 10 | "javascriptreact", 11 | "html", 12 | "typescriptreact" 13 | ], 14 | 15 | "javascript.validate.enable": false, 16 | "javascript.format.enable": false, 17 | "typescript.format.enable": false, 18 | 19 | "search.exclude": { 20 | ".git": true, 21 | ".eslintcache": true, 22 | ".erb/dll": true, 23 | "release/{build,app/dist}": true, 24 | "node_modules": true, 25 | "npm-debug.log.*": true, 26 | "test/**/__snapshots__": true, 27 | "package-lock.json": true, 28 | "*.{css,sass,scss}.d.ts": true 29 | }, 30 | "cmake.configureOnOpen": false 31 | } 32 | -------------------------------------------------------------------------------- /CONTRIBUTIONS.md: -------------------------------------------------------------------------------- 1 | ## Improving the docs 2 | 3 | We welcome you to improve our documentation. Our current documentation is found [here](https://github.com/vbackend/visual-backend/tree/main/docs) 4 | 5 | Everything is written in Markdown. To contribute to the documentation, simply fork & clone the repo, open a new branch and edit the documentation (under the docs folder), then commit your changes and make a pull request. 6 | 7 | Feel free to add documentation for a new section, but think about opening an issue first to discuss whether the new section will be accepted, before working on it. 8 | 9 | ## Building new modules for Visual Backend 10 | 11 | We very much welcome our community to create new modules on top of the existing ones at Visual Backend. However, at the moment, we are still developing a streamlined way to do so, and will implement this feature soon. 12 | 13 | ## Opening issues 14 | 15 | Open an issue to report bugs or request new features 16 | 17 | **When reporting a bug, please describe as clearly as possible:** 18 | - What the bug is 19 | - Steps to reproduce or if unable to, how you came across the bug in as much detail as possible 20 | - Platform you are using (Windows or Mac) 21 | 22 | **When requesting a new feature:** 23 | - Explain the feature you'd like to be added in detail 24 | - Why you'd like the feature to be added 25 | 26 | ## Pull requests 27 | 28 | Make pull requests to propose changes, new features, or fix existing bugs. 29 | 30 | To make pull requests, please: 31 | 1. Fork the repository (clone it locally) 32 | 2. Create a new branch to work on your proposed fix or feature 33 | 3. Write robust tests for that particular fix or feature 34 | 4. Commit your changes, push the new code, and open a pull request to the main branch 35 | 36 | To understand the code for Visual Backend better, do read our docs [here](https://github.com/vbackend/visual-backend/tree/main/docs) 37 | 38 | --- 39 | If you have any questions, do get in touch with me via email at john@visual-backend.com -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Visual Backend 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /assets/assets.d.ts: -------------------------------------------------------------------------------- 1 | type Styles = Record; 2 | 3 | declare module '*.svg' { 4 | import React = require('react'); 5 | 6 | export const ReactComponent: React.FC>; 7 | 8 | const content: string; 9 | export default content; 10 | } 11 | 12 | declare module '*.png' { 13 | const content: string; 14 | export default content; 15 | } 16 | 17 | declare module '*.jpg' { 18 | const content: string; 19 | export default content; 20 | } 21 | 22 | declare module '*.scss' { 23 | const content: Styles; 24 | export default content; 25 | } 26 | 27 | declare module '*.sass' { 28 | const content: Styles; 29 | export default content; 30 | } 31 | 32 | declare module '*.css' { 33 | const content: Styles; 34 | export default content; 35 | } 36 | -------------------------------------------------------------------------------- /assets/code-templates/express/endpoint_init.txt: -------------------------------------------------------------------------------- 1 | export const {{func_name}} = async (req, res{{middleware_text}}) => { 2 | // Complete function 3 | } 4 | -------------------------------------------------------------------------------- /assets/code-templates/express/general/index_old.txt: -------------------------------------------------------------------------------- 1 | import { config } from 'dotenv'; 2 | import express from 'express'; 3 | import { root_router } from './api/root_router.js'; 4 | import bodyParser from 'body-parser'; 5 | import cors from 'cors'; 6 | 7 | {{import_statements}} 8 | const init = async () => { 9 | config(); 10 | 11 | {{func_statements}} 12 | const app = express(); 13 | app.use(cors()); 14 | app.use(bodyParser.json()); 15 | app.use('/', root_router); 16 | app.get('/', (req: any, res: any) => { 17 | res.send('Hello from visual backend server!'); 18 | }); 19 | 20 | const args = process.argv.slice(2); 21 | let port = 8080; 22 | if (args.length > 0) { 23 | port = parseInt(args[0]); 24 | } 25 | 26 | const server = app.listen(port, () => { 27 | console.log(`Server started at http://localhost:${port}`); 28 | }); 29 | 30 | process.on('SIGTERM', () => { 31 | server.close(() => { 32 | process.exit(0); 33 | }); 34 | }); 35 | 36 | process.on('SIGINT', () => { 37 | server.close(() => { 38 | process.exit(0); 39 | }); 40 | }); 41 | }; 42 | 43 | init(); 44 | -------------------------------------------------------------------------------- /assets/code-templates/express/general/init_modules.txt: -------------------------------------------------------------------------------- 1 | {{import_statements}} 2 | export const initModules = async () => { 3 | 4 | {{func_statements}} 5 | }; 6 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/auth/fbAuthStarter.txt: -------------------------------------------------------------------------------- 1 | import { auth } from "@/modules/firebase/init.js"; 2 | 3 | export const ${funcName} = async () => { 4 | 5 | 6 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/auth/firebaseAuthMiddleware.txt: -------------------------------------------------------------------------------- 1 | import admin from "firebase-admin"; 2 | 3 | export const firebaseAuthMiddleware = async (req, res, next) => { 4 | let auth = admin.auth(); 5 | let authHeader = req.headers.authorization; 6 | 7 | if (!authHeader) { 8 | return res.status(401).json({ message: 'Unauthorized' }); 9 | } 10 | 11 | const token = authHeader.split(' ')[1]; 12 | 13 | try { 14 | const decodeValue = await auth.verifyIdToken(token); 15 | 16 | if (decodeValue) { 17 | console.log(decodeValue); 18 | return next(); 19 | } 20 | 21 | return res.status(401).json({ message: 'Unauthorized' }); 22 | } catch (e) { 23 | return res.status(500).json({ message: 'Internal Error' }); 24 | } 25 | }; 26 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/firebaseStarter.txt: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin' 2 | 3 | export const ${funcName} = async () => { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/firestore/firestoreStarter.txt: -------------------------------------------------------------------------------- 1 | import { firestore } from "@/modules/firebase/init.js"; 2 | 3 | export const ${funcName} = async () => { 4 | 5 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/initFirebase.txt: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | 5 | export const initFirebase = () => { 6 | try { 7 | let credentials; 8 | if (process.env.NODE_ENV == 'production') { 9 | credentials = process.env.FIREBASE_CREDENTIALS 10 | ? JSON.parse(process.env.FIREBASE_CREDENTIALS) 11 | : undefined; 12 | } else { 13 | let credentialsPath = path.join( 14 | process.cwd(), 15 | 'credentials', 16 | 'firebase_credentials.json' 17 | ); 18 | credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8')); 19 | } 20 | 21 | admin.initializeApp({ 22 | credential: admin.credential.cert(credentials), 23 | }); 24 | 25 | console.log('Firebase module initialised'); 26 | } catch (error) { 27 | console.log('Firebase module failed to initialise'); 28 | } 29 | }; 30 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/firebase/initFirebaseOld.txt: -------------------------------------------------------------------------------- 1 | import admin from 'firebase-admin'; 2 | import path from 'path'; 3 | import fs from 'fs'; 4 | import { Auth } from 'firebase-admin/auth'; 5 | import { Firestore } from 'firebase-admin/firestore'; 6 | 7 | export let auth: Auth; 8 | export let firestore: Firestore; 9 | 10 | export const initFirebase = () => { 11 | try { 12 | let credentials; 13 | if (process.env.NODE_ENV == 'production') { 14 | credentials = process.env.FIREBASE_CREDENTIALS 15 | ? JSON.parse(process.env.FIREBASE_CREDENTIALS) 16 | : undefined; 17 | } else { 18 | let credentialsPath = path.join( 19 | process.cwd(), 20 | 'credentials', 21 | 'firebase_credentials.json' 22 | ); 23 | credentials = JSON.parse(fs.readFileSync(credentialsPath, 'utf-8')); 24 | } 25 | 26 | admin.initializeApp({ 27 | credential: admin.credential.cert(credentials), 28 | }); 29 | 30 | auth = admin.auth(); 31 | firestore = admin.firestore(); 32 | 33 | console.log('Firebase module initialised'); 34 | } catch (error) { 35 | console.log('Firebase module failed to initialise'); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/gpt/createChatCompletion.txt: -------------------------------------------------------------------------------- 1 | import { openaiCli } from '@/modules/gpt/init.js' 2 | 3 | export const createChatCompletion = async (content: string) => { 4 | const result = await openaiCli.chat.completions.create({ 5 | messages: [{ role: "user", content }], 6 | model: "gpt-3.5-turbo", 7 | temperature: 0, 8 | }); 9 | 10 | return result.choices[0].message 11 | }; 12 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/gpt/gptStarter.txt: -------------------------------------------------------------------------------- 1 | import { openaiCli } from '@/modules/gpt/init.js' 2 | 3 | export const ${funcName} = () => { 4 | 5 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/gpt/initGpt.txt: -------------------------------------------------------------------------------- 1 | import OpenAI from "openai"; 2 | 3 | export let openaiCli: OpenAI; 4 | export const initGpt = () => { 5 | console.log("OpenAI module initialised") 6 | openaiCli = new OpenAI({ 7 | apiKey: process.env.OPENAI_API_KEY, 8 | }); 9 | }; 10 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/jwt/generateToken.txt: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | let expiry = { 4 | access: '1h', 5 | refresh: '1d' 6 | } 7 | 8 | export const generateToken = (payload, type) => { 9 | 10 | let secretKey, expiresIn; 11 | 12 | if (type == 'access') { 13 | secretKey = process.env.JWT_ACCESS_SECRET 14 | expiresIn = expiry.access 15 | } else { 16 | secretKey = process.env.JWT_REFRESH_SECRET 17 | expiresIn = expiry.refresh 18 | } 19 | 20 | try { 21 | // Generate the access token 22 | const accessToken = jwt.sign(payload, secretKey, { expiresIn }); 23 | 24 | return accessToken; 25 | } catch (error) { 26 | // Handle errors if JWT generation fails 27 | console.error('Error generating access token:', error); 28 | return null; 29 | } 30 | }; 31 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/jwt/jwtAuthMiddleware.txt: -------------------------------------------------------------------------------- 1 | import { parseToken } from '@/modules/jwt/parseToken.js' 2 | 3 | export const jwtAuthMiddleware = async (req, res, next) => { 4 | let authHeader = req.headers.authorization; 5 | 6 | if (!authHeader) { 7 | return res.status(401).json({ message: 'Unauthorized' }); 8 | } 9 | 10 | const token = authHeader.split(' ')[1]; 11 | 12 | try { 13 | const decodeValue = await parseToken(token, 'access'); 14 | 15 | if (decodeValue) { 16 | console.log(decodeValue); 17 | return next(); 18 | } 19 | 20 | return res.status(401).json({ message: 'Unauthorized' }); 21 | } catch (e) { 22 | return res.status(500).json({ message: 'Internal Error' }); 23 | } 24 | }; 25 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/jwt/jwtStarter.txt: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export const ${funcName} = async () => { 4 | 5 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/jwt/parseToken.txt: -------------------------------------------------------------------------------- 1 | import jwt from 'jsonwebtoken' 2 | 3 | export const parseToken = (accessToken, type) => { 4 | 5 | let secretKey; 6 | if (type == 'access') { 7 | secretKey = process.env.JWT_ACCESS_SECRET 8 | } else { 9 | secretKey = process.env.JWT_REFRESH_SECRET 10 | } 11 | 12 | try { 13 | // Verify and decode the token using the secret key 14 | const decoded = jwt.verify(accessToken, secretKey); 15 | 16 | // Return the decoded payload 17 | return decoded; 18 | } catch (error) { 19 | // Handle errors if token verification fails 20 | console.error('Error verifying access token:', error); 21 | return null; 22 | } 23 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/mongo/initMongo.txt: -------------------------------------------------------------------------------- 1 | import { Db, MongoClient } from "mongodb"; 2 | export let mongoCli: MongoClient; 3 | export let defaultDb: Db; 4 | 5 | export const initMongo = async () => { 6 | try { 7 | let connString = process.env.MONGO_CONN_STRING 8 | mongoCli = new MongoClient(connString!); 9 | await mongoCli.connect(); 10 | 11 | defaultDb = mongoCli.db(process.env.MONGO_DEFAULT_DB) 12 | console.log("Mongo module initialised"); 13 | } catch (err) { 14 | console.error("Error connecting to MongoDB:", err); 15 | } 16 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/mongo/mongoStarter.txt: -------------------------------------------------------------------------------- 1 | import { mongoCli, defaultDb } from '@/modules/mongo/init.js' 2 | 3 | export const ${funcName} = async () => { 4 | 5 | } 6 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/resend/htmlTemplate.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | <%=varKey %> 6 |

7 | 8 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/resend/initResend.txt: -------------------------------------------------------------------------------- 1 | import { Resend } from "resend"; 2 | export let resendCli: Resend; 3 | 4 | export const initResend = () => { 5 | resendCli = new Resend(process.env.RESEND_API_KEY); 6 | console.log("Resend module initialised"); 7 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/resend/reactEmailTemplate.txt: -------------------------------------------------------------------------------- 1 | const EmailTemplate = () => { 2 | 3 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/resend/resendStarter.txt: -------------------------------------------------------------------------------- 1 | import { resendCli } from "@/modules/resend/init.js"; 2 | 3 | export const ${funcName} = async () => { 4 | 5 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/resend/sendEmail.txt: -------------------------------------------------------------------------------- 1 | import { resendCli } from "@/modules/resend/init.js"; 2 | 3 | export const sendEmail = async (sender, recipient, subject, html) => { 4 | try { 5 | let res: any = await resendCli.emails.send({ 6 | from: `Acme <${sender}>`, 7 | to: recipient, 8 | subject: subject, 9 | html: html, 10 | }); 11 | 12 | console.log("Successfully sent email, res:", res); 13 | } catch (error) { 14 | console.log("Failed to send resend email:", error); 15 | } 16 | }; 17 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/createCheckout.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from "@/modules/stripe/init.js"; 2 | 3 | export const createCheckout = async () => { 4 | let priceId = ""; // price_id attached to product 5 | let clientReferenceId = ""; // unique customer identifier 6 | let mode = "subscription"; // mode: subscription or payment 7 | 8 | try { 9 | const session = await stripeCli.checkout.sessions.create({ 10 | line_items: [ 11 | { 12 | price: priceId, 13 | quantity: 1, 14 | }, 15 | ], 16 | mode: mode, 17 | client_reference_id: clientReferenceId, 18 | success_url: `http://localhost:3000/success`, 19 | cancel_url: `http://localhost:3000/cancel`, 20 | }); 21 | 22 | return session.url; 23 | } catch (error) { 24 | console.log("Failed to create checkout"); 25 | return null; 26 | } 27 | }; 28 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/createCustomerPortal.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from "@/modules/stripe/init.js"; 2 | 3 | export const createCustomerPortal = async (customerId) => { 4 | try { 5 | const session = await stripeCli.billingPortal.sessions.create({ 6 | customer: customerId, 7 | return_url: "https://localhost:3000", 8 | }); 9 | 10 | return session.url; 11 | } catch (error) { 12 | console.error("Error creating customer portal:", error); 13 | throw error; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/initStripe.txt: -------------------------------------------------------------------------------- 1 | import Stripe from "stripe"; 2 | 3 | export let stripeCli: Stripe; 4 | 5 | export const initStripe = async () => { 6 | let key: string; 7 | if (process.env.NODE_ENV == "production") { 8 | key = process.env.STRIPE_LIVE_KEY; 9 | } else { 10 | key = process.env.STRIPE_TEST_KEY; 11 | } 12 | 13 | stripeCli = new Stripe(key, null); 14 | console.log("Stripe module initialised"); 15 | }; 16 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/stripeStarter.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from "@/modules/stripe/init.js"; 2 | 3 | export const ${funcName} = async () => { 4 | 5 | } -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/stripeWebhookHandler.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from "@/modules/stripe/init.js"; 2 | 3 | export const stripeWebhookHandler = (req, res) => { 4 | let event = req.body; 5 | console.log("Received webhook:", event.type); 6 | 7 | switch (event.type) { 8 | case "checkout.session.expired": 9 | // When checkout session created, but not completed 10 | let checkoutSess = event.data.object; 11 | 12 | case "invoice.paid": 13 | // when checkout or subscription successfully completes 14 | let paidInvoice = event.data.object; 15 | 16 | case "invoice.payment_failed": 17 | // when checkout or subscription successfully fails 18 | let failedInvoice = event.data.object; 19 | } 20 | 21 | res.status(200).send(); 22 | }; 23 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/webhook-templates/subDeleted.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from '@/modules/stripe/init.js'; 2 | 3 | export const subDeleted = async (data) => { 4 | let { 5 | id, // stripe sub id 6 | customer, // stripe customer id 7 | cancel_at_period_end 8 | } = data; 9 | 10 | if (cancel_at_period_end) { 11 | // expired after being cancelled by user 12 | } 13 | 14 | // handle sub expired completely 15 | }; 16 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/webhook-templates/subInvoicePaid.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from '@/modules/stripe/init.js'; 2 | 3 | export const subInvoicePaid = async (data) => { 4 | let { 5 | id, // stripe invoice id 6 | customer, // stripe customer id 7 | customer_email, // customer email 8 | subscription, // stripe subscription id 9 | status, // invoice status, 10 | billing_reason, // 11 | lines, // array of items 12 | } = data; 13 | 14 | if (billing_reason != 'subscription_create' 15 | && billing_reason != 'subscription_cycle') return; 16 | 17 | let firstItem = lines.data[0]; 18 | let priceId = firstItem.price.id; 19 | 20 | // handle sub invoice paid 21 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/webhook-templates/subInvoicePaymentFailed.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from '@/modules/stripe/init.js'; 2 | 3 | export const subInvoicePaymentFailed = async (data) => { 4 | let { 5 | id, // stripe invoice id 6 | customer, // stripe customer id 7 | customer_email, // customer email 8 | subscription, // stripe subscription id 9 | status, // invoice status, 10 | billing_reason, // 11 | lines, // array of items 12 | attempt_count, // number of attempts 13 | } = data; 14 | 15 | let firstItem = lines.data[0]; 16 | let priceId = firstItem.price.id; 17 | 18 | if (billing_reason != 'subscription_cycle') return; 19 | 20 | // handle sub invoice payment failed 21 | 22 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/webhook-templates/subUpdated.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from '@/modules/stripe/init.js'; 2 | 3 | export const subUpdated = async (data) => { 4 | let { 5 | id, // stripe sub id 6 | cancel_at, // when sub will expire 7 | cancel_at_period_end, // sub will cancel at end of cycle 8 | customer // stripe customer id 9 | } = data; 10 | 11 | if (cancel_at_period_end) { 12 | // customer cancelled subscription 13 | } 14 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/stripe/webhook-templates/subWebhooksHandler.txt: -------------------------------------------------------------------------------- 1 | import { stripeCli } from '@/modules/stripe/init.js'; 2 | import { subInvoicePaid } from '@/modules/stripe/subInvoicePaid.js' 3 | import { subInvoicePaymentFailed } from '@/modules/stripe/subInvoicePaymentFailed.js' 4 | import { subUpdated } from '@/modules/stripe/subUpdated.js' 5 | import { subDeleted } from '@/modules/stripe/subDeleted.js' 6 | 7 | export const subWebhooksHandler = async (req, res) => { 8 | let event = req.body; 9 | 10 | switch (event.type) { 11 | 12 | // subscription payment success 13 | case "invoice.paid": 14 | await subInvoicePaid(event.data.object); 15 | break; 16 | 17 | // subscription payment failed 18 | case "invoice.payment_failed": 19 | await subInvoicePaymentFailed(event.data.object); 20 | break; 21 | 22 | // subscription cancelled 23 | case "customer.subscription.updated": 24 | await subUpdated(event.data.object); 25 | break; 26 | 27 | // subscription deleted, set status to deleted 28 | case "customer.subscription.deleted": 29 | await subDeleted(event.data.object); 30 | break; 31 | 32 | } 33 | 34 | res.status(200).send(); 35 | }; -------------------------------------------------------------------------------- /assets/code-templates/express/modules/supabase/auth/supabaseAuthMiddleware.txt: -------------------------------------------------------------------------------- 1 | import { supabaseCli } from "@/modules/supabase/init.js"; 2 | 3 | export const supabaseAuthMiddleware = async (req, res, next) => { 4 | let authHeader = req.headers.authorization; 5 | 6 | if (!authHeader) { 7 | return res.status(401).json({ message: 'Unauthorized' }); 8 | } 9 | 10 | const token = authHeader.split(' ')[1]; 11 | 12 | let { data, error } = await supabaseCli.auth.getUser(token); 13 | if (error) { 14 | console.log("Failed to get supabase auth user:", error); 15 | return res.status(401).json({ message: 'Unauthorized' }); 16 | } 17 | 18 | req.user = data.user 19 | return next(); 20 | 21 | }; 22 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/supabase/initSupabase.txt: -------------------------------------------------------------------------------- 1 | import { SupabaseClient, createClient } from "@supabase/supabase-js"; 2 | 3 | export let supabaseCli: SupabaseClient; 4 | 5 | export const initSupabase = async () => { 6 | let projectUrl = process.env.SUPABASE_PROJECT_URL; 7 | let serviceKey = 8 | process.env.SUPABASE_SERVICE_KEY 9 | 10 | supabaseCli = createClient(projectUrl, serviceKey); 11 | console.log("Supabase module initialised"); 12 | }; 13 | -------------------------------------------------------------------------------- /assets/code-templates/express/modules/supabase/supabaseStarter.txt: -------------------------------------------------------------------------------- 1 | import { supabaseCli } from "@/modules/supabase/init.js"; 2 | 3 | 4 | export const ${funcName} = async () => { 5 | 6 | }; 7 | -------------------------------------------------------------------------------- /assets/code-templates/express/router.txt: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | {{import_statements}} 3 | 4 | export const {{func_name}}_router = express.Router(); 5 | {{endpoints}} 6 | -------------------------------------------------------------------------------- /assets/code-templates/express/router_init.txt: -------------------------------------------------------------------------------- 1 | import express from 'express' 2 | 3 | export const {{func_name}}_router = express.Router(); 4 | 5 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/endpoint_init.txt: -------------------------------------------------------------------------------- 1 | from fastapi.requests import Request 2 | from fastapi.responses import Response 3 | 4 | async def {{func_name}}(req: Request, res: Response): 5 | {{dependency_text}} 6 | next 7 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/general/index_old.txt: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from dotenv import load_dotenv 3 | from contextlib import asynccontextmanager 4 | from fastapi.middleware.cors import CORSMiddleware 5 | from src.api.root_router import root_router 6 | {{import_statements}} 7 | 8 | @asynccontextmanager 9 | async def lifespan(app: FastAPI): 10 | {{func_statements}} 11 | yield 12 | {{end_statements}} 13 | 14 | load_dotenv() 15 | app = FastAPI(lifespan=lifespan) 16 | app.include_router(router=root_router) 17 | 18 | app.add_middleware( 19 | CORSMiddleware, 20 | allow_origins=["*"], 21 | allow_credentials=True, 22 | allow_methods=["*"], 23 | allow_headers=["*"], 24 | ) 25 | 26 | @app.get("/") 27 | def root_get(): 28 | return "Hello world from Visual Backend server!" 29 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/general/init_modules.txt: -------------------------------------------------------------------------------- 1 | {{import_statements}} 2 | 3 | def init_modules(app): 4 | {{func_statements}} 5 | return 6 | 7 | def end_modules(app): 8 | {{end_statements}} 9 | return 10 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/firebase/auth/firebase_auth_middleware.txt: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, Request 2 | from starlette.status import HTTP_401_UNAUTHORIZED 3 | from firebase_admin import auth 4 | 5 | 6 | async def firebase_auth_middleware(req: Request): 7 | # Extract the authorization header 8 | auth_header = req.headers.get("authorization") 9 | if not auth_header: 10 | raise HTTPException( 11 | status_code=HTTP_401_UNAUTHORIZED, detail="Authorization header missing" 12 | ) 13 | 14 | # Token verification 15 | token = auth_header.split(" ")[1] if len(auth_header.split(" ")) > 1 else None 16 | if not token: 17 | raise HTTPException( 18 | status_code=HTTP_401_UNAUTHORIZED, 19 | detail="Invalid authorization header format", 20 | ) 21 | 22 | # Verify token with firebase 23 | try: 24 | decoded_token = auth.verify_id_token(token) 25 | req.state.user_id = decoded_token["user_id"] 26 | req.state.email = decoded_token["email"] 27 | 28 | except Exception as e: 29 | print("Failed to verify id token:", e) 30 | raise HTTPException( 31 | status_code=HTTP_401_UNAUTHORIZED, detail="Invalid token or user not found" 32 | ) 33 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/firebase/firebase_starter.txt: -------------------------------------------------------------------------------- 1 | from firebase_admin import {{fb_service}} 2 | 3 | def {{func_name}}(): 4 | next 5 | 6 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/firebase/init_firebase.txt: -------------------------------------------------------------------------------- 1 | import firebase_admin 2 | from firebase_admin import credentials 3 | from pathlib import Path 4 | import os 5 | import json 6 | 7 | 8 | def init_firebase(): 9 | is_production = os.getenv("ENV") == "production" 10 | if is_production: 11 | fb_credentials = credentials.Certificate( 12 | json.loads(os.getenv("FIREBASE_CREDENTIALS")) 13 | ) 14 | else: 15 | credentials_path = Path("./credentials/firebase_credentials.json") 16 | fb_credentials = credentials.Certificate(credentials_path) 17 | 18 | firebase_admin.initialize_app(credential=fb_credentials) 19 | print("Firebase module initialised") 20 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/gpt/create_chat_completion.txt: -------------------------------------------------------------------------------- 1 | import openai 2 | 3 | def create_chat_completion(prompt): 4 | 5 | # Generate chat completion 6 | response = openai.Completion.create( 7 | engine="gpt-3.5-turbo", 8 | prompt=prompt, 9 | max_tokens=100, 10 | temperature=0.7, 11 | n=1, 12 | stop=None 13 | ) 14 | 15 | # Return the generated completion 16 | return response.choices[0].text.strip() 17 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/gpt/gpt_starter.txt: -------------------------------------------------------------------------------- 1 | import openai 2 | 3 | def {{func_name}}(): 4 | return 5 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/gpt/init_gpt.txt: -------------------------------------------------------------------------------- 1 | import os 2 | import openai 3 | 4 | 5 | def init_gpt(): 6 | print("OpenAI module initialised") 7 | openai.api_key = os.getenv("OPENAI_API_KEY") 8 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/jwt/generate_token.txt: -------------------------------------------------------------------------------- 1 | import jwt 2 | import datetime 3 | import os 4 | 5 | expiry = { 6 | "access": 30, # 30 minues 7 | "refresh": 30 * 24 * 1, # 1 day 8 | } 9 | 10 | 11 | def generate_token(payload, type): 12 | exp_minutes = expiry["access"] if type == "access" else expiry["refresh"] 13 | secret_key = ( 14 | os.getenv("JWT_ACCESS_SECRET") 15 | if type == "access" 16 | else os.getenv("JWT_REFRESH_SECRET") 17 | ) 18 | 19 | if not secret_key: 20 | raise Exception("JWT secret key not found") 21 | 22 | try: 23 | # Add expiration time to payload 24 | exp = datetime.datetime.utcnow() + datetime.timedelta(minutes=exp_minutes) 25 | payload.update({"exp": exp}) 26 | 27 | # Generate JWT token 28 | token = jwt.encode(payload, secret_key, algorithm="HS256") 29 | 30 | return token 31 | 32 | except Exception as e: 33 | print("Failed to generate jwt token") 34 | raise e 35 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/jwt/jwt_auth_middleware.txt: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, Request 2 | from starlette.status import HTTP_401_UNAUTHORIZED 3 | from src.modules.jwt.parse_token import parse_token 4 | 5 | 6 | def jwt_auth_middleware(req: Request): 7 | # Extract the authorization header 8 | auth_header = req.headers.get("authorization") 9 | if not auth_header: 10 | raise HTTPException( 11 | status_code=HTTP_401_UNAUTHORIZED, detail="Authorization header missing" 12 | ) 13 | 14 | # Token verification 15 | token = auth_header.split(" ")[1] if len(auth_header.split(" ")) > 1 else None 16 | if not token: 17 | raise HTTPException( 18 | status_code=HTTP_401_UNAUTHORIZED, 19 | detail="Invalid authorization header format", 20 | ) 21 | 22 | decoded_token = parse_token(token, "access") 23 | if not decoded_token: 24 | raise HTTPException( 25 | status_code=HTTP_401_UNAUTHORIZED, 26 | detail="invalid token", 27 | ) 28 | 29 | # do something with decoded_token 30 | # e.g. req.state.user_id = decoded_token["user_id"] 31 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/jwt/jwt_starter.txt: -------------------------------------------------------------------------------- 1 | import jwt 2 | 3 | def {{func_name}}(): 4 | return 5 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/jwt/parse_token.txt: -------------------------------------------------------------------------------- 1 | import jwt 2 | import os 3 | 4 | 5 | def parse_token(token, type): 6 | secret_key = ( 7 | os.getenv("JWT_ACCESS_SECRET") 8 | if type == "access" 9 | else os.getenv("JWT_REFRESH_SECRET") 10 | ) 11 | 12 | try: 13 | # Decoding the JWT token 14 | decoded_token = jwt.decode(token, secret_key, algorithms=["HS256"]) 15 | 16 | return decoded_token 17 | 18 | except jwt.ExpiredSignatureError: 19 | print("The token has expired.") 20 | return None 21 | 22 | except jwt.InvalidTokenError: 23 | print("Invalid token.") 24 | return None 25 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/mongo/init_mongo.txt: -------------------------------------------------------------------------------- 1 | from pymongo import MongoClient 2 | import os 3 | 4 | def init_mongo(): 5 | mongo_cli = MongoClient( 6 | os.getenv("MONGO_CONN_STRING"), tls=True, tlsAllowInvalidCertificates=True 7 | ) 8 | default_db = mongo_cli.get_database(os.getenv('MONGO_DEFAULT_DB')) 9 | print("Mongo module initialised") 10 | return mongo_cli, default_db 11 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/mongo/mongo_starter.txt: -------------------------------------------------------------------------------- 1 | from fastapi.requests import Request 2 | from pymongo import MongoClient 3 | from pymongo.database import Database 4 | 5 | 6 | def {{func_name}}(req: Request): 7 | mongo_cli: MongoClient = req.app.state.default_db 8 | default_db: Database = req.app.state.default_db 9 | 10 | return 11 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/resend/html_template.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 | <%=varKey %> 6 |

7 | 8 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/resend/init_resend.txt: -------------------------------------------------------------------------------- 1 | import resend 2 | import os 3 | 4 | def init_resend(): 5 | resend.api_key = os.environ["RESEND_API_KEY"] 6 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/resend/resend_starter.txt: -------------------------------------------------------------------------------- 1 | import resend 2 | 3 | def {{func_name}}(): 4 | return 5 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/resend/send_email.txt: -------------------------------------------------------------------------------- 1 | import resend 2 | 3 | def send_email(sender, recipient, subject, html): 4 | params = { 5 | "from": f"Acme <{sender}>", 6 | "to": [recipient], 7 | "subject": subject, 8 | "html": html, 9 | } 10 | 11 | email = resend.Emails.send(params) 12 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/create_checkout.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def create_checkout(): 5 | price_id = "" 6 | client_reference_id = "123" 7 | mode = "subscription" 8 | 9 | session = stripe.checkout.Session.create( 10 | line_items=[ 11 | { 12 | "price": price_id, 13 | "quantity": 2, 14 | }, 15 | ], 16 | success_url="https://example.com/success", 17 | cancel_url="https://example.com/cancel", 18 | client_reference_id=client_reference_id, 19 | mode=mode, 20 | ) 21 | return session.url 22 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/create_customer_portal.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def create_customer_portal(customer_id): 5 | session = stripe.billing_portal.Session.create( 6 | customer=customer_id, return_url="http://localhost:8000" 7 | ) 8 | 9 | return session.url 10 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/init_stripe.txt: -------------------------------------------------------------------------------- 1 | import os 2 | import stripe 3 | 4 | 5 | def init_stripe(): 6 | env = os.getenv("ENV") 7 | if env == "production": 8 | stripe.api_key = os.getenv("STRIPE_LIVE_KEY") 9 | else: 10 | stripe.api_key = os.getenv("STRIPE_TEST_KEY") 11 | 12 | print("Stripe module initialised") 13 | 14 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/stripe_starter.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | def {{func_name}}(): 4 | return 5 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/webhook-templates/sub_deleted.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def sub_deleted(data: dict): 5 | sub_id = data["id"] 6 | customer_id = data["customer"] 7 | cancel_at_period_end = data["cancel_at_period_end"] 8 | 9 | if cancel_at_period_end: 10 | # deletion comes from sub being cancelled by user 11 | pass 12 | 13 | # deletion comes from other reasons 14 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/webhook-templates/sub_invoice_paid.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def sub_invoice_paid(data: dict): 5 | invoice_id = data["id"] 6 | customer_id = data["customer"] 7 | customer_email = data["customer_email"] 8 | subscription_id = data["subscription"] 9 | invoice_status = data["status"] 10 | billing_reason = data["billing_reason"] 11 | lines = data["lines"] 12 | 13 | first_item = lines["data"][0] 14 | price_id = first_item["price"]["id"] 15 | 16 | if ( 17 | billing_reason != "subscription_create" 18 | and billing_reason != "subscription_cycle" 19 | ): 20 | return 21 | 22 | return 23 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/webhook-templates/sub_invoice_payment_failed.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def sub_invoice_payment_failed(data: dict): 5 | invoice_id = data["id"] 6 | customer_id = data["customer"] 7 | customer_email = data["customer_email"] 8 | subscription_id = data["subscription"] 9 | invoice_status = data["status"] 10 | billing_reason = data["billing_reason"] 11 | lines = data["lines"] 12 | 13 | first_item = lines["data"][0] 14 | price_id = first_item["price"]["id"] 15 | 16 | if billing_reason != "subscription_cycle": 17 | return 18 | 19 | return 20 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/webhook-templates/sub_updated.txt: -------------------------------------------------------------------------------- 1 | import stripe 2 | 3 | 4 | def sub_updated(data: dict): 5 | sub_id = data["id"] 6 | cancel_at = data["cancel_at"] 7 | cancel_at_period_end = data["cancel_at_period_end"] 8 | customer_id = data["customer"] 9 | 10 | if cancel_at_period_end: 11 | # customer cancelled subscription 12 | pass 13 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/stripe/webhook-templates/sub_webhooks_handler.txt: -------------------------------------------------------------------------------- 1 | from fastapi import Header, Request 2 | from src.modules.stripe.sub_invoice_paid import sub_invoice_paid 3 | from src.modules.stripe.sub_invoice_payment_failed import sub_invoice_payment_failed 4 | from src.modules.stripe.sub_updated import sub_updated 5 | from src.modules.stripe.sub_deleted import sub_deleted 6 | 7 | 8 | async def sub_webhooks_handler(req: Request, stripe_signature: str = Header(None)): 9 | try: 10 | # To verify webhook signature 11 | # webhook_secret = os.environ["STRIPE_WEBHOOK_SECRET"] 12 | # data = await req.body() 13 | # event = stripe.Webhook.construct_event( 14 | # payload=data, 15 | # # sig_header=stripe_signature, 16 | # # secret=webhook_secret # this is optional 17 | # ) 18 | 19 | event = await req.json() 20 | event_data = event["data"] 21 | 22 | except Exception as e: 23 | print("Failed to handle sub webhooks:", str(e)) 24 | raise e 25 | 26 | event_type = event["type"] 27 | event_data_object = event_data["object"] 28 | 29 | match event_type: 30 | case "invoice.paid": 31 | sub_invoice_paid(event_data_object) 32 | 33 | case "invoice.payment_failed": 34 | sub_invoice_payment_failed(event_data_object) 35 | 36 | case "customer.subscription.updated": 37 | sub_updated(event_data_object) 38 | 39 | case "customer.subscription.deleted": 40 | sub_deleted(event_data_object) 41 | 42 | return {"status": "success"} 43 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/supabase/auth/supabase_auth_middleware.txt: -------------------------------------------------------------------------------- 1 | from fastapi import HTTPException, Request 2 | from starlette.status import HTTP_401_UNAUTHORIZED 3 | from supabase import Client 4 | 5 | async def supabase_auth_middleware(req: Request): 6 | supabase_cli: Client = req.app.state.supabase_cli 7 | 8 | # Extract the authorization header 9 | auth_header = req.headers.get("authorization") 10 | if not auth_header: 11 | raise HTTPException( 12 | status_code=HTTP_401_UNAUTHORIZED, detail="Authorization header missing" 13 | ) 14 | 15 | # Token verification 16 | token = auth_header.split(" ")[1] if len(auth_header.split(" ")) > 1 else None 17 | if not token: 18 | raise HTTPException( 19 | status_code=HTTP_401_UNAUTHORIZED, 20 | detail="Invalid authorization header format", 21 | ) 22 | 23 | try: 24 | # Verify the token with Supabase 25 | data = supabase_cli.auth.get_user(token) 26 | 27 | # Add user information to the request state, if needed 28 | req.state.user = data.user 29 | 30 | except Exception as e: 31 | print("Failed to verify auth token:", e) 32 | raise HTTPException( 33 | status_code=HTTP_401_UNAUTHORIZED, detail="Invalid token or user not found" 34 | ) 35 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/supabase/init_supabase.txt: -------------------------------------------------------------------------------- 1 | from supabase import create_client, Client 2 | import os 3 | 4 | def init_supabase(): 5 | supabase_project_url = os.environ['SUPABASE_PROJECT_URL'] 6 | supabase_service_key = os.environ['SUPABASE_SERVICE_KEY'] 7 | 8 | supabase_cli: Client = create_client( 9 | supabase_url=supabase_project_url, 10 | supabase_key=supabase_service_key, 11 | ) 12 | 13 | print("Supabase module initialised") 14 | return supabase_cli 15 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/modules/supabase/supabase_starter.txt: -------------------------------------------------------------------------------- 1 | from supabase import Client 2 | from fastapi import Request 3 | 4 | def {{func_name}}(req: Request): 5 | supabase_cli: Client = req.app.state.supabase_cli 6 | 7 | next 8 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/router.txt: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter, Depends 2 | {{import_statements}} 3 | 4 | {{func_name}}_router = APIRouter(prefix="{{route_key}}"{{dependencies}}) 5 | {{endpoints}} 6 | -------------------------------------------------------------------------------- /assets/code-templates/fastapi/router_init.txt: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | {{func_name}}_router = APIRouter(prefix="/{{route_key}}") 4 | -------------------------------------------------------------------------------- /assets/entitlements.mac.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.cs.allow-unsigned-executable-memory 6 | 7 | com.apple.security.cs.allow-jit 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /assets/express-template/.dockerignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | /.pnp 4 | .pnp.js 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* -------------------------------------------------------------------------------- /assets/express-template/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | 3 | .DS_Store -------------------------------------------------------------------------------- /assets/express-template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:lts-alpine 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | RUN npm install 8 | 9 | COPY . . 10 | 11 | EXPOSE 8080 12 | 13 | RUN npm run build 14 | 15 | CMD ["node", "dist/index.js", "8080"] 16 | 17 | 18 | -------------------------------------------------------------------------------- /assets/express-template/cloudbuild.yaml: -------------------------------------------------------------------------------- 1 | 2 | steps: 3 | # Build the Docker image 4 | - name: 'gcr.io/cloud-builders/docker' 5 | args: ['build', '-t', 'asia-southeast1-docker.pkg.dev/$PROJECT_ID/visual-backend/64e783dd0aef5509c408938a', '.'] 6 | 7 | # Push the Docker image to Container Registry 8 | - name: 'gcr.io/cloud-builders/docker' 9 | args: ['push', 'asia-southeast1-docker.pkg.dev/$PROJECT_ID/visual-backend/64e783dd0aef5509c408938a'] 10 | 11 | # Deploy the Docker image to Cloud Run 12 | - name: 'gcr.io/cloud-builders/gcloud' 13 | args: ['run', 'deploy', 'id-64e783dd0aef5509c408938a', 14 | '--image', 'asia-southeast1-docker.pkg.dev/$PROJECT_ID/visual-backend/64e783dd0aef5509c408938a', 15 | '--platform', 'managed', 16 | '--region', 'asia-east1', 17 | '--allow-unauthenticated' 18 | ] 19 | 20 | images: 21 | - 'asia-southeast1-docker.pkg.dev/$PROJECT_ID/visual-backend/64e783dd0aef5509c408938a' 22 | -------------------------------------------------------------------------------- /assets/express-template/dist/api/Router.js: -------------------------------------------------------------------------------- 1 | import express from "express"; 2 | export const Router = express.Router(); 3 | //# sourceMappingURL=Router.js.map -------------------------------------------------------------------------------- /assets/express-template/dist/api/Router.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Router.js","sourceRoot":"","sources":["../../src/api/Router.ts"],"names":[],"mappings":"AAAA,OAAO,OAAO,MAAM,SAAS,CAAC;AAE9B,MAAM,CAAC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC"} -------------------------------------------------------------------------------- /assets/express-template/dist/index.js: -------------------------------------------------------------------------------- 1 | import { config } from "dotenv"; 2 | import express from "express"; 3 | import { Router } from "./api/Router.js"; 4 | import bodyParser from "body-parser"; 5 | const init = async () => { 6 | config(); 7 | const app = express(); 8 | app.use(bodyParser.json()); 9 | app.use("/", Router); 10 | app.get("/", (req, res) => res.status(200).send("Hello from local server")); 11 | const args = process.argv.slice(2); 12 | let port = 8080; 13 | if (args.length > 0) { 14 | port = parseInt(args[0]); 15 | } 16 | const server = app.listen(port, () => { 17 | console.log(`Server started at http://localhost:${port}`); 18 | }); 19 | // Handle server shutdown gracefully 20 | process.on("SIGTERM", () => { 21 | // console.log('Received SIGTERM. Shutting down gracefully...'); 22 | server.close(() => { 23 | console.log("Express server closed."); 24 | process.exit(0); 25 | }); 26 | }); 27 | process.on("SIGINT", () => { 28 | // console.log('Received SIGINT. Shutting down gracefully...'); 29 | server.close(() => { 30 | console.log("Express server closed."); 31 | process.exit(0); 32 | }); 33 | }); 34 | }; 35 | init(); 36 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /assets/express-template/dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAChC,OAAO,OAAO,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,UAAU,MAAM,aAAa,CAAC;AAErC,MAAM,IAAI,GAAG,KAAK,IAAI,EAAE;IACvB,MAAM,EAAE,CAAC;IAET,MAAM,GAAG,GAAG,OAAO,EAAE,CAAC;IACtB,GAAG,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;IACrB,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC,CAAC;IAE5E,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACnC,IAAI,IAAI,GAAG,IAAI,CAAC;IAChB,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE;QACpB,IAAI,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;KACzB;IACD,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpC,OAAO,CAAC,GAAG,CAAC,sCAAsC,IAAI,EAAE,CAAC,CAAC;IAC3D,CAAC,CAAC,CAAC;IAEH,oCAAoC;IACpC,OAAO,CAAC,EAAE,CAAC,SAAS,EAAE,GAAG,EAAE;QAC1B,gEAAgE;QAChE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;YACjB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;IAEH,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACzB,+DAA+D;QAC/D,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE;YACjB,OAAO,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC;YACtC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QACjB,CAAC,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC;AACJ,CAAC,CAAC;AAEF,IAAI,EAAE,CAAC"} -------------------------------------------------------------------------------- /assets/express-template/dist/models/project.js: -------------------------------------------------------------------------------- 1 | var _a; 2 | export class ProjectFuncs { 3 | } 4 | _a = ProjectFuncs; 5 | ProjectFuncs.formatProjectName = (name) => name.toLowerCase().replace(" ", "-"); 6 | ProjectFuncs.getProjectUri = (username, projName) => { 7 | return `https://gitlab.com/visual-backend/${username}/${_a.formatProjectName(projName)}.git`; 8 | }; 9 | //# sourceMappingURL=project.js.map -------------------------------------------------------------------------------- /assets/express-template/dist/models/project.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"project.js","sourceRoot":"","sources":["../../src/models/project.ts"],"names":[],"mappings":";AAUA,MAAM,OAAO,YAAY;;;AACjB,8BAAiB,GAAG,CAAC,IAAY,EAAE,EAAE,CAC3C,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,GAAG,EAAE,GAAG,CAAC,AADb,CACc;AAE/B,0BAAa,GAAG,CAAC,QAAgB,EAAE,QAAgB,EAAE,EAAE;IAC7D,OAAO,qCAAqC,QAAQ,IAAI,EAAI,CAAC,iBAAiB,CAC7E,QAAQ,CACR,MAAM,CAAC;AACT,CAAC,AAJmB,CAIlB"} -------------------------------------------------------------------------------- /assets/express-template/dist/models/user.js: -------------------------------------------------------------------------------- 1 | export {}; 2 | //# sourceMappingURL=user.js.map -------------------------------------------------------------------------------- /assets/express-template/dist/models/user.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"user.js","sourceRoot":"","sources":["../../src/models/user.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /assets/express-template/nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "execMap": { 3 | "ts": "tsx" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /assets/express-template/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "express-app", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "type": "module", 7 | "scripts": { 8 | "build": "tspc", 9 | "run:prod": "node dist/index.js", 10 | "dev": "tsx src/index.ts", 11 | "dev2": "nodemon src/index.ts", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [], 15 | "author": "", 16 | "license": "ISC", 17 | "devDependencies": { 18 | "@types/express": "^4.17.17", 19 | "@types/jsonwebtoken": "^9.0.2", 20 | "@types/node": "^20.4.5", 21 | "ts-node": "^10.9.1", 22 | "typescript": "^5.1.6", 23 | "nodemon": "^3.0.1" 24 | }, 25 | "dependencies": { 26 | "body-parser": "^1.20.2", 27 | "cors": "^2.8.5", 28 | "ngrok": "^5.0.0-beta.2", 29 | "dotenv": "^16.3.1", 30 | "express": "^4.18.2", 31 | "ts-patch": "^3.0.2", 32 | "typescript-transform-paths": "^3.4.6", 33 | "tsx": "^4.6.1" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /assets/express-template/src/api/root_router.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | 3 | export const root_router = express.Router(); 4 | -------------------------------------------------------------------------------- /assets/express-template/src/index.ts: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import bodyParser from 'body-parser'; 3 | import cors from 'cors'; 4 | import { config } from 'dotenv'; 5 | import { root_router } from './api/root_router.js'; 6 | import { initModules } from './initModules.js'; 7 | 8 | const init = async () => { 9 | config(); 10 | await initModules(); 11 | const app = express(); 12 | app.use(bodyParser.json()); 13 | app.use(cors()); 14 | app.use('/', root_router); 15 | app.get('/', (req, res) => 16 | res.status(200).send('Hello from visual-backend server!') 17 | ); 18 | 19 | const args = process.argv.slice(2); 20 | let port = 8080; 21 | if (args.length > 0) { 22 | port = parseInt(args[0]); 23 | } 24 | 25 | const server = app.listen(port, () => { 26 | console.log(`Server started at http://localhost:${port}`); 27 | }); 28 | 29 | process.on('SIGTERM', () => { 30 | server.close(() => { 31 | process.exit(0); 32 | }); 33 | }); 34 | 35 | process.on('SIGINT', () => { 36 | server.close(() => { 37 | process.exit(0); 38 | }); 39 | }); 40 | }; 41 | 42 | init(); 43 | -------------------------------------------------------------------------------- /assets/express-template/src/initModules.ts: -------------------------------------------------------------------------------- 1 | export const initModules = async () => {}; 2 | -------------------------------------------------------------------------------- /assets/express-template/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compiler": "ts-patch/compiler" 4 | }, 5 | "ts-node-esm": { 6 | "compiler": "ts-patch/compiler" 7 | }, 8 | "compilerOptions": { 9 | "skipLibCheck": true, 10 | "module": "NodeNext", 11 | "moduleResolution": "NodeNext", 12 | "target": "ES2020", 13 | "sourceMap": true, 14 | "outDir": "dist", 15 | "baseUrl": "./src", 16 | "paths": { 17 | "@/*": ["*"] 18 | }, 19 | "plugins": [ 20 | // Transform paths in output .js files 21 | { "transform": "typescript-transform-paths" }, 22 | 23 | // Transform paths in output .d.ts files (Include this line if you output declarations files) 24 | { "transform": "typescript-transform-paths", "afterDeclarations": true } 25 | ], 26 | "typeRoots": ["node_modules/@types"] 27 | }, 28 | "include": ["src/**/*"] 29 | } 30 | -------------------------------------------------------------------------------- /assets/fastapi-template/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | .ipynb_checkpoints 3 | .mypy_cache 4 | .vscode 5 | __pycache__ 6 | .pytest_cache 7 | htmlcov 8 | dist 9 | site 10 | .coverage 11 | coverage.xml 12 | .netlify 13 | test.db 14 | log.txt 15 | Pipfile.lock 16 | env3.* 17 | env 18 | docs_build 19 | site_build 20 | venv 21 | docs.zip 22 | archive.zip 23 | 24 | # vim temporary files 25 | *~ 26 | .*.sw? 27 | .cache 28 | 29 | # macOS 30 | .DS_Store 31 | .env 32 | .venv 33 | credentials 34 | -------------------------------------------------------------------------------- /assets/fastapi-template/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11 2 | 3 | WORKDIR /app 4 | 5 | COPY requirements.txt ./requirements.txt 6 | 7 | RUN pip install --no-cache-dir --upgrade -r /app/requirements.txt 8 | 9 | COPY . . 10 | 11 | EXPOSE 8080 12 | 13 | ENV ENV=production 14 | 15 | CMD uvicorn src.main:app --host 0.0.0.0 --port 8080 16 | -------------------------------------------------------------------------------- /assets/fastapi-template/requirements.txt: -------------------------------------------------------------------------------- 1 | fastapi[all] 2 | python-dotenv 3 | -------------------------------------------------------------------------------- /assets/fastapi-template/run.py: -------------------------------------------------------------------------------- 1 | import uvicorn 2 | import sys 3 | 4 | if __name__ == "__main__": 5 | uvicorn.run( 6 | "src.main:app", 7 | reload=True, 8 | port=(int(sys.argv[1]) if len(sys.argv) > 1 else 8000), 9 | ) 10 | -------------------------------------------------------------------------------- /assets/fastapi-template/src/api/root_router.py: -------------------------------------------------------------------------------- 1 | from fastapi import APIRouter 2 | 3 | root_router = APIRouter(prefix="/auth", tags=["auth"]) 4 | -------------------------------------------------------------------------------- /assets/fastapi-template/src/init_modules.py: -------------------------------------------------------------------------------- 1 | def init_modules(app): 2 | return 3 | 4 | 5 | def end_modules(app): 6 | return 7 | -------------------------------------------------------------------------------- /assets/fastapi-template/src/main.py: -------------------------------------------------------------------------------- 1 | from fastapi import FastAPI 2 | from dotenv import load_dotenv 3 | from contextlib import asynccontextmanager 4 | from src.api.root_router import root_router 5 | from src.init_modules import init_modules, end_modules 6 | from fastapi.middleware.cors import CORSMiddleware 7 | 8 | 9 | @asynccontextmanager 10 | async def lifespan(app: FastAPI): 11 | init_modules(app) 12 | yield 13 | end_modules(app) 14 | 15 | 16 | load_dotenv() 17 | app = FastAPI(lifespan=lifespan) 18 | app.include_router(router=root_router) 19 | app.add_middleware( 20 | CORSMiddleware, 21 | allow_origins=["*"], 22 | allow_credentials=True, 23 | allow_methods=["*"], 24 | allow_headers=["*"], 25 | ) 26 | 27 | 28 | @app.get("/") 29 | def root_get(): 30 | return "Hello world from Visual Backend server!" 31 | 32 | 33 | # from fastapi import FastAPI 34 | 35 | # from api.router import main_router 36 | 37 | # app = FastAPI() 38 | # app.include_router(router=main_router) 39 | -------------------------------------------------------------------------------- /assets/github-images/demo_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/github-images/demo_screenshot.png -------------------------------------------------------------------------------- /assets/github-images/github_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/github-images/github_banner.png -------------------------------------------------------------------------------- /assets/github-images/module_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/github-images/module_screenshot.png -------------------------------------------------------------------------------- /assets/github-images/routes_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/github-images/routes_screenshot.png -------------------------------------------------------------------------------- /assets/github-images/stripe_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/github-images/stripe_screenshot.png -------------------------------------------------------------------------------- /assets/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icon.icns -------------------------------------------------------------------------------- /assets/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icon.ico -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icon.png -------------------------------------------------------------------------------- /assets/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-128x128.png -------------------------------------------------------------------------------- /assets/icons/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-16x16.png -------------------------------------------------------------------------------- /assets/icons/icon-256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-256x256.png -------------------------------------------------------------------------------- /assets/icons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-32x32.png -------------------------------------------------------------------------------- /assets/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-512x512.png -------------------------------------------------------------------------------- /assets/icons/icon-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/assets/icons/icon-64x64.png -------------------------------------------------------------------------------- /release/app/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "visual-backend", 3 | "version": "1.0.0", 4 | "description": "A foundation for scalable desktop apps", 5 | "license": "MIT", 6 | "author": { 7 | "name": "Visual Backend", 8 | "email": "team@visual-backend.com", 9 | "url": "https://visual-backend.com" 10 | }, 11 | "main": "./dist/main/main.js", 12 | "scripts": { 13 | "rebuild": "node -r ts-node/register ../../.erb/scripts/electron-rebuild.js", 14 | "postinstall": "npm run rebuild && npm run link-modules", 15 | "link-modules": "node -r ts-node/register ../../.erb/scripts/link-modules.ts" 16 | }, 17 | "dependencies": { 18 | "sqlite3": "^5.1.6" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/__tests__/App.test.tsx: -------------------------------------------------------------------------------- 1 | import '@testing-library/jest-dom'; 2 | import { render } from '@testing-library/react'; 3 | import App from '../renderer/App'; 4 | 5 | describe('App', () => { 6 | it('should render', () => { 7 | expect(render()).toBeTruthy(); 8 | }); 9 | }); 10 | -------------------------------------------------------------------------------- /src/main/generate/cloudbuild.ts: -------------------------------------------------------------------------------- 1 | import { serverLocation } from '@/renderer/misc/constants'; 2 | import { FileFuncs } from '../helpers/fileFuncs'; 3 | import { MainFuncs } from '@/shared/utils/MainFuncs'; 4 | import path from 'path'; 5 | 6 | export const generateCloudBuildYaml = ( 7 | projId: string, 8 | secretsString: string 9 | ) => { 10 | let loc = serverLocation; 11 | return ` 12 | steps: 13 | # Build the Docker image 14 | - name: 'gcr.io/cloud-builders/docker' 15 | args: ['build', '-t', '${loc}-docker.pkg.dev/$PROJECT_ID/visual-backend/${projId}', '.'] 16 | 17 | # Push the Docker image to Container Registry 18 | - name: 'gcr.io/cloud-builders/docker' 19 | args: ['push', '${loc}-docker.pkg.dev/$PROJECT_ID/visual-backend/${projId}'] 20 | 21 | # Deploy the Docker image to Cloud Run 22 | - name: 'gcr.io/cloud-builders/gcloud' 23 | 24 | entrypoint: 'bash' 25 | args: [ 26 | '-c', 27 | 'gcloud run deploy id-${projId} 28 | --image=${loc}-docker.pkg.dev/$PROJECT_ID/visual-backend/${projId} 29 | --platform=managed 30 | --region=${loc} 31 | --allow-unauthenticated 32 | ${secretsString} 33 | ' 34 | ] 35 | 36 | images: 37 | - '${loc}-docker.pkg.dev/$PROJECT_ID/visual-backend/${projId}' 38 | `; 39 | }; 40 | 41 | export const writeCloudBuildYaml = async ( 42 | projKey: string, 43 | projId: string, 44 | secretsString: string 45 | ) => { 46 | await FileFuncs.writeFile( 47 | path.join(MainFuncs.getProjectPath(projKey), 'cloudbuild.yaml'), 48 | generateCloudBuildYaml(projId, secretsString) 49 | ); 50 | }; 51 | -------------------------------------------------------------------------------- /src/main/generate/func.ts: -------------------------------------------------------------------------------- 1 | import { BFunc, BFuncHelpers } from '@/shared/models/BFunc'; 2 | import { FileFuncs } from '../helpers/fileFuncs'; 3 | import { MainFuncs } from '@/shared/utils/MainFuncs'; 4 | import path from 'path'; 5 | 6 | export const createFuncFile = async ( 7 | projKey: string, 8 | newFunc: BFunc, 9 | contents: string 10 | ) => { 11 | let funcName = BFuncHelpers.getFuncName(newFunc); 12 | 13 | let modulePath = path.join( 14 | MainFuncs.getProjectPath(projKey), 15 | 'src', 16 | 'modules', 17 | newFunc.moduleKey, 18 | newFunc.funcGroup 19 | ); 20 | let filePath = path.join(modulePath, `${funcName}.ts`); 21 | 22 | await FileFuncs.createDirIfNotExists(modulePath); 23 | await FileFuncs.writeFile(filePath, contents); 24 | }; 25 | -------------------------------------------------------------------------------- /src/main/generate/modules/firebase/firebaseGen.ts: -------------------------------------------------------------------------------- 1 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 2 | import { ProjectService } from '@/main/services/ProjectService'; 3 | import { BFunc, BFuncHelpers } from '@/shared/models/BFunc'; 4 | import { BModule, modConfig } from '@/shared/models/BModule'; 5 | import { PathFuncs } from '@/shared/utils/MainFuncs'; 6 | import path from 'path'; 7 | 8 | // export const writeFirebaseCredentials = async ( 9 | // projectId: string, 10 | // projKey: string, 11 | // credPath: string 12 | // ) => { 13 | // let credDir = path.join(PathFuncs.getProjectPath(projKey), 'credentials'); 14 | // let targetFile = path.join(credDir, 'firebase_credentials.json'); 15 | 16 | // await FileFuncs.createDirIfNotExists(credDir); 17 | // let data: any = await FileFuncs.readFile(credPath); 18 | 19 | // // 3. Add env file to firebase 20 | // ProjectService.addEnvVars({ 21 | // projectId, 22 | // envVars: [{ key: 'FIREBASE_CREDENTIALS', val: data }], 23 | // }); 24 | 25 | // await FileFuncs.writeFile(targetFile, data); 26 | // console.log('Successfully written firebase credentials'); 27 | // }; 28 | 29 | export const writeFbInit = async (projKey: string) => { 30 | let templatePath = path.join( 31 | PathFuncs.getCodeTemplatesPath(), 32 | 'firebase', 33 | 'initFirebaseTemplate.ts' 34 | ); 35 | let fbModulePath = path.join(PathFuncs.getModulesPath(projKey), 'firebase'); 36 | let fbInitPath = path.join(fbModulePath, 'init.ts'); 37 | 38 | await FileFuncs.createDirIfNotExists(fbModulePath); 39 | 40 | let data: any = await FileFuncs.readFile(templatePath); 41 | await FileFuncs.writeFile(fbInitPath, data); 42 | }; 43 | -------------------------------------------------------------------------------- /src/main/ipc/auth/authFuncs.ts: -------------------------------------------------------------------------------- 1 | import { accessTokenKey, refreshTokenKey } from '@/renderer/misc/constants'; 2 | import Store from 'electron-store'; 3 | 4 | export const setAuthTokensFunc = (data: any) => { 5 | const { accessToken, refreshToken } = data; 6 | let s = new Store(); 7 | s.set(accessTokenKey, accessToken); 8 | s.set(refreshTokenKey, refreshToken); 9 | }; 10 | export const setAuthTokens = async ( 11 | event: Electron.IpcMainInvokeEvent, 12 | payload: any, 13 | g: { 14 | store: Store; 15 | } 16 | ) => { 17 | const { accessToken, refreshToken } = payload; 18 | g.store.set(accessTokenKey, accessToken); 19 | g.store.set(refreshTokenKey, refreshToken); 20 | return true; 21 | }; 22 | 23 | export const getAccessToken = async ( 24 | event: Electron.IpcMainInvokeEvent, 25 | payload: any, 26 | g: { 27 | store: Store; 28 | } 29 | ) => { 30 | let accessToken = g.store.get(accessTokenKey); 31 | return accessToken; 32 | }; 33 | 34 | export const getRefreshToken = async ( 35 | event: Electron.IpcMainInvokeEvent, 36 | payload: any 37 | ) => { 38 | let s = new Store(); 39 | return s.get(refreshTokenKey); 40 | }; 41 | 42 | export const deleteTokens = async ( 43 | event: Electron.IpcMainInvokeEvent, 44 | payload: any 45 | ) => { 46 | let s = new Store(); 47 | s.delete(refreshTokenKey); 48 | s.delete(accessTokenKey); 49 | return true; 50 | }; 51 | -------------------------------------------------------------------------------- /src/main/ipc/auth/subscriptionFuncs.ts: -------------------------------------------------------------------------------- 1 | import { StripeService } from '@/main/services/StripeService'; 2 | import electron from 'electron'; 3 | 4 | export const openCheckoutPage = async ( 5 | e: Electron.IpcMainInvokeEvent, 6 | payload: any 7 | ) => { 8 | let { data } = await StripeService.getCheckoutLink(); 9 | electron.shell.openExternal(data); 10 | }; 11 | 12 | export const openCustomerPortal = async ( 13 | e: Electron.IpcMainInvokeEvent, 14 | payload: any 15 | ) => { 16 | let { data } = await StripeService.getPortalLink(); 17 | electron.shell.openExternal(data); 18 | }; 19 | -------------------------------------------------------------------------------- /src/main/ipc/project/editorFuncs.ts: -------------------------------------------------------------------------------- 1 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 2 | import { GenFuncs } from '@/shared/utils/GenFuncs'; 3 | import { PathFuncs } from '@/shared/utils/MainFuncs'; 4 | import { exec } from 'child_process'; 5 | import { app, shell } from 'electron'; 6 | 7 | export const getFileContents = async ( 8 | event: Electron.IpcMainInvokeEvent, 9 | payload: any 10 | ) => { 11 | const { path, projKey } = payload; 12 | let contents = await FileFuncs.readFile( 13 | `${PathFuncs.getProjectPath(projKey)}${path}` 14 | ); 15 | 16 | return contents; 17 | }; 18 | 19 | export const saveFileContents = async ( 20 | event: Electron.IpcMainInvokeEvent, 21 | payload: any 22 | ) => { 23 | const { path, projKey, contents } = payload; 24 | let filePath = `${PathFuncs.getProjectPath(projKey)}${path}`; 25 | await FileFuncs.writeFile(filePath, contents); 26 | 27 | return true; 28 | }; 29 | 30 | export const openFile = async (event: Electron.IpcMainEvent, payload: any) => { 31 | const { path, projKey, contents } = payload; 32 | let filePath = `${PathFuncs.getProjectPath(projKey)}${path}`; 33 | console.log('Opening file path:', filePath); 34 | shell.openPath(filePath); 35 | // exec(`"${filePath}"`); 36 | }; 37 | 38 | export const openProjectInVs = async ( 39 | event: Electron.IpcMainEvent, 40 | payload: any 41 | ) => { 42 | const { projKey } = payload; 43 | exec(`cd "${PathFuncs.getProjectPath(projKey)}" && code .`); 44 | }; 45 | 46 | export const openProjectInIntelliJ = async ( 47 | event: Electron.IpcMainEvent, 48 | payload: any 49 | ) => { 50 | const { projKey } = payload; 51 | if (process.platform == 'win32') { 52 | exec(`cd "${PathFuncs.getProjectPath(projKey)}" && idea .`); 53 | } else { 54 | exec(`open -na "IntelliJ IDEA.app" "${PathFuncs.getProjectPath(projKey)}"`); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/main/ipc/project/envFuncs.ts: -------------------------------------------------------------------------------- 1 | import { 2 | removeEnvVars, 3 | updateEnvVars, 4 | writeEnvVars, 5 | } from '@/main/generate/env'; 6 | import { PathFuncs } from '@/shared/utils/MainFuncs'; 7 | import fs from 'fs'; 8 | import path from 'path'; 9 | 10 | function parseEnvFile(filePath: string) { 11 | try { 12 | // Read the content of the environment file 13 | const data = fs.readFileSync(filePath, 'utf8'); 14 | 15 | // Split the file content into lines 16 | const lines = data.split('\n'); 17 | 18 | // Initialize an empty array to store key-value pairs 19 | const envVariables = []; 20 | 21 | // Iterate through each line and parse key-value pairs 22 | for (const line of lines) { 23 | // Remove leading and trailing whitespace 24 | const trimmedLine = line.trim(); 25 | 26 | // Ignore lines starting with "#" (comments) or empty lines 27 | if (!trimmedLine || trimmedLine.startsWith('#')) { 28 | continue; 29 | } 30 | 31 | // Split the line into key and value at the first "=" character 32 | const [key, value] = trimmedLine.split('='); 33 | 34 | // Store the key-value pair in the array 35 | envVariables.push({ key, value }); 36 | } 37 | 38 | return envVariables; 39 | } catch (err: any) { 40 | console.error(`Error parsing env file: ${err.message}`); 41 | return []; 42 | } 43 | } 44 | 45 | export const getEnvVars = (e: Electron.IpcMainInvokeEvent, p: any) => { 46 | let { projKey } = p; 47 | let envPath = path.join(PathFuncs.getProjectPath(projKey), '.env'); 48 | 49 | let envVars = parseEnvFile(envPath); 50 | 51 | return envVars; 52 | }; 53 | 54 | export const createEnvVar = async (e: Electron.IpcMainInvokeEvent, p: any) => { 55 | let { projId, projKey, key, val } = p; 56 | await writeEnvVars(projId, projKey, [{ key, val }]); 57 | return; 58 | }; 59 | 60 | export const editEnvVars = async (e: Electron.IpcMainInvokeEvent, p: any) => { 61 | let { projId, projKey, key, val } = p; 62 | await updateEnvVars(projId, projKey, [{ key, val }]); 63 | return; 64 | }; 65 | 66 | export const deleteEnvVar = async (e: Electron.IpcMainInvokeEvent, p: any) => { 67 | let { projdId, projKey, key } = p; 68 | await removeEnvVars(projdId, projKey, [{ key, val: '' }]); 69 | return; 70 | }; 71 | -------------------------------------------------------------------------------- /src/main/ipc/project/fastapi/pythonProjectFuncs.ts: -------------------------------------------------------------------------------- 1 | import { connectDb } from '@/main/db/connect'; 2 | import { insertRoute } from '@/main/db/route/routeQueries'; 3 | import { 4 | checkPython3Ver, 5 | createVirtualEnv, 6 | installPyRequirements, 7 | } from '@/main/generate/fastapi/pythonInstall'; 8 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 9 | import { RouteType } from '@/shared/models/route'; 10 | import { MainFuncs } from '@/shared/utils/MainFuncs'; 11 | import path from 'path'; 12 | 13 | // PYTHON FUNCTIONS 14 | export const createFastAPIProject = async (payload: any) => { 15 | const { projKey } = payload; 16 | let projectDir = MainFuncs.getProjectPath(projKey); 17 | 18 | // 1. Check if python3 is installed 19 | console.log('1. Checking that python3 is installed'); 20 | let { installed, error } = await checkPython3Ver(); 21 | if (!installed) { 22 | return { error: 'python3 not found' }; 23 | } 24 | 25 | // 3. Copy project over 26 | console.log('2. Creating project folder'); 27 | let assetDir = path.join(MainFuncs.getAssetsPath(), 'fastapi-template'); 28 | await FileFuncs.copyDir(assetDir, projectDir); 29 | 30 | // 4. Create db 31 | console.log('3. Creating db file'); 32 | let dbPath = path.join(projectDir, `${projKey}.db`); 33 | FileFuncs.createFileIfNotExists(dbPath); 34 | await connectDb(projKey); 35 | 36 | // 5. insert into db 37 | console.log('4. Inserting root route'); 38 | let newRoute = await insertRoute({ 39 | key: '', 40 | parentPath: '', 41 | parentFilePath: '', 42 | parentId: -1, 43 | type: RouteType.group, 44 | }); 45 | 46 | // 6. Run pip install 47 | try { 48 | console.log('3. Creating python venv'); 49 | await createVirtualEnv(projKey); 50 | 51 | console.log('4. Installing python requirements.txt'); 52 | let installed = await installPyRequirements(projKey); 53 | return { installed, error: null }; 54 | } catch (error) { 55 | console.log('Failed to create python env or install reqs:', error); 56 | return { installed: false, error: null }; 57 | // return { error: 'Failed to create python venv & install requirements' }; 58 | } 59 | }; 60 | -------------------------------------------------------------------------------- /src/main/ipc/project/modules/firebase/firebaseAuthFuncs.ts: -------------------------------------------------------------------------------- 1 | import { insertFuncQuery } from '@/main/db/funcs/funcQueries'; 2 | // import { writeFbAuthFuncFile } from '@/main/generate/modules/firebase/firebaseGen'; 3 | import { BFunc } from '@/shared/models/BFunc'; 4 | import { BModuleType } from '@/shared/models/BModule'; 5 | 6 | export const createFirebaseAuthFunc = async ( 7 | e: Electron.IpcMainInvokeEvent, 8 | p: any 9 | ) => { 10 | // 1. Insert func module query 11 | const { funcName, projKey } = p; 12 | // let lastId = await insertFuncQuery(funcName, BModuleType.FirebaseAuth, '*'); 13 | if (!lastId) { 14 | return { error: 'Failed to insert' }; 15 | } 16 | // 2. Create new folder & file 17 | let newFunc: BFunc = { 18 | id: lastId, 19 | key: funcName, 20 | moduleKey: BModuleType.FirebaseAuth, 21 | funcGroup: '*', 22 | }; 23 | 24 | // await writeFbAuthFuncFile(projKey, newFunc); 25 | 26 | return { error: null, newFunc }; 27 | }; 28 | -------------------------------------------------------------------------------- /src/main/ipc/project/modules/mongodb/mongoFuncs.ts: -------------------------------------------------------------------------------- 1 | import { MongoClient } from 'mongodb'; 2 | import { GenFuncs } from '@/shared/utils/GenFuncs'; 3 | 4 | export const getMongoDbs = async ( 5 | event: Electron.IpcMainInvokeEvent, 6 | payload: string 7 | ) => { 8 | // await GenFuncs.timeout(500); 9 | console.log('Getting mongodbs'); 10 | try { 11 | let client = new MongoClient(payload, { 12 | tlsAllowInvalidCertificates: true, 13 | }); 14 | await client.connect(); 15 | const admin = client.db('admin'); 16 | 17 | const result = await admin.command({ listDatabases: 1, nameOnly: true }); 18 | return result.databases; 19 | } catch { 20 | console.log('Failed to get mongo client'); 21 | return undefined; 22 | } 23 | }; 24 | 25 | export const getDbCols = async (connString: string, dbName: string) => { 26 | const client = new MongoClient(connString); 27 | try { 28 | await client.connect(); 29 | const database = client.db(dbName); 30 | const collectionNames = await database.listCollections().toArray(); 31 | const collectionNamesArray = collectionNames.map( 32 | (collection) => collection.name 33 | ); 34 | return collectionNamesArray; 35 | } catch (err) { 36 | console.error('Error getting collections:', err); 37 | throw err; 38 | } finally { 39 | client.close(); 40 | } 41 | }; 42 | 43 | export const getMongoCols = async ( 44 | event: Electron.IpcMainInvokeEvent, 45 | payload: any 46 | ) => { 47 | // 1. get cols 48 | const { connString, dbName } = payload; 49 | 50 | try { 51 | let cols = await getDbCols(connString, dbName); 52 | return { cols }; 53 | } catch (error) { 54 | return { error: 'Failed to connect to mongodb' }; 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /src/main/ipc/project/modules/resend/resendFuncs.ts: -------------------------------------------------------------------------------- 1 | import { insertFuncQuery } from '@/main/db/funcs/funcQueries'; 2 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 3 | import { BFuncHelpers } from '@/shared/models/BFunc'; 4 | import { PathFuncs } from '@/shared/utils/MainFuncs'; 5 | import path from 'path'; 6 | 7 | export enum TemplateType { 8 | ReactEmail = 'react-email', 9 | Html = 'html', 10 | } 11 | 12 | export const createEmailTemplate = async ( 13 | e: Electron.IpcMainInvokeEvent, 14 | payload: any 15 | ) => { 16 | const { projKey, name, type } = payload; 17 | 18 | // copy paste from template 19 | let resendTemplatePath = path.join( 20 | PathFuncs.getCodeTemplatesPath(), 21 | 'resend' 22 | ); 23 | let templatesFolder = path.join( 24 | PathFuncs.getModulesPath(projKey), 25 | 'resend', 26 | 'templates' 27 | ); 28 | 29 | let templatePath, targetPath, extension; 30 | let funcName = BFuncHelpers.nameToFuncName(name); 31 | 32 | if (type == TemplateType.Html) { 33 | templatePath = path.join(resendTemplatePath, 'htmlTemplate.txt'); 34 | targetPath = path.join(templatesFolder, `${funcName}.html`); 35 | extension = 'html'; 36 | } else { 37 | templatePath = path.join(resendTemplatePath, 'reactEmailTemplate.txt'); 38 | targetPath = path.join(templatesFolder, `${funcName}.tsx`); 39 | extension = 'tsx'; 40 | } 41 | 42 | await FileFuncs.createDirIfNotExists(templatesFolder); 43 | await FileFuncs.copyFileContent(templatePath, targetPath); 44 | 45 | // insert func 46 | let newFunc = await insertFuncQuery(name, 'resend', 'templates', extension); 47 | return { newFunc, error: null }; 48 | }; 49 | -------------------------------------------------------------------------------- /src/main/ipc/project/modules/stripe/stripeFuncs.ts: -------------------------------------------------------------------------------- 1 | import { insertFuncQuery } from '@/main/db/funcs/funcQueries'; 2 | import { modConfig } from '@/shared/models/BModule'; 3 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 4 | import { MainFuncs, PathFuncs } from '@/shared/utils/MainFuncs'; 5 | import path from 'path'; 6 | import { BFuncHelpers } from '@/shared/models/BFunc'; 7 | import { GenFuncs } from '@/shared/utils/GenFuncs'; 8 | import { ProjectType } from '@/shared/models/project'; 9 | 10 | export const addWebhookTemplate = async ( 11 | payload: any, 12 | index: number, 13 | projType: ProjectType = ProjectType.Express 14 | ) => { 15 | let { projKey, module } = payload; 16 | 17 | let mConfig = GenFuncs.getModConfig(module.key, projType); 18 | let templates = mConfig.webhookTemplates; 19 | let template = templates[index]; 20 | 21 | let newFuncs = []; 22 | 23 | for (let i = 0; i < template.functions.length; i++) { 24 | let func = template.functions[i]; 25 | let newFunc = await insertFuncQuery( 26 | func.key, 27 | module.key, 28 | func.funcGroup, 29 | MainFuncs.getExtension(projType) 30 | ); 31 | newFuncs.push(newFunc); 32 | 33 | let fromPath = path.join( 34 | PathFuncs.getWebhookTemplatesPath(module.key, projType), 35 | `${newFunc?.key}.txt` 36 | ); 37 | 38 | let toPath = path.join( 39 | PathFuncs.getModulesPath(projKey), 40 | PathFuncs.getModulePath(mConfig.path), 41 | BFuncHelpers.getFuncPath(newFunc!) 42 | ); 43 | await FileFuncs.copyFileContent(fromPath, toPath); 44 | } 45 | return newFuncs; 46 | }; 47 | 48 | export const addWebhookTemplates = async ( 49 | e: Electron.IpcMainInvokeEvent, 50 | payload: any 51 | ) => { 52 | let { projKey, module, templatesChecked } = payload; 53 | let { projType } = MainFuncs.getCurProject(); 54 | 55 | let mConfig = MainFuncs.getModConfig(module.key, projType); 56 | 57 | let templates = mConfig.webhookTemplates; 58 | let returnFuncs: any = []; 59 | for (let i = 0; i < templates.length; i++) { 60 | if (templatesChecked[i]) { 61 | returnFuncs = returnFuncs.concat( 62 | await addWebhookTemplate(payload, i, projType) 63 | ); 64 | } 65 | } 66 | return returnFuncs; 67 | }; 68 | -------------------------------------------------------------------------------- /src/main/ipc/project/modules/supabase/supabaseFuncs.ts: -------------------------------------------------------------------------------- 1 | import { writeEnvVars } from '@/main/generate/env'; 2 | import { installPackages } from '@/main/generate/install'; 3 | import { modConfig } from '@/shared/models/BModule'; 4 | import { writeModuleStarterFuncs, writeModuleTemplate } from '../helpers'; 5 | 6 | export const createSupabaseModuleFiles = async (payload: any) => { 7 | let { projId, projKey, key } = payload; 8 | let bConfig = modConfig[key]; 9 | 10 | let envs: Array<{ key: string; val: string }> = []; 11 | for (let i = 0; i < bConfig.envVars.length; i++) { 12 | let key = bConfig.envVars[i]; 13 | let val = payload[key]; 14 | envs.push({ key, val }); 15 | } 16 | 17 | let promises = []; 18 | promises.push(writeEnvVars(projId, projKey, envs)); 19 | promises.push(installPackages(bConfig.dependencies, projKey)); 20 | 21 | // 3. create init file 22 | if (bConfig.initFile) { 23 | promises.push( 24 | writeModuleTemplate(bConfig.path, bConfig.initFile, `init.ts`, projKey) 25 | ); 26 | } 27 | 28 | // 4. handle starter funcs 29 | try { 30 | let newFuncs = await writeModuleStarterFuncs(projKey, key); 31 | await Promise.all(promises); 32 | return newFuncs; 33 | } catch (error) { 34 | console.log('Failed to create module files'); 35 | return { error: 'Failed to create module files' }; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /src/main/ipc/project/packageFuncs.ts: -------------------------------------------------------------------------------- 1 | import { deletePackages, installPackages } from '@/main/generate/install'; 2 | import { FileFuncs } from '@/main/helpers/fileFuncs'; 3 | import { MainFuncs } from '@/shared/utils/MainFuncs'; 4 | import path from 'path'; 5 | 6 | export const getProjectPackages = async ( 7 | e: Electron.IpcMainInvokeEvent, 8 | payload: any 9 | ) => { 10 | let { projKey } = payload; 11 | let packageJsonContents: any = await FileFuncs.readFile( 12 | path.join(MainFuncs.getProjectPath(projKey), 'package.json') 13 | ); 14 | let pkgJson = JSON.parse(packageJsonContents); 15 | let dependencies = pkgJson.dependencies; 16 | return dependencies; 17 | }; 18 | 19 | export const installNpmPackage = async ( 20 | e: Electron.IpcMainInvokeEvent, 21 | payload: any 22 | ) => { 23 | const { projKey, pkgName } = payload; 24 | console.log('Installing package:', payload); 25 | try { 26 | await installPackages([pkgName], projKey); 27 | } catch (error) { 28 | return { error: 'Failed' }; 29 | } 30 | return {}; 31 | }; 32 | 33 | export const deleteNpmPackage = async ( 34 | e: Electron.IpcMainInvokeEvent, 35 | payload: any 36 | ) => { 37 | const { projKey, pkgName } = payload; 38 | console.log('Deleting package:', payload); 39 | try { 40 | await deletePackages([pkgName], projKey); 41 | } catch (error) { 42 | return { error: 'Failed' }; 43 | } 44 | return {}; 45 | }; 46 | -------------------------------------------------------------------------------- /src/main/services/GptService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { requestInterceptor, responseInterceptor } from './config'; 3 | import { endpoint } from '@/renderer/misc/constants'; 4 | import { ProjectType } from '@/shared/models/project'; 5 | 6 | axios.interceptors.request.use(requestInterceptor, (error) => 7 | Promise.reject(error) 8 | ); 9 | 10 | axios.interceptors.response.use((response) => { 11 | return response; 12 | }, responseInterceptor); 13 | 14 | export class GptService { 15 | static generateFunc = async (data: { 16 | funcScaffold: string; 17 | funcName: string; 18 | serviceName: string; 19 | details: string; 20 | projectType: ProjectType; 21 | }) => await axios.post(`${endpoint}/private/gpt/generate_function`, data); 22 | } 23 | -------------------------------------------------------------------------------- /src/main/services/ProjectService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { requestInterceptor, responseInterceptor } from './config'; 3 | import { endpoint } from '@/renderer/misc/constants'; 4 | 5 | axios.interceptors.request.use(requestInterceptor, (error) => 6 | Promise.reject(error) 7 | ); 8 | 9 | axios.interceptors.response.use((response) => { 10 | return response; 11 | }, responseInterceptor); 12 | 13 | export class ProjectService { 14 | static getProjectSecretStatements = async (projId: string) => 15 | await axios.get(`${endpoint}/private/projects/${projId}/secrets`); 16 | 17 | static addEnvVars = async (data: any) => 18 | await axios.post(`${endpoint}/private/projects/env_vars`, data); 19 | 20 | static deleteEnvVars = async (data: any) => 21 | await axios.post(`${endpoint}/private/projects/delete_env_vars`, data); 22 | 23 | static updateEnvVars = async (data: any) => 24 | await axios.post(`${endpoint}/private/projects/update_env_vars`, data); 25 | } 26 | -------------------------------------------------------------------------------- /src/main/services/StripeService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { requestInterceptor, responseInterceptor } from './config'; 3 | import { endpoint } from '@/renderer/misc/constants'; 4 | 5 | axios.interceptors.request.use(requestInterceptor, (error) => 6 | Promise.reject(error) 7 | ); 8 | 9 | axios.interceptors.response.use((response) => { 10 | return response; 11 | }, responseInterceptor); 12 | 13 | export class StripeService { 14 | static getCheckoutLink = async () => 15 | await axios.get(`${endpoint}/private/stripe/checkout_link`); 16 | static getPortalLink = async () => 17 | await axios.get(`${endpoint}/private/stripe/portal_link`); 18 | } 19 | -------------------------------------------------------------------------------- /src/main/services/UserService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { requestInterceptor, responseInterceptor } from './config'; 3 | import { endpoint } from '@/renderer/misc/constants'; 4 | 5 | axios.interceptors.request.use(requestInterceptor, (error) => 6 | Promise.reject(error) 7 | ); 8 | 9 | axios.interceptors.response.use((response) => { 10 | return response; 11 | }, responseInterceptor); 12 | 13 | export class UserService {} 14 | -------------------------------------------------------------------------------- /src/main/services/config.ts: -------------------------------------------------------------------------------- 1 | import { 2 | accessTokenKey, 3 | endpoint, 4 | refreshTokenKey, 5 | } from '@/renderer/misc/constants'; 6 | import axios, { AxiosError } from 'axios'; 7 | import Store from 'electron-store'; 8 | import { setAuthTokens, setAuthTokensFunc } from '../ipc/auth/authFuncs'; 9 | 10 | export const isPrivatePath = (url: string) => { 11 | let parsedUrl = new URL(url); 12 | let authPath = parsedUrl.pathname.split('/')[1]; 13 | if (authPath == 'private') return true; 14 | return false; 15 | }; 16 | 17 | export const requestInterceptor = async (config: any) => { 18 | let s = new Store(); 19 | const { url } = config; 20 | if (!isPrivatePath(url)) return config; 21 | 22 | let access_token = s.get(accessTokenKey); 23 | if (access_token === null || access_token === undefined) { 24 | return config; 25 | } 26 | 27 | config.headers.Authorization = `Bearer ${access_token}`; 28 | 29 | return config; 30 | }; 31 | 32 | export const responseInterceptor = async (error: AxiosError) => { 33 | const { config, response }: any = error; 34 | 35 | let s = new Store(); 36 | let url = config.url; 37 | let parsedUrl = new URL(url); 38 | 39 | let refreshCondition = 40 | isPrivatePath(url) && 41 | response.status == 401 && 42 | url != `${parsedUrl.origin}/public/auth/refresh_token`; 43 | 44 | if (!refreshCondition) return Promise.reject(error); 45 | 46 | const refreshToken = s.get(refreshTokenKey); 47 | 48 | if (refreshToken === null || refreshToken === undefined) { 49 | return Promise.reject(error); 50 | } 51 | 52 | try { 53 | const res = await axios.post( 54 | `${parsedUrl.origin}/public/auth/refresh_token`, 55 | { 56 | refreshToken, 57 | } 58 | ); 59 | setAuthTokensFunc(res.data); 60 | return axios(config); 61 | } catch (error) { 62 | return Promise.reject(error); 63 | } 64 | }; 65 | -------------------------------------------------------------------------------- /src/main/util.ts: -------------------------------------------------------------------------------- 1 | /* eslint import/prefer-default-export: off */ 2 | import { URL } from 'url'; 3 | import path from 'path'; 4 | 5 | export function resolveHtmlPath(htmlFileName: string) { 6 | if (process.env.NODE_ENV === 'development') { 7 | const port = process.env.PORT || 1212; 8 | const url = new URL(`http://localhost:${port}`); 9 | url.pathname = htmlFileName; 10 | return url.href; 11 | } 12 | return `file://${path.resolve(__dirname, '../renderer/', htmlFileName)}`; 13 | } 14 | -------------------------------------------------------------------------------- /src/renderer/App.scss: -------------------------------------------------------------------------------- 1 | .App { 2 | text-align: center; 3 | } 4 | 5 | .App-logo { 6 | height: 40vmin; 7 | pointer-events: none; 8 | } 9 | 10 | @media (prefers-reduced-motion: no-preference) { 11 | .App-logo { 12 | animation: App-logo-spin infinite 20s linear; 13 | } 14 | } 15 | 16 | .App-header { 17 | // background-color: #282c34; 18 | min-height: 100vh; 19 | display: flex; 20 | flex-direction: column; 21 | align-items: center; 22 | justify-content: center; 23 | font-size: calc(10px + 2vmin); 24 | color: white; 25 | } 26 | 27 | .App-link { 28 | color: #61dafb; 29 | } 30 | 31 | @keyframes App-logo-spin { 32 | from { 33 | transform: rotate(0deg); 34 | } 35 | to { 36 | transform: rotate(360deg); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/renderer/App.tsx: -------------------------------------------------------------------------------- 1 | import './App.scss'; 2 | import { useEffect } from 'react'; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { RootState } from './redux/store'; 5 | import ProjectScreen from './screens/Project/ProjectScreen'; 6 | import AuthScreen from './screens/Auth/AuthScreen'; 7 | import { AppPage, setPlatform } from './redux/app/appSlice'; 8 | import HomeScreen from './screens/Home/Home'; 9 | 10 | function App() { 11 | const appState = useSelector((state: RootState) => state.app); 12 | const dispatch = useDispatch(); 13 | const init = async () => { 14 | dispatch(setPlatform(await window.electron.getDeviceType())); 15 | }; 16 | 17 | const curPage = appState.curPage; 18 | 19 | useEffect(() => { 20 | init(); 21 | }, []); 22 | 23 | return ( 24 | <> 25 | {curPage === AppPage.Auth ? ( 26 | 27 | ) : curPage === AppPage.Home ? ( 28 | 29 | ) : ( 30 | 31 | )} 32 | 33 | ); 34 | } 35 | 36 | export default App; 37 | -------------------------------------------------------------------------------- /src/renderer/components/general/Margin.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | type MarginProps = { 4 | width?: number; 5 | height?: number; 6 | }; 7 | 8 | function Margin({ width, height }: MarginProps) { 9 | return ( 10 |
17 | ); 18 | } 19 | 20 | export default Margin; 21 | -------------------------------------------------------------------------------- /src/renderer/declaration.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.scss'; 2 | -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaCode.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaCode.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaCodeItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaCodeItalic.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaCodePL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaCodePL.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaCodePLItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaCodePLItalic.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaMono.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaMono.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaMonoItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaMonoItalic.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaMonoPL.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaMonoPL.woff2 -------------------------------------------------------------------------------- /src/renderer/fonts/CascadiaMonoPLItalic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/renderer/fonts/CascadiaMonoPLItalic.woff2 -------------------------------------------------------------------------------- /src/renderer/hooks/useClickOutside.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useRef } from 'react'; 2 | 3 | const useClickOutside = (callback: any) => { 4 | const ref: any = useRef(null); 5 | 6 | const handleClickOutside = (event: any) => { 7 | if (ref.current && !ref.current.contains(event.target)) { 8 | callback(); 9 | } 10 | }; 11 | 12 | useEffect(() => { 13 | document.addEventListener('click', handleClickOutside); 14 | return () => { 15 | document.removeEventListener('click', handleClickOutside); 16 | }; 17 | }, []); 18 | 19 | return ref; 20 | }; 21 | 22 | export default useClickOutside; 23 | -------------------------------------------------------------------------------- /src/renderer/hooks/useEscClicked.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | 3 | const useEscHook = (callback: any) => { 4 | useEffect(() => { 5 | const handleEscKeyPress = (event: any) => { 6 | if (event.key === 'Escape') { 7 | callback(); 8 | } 9 | }; 10 | 11 | document.addEventListener('keydown', handleEscKeyPress); 12 | 13 | return () => { 14 | document.removeEventListener('keydown', handleEscKeyPress); 15 | }; 16 | }, [callback]); 17 | }; 18 | 19 | export default useEscHook; 20 | -------------------------------------------------------------------------------- /src/renderer/hooks/useKeyPress.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function useKeyPress(targetKey: any, onPress: any) { 4 | useEffect(() => { 5 | function handleKeyPress(event: any) { 6 | if (event.key === targetKey) { 7 | onPress(); 8 | } 9 | } 10 | 11 | window.addEventListener('keydown', handleKeyPress); 12 | 13 | return () => { 14 | window.removeEventListener('keydown', handleKeyPress); 15 | }; 16 | }, [targetKey, onPress]); 17 | } 18 | 19 | export default useKeyPress; 20 | -------------------------------------------------------------------------------- /src/renderer/hooks/useShortcut.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function useShortcut(key: string, callback: any) { 4 | // let deps = dependencies ? dependencies : []; 5 | let listener = (e: any, payload: string) => { 6 | if (payload === key) { 7 | callback(); 8 | } 9 | }; 10 | 11 | useEffect(() => { 12 | const handleKeyPress = async (event: any) => { 13 | let deviceType = await window.electron.getDeviceType(); 14 | 15 | let keyMatch = false; 16 | if (deviceType == 'darwin') { 17 | if ( 18 | event.metaKey && 19 | (event.key === key.toLowerCase() || event.key === key.toUpperCase()) 20 | ) 21 | keyMatch = true; 22 | } else if ( 23 | event.ctrlKey && 24 | (event.key === key.toLowerCase() || event.key === key.toUpperCase()) 25 | ) { 26 | keyMatch = true; 27 | } 28 | 29 | if (keyMatch) { 30 | event.preventDefault(); 31 | callback(); 32 | } 33 | }; 34 | 35 | window.addEventListener('keydown', handleKeyPress); 36 | return () => { 37 | window.removeEventListener('keydown', handleKeyPress); 38 | }; 39 | }, [key, callback]); 40 | } 41 | 42 | export default useShortcut; 43 | -------------------------------------------------------------------------------- /src/renderer/hooks/useShortcutShift.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react'; 2 | 3 | function useShortcutShift(key: string, callback: any) { 4 | useEffect(() => { 5 | const handleKeyPress = async (event: any) => { 6 | let deviceType = await window.electron.getDeviceType(); 7 | let isMac = deviceType === 'darwin'; 8 | 9 | let keyMatch = 10 | event.key == key.toUpperCase() || event.key == key.toLowerCase(); 11 | if ( 12 | ((isMac && event.metaKey) || (!isMac && event.ctrlKey)) && 13 | event.shiftKey && 14 | keyMatch 15 | ) { 16 | event.preventDefault(); 17 | callback(); 18 | } 19 | }; 20 | 21 | window.addEventListener('keydown', handleKeyPress); 22 | 23 | return () => { 24 | window.removeEventListener('keydown', handleKeyPress); 25 | }; 26 | }, [key, callback]); 27 | } 28 | 29 | export default useShortcutShift; 30 | -------------------------------------------------------------------------------- /src/renderer/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | Visual Backend 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /src/renderer/index.tsx: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | import App from './App'; 3 | import { Provider } from 'react-redux'; 4 | import store from './redux/store'; 5 | import './styles/index.scss'; 6 | 7 | const container = document.getElementById('root') as HTMLElement; 8 | const root = createRoot(container); 9 | root.render( 10 | 11 | 12 | 13 | ); 14 | -------------------------------------------------------------------------------- /src/renderer/misc/constants.ts: -------------------------------------------------------------------------------- 1 | let localEndpoint = 'http://localhost:8081'; 2 | let publicEndpoint = 'https://visual-backend-tsne2r6uva-nw.a.run.app'; 3 | 4 | // export const endpoint = publicEndpoint; 5 | export let endpoint = 'https://eliseserver.com'; 6 | // endpoint = 'http://127.0.0.1:8000'; 7 | 8 | export const accessTokenKey = 'access_token'; 9 | export const refreshTokenKey = 'refresh_token'; 10 | export const curProjectKey = 'cur_project_key'; 11 | 12 | export const electronStoreKeys = { 13 | openWithVsKey: 'open_with_vs', 14 | editorToUseKey: 'editor_to_use', 15 | }; 16 | export const nodeTypeKey = 'node_type'; 17 | 18 | export const projWindowSizeNoVs = { 19 | width: 800, 20 | height: 700, 21 | }; 22 | 23 | export const projWindowSizeVs = { 24 | width: 480, 25 | height: 650, 26 | }; 27 | 28 | export const homeWindowSize = { 29 | width: 600, 30 | height: 550, 31 | }; 32 | 33 | export const serverLocation = 'europe-west2'; 34 | 35 | export const envConsts: { 36 | [key: string]: string; 37 | } = { 38 | SUPABASE_PROJECT_URL: 'SUPABASE_PROJECT_URL', 39 | SUPABASE_SERVICE_KEY: 'SUPABASE_SERVICE_KEY', 40 | MONGO_CONN_STRING: 'MONGO_CONN_STRING', 41 | MONGO_DEFAULT_DB: 'MONGO_DEFAULT_DB', 42 | JWT_ACCESS_SECRET: 'JWT_ACCESS_SECRET', 43 | JWT_REFRESH_SECRET: 'JWT_REFRESH_SECRET', 44 | STRIPE_TEST_KEY: 'STRIPE_TEST_KEY', 45 | STRIPE_LIVE_KEY: 'STRIPE_LIVE_KEY', 46 | OPENAI_API_KEY: 'OPENAI_API_KEY', 47 | RESEND_API_KEY: 'RESEND_API_KEY', 48 | FIREBASE_CREDENTIALS: 'FIREBASE_CREDENTIALS', 49 | }; 50 | -------------------------------------------------------------------------------- /src/renderer/preload.d.ts: -------------------------------------------------------------------------------- 1 | import { ElectronHandler } from 'main/preload'; 2 | 3 | declare global { 4 | // eslint-disable-next-line no-unused-vars 5 | interface Window { 6 | electron: ElectronHandler; 7 | } 8 | } 9 | 10 | export {}; 11 | -------------------------------------------------------------------------------- /src/renderer/redux/app/appReducer.ts: -------------------------------------------------------------------------------- 1 | import { Project } from '@/shared/models/project'; 2 | import { AppPage, AppState } from './appSlice'; 3 | import { Route } from '@/shared/models/route'; 4 | 5 | export const setAppPageReducer = ( 6 | state: AppState, 7 | action: { 8 | payload: AppPage; 9 | } 10 | ) => { 11 | state.curPage = action.payload; 12 | }; 13 | 14 | export const setLoggedInReducer = ( 15 | state: AppState, 16 | action: { 17 | payload: boolean; 18 | } 19 | ) => { 20 | state.loggedIn = action.payload; 21 | }; 22 | 23 | export const setProjectsReducer = ( 24 | state: AppState, 25 | action: { 26 | payload: Array; 27 | } 28 | ) => { 29 | state.projects = action.payload; 30 | }; 31 | 32 | export const addProjectReducer = ( 33 | state: AppState, 34 | action: { 35 | payload: Project; 36 | } 37 | ) => { 38 | let newProjs = [...state.projects]; 39 | newProjs.push(action.payload); 40 | state.projects = newProjs; 41 | }; 42 | 43 | export const setProjectReducer = ( 44 | state: AppState, 45 | action: { 46 | payload: Project; 47 | } 48 | ) => { 49 | state.currentProject = action.payload; 50 | }; 51 | 52 | export const setRouteReducer = ( 53 | state: AppState, 54 | action: { 55 | payload: Route; 56 | } 57 | ) => { 58 | state.currentRoute = action.payload; 59 | }; 60 | 61 | export const removeProjectReducer = (state: AppState, action: any) => { 62 | const { id } = action.payload; 63 | const updatedProjs = state.projects.filter((item) => item._id !== id); 64 | state.projects = updatedProjs; 65 | state.currentProject = null; 66 | }; 67 | -------------------------------------------------------------------------------- /src/renderer/redux/cloud/cloudReducer.ts: -------------------------------------------------------------------------------- 1 | import { CloudState } from './cloudSlice'; 2 | 3 | export const initCloudStateReducer = (state: CloudState, action: any) => { 4 | state.builds = action.payload.builds; 5 | state.cloudRunService = action.payload.cloudRunService; 6 | }; 7 | 8 | export const addBuildReducer = (state: CloudState, action: any) => { 9 | let newBuilds = [action.payload, ...state.builds]; 10 | state.builds = newBuilds; 11 | }; 12 | 13 | export const setCloudDataReducer = (state: CloudState, action: any) => { 14 | state.builds = action.payload.builds; 15 | state.cloudRunService = action.payload.cloudRunService; 16 | }; 17 | -------------------------------------------------------------------------------- /src/renderer/redux/cloud/cloudSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { GBuild } from '@/shared/models/GBuild'; 3 | import { 4 | addBuildReducer, 5 | initCloudStateReducer, 6 | setCloudDataReducer, 7 | } from './cloudReducer'; 8 | 9 | export type CloudState = { 10 | builds: Array | null; 11 | cloudRunService: any; 12 | }; 13 | 14 | const getInitialState = (): CloudState => { 15 | return { 16 | builds: null, 17 | cloudRunService: null, 18 | }; 19 | }; 20 | // create a slice 21 | export const cloudSlice = createSlice({ 22 | name: 'cloudSlice', 23 | initialState: getInitialState(), 24 | 25 | reducers: { 26 | resetCloudState(state, action) { 27 | state.builds = null; 28 | state.cloudRunService = null; 29 | }, 30 | initCloudState(state, action) { 31 | initCloudStateReducer(state, action); 32 | }, 33 | addBuild(state, action) { 34 | addBuildReducer(state, action); 35 | }, 36 | setCloudData(state, action) { 37 | setCloudDataReducer(state, action); 38 | }, 39 | }, 40 | }); 41 | 42 | export const { resetCloudState, initCloudState, addBuild, setCloudData } = 43 | cloudSlice.actions; 44 | export default cloudSlice.reducer; 45 | -------------------------------------------------------------------------------- /src/renderer/redux/editor/editorReducer.ts: -------------------------------------------------------------------------------- 1 | import { EditorState } from './editorSlice'; 2 | 3 | export const setCurFileReducer = (state: EditorState, action: any) => { 4 | state.currentFile = action.payload; 5 | }; 6 | -------------------------------------------------------------------------------- /src/renderer/redux/editor/editorSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { setCurFileReducer } from './editorReducer'; 3 | 4 | export enum EditorType { 5 | Route = 'route', 6 | Func = 'func', 7 | } 8 | 9 | export type EditorFile = { 10 | title: string; 11 | path: string; 12 | type: EditorType; 13 | metadata: any; 14 | }; 15 | 16 | export type EditorState = { 17 | currentFile: EditorFile | null; 18 | }; 19 | 20 | const getInitialState = (): EditorState => { 21 | return { 22 | currentFile: null, 23 | }; 24 | }; 25 | // create a slice 26 | export const editorSlice = createSlice({ 27 | name: 'editorState', 28 | initialState: getInitialState(), 29 | 30 | reducers: { 31 | setCurFile(state, action) { 32 | setCurFileReducer(state, action); 33 | }, 34 | }, 35 | }); 36 | 37 | export const { setCurFile } = editorSlice.actions; 38 | export default editorSlice.reducer; 39 | -------------------------------------------------------------------------------- /src/renderer/redux/project/projectReducers.ts: -------------------------------------------------------------------------------- 1 | import { ProjectState, ProjectTab } from './projectSlice'; 2 | 3 | export const initialiseProjectReducer = (state: ProjectState, action: any) => { 4 | state.createModuleOpen = false; 5 | state.curRoute = null; 6 | state.currentTab = ProjectTab.Routes; 7 | state.termData = []; 8 | }; 9 | 10 | export const setCreateModuleOpenReducer = ( 11 | state: ProjectState, 12 | action: any 13 | ) => { 14 | state.createModuleOpen = action.payload; 15 | }; 16 | 17 | export const setCurrentTabReducer = (state: ProjectState, action: any) => { 18 | state.currentTab = action.payload; 19 | }; 20 | 21 | export const setCurRouteReducer = (state: ProjectState, action: any) => { 22 | state.curRoute = action.payload; 23 | }; 24 | 25 | export const addTermMsgReducer = (state: ProjectState, action: any) => { 26 | // let data = action.payload.data; 27 | // let lines = data.trim('\n').split('\n'); 28 | // let newLines: any = []; 29 | // for (let i = 0; i < lines.length; i++) { 30 | // newLines.push({ type: action.payload.type, data: lines[i] }); 31 | // } 32 | 33 | let newData = [...state.termData, ...action.payload]; 34 | state.termData = newData; 35 | }; 36 | 37 | export const resetTermDataReducer = (state: ProjectState, action: any) => { 38 | state.termData = []; 39 | }; 40 | 41 | export const setServerRunningReducer = (state: ProjectState, action: any) => { 42 | state.serverRunning = action.payload; 43 | }; 44 | -------------------------------------------------------------------------------- /src/renderer/redux/project/projectSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { 3 | addTermMsgReducer, 4 | initialiseProjectReducer, 5 | resetTermDataReducer, 6 | setCreateModuleOpenReducer, 7 | setCurRouteReducer, 8 | setCurrentTabReducer, 9 | setServerRunningReducer, 10 | } from './projectReducers'; 11 | import { Route } from '@/shared/models/route'; 12 | 13 | export enum ProjectTab { 14 | Routes = 'routes', 15 | Packages = 'packages', 16 | Module = 'module', 17 | CreateModule = 'create-module', 18 | Hosting = 'hosting', 19 | Env = 'env', 20 | } 21 | 22 | export type ProjectState = { 23 | createModuleOpen: boolean; 24 | routes: Array; 25 | currentTab: ProjectTab; 26 | curRoute: Route | null; 27 | termData: Array; 28 | serverRunning: boolean; 29 | showTerm: boolean; 30 | }; 31 | 32 | const getInitialState = (): ProjectState => { 33 | return { 34 | createModuleOpen: false, 35 | currentTab: ProjectTab.Routes, 36 | routes: [], 37 | curRoute: null, 38 | termData: [], 39 | serverRunning: false, 40 | showTerm: false, 41 | }; 42 | }; 43 | // create a slice 44 | export const projectSlice = createSlice({ 45 | name: 'projectState', 46 | initialState: getInitialState(), 47 | 48 | reducers: { 49 | initialiseProject(state, action) { 50 | initialiseProjectReducer(state, action); 51 | }, 52 | 53 | setShowTerm(state, action) { 54 | state.showTerm = action.payload; 55 | }, 56 | 57 | setCreateModuleOpen(state, action) { 58 | setCreateModuleOpenReducer(state, action); 59 | }, 60 | 61 | setCurrentTab(state, action) { 62 | setCurrentTabReducer(state, action); 63 | }, 64 | 65 | setCurRoute(state, action) { 66 | setCurRouteReducer(state, action); 67 | }, 68 | 69 | addTermMsg(state, action) { 70 | addTermMsgReducer(state, action); 71 | }, 72 | 73 | resetTermData(state, action) { 74 | resetTermDataReducer(state, action); 75 | }, 76 | setServerRunning(state, action) { 77 | setServerRunningReducer(state, action); 78 | }, 79 | }, 80 | }); 81 | 82 | export const { 83 | setShowTerm, 84 | initialiseProject, 85 | setCreateModuleOpen, 86 | setCurrentTab, 87 | setCurRoute, 88 | addTermMsg, 89 | resetTermData, 90 | setServerRunning, 91 | } = projectSlice.actions; 92 | export default projectSlice.reducer; 93 | -------------------------------------------------------------------------------- /src/renderer/redux/routes/routesReducer.ts: -------------------------------------------------------------------------------- 1 | import { Route, RouteNode } from '@/shared/models/route'; 2 | import { RoutesState } from './routesSlice'; 3 | 4 | export const toggleRouteOpenedReducer = (state: RoutesState, action: any) => { 5 | let route = action.payload; 6 | if (!state.openedRoutes[route.id]) { 7 | state.openedRoutes[route.id] = false; 8 | } 9 | state.openedRoutes[route.id] = !state.openedRoutes[route.id]; 10 | }; 11 | 12 | const insertRoute = (node: RouteNode, routes: Array) => { 13 | for (let i = 0; i < routes.length; i++) { 14 | if (routes[i].parentId == node.id) { 15 | let newRouteNode: RouteNode = { ...routes[i], children: [] }; 16 | node.children.push(newRouteNode); 17 | insertRoute(newRouteNode, routes); 18 | } 19 | } 20 | }; 21 | 22 | export const initRoutesReducer = (state: RoutesState, action: any) => { 23 | let routes = action.payload.routes; 24 | 25 | let root = null; 26 | routes.map((route: Route) => { 27 | if (route.parentId == -1) { 28 | root = { ...route, children: [] }; 29 | insertRoute(root, routes); 30 | } 31 | }); 32 | state.rootNode = root; 33 | state.routes = action.payload.routes; 34 | state.openedRoutes = {}; 35 | }; 36 | 37 | const insertNodeRecursive = (curNode: RouteNode, newNode: RouteNode) => { 38 | if (curNode.id == newNode.parentId) { 39 | curNode.children.push(newNode); 40 | return curNode; 41 | } 42 | 43 | for (let i = 0; i < curNode.children.length; i++) { 44 | curNode.children[i] = insertNodeRecursive(curNode.children[i], newNode); 45 | } 46 | 47 | return curNode; 48 | }; 49 | 50 | export const addRouteReducer = (state: RoutesState, action: any) => { 51 | let newNode = action.payload; 52 | 53 | state.rootNode = insertNodeRecursive(state.rootNode!, newNode); 54 | }; 55 | 56 | const deleteNodeRecursive = (curNode: RouteNode, deletedNode: RouteNode) => { 57 | if (curNode.id == deletedNode.parentId) { 58 | curNode.children = curNode.children.filter( 59 | (child) => child.id !== deletedNode.id 60 | ); 61 | return curNode; 62 | } 63 | 64 | for (let i = 0; i < curNode.children.length; i++) { 65 | curNode.children[i] = deleteNodeRecursive(curNode.children[i], deletedNode); 66 | } 67 | 68 | return curNode; 69 | }; 70 | export const deleteRouteReducer = (state: RoutesState, action: any) => { 71 | let deletedNode = action.payload; 72 | state.rootNode = deleteNodeRecursive(state.rootNode!, deletedNode); 73 | }; 74 | -------------------------------------------------------------------------------- /src/renderer/redux/routes/routesSlice.ts: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | import { Route, RouteNode } from '@/shared/models/route'; 3 | import { 4 | addRouteReducer, 5 | deleteRouteReducer, 6 | initRoutesReducer, 7 | toggleRouteOpenedReducer, 8 | } from './routesReducer'; 9 | 10 | export type RoutesState = { 11 | openedRoutes: { [key: number]: boolean }; 12 | routes: Array; 13 | rootNode: RouteNode | null; 14 | }; 15 | 16 | const getInitialState = (): RoutesState => { 17 | return { 18 | openedRoutes: {}, 19 | routes: [], 20 | rootNode: null, 21 | }; 22 | }; 23 | // create a slice 24 | export const routesSlice = createSlice({ 25 | name: 'routesSlice', 26 | initialState: getInitialState(), 27 | 28 | reducers: { 29 | toggleRouteOpened(state, action) { 30 | toggleRouteOpenedReducer(state, action); 31 | }, 32 | initRoutes(state, action) { 33 | initRoutesReducer(state, action); 34 | }, 35 | addRoute(state, action) { 36 | addRouteReducer(state, action); 37 | }, 38 | deleteRoute(state, action) { 39 | deleteRouteReducer(state, action); 40 | }, 41 | }, 42 | }); 43 | 44 | export const { initRoutes, toggleRouteOpened, addRoute, deleteRoute } = 45 | routesSlice.actions; 46 | export default routesSlice.reducer; 47 | -------------------------------------------------------------------------------- /src/renderer/redux/store.ts: -------------------------------------------------------------------------------- 1 | import { 2 | configureStore, 3 | ThunkAction, 4 | Action, 5 | combineReducers, 6 | } from '@reduxjs/toolkit'; 7 | import { Reducer } from 'react'; 8 | import appSlice from './app/appSlice'; 9 | import projectSlice from './project/projectSlice'; 10 | import moduleSlice from './module/moduleSlice'; 11 | import routesSlice from './routes/routesSlice'; 12 | import editorSlice from './editor/editorSlice'; 13 | import cloudSlice from './cloud/cloudSlice'; 14 | 15 | const store = configureStore({ 16 | reducer: { 17 | app: appSlice, 18 | project: projectSlice, 19 | module: moduleSlice, 20 | routes: routesSlice, 21 | editor: editorSlice, 22 | cloud: cloudSlice, 23 | }, 24 | }); 25 | 26 | export default store; 27 | export type RootState = ReturnType; 28 | export type AppDispatch = typeof store.dispatch; 29 | -------------------------------------------------------------------------------- /src/renderer/screens/Auth/AuthScreen.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import '@/renderer/styles/Auth/AuthScreen.scss'; 3 | import { Button } from 'antd'; 4 | import { useDispatch } from 'react-redux'; 5 | import { AppPage, setCurPage } from '@/renderer/redux/app/appSlice'; 6 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 7 | import { faGithub } from '@fortawesome/free-brands-svg-icons'; 8 | 9 | function AuthScreen() { 10 | const [isSignUp, setIsSignUp] = useState(false); 11 | const dispatch = useDispatch(); 12 | 13 | let redirectUri = 'visual-backend://eliseapp.com/auth/callback'; 14 | let githubSignIn = `https://github.com/login/oauth/authorize?client_id=Iv1.c8341ac1688e6ef8&redirect_uri=${redirectUri}`; 15 | const [test, setTest] = useState("") 16 | 17 | useEffect(() => { 18 | let handleMessage: any = (event: any, payload: any) => { 19 | if (payload.status == 'success') { 20 | dispatch(setCurPage(AppPage.Home)); 21 | } 22 | }; 23 | 24 | window.electron.onAuthUpdate(handleMessage); 25 | }, []); 26 | 27 | 28 | 29 | 30 | return ( 31 |
32 |

Visual Backend

33 | {/*
*/} 37 | 46 | {/* {isSignUp ? ( 47 | 48 | ) : ( 49 | 50 | )} */} 51 | {/*
*/} 52 |
53 | ); 54 | } 55 | 56 | export default AuthScreen; 57 | -------------------------------------------------------------------------------- /src/renderer/screens/Auth/LoginContainer.tsx: -------------------------------------------------------------------------------- 1 | import Margin from '@/renderer/components/general/Margin'; 2 | import { AppPage, setCurPage } from '@/renderer/redux/app/appSlice'; 3 | import { UserService } from '@/renderer/services/UserService'; 4 | import { Button, Input } from 'antd'; 5 | import React, { Dispatch, SetStateAction, useState } from 'react'; 6 | import { useDispatch } from 'react-redux'; 7 | import { useNavigate } from 'react-router'; 8 | type LoginContainerProps = { 9 | setIsSignUp: Dispatch>; 10 | }; 11 | 12 | function LoginContainer({ setIsSignUp }: LoginContainerProps) { 13 | const dispatch = useDispatch(); 14 | const [email, setEmail] = useState(''); 15 | const [password, setPassword] = useState(''); 16 | const [errorText, setErrorText] = useState(''); 17 | const [loading, setLoading] = useState(false); 18 | 19 | const login = async () => { 20 | setLoading(true); 21 | 22 | try { 23 | let res = await UserService.login(email, password); 24 | console.log('Res:', res); 25 | await window.electron.setAuthTokens(res.data); 26 | dispatch(setCurPage(AppPage.Home)); 27 | } catch (error) { 28 | console.log(error); 29 | setErrorText('Email or password was invalid'); 30 | } 31 | setLoading(false); 32 | }; 33 | 34 | return ( 35 | <> 36 |

Welcome back!

37 | setEmail(e.target.value)} 41 | placeholder="Email" 42 | /> 43 | 44 | setPassword(e.target.value)} 48 | placeholder="Password" 49 | type="password" 50 | /> 51 | 59 | 62 | {errorText &&

{errorText}

} 63 | 64 | ); 65 | } 66 | 67 | export default LoginContainer; 68 | -------------------------------------------------------------------------------- /src/renderer/screens/Home/NewPremiumModal.tsx: -------------------------------------------------------------------------------- 1 | import Margin from '@/renderer/components/general/Margin'; 2 | import { 3 | faCheck, 4 | faXmark, 5 | } from '@fortawesome/free-solid-svg-icons'; 6 | import {HiSparkles} from 'react-icons/hi'; 7 | 8 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 9 | import '@/renderer/styles/Home/NewPremiumModal.scss'; 10 | 11 | function NewPremiumModal({ setModalOpen }: any) { 12 | return ( 13 |
14 |
15 |
16 | 19 |
20 |
21 |
22 | 23 | Welcome to the premium version of Visual Backend 24 |
25 | 26 |

27 | Thank you for your support, it makes what we do here worth it. Enjoy 28 | the new features available to you! 29 |

30 | 31 | 32 |
33 |
34 | 35 |

Access to all integrations

36 |
37 | 38 |
39 | 40 |

Access to hosting

41 |
42 | 43 |
44 | 45 |

Unlimited kickstart with AI uses

46 |
47 | 48 |
49 | 50 |

Create unlimited projects

51 |
52 |
53 | 54 |
55 |
56 |
57 | ); 58 | } 59 | 60 | export default NewPremiumModal; 61 | -------------------------------------------------------------------------------- /src/renderer/screens/Home/RequireUpgradeModal.tsx: -------------------------------------------------------------------------------- 1 | import Margin from '@/renderer/components/general/Margin'; 2 | import { faXmark, faRocket } from '@fortawesome/free-solid-svg-icons'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { LoadingOutlined } from '@ant-design/icons'; 5 | 6 | import React, { useState } from 'react'; 7 | 8 | function RequireUpgradeModal({ setModalOpen }: any) { 9 | const [checkoutLoading, setCheckoutLoading] = useState(false); 10 | 11 | const onUpgradeClicked = async () => { 12 | if (checkoutLoading) return; 13 | setCheckoutLoading(true); 14 | await window.electron.openCheckoutPage({}); 15 | setCheckoutLoading(false); 16 | }; 17 | 18 | return ( 19 |
20 |
21 |
22 |
23 | Upgrade to premium 24 |
25 | 28 |
29 | 30 |
31 |

32 | To create more than 5 projects, you need to have a premium account. 33 |

34 | 35 | 45 |
46 |
47 |
48 | ); 49 | } 50 | 51 | export default RequireUpgradeModal; 52 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/CreateModule/CreateModuleModal.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import '@/renderer/styles/Project/CreateModule/CreateModuleModal.scss'; 3 | 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { setCreateModuleOpen } from '@/renderer/redux/project/projectSlice'; 6 | import SelectModule from './SelectModule'; 7 | import { RootState } from '@/renderer/redux/store'; 8 | 9 | import useEscHook from '@/renderer/hooks/useEscClicked'; 10 | import { BModuleType, modConfig } from '@/shared/models/BModule'; 11 | import CreateMongoDB from '../MongoModule/CreateMongoDB'; 12 | import CreateFirebaseModule from '../../../../../../archives/CreateFirebaseModule'; 13 | 14 | function CreateModuleModal() { 15 | const dispatch = useDispatch(); 16 | const project = useSelector((state: RootState) => state.project); 17 | const [page, setPage] = useState(1); 18 | 19 | const [selection, setSelection] = useState(null); 20 | 21 | useEscHook(() => dispatch(setCreateModuleOpen(false))); 22 | const getModuleCreateModal = () => { 23 | if (selection == BModuleType.Mongo) 24 | return ; 25 | 26 | if (selection && modConfig[selection]) { 27 | let conf = modConfig[selection!]; 28 | return conf.createComp(setSelection, selection); 29 | } 30 | return ; 31 | }; 32 | return ( 33 |
34 |
{getModuleCreateModal()}
35 |
36 | ); 37 | } 38 | 39 | export default CreateModuleModal; 40 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/FirebaseModule/CreateFsColModal.tsx: -------------------------------------------------------------------------------- 1 | import Margin from '@/renderer/components/general/Margin'; 2 | import { faXmark } from '@fortawesome/free-solid-svg-icons'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { Button, Input } from 'antd'; 5 | import React, { useEffect, useRef, useState } from 'react'; 6 | 7 | function CreateFsColModal({ setModalOpen, onCreateClicked }: any) { 8 | let [colName, setColName] = useState(''); 9 | const [errText, setErrText] = useState(''); 10 | const inputRef = useRef(null); 11 | 12 | useEffect(() => { 13 | inputRef.current.focus(); 14 | }, []); 15 | 16 | return ( 17 |
18 |
19 |
20 |

Add new firestore group

21 | 24 |
25 |
26 |

Name (should be same as collection)

27 | setColName(e.target.value)} 31 | placeholder="e.g. user" 32 | className="input" 33 | /> 34 |
35 | 36 | 42 | {errText &&

{errText}

} 43 |
44 |
45 | ); 46 | } 47 | 48 | export default CreateFsColModal; 49 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/General/CreateModalHeader.tsx: -------------------------------------------------------------------------------- 1 | import { faArrowLeft } from '@fortawesome/free-solid-svg-icons'; 2 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 3 | import React from 'react'; 4 | 5 | function CreateModalHeader({ title, setSelection }: any) { 6 | return ( 7 |
8 | 11 |

Add {title} module

12 |
13 | ); 14 | } 15 | 16 | export default CreateModalHeader; 17 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/General/FuncButton.tsx: -------------------------------------------------------------------------------- 1 | import { Editor } from '@/renderer/redux/app/appSlice'; 2 | import { EditorType, setCurFile } from '@/renderer/redux/editor/editorSlice'; 3 | import { RootState } from '@/renderer/redux/store'; 4 | import { BFunc, BFuncHelpers } from '@/shared/models/BFunc'; 5 | import { BModule, BModuleType } from '@/shared/models/BModule'; 6 | import { RenFuncs } from '@/shared/utils/RenFuncs'; 7 | import React, { useEffect, useRef } from 'react'; 8 | import { useDispatch, useSelector } from 'react-redux'; 9 | 10 | export const FuncButton = ({ func, module }: any) => { 11 | let btnRef: any = useRef(null); 12 | const dispatch = useDispatch(); 13 | const curProject = useSelector( 14 | (state: RootState) => state.app.currentProject 15 | ); 16 | const editorUsed = useSelector((state: RootState) => state.app.editorToUse); 17 | 18 | const funcClicked = async (f: BFunc, m: BModule) => { 19 | if (editorUsed != Editor.VISUALBACKEND) { 20 | window.electron.openFile({ 21 | path: RenFuncs.getFuncFileData(f, m).path, 22 | projKey: curProject!.key, 23 | }); 24 | } else { 25 | dispatch(setCurFile(RenFuncs.getFuncFileData(f, m))); 26 | } 27 | }; 28 | 29 | useEffect(() => { 30 | const handleContextMenu = async (e: any) => { 31 | if (btnRef.current.contains(e.target)) { 32 | e.preventDefault(); 33 | await window.electron.showFuncContextMenu({ 34 | projKey: curProject!.key, 35 | func: func, 36 | module: module, 37 | }); 38 | } 39 | }; 40 | window.addEventListener('contextmenu', handleContextMenu); 41 | return () => { 42 | window.removeEventListener('contextmenu', handleContextMenu); 43 | }; 44 | }, []); 45 | 46 | return ( 47 | 55 | ); 56 | }; 57 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/General/FuncSection.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | 3 | import { Button } from 'antd'; 4 | import CreateFuncModal from './CreateFuncModal'; 5 | import { FuncButton } from './FuncButton'; 6 | import { useSelector } from 'react-redux'; 7 | import { RootState } from '@/renderer/redux/store'; 8 | import { BFunc, ExtensionType } from '@/shared/models/BFunc'; 9 | import { MdNoteAdd } from 'react-icons/md'; 10 | 11 | function FuncSection({ 12 | createFuncClicked, 13 | title = 'Functions', 14 | funcFilter, 15 | funcGroup = '*', 16 | }: any) { 17 | const funcs = useSelector((state: RootState) => state.module.funcs); 18 | const curModule = useSelector((state: RootState) => state.module.curModule); 19 | let [modFuncs, setModFuncs] = useState([]); 20 | let [createModalOpen, setCreateModalOpen] = useState(false); 21 | 22 | useEffect(() => { 23 | let filter = 24 | funcFilter != null && funcFilter != undefined 25 | ? funcFilter 26 | : (func: BFunc) => 27 | func.moduleKey == curModule?.key && 28 | func.extension != ExtensionType.html; 29 | let newModFuncs = funcs?.filter(filter); 30 | setModFuncs(newModFuncs); 31 | }, [funcs]); 32 | 33 | return ( 34 | <> 35 | {createModalOpen && ( 36 | 41 | )} 42 |
43 |

{title}

44 | 51 |
52 | {modFuncs.map((func: BFunc) => ( 53 | 54 | ))} 55 | 56 | ); 57 | } 58 | 59 | export default FuncSection; 60 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/GptModule/CreateGpt.tsx: -------------------------------------------------------------------------------- 1 | import { RootState } from '@/renderer/redux/store'; 2 | import { BModuleType } from '@/shared/models/BModule'; 3 | import { RenFuncs } from '@/shared/utils/RenFuncs'; 4 | import React, { useState } from 'react'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | import CreateModalHeader from '../General/CreateModalHeader'; 7 | import { Button, Input } from 'antd'; 8 | import Margin from '@/renderer/components/general/Margin'; 9 | import { envConsts } from '@/renderer/misc/constants'; 10 | 11 | function CreateGpt({ setSelection, selection }: any) { 12 | const dispatch = useDispatch(); 13 | const curProject = useSelector( 14 | (state: RootState) => state.app.currentProject 15 | ); 16 | 17 | const [errText, setErrText] = useState(''); 18 | const [createLoading, setCreateLoading] = useState(false); 19 | const [details, setDetails] = useState({ 20 | apiKey: '', 21 | }); 22 | 23 | const onCreate = async () => { 24 | setCreateLoading(true); 25 | let envData: any = {}; 26 | envData[envConsts.OPENAI_API_KEY] = details.apiKey; 27 | let { newModule, newFuncs, error } = await window.electron.createModule({ 28 | key: selection, 29 | projId: curProject?._id, 30 | projKey: curProject?.key, 31 | ...envData, 32 | }); 33 | 34 | if (error) { 35 | setCreateLoading(false); 36 | return; 37 | } 38 | 39 | setCreateLoading(false); 40 | RenFuncs.createModuleSuccess(dispatch, newModule, newFuncs); 41 | }; 42 | 43 | const disabled = () => { 44 | return details.apiKey == ''; 45 | }; 46 | return ( 47 |
48 | 49 |
50 |

Access token secret

51 | setDetails({ ...details, apiKey: e.target.value })} 53 | value={details.apiKey} 54 | className="createInput" 55 | placeholder="API Key" 56 | /> 57 |
58 | 59 | 62 |
63 | ); 64 | } 65 | 66 | export default CreateGpt; 67 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/ResendModule/CreateResend.tsx: -------------------------------------------------------------------------------- 1 | import { RootState } from '@/renderer/redux/store'; 2 | import { BModuleType } from '@/shared/models/BModule'; 3 | import { RenFuncs } from '@/shared/utils/RenFuncs'; 4 | import React, { useState } from 'react'; 5 | import { useDispatch, useSelector } from 'react-redux'; 6 | import CreateModalHeader from '../General/CreateModalHeader'; 7 | import { Button, Input } from 'antd'; 8 | import Margin from '@/renderer/components/general/Margin'; 9 | import { envConsts } from '@/renderer/misc/constants'; 10 | 11 | function CreateResend({ setSelection, selection }: any) { 12 | const dispatch = useDispatch(); 13 | const curProject = useSelector( 14 | (state: RootState) => state.app.currentProject 15 | ); 16 | 17 | const [errText, setErrText] = useState(''); 18 | const [createLoading, setCreateLoading] = useState(false); 19 | const [details, setDetails] = useState({ 20 | apiKey: '', 21 | }); 22 | 23 | const onCreate = async () => { 24 | setCreateLoading(true); 25 | 26 | let envData: any = {}; 27 | envData[envConsts.RESEND_API_KEY] = details.apiKey; 28 | let { newModule, newFuncs, error } = await window.electron.createModule({ 29 | key: selection, 30 | projId: curProject?._id, 31 | projKey: curProject?.key, 32 | ...envData, 33 | }); 34 | 35 | if (error) { 36 | setCreateLoading(false); 37 | return; 38 | } 39 | 40 | setCreateLoading(false); 41 | RenFuncs.createModuleSuccess(dispatch, newModule, newFuncs); 42 | }; 43 | 44 | const disabled = () => { 45 | return details.apiKey == ''; 46 | }; 47 | return ( 48 |
49 | 50 |
51 |

API Key

52 | setDetails({ ...details, apiKey: e.target.value })} 54 | value={details.apiKey} 55 | className="createInput" 56 | placeholder="API Key" 57 | /> 58 |
59 | 60 | 63 |
64 | ); 65 | } 66 | 67 | export default CreateResend; 68 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/Modules/SupabaseModule/CreateSbTableModal.tsx: -------------------------------------------------------------------------------- 1 | import Margin from '@/renderer/components/general/Margin'; 2 | import { faXmark } from '@fortawesome/free-solid-svg-icons'; 3 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 4 | import { Button, Input } from 'antd'; 5 | import React, { useEffect, useRef, useState } from 'react'; 6 | 7 | function CreateSbTableModal({ setModalOpen, onCreateClicked }: any) { 8 | let [colName, setColName] = useState(''); 9 | const [errText, setErrText] = useState(''); 10 | const inputRef = useRef(null); 11 | 12 | useEffect(() => { 13 | inputRef.current.focus(); 14 | }, []); 15 | 16 | return ( 17 |
18 |
19 |
20 |

Create new supabase db group

21 | 24 |
25 |
26 |

Table name

27 | setColName(e.target.value)} 31 | placeholder="Name" 32 | className="input" 33 | /> 34 |
35 | 36 | 42 | {errText &&

{errText}

} 43 |
44 |
45 | ); 46 | } 47 | 48 | export default CreateSbTableModal; 49 | -------------------------------------------------------------------------------- /src/renderer/screens/Project/SectionManager/SectionManager.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import '@/renderer/styles/Project/SectionManager/SectionManager.scss'; 3 | import { useSelector } from 'react-redux'; 4 | import { RootState } from '@/renderer/redux/store'; 5 | import { ProjectTab } from '@/renderer/redux/project/projectSlice'; 6 | import ModuleScreen from './Modules/ModuleScreen'; 7 | import HostingManager from './HostingScreen/HostingManager'; 8 | import PackageManager from './PackageManager/PackageManager'; 9 | import { BModuleType } from '@/shared/models/BModule'; 10 | import EnvManager from './EnvManager/EnvManager'; 11 | import RoutesManager from './RoutesManager/RoutesManager'; 12 | import { Editor } from '@/renderer/redux/app/appSlice'; 13 | 14 | function SectionManager() { 15 | let curTab = useSelector((state: RootState) => state.project.currentTab); 16 | let editorToUse = useSelector((state: RootState) => state.app.editorToUse); 17 | 18 | return ( 19 |
30 | {curTab == ProjectTab.Routes ? ( 31 | 32 | ) : curTab == ProjectTab.Module ? ( 33 | 34 | ) : curTab == ProjectTab.Packages ? ( 35 | 36 | ) : curTab == ProjectTab.Env ? ( 37 | 38 | ) : ( 39 | 40 | )} 41 |
42 | ); 43 | } 44 | 45 | export default SectionManager; 46 | -------------------------------------------------------------------------------- /src/renderer/services/ProjectService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { endpoint } from '../misc/constants'; 3 | import { requestInterceptor, responseInterceptor } from './config'; 4 | 5 | axios.interceptors.request.use(requestInterceptor, (error) => 6 | Promise.reject(error) 7 | ); 8 | 9 | axios.interceptors.response.use((response) => { 10 | return response; 11 | }, responseInterceptor); 12 | 13 | export class ProjectService { 14 | static createProject = async (name: string, type: string) => 15 | await axios.post(`${endpoint}/auth/visual_backend/projects`, { 16 | name, 17 | type, 18 | }); 19 | 20 | // static getProjectData = async (id: string) => 21 | // await axios.get(`${endpoint}/private/projects/${id}`); 22 | 23 | static createBuild = async (id: string) => 24 | await axios.post(`${endpoint}/private/projects/build/${id}`); 25 | 26 | static deleteProject = async (id: string) => 27 | await axios.delete(`${endpoint}/auth/visual_backend/projects/${id}`); 28 | 29 | static getCloudData = async (id: string) => 30 | await axios.get(`${endpoint}/private/projects/${id}/cloud`); 31 | 32 | static createProjectTrigger = async (id: string) => 33 | await axios.post(`${endpoint}/private/projects/triggers/${id}`, null); 34 | 35 | static addEnvVars = async (data: any) => 36 | await axios.post(`${endpoint}/private/projects/env_vars`, data); 37 | static deleteEnvVars = async (data: any) => 38 | await axios.post(`${endpoint}/private/projects/delete_env_vars`, data); 39 | static updateEnvVars = async (data: any) => 40 | await axios.post(`${endpoint}/private/projects/update_env_vars`, data); 41 | } 42 | -------------------------------------------------------------------------------- /src/renderer/services/UserService.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { endpoint } from '../misc/constants'; 3 | import { requestInterceptor, responseInterceptor } from './config'; 4 | 5 | axios.interceptors.request.use(requestInterceptor, (error) => 6 | Promise.reject(error) 7 | ); 8 | 9 | axios.interceptors.response.use((response) => { 10 | return response; 11 | }, responseInterceptor); 12 | 13 | export class UserService { 14 | static getUser = async () => await axios.get(`${endpoint}/auth/user`); 15 | 16 | static getProjects = async () => 17 | await axios.get(`${endpoint}/auth/visual_backend/projects`); 18 | 19 | static login = async (email: string, password: string) => 20 | await axios.post(`${endpoint}/public/auth/login`, { 21 | email, 22 | password, 23 | }); 24 | 25 | static signup = async (details: any) => 26 | await axios.post(`${endpoint}/public/auth/signup`, details); 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/services/config.ts: -------------------------------------------------------------------------------- 1 | import { endpoint } from '@/renderer/misc/constants'; 2 | import axios, { AxiosError } from 'axios'; 3 | 4 | export const isPrivatePath = (url: string) => { 5 | let parsedUrl = new URL(url); 6 | let authPath = parsedUrl.pathname.split('/')[1]; 7 | if (authPath == 'auth') return true; 8 | return false; 9 | }; 10 | 11 | export const requestInterceptor = async (config: any) => { 12 | let { url } = config; 13 | 14 | if (!isPrivatePath(url)) return config; 15 | 16 | let access_token = await window.electron.getAccessToken({}); 17 | if (access_token === null || access_token === undefined) { 18 | return config; 19 | } 20 | 21 | config.headers.Authorization = `Bearer ${access_token}`; 22 | 23 | return config; 24 | }; 25 | 26 | export const responseInterceptor = async (error: AxiosError) => { 27 | const { config, response }: any = error; 28 | 29 | let url = config.url; 30 | let parsedUrl = new URL(url); 31 | 32 | let refreshCondition = 33 | isPrivatePath(url) && 34 | response.status == 401 && 35 | url != `${parsedUrl.origin}/public/auth/refresh_token`; 36 | 37 | if (!refreshCondition) return Promise.reject(error); 38 | 39 | const refreshToken = await window.electron.getRefreshToken({}); 40 | 41 | if (refreshToken === null || refreshToken === undefined) { 42 | return Promise.reject(error); 43 | } 44 | 45 | try { 46 | const res = await axios.post( 47 | `${parsedUrl.origin}/public/auth/refresh_token`, 48 | { 49 | refreshToken, 50 | } 51 | ); 52 | window.electron.setAuthTokens(res.data); 53 | return axios(config); 54 | } catch (error) { 55 | return Promise.reject(error); 56 | } 57 | }; 58 | -------------------------------------------------------------------------------- /src/renderer/styles/Auth/AuthScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .authBg { 4 | width: 100vw; 5 | height: 100vh; 6 | 7 | @include flexCenter(column); 8 | h1 { 9 | font-size: 25px; 10 | color: #aaa; 11 | } 12 | } 13 | 14 | .authContainer { 15 | width: 350px; 16 | box-shadow: rgba(100, 100, 111, 0.2) 0px 7px 29px 0px; 17 | border-radius: 10px; 18 | @include flexCenter(column); 19 | justify-content: center; 20 | margin-bottom: 20px; 21 | background-color: white; 22 | padding: 30px 40px; 23 | 24 | .welcomeText { 25 | font-weight: bold; 26 | font-size: 20px; 27 | margin-bottom: 20px; 28 | width: 100%; 29 | } 30 | 31 | .altText { 32 | margin-top: 5px; 33 | font-weight: 500; 34 | font-size: 12px; 35 | color: #888; 36 | } 37 | } 38 | 39 | .authInput { 40 | height: 40px; 41 | font-family: $primary-font; 42 | font-size: 15px; 43 | } 44 | 45 | .nameInputs { 46 | display: flex; 47 | justify-content: space-between; 48 | 49 | .authInput { 50 | width: 48%; 51 | } 52 | } 53 | 54 | .actionBtn { 55 | width: 100%; 56 | height: 40px; 57 | color: white; 58 | @include flexCenter(row); 59 | font-size: 15px; 60 | font-weight: bold; 61 | margin-top: 20px; 62 | margin-bottom: 5px; 63 | transition: 0.2s; 64 | } 65 | 66 | .errorText { 67 | color: rgb(255, 31, 87); 68 | font-size: $para5; 69 | margin-top: 10px; 70 | } 71 | 72 | .githubBtn { 73 | height: 40px; 74 | @include flexCenter(row); 75 | 76 | .icon { 77 | margin-right: 10px; 78 | font-size: $header4; 79 | margin-bottom: 0.5px; 80 | } 81 | 82 | .text { 83 | font-size: $para2; 84 | font-weight: 600; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/renderer/styles/Home/CreateProjectModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .createProjModal .contentContainer { 4 | min-width: none; 5 | max-width: none; 6 | width: 350px; 7 | } 8 | 9 | .contentContainer .topBar { 10 | padding: 0px; 11 | height: 30px; 12 | 13 | .title { 14 | font-size: $para1; 15 | } 16 | } 17 | 18 | .contentContainer .middleBar { 19 | align-items: flex-start; 20 | 21 | .inputTitle { 22 | margin-bottom: 10px; 23 | font-size: $para3; 24 | font-weight: 600; 25 | color: #222; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/renderer/styles/Home/HomeSidebar.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .homeSidebar { 4 | height: 100vh; 5 | background-color: white; 6 | padding: 30px 20px; 7 | padding-right: 40px; 8 | width: 200px; 9 | z-index: 10; 10 | } 11 | 12 | .homeSidebar .logoContainer { 13 | display: flex; 14 | align-items: center; 15 | transform: translateX(-5px); 16 | 17 | .logoImg { 18 | object-fit: contain; 19 | width: 27px; 20 | margin-right: 5px; 21 | } 22 | 23 | .logoTxt { 24 | font-size: $para3; 25 | color: black; 26 | font-weight: bold; 27 | } 28 | } 29 | 30 | .homeSidebar .profileContainer { 31 | display: flex; 32 | align-items: center; 33 | margin-bottom: 10px; 34 | 35 | .iconContainer { 36 | @include flexCenter(row); 37 | margin-right: 6px; 38 | color: $textGrey; 39 | } 40 | 41 | p { 42 | font-size: $para5; 43 | color: $textSecondary; 44 | } 45 | } 46 | 47 | .homeSidebar .right { 48 | .homeBtn { 49 | width: 115%; 50 | @include flexCenter(row); 51 | font-size: $para5; 52 | } 53 | } 54 | 55 | // .vsSwitchContainer { 56 | // display: flex; 57 | // align-items: center; 58 | // transform: translateX(-5px); 59 | 60 | // .logoImg { 61 | // object-fit: contain; 62 | // width: 16px; 63 | // margin-right: 8px; 64 | // margin-left: 8px; 65 | // } 66 | 67 | // p { 68 | // font-size: $para4; 69 | // color: $textSecondary 70 | // } 71 | // } 72 | -------------------------------------------------------------------------------- /src/renderer/styles/Home/NewPremiumModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .newPremiumModal .contentContainer{ 4 | width: 370px; 5 | max-width: none; 6 | min-width: none; 7 | padding: 10px 25px; 8 | } 9 | 10 | .newPremiumModal .contentContainer .topBar { 11 | justify-content: flex-end; 12 | margin-bottom: 0px; 13 | 14 | button .icon { 15 | color: $textGrey 16 | } 17 | } 18 | 19 | .newPremiumModal .contentContainer .middleContainer { 20 | .title { 21 | font-size: $header3; 22 | 23 | .icon { 24 | margin-right: 10px; 25 | } 26 | 27 | span { 28 | @include primaryGradientText(); 29 | } 30 | } 31 | 32 | .thankYouTxt { 33 | font-size: $para3; 34 | color: $textSecondary; 35 | 36 | } 37 | } -------------------------------------------------------------------------------- /src/renderer/styles/Home/OpenWithVsModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .openWithVsModal .contentContainer { 4 | max-width: none; 5 | width: 420px; 6 | } 7 | 8 | .openWithVsModal .middleBar { 9 | .inputTitle { 10 | margin-bottom: 6px; 11 | } 12 | 13 | .requirementContainer { 14 | display: flex; 15 | align-items: flex-start; 16 | margin-bottom: 4px; 17 | 18 | .bulletIcon { 19 | width: 15px; 20 | } 21 | 22 | p { 23 | font-size: $para4; 24 | margin-right: 5px; 25 | color: $textSecondary; 26 | } 27 | 28 | .icon { 29 | color: darken($successColor, 10%); 30 | } 31 | 32 | .reqFailedIcon { 33 | margin-top: 1.5px; 34 | color: $errColor; 35 | } 36 | } 37 | 38 | .noteContainer { 39 | flex-direction: column; 40 | 41 | p:nth-child(1) { 42 | margin-bottom: 10px; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/renderer/styles/Home/UpgradeModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .upgradeModal .contentContainer { 4 | max-width: none; 5 | width: 380px; 6 | padding: 30px; 7 | } 8 | 9 | .upgradeModal .contentContainer .topBar { 10 | .title { 11 | font-size: $header2; 12 | span { 13 | @include primaryGradientText(); 14 | } 15 | } 16 | 17 | .icon { 18 | font-size: $header3; 19 | color: $textGrey; 20 | } 21 | } 22 | 23 | .benefitsContainer { 24 | display: flex; 25 | flex-direction: column; 26 | 27 | .row { 28 | display: flex; 29 | align-items: center; 30 | margin-bottom: 10px; 31 | 32 | .icon { 33 | margin-right: 10px; 34 | font-size: $para3; 35 | } 36 | 37 | .text { 38 | font-size: $para2; 39 | font-weight: 600; 40 | } 41 | } 42 | } 43 | 44 | .upgradeBtn { 45 | font-size: $para1; 46 | @include flexCenter(row); 47 | width: 100%; 48 | @include primaryGradient(); 49 | color: white; 50 | height: 40px; 51 | border-radius: 10px; 52 | 53 | &:hover { 54 | background: linear-gradient(to right, #7834e6, #1632e8); 55 | } 56 | 57 | .icon { 58 | margin-right: 10px; 59 | } 60 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateModule/CreateFirebase.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | .createFirebase .headerBar { 5 | @include createModuleHeaderBar(); 6 | } 7 | 8 | .firebaseUpload { 9 | max-width: 100%; 10 | } 11 | 12 | .firebaseConnected { 13 | font-size: $para3; 14 | margin-bottom: 20px; 15 | margin-left: 3px; 16 | color: $textGrey; 17 | 18 | .icon { 19 | font-size: $para5; 20 | margin-right: 6px; 21 | } 22 | 23 | p { 24 | margin-top: 2px; 25 | font-weight: bold; 26 | color: #222; 27 | } 28 | } 29 | 30 | .projectStatusContainer { 31 | display: flex; 32 | align-items: center; 33 | 34 | .statusIcon { 35 | margin-left: 3px; 36 | margin-right: 6px; 37 | } 38 | .statusIconEmpty { 39 | color: rgb(255, 217, 0); 40 | } 41 | .statusIconSuccess { 42 | color: $successColor; 43 | } 44 | .statusIconError { 45 | color: $errColor; 46 | } 47 | 48 | .text { 49 | font-size: $para4; 50 | color: $textGrey; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateModule/CreateModule.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | @import '../projectConfig'; 3 | 4 | .createModule .headerBar { 5 | @include createModuleHeaderBar(); 6 | } 7 | 8 | .inputDescription { 9 | margin-bottom: 20px; 10 | font-size: $para5; 11 | } 12 | 13 | // Firebase Specific 14 | 15 | .firebaseConnected { 16 | font-size: $para3; 17 | margin-bottom: 20px; 18 | margin-left: 3px; 19 | color: $textGrey; 20 | 21 | .icon { 22 | font-size: $para5; 23 | margin-right: 6px; 24 | } 25 | 26 | p { 27 | margin-top: 2px; 28 | font-weight: bold; 29 | color: #222; 30 | } 31 | } 32 | 33 | .projectStatusContainer { 34 | display: flex; 35 | align-items: center; 36 | 37 | .statusIcon { 38 | margin-left: 3px; 39 | margin-right: 6px; 40 | } 41 | .statusIconEmpty { 42 | color: rgb(255, 217, 0); 43 | } 44 | .statusIconSuccess { 45 | color: $successColor; 46 | } 47 | .statusIconError { 48 | color: $errColor; 49 | } 50 | 51 | .text { 52 | font-size: $para4; 53 | color: $textGrey; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateModule/CreateModuleModal.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | @import '../projectConfig';; 3 | 4 | .modalBackground { 5 | @include modalBg; 6 | 7 | } 8 | 9 | .createModuleModal .contentContainer { 10 | background-color: white; 11 | max-width: none; 12 | width: 350px; 13 | } 14 | 15 | .dbSelect { 16 | width: 50%; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateModule/ModuleTypes/CreateMongoDB.scss: -------------------------------------------------------------------------------- 1 | @import '../../projectConfig'; 2 | @import '../../../config'; 3 | 4 | .createMongoDb .headerBar { 5 | display: flex; 6 | align-items: center; 7 | justify-content: flex-start; 8 | margin-bottom: 20px; 9 | 10 | .header { 11 | font-weight: bold; 12 | margin-left: 10px; 13 | } 14 | } 15 | 16 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateModule/SelectModule.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | 5 | .selectModule .headerBar { 6 | font-weight: bold; 7 | margin-bottom: 20px; 8 | display: flex; 9 | align-items: center; 10 | justify-content: space-between; 11 | } 12 | 13 | .selectModule .selectionContainer { 14 | display: flex; 15 | flex-direction: column; 16 | } 17 | 18 | .moduleSelection { 19 | width: 100%; 20 | height: 45px; 21 | padding: 0px 10px; 22 | margin-bottom: 15px; 23 | 24 | box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 15px 0px, rgba(0, 0, 0, 0.08) 0px 0px 0px 1px; 25 | background-color: white; 26 | border-radius: 5px; 27 | display: flex; 28 | flex-direction: row; 29 | align-items: center; 30 | justify-content: space-between; 31 | 32 | .left { 33 | display: flex; 34 | align-items: center; 35 | } 36 | 37 | .left .logo { 38 | object-fit: contain; 39 | width: 25px; 40 | height: 25px; 41 | margin-right: 10px; 42 | } 43 | 44 | .right { 45 | margin-right: 10px; 46 | } 47 | } 48 | 49 | .moreComingTxt { 50 | font-size: $para5; 51 | color: $textGrey; 52 | padding-left: 2.5px; 53 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/CreateRouteModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | @import './projectConfig'; 3 | 4 | .modalBackground { 5 | @include modalBg; 6 | } 7 | 8 | .contentContainer { 9 | width: 300px; 10 | background-color: white; 11 | padding: 20px; 12 | position: relative; 13 | } 14 | 15 | .contentContainer .title { 16 | font-weight: bold; 17 | font-size: $para1; 18 | } 19 | 20 | .contentContainer .topBar { 21 | display: flex; 22 | align-items: center; 23 | justify-content: space-between; 24 | margin-bottom: 10px; 25 | 26 | .closeBtn .icon { 27 | font-size: 18px; 28 | } 29 | } 30 | 31 | .recText { 32 | font-size: $para5; 33 | color: $textGrey; 34 | padding-left: 3px; 35 | margin-bottom: 10px; 36 | } 37 | 38 | .pathContainer { 39 | display: flex; 40 | align-items: center; 41 | } 42 | 43 | .createInput { 44 | width: 80%; 45 | height: 30px; 46 | } 47 | 48 | .pathPreview { 49 | font-size: 15px; 50 | } 51 | 52 | .contentContainer .closeBtn { 53 | // font-size: 20px; 54 | // position: absolute; 55 | // top: 10px; 56 | // right: 10px; 57 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/DeleteProjectModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | @import './projectConfig'; 3 | 4 | .deleteProject .contentContainer { 5 | min-width: none; 6 | max-width: none; 7 | width: 350px; 8 | } 9 | 10 | .deleteProject .contentContainer .middleBar { 11 | 12 | .description { 13 | color: $textGrey; 14 | font-size: $para5; 15 | ; 16 | } 17 | 18 | .projectKeyTxt { 19 | width: fit-content; 20 | margin: 15px 0px; 21 | padding: 5px 10px; 22 | font-size: $para5; 23 | background-color: #eee; 24 | border-radius: 5px; 25 | font-weight: 600; 26 | 27 | } 28 | } 29 | 30 | .btnText { 31 | margin-top: 5px; 32 | font-size: $para5; 33 | color: $textGrey; 34 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/EditorScreen/EditorScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | .editorScreen { 5 | width: 100%; 6 | min-height: 100vh; 7 | 8 | display: flex; 9 | flex-direction: column; 10 | } 11 | 12 | .editorScreen .topBar { 13 | width: 100%; 14 | display: flex; 15 | flex-direction: column; 16 | 17 | padding: 10px 20px; 18 | border-bottom: 1px solid #ccc; 19 | 20 | .title { 21 | font-weight: 600; 22 | font-size: $para3; 23 | margin-bottom: 5px; 24 | transition: 0.1s; 25 | } 26 | 27 | .titleUnsaved { 28 | color: #888; 29 | transition: 0.1s; 30 | } 31 | 32 | .afterText { 33 | font-size: $para5; 34 | color: #555; 35 | } 36 | } 37 | 38 | .editorScreen .topBar .row1 { 39 | display: flex; 40 | justify-content: space-between; 41 | align-items: center; 42 | } 43 | 44 | .editorScreen .editorContainer { 45 | height: 100%; 46 | } 47 | 48 | #my-editor { 49 | .ace_gutter { 50 | background-color: white; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Endpoint/EndpointScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | 3 | .endpointContainer { 4 | width: 100%; 5 | } 6 | 7 | $topBarHeight: 40px; 8 | .topBar { 9 | height: $topBarHeight; 10 | display: flex; 11 | } 12 | 13 | .middleBar { 14 | width: 100%; 15 | height: calc(100% - $topBarHeight); 16 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/CreateFuncModal.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | .createFuncModal { 5 | 6 | } 7 | 8 | .inputTitle { 9 | font-weight: bold; 10 | font-size: $para4; 11 | color: #444; 12 | margin-bottom: 10px; 13 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/FirebaseModule/FirebaseAuthManager.scss: -------------------------------------------------------------------------------- 1 | @import '../../../config'; 2 | @import '../../projectConfig'; 3 | 4 | 5 | .fbAuthScreen .mainContainer{ 6 | .description { 7 | width: 100%; 8 | display: flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | } 12 | 13 | .description .addBtn { 14 | margin-right: 10px; 15 | .icon { 16 | margin-right: 5px; 17 | } 18 | } 19 | } 20 | 21 | .fbAuthScreen .infoContainer { 22 | .variableContainer { 23 | display: flex; 24 | 25 | .varName { 26 | font-weight: bold; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/FirebaseModule/FirestoreManager.scss: -------------------------------------------------------------------------------- 1 | @import '../../../config'; 2 | @import '../../projectConfig'; 3 | 4 | .fbFirestoreScreen .infoContainer { 5 | .variableContainer { 6 | display: flex; 7 | 8 | .varName { 9 | font-weight: bold; 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/ModuleFuncScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | @import '../projectConfig'; 3 | 4 | 5 | $topbarHeight: 40px; 6 | .funcScreen { 7 | width: 100%; 8 | height: calc(100vh - 40px); 9 | } 10 | 11 | .funcScreen .topBar { 12 | display: flex; 13 | align-items: center; 14 | height: 40px; 15 | padding: 0px 10px; 16 | justify-content: space-between; 17 | } 18 | 19 | .my-ace-editor .ace_gutter, .ace_scroller{ 20 | background-color: white; 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/MongoModule/CreateMongoFuncModal.scss: -------------------------------------------------------------------------------- 1 | @import '../../projectConfig'; 2 | @import '../../../config'; 3 | 4 | 5 | .createDbFuncModal .contentContainer { 6 | width: 300px; 7 | background-color: white; 8 | padding: 20px; 9 | } 10 | 11 | .contentContainer .topBar { 12 | height: 30px; 13 | } 14 | 15 | .inputTitle { 16 | margin-bottom: 10px; 17 | font-size: $para4; 18 | font-weight: 600; 19 | } 20 | 21 | .contentContainer .middleContainer { 22 | 23 | .input { 24 | font-size: 13px; 25 | } 26 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/MongoModule/ManageMongo.scss: -------------------------------------------------------------------------------- 1 | @import '../../../config'; 2 | @import '../../projectConfig'; 3 | 4 | .mongoScreen { 5 | width: 100%; 6 | height: 100%; 7 | } 8 | 9 | .mongoScreen .headerContainer { 10 | display: flex; 11 | align-items: center; 12 | 13 | .title { 14 | font-size: $para3; 15 | margin-right: 10px; 16 | } 17 | } 18 | 19 | .mongoScreen .infoContainer { 20 | padding: 0px $sectionHorizPadding; 21 | font-size: $para5; 22 | margin-bottom: 20px; 23 | 24 | .variableContainer { 25 | display: flex; 26 | .varName { 27 | font-weight: bold; 28 | width: 80px; 29 | } 30 | } 31 | } 32 | 33 | .mongoScreen .mainContainer { 34 | width: 100%; 35 | padding: 0px $sectionHorizPadding; 36 | font-weight: bold; 37 | font-size: $para4; 38 | box-shadow: none; 39 | 40 | .description .title { 41 | margin-bottom: 5px; 42 | } 43 | 44 | .description .info { 45 | font-weight: 500; 46 | font-size: $para5; 47 | } 48 | } 49 | 50 | .mongoScreen .mainContainer { 51 | justify-content: flex-start; 52 | } 53 | 54 | .mongoScreen .mainContainer .colContainer { 55 | 56 | background-color: white; 57 | border-radius: 5px; 58 | padding: 10px 10px; 59 | width: 100%; 60 | box-shadow: rgba(0, 0, 0, 0.02) 0px 6px 20px 0px, rgba(0, 0, 0, 0.08) 0px 0px 0px 1px; 61 | margin-bottom: 20px; 62 | 63 | .colHeader { 64 | display: flex; 65 | align-items: center; 66 | justify-content: space-between; 67 | width: 100%; 68 | height: 30px; 69 | 70 | .colTitle { 71 | margin-right: 10px; 72 | } 73 | 74 | .createBtn { 75 | display: flex; 76 | align-items: center; 77 | height: 100%; 78 | font-size: $para5; 79 | width: 100px; 80 | color: #555; 81 | font-family: $primary-font; 82 | } 83 | } 84 | 85 | .funcContainer { 86 | padding: 0px 10px; 87 | margin: 0px 10px; 88 | height: 30px; 89 | display: flex; 90 | align-items: center; 91 | border-left: 1px solid #222; 92 | font-size: $para5; 93 | font-weight: 500; 94 | transition: 0.2s ; 95 | &:hover { 96 | background-color: #EEE; 97 | } 98 | } 99 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/MongoModule/funcScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | @import '../projectConfig'; 3 | 4 | 5 | $topbarHeight: 40px; 6 | .mongoScreen { 7 | width: 100%; 8 | height: calc(100% - 40px); 9 | } 10 | 11 | .mongoScreen .topBar { 12 | display: flex; 13 | align-items: center; 14 | height: 40px; 15 | padding: 0px 10px; 16 | justify-content: space-between; 17 | } 18 | 19 | .my-ace-editor .ace_gutter, .ace_scroller{ 20 | background-color: #FAFAFA; 21 | } 22 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Modules/StripeModule/AddWebhookTemplateModal.scss: -------------------------------------------------------------------------------- 1 | @import '../../projectConfig'; 2 | @import '../../../config'; 3 | 4 | .addWebhookTemplateModal .contentContainer{ 5 | min-width: none; 6 | max-width: none; 7 | width: 350px; 8 | } 9 | 10 | .webhookTemplateContainer { 11 | box-shadow: rgba(0, 0, 0, 0.05) 0px 6px 24px 0px, rgba(0, 0, 0, 0.08) 0px 0px 0px 1px; 12 | padding: 15px 12px; 13 | border-radius: 6px; 14 | font-weight: 500; 15 | width: 100%; 16 | display: flex; 17 | justify-content: space-between; 18 | align-items: center; 19 | margin-bottom: 25px; 20 | transition: 0.2s; 21 | 22 | &:hover { 23 | transform: translateY(-2px); 24 | } 25 | } 26 | 27 | .webhookTemplateContainer .row1 { 28 | display: flex; 29 | align-items: center; 30 | 31 | .icon { 32 | margin-right: 12px; 33 | width: 25px; 34 | } 35 | 36 | .name { 37 | font-size: $para3; 38 | color: $textSecondary; 39 | padding-bottom: 1px; 40 | font-weight: 600; 41 | } 42 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/PackageManager/PackageManager.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | .packageManager .headerContainer { 5 | padding: $sectionHeaderPadding; 6 | 7 | .header { 8 | font-size: $para2; 9 | font-weight: bold; 10 | margin-bottom: 10px; 11 | } 12 | 13 | .installHeader { 14 | font-weight: 600; 15 | margin-bottom: 5px; 16 | font-size: $para3; 17 | } 18 | } 19 | 20 | .packageManager .middleContainer { 21 | margin-top: 20px; 22 | 23 | .header { 24 | margin-bottom: 10px; 25 | font-size: $para3; 26 | font-weight: 600; 27 | padding: 0px $sectionHorizPadding; 28 | } 29 | 30 | .pkgRow { 31 | padding: 0px $sectionHorizPadding; 32 | display: flex; 33 | justify-content: space-between; 34 | align-items: center; 35 | height: 35px; 36 | font-size: $para4; 37 | transition: 0.2s; 38 | 39 | &:hover { 40 | background-color: #fafafa; 41 | } 42 | } 43 | 44 | .pkgRow .left { 45 | display: flex; 46 | 47 | .pkgName { 48 | width: 100px; 49 | overflow: hidden; 50 | text-overflow: ellipsis; 51 | white-space: nowrap; 52 | } 53 | 54 | .pkgVersion { 55 | width: 80px; 56 | overflow: hidden; 57 | text-overflow: ellipsis; 58 | white-space: nowrap; 59 | } 60 | } 61 | 62 | .delBtn { 63 | height: 80%; 64 | @include flexCenter(row); 65 | color: grey; 66 | } 67 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/ProjectScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .projectScreen { 4 | max-width: 100vw; 5 | min-width: 100vw; 6 | min-height: 100vh; 7 | display: flex; 8 | overflow-x: hidden; 9 | overflow-y: hidden; 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/RouteRow.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | .routeRow { 4 | max-width: 100%; 5 | @include flexCenter(row); 6 | justify-content: flex-start; 7 | height: 35px; 8 | padding: 0px 15px; 9 | transition: 0.2s; 10 | user-select: none; 11 | font-size: 14px; 12 | cursor: pointer; 13 | 14 | .type { 15 | font-weight: bold; 16 | width: 70px; 17 | } 18 | 19 | .path { 20 | width: 140px; 21 | overflow: hidden; 22 | text-overflow: ellipsis; 23 | } 24 | 25 | .chevronIcon { 26 | font-size: 12px; 27 | margin-right: 10px; 28 | transition: 0.2s; 29 | } 30 | 31 | .chevronIconOpened { 32 | transform: rotateZ(90deg); 33 | } 34 | 35 | .chevronIconHidden { 36 | color: transparent !important; 37 | } 38 | 39 | &:hover { 40 | background-color: #EFEFEF; 41 | } 42 | } 43 | 44 | .groupActionBtn { 45 | .icon { 46 | color: #434851; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/Routes/RoutesScreen.scss: -------------------------------------------------------------------------------- 1 | @import '../../config'; 2 | 3 | .routesBg { 4 | height: 100vh; 5 | overflow: hidden; 6 | } 7 | 8 | .headerContainer { 9 | // margin-bottom: 5px; 10 | height: 40px; 11 | padding: 20px 12px 0px 12px; 12 | margin-bottom: 10px; 13 | font-size: $para4; 14 | } 15 | 16 | .routesContainer { 17 | width: 100%; 18 | height: calc(100vh - 40px - 10px); 19 | padding-bottom: 50px; 20 | overflow: scroll; 21 | overflow-x: hidden; 22 | } 23 | 24 | .routeHeader { 25 | font-weight: bold; 26 | } 27 | 28 | .groupActionBtn { 29 | background-color: transparent; 30 | color: black; 31 | padding: 0px; 32 | font-size: 20px; 33 | margin-right: 20px; 34 | } 35 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/SearchFuncsModal.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | @import './projectConfig'; 3 | 4 | .modalBackground { 5 | @include modalBg; 6 | z-index: 1000; 7 | } 8 | 9 | .modalBackground .searchContainer { 10 | width: 350px; 11 | background-color: white; 12 | padding: 10px; 13 | 14 | .ant-input-affix-wrapper { 15 | .ant-input-prefix { 16 | margin-right: 10px; 17 | font-size: 13px; 18 | color: grey; 19 | } 20 | } 21 | } 22 | 23 | .funcSearchInput { 24 | box-shadow: none; 25 | border: 1px solid #ccc; 26 | border-radius: 5px !important; 27 | margin-bottom: 10px; 28 | 29 | .ant-input-prefix { 30 | font-size: 12px !important; 31 | } 32 | 33 | &:hover { 34 | border-color: #ccc !important; 35 | } 36 | } 37 | 38 | .searchContainer .funcsContainer .funcContainer { 39 | width: 100%; 40 | height: 20px; 41 | padding: 14px 10px; 42 | font-weight: 0px; 43 | display: flex; 44 | align-items: center; 45 | font-size: $para4; 46 | font-weight: 500; 47 | color: #555; 48 | border-radius: 2px; 49 | 50 | &:hover { 51 | background-color: #fafafa; 52 | } 53 | } 54 | 55 | .activeFuncSelection { 56 | background-color: #d8d8d8; 57 | color: black !important; 58 | 59 | &:hover { 60 | background-color: #d8d8d8 !important; 61 | } 62 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/SectionManager/HostingManager/HostingManager.scss: -------------------------------------------------------------------------------- 1 | @import '../../projectConfig'; 2 | @import '../../../config'; 3 | 4 | .hostingManager { 5 | height: 100%; 6 | width: 100%; 7 | padding: 20px $sectionHorizPadding; 8 | 9 | .title { 10 | font-size: $para2; 11 | } 12 | } 13 | 14 | .hostingManager .row1 { 15 | width: 100%; 16 | display: flex; 17 | align-items: center; 18 | padding: 0px; 19 | 20 | .actionBtn { 21 | height: 30px; 22 | width: fit-content; 23 | font-size: $para5; 24 | color: #333; 25 | font-weight: 400; 26 | margin: 0px; 27 | } 28 | } 29 | 30 | .sectionTitle { 31 | font-weight: bold; 32 | font-size: $para3; 33 | } 34 | 35 | .noBuildsTxt { 36 | color: $textGrey; 37 | font-size: $para3; 38 | margin-top: 8px; 39 | } 40 | 41 | .buildRow { 42 | display: flex; 43 | align-items: center; 44 | height: 30px; 45 | 46 | .statusIcon { 47 | font-size: $para6; 48 | margin-right: 10px; 49 | } 50 | 51 | .successIcon { 52 | color: rgb(0, 190, 95) 53 | } 54 | 55 | .failIcon { 56 | color: $errColor; 57 | } 58 | 59 | .buildId { 60 | font-size: $para4; 61 | width: 100px; 62 | } 63 | 64 | .buildTime { 65 | font-size: $para5; 66 | color: $textGrey; 67 | } 68 | } -------------------------------------------------------------------------------- /src/renderer/styles/Project/SectionManager/SectionManager.scss: -------------------------------------------------------------------------------- 1 | @import '../projectConfig'; 2 | @import '../../config'; 3 | 4 | .sectionManager { 5 | max-width: $sectionManagerWidth; 6 | min-width: $sectionManagerWidth; 7 | background-color: white; 8 | box-shadow: rgba(0, 0, 0, 0.05) 3px 0px 3px; 9 | z-index: 10; 10 | } 11 | -------------------------------------------------------------------------------- /src/renderer/styles/Project/_projectConfig.scss: -------------------------------------------------------------------------------- 1 | @import '../config'; 2 | 3 | $sectionHorizPadding: 12px; 4 | $sectionHeaderPadding: 20px 12px 0px 12px; 5 | 6 | .emptyContainer { 7 | width: 100%; 8 | height: 100vh; 9 | display: flex; 10 | align-items: center; 11 | justify-content: center; 12 | min-width: 250px; 13 | padding: 20px; 14 | text-align: center; 15 | 16 | p { 17 | color: #555; 18 | font-size: $para4; 19 | } 20 | } 21 | 22 | @mixin createModuleHeaderBar() { 23 | display: flex; 24 | align-items: center; 25 | justify-content: flex-start; 26 | margin-bottom: 20px; 27 | 28 | .header { 29 | font-weight: bold; 30 | margin-left: 10px; 31 | } 32 | } 33 | 34 | 35 | @mixin modalBg() { 36 | width: 100vw; 37 | height: 100vh; 38 | position: fixed; 39 | left: 0px; 40 | top: 0px; 41 | background-color: rgba(0, 0, 0, 0.4); 42 | @include flexCenter(row); 43 | z-index: 1001; 44 | } 45 | 46 | @mixin modalContentContainer () { 47 | background-color: white; 48 | padding: 20px; 49 | position: relative; 50 | } -------------------------------------------------------------------------------- /src/renderer/styles/_config.scss: -------------------------------------------------------------------------------- 1 | $primary-font: 'Figtree'; 2 | $font2: 'Inter'; 3 | 4 | $primaryColor: #007bff; 5 | $errColor: rgb(255, 31, 87); 6 | $successColor: rgb(0, 190, 95); 7 | $textSecondary: #444; 8 | $textGrey: #666; 9 | 10 | $sidebarWidth: 180px; 11 | $sectionManagerWidth: 250px; 12 | $bgColor1: white; 13 | $bgColor2: #FAFAFA; 14 | $iconColor: #535469; 15 | 16 | @mixin flexCenter($flex-direction) { 17 | display: flex; 18 | flex-direction: $flex-direction; 19 | justify-content: center; 20 | align-items: center; 21 | } 22 | 23 | 24 | $header1: 24px; 25 | $header2: 22px; 26 | $header3: 20px; 27 | $header4: 19px; 28 | $header5: 18px; 29 | $para1: 17px; 30 | $para2: 16px; 31 | $para3: 15px; 32 | $para4: 14px; 33 | $para5: 13px; 34 | $para6: 12px; 35 | $antdRadius: 6px; 36 | 37 | @mixin primaryGradient() { 38 | background: #4776E6; 39 | background: -webkit-linear-gradient(to right, #8E54E9, #4776E6); 40 | background: linear-gradient(to right, #8E54E9, #3d55f6); 41 | } 42 | 43 | @mixin primaryGradientText() { 44 | background: #4776E6; 45 | background: -webkit-linear-gradient(to bottom, #8E54E9, #4776E6); 46 | background: linear-gradient(to right, #8E54E9, #3d55f6); 47 | -webkit-background-clip: text; 48 | background-clip: text; 49 | -webkit-text-fill-color: transparent; 50 | } 51 | -------------------------------------------------------------------------------- /src/renderer/styles/index.scss: -------------------------------------------------------------------------------- 1 | @import './config'; 2 | 3 | * { 4 | box-sizing: border-box; 5 | background-color: transparent; 6 | } 7 | 8 | body { 9 | margin: 0; 10 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 11 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 12 | sans-serif; 13 | 14 | font-family: $primary-font; 15 | font-weight: 500; 16 | -webkit-font-smoothing: antialiased; 17 | -moz-osx-font-smoothing: grayscale; 18 | background-color: transparent; 19 | box-shadow: rgba(0, 0, 0, 0.24) 0px 3px 8px; 20 | } 21 | 22 | button { 23 | border: none; 24 | background-color: transparent; 25 | padding: 0px; 26 | font-weight: bold; 27 | cursor: pointer; 28 | width: fit-content; 29 | font-family: $primary-font; 30 | } 31 | 32 | h3, 33 | p { 34 | margin: 0px; 35 | } 36 | 37 | code { 38 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 39 | monospace; 40 | } 41 | 42 | .ant-button { 43 | font-weight: bold; 44 | } 45 | 46 | @import url('https://fonts.googleapis.com/css2?family=Figtree:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap'); 47 | -------------------------------------------------------------------------------- /src/shared/assets/images/chatgpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/chatgpt.png -------------------------------------------------------------------------------- /src/shared/assets/images/fb-auth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/fb-auth.png -------------------------------------------------------------------------------- /src/shared/assets/images/fb-firestore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/fb-firestore.png -------------------------------------------------------------------------------- /src/shared/assets/images/firebase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/firebase.png -------------------------------------------------------------------------------- /src/shared/assets/images/jwt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/jwt.png -------------------------------------------------------------------------------- /src/shared/assets/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/logo.png -------------------------------------------------------------------------------- /src/shared/assets/images/mongodb-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/mongodb-logo.png -------------------------------------------------------------------------------- /src/shared/assets/images/resend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/resend.png -------------------------------------------------------------------------------- /src/shared/assets/images/sendgrid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/sendgrid.png -------------------------------------------------------------------------------- /src/shared/assets/images/stripe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/stripe.png -------------------------------------------------------------------------------- /src/shared/assets/images/supabase.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/supabase.png -------------------------------------------------------------------------------- /src/shared/assets/images/vscode.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/assets/images/vscode.png -------------------------------------------------------------------------------- /src/shared/db/main.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/db/main.db -------------------------------------------------------------------------------- /src/shared/db/test-project-gen.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/vbackend/visual-backend/4b2ffed259962ac3da733fb282b1e15d5a9a6a5b/src/shared/db/test-project-gen.db -------------------------------------------------------------------------------- /src/shared/models/BFunc.ts: -------------------------------------------------------------------------------- 1 | import { GenFuncs } from '../utils/GenFuncs'; 2 | import { BModule, BModuleFuncs } from './BModule'; 3 | 4 | export enum ExtensionType { 5 | ts = 'ts', 6 | html = 'html', 7 | tsx = 'tsx', 8 | } 9 | 10 | export type BFunc = { 11 | id?: number; 12 | key: string; 13 | moduleKey: string; 14 | funcGroup: string; 15 | extension: string; 16 | }; 17 | 18 | export class BFuncHelpers { 19 | static camelise = (str: string) => { 20 | return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { 21 | if (+match === 0) return ''; // or if (/\s+/.test(match)) for white spaces 22 | return index === 0 ? match.toLowerCase() : match.toUpperCase(); 23 | }); 24 | }; 25 | 26 | static getImportStatement = (func: BFunc, m: BModule) => { 27 | let funcName = BFuncHelpers.getFuncName(func); 28 | let path = 29 | func.funcGroup == '*' ? `${funcName}` : `${func.funcGroup}/${funcName}`; 30 | 31 | if (func.extension == 'html') 32 | return `import fs from 'fs'; 33 | import ejs from 'ejs'; 34 | import path from 'path'`; 35 | 36 | return `import { ${funcName} } from '@/modules/${m.path}/${path}.js'`; 37 | }; 38 | 39 | static nameToFuncName = (name: string) => { 40 | return this.camelise(name); 41 | }; 42 | 43 | static getFuncName = (func: BFunc) => { 44 | // return this.camelise(`${func.key}`); 45 | return func.key; 46 | }; 47 | 48 | static getFuncCallTemplate = (f: BFunc) => { 49 | if (f.extension == 'html') { 50 | return ` 51 | let htmlFilePath = path.join(process.cwd(), 'src/modules/resend/templates', '${this.getFuncName( 52 | f 53 | )}.html') 54 | const template = fs.readFileSync(htmlFilePath, 'utf8'); 55 | const data = { varKey: 'value' }; 56 | const htmlString = ejs.render(template, data);`; 57 | } else return `${this.getFuncName(f)}();`; 58 | }; 59 | 60 | static getFuncPath = (f: BFunc) => { 61 | if (f.funcGroup == '*') { 62 | return `${f.key}.${f.extension}`; 63 | } else return `${f.funcGroup}/${f.key}.${f.extension}`; 64 | }; 65 | } 66 | -------------------------------------------------------------------------------- /src/shared/models/Editor.ts: -------------------------------------------------------------------------------- 1 | export enum Editor { 2 | VISUALBACKEND = 'Visual Backend', 3 | VSCODE = 'VS Code', 4 | INTELLIJ = 'IntelliJ IDEA', 5 | // WebStorm = 'WebStorm', 6 | // PyCharm = 'PyCharm', 7 | } 8 | -------------------------------------------------------------------------------- /src/shared/models/GBuild.ts: -------------------------------------------------------------------------------- 1 | export enum BuildStatus { 2 | SUCCESS = 'SUCCESS', 3 | WORKING = 'WORKING', 4 | } 5 | 6 | export type GBuild = { 7 | id: string; 8 | status: BuildStatus; 9 | createTime: number; 10 | }; 11 | -------------------------------------------------------------------------------- /src/shared/models/NodeType.ts: -------------------------------------------------------------------------------- 1 | export enum NodeType { 2 | Downloaded = 'downloaded', 3 | Default = 'default', 4 | InvalidVersion = 'invalid-version', 5 | NotFound = 'not-found', 6 | } 7 | -------------------------------------------------------------------------------- /src/shared/models/User.ts: -------------------------------------------------------------------------------- 1 | export enum AccountTier { 2 | Starter = 'starter', 3 | Premium = 'premium', 4 | } 5 | 6 | export enum SubStatus { 7 | Active = 'active', 8 | PastDue = 'past_due', 9 | Cancelled = 'cancelled', 10 | } 11 | 12 | export type User = { 13 | username: string; 14 | email: string; 15 | accountTier: AccountTier; 16 | subscription: { 17 | subId: string; 18 | status: SubStatus; 19 | } | null; 20 | }; 21 | -------------------------------------------------------------------------------- /src/shared/models/dbConn.ts: -------------------------------------------------------------------------------- 1 | export enum DbType { 2 | MySQL = 'MySQL', 3 | MongoDB = 'MongoDB', 4 | } 5 | 6 | export type DbConn = { 7 | id: number; 8 | type: DbType; 9 | connString: string; 10 | dbName: string; 11 | }; 12 | -------------------------------------------------------------------------------- /src/shared/models/project.ts: -------------------------------------------------------------------------------- 1 | export enum ProjectType { 2 | Express = 'express', 3 | FastAPI = 'fastapi', 4 | } 5 | 6 | export type Project = { 7 | _id: string; 8 | project_id?: number; 9 | name: string; 10 | key: string; 11 | project_type?: ProjectType; 12 | gitlabProjectId: number; 13 | projectAccessToken: string; 14 | triggerId: string; 15 | }; 16 | 17 | export type EnvVar = { 18 | key: string; 19 | val: string; 20 | }; 21 | 22 | export class ProjectFuncs { 23 | static getKey = (name: string) => name.toLowerCase().replaceAll(' ', '-'); 24 | } 25 | -------------------------------------------------------------------------------- /src/shared/utils/GenFuncs.ts: -------------------------------------------------------------------------------- 1 | import { app } from 'electron'; 2 | import { Route, RouteFuncs } from '../models/route'; 3 | import { Platform } from '@/renderer/redux/app/appSlice'; 4 | import { ProjectType } from '../models/project'; 5 | import { modConfig, pyModConfig } from '../models/BModule'; 6 | 7 | export class GenFuncs { 8 | static toKey = (name: string) => name.toLowerCase().replaceAll(' ', '-'); 9 | 10 | static getRoutePath = (route: Route) => { 11 | if (route.key == '') { 12 | return route.parentPath; 13 | } else { 14 | return `${route.parentPath}/${route.key}`; 15 | } 16 | }; 17 | 18 | static getFilePath = ( 19 | route: Route, 20 | projType: ProjectType = ProjectType.Express 21 | ) => { 22 | if (route.parentId == -1) { 23 | return ''; 24 | } else { 25 | return `${route.parentFilePath}/${RouteFuncs.getFuncName( 26 | route, 27 | projType 28 | )}`; 29 | } 30 | }; 31 | 32 | static timeout(ms: number) { 33 | return new Promise((resolve) => setTimeout(resolve, ms)); 34 | } 35 | 36 | static camelise = (str: string) => { 37 | return str.replace(/(?:^\w|[A-Z]|\b\w|\s+)/g, function (match, index) { 38 | if (+match === 0) return ''; // or if (/\s+/.test(match)) for white spaces 39 | return index === 0 ? match.toLowerCase() : match.toUpperCase(); 40 | }); 41 | }; 42 | 43 | static getExtension = (projType: ProjectType) => { 44 | return projType == ProjectType.FastAPI ? 'py' : 'ts'; 45 | }; 46 | 47 | static isFastApi = (projType: ProjectType) => { 48 | return projType == ProjectType.FastAPI; 49 | }; 50 | 51 | static getModConfig = (modKey: string, projType: string) => { 52 | if (projType == ProjectType.FastAPI) { 53 | return pyModConfig[modKey]; 54 | } else { 55 | return modConfig[modKey]; 56 | } 57 | }; 58 | } 59 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "ts-node": { 3 | "compiler": "ts-patch/compiler" 4 | }, 5 | "compilerOptions": { 6 | "incremental": true, 7 | "target": "es2021", 8 | "module": "commonjs", 9 | "lib": ["dom", "es2021"], 10 | "jsx": "react-jsx", 11 | "strict": true, 12 | "sourceMap": true, 13 | "moduleResolution": "node", 14 | "esModuleInterop": true, 15 | "allowSyntheticDefaultImports": true, 16 | "resolveJsonModule": true, 17 | "allowJs": true, 18 | "outDir": ".erb/dll", 19 | "baseUrl": "./src", 20 | "paths": { 21 | "@/*": ["*"] 22 | }, 23 | "plugins": [ 24 | // Transform paths in output .js files 25 | { "transform": "typescript-transform-paths" }, 26 | 27 | // Transform paths in output .d.ts files (Include this line if you output declarations files) 28 | { "transform": "typescript-transform-paths", "afterDeclarations": true } 29 | ] 30 | }, 31 | "exclude": ["test", "release/build", "release/app/dist", ".erb/dll"] 32 | } 33 | -------------------------------------------------------------------------------- /zip.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const archiver = require('archiver'); 3 | 4 | const output = fs.createWriteStream( 5 | 'release/build/mac-arm64/VisualBackend-arm64.zip' 6 | ); 7 | const archive = archiver('zip', { 8 | zlib: { level: 6 }, // Sets the compression level. 9 | }); 10 | 11 | output.on('close', function () { 12 | console.log(archive.pointer() + ' total bytes'); 13 | console.log( 14 | 'archiver has been finalized and the output file descriptor has closed.' 15 | ); 16 | }); 17 | 18 | output.on('end', function () { 19 | console.log('Data has been drained'); 20 | }); 21 | 22 | const file = __dirname + '/release/build/mac-arm64/VisualBackend.app'; 23 | // console.log(file); 24 | archive.pipe(output); 25 | archive.directory(file, 'VisualBackend.app'); 26 | 27 | archive.finalize(); 28 | --------------------------------------------------------------------------------