├── .clinerules ├── .eslintignore ├── .eslintrc.js ├── .github └── workflows │ ├── autofix.yml │ ├── backend-ci.yml │ ├── codecov.yml │ ├── frontend-ci.yml │ └── llm-server.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.js ├── .tmuxinator └── dev.yml ├── .vscode ├── config.json └── settings.json ├── README.md ├── assets ├── badge.svg ├── codefox.png ├── logo-trans.png └── logo.svg ├── backend ├── .env.example ├── .eslintrc.js ├── .gitignore ├── .prettierrc ├── README.md ├── jest.config.js ├── nest-cli.json ├── package.json ├── src │ ├── app.module.ts │ ├── app.resolver.ts │ ├── auth │ │ ├── auth.module.ts │ │ ├── auth.resolver.ts │ │ ├── auth.service.ts │ │ ├── dto │ │ │ ├── check-token.input.ts │ │ │ └── login-user.input.ts │ │ ├── entities │ │ │ └── refresh-token.entity.ts │ │ ├── google.controller.ts │ │ ├── menu │ │ │ └── menu.model.ts │ │ ├── oauth │ │ │ └── GoogleStrategy.ts │ │ ├── refresh-token │ │ │ └── refresh-token.model.ts │ │ └── role │ │ │ └── role.model.ts │ ├── build-system │ │ ├── __tests__ │ │ │ ├── copy-project-template.spec.ts │ │ │ ├── fullstack-gen.spec.ts │ │ │ ├── mock │ │ │ │ ├── MockBuilderContext.ts │ │ │ │ └── test_files │ │ │ │ │ ├── Backend_Requirements_Node.md │ │ │ │ │ ├── File_Arch.md │ │ │ │ │ ├── File_Structure_Generation.md │ │ │ │ │ ├── UX_DataMap_Document_Node.md │ │ │ │ │ └── UX_Sitemap_Structure_Node.md │ │ │ ├── test-file-create-and-path.spec.ts │ │ │ ├── test-file-create.spec.ts │ │ │ ├── test-generate-doc.spec.ts │ │ │ ├── test.backend-code-generator.spec.ts │ │ │ ├── test.frontend-code-generate.spec.ts │ │ │ ├── test.sms-lvl2.spec.ts │ │ │ ├── utils.ts │ │ │ └── virtual-dir.spec.ts │ │ ├── context.ts │ │ ├── errors.ts │ │ ├── handlers │ │ │ ├── backend │ │ │ │ ├── code-generate │ │ │ │ │ ├── index.ts │ │ │ │ │ └── prompt.ts │ │ │ │ ├── file-review │ │ │ │ │ ├── file-review.ts │ │ │ │ │ └── prompt.ts │ │ │ │ └── requirements-document │ │ │ │ │ ├── index.ts │ │ │ │ │ └── prompt.ts │ │ │ ├── database │ │ │ │ ├── requirements-document │ │ │ │ │ ├── index.ts │ │ │ │ │ └── prompt.ts │ │ │ │ └── schemas │ │ │ │ │ ├── prompt.ts │ │ │ │ │ └── schemas.ts │ │ │ ├── file-manager │ │ │ │ ├── file-generate │ │ │ │ │ └── index.ts │ │ │ │ └── file-struct │ │ │ │ │ └── index.ts │ │ │ ├── frontend-code-generate │ │ │ │ ├── CodeReview.ts │ │ │ │ ├── CodeValidator.ts │ │ │ │ ├── FileOperationManager.ts │ │ │ │ ├── index.ts │ │ │ │ └── prompt.ts │ │ │ ├── product-manager │ │ │ │ └── product-requirements-document │ │ │ │ │ ├── prd.ts │ │ │ │ │ └── prompt.ts │ │ │ ├── project-init.ts │ │ │ └── ux │ │ │ │ ├── datamap │ │ │ │ ├── index.ts │ │ │ │ └── prompt.ts │ │ │ │ ├── sitemap-document │ │ │ │ ├── index.ts │ │ │ │ └── prompt.ts │ │ │ │ ├── sitemap-structure │ │ │ │ ├── index.ts │ │ │ │ ├── prompt.ts │ │ │ │ └── sms-page.ts │ │ │ │ └── uiux-layout │ │ │ │ ├── index.ts │ │ │ │ └── prompt.ts │ │ ├── hanlder-manager.ts │ │ ├── logger.ts │ │ ├── monitor.ts │ │ ├── project-builder.module.ts │ │ ├── project-builder.service.ts │ │ ├── retry-handler.ts │ │ ├── types.ts │ │ ├── utils │ │ │ ├── __test__ │ │ │ │ └── build-utils.spec.ts │ │ │ ├── build-utils.ts │ │ │ ├── database-utils.ts │ │ │ ├── file_generator_util.ts │ │ │ ├── files.ts │ │ │ ├── handler-helper.ts │ │ │ ├── security │ │ │ │ └── path-check.ts │ │ │ └── strings.ts │ │ └── virtual-dir │ │ │ └── index.ts │ ├── chat │ │ ├── chat.controller.ts │ │ ├── chat.model.ts │ │ ├── chat.module.ts │ │ ├── chat.resolver.ts │ │ ├── chat.service.ts │ │ ├── dto │ │ │ ├── chat-rest.dto.ts │ │ │ └── chat.input.ts │ │ ├── message.model.ts │ │ └── protocol.ts │ ├── common │ │ ├── decorators │ │ │ └── universal-date-column.ts │ │ ├── enums │ │ │ └── role.enum.ts │ │ ├── interfaces │ │ │ └── entity.interfaces.ts │ │ ├── model-provider │ │ │ ├── openai-model-provider.ts │ │ │ └── types.ts │ │ ├── scalar │ │ │ └── date.scalar.ts │ │ ├── security │ │ │ └── file_check.ts │ │ └── utils.ts │ ├── config │ │ ├── config.module.ts │ │ ├── config.service.ts │ │ └── env.validation.ts │ ├── database.config.ts │ ├── decorator │ │ ├── auth.decorator.ts │ │ ├── get-auth-token.decorator.ts │ │ ├── jwt-auth.decorator.ts │ │ ├── menu.decorator.ts │ │ └── roles.decorator.ts │ ├── downloader │ │ ├── __tests__ │ │ │ └── download-model.spec.ts │ │ ├── const.ts │ │ ├── embedding-downloader.ts │ │ ├── universal-status.ts │ │ └── universal-utils.ts │ ├── embedding │ │ └── local-embedding-provider.ts │ ├── github │ │ ├── github.controller.ts │ │ ├── github.module.ts │ │ ├── github.service.ts │ │ └── githubApp.service.ts │ ├── guard │ │ ├── chat.guard.ts │ │ ├── jwt-auth.guard.ts │ │ ├── menu.guard.ts │ │ └── project.guard.ts │ ├── init │ │ ├── init-roles.service.ts │ │ └── init.module.ts │ ├── interceptor │ │ ├── LoggingInterceptor.ts │ │ └── roles.guard.ts │ ├── jwt-cache │ │ ├── jwt-cache.module.ts │ │ └── jwt-cache.service.ts │ ├── mail │ │ ├── mail.module.ts │ │ ├── mail.service.ts │ │ └── templates │ │ │ ├── confirmation.hbs │ │ │ └── passwordReset.hbs │ ├── main.ts │ ├── project │ │ ├── build-system-utils.ts │ │ ├── downloadController.ts │ │ ├── dto │ │ │ └── project.input.ts │ │ ├── project-limits.ts │ │ ├── project-packages.model.ts │ │ ├── project.model.ts │ │ ├── project.module.ts │ │ ├── project.resolver.ts │ │ └── project.service.ts │ ├── prompt-tool │ │ ├── prompt-tool.module.ts │ │ ├── prompt-tool.resolver.ts │ │ └── prompt-tool.service.ts │ ├── system-base-model │ │ ├── __tests__ │ │ │ └── system-base.spec.ts │ │ └── system-base.model.ts │ ├── token │ │ ├── api-token.model.ts │ │ └── token.module.ts │ ├── upload │ │ ├── upload.module.ts │ │ └── upload.service.ts │ └── user │ │ ├── __tests__ │ │ ├── dto │ │ │ └── register-user.input.spec.ts │ │ ├── user.model.spec.ts │ │ └── user.service.spec.ts │ │ ├── dto │ │ ├── login-user.input.ts │ │ ├── register-user.input.ts │ │ ├── resend-email.input.ts │ │ └── upload-avatar.input.ts │ │ ├── user.model.ts │ │ ├── user.module.ts │ │ ├── user.resolver.ts │ │ └── user.service.ts ├── template │ ├── react-ts │ │ ├── .gitignore │ │ ├── Dockerfile │ │ ├── README.md │ │ ├── components.json │ │ ├── eslint.config.js │ │ ├── index.html │ │ ├── package-lock.json │ │ ├── package.json │ │ ├── public │ │ │ └── codefox.svg │ │ ├── src │ │ │ ├── components │ │ │ │ └── ui │ │ │ │ │ ├── accordion.tsx │ │ │ │ │ ├── alert-dialog.tsx │ │ │ │ │ ├── alert.tsx │ │ │ │ │ ├── aspect-ratio.tsx │ │ │ │ │ ├── avatar.tsx │ │ │ │ │ ├── badge.tsx │ │ │ │ │ ├── breadcrumb.tsx │ │ │ │ │ ├── button.tsx │ │ │ │ │ ├── calendar.tsx │ │ │ │ │ ├── card.tsx │ │ │ │ │ ├── carousel.tsx │ │ │ │ │ ├── chart.tsx │ │ │ │ │ ├── checkbox.tsx │ │ │ │ │ ├── collapsible.tsx │ │ │ │ │ ├── command.tsx │ │ │ │ │ ├── context-menu.tsx │ │ │ │ │ ├── dialog.tsx │ │ │ │ │ ├── drawer.tsx │ │ │ │ │ ├── dropdown-menu.tsx │ │ │ │ │ ├── form.tsx │ │ │ │ │ ├── hover-card.tsx │ │ │ │ │ ├── input-otp.tsx │ │ │ │ │ ├── input.tsx │ │ │ │ │ ├── label.tsx │ │ │ │ │ ├── menubar.tsx │ │ │ │ │ ├── navigation-menu.tsx │ │ │ │ │ ├── pagination.tsx │ │ │ │ │ ├── popover.tsx │ │ │ │ │ ├── progress.tsx │ │ │ │ │ ├── radio-group.tsx │ │ │ │ │ ├── resizable.tsx │ │ │ │ │ ├── scroll-area.tsx │ │ │ │ │ ├── select.tsx │ │ │ │ │ ├── separator.tsx │ │ │ │ │ ├── sheet.tsx │ │ │ │ │ ├── sidebar.tsx │ │ │ │ │ ├── skeleton.tsx │ │ │ │ │ ├── slider.tsx │ │ │ │ │ ├── sonner.tsx │ │ │ │ │ ├── switch.tsx │ │ │ │ │ ├── table.tsx │ │ │ │ │ ├── tabs.tsx │ │ │ │ │ ├── textarea.tsx │ │ │ │ │ ├── toggle-group.tsx │ │ │ │ │ ├── toggle.tsx │ │ │ │ │ └── tooltip.tsx │ │ │ ├── hooks │ │ │ │ └── use-mobile.ts │ │ │ ├── index.css │ │ │ ├── index.tsx │ │ │ ├── lib │ │ │ │ └── utils.ts │ │ │ └── vite-env.d.ts │ │ ├── tsconfig.app.json │ │ ├── tsconfig.app.tsbuildinfo │ │ ├── tsconfig.json │ │ ├── tsconfig.node.json │ │ └── vite.config.ts │ └── template-backend │ │ ├── package.json │ │ └── tsconfig.json ├── tsconfig.build.json └── tsconfig.json ├── codecov.yml ├── codefox-common ├── README.md ├── jest.config.js ├── package.json ├── src │ ├── common-path.ts │ ├── config-loader.ts │ ├── index.ts │ └── model-api.ts ├── tsconfig.cjs.json ├── tsconfig.esm.json ├── tsconfig.json └── tsconfig.types.json ├── codefox-docs ├── .gitignore ├── README.md ├── blog │ ├── 2019-05-28-first-blog-post.md │ ├── 2019-05-29-long-blog-post.md │ ├── 2021-08-01-mdx-blog-post.mdx │ ├── 2021-08-26-welcome │ │ ├── docusaurus-plushie-banner.jpeg │ │ └── index.md │ ├── authors.yml │ └── tags.yml ├── docs │ ├── intro.md │ ├── tutorial-basics │ │ ├── _category_.json │ │ ├── congratulations.md │ │ ├── create-a-blog-post.md │ │ ├── create-a-document.md │ │ ├── create-a-page.md │ │ ├── deploy-your-site.md │ │ └── markdown-features.mdx │ └── tutorial-extras │ │ ├── _category_.json │ │ ├── img │ │ ├── docsVersionDropdown.png │ │ └── localeDropdown.png │ │ ├── manage-docs-versions.md │ │ └── translate-your-site.md ├── docusaurus.config.ts ├── package.json ├── sidebars.ts ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── index.tsx │ │ │ └── styles.module.css │ ├── css │ │ └── custom.css │ └── pages │ │ ├── index.module.css │ │ ├── index.tsx │ │ └── markdown-page.md ├── static │ ├── .nojekyll │ ├── arch-md │ │ └── build-system.md │ └── img │ │ ├── docusaurus-social-card.jpg │ │ ├── docusaurus.png │ │ ├── favicon.ico │ │ ├── logo.svg │ │ ├── undraw_docusaurus_mountain.svg │ │ ├── undraw_docusaurus_react.svg │ │ └── undraw_docusaurus_tree.svg └── tsconfig.json ├── config.schema.json ├── docker ├── docker-compose.pord.yml ├── docker-compose.yml ├── project-base-image │ └── Dockerfile └── traefik-config │ ├── services.yml │ └── tls.yml ├── frontend ├── .env.example ├── .eslintrc.json ├── .gitignore ├── .tmuxinator │ └── build.yml ├── LICENSE ├── README.md ├── apollo.config.js ├── apollo.config.json ├── codegen.ts ├── components.json ├── example.env ├── jest.config.js ├── next.config.mjs ├── package.json ├── postcss.config.js ├── public │ ├── codefox.png │ ├── codefox.svg │ ├── images │ │ ├── github.svg │ │ └── google.svg │ ├── lightbulb.svg │ ├── next.svg │ ├── user.jpg │ └── vercel.svg ├── src │ ├── api │ │ └── ChatStreamAPI.ts │ ├── app │ │ ├── (main) │ │ │ ├── layout.tsx │ │ │ ├── page.tsx │ │ │ └── settings │ │ │ │ └── page.tsx │ │ ├── about │ │ │ └── page.tsx │ │ ├── api │ │ │ ├── file │ │ │ │ └── route.ts │ │ │ ├── filestructure │ │ │ │ └── route.ts │ │ │ ├── media │ │ │ │ └── [...path] │ │ │ │ │ └── route.ts │ │ │ ├── project │ │ │ │ └── route.ts │ │ │ ├── runProject │ │ │ │ └── route.ts │ │ │ └── screenshot │ │ │ │ └── route.ts │ │ ├── auth │ │ │ ├── confirm │ │ │ │ └── page.tsx │ │ │ └── oauth-callback │ │ │ │ └── page.tsx │ │ ├── chat │ │ │ ├── layout.tsx │ │ │ └── page.tsx │ │ ├── github │ │ │ └── callback │ │ │ │ └── page.tsx │ │ ├── globals.css │ │ ├── layout.tsx │ │ └── log │ │ │ └── logger.ts │ ├── components │ │ ├── auth-choice-modal.tsx │ │ ├── avatar-uploader.tsx │ │ ├── chat │ │ │ ├── chat-bottombar.tsx │ │ │ ├── chat-layout.tsx │ │ │ ├── chat-list.tsx │ │ │ ├── chat-panel.tsx │ │ │ ├── chat-topbar.tsx │ │ │ ├── code-engine │ │ │ │ ├── code-engine.tsx │ │ │ │ ├── file-explorer-button.tsx │ │ │ │ ├── file-structure.tsx │ │ │ │ ├── project-context.tsx │ │ │ │ ├── responsive-toolbar.tsx │ │ │ │ ├── save-changes-bar.tsx │ │ │ │ ├── tabs │ │ │ │ │ ├── code-tab.tsx │ │ │ │ │ ├── console-tab.tsx │ │ │ │ │ └── preview-tab.tsx │ │ │ │ └── web-view.tsx │ │ │ ├── index.tsx │ │ │ ├── project-modal.tsx │ │ │ └── thinking-process-block.tsx │ │ ├── code-display-block.tsx │ │ ├── detail-settings.tsx │ │ ├── emoji-picker.tsx │ │ ├── file-sidebar.tsx │ │ ├── github-callback.tsx │ │ ├── global-loading.tsx │ │ ├── image-embedder.tsx │ │ ├── magicui │ │ │ └── aurora-text.tsx │ │ ├── modal.tsx │ │ ├── mode-toggle.tsx │ │ ├── pull-model-form.tsx │ │ ├── pull-model.tsx │ │ ├── root │ │ │ ├── expand-card.tsx │ │ │ ├── nav-layout.tsx │ │ │ ├── nav.tsx │ │ │ ├── projects-section.tsx │ │ │ ├── prompt-form.tsx │ │ │ └── root-layout.tsx │ │ ├── settings │ │ │ └── settings.tsx │ │ ├── sidebar-item.tsx │ │ ├── sidebar-skeleton.tsx │ │ ├── sidebar.tsx │ │ ├── sign-in-modal.tsx │ │ ├── sign-up-modal.tsx │ │ ├── team-selector.tsx │ │ ├── ui │ │ │ ├── animate-number.tsx │ │ │ ├── avatar.tsx │ │ │ ├── background-gradient.tsx │ │ │ ├── badge.tsx │ │ │ ├── button.tsx │ │ │ ├── card.tsx │ │ │ ├── dialog.tsx │ │ │ ├── dropdown-menu.tsx │ │ │ ├── form.tsx │ │ │ ├── icons │ │ │ │ └── index.tsx │ │ │ ├── input.tsx │ │ │ ├── label.tsx │ │ │ ├── moving-border.tsx │ │ │ ├── popover.tsx │ │ │ ├── resizable.tsx │ │ │ ├── scroll-area.tsx │ │ │ ├── select.tsx │ │ │ ├── separator.tsx │ │ │ ├── sheet.tsx │ │ │ ├── sidebar.tsx │ │ │ ├── skeleton.tsx │ │ │ ├── sonner.tsx │ │ │ ├── tabs.tsx │ │ │ ├── textarea.tsx │ │ │ ├── texture-card.tsx │ │ │ └── tooltip.tsx │ │ ├── user-settings-bar.tsx │ │ └── username-form.tsx │ ├── config │ │ └── common-path.ts │ ├── const │ │ ├── EventEnum.ts │ │ └── MessageType.ts │ ├── graphql │ │ ├── mutations │ │ │ └── auth.ts │ │ ├── request.ts │ │ ├── schema.gql │ │ └── type.tsx │ ├── hooks │ │ ├── multi-agent │ │ │ ├── agentPrompt.ts │ │ │ ├── managerAgent.ts │ │ │ ├── toolNodes.ts │ │ │ └── tools.ts │ │ ├── useChatList.ts │ │ ├── useChatStore.ts │ │ ├── useChatStream.ts │ │ ├── useIsMobile.tsx │ │ ├── useLocalStorageData.ts │ │ ├── useModels.ts │ │ ├── useProjectStatusMonitor.ts │ │ └── useSpeechRecognition.ts │ ├── lib │ │ ├── authenticatedFetch.ts │ │ ├── client.ts │ │ ├── storage.ts │ │ └── utils.ts │ ├── providers │ │ ├── AuthProvider.tsx │ │ ├── BaseProvider.tsx │ │ ├── DynamicApolloProvider.tsx │ │ └── ThemeProvider.tsx │ └── utils │ │ ├── const.ts │ │ ├── file-reader.ts │ │ ├── initial-questions.ts │ │ └── parser.ts ├── tailwind.config.ts └── tsconfig.json ├── llm-server ├── .env.template ├── .eslintrc.cjs ├── .gitignore ├── .prettierrc.cjs ├── jest.config.js ├── package.json ├── src │ ├── downloader │ │ ├── const.ts │ │ ├── model-downloader.ts │ │ ├── universal-status.ts │ │ └── universal-utils.ts │ ├── llm-provider.ts │ ├── main.ts │ ├── model │ │ ├── llama-model-provider.ts │ │ ├── remote-model-factory.ts │ │ ├── remote-model-instance.ts │ │ └── remote-model-provider.ts │ ├── prompt │ │ └── systemPrompt.ts │ ├── protocol.ts │ └── types.ts ├── tsconfig.json └── tsconfig.test.json ├── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml └── turbo.json /.clinerules: -------------------------------------------------------------------------------- 1 | # Code Quality Rules 2 | 3 | 1. Never use Chinese comments -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | .github 5 | .husky 6 | *.config.js 7 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-undef */ 2 | module.exports = { 3 | root: true, 4 | parser: '@typescript-eslint/parser', 5 | extends: [ 6 | 'eslint:recommended', 7 | 'plugin:@typescript-eslint/recommended', 8 | 'plugin:prettier/recommended', 9 | ], 10 | plugins: ['@typescript-eslint'], 11 | ignorePatterns: ['node_modules', 'dist', '.turbo', '.next', 'build'], 12 | rules: { 13 | '@typescript-eslint/no-unused-vars': [ 14 | 'warn', 15 | { 16 | argsIgnorePattern: '^_', 17 | varsIgnorePattern: '^_', 18 | }, 19 | ], 20 | 'no-console': 'warn', 21 | 'prefer-const': 'error', 22 | 'no-var': 'error', 23 | '@typescript-eslint/no-explicit-any': 'warn', 24 | }, 25 | }; 26 | -------------------------------------------------------------------------------- /.github/workflows/codecov.yml: -------------------------------------------------------------------------------- 1 | name: Run tests and upload coverage 2 | 3 | on: 4 | push 5 | 6 | jobs: 7 | test: 8 | name: Run tests and collect coverage 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v4 14 | with: 15 | fetch-depth: 0 16 | 17 | - uses: pnpm/action-setup@v2 18 | with: 19 | version: 8 20 | 21 | - name: Setup Node.js 22 | uses: actions/setup-node@v4 23 | with: 24 | node-version: '20' 25 | cache: 'pnpm' 26 | 27 | - name: Get pnpm store directory 28 | id: pnpm-cache 29 | shell: bash 30 | run: | 31 | echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT 32 | 33 | - name: Setup pnpm cache 34 | uses: actions/cache@v3 35 | with: 36 | path: ${{ steps.pnpm-cache.outputs.STORE_PATH }} 37 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 38 | restore-keys: | 39 | ${{ runner.os }}-pnpm-store- 40 | 41 | - name: Install dependencies 42 | run: | 43 | cd backend 44 | pnpm install --frozen-lockfile 45 | 46 | - name: Run tests 47 | run: | 48 | cd backend 49 | pnpm exec jest --coverage --maxWorkers=2 --forceExit 50 | 51 | - name: Upload coverage to Codecov 52 | uses: codecov/codecov-action@v4 53 | with: 54 | token: ${{ secrets.CODECOV_TOKEN }} 55 | directory: ./backend/coverage 56 | flags: backend 57 | fail_ci_if_error: true 58 | verbose: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | node_modules/ 3 | */**/node_modules/ 4 | 5 | # Turbo 6 | .turbo/ 7 | */**/.turbo/ 8 | 9 | # Build outputs 10 | dist/ 11 | */**/dist/ 12 | 13 | # Models 14 | models/ 15 | */**/models/ 16 | 17 | */**/database.sqlite 18 | ./backend/src/database.sqlite 19 | .codefox 20 | 21 | .env -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | coverage 4 | .github 5 | package-lock.json 6 | yarn.lock 7 | pnpm-lock.yaml -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'es5', 4 | printWidth: 80, 5 | tabWidth: 2, 6 | semi: true, 7 | bracketSpacing: true, 8 | endOfLine: 'lf', 9 | }; 10 | -------------------------------------------------------------------------------- /.tmuxinator/dev.yml: -------------------------------------------------------------------------------- 1 | name: codefox 2 | root: <%= ENV["PWD"] %> 3 | 4 | on_project_start: tmux set-option -g prefix C-a 5 | 6 | windows: 7 | - backend: 8 | root: <%= ENV["PWD"] %>/backend 9 | panes: 10 | - backend: 11 | - echo "Backend Server (Ctrl+a 1 to focus, Ctrl+a r to restart)" 12 | - pnpm dev 13 | - frontend: 14 | root: <%= ENV["PWD"] %>/frontend 15 | layout: main-vertical 16 | panes: 17 | - frontend: 18 | - echo "Frontend Server (Ctrl+a 2 to focus, Ctrl+a r to restart)" 19 | - pnpm dev 20 | - codegen: 21 | - echo "Codegen Watch (Ctrl+a 2 to focus, Ctrl+a r to restart)" 22 | - pnpm generate:watch 23 | - llm: 24 | root: <%= ENV["PWD"] %>/llm-server 25 | panes: 26 | - llm: 27 | - echo "LLM Server (Ctrl+a 3 to focus, Ctrl+a r to restart)" 28 | - pnpm dev 29 | - docker: 30 | root: <%= ENV["PWD"] %>/docker 31 | panes: 32 | - docker: 33 | - echo "Docker Services (Ctrl+a 4 to focus)" 34 | - docker compose up 35 | -------------------------------------------------------------------------------- /.vscode/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } 5 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cSpell.words": ["upsert"] 3 | } 4 | -------------------------------------------------------------------------------- /assets/codefox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeFox-Repo/codefox/8102632122000fe13ef274ce3faa3a822154e595/assets/codefox.png -------------------------------------------------------------------------------- /assets/logo-trans.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeFox-Repo/codefox/8102632122000fe13ef274ce3faa3a822154e595/assets/logo-trans.png -------------------------------------------------------------------------------- /backend/.env.example: -------------------------------------------------------------------------------- 1 | # Server Configuration 2 | PORT=8080 3 | 4 | # DEV PROD OR TEST 5 | NODE_ENV="DEV" 6 | # JWT Configuration 7 | JWT_SECRET="your_jwt_secret_here" 8 | JWT_REFRESH="your_jwt_refresh_secret_here" 9 | SALT_ROUNDS=10 10 | 11 | # OpenAI Configuration 12 | OPENAI_BASE_URI="http://localhost:3001" 13 | 14 | # S3/Cloudflare R2 Configuration (Optional) 15 | # If not provided, local file storage will be used 16 | S3_ACCESS_KEY_ID="your_s3_access_key_id" # Must be 32 characters for Cloudflare R2 17 | S3_SECRET_ACCESS_KEY="your_s3_secret_access_key" 18 | S3_REGION="auto" # Use 'auto' for Cloudflare R2 19 | S3_BUCKET_NAME="your_bucket_name" 20 | S3_ENDPOINT="https://.r2.cloudflarestorage.com" # Cloudflare R2 endpoint 21 | S3_ACCOUNT_ID="your_cloudflare_account_id" # Your Cloudflare account ID 22 | S3_PUBLIC_URL="https://pub-xxx.r2.dev" # Your R2 public bucket URL 23 | 24 | # mail 25 | # Set to false to disable all email functionality 26 | MAIL_ENABLED=false 27 | 28 | MAIL_HOST=smtp.example.com 29 | MAIL_USER=user@example.com 30 | MAIL_PASSWORD=topsecret 31 | MAIL_FROM=noreply@example.com 32 | MAIL_PORT=587 33 | MAIL_DOMAIN=your_net 34 | 35 | # github app 36 | GITHUB_ENABLED=true # set false to disable GitHub 37 | GITHUB_APP_ID="GITHUB_APP_ID" 38 | GITHUB_PRIVATE_KEY_PATH="YOUR_PATH" 39 | GITHUB_CLIENT_ID="GITHUB_CLIENT_ID" 40 | GITHUB_CLIENT_SECRET="GITHUB_CLIENT_SECRET" 41 | GITHUB_WEBHOOK_SECRET="GITHUB_WEBHOOK_SECRET" 42 | CALLBACK="CALLBACK" 43 | 44 | # Database Configuration 45 | USE_REMOTE_DB=false 46 | DB_TYPE= 47 | DB_HOST= 48 | DB_PORT=0 49 | DB_USERNAME= 50 | DB_NAME= 51 | DB_REGION=us-east-2 52 | 53 | ## Google OAuth 54 | GOOGLE_CLIENT_ID=YOUR_CLIENT_ID 55 | GOOGLE_SECRET=Your_SECRET 56 | GOOGLE_CALLBACK_URL=http://localhost:8080/auth/google/callback -------------------------------------------------------------------------------- /backend/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | parser: '@typescript-eslint/parser', 3 | parserOptions: { 4 | project: 'tsconfig.json', 5 | tsconfigRootDir: __dirname, 6 | sourceType: 'module', 7 | }, 8 | plugins: ['@typescript-eslint/eslint-plugin', 'unused-imports'], 9 | extends: [ 10 | 'plugin:@typescript-eslint/recommended', 11 | 'plugin:prettier/recommended', 12 | '../.eslintrc.js', 13 | ], 14 | root: true, 15 | env: { 16 | node: true, 17 | jest: true, 18 | }, 19 | ignorePatterns: ['.eslintrc.js'], 20 | rules: { 21 | '@typescript-eslint/interface-name-prefix': 'off', 22 | '@typescript-eslint/explicit-function-return-type': 'off', 23 | '@typescript-eslint/explicit-module-boundary-types': 'off', 24 | '@typescript-eslint/no-explicit-any': 'off', 25 | '@typescript-eslint/no-unused-vars': 'off', 26 | 'prefer-const': [ 27 | 'error', 28 | { 29 | destructuring: 'all', 30 | ignoreReadBeforeAssign: true, 31 | }, 32 | ], 33 | 'unused-imports/no-unused-imports': 'error', 34 | 'unused-imports/no-unused-vars': [ 35 | 'warn', 36 | { 37 | vars: 'all', 38 | varsIgnorePattern: '^_', 39 | args: 'after-used', 40 | argsIgnorePattern: '^_', 41 | }, 42 | ], 43 | }, 44 | }; 45 | -------------------------------------------------------------------------------- /backend/.gitignore: -------------------------------------------------------------------------------- 1 | # compiled output 2 | /dist 3 | /node_modules 4 | /build 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | pnpm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | lerna-debug.log* 14 | 15 | # OS 16 | .DS_Store 17 | *.db 18 | 19 | # Tests 20 | /coverage 21 | /.nyc_output 22 | 23 | # IDEs and editors 24 | /.idea 25 | .project 26 | .classpath 27 | .c9/ 28 | *.launch 29 | .settings/ 30 | *.sublime-workspace 31 | 32 | # IDE - VSCode 33 | .vscode/* 34 | !.vscode/settings.json 35 | !.vscode/tasks.json 36 | !.vscode/launch.json 37 | !.vscode/extensions.json 38 | 39 | 40 | # temp directory 41 | .temp 42 | .tmp 43 | 44 | # Runtime data 45 | pids 46 | *.pid 47 | *.seed 48 | *.pid.lock 49 | 50 | # Diagnostic reports (https://nodejs.org/api/report.html) 51 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 52 | 53 | # log files with timestamp 54 | log-*/ 55 | 56 | 57 | # Backend 58 | /backend/package-lock.json 59 | .env -------------------------------------------------------------------------------- /backend/.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "trailingComma": "all", 4 | "endOfLine": "auto" 5 | } -------------------------------------------------------------------------------- /backend/jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | moduleFileExtensions: ['js', 'json', 'ts'], 4 | rootDir: 'src', 5 | testRegex: '.*\\.spec\\.ts$', 6 | transform: { 7 | '^.+\\.(t|j)s$': [ 8 | 'ts-jest', 9 | { 10 | tsconfig: { 11 | allowJs: true, 12 | module: 'ESNext', 13 | }, 14 | useESM: true, 15 | }, 16 | ], 17 | }, 18 | collectCoverageFrom: ['**/*.(t|j)s'], 19 | coverageDirectory: '../coverage', 20 | testEnvironment: 'node', 21 | moduleNameMapper: { 22 | '^src/(.*)$': '/$1', 23 | '^(\\.{1,2}/.*)\\.js$': '$1', 24 | }, 25 | modulePaths: [''], 26 | moduleDirectories: ['node_modules', 'src'], 27 | testPathIgnorePatterns: ['/template'], 28 | transformIgnorePatterns: [ 29 | 'node_modules/(?!(strip-json-comments|other-esm-packages)/)', 30 | ], 31 | preset: 'ts-jest/presets/default-esm', 32 | }; 33 | -------------------------------------------------------------------------------- /backend/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/nest-cli", 3 | "collection": "@nestjs/schematics", 4 | "sourceRoot": "src", 5 | "compilerOptions": { 6 | "assets": ["mail/templates/**/*"], 7 | "deleteOutDir": true 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/app.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Query, Resolver } from '@nestjs/graphql'; 2 | import { RequireRoles } from './decorator/auth.decorator'; 3 | 4 | @Resolver() 5 | export class AppResolver { 6 | @Query(() => String) 7 | @RequireRoles('Admin') 8 | getHello(): string { 9 | return 'Hello World!'; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/auth/auth.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Menu } from './menu/menu.model'; 4 | import { JwtModule } from '@nestjs/jwt'; 5 | import { Role } from './role/role.model'; 6 | import { AuthService } from './auth.service'; 7 | import { User } from 'src/user/user.model'; 8 | import { AppConfigService } from 'src/config/config.service'; 9 | import { AuthResolver } from './auth.resolver'; 10 | import { RefreshToken } from './refresh-token/refresh-token.model'; 11 | import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module'; 12 | import { MailModule } from 'src/mail/mail.module'; 13 | import { GoogleStrategy } from './oauth/GoogleStrategy'; 14 | import { GoogleController } from './google.controller'; 15 | import { AppConfigModule } from 'src/config/config.module'; 16 | 17 | @Module({ 18 | imports: [ 19 | AppConfigModule, 20 | TypeOrmModule.forFeature([Role, Menu, User, RefreshToken]), 21 | JwtModule.registerAsync({ 22 | imports: [AppConfigModule], 23 | useFactory: async (config: AppConfigService) => ({ 24 | secret: config.jwtSecret, 25 | signOptions: { expiresIn: '24h' }, 26 | }), 27 | inject: [AppConfigService], 28 | }), 29 | JwtCacheModule, 30 | MailModule, 31 | ], 32 | controllers: [GoogleController], 33 | providers: [AuthService, AuthResolver, GoogleStrategy], 34 | exports: [AuthService, JwtModule], 35 | }) 36 | export class AuthModule {} 37 | -------------------------------------------------------------------------------- /backend/src/auth/auth.resolver.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Args, 3 | Query, 4 | Resolver, 5 | Mutation, 6 | Field, 7 | ObjectType, 8 | } from '@nestjs/graphql'; 9 | import { AuthService } from './auth.service'; 10 | import { CheckTokenInput } from './dto/check-token.input'; 11 | 12 | @ObjectType() 13 | export class RefreshTokenResponse { 14 | @Field() 15 | accessToken: string; 16 | 17 | @Field() 18 | refreshToken: string; 19 | } 20 | 21 | @ObjectType() 22 | export class EmailConfirmationResponse { 23 | @Field() 24 | message: string; 25 | 26 | @Field({ nullable: true }) 27 | success?: boolean; 28 | } 29 | 30 | @Resolver() 31 | export class AuthResolver { 32 | constructor(private readonly authService: AuthService) {} 33 | 34 | @Query(() => Boolean) 35 | async checkToken(@Args('input') params: CheckTokenInput): Promise { 36 | return this.authService.validateToken(params); 37 | } 38 | 39 | @Mutation(() => RefreshTokenResponse) 40 | async refreshToken( 41 | @Args('refreshToken') refreshToken: string, 42 | ): Promise { 43 | return this.authService.refreshToken(refreshToken); 44 | } 45 | 46 | @Mutation(() => EmailConfirmationResponse) 47 | async confirmEmail( 48 | @Args('token') token: string, 49 | ): Promise { 50 | return this.authService.confirmEmail(token); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /backend/src/auth/dto/check-token.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql'; 2 | import { IsString } from 'class-validator'; 3 | 4 | @InputType() 5 | export class CheckTokenInput { 6 | @Field() 7 | @IsString() 8 | token: string; 9 | } 10 | -------------------------------------------------------------------------------- /backend/src/auth/dto/login-user.input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from '@nestjs/graphql'; 2 | 3 | @InputType() 4 | export class LoginUserInput { 5 | @Field() 6 | email: string; 7 | 8 | @Field() 9 | password: string; 10 | } 11 | -------------------------------------------------------------------------------- /backend/src/auth/entities/refresh-token.entity.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Entity, 3 | Column, 4 | PrimaryGeneratedColumn, 5 | CreateDateColumn, 6 | } from 'typeorm'; 7 | 8 | @Entity() 9 | export class RefreshToken { 10 | @PrimaryGeneratedColumn() 11 | id: number; 12 | 13 | @Column() 14 | token: string; 15 | 16 | @Column() 17 | userId: number; 18 | 19 | @Column() 20 | expiresAt: Date; 21 | 22 | @CreateDateColumn() 23 | createdAt: Date; 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/auth/google.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger, Req, Res, UseGuards } from '@nestjs/common'; 2 | import { AuthGuard } from '@nestjs/passport'; 3 | import { ConfigService } from '@nestjs/config'; 4 | import { AuthService } from './auth.service'; 5 | 6 | @Controller('auth') 7 | export class GoogleController { 8 | constructor( 9 | private configService: ConfigService, 10 | private authService: AuthService, 11 | ) {} 12 | 13 | @Get('google') 14 | @UseGuards(AuthGuard('google')) 15 | async googleAuth() { 16 | // This route initiates the Google OAuth flow 17 | // The guard redirects to Google 18 | } 19 | 20 | @Get('google/callback') 21 | @UseGuards(AuthGuard('google')) 22 | async googleAuthCallback(@Req() req, @Res() res) { 23 | Logger.log('Google callback'); 24 | const googleProfile = req.user as { 25 | googleId: string; 26 | email: string; 27 | firstName?: string; 28 | lastName?: string; 29 | }; 30 | 31 | // Call the AuthService method 32 | const { accessToken, refreshToken } = 33 | await this.authService.handleGoogleCallback(googleProfile); 34 | 35 | const frontendUrl = 36 | this.configService.get('FRONTEND_URL') || 'http://localhost:3000'; 37 | 38 | // TO DO IS UNSAFE 39 | // Redirect to frontend, pass tokens in query params 40 | return res.redirect( 41 | `${frontendUrl}/auth/oauth-callback?accessToken=${accessToken}&refreshToken=${refreshToken}`, 42 | ); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /backend/src/auth/menu/menu.model.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from '@nestjs/graphql'; 2 | import { SystemBaseModel } from 'src/system-base-model/system-base.model'; 3 | import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; 4 | import { Role } from '../role/role.model'; 5 | 6 | @Entity() 7 | @ObjectType() 8 | export class Menu extends SystemBaseModel { 9 | @Field(() => ID) 10 | @PrimaryGeneratedColumn() 11 | id: string; 12 | 13 | @Field() 14 | @Column() 15 | name: string; 16 | 17 | @Field() 18 | @Column() 19 | path: string; 20 | 21 | @Field() 22 | @Column() 23 | permission: string; 24 | 25 | @ManyToMany(() => Role, (role) => role.menus) 26 | roles: Role[]; 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/auth/oauth/GoogleStrategy.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { PassportStrategy } from '@nestjs/passport'; 3 | import { Strategy, VerifyCallback } from 'passport-google-oauth20'; 4 | import { ConfigService } from '@nestjs/config'; 5 | import { AuthService } from '../auth.service'; 6 | 7 | @Injectable() 8 | export class GoogleStrategy extends PassportStrategy(Strategy, 'google') { 9 | constructor( 10 | private configService: ConfigService, 11 | private authService: AuthService, 12 | ) { 13 | super({ 14 | clientID: 15 | configService.get('GOOGLE_CLIENT_ID') || 16 | 'Just_a_placeholder_GOOGLE_CLIENT_ID', 17 | clientSecret: 18 | configService.get('GOOGLE_SECRET') || 19 | 'Just_a_placeholder_GOOGLE_SECRET', 20 | callbackURL: configService.get('GOOGLE_CALLBACK_URL'), 21 | scope: ['email', 'profile'], 22 | prompt: 'select_account', 23 | }); 24 | } 25 | 26 | async validate( 27 | accessToken: string, 28 | refreshToken: string, 29 | profile: any, 30 | done: VerifyCallback, 31 | ): Promise { 32 | const { name, emails, photos, id } = profile; 33 | Logger.log(`Google profile ID: ${id}`); 34 | 35 | const user = { 36 | id: id, // Include the profile ID 37 | googleId: id, // Also map to googleId 38 | email: emails[0].value, 39 | firstName: name.givenName, 40 | lastName: name.familyName, 41 | picture: photos[0].value, 42 | accessToken, 43 | refreshToken, 44 | }; 45 | 46 | done(null, user); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /backend/src/auth/refresh-token/refresh-token.model.ts: -------------------------------------------------------------------------------- 1 | import { Entity, Column, PrimaryGeneratedColumn, ManyToOne } from 'typeorm'; 2 | import { User } from '../../user/user.model'; 3 | 4 | @Entity() 5 | export class RefreshToken { 6 | @PrimaryGeneratedColumn('uuid') 7 | id: string; 8 | 9 | @Column() 10 | token: string; 11 | 12 | @Column() 13 | expiresAt: Date; 14 | 15 | @ManyToOne(() => User, { onDelete: 'CASCADE' }) 16 | user: User; 17 | 18 | @Column() 19 | userId: number; 20 | } 21 | -------------------------------------------------------------------------------- /backend/src/auth/role/role.model.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from '@nestjs/graphql'; 2 | import { User } from 'src/user/user.model'; 3 | import { 4 | Entity, 5 | PrimaryGeneratedColumn, 6 | Column, 7 | ManyToMany, 8 | JoinTable, 9 | } from 'typeorm'; 10 | import { Menu } from '../menu/menu.model'; 11 | 12 | @ObjectType() 13 | @Entity() 14 | export class Role { 15 | @Field(() => ID) 16 | @PrimaryGeneratedColumn('uuid') 17 | id: string; 18 | 19 | @Field() 20 | @Column({ unique: true }) 21 | name: string; 22 | 23 | @Field({ nullable: true }) 24 | @Column({ nullable: true }) 25 | description?: string; 26 | 27 | @Field(() => [Menu], { nullable: true }) 28 | @ManyToMany(() => Menu, { eager: true }) 29 | @JoinTable({ 30 | name: 'role_menus', 31 | joinColumn: { 32 | name: 'role_id', 33 | referencedColumnName: 'id', 34 | }, 35 | inverseJoinColumn: { 36 | name: 'menu_id', 37 | referencedColumnName: 'id', 38 | }, 39 | }) 40 | menus?: Menu[]; 41 | 42 | @ManyToMany(() => User, (user) => user.roles) 43 | @JoinTable({ 44 | name: 'user_roles', 45 | joinColumn: { 46 | name: 'role_id', 47 | referencedColumnName: 'id', 48 | }, 49 | inverseJoinColumn: { 50 | name: 'user_id', 51 | referencedColumnName: 'id', 52 | }, 53 | }) 54 | users?: User[]; 55 | } 56 | -------------------------------------------------------------------------------- /backend/src/build-system/__tests__/copy-project-template.spec.ts: -------------------------------------------------------------------------------- 1 | import { copyProjectTemplate } from 'src/build-system/utils/files'; 2 | import { promises as fs } from 'fs'; 3 | import { v4 as uuidv4 } from 'uuid'; 4 | import { Logger } from '@nestjs/common'; 5 | import { getTemplatePath } from 'codefox-common'; 6 | 7 | /** 8 | * This test ensure project template exist 9 | */ 10 | describe('Copy Project Template', () => { 11 | it('should copy the template to the specified UUID folder', async () => { 12 | const templatePath = getTemplatePath('template-backend'); 13 | const projectUUID = uuidv4(); 14 | 15 | Logger.log('template-path:', templatePath); 16 | const projectPath = await copyProjectTemplate(templatePath, projectUUID); 17 | expect(await fs.access(projectPath)).toBeUndefined(); // Project folder exists 18 | await fs.rm(projectPath, { recursive: true, force: true }); 19 | }); 20 | }); 21 | -------------------------------------------------------------------------------- /backend/src/build-system/__tests__/test.frontend-code-generate.spec.ts: -------------------------------------------------------------------------------- 1 | import { MockBuilderContext } from './mock/MockBuilderContext'; 2 | import { FrontendCodeHandler } from '../handlers/frontend-code-generate'; 3 | import { BuildSequence } from '../types'; 4 | 5 | describe('FrontendCodeHandler', () => { 6 | let handler: FrontendCodeHandler; 7 | let context: MockBuilderContext; 8 | 9 | const sequence: BuildSequence = { 10 | id: 'test-backend-sequence', 11 | version: '1.0.0', 12 | name: 'Spotify-like Music Web', 13 | description: 'Users can play music', 14 | databaseType: 'SQLite', 15 | model: 'o3-mini-high', 16 | nodes: [ 17 | { 18 | handler: FrontendCodeHandler, 19 | name: 'Frontend Code Handler', 20 | // requires: ['op:FILE:STRUCT', 'op:UX:DATAMAP:DOC'], 21 | }, 22 | ], 23 | packages: [], 24 | }; 25 | 26 | beforeEach(() => { 27 | handler = new FrontendCodeHandler(); 28 | context = new MockBuilderContext(sequence, 'frontend-only'); 29 | }); 30 | 31 | //rember to comment requirement in FrontendCodeHandler 32 | 33 | it('should generate frontend code successfully', async () => { 34 | const result = await handler.run(context); 35 | 36 | expect(result.success).toBe(true); 37 | }, 6000000); 38 | }); 39 | -------------------------------------------------------------------------------- /backend/src/build-system/project-builder.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { HttpModule } from '@nestjs/axios'; 3 | import { ChatProxyService } from 'src/chat/chat.service'; 4 | import { ProjectBuilderService } from './project-builder.service'; 5 | 6 | @Module({ 7 | imports: [HttpModule], 8 | providers: [ProjectBuilderService, ChatProxyService], 9 | exports: [ProjectBuilderService], 10 | }) 11 | export class ProjectBuilderModule {} 12 | -------------------------------------------------------------------------------- /backend/src/build-system/project-builder.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger } from '@nestjs/common'; 2 | import { ChatProxyService } from 'src/chat/chat.service'; 3 | import { OpenAIModelProvider } from 'src/common/model-provider/openai-model-provider'; 4 | 5 | @Injectable() 6 | export class ProjectBuilderService { 7 | private readonly logger = new Logger(ProjectBuilderService.name); 8 | 9 | private models: OpenAIModelProvider = OpenAIModelProvider.getInstance(); 10 | constructor(private chatProxyService: ChatProxyService) {} 11 | 12 | async createProject(input: { 13 | name: string; 14 | projectDescription: string; 15 | }): Promise { 16 | this.logger.log(`Creating project: ${input.name}`); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /backend/src/build-system/utils/handler-helper.ts: -------------------------------------------------------------------------------- 1 | import { ChatInput } from 'src/common/model-provider/types'; 2 | import { BuilderContext } from '../context'; 3 | import { BuildMonitor } from '../monitor'; 4 | 5 | export async function chatSyncWithClocker( 6 | context: BuilderContext, 7 | input: ChatInput, 8 | step: string, 9 | name: string, 10 | ): Promise { 11 | const startTime = new Date(); 12 | const modelResponse = await context.model.chatSync(input); 13 | const endTime = new Date(); 14 | const duration = endTime.getTime() - startTime.getTime(); 15 | 16 | const inputContent = input.messages.map((m) => m.content).join(''); 17 | BuildMonitor.getInstance().timeRecorder( 18 | duration, 19 | name, 20 | step, 21 | inputContent, 22 | modelResponse, 23 | ); 24 | return modelResponse; 25 | } 26 | 27 | export async function batchChatSyncWithClock( 28 | context: BuilderContext, 29 | step: string, 30 | id: string, 31 | inputs: ChatInput[], 32 | ): Promise { 33 | const startTime = new Date(); 34 | const modelResponses = await context.model.batchChatSync(inputs); 35 | const endTime = new Date(); 36 | const duration = endTime.getTime() - startTime.getTime(); 37 | 38 | const inputContent = inputs 39 | .map((input) => input.messages.map((m) => m.content).join('')) 40 | .join(''); 41 | BuildMonitor.getInstance().timeRecorder( 42 | duration, 43 | id, 44 | step, 45 | inputContent, 46 | modelResponses.join(''), 47 | ); 48 | return modelResponses; 49 | } 50 | -------------------------------------------------------------------------------- /backend/src/build-system/utils/security/path-check.ts: -------------------------------------------------------------------------------- 1 | // securityCheckUtil.ts 2 | import path from 'path'; 3 | 4 | export interface SecurityCheckOptions { 5 | projectRoot: string; 6 | allowedPaths?: string[]; 7 | } 8 | 9 | /** 10 | * Performs security checks on a given file path to ensure it is within 11 | * the allowed project scope and doesn’t target restricted files. 12 | * 13 | * @param filePath - The path to be checked. 14 | * @param options - The security options, including projectRoot and allowedPaths. 15 | * @throws If the path is outside the project root or is otherwise disallowed. 16 | */ 17 | export function filePathSafetyChecks( 18 | filePath: string, 19 | options: SecurityCheckOptions, 20 | ) { 21 | const { projectRoot, allowedPaths } = options; 22 | 23 | const targetPath = path.resolve(projectRoot, filePath); 24 | const relativePath = path.relative(projectRoot, targetPath); 25 | // Prevent path traversal attacks 26 | if (relativePath.startsWith('..') || path.isAbsolute(relativePath)) { 27 | throw new Error('Unauthorized file access detected'); 28 | } 29 | 30 | // To do white list check 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/chat/chat.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ChatResolver } from './chat.resolver'; 3 | import { ChatController } from './chat.controller'; 4 | import { ChatProxyService, ChatService } from './chat.service'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { User } from 'src/user/user.model'; 7 | import { Chat } from './chat.model'; 8 | import { ChatGuard } from '../guard/chat.guard'; 9 | import { AuthModule } from '../auth/auth.module'; 10 | import { UserService } from 'src/user/user.service'; 11 | import { PubSub } from 'graphql-subscriptions'; 12 | import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module'; 13 | import { UploadModule } from 'src/upload/upload.module'; 14 | import { GitHubModule } from 'src/github/github.module'; 15 | 16 | @Module({ 17 | imports: [ 18 | TypeOrmModule.forFeature([Chat, User]), 19 | AuthModule, 20 | JwtCacheModule, 21 | UploadModule, 22 | GitHubModule, 23 | ], 24 | controllers: [ChatController], 25 | providers: [ 26 | ChatResolver, 27 | ChatProxyService, 28 | ChatService, 29 | ChatGuard, 30 | UserService, 31 | { 32 | provide: 'PUB_SUB', 33 | useValue: new PubSub(), 34 | }, 35 | ], 36 | exports: [ChatService, ChatGuard], 37 | }) 38 | export class ChatModule {} 39 | -------------------------------------------------------------------------------- /backend/src/chat/dto/chat-rest.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsString, IsBoolean, IsOptional } from 'class-validator'; 2 | 3 | export class ChatRestDto { 4 | @IsString() 5 | chatId: string; 6 | 7 | @IsString() 8 | message: string; 9 | 10 | @IsString() 11 | model: string; 12 | 13 | @IsBoolean() 14 | @IsOptional() 15 | stream?: boolean = false; 16 | } 17 | -------------------------------------------------------------------------------- /backend/src/chat/dto/chat.input.ts: -------------------------------------------------------------------------------- 1 | // DTOs for Project APIs 2 | import { InputType, Field } from '@nestjs/graphql'; 3 | import { MessageRole } from '../message.model'; 4 | 5 | @InputType() 6 | export class NewChatInput { 7 | @Field({ nullable: true }) 8 | title: string; 9 | } 10 | 11 | @InputType() 12 | export class UpdateChatTitleInput { 13 | @Field() 14 | chatId: string; 15 | 16 | @Field({ nullable: true }) 17 | title: string; 18 | } 19 | 20 | // TODO: using ChatInput in model-provider.ts 21 | @InputType('ChatInputType') 22 | export class ChatInput { 23 | @Field() 24 | chatId: string; 25 | @Field() 26 | message: string; 27 | 28 | @Field() 29 | model: string; 30 | @Field() 31 | role: MessageRole; 32 | } 33 | -------------------------------------------------------------------------------- /backend/src/chat/message.model.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType, ID, registerEnumType } from '@nestjs/graphql'; 2 | 3 | /** 4 | * Represents the different roles in a chat conversation 5 | */ 6 | export enum MessageRole { 7 | /** 8 | * Represents the end user who sends queries or requests to the model. 9 | * Contains questions, instructions, or any input that requires a response. 10 | */ 11 | User = 'user', 12 | 13 | /** 14 | * Represents the AI model's responses in the conversation. 15 | * Contains generated answers, explanations, or any output based on user input. 16 | */ 17 | Assistant = 'assistant', 18 | 19 | /** 20 | * Represents system-level instructions that define the behavior and context. 21 | * Used to set the model's personality, constraints, and background information. 22 | * Typically appears at the start of a conversation. 23 | */ 24 | System = 'system', 25 | } 26 | 27 | registerEnumType(MessageRole, { 28 | name: 'Role', 29 | }); 30 | 31 | @ObjectType() 32 | export class Message { 33 | @Field(() => ID) 34 | id: string; 35 | 36 | @Field() 37 | content: string; 38 | 39 | @Field(() => MessageRole) 40 | role: MessageRole; 41 | 42 | @Field(() => Date) 43 | createdAt: Date; 44 | 45 | @Field(() => Date) 46 | updatedAt: Date; 47 | 48 | @Field() 49 | isActive: boolean; 50 | 51 | @Field() 52 | isDeleted: boolean; 53 | 54 | @Field({ nullable: true }) 55 | modelId?: string; 56 | } 57 | -------------------------------------------------------------------------------- /backend/src/common/decorators/universal-date-column.ts: -------------------------------------------------------------------------------- 1 | import { CreateDateColumn, UpdateDateColumn, ColumnType } from 'typeorm'; 2 | 3 | /** 4 | * Universal date column options 5 | */ 6 | interface UniversalDateOptions { 7 | nullable?: boolean; 8 | update?: boolean; 9 | } 10 | 11 | /** 12 | * Get database column type based on environment 13 | * @returns 'timestamp' for remote database, 'datetime' for local 14 | */ 15 | function getDateColumnType(): ColumnType { 16 | // FIXME: optimize this logic to use a config file or environment variable 17 | // const useRemoteDb = process.env.USE_REMOTE_DB === 'true'; 18 | // use timestamp for remote database 19 | return 'datetime' as ColumnType; 20 | } 21 | 22 | /** 23 | * Universal create date column decorator that handles both remote and local databases 24 | */ 25 | export function UniversalCreateDateColumn(options: UniversalDateOptions = {}) { 26 | return CreateDateColumn({ 27 | type: getDateColumnType(), 28 | nullable: options.nullable, 29 | transformer: { 30 | to: (value: Date) => value, 31 | from: (value: any) => (value ? new Date(value) : null), 32 | }, 33 | }); 34 | } 35 | 36 | /** 37 | * Universal update date column decorator that handles both remote and local databases 38 | */ 39 | export function UniversalUpdateDateColumn(options: UniversalDateOptions = {}) { 40 | return UpdateDateColumn({ 41 | type: getDateColumnType(), 42 | nullable: options.nullable, 43 | transformer: { 44 | to: (value: Date) => value, 45 | from: (value: any) => (value ? new Date(value) : null), 46 | }, 47 | }); 48 | } 49 | -------------------------------------------------------------------------------- /backend/src/common/enums/role.enum.ts: -------------------------------------------------------------------------------- 1 | export enum DefaultRoles { 2 | ADMIN = 'Admin', 3 | } 4 | -------------------------------------------------------------------------------- /backend/src/common/interfaces/entity.interfaces.ts: -------------------------------------------------------------------------------- 1 | import { MessageRole } from '../../chat/message.model'; 2 | 3 | export interface IUser { 4 | id: string; 5 | username: string; 6 | email: string; 7 | chats?: IChat[]; 8 | roles?: any[]; 9 | } 10 | 11 | export interface IChat { 12 | id: string; 13 | title?: string; 14 | messages?: IMessage[]; 15 | user?: IUser; 16 | userId?: string; 17 | isActive?: boolean; 18 | isDeleted?: boolean; 19 | createdAt?: Date; 20 | updatedAt?: Date; 21 | } 22 | 23 | export interface IMessage { 24 | id: string; 25 | content: string; 26 | role: MessageRole; 27 | createdAt: Date; 28 | updatedAt: Date; 29 | isActive: boolean; 30 | isDeleted: boolean; 31 | } 32 | -------------------------------------------------------------------------------- /backend/src/common/model-provider/types.ts: -------------------------------------------------------------------------------- 1 | import { ChatCompletionChunk } from 'src/chat/chat.model'; 2 | 3 | export interface ModelChatStreamConfig { 4 | endpoint: string; 5 | model?: string; 6 | } 7 | 8 | export type CustomAsyncIterableIterator = AsyncIterator & { 9 | [Symbol.asyncIterator](): AsyncIterableIterator; 10 | }; 11 | 12 | export interface ModelProviderConfig { 13 | endpoint: string; 14 | defaultModel?: string; 15 | } 16 | 17 | export interface MessageInterface { 18 | content: string; 19 | role: 'user' | 'assistant' | 'system'; 20 | } 21 | 22 | export interface ChatInput { 23 | // if model not provided, default model will be used 24 | model?: string; 25 | messages: MessageInterface[]; 26 | } 27 | 28 | export interface IModelProvider { 29 | /** 30 | * Synchronous chat method that returns a complete response 31 | * @param input The chat input containing messages and model 32 | * @returns Promise resolving to the complete response string 33 | */ 34 | chatSync(input: ChatInput): Promise; 35 | 36 | /** 37 | * Stream-based chat method that returns an async iterator 38 | * @param input The chat input containing messages 39 | * @param model The model to use for chat 40 | * @param chatId Optional chat ID for conversation tracking 41 | * @returns CustomAsyncIterableIterator for streaming responses 42 | */ 43 | chat( 44 | input: ChatInput, 45 | model: string, 46 | ): CustomAsyncIterableIterator; 47 | 48 | /** 49 | * Get all active chat promises 50 | * @returns Array of active chat promises 51 | */ 52 | getAllActivePromises(): Promise[]; 53 | 54 | /** 55 | * Fetch available model names 56 | * @returns Promise resolving to array of model names 57 | */ 58 | fetchModelsName(): Promise; 59 | } 60 | -------------------------------------------------------------------------------- /backend/src/common/scalar/date.scalar.ts: -------------------------------------------------------------------------------- 1 | import { CustomScalar, Scalar } from '@nestjs/graphql'; 2 | import { Kind, ValueNode } from 'graphql'; 3 | 4 | @Scalar('Date', (type) => Date) 5 | export class DateScalar implements CustomScalar { 6 | description = 'Date custom scalar type'; 7 | 8 | parseValue(value: number): Date { 9 | return new Date(value); // value from the client 10 | } 11 | 12 | serialize(value: Date): number { 13 | return value.getTime(); 14 | } 15 | 16 | parseLiteral(ast: ValueNode): Date { 17 | if (ast.kind === Kind.INT) { 18 | return new Date(ast.value); 19 | } 20 | return null; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /backend/src/common/utils.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * 3 | * @returns wether is integration test or not 4 | */ 5 | export const isIntegrationTest = !!process.env.INTEGRATION_TEST; 6 | -------------------------------------------------------------------------------- /backend/src/config/config.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ConfigModule as NestConfigModule } from '@nestjs/config'; 3 | import { AppConfigService } from './config.service'; 4 | import { EnvironmentVariables } from './env.validation'; 5 | import { plainToInstance } from 'class-transformer'; 6 | import { validateSync } from 'class-validator'; 7 | 8 | const validate = (config: Record) => { 9 | const validatedConfig = plainToInstance(EnvironmentVariables, config, { 10 | enableImplicitConversion: true, 11 | }); 12 | 13 | const errors = validateSync(validatedConfig, { 14 | skipMissingProperties: false, 15 | }); 16 | 17 | if (errors.length > 0) { 18 | throw new Error(errors.toString()); 19 | } 20 | 21 | return validatedConfig; 22 | }; 23 | 24 | @Module({ 25 | imports: [ 26 | NestConfigModule.forRoot({ 27 | validate, 28 | isGlobal: true, 29 | }), 30 | ], 31 | providers: [AppConfigService], 32 | exports: [AppConfigService], 33 | }) 34 | export class AppConfigModule {} 35 | -------------------------------------------------------------------------------- /backend/src/database.config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { TypeOrmModuleOptions } from '@nestjs/typeorm'; 3 | import { AppConfigService } from './config/config.service'; 4 | 5 | export async function getDatabaseConfig( 6 | config: AppConfigService, 7 | ): Promise { 8 | const entities = [join(__dirname, '**', '*.model.{ts,js}')]; 9 | 10 | // Use SQLite for local development 11 | if (!config.useRemoteDb) { 12 | return { 13 | type: 'sqlite', 14 | database: join(process.cwd(), './database.db'), 15 | synchronize: config.isDevEnv, 16 | entities, 17 | migrations: [join(__dirname, 'migrations', '*.{ts,js}')], 18 | migrationsRun: !config.isDevEnv, 19 | logging: !config.isProduction, 20 | }; 21 | } 22 | 23 | const dbConfig = config.dbConfig; 24 | return { 25 | type: 'postgres', 26 | ...dbConfig, 27 | synchronize: false, 28 | entities, 29 | migrations: [join(__dirname, 'migrations', '*.{ts,js}')], 30 | migrationsRun: true, 31 | logging: !config.isProduction, 32 | poolSize: config.isProduction ? 50 : 20, 33 | connectTimeoutMS: 10000, 34 | extra: { 35 | max: config.isProduction ? 50 : 20, 36 | connectionTimeoutMillis: 10000, 37 | idleTimeoutMillis: 60000, 38 | }, 39 | retryAttempts: 3, 40 | retryDelay: 3000, 41 | keepConnectionAlive: true, 42 | } as TypeOrmModuleOptions; 43 | } 44 | -------------------------------------------------------------------------------- /backend/src/decorator/auth.decorator.ts: -------------------------------------------------------------------------------- 1 | // This is combination of RolesGuard, MenuGuard, roles.decorator.ts, menu.decorator.ts, auth.decorator.ts 2 | // Example usage: 3 | // @Query(() => [Project]) 4 | // @RequireRoles('Admin', 'Manager') 5 | // @RequireMenu('/project/create') 6 | // @RequireAuth({ 7 | // roles: ['Admin'], 8 | // menuPath: '/project/detail' 9 | // }) 10 | // async getProjects() { 11 | // return this.projectService.findAll(); 12 | // } 13 | import { applyDecorators, UseGuards } from '@nestjs/common'; 14 | import { Roles } from './roles.decorator'; 15 | import { Menu } from './menu.decorator'; 16 | import { RolesGuard } from 'src/interceptor/roles.guard'; 17 | import { MenuGuard } from 'src/guard/menu.guard'; 18 | 19 | export function Auth() { 20 | return applyDecorators(UseGuards(RolesGuard, MenuGuard)); 21 | } 22 | 23 | export function RequireRoles(...roles: string[]) { 24 | return applyDecorators(Roles(...roles), UseGuards(RolesGuard)); 25 | } 26 | 27 | export function RequireMenu(path: string) { 28 | return applyDecorators(Menu(path), UseGuards(MenuGuard)); 29 | } 30 | 31 | export function RequireAuth(options?: { roles?: string[]; menuPath?: string }) { 32 | if (!options) { 33 | return Auth(); 34 | } 35 | 36 | const decorators = [Auth()]; 37 | 38 | if (options.roles?.length) { 39 | decorators.push(Roles(...options.roles)); 40 | } 41 | 42 | if (options.menuPath) { 43 | decorators.push(Menu(options.menuPath)); 44 | } 45 | 46 | return applyDecorators(...decorators); 47 | } 48 | -------------------------------------------------------------------------------- /backend/src/decorator/get-auth-token.decorator.ts: -------------------------------------------------------------------------------- 1 | //this is parameter decorator to get the token from the request header 2 | import { 3 | createParamDecorator, 4 | ExecutionContext, 5 | Logger, 6 | UnauthorizedException, 7 | } from '@nestjs/common'; 8 | import { GqlExecutionContext } from '@nestjs/graphql'; 9 | import { JwtService } from '@nestjs/jwt'; 10 | 11 | export const GetAuthToken = createParamDecorator( 12 | (data: unknown, context: ExecutionContext) => { 13 | const gqlContext = GqlExecutionContext.create(context); 14 | const request = gqlContext.getContext().req; 15 | const authHeader = request.headers.authorization; 16 | 17 | if (authHeader && authHeader.split(' ')[0] === 'Bearer') { 18 | return authHeader.split(' ')[1]; 19 | } 20 | return null; 21 | }, 22 | ); 23 | 24 | export const GetUserIdFromToken = createParamDecorator( 25 | (data: unknown, context: ExecutionContext) => { 26 | const ctx = GqlExecutionContext.create(context); 27 | const request = ctx.getContext().req; 28 | const authHeader = request.headers.authorization; 29 | 30 | if (!authHeader || !authHeader.startsWith('Bearer ')) { 31 | Logger.error('Authorization token is missing or invalid'); 32 | throw new UnauthorizedException( 33 | 'Authorization token is missing or invalid', 34 | ); 35 | } 36 | 37 | const token = authHeader.split(' ')[1]; 38 | const jwtService = new JwtService({}); 39 | const decodedToken: any = jwtService.decode(token); 40 | 41 | if (!decodedToken || !decodedToken.userId) { 42 | Logger.debug('invalid token, token:' + token); 43 | throw new UnauthorizedException('Invalid token, token:', token); 44 | } 45 | 46 | return decodedToken.userId; 47 | }, 48 | ); 49 | -------------------------------------------------------------------------------- /backend/src/decorator/jwt-auth.decorator.ts: -------------------------------------------------------------------------------- 1 | //function decorator for JWTAuth 2 | import { applyDecorators, UseGuards } from '@nestjs/common'; 3 | import { JWTAuthGuard } from 'src/guard/jwt-auth.guard'; 4 | 5 | export function JWTAuth() { 6 | return applyDecorators(UseGuards(JWTAuthGuard)); 7 | } 8 | -------------------------------------------------------------------------------- /backend/src/decorator/menu.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const MENU_PATH_KEY = 'menuPath'; 4 | export const Menu = (path: string) => SetMetadata(MENU_PATH_KEY, path); 5 | -------------------------------------------------------------------------------- /backend/src/decorator/roles.decorator.ts: -------------------------------------------------------------------------------- 1 | import { SetMetadata } from '@nestjs/common'; 2 | 3 | export const ROLES_KEY = 'roles'; 4 | export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); 5 | -------------------------------------------------------------------------------- /backend/src/downloader/const.ts: -------------------------------------------------------------------------------- 1 | export const REMOTE_MODEL_LISTS = [ 2 | 'gpt-4-0125-preview', 3 | 'gpt-4-1106-preview', 4 | 'gpt-4-vision-preview', 5 | 'gpt-4', 6 | 'gpt-4-32k', 7 | 'gpt-3.5-turbo-0125', 8 | 'gpt-3.5-turbo-1106', 9 | 'gpt-3.5-turbo', 10 | 'gpt-3.5-turbo-16k', 11 | 'claude-3-opus-20240229', 12 | 'claude-3-sonnet-20240229', 13 | 'claude-3-haiku-20240229', 14 | 'claude-2.1', 15 | 'claude-2.0', 16 | 'claude-instant-1.2', 17 | 'gemini-1.0-pro', 18 | 'gemini-1.0-pro-vision', 19 | 'gemini-1.0-ultra', 20 | 'mistral-large-latest', 21 | 'mistral-medium-latest', 22 | 'mistral-small-latest', 23 | 'command', 24 | 'command-light', 25 | 'command-nightly', 26 | 'azure-gpt-4', 27 | 'azure-gpt-35-turbo', 28 | 'azure-gpt-4-32k', 29 | 'yi-34b-chat', 30 | 'yi-34b-200k', 31 | 'yi-34b', 32 | 'yi-6b-chat', 33 | 'yi-6b-200k', 34 | 'yi-6b', 35 | 'text-bison-001', 36 | 'chat-bison-001', 37 | 'claude-2.0', 38 | 'claude-1.2', 39 | 'claude-1.0', 40 | 'claude-instant-1.2', 41 | 'llama-2-70b-chat', 42 | 'llama-2-13b-chat', 43 | 'llama-2-7b-chat', 44 | 'qwen-72b-chat', 45 | 'qwen-14b-chat', 46 | 'qwen-7b-chat', 47 | 'deepseek-67b-chat', 48 | 'deepseek-33b-chat', 49 | 'deepseek-7b-chat', 50 | 'mixtral-8x7b-32k', 51 | 'mixtral-8x7b', 52 | 'baichuan-2-53b', 53 | 'baichuan-2-13b', 54 | 'baichuan-2-7b', 55 | 'xverse-65b-chat', 56 | 'xverse-13b-chat', 57 | 'xverse-7b-chat', 58 | 'command-r', 59 | 'command-light-r', 60 | 'claude-instant', 61 | 'vicuna-13b', 62 | 'vicuna-7b', 63 | 'falcon-40b', 64 | 'falcon-7b', 65 | 'stablelm-7b', 66 | 'mpt-7b', 67 | 'dolly-12b', 68 | 'alpaca-13b', 69 | 'pythia-12b', 70 | ]; 71 | 72 | export const isRemoteModel = (model: string): boolean => 73 | REMOTE_MODEL_LISTS.includes(model); 74 | -------------------------------------------------------------------------------- /backend/src/downloader/embedding-downloader.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { UniversalStatusManager } from './universal-status'; 3 | import { EmbeddingModel, FlagEmbedding } from 'fastembed'; 4 | import { getEmbDir } from 'codefox-common'; 5 | export class EmbeddingDownloader { 6 | readonly logger = new Logger(EmbeddingDownloader.name); 7 | private static instance: EmbeddingDownloader; 8 | private readonly statusManager = UniversalStatusManager.getInstance(); 9 | 10 | public static getInstance(): EmbeddingDownloader { 11 | if (!EmbeddingDownloader.instance) { 12 | EmbeddingDownloader.instance = new EmbeddingDownloader(); 13 | } 14 | 15 | return EmbeddingDownloader.instance; 16 | } 17 | 18 | async getPipeline(model: string): Promise { 19 | if (!Object.values(EmbeddingModel).includes(model as EmbeddingModel)) { 20 | this.logger.error( 21 | `Invalid model: ${model} is not a valid EmbeddingModel.`, 22 | ); 23 | return null; 24 | } 25 | try { 26 | const embeddingModel = await FlagEmbedding.init({ 27 | model: model as EmbeddingModel, 28 | cacheDir: getEmbDir(), 29 | }); 30 | this.statusManager.updateStatus(model, true); 31 | return embeddingModel; 32 | } catch (error) { 33 | this.logger.error( 34 | `Failed to load local model: ${model} with error: ${error.message || error}`, 35 | ); 36 | return null; 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /backend/src/embedding/local-embedding-provider.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common'; 2 | import { EmbeddingModel } from 'fastembed'; 3 | import { EmbeddingDownloader } from 'src/downloader/embedding-downloader'; 4 | 5 | export class EmbeddingProvider { 6 | private static logger = new Logger(EmbeddingProvider.name); 7 | 8 | static async generateEmbResponse(model: string, message: string[]) { 9 | const embLoader = EmbeddingDownloader.getInstance(); 10 | try { 11 | const embeddingModel = await embLoader.getPipeline(model); 12 | const embeddings = embeddingModel.embed(message); 13 | 14 | for await (const batch of embeddings) { 15 | Logger.log(batch); 16 | } 17 | } catch (error) { 18 | this.logger.log(`error when using ${model} api`); 19 | } 20 | } 21 | 22 | static async getEmbList() { 23 | Object.values(EmbeddingModel).forEach((model) => { 24 | this.logger.log(model); 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /backend/src/github/github.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { AuthModule } from '../auth/auth.module'; 4 | import { ProjectGuard } from '../guard/project.guard'; 5 | import { ChatService } from 'src/chat/chat.service'; 6 | import { User } from 'src/user/user.model'; 7 | import { Chat } from 'src/chat/chat.model'; 8 | import { AppConfigModule } from 'src/config/config.module'; 9 | import { UploadModule } from 'src/upload/upload.module'; 10 | import { GitHubAppService } from './githubApp.service'; 11 | import { GitHubService } from './github.service'; 12 | import { Project } from 'src/project/project.model'; 13 | import { ProjectPackages } from 'src/project/project-packages.model'; 14 | import { GitHuController } from './github.controller'; 15 | import { ProjectService } from 'src/project/project.service'; 16 | import { UserModule } from 'src/user/user.module'; 17 | 18 | @Module({ 19 | imports: [ 20 | TypeOrmModule.forFeature([Project, Chat, User, ProjectPackages]), 21 | AuthModule, 22 | AppConfigModule, 23 | UploadModule, 24 | forwardRef(() => UserModule), 25 | ], 26 | controllers: [GitHuController], 27 | providers: [ 28 | ProjectService, 29 | ProjectGuard, 30 | GitHubAppService, 31 | GitHubService, 32 | ChatService, 33 | ], 34 | exports: [GitHubService], 35 | }) 36 | export class GitHubModule {} 37 | -------------------------------------------------------------------------------- /backend/src/init/init-roles.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, Logger, OnApplicationBootstrap } from '@nestjs/common'; 2 | import { InjectRepository } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { Role } from '../auth/role/role.model'; 5 | import { DefaultRoles } from '../common/enums/role.enum'; 6 | 7 | @Injectable() 8 | export class InitRolesService implements OnApplicationBootstrap { 9 | private readonly logger = new Logger(InitRolesService.name); 10 | 11 | constructor( 12 | @InjectRepository(Role) 13 | private roleRepository: Repository, 14 | ) {} 15 | 16 | async onApplicationBootstrap() { 17 | await this.initializeDefaultRoles(); 18 | } 19 | 20 | private async initializeDefaultRoles() { 21 | this.logger.log('Checking and initializing default roles...'); 22 | 23 | const defaultRoles = Object.values(DefaultRoles); 24 | 25 | for (const roleName of defaultRoles) { 26 | const existingRole = await this.roleRepository.findOne({ 27 | where: { name: roleName }, 28 | }); 29 | 30 | if (!existingRole) { 31 | await this.createDefaultRole(roleName); 32 | } 33 | } 34 | 35 | this.logger.log('Default roles initialization completed'); 36 | } 37 | 38 | private async createDefaultRole(roleName: string) { 39 | try { 40 | const newRole = this.roleRepository.create({ 41 | name: roleName, 42 | description: `Default ${roleName} role`, 43 | menus: [], 44 | }); 45 | 46 | await this.roleRepository.save(newRole); 47 | this.logger.log(`Created default role: ${roleName}`); 48 | } catch (error) { 49 | this.logger.error( 50 | `Failed to create default role ${roleName}:`, 51 | error.message, 52 | ); 53 | throw error; 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /backend/src/init/init.module.ts: -------------------------------------------------------------------------------- 1 | // This module is use for init operation like init roles, init permissions, init users, etc. 2 | // This module is imported in app.module.ts 3 | // @Author: Jackson Chen 4 | import { Module } from '@nestjs/common'; 5 | import { TypeOrmModule } from '@nestjs/typeorm'; 6 | import { Role } from '../auth/role/role.model'; 7 | import { InitRolesService } from './init-roles.service'; 8 | 9 | @Module({ 10 | imports: [TypeOrmModule.forFeature([Role])], 11 | providers: [InitRolesService], 12 | }) 13 | export class InitModule {} 14 | -------------------------------------------------------------------------------- /backend/src/jwt-cache/jwt-cache.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { JwtCacheService } from './jwt-cache.service'; 3 | 4 | @Module({ 5 | exports: [JwtCacheService], 6 | providers: [JwtCacheService], 7 | }) 8 | export class JwtCacheModule {} 9 | -------------------------------------------------------------------------------- /backend/src/mail/mail.module.ts: -------------------------------------------------------------------------------- 1 | import { MailerModule } from '@nestjs-modules/mailer'; 2 | import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter'; 3 | import { Module } from '@nestjs/common'; 4 | import { MailService } from './mail.service'; 5 | import { join } from 'path'; 6 | import { ConfigModule } from '@nestjs/config'; 7 | import { TypeOrmModule } from '@nestjs/typeorm'; 8 | import { User } from 'src/user/user.model'; 9 | import { JwtModule } from '@nestjs/jwt'; 10 | import { AppConfigService } from 'src/config/config.service'; 11 | import { AppConfigModule } from 'src/config/config.module'; 12 | 13 | @Module({ 14 | imports: [ 15 | ConfigModule, 16 | TypeOrmModule.forFeature([User]), 17 | AppConfigModule, 18 | MailerModule.forRootAsync({ 19 | imports: [AppConfigModule], 20 | useFactory: async (config: AppConfigService) => { 21 | const mailConfig = config.mailConfig; 22 | return { 23 | transport: { 24 | host: mailConfig.host, 25 | port: mailConfig.port, 26 | secure: false, 27 | auth: { 28 | user: mailConfig.user, 29 | pass: mailConfig.password, 30 | }, 31 | }, 32 | defaults: { 33 | from: `"Your App" <${mailConfig.from}>`, 34 | }, 35 | template: { 36 | dir: join(__dirname, 'templates'), 37 | adapter: new HandlebarsAdapter(), 38 | options: { 39 | strict: true, 40 | }, 41 | }, 42 | }; 43 | }, 44 | inject: [AppConfigService], 45 | }), 46 | JwtModule, 47 | ], 48 | providers: [MailService], 49 | exports: [MailService], 50 | }) 51 | export class MailModule {} 52 | -------------------------------------------------------------------------------- /backend/src/mail/templates/confirmation.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Confirm Your Email 6 | 7 | 8 |
9 |

Welcome to CodeFox! 🎉

10 |

11 | Hi there! Thanks for signing up. Please confirm your email address to activate your account. 12 |

13 |

14 | 15 | Confirm Email 16 | 17 |

18 |

19 | If you didn't create an account, you can ignore this email. The confirmation link will expire in 24 hours. 20 |

21 |

22 | Need help? Contact our support team at support@codefox.net. 23 |

24 |
25 | 26 | 27 | -------------------------------------------------------------------------------- /backend/src/mail/templates/passwordReset.hbs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Email Confirmation 7 | 27 | 28 | 29 |
30 |

Welcome to Our App!

31 |

Hello test user,

32 |

Thank you for registering. Please confirm your email address by clicking the button below:

33 |

34 | 35 |

36 |

If you did not request this email, please ignore it.

37 |

This link will expire in 24 hours.

38 |

Regards,
The App Team

39 |
40 | 41 | -------------------------------------------------------------------------------- /backend/src/project/downloadController.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Logger, Param, Res } from '@nestjs/common'; 2 | import { ProjectService } from 'src/project/project.service'; 3 | import { GetUserIdFromToken } from 'src/decorator/get-auth-token.decorator'; 4 | import { Response } from 'express'; 5 | import * as fs from 'fs'; 6 | 7 | @Controller('download') 8 | export class DownloadController { 9 | private readonly logger = new Logger('DownloadController'); 10 | constructor(private readonly projectService: ProjectService) {} 11 | 12 | @Get('project/:projectId') 13 | async downloadProject( 14 | @GetUserIdFromToken() userId: string, 15 | @Param('projectId') projectId: string, 16 | @Res() response: Response, 17 | ) { 18 | this.logger.log(`User ${userId} downloading project ${projectId}`); 19 | 20 | const { zipPath, fileName } = await this.projectService.createProjectZip( 21 | userId, 22 | projectId, 23 | ); 24 | 25 | response.set({ 26 | 'Content-Type': 'application/zip', 27 | 'Content-Disposition': `attachment; filename="${fileName}"`, 28 | }); 29 | 30 | const fileStream = fs.createReadStream(zipPath); 31 | fileStream.pipe(response); 32 | 33 | fileStream.on('end', () => { 34 | fs.unlink(zipPath, (err) => { 35 | if (err) { 36 | this.logger.error(`Error deleting zip file: ${err.message}`); 37 | } 38 | }); 39 | }); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /backend/src/project/project-limits.ts: -------------------------------------------------------------------------------- 1 | import { ForbiddenException, HttpStatus } from '@nestjs/common'; 2 | import { GraphQLError } from 'graphql'; 3 | 4 | export const PROJECT_DAILY_LIMIT = 3; // Maximum number of projects a user can create per day 5 | 6 | export enum ProjectErrorCode { 7 | DAILY_LIMIT_EXCEEDED = 'DAILY_LIMIT_EXCEEDED', 8 | } 9 | 10 | export class ProjectRateLimitException extends ForbiddenException { 11 | constructor(limit: number) { 12 | super( 13 | `Daily project creation limit of ${limit} reached. Please try again tomorrow.`, 14 | ); 15 | } 16 | 17 | getGraphQLError(): GraphQLError { 18 | return new GraphQLError(this.message, { 19 | extensions: { 20 | code: ProjectErrorCode.DAILY_LIMIT_EXCEEDED, 21 | limit: PROJECT_DAILY_LIMIT, 22 | status: HttpStatus.TOO_MANY_REQUESTS, 23 | }, 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /backend/src/project/project-packages.model.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from '@nestjs/graphql'; 2 | import { SystemBaseModel } from 'src/system-base-model/system-base.model'; 3 | import { Entity, PrimaryGeneratedColumn, Column, ManyToMany } from 'typeorm'; 4 | import { Project } from './project.model'; 5 | 6 | @Entity() 7 | @ObjectType() 8 | export class ProjectPackages extends SystemBaseModel { 9 | @Field(() => ID) 10 | @PrimaryGeneratedColumn() 11 | id: string; 12 | 13 | @Field() 14 | @Column({ nullable: false }) 15 | name: string; 16 | 17 | @Field() 18 | @Column('text') 19 | content: string; 20 | 21 | @Field() 22 | @Column() 23 | version: string; 24 | 25 | @ManyToMany(() => Project, (project) => project.projectPackages, { 26 | nullable: true, 27 | }) 28 | projects: Project[]; 29 | } 30 | -------------------------------------------------------------------------------- /backend/src/project/project.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { Project } from './project.model'; 4 | import { ProjectPackages } from './project-packages.model'; 5 | import { ProjectService } from './project.service'; 6 | import { ProjectsResolver } from './project.resolver'; 7 | import { AuthModule } from '../auth/auth.module'; 8 | import { ProjectGuard } from '../guard/project.guard'; 9 | import { ChatService } from 'src/chat/chat.service'; 10 | import { User } from 'src/user/user.model'; 11 | import { Chat } from 'src/chat/chat.model'; 12 | import { AppConfigModule } from 'src/config/config.module'; 13 | import { UploadModule } from 'src/upload/upload.module'; 14 | import { DownloadController } from './downloadController'; 15 | import { GitHubService } from 'src/github/github.service'; 16 | import { UserService } from 'src/user/user.service'; 17 | 18 | @Module({ 19 | imports: [ 20 | TypeOrmModule.forFeature([Project, Chat, User, ProjectPackages]), 21 | AuthModule, 22 | AppConfigModule, 23 | UploadModule, 24 | ], 25 | controllers: [DownloadController], 26 | providers: [ 27 | ChatService, 28 | ProjectService, 29 | ProjectsResolver, 30 | ProjectGuard, 31 | GitHubService, 32 | UserService, 33 | ], 34 | exports: [ProjectService, ProjectGuard], 35 | }) 36 | export class ProjectModule {} 37 | -------------------------------------------------------------------------------- /backend/src/prompt-tool/prompt-tool.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { ProjectModule } from '../project/project.module'; 3 | import { PromptToolService } from './prompt-tool.service'; 4 | import { PromptToolResolver } from './prompt-tool.resolver'; 5 | import { AuthModule } from '../auth/auth.module'; 6 | import { JwtCacheModule } from 'src/jwt-cache/jwt-cache.module'; 7 | 8 | @Module({ 9 | imports: [ProjectModule, JwtCacheModule, AuthModule], 10 | providers: [PromptToolResolver, PromptToolService], 11 | }) 12 | export class PromptToolModule {} 13 | -------------------------------------------------------------------------------- /backend/src/prompt-tool/prompt-tool.resolver.ts: -------------------------------------------------------------------------------- 1 | import { Resolver, Mutation, Args } from '@nestjs/graphql'; 2 | import { JWTAuth } from '../decorator/jwt-auth.decorator'; 3 | import { PromptToolService } from './prompt-tool.service'; 4 | 5 | @Resolver() 6 | export class PromptToolResolver { 7 | constructor(private readonly promptService: PromptToolService) {} 8 | 9 | @Mutation(() => String) 10 | @JWTAuth() 11 | async regenerateDescription(@Args('input') input: string): Promise { 12 | return this.promptService.regenerateDescription(input); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /backend/src/system-base-model/system-base.model.ts: -------------------------------------------------------------------------------- 1 | import { Field, ObjectType } from '@nestjs/graphql'; 2 | import { Column } from 'typeorm'; 3 | import { 4 | UniversalCreateDateColumn, 5 | UniversalUpdateDateColumn, 6 | } from '../common/decorators/universal-date-column'; 7 | 8 | @ObjectType() 9 | export class SystemBaseModel { 10 | @Field() 11 | @UniversalCreateDateColumn() 12 | createdAt: Date; 13 | 14 | @Field() 15 | @UniversalUpdateDateColumn() 16 | updatedAt: Date; 17 | 18 | @Field() 19 | @Column({ default: true }) 20 | isActive: boolean; 21 | 22 | @Field() 23 | @Column({ default: false }) 24 | isDeleted: boolean; 25 | } 26 | -------------------------------------------------------------------------------- /backend/src/token/api-token.model.ts: -------------------------------------------------------------------------------- 1 | import { ObjectType, Field, ID } from '@nestjs/graphql'; 2 | import { SystemBaseModel } from 'src/system-base-model/system-base.model'; 3 | import { 4 | Entity, 5 | PrimaryGeneratedColumn, 6 | Column, 7 | ManyToOne, 8 | JoinColumn, 9 | } from 'typeorm'; 10 | import { User } from 'src/user/user.model'; 11 | 12 | @Entity() 13 | @ObjectType() 14 | export class APIToken extends SystemBaseModel { 15 | @Field(() => ID) 16 | @PrimaryGeneratedColumn() 17 | id: string; 18 | 19 | @Field(() => ID) 20 | @Column() 21 | user_id: string; 22 | 23 | @Field() 24 | @Column() 25 | api: string; 26 | 27 | @ManyToOne(() => User) 28 | @JoinColumn({ name: 'user_id' }) 29 | user: User; 30 | } 31 | -------------------------------------------------------------------------------- /backend/src/token/token.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { TypeOrmModule } from '@nestjs/typeorm'; 3 | import { APIToken } from './api-token.model'; 4 | 5 | @Module({ 6 | imports: [TypeOrmModule.forFeature([APIToken])], 7 | // providers: [TokenService], 8 | // exports: [TokenService], 9 | }) 10 | export class TokenModule {} 11 | -------------------------------------------------------------------------------- /backend/src/upload/upload.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common'; 2 | import { UploadService } from './upload.service'; 3 | import { AppConfigModule } from '../config/config.module'; 4 | 5 | @Module({ 6 | imports: [AppConfigModule], 7 | providers: [UploadService], 8 | exports: [UploadService], 9 | }) 10 | export class UploadModule {} 11 | -------------------------------------------------------------------------------- /backend/src/user/__tests__/user.model.spec.ts: -------------------------------------------------------------------------------- 1 | import { validate } from 'class-validator'; 2 | import { User } from '../user.model'; 3 | 4 | describe('User Model', () => { 5 | it('should validate a valid user', async () => { 6 | // Arrange 7 | const user = new User(); 8 | user.username = 'testuser'; 9 | user.email = 'test@example.com'; 10 | user.password = 'password123'; 11 | 12 | // Act 13 | const errors = await validate(user); 14 | 15 | // Assert 16 | expect(errors.length).toBe(0); 17 | }); 18 | 19 | it('should fail validation with invalid email', async () => { 20 | // Arrange 21 | const user = new User(); 22 | user.username = 'testuser'; 23 | user.email = 'invalid-email'; 24 | user.password = 'password123'; 25 | 26 | // Act 27 | const errors = await validate(user); 28 | 29 | // Assert 30 | expect(errors.length).toBeGreaterThan(0); 31 | expect(errors[0].constraints).toHaveProperty('isEmail'); 32 | }); 33 | }); 34 | -------------------------------------------------------------------------------- /backend/src/user/__tests__/user.service.spec.ts: -------------------------------------------------------------------------------- 1 | import { Test, TestingModule } from '@nestjs/testing'; 2 | import { getRepositoryToken } from '@nestjs/typeorm'; 3 | import { Repository } from 'typeorm'; 4 | import { JwtService } from '@nestjs/jwt'; 5 | import { ConfigService } from '@nestjs/config'; 6 | import { User } from '../user.model'; 7 | import { UserService } from '../user.service'; 8 | 9 | describe('UserService', () => { 10 | let service: UserService; 11 | let userRepository: Repository; 12 | 13 | beforeEach(async () => { 14 | const module: TestingModule = await Test.createTestingModule({ 15 | providers: [ 16 | UserService, 17 | { 18 | provide: getRepositoryToken(User), 19 | useValue: { 20 | findOne: jest.fn(), 21 | save: jest.fn(), 22 | create: jest.fn(), 23 | }, 24 | }, 25 | { 26 | provide: JwtService, 27 | useValue: { 28 | sign: jest.fn(), 29 | }, 30 | }, 31 | { 32 | provide: ConfigService, 33 | useValue: { 34 | get: jest.fn(), 35 | }, 36 | }, 37 | ], 38 | }).compile(); 39 | 40 | service = module.get(UserService); 41 | userRepository = module.get(getRepositoryToken(User)); 42 | }); 43 | 44 | it('should be defined', () => { 45 | expect(service).toBeDefined(); 46 | }); 47 | 48 | // TODO(Sma1lboy): Add more tests 49 | }); 50 | -------------------------------------------------------------------------------- /backend/src/user/dto/login-user.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql'; 2 | import { IsEmail, MinLength } from 'class-validator'; 3 | 4 | @InputType() 5 | export class LoginUserInput { 6 | @Field() 7 | @IsEmail() 8 | email: string; 9 | 10 | @Field() 11 | @MinLength(6) 12 | password: string; 13 | } 14 | -------------------------------------------------------------------------------- /backend/src/user/dto/register-user.input.ts: -------------------------------------------------------------------------------- 1 | import { InputType, Field } from '@nestjs/graphql'; 2 | import { IsEmail, IsString, MinLength } from 'class-validator'; 3 | 4 | @InputType() 5 | export class RegisterUserInput { 6 | @Field() 7 | @IsString() 8 | @MinLength(3) 9 | username: string; 10 | 11 | @Field() 12 | @IsString() 13 | @MinLength(6) 14 | password: string; 15 | 16 | @Field() 17 | @IsString() 18 | @MinLength(6) 19 | confirmPassword: string; 20 | 21 | @Field() 22 | @IsEmail() 23 | email: string; 24 | } 25 | -------------------------------------------------------------------------------- /backend/src/user/dto/resend-email.input.ts: -------------------------------------------------------------------------------- 1 | // src/auth/dto/resend-email.input.ts 2 | import { InputType, Field } from '@nestjs/graphql'; 3 | import { IsEmail, IsNotEmpty } from 'class-validator'; 4 | 5 | @InputType() 6 | export class ResendEmailInput { 7 | @Field() 8 | @IsEmail() 9 | @IsNotEmpty() 10 | email: string; 11 | } 12 | -------------------------------------------------------------------------------- /backend/src/user/dto/upload-avatar.input.ts: -------------------------------------------------------------------------------- 1 | import { Field, InputType } from '@nestjs/graphql'; 2 | import { FileUpload, GraphQLUpload } from 'graphql-upload-minimal'; 3 | 4 | @InputType() 5 | export class UploadAvatarInput { 6 | @Field(() => GraphQLUpload) 7 | file: Promise; 8 | } 9 | -------------------------------------------------------------------------------- /backend/src/user/user.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common'; 2 | import { UserResolver } from './user.resolver'; 3 | import { UserService } from './user.service'; 4 | import { DateScalar } from 'src/common/scalar/date.scalar'; 5 | import { User } from './user.model'; 6 | import { TypeOrmModule } from '@nestjs/typeorm'; 7 | import { JwtModule } from '@nestjs/jwt'; 8 | import { AuthModule } from 'src/auth/auth.module'; 9 | import { MailModule } from 'src/mail/mail.module'; 10 | import { UploadModule } from 'src/upload/upload.module'; 11 | import { GitHubModule } from 'src/github/github.module'; 12 | 13 | @Module({ 14 | imports: [ 15 | TypeOrmModule.forFeature([User]), 16 | JwtModule, 17 | AuthModule, 18 | MailModule, 19 | UploadModule, 20 | forwardRef(() => GitHubModule), 21 | ], 22 | providers: [UserResolver, UserService, DateScalar], 23 | exports: [UserService], 24 | }) 25 | export class UserModule {} 26 | -------------------------------------------------------------------------------- /backend/template/react-ts/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /backend/template/react-ts/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:20 2 | 3 | WORKDIR /app 4 | 5 | COPY package*.json ./ 6 | 7 | COPY . . 8 | 9 | EXPOSE 5173 10 | 11 | CMD sh -c "npm install --include=dev && npm run dev -- --host 0.0.0.0" 12 | -------------------------------------------------------------------------------- /backend/template/react-ts/README.md: -------------------------------------------------------------------------------- 1 | # React + TypeScript + Vite 2 | 3 | This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules. 4 | 5 | Currently, two official plugins are available: 6 | 7 | - [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh 8 | - [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh 9 | 10 | ## Expanding the ESLint configuration 11 | 12 | If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: 13 | 14 | - Configure the top-level `parserOptions` property like this: 15 | 16 | ```js 17 | export default tseslint.config({ 18 | languageOptions: { 19 | // other options... 20 | parserOptions: { 21 | project: ['./tsconfig.node.json', './tsconfig.app.json'], 22 | tsconfigRootDir: import.meta.dirname, 23 | }, 24 | }, 25 | }); 26 | ``` 27 | 28 | - Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked` 29 | - Optionally add `...tseslint.configs.stylisticTypeChecked` 30 | - Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config: 31 | 32 | ```js 33 | // eslint.config.js 34 | import react from 'eslint-plugin-react'; 35 | 36 | export default tseslint.config({ 37 | // Set the react version 38 | settings: { react: { version: '18.3' } }, 39 | plugins: { 40 | // Add the react plugin 41 | react, 42 | }, 43 | rules: { 44 | // other rules... 45 | // Enable its recommended rules 46 | ...react.configs.recommended.rules, 47 | ...react.configs['jsx-runtime'].rules, 48 | }, 49 | }); 50 | ``` 51 | -------------------------------------------------------------------------------- /backend/template/react-ts/components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/index.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } 22 | -------------------------------------------------------------------------------- /backend/template/react-ts/eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from '@eslint/js'; 2 | import globals from 'globals'; 3 | import reactHooks from 'eslint-plugin-react-hooks'; 4 | import reactRefresh from 'eslint-plugin-react-refresh'; 5 | import tseslint from 'typescript-eslint'; 6 | 7 | export default tseslint.config( 8 | { ignores: ['dist'] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ['**/*.{ts,tsx}'], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | 'react-hooks': reactHooks, 18 | 'react-refresh': reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | 'react-refresh/only-export-components': [ 23 | 'warn', 24 | { allowConstantExport: true }, 25 | ], 26 | }, 27 | }, 28 | ); 29 | -------------------------------------------------------------------------------- /backend/template/react-ts/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Codefox generated project 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from '@radix-ui/react-aspect-ratio'; 2 | 3 | function AspectRatio({ 4 | ...props 5 | }: React.ComponentProps) { 6 | return ; 7 | } 8 | 9 | export { AspectRatio }; 10 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as AvatarPrimitive from '@radix-ui/react-avatar'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | function Avatar({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | function AvatarImage({ 25 | className, 26 | ...props 27 | }: React.ComponentProps) { 28 | return ( 29 | 34 | ); 35 | } 36 | 37 | function AvatarFallback({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ); 51 | } 52 | 53 | export { Avatar, AvatarImage, AvatarFallback }; 54 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import { Slot } from '@radix-ui/react-slot'; 3 | import { cva, type VariantProps } from 'class-variance-authority'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | const badgeVariants = cva( 8 | 'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-semibold w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0 transition-[color,box-shadow]', 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | 'border-transparent bg-primary text-primary-foreground shadow-sm [a&]:hover:bg-primary/90', 14 | secondary: 15 | 'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', 16 | destructive: 17 | 'border-transparent bg-destructive text-destructive-foreground shadow-sm [a&]:hover:bg-destructive/90', 18 | outline: 19 | 'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', 20 | }, 21 | }, 22 | defaultVariants: { 23 | variant: 'default', 24 | }, 25 | }, 26 | ); 27 | 28 | function Badge({ 29 | className, 30 | variant, 31 | asChild = false, 32 | ...props 33 | }: React.ComponentProps<'span'> & 34 | VariantProps & { asChild?: boolean }) { 35 | const Comp = asChild ? Slot : 'span'; 36 | 37 | return ( 38 | 43 | ); 44 | } 45 | 46 | export { Badge, badgeVariants }; 47 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/checkbox.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as CheckboxPrimitive from '@radix-ui/react-checkbox'; 5 | import { CheckIcon } from 'lucide-react'; 6 | 7 | import { cn } from '@/lib/utils'; 8 | 9 | function Checkbox({ 10 | className, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 26 | 27 | 28 | 29 | ); 30 | } 31 | 32 | export { Checkbox }; 33 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/collapsible.tsx: -------------------------------------------------------------------------------- 1 | import * as CollapsiblePrimitive from '@radix-ui/react-collapsible'; 2 | 3 | function Collapsible({ 4 | ...props 5 | }: React.ComponentProps) { 6 | return ; 7 | } 8 | 9 | function CollapsibleTrigger({ 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 17 | ); 18 | } 19 | 20 | function CollapsibleContent({ 21 | ...props 22 | }: React.ComponentProps) { 23 | return ( 24 | 28 | ); 29 | } 30 | 31 | export { Collapsible, CollapsibleTrigger, CollapsibleContent }; 32 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/hover-card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as HoverCardPrimitive from '@radix-ui/react-hover-card'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | function HoverCard({ 7 | ...props 8 | }: React.ComponentProps) { 9 | return ; 10 | } 11 | 12 | function HoverCardTrigger({ 13 | ...props 14 | }: React.ComponentProps) { 15 | return ( 16 | 17 | ); 18 | } 19 | 20 | function HoverCardContent({ 21 | className, 22 | align = 'center', 23 | sideOffset = 4, 24 | ...props 25 | }: React.ComponentProps) { 26 | return ( 27 | 37 | ); 38 | } 39 | 40 | export { HoverCard, HoverCardTrigger, HoverCardContent }; 41 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<'input'>) { 6 | return ( 7 | 16 | ); 17 | } 18 | 19 | export { Input }; 20 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as LabelPrimitive from '@radix-ui/react-label'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ); 22 | } 23 | 24 | export { Label }; 25 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/popover.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as PopoverPrimitive from '@radix-ui/react-popover'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | function Popover({ 7 | ...props 8 | }: React.ComponentProps) { 9 | return ; 10 | } 11 | 12 | function PopoverTrigger({ 13 | ...props 14 | }: React.ComponentProps) { 15 | return ; 16 | } 17 | 18 | function PopoverContent({ 19 | className, 20 | align = 'center', 21 | sideOffset = 4, 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 26 | 36 | 37 | ); 38 | } 39 | 40 | function PopoverAnchor({ 41 | ...props 42 | }: React.ComponentProps) { 43 | return ; 44 | } 45 | 46 | export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }; 47 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/progress.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import * as React from 'react'; 4 | import * as ProgressPrimitive from '@radix-ui/react-progress'; 5 | 6 | import { cn } from '@/lib/utils'; 7 | 8 | function Progress({ 9 | className, 10 | value, 11 | ...props 12 | }: React.ComponentProps) { 13 | return ( 14 | 22 | 27 | 28 | ); 29 | } 30 | 31 | export { Progress }; 32 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/radio-group.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as RadioGroupPrimitive from '@radix-ui/react-radio-group'; 3 | import { CircleIcon } from 'lucide-react'; 4 | 5 | import { cn } from '@/lib/utils'; 6 | 7 | function RadioGroup({ 8 | className, 9 | ...props 10 | }: React.ComponentProps) { 11 | return ( 12 | 17 | ); 18 | } 19 | 20 | function RadioGroupItem({ 21 | className, 22 | ...props 23 | }: React.ComponentProps) { 24 | return ( 25 | 33 | 37 | 38 | 39 | 40 | ); 41 | } 42 | 43 | export { RadioGroup, RadioGroupItem }; 44 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/separator.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as SeparatorPrimitive from '@radix-ui/react-separator'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | function Separator({ 7 | className, 8 | orientation = 'horizontal', 9 | decorative = true, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 23 | ); 24 | } 25 | 26 | export { Separator }; 27 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '@/lib/utils'; 2 | 3 | function Skeleton({ className, ...props }: React.ComponentProps<'div'>) { 4 | return ( 5 |
10 | ); 11 | } 12 | 13 | export { Skeleton }; 14 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/sonner.tsx: -------------------------------------------------------------------------------- 1 | 'use client'; 2 | 3 | import { useTheme } from 'next-themes'; 4 | import { Toaster as Sonner, ToasterProps } from 'sonner'; 5 | 6 | const Toaster = ({ ...props }: ToasterProps) => { 7 | const { theme = 'system' } = useTheme(); 8 | 9 | return ( 10 | 26 | ); 27 | }; 28 | 29 | export { Toaster }; 30 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | import * as SwitchPrimitive from '@radix-ui/react-switch'; 3 | 4 | import { cn } from '@/lib/utils'; 5 | 6 | function Switch({ 7 | className, 8 | ...props 9 | }: React.ComponentProps) { 10 | return ( 11 | 19 | 25 | 26 | ); 27 | } 28 | 29 | export { Switch }; 30 | -------------------------------------------------------------------------------- /backend/template/react-ts/src/components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react'; 2 | 3 | import { cn } from '@/lib/utils'; 4 | 5 | function Textarea({ className, ...props }: React.ComponentProps<'textarea'>) { 6 | return ( 7 |