├── .devcontainer ├── Dockerfile ├── devcontainer.json └── setup.sh ├── .gitattributes ├── .github ├── FUNDING.yml └── workflows │ ├── commit-checks.yml │ ├── react-admin.yml │ ├── react-frontend.yml │ ├── rust-clippy.yml │ ├── rust-fmt.yml │ └── rust.yml ├── .gitignore ├── Cargo-backup.toml ├── Cargo.lock ├── Cargo.toml ├── Dockerfile ├── EXAMPLE.env ├── LICENSE ├── README.md ├── avored.http ├── build.rs ├── data └── avored.db │ └── .gitignore ├── proto ├── admin_user.proto ├── asset.proto ├── auth.proto ├── cms.proto ├── content.proto ├── dashboard.proto ├── echo.proto ├── general.proto ├── misc.proto └── setting.proto ├── public ├── images │ ├── favicon.ico │ ├── locales │ │ ├── en.svg │ │ └── fr.svg │ └── logo_only.svg ├── log │ └── .gitkeep └── upload │ └── .gitkeep ├── resources ├── locales │ ├── en.json │ └── fr.json ├── mail │ ├── contact-us-email.hbs │ └── forgot-password.hbs └── postman │ └── Avored cms.postman_collection.json ├── src ├── api │ ├── admin_user_api.rs │ ├── asset_api.rs │ ├── auth_api.rs │ ├── cms_api.rs │ ├── content_api.rs │ ├── dashboard_api.rs │ ├── general_api.rs │ ├── handlers │ │ ├── asset │ │ │ ├── mod.rs │ │ │ ├── request │ │ │ │ ├── mod.rs │ │ │ │ └── store_asset_request.rs │ │ │ └── store_asset_api_handler.rs │ │ └── mod.rs │ ├── misc_api.rs │ ├── mod.rs │ ├── proto │ │ ├── admin_user.rs │ │ ├── asset.rs │ │ ├── auth.rs │ │ ├── cms.rs │ │ ├── content.rs │ │ ├── dashboard.rs │ │ ├── echo.rs │ │ ├── general.rs │ │ ├── misc.rs │ │ ├── mod.rs │ │ └── setting.rs │ ├── rest_api_routes.rs │ ├── setting_api.rs │ └── test_api.rs ├── avored_state.rs ├── error.rs ├── extensions │ ├── email_message_builder.rs │ └── mod.rs ├── main.rs ├── middleware │ ├── grpc_auth_middleware.rs │ ├── mod.rs │ ├── require_jwt_authentication.rs │ └── validate_cms_authentication.rs ├── models │ ├── admin_user_model.rs │ ├── asset_model.rs │ ├── collection_model.rs │ ├── content_model.rs │ ├── mod.rs │ ├── password_rest_model.rs │ ├── role_model.rs │ ├── setting_model.rs │ ├── token_claim_model.rs │ └── validation_error.rs ├── providers │ ├── avored_config_provider.rs │ ├── avored_database_provider.rs │ ├── avored_template_provider.rs │ └── mod.rs ├── repositories │ ├── admin_user_repository.rs │ ├── asset_repository.rs │ ├── collection_repository.rs │ ├── content_repository.rs │ ├── mod.rs │ ├── password_reset_repository.rs │ ├── role_repository.rs │ └── setting_repository.rs ├── requests │ ├── admin_user_request │ │ ├── mod.rs │ │ ├── store_admin_user_request.rs │ │ ├── store_role_request.rs │ │ └── update_role_request.rs │ ├── auth_request │ │ ├── forgot_password_request.rs │ │ ├── login_request.rs │ │ ├── mod.rs │ │ └── reset_password_request.rs │ ├── misc_request │ │ ├── mod.rs │ │ └── setup_request.rs │ └── mod.rs └── services │ ├── admin_user_service.rs │ ├── asset_service.rs │ ├── auth_service.rs │ ├── cms_service.rs │ ├── content_service.rs │ ├── general_service.rs │ ├── misc_service.rs │ ├── mod.rs │ └── setting_service.rs ├── ts-grpc-react-admin ├── .env ├── .env.example ├── .env.production.local ├── .gitignore ├── README.md ├── build │ ├── asset-manifest.json │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── logo_only.svg │ ├── manifest.json │ ├── output.css │ ├── robots.txt │ └── static │ │ ├── css │ │ ├── main.7047f949.css │ │ └── main.7047f949.css.map │ │ ├── js │ │ ├── 453.ebcd728c.chunk.js │ │ ├── 453.ebcd728c.chunk.js.map │ │ ├── main.754b7e0e.js │ │ ├── main.754b7e0e.js.LICENSE.txt │ │ └── main.754b7e0e.js.map │ │ └── media │ │ ├── en.9acbcf1017616e2140a1935111c50f28.svg │ │ ├── logo_only.844fbcaa6b01372b345470d497f050fa.svg │ │ └── marked.9d11e79e65a241a47955.cjs ├── package-lock.json ├── package.json ├── postcss.config.mjs ├── public │ ├── index.html │ ├── logo192.png │ ├── logo512.png │ ├── logo_only.svg │ ├── manifest.json │ ├── output.css │ └── robots.txt ├── src │ ├── App.tsx │ ├── assets │ │ ├── images │ │ │ ├── favicon.ico │ │ │ ├── locales │ │ │ │ ├── en.svg │ │ │ │ └── fr.svg │ │ │ └── logo_only.svg │ │ └── logo_only.svg │ ├── components │ │ ├── AvoRedButton.tsx │ │ ├── AvoRedIconButton.tsx │ │ ├── AvoRedMultiSelectField.tsx │ │ ├── AvoRedTable.tsx │ │ ├── AvoredModal.tsx │ │ ├── ErrorMessage.tsx │ │ ├── HasPermission.tsx │ │ ├── InputField.tsx │ │ ├── Pagination.tsx │ │ └── TextareaField.tsx │ ├── context │ │ └── ThemeContext.tsx │ ├── grpc_generated │ │ ├── Admin_userServiceClientPb.ts │ │ ├── AssetServiceClientPb.ts │ │ ├── AuthServiceClientPb.ts │ │ ├── ContentServiceClientPb.ts │ │ ├── GeneralServiceClientPb.ts │ │ ├── MiscServiceClientPb.ts │ │ ├── SettingServiceClientPb.ts │ │ ├── admin_user_pb.d.ts │ │ ├── admin_user_pb.js │ │ ├── asset_pb.d.ts │ │ ├── asset_pb.js │ │ ├── auth_pb.d.ts │ │ ├── auth_pb.js │ │ ├── content_pb.d.ts │ │ ├── content_pb.js │ │ ├── general_pb.d.ts │ │ ├── general_pb.js │ │ ├── misc_pb.d.ts │ │ ├── misc_pb.js │ │ ├── setting_pb.d.ts │ │ └── setting_pb.js │ ├── hooks │ │ ├── admin_user │ │ │ ├── UseAdminUserPaginateHook.ts │ │ │ ├── UseGetAdminUserHook.ts │ │ │ ├── UseGetRoleHook.ts │ │ │ ├── UseGetRoleOption.ts │ │ │ ├── UsePutRoleIdentifierHook.ts │ │ │ ├── UseRolePaginateHook.ts │ │ │ ├── UseStoreAdminUserHook.ts │ │ │ ├── UseStoreRoleHook.ts │ │ │ ├── UseUpdateAdminUserHook.ts │ │ │ └── UseUpdateRoleHook.ts │ │ ├── asset │ │ │ ├── UseAssetTableHook.ts │ │ │ ├── UseCreateFolderHook.ts │ │ │ ├── UseDeleteAssetHook.ts │ │ │ ├── UseDeleteFolderHook.ts │ │ │ ├── UseRenameAssetHook.ts │ │ │ └── UseStoreAssetHook.ts │ │ ├── auth │ │ │ ├── UseLoginHook.ts │ │ │ ├── UseResetPasswordHook.ts │ │ │ └── useForgotPasswordHook.ts │ │ ├── content │ │ │ ├── UseCollectionAllHook.ts │ │ │ ├── UseContentPaginateHook.ts │ │ │ ├── UseGetContentHook.ts │ │ │ ├── UsePutContentIdentifierHook.ts │ │ │ ├── UseStoreCollectionHook.ts │ │ │ ├── UseStoreContentHook.ts │ │ │ ├── UseUpdateCollectionHook.ts │ │ │ └── UseUpdateContentHook.ts │ │ ├── general │ │ │ └── UseLoggedInUserHook.ts │ │ ├── misc │ │ │ ├── UseAxiosHook.ts │ │ │ ├── UseHealthCheckHook.ts │ │ │ └── UseSetupPost.ts │ │ └── setting │ │ │ ├── UseSettingHook.ts │ │ │ └── UseStoreSettingHook.ts │ ├── index.css │ ├── index.tsx │ ├── layouts │ │ ├── AppLayout.tsx │ │ └── partials │ │ │ ├── AppHeader.tsx │ │ │ ├── AppSidebar.tsx │ │ │ ├── DeleteDataConfirmationModal.tsx │ │ │ └── InstallDataConfirmationModal.tsx │ ├── locales │ │ ├── en.json │ │ └── fr.json │ ├── pages │ │ ├── DashboardPage.tsx │ │ ├── HomePage.tsx │ │ ├── admin_user │ │ │ ├── AdminUserCreatePage.tsx │ │ │ ├── AdminUserEditPage.tsx │ │ │ ├── AdminUserTablePage.tsx │ │ │ ├── RoleCreatePage.tsx │ │ │ ├── RoleEditPage.tsx │ │ │ └── RoleTablePage.tsx │ │ ├── asset │ │ │ ├── AssetTablePage.tsx │ │ │ ├── AssetUploadModal.tsx │ │ │ ├── CreateFolderModal.tsx │ │ │ ├── DisplayAsset.tsx │ │ │ └── RenameAssetModal.tsx │ │ ├── auth │ │ │ ├── ForgotPasswordPage.tsx │ │ │ ├── LoginPage.tsx │ │ │ ├── LogoutPage.tsx │ │ │ └── ResetPasswordPage.tsx │ │ ├── content │ │ │ ├── ContentCreatePage.tsx │ │ │ ├── ContentEditPage.tsx │ │ │ ├── ContentFieldModal.tsx │ │ │ ├── ContentSidebar.tsx │ │ │ └── ContentTablePage.tsx │ │ ├── misc │ │ │ └── SetupPage.tsx │ │ └── setting │ │ │ └── SettingPage.tsx │ ├── react-app-env.d.ts │ ├── reportWebVitals.ts │ ├── schemas │ │ ├── admin_user │ │ │ ├── UseAdminUserCreateSchema.ts │ │ │ ├── UseAdminUserEditSchema.ts │ │ │ ├── UsePutRoleSchema.ts │ │ │ ├── UseRoleCreateSchema.ts │ │ │ └── UserRoleEditSchema.ts │ │ ├── asset │ │ │ ├── AssetSaveSchema.ts │ │ │ ├── UseCreateFolderSchema.ts │ │ │ └── UseRenameFolderSchema.ts │ │ ├── auth │ │ │ ├── UseForgotPasswordSchema.ts │ │ │ ├── UseLoginSchema.ts │ │ │ └── UseResetPasswordSchema.ts │ │ ├── content │ │ │ ├── UseCollectionCreateSchema.ts │ │ │ ├── UseCollectionUpdateSchema.ts │ │ │ ├── UseContentCreateSchema.ts │ │ │ └── UseContentEditSchema.ts │ │ ├── misc │ │ │ └── SetupSchema.ts │ │ └── setting │ │ │ └── SettingSaveSchema.ts │ ├── setupTests.ts │ └── types │ │ ├── admin_user │ │ └── AdminUserType.ts │ │ ├── asset │ │ └── AssetType.ts │ │ ├── auth │ │ └── LoginPostType.ts │ │ ├── common │ │ ├── ErrorType.ts │ │ └── common.ts │ │ ├── content │ │ └── ContentType.ts │ │ ├── misc │ │ ├── PaginateType.ts │ │ └── SetupType.ts │ │ └── setting │ │ └── SettingType.ts └── tsconfig.json └── ts-grpc-react-front ├── .env ├── .env.example ├── .env.production.local ├── .gitignore ├── README.md ├── build ├── asset-manifest.json ├── avored.svg ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── main-hero.svg ├── manifest.json ├── output.css ├── robots.txt └── static │ ├── css │ ├── main.327d90b5.css │ └── main.327d90b5.css.map │ ├── js │ ├── main.7fd6454a.js │ ├── main.7fd6454a.js.LICENSE.txt │ └── main.7fd6454a.js.map │ └── media │ ├── avored.844fbcaa6b01372b345470d497f050fa.svg │ └── main-hero.d1925a28ed0b905a765459c51e1fa8ff.svg ├── package-lock.json ├── package.json ├── public ├── avored.svg ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── main-hero.svg ├── manifest.json ├── output.css └── robots.txt ├── src ├── App.tsx ├── assets │ ├── avored.svg │ └── main-hero.svg ├── grpc_generated │ ├── CmsServiceClientPb.ts │ ├── ContentServiceClientPb.ts │ ├── cms_pb.d.ts │ ├── cms_pb.js │ ├── content_pb.d.ts │ └── content_pb.js ├── hooks │ └── useAxios.ts ├── index.css ├── index.tsx ├── layout │ ├── AppLayout.tsx │ └── partials │ │ ├── AppFooter.tsx │ │ ├── AppHeader.tsx │ │ └── GetStartedModal.tsx ├── lib │ ├── axios.ts │ └── page.ts ├── logo.svg ├── pages │ ├── PrivacyPage.tsx │ ├── Test.tsx │ └── home │ │ ├── ContactSection.tsx │ │ ├── FeaturesSection.tsx │ │ ├── HomePage.tsx │ │ ├── MainHeroSection.tsx │ │ ├── RepositoryInformation.tsx │ │ ├── hooks │ │ ├── useContactUsForm.ts │ │ ├── useHomeCmsPage.ts │ │ └── useRepositoryInformation.ts │ │ └── schemas │ │ └── useContactUsFormSchema.ts ├── react-app-env.d.ts ├── reportWebVitals.ts ├── setupTests.ts └── types │ ├── CmsPageType.ts │ ├── ContactUsType.ts │ └── RepositoryInformationType.ts └── tsconfig.json /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | WORKDIR /home/ 4 | 5 | COPY . . 6 | 7 | RUN bash ./setup.sh 8 | 9 | ENV PATH="/root/.cargo/bin:$PATH" -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "AvoRed Rust CMS", 3 | "extensions": [ 4 | "cschleiden.vscode-github-actions", 5 | "ms-vsliveshare.vsliveshare", 6 | "matklad.rust-analyzer", 7 | "serayuzgur.crates", 8 | "vadimcn.vscode-lldb" 9 | ], 10 | "dockerFile": "Dockerfile", 11 | "settings": { 12 | "editor.formatOnSave": true, 13 | "terminal.integrated.shell.linux": "/usr/bin/zsh" 14 | } 15 | } -------------------------------------------------------------------------------- /.devcontainer/setup.sh: -------------------------------------------------------------------------------- 1 | ## update and install some things we should probably have 2 | apt-get update 3 | apt-get install -y \ 4 | curl \ 5 | git \ 6 | gnupg2 \ 7 | jq \ 8 | sudo \ 9 | zsh \ 10 | vim \ 11 | build-essential \ 12 | openssl \ 13 | libssl-dev \ 14 | pkg-config \ 15 | llvm-dev \ 16 | libclang-dev clang 17 | 18 | ## Install rustup and common components 19 | curl https://sh.rustup.rs -sSf | sh -s -- -y 20 | rustup install nightly 21 | rustup component add rustfmt 22 | rustup component add clippy 23 | 24 | cargo install cargo-expand 25 | cargo install cargo-edit 26 | 27 | 28 | ## setup and install oh-my-zsh 29 | sh -c "$(curl -fsSL https://raw.githubusercontent.com/robbyrussell/oh-my-zsh/master/tools/install.sh)" 30 | cp -R /root/.oh-my-zsh /home/$USERNAME 31 | cp /root/.zshrc /home/$USERNAME 32 | sed -i -e "s/\/root\/.oh-my-zsh/\/home\/$USERNAME\/.oh-my-zsh/g" /home/$USERNAME/.zshrc 33 | chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc 34 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | *.ts linguist-vendored 4 | *.js linguist-vendored 5 | *.hbs linguist-vendored 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: [indpurvesh] 4 | -------------------------------------------------------------------------------- /.github/workflows/commit-checks.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | # build: 11 | # runs-on: ubuntu-latest 12 | # steps: 13 | # - uses: actions/checkout@v4 14 | # - uses: Swatinem/rust-cache@v2.4.0 15 | # - name: Build the Docker image 16 | # run: docker build . --file Dockerfile --tag marks:$(date +%s) 17 | # format: 18 | # runs-on: ubuntu-latest 19 | # steps: 20 | # - uses: actions/checkout@v3 21 | # - uses: dtolnay/rust-toolchain@stable 22 | # - uses: Swatinem/rust-cache@v2.4.0 23 | # - uses: mbrobbel/rustfmt-check@master 24 | # with: 25 | # token: ${{ secrets.CI_TOKEN }} 26 | 27 | # mode: review ## Review mode requires to be on pull_request only.. 28 | # steps: 29 | # - uses: actions/checkout@v3 30 | # with: 31 | # repository: avored/avored-rust-cms 32 | # - uses: Swatinem/rust-cache@v2.4.0 33 | # - name: github-action-auto-format 34 | # uses: cloudposse/github-action-auto-format@v0.12.0 35 | # with: 36 | # # GitHub Token for use `github_format.sh` and PR creation steps. This token must be granted `workflows` permissions. 37 | # workflow-token: ${{ secrets.CI_TOKEN }} 38 | greet: 39 | runs-on: ubuntu-latest 40 | steps: 41 | - name: First interaction 42 | uses: actions/first-interaction@v1.1.1 43 | with: 44 | # Token for the repository. Can be passed in using {{ secrets.GITHUB_TOKEN }} 45 | repo-token: ${{ secrets.CI_TOKEN }} 46 | issue-message: | 47 | Hello! Thank you for filing an issue. 48 | 49 | Please include relevant logs or detailed description for faster resolutions. 50 | 51 | We really appreciate your contribution! 52 | 53 | Thanks 54 | Avored 55 | pr-message: | 56 | Hello! Thank you for your contribution. 57 | 58 | If you are fixing a bug, please reference the issue number in the description. 59 | 60 | If you are implementing a feature request, please check with the maintainers that the feature will be accepted first. 61 | 62 | We really appreciate your contribution! 63 | 64 | Thanks 65 | Avored 66 | -------------------------------------------------------------------------------- /.github/workflows/react-admin.yml: -------------------------------------------------------------------------------- 1 | name: React Admin deploy 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-22.04 10 | defaults: 11 | run: 12 | working-directory: 'ts-grpc-react-admin' 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js environment 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '22' 21 | 22 | - name: Install dependencies 23 | run: REACT_APP_FRONTEND_BASE_URL=${{secrets.REACT_APP_FRONTEND_BASE_URL}} 24 | REACT_APP_BACKEND_BASE_URL=${{secrets.REACT_APP_BACKEND_BASE_URL}} npm ci 25 | 26 | - name: Build 27 | run: REACT_APP_FRONTEND_BASE_URL=${{secrets.REACT_APP_FRONTEND_BASE_URL}} 28 | REACT_APP_BACKEND_BASE_URL=${{secrets.REACT_APP_BACKEND_BASE_URL}} npm run build 29 | 30 | # - name: Copy files via ssh 31 | # uses: appleboy/scp-action@master 32 | # with: 33 | # host: ${{ secrets.HOST }} 34 | # username: ${{ secrets.USERNAME }} 35 | # key: ${{ secrets.SSH_KEY }} 36 | # source: "./ts-grpc-react-admin/build" 37 | # target: ${{ secrets.REACT_TARGET_PATH }} 38 | 39 | - name: deploy demo app 40 | uses: appleboy/scp-action@master 41 | with: 42 | host: ${{ secrets.HOST }} 43 | username: ${{ secrets.USERNAME }} 44 | key: ${{ secrets.SSH_KEY }} 45 | source: "./ts-grpc-react-admin/build" 46 | target: ${{ secrets.REACT_DEMO_TARGET_PATH }} 47 | -------------------------------------------------------------------------------- /.github/workflows/react-frontend.yml: -------------------------------------------------------------------------------- 1 | name: Frontend deploy 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | 7 | jobs: 8 | build: 9 | runs-on: ubuntu-22.04 10 | defaults: 11 | run: 12 | working-directory: 'ts-grpc-react-front' 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | 17 | - name: Setup Node.js environment 18 | uses: actions/setup-node@v4 19 | with: 20 | node-version: '22' 21 | 22 | - name: Install dependencies 23 | run: npm ci 24 | 25 | - name: Build 26 | run: VITE_AVORED_FRONTEND_BASE_URL=${{secrets.VITE_AVORED_FRONTEND_BASE_URL}} 27 | VITE_AVORED_BACKEND_BASE_URL=${{secrets.VITE_AVORED_BACKEND_BASE_URL}} 28 | VITE_AVORED_CMS_TOKEN=${{secrets.VITE_AVORED_CMS_TOKEN}} npm run build 29 | # 30 | # - name: Copy files via ssh 31 | # uses: appleboy/scp-action@master 32 | # with: 33 | # host: ${{ secrets.HOST }} 34 | # username: ${{ secrets.USERNAME }} 35 | # key: ${{ secrets.SSH_KEY }} 36 | # source: "./ts-grpc-react-front/build" 37 | # target: ${{ secrets.REACT_TARGET_PATH }} 38 | 39 | - name: Deploy demo front files via ssh 40 | uses: appleboy/scp-action@master 41 | with: 42 | host: ${{ secrets.HOST }} 43 | username: ${{ secrets.USERNAME }} 44 | key: ${{ secrets.SSH_KEY }} 45 | source: "./ts-grpc-react-front/build" 46 | target: ${{ secrets.REACT_DEMO_TARGET_PATH }} 47 | -------------------------------------------------------------------------------- /.github/workflows/rust-clippy.yml: -------------------------------------------------------------------------------- 1 | # This workflow uses actions that are not certified by GitHub. 2 | # They are provided by a third-party and are governed by 3 | # separate terms of service, privacy policy, and support 4 | # documentation. 5 | # rust-clippy is a tool that runs a bunch of lints to catch common 6 | # mistakes in your Rust code and help improve your Rust code. 7 | # More details at https://github.com/rust-lang/rust-clippy 8 | # and https://rust-lang.github.io/rust-clippy/ 9 | 10 | name: Rust Clippy analyze 11 | 12 | on: [push, pull_request] 13 | 14 | jobs: 15 | rust-clippy-analyze: 16 | name: Run rust-clippy analyzing 17 | runs-on: ubuntu-22.04 18 | permissions: 19 | contents: read 20 | security-events: write 21 | actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status 22 | steps: 23 | - name: Checkout code 24 | uses: actions/checkout@v4 25 | 26 | - name: Install Rust toolchain 27 | uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 28 | with: 29 | profile: minimal 30 | toolchain: stable 31 | components: clippy 32 | override: true 33 | 34 | - name: Install required cargo 35 | run: cargo install clippy-sarif sarif-fmt 36 | - name: cache project 37 | uses: Swatinem/rust-cache@v2 38 | 39 | - name: Run rust-clippy 40 | run: 41 | cargo clippy 42 | --all-features 43 | --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt 44 | continue-on-error: true 45 | 46 | 47 | 48 | - name: Upload analysis results to GitHub 49 | uses: github/codeql-action/upload-sarif@v2 50 | with: 51 | sarif_file: rust-clippy-results.sarif 52 | wait-for-processing: true 53 | -------------------------------------------------------------------------------- /.github/workflows/rust-fmt.yml: -------------------------------------------------------------------------------- 1 | name: Rust fmt check 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | CARGO_TERM_COLOR: always 7 | 8 | jobs: 9 | test: 10 | name: AvoredCMS 11 | runs-on: ubuntu-22.04 12 | 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: Swatinem/rust-cache@v2.4.0 16 | - uses: arduino/setup-protoc@v3 17 | - uses: actions-rs/toolchain@v1 18 | with: 19 | profile: minimal 20 | toolchain: stable 21 | override: true 22 | - name: Rust Check 23 | run: | 24 | cargo check 25 | -------------------------------------------------------------------------------- /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust deploy 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | 7 | env: 8 | CARGO_TERM_COLOR: always 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-22.04 13 | steps: 14 | - uses: actions/checkout@v4 15 | - uses: Swatinem/rust-cache@v2.4.0 16 | - uses: arduino/setup-protoc@v3 17 | 18 | # - name: Build 19 | # run: cargo build --verbose 20 | - name: Run releases 21 | run: cargo build --release 22 | # - name: Deploy release 23 | # uses: appleboy/scp-action@master 24 | # with: 25 | # host: ${{ secrets.HOST }} 26 | # username: ${{ secrets.USERNAME }} 27 | # key: ${{ secrets.SSH_KEY }} 28 | # source: "./target/release" 29 | # target: ${{ secrets.RUST_TARGET_PATH }} 30 | - name: Deploy demo release 31 | uses: appleboy/scp-action@master 32 | with: 33 | host: ${{ secrets.HOST }} 34 | username: ${{ secrets.USERNAME }} 35 | key: ${{ secrets.SSH_KEY }} 36 | source: "./target/release" 37 | target: ${{ secrets.RUST_DEMO_TARGET_PATH }} 38 | # - name: Deploy files 39 | # uses: appleboy/scp-action@master 40 | # with: 41 | # host: ${{ secrets.HOST }} 42 | # username: ${{ secrets.USERNAME }} 43 | # key: ${{ secrets.SSH_KEY }} 44 | # source: "./public,./resources" 45 | # target: ${{ secrets.RUST_DEPLOY_PATH }} 46 | - name: Deploy demo files 47 | uses: appleboy/scp-action@master 48 | with: 49 | host: ${{ secrets.HOST }} 50 | username: ${{ secrets.USERNAME }} 51 | key: ${{ secrets.SSH_KEY }} 52 | source: "./public,./resources" 53 | target: ${{ secrets.RUST_DEMO_DEPLOY_PATH }} 54 | - name: restart service 55 | uses: appleboy/ssh-action@v1.1.0 56 | with: 57 | host: ${{ secrets.HOST }} 58 | username: ${{ secrets.USERNAME }} 59 | key: ${{ secrets.SSH_KEY }} 60 | script: | 61 | sudo systemctl stop demoavored.service 62 | sudo systemctl start demoavored.service 63 | 64 | 65 | 66 | 67 | 68 | 69 | # - name: change file ownership 70 | # uses: appleboy/ssh-action@v1.0.3 71 | # with: 72 | # host: ${{ secrets.HOST }} 73 | # username: ${{ secrets.USERNAME }} 74 | # key: ${{ secrets.SSH_KEY }} 75 | # port: ${{ secrets.PORT }} 76 | # script: sudo chown -R www-data:www-data ${{ secrets.RUST_TARGET_PATH}} 77 | 78 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | .DS_Store 3 | .env.dev 4 | .env.test 5 | .env.stag 6 | .env.prod 7 | .idea 8 | .vscode 9 | .devcontainer 10 | /target/debug 11 | /target/release 12 | /target/tmp 13 | /target/.rustc_info.json 14 | /target/CACHEDIR.TAG 15 | /public/upload 16 | /public/log 17 | /public/install_demo 18 | ts-grpc-react-admin/node_modules -------------------------------------------------------------------------------- /Cargo-backup.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avored-rust-cms" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | axum = { version = "0.8.3", features = ["multipart"] } 10 | serde = { version = "1.0.219", features = ["derive"] } 11 | tokio = { version = "1.44.2", features = ["full"] } 12 | tracing = "0.1.41" 13 | tracing-subscriber = { version = "0.3.19", features = ["env-filter"] } 14 | tower-http = { version = "0.6.2", features = ["fs", "cors"] } 15 | dotenvy = "0.15.7" 16 | axum-extra = { version = "0.10.1", features = ["cookie", "cookie-signed"] } 17 | argon2 = "0.5.3" 18 | rand = "0.9.0" 19 | urlencoding = "2.1.3" 20 | serde_json = "1.0.140" 21 | surrealdb = { version = "2.2.2", features = ["kv-rocksdb", "kv-mem"] } 22 | jsonwebtoken = "9.3.1" 23 | chrono = { version = "0.4.40", features = [] } 24 | email_address = "0.2.9" 25 | rust-i18n = "3.1.4" 26 | lettre = { version = "0.11.15", features = ["tokio1-native-tls"] } 27 | handlebars = "6.3.2" 28 | utoipa = "5.3.1" 29 | prost = { version = "0.13.5", features = ["prost-derive"] } 30 | prost-types = "0.13.5" 31 | tonic = "0.13.0" 32 | 33 | [build-dependencies] 34 | tonic-build = "0.13.0" 35 | 36 | [dev-dependencies] 37 | tower = { version = "0.5.2", features = ["util"] } 38 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "avored-rust-cms" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | 7 | [dependencies] 8 | axum = { version = "0.8.4", features = ["multipart", "http2"] } 9 | prost = "0.13.5" 10 | prost-types = "0.13.5" 11 | tokio = { version = "1.45.1", features = ["full"] } 12 | tonic = { version = "0.13.1" } 13 | axum_tonic = "0.4.0" 14 | tracing = "0.1.41" 15 | tracing-subscriber = "0.3.19" 16 | argon2 = "0.5.3" 17 | serde = { version = "1.0.219", features = ["derive"] } 18 | serde_json = "1.0.140" 19 | jsonwebtoken = "9.3.1" 20 | chrono = { version = "0.4.41", features = [] } 21 | email_address = "0.2.9" 22 | rust-i18n = "3.1.5" 23 | surrealdb = { version = "2.3.3", features = ["kv-rocksdb", "kv-mem"] } 24 | rand = "0.9.1" 25 | dotenvy = "0.15.7" 26 | tower-http = { version = "0.6.4", features = ["fs", "cors"] } 27 | lettre = { version = "0.11.16", features = ["tokio1-native-tls"] } 28 | handlebars = "6.3.2" 29 | 30 | 31 | [build-dependencies] 32 | tonic-build = { version = "0.13.1", features = ["prost"] } 33 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Using the `rust-musl-builder` as base image, instead of 2 | # the official Rust toolchain 3 | FROM rust:1.70 AS chef 4 | USER root 5 | RUN cargo install cargo-chef 6 | RUN apt-get update && apt-get install -y libclang-dev librocksdb-dev llvm 7 | WORKDIR /app 8 | 9 | FROM chef AS planner 10 | COPY . . 11 | RUN cargo chef prepare --recipe-path recipe.json 12 | 13 | FROM chef AS builder 14 | COPY --from=planner /app/recipe.json recipe.json 15 | # Notice that we are specifying the --target flag! 16 | RUN cargo chef cook --release --target x86_64-unknown-linux-gnu --recipe-path recipe.json 17 | COPY . . 18 | RUN cp .env.example .env 19 | RUN cargo build --release --target x86_64-unknown-linux-gnu 20 | 21 | FROM alpine AS runtime 22 | RUN addgroup -S myuser && adduser -S myuser -G myuser 23 | COPY --from=builder /app/target/x86_64-unknown-linux-gnu/release/avored-rust-cms /usr/local/bin/ 24 | USER myuser 25 | CMD ["/usr/local/bin/avored-rust-cms"] 26 | -------------------------------------------------------------------------------- /EXAMPLE.env: -------------------------------------------------------------------------------- 1 | APP_ENV=dev 2 | AVORED_DATABASE_NAMESPACE=public 3 | AVORED_DATABASE_NAME=avored_cms 4 | AVORED_DATABASE_FOLDER_NAME=rocksdb://data/avored.db 5 | 6 | AVORED_PASSWORD_SALT=sixty_for_charactor_long_string_goes_here 7 | 8 | AVORED_JWT_SECRET=sixty_for_charactor_long_string_goes_here 9 | AVORED_JWT_EXPIRED_IN=60m 10 | AVORED_JWT_MAXAGE=60 11 | 12 | AVORED_BACK_END_APP_URL=http://localhost:50051 13 | AVORED_REACT_ADMIN_APP_URL=http://localhost:3000 14 | AVORED_REACT_FRONTEND_APP_URL=http://localhost:5173 15 | 16 | 17 | ## multiple value is supported as comma seperated 18 | AVORED_CORS_ALLOWED_APP_URL=http://localhost:3000,http://localhost:50051,http://localhost:5173 19 | 20 | 21 | #AVORED_BACK_END_APP_URL=https://api.avored.com 22 | #AVORED_REACT_ADMIN_APP_URL=https://demo.avored.com 23 | #AVORED_REACT_FRONTEND_APP_URL=https://avored.com 24 | 25 | SMTP_HOST=sandbox.smtp.mailtrap.io 26 | SMTP_USERNAME= 27 | SMTP_PASSWORD= 28 | SMTP_PORT=587 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 AvoRed rust content management system 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /avored.http: -------------------------------------------------------------------------------- 1 | 2 | ### API POST ADMIN USER LOGIN 3 | POST http://localhost:8080/api/login 4 | Content-Type: application/json 5 | 6 | {"email": "admin@admin.com", "password": "admin123"} 7 | 8 | 9 | ### Create ADMIN USER POST 10 | POST http://localhost:8080/api/admin-user 11 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxOHVlMGl1cmloaW8weWdva3hkYyIsIm5hbWUiOiJBZG1pbiIsImVtYWlsIjoiYWRtaW5AYWRtaW4uY29tIiwiaWF0IjoxNzAzNjI0NTMyLCJleHAiOjE3MDM2MjgxMzJ9.7paXVLMFu-ODKgxD1PS-r2S1JJRIz6KOEeRM9m-FkGk 12 | Content-Type: multipart/form-data; boundary=boundary 13 | 14 | --boundary 15 | Content-Disposition: form-data; full_name: "admin2" ; image: "test.jpg"; 16 | 17 | // The 'input.txt' file will be uploaded 18 | < ./test.jpg 19 | 20 | -------------------------------------------------------------------------------- /build.rs: -------------------------------------------------------------------------------- 1 | fn main() -> Result<(), Box> { 2 | 3 | let proto_root = "./proto"; 4 | let proto_files = &[ 5 | "echo.proto", 6 | "misc.proto", 7 | "auth.proto", 8 | "dashboard.proto", 9 | "admin_user.proto", 10 | "content.proto", 11 | "setting.proto", 12 | "cms.proto", 13 | "general.proto", 14 | "asset.proto" 15 | ]; 16 | 17 | tonic_build::configure() 18 | .out_dir("src/api/proto") 19 | .build_server(true) 20 | .compile_protos(proto_files, &[proto_root])?; 21 | Ok(()) 22 | } 23 | -------------------------------------------------------------------------------- /data/avored.db/.gitignore: -------------------------------------------------------------------------------- 1 | * -------------------------------------------------------------------------------- /proto/asset.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package asset; 3 | 4 | 5 | import "google/protobuf/timestamp.proto"; 6 | 7 | message FolderTypeMetaData { 8 | string color = 1; 9 | } 10 | 11 | message FileTypeMetaData { 12 | string file_type = 1; 13 | } 14 | 15 | message MetaDataType { 16 | optional FileTypeMetaData file_meta_data = 1; 17 | optional FolderTypeMetaData folder_meta_data = 2; 18 | } 19 | 20 | message AssetModel { 21 | string id = 1; 22 | optional string parent_id = 2; 23 | string name = 3; 24 | string new_path = 4; 25 | string asset_type = 5; 26 | MetaDataType metadata = 6; 27 | google.protobuf.Timestamp created_at = 7; 28 | google.protobuf.Timestamp updated_at = 8; 29 | string created_by = 9; 30 | string updated_by = 10; 31 | } 32 | 33 | 34 | 35 | 36 | message AssetPaginateRequest { 37 | optional int64 page = 1; 38 | optional string order = 2; 39 | } 40 | 41 | message AssetPaginateResponse { 42 | bool status = 1; 43 | 44 | message AssetPagination { 45 | int64 total = 1; 46 | } 47 | message AssetPaginateData { 48 | AssetPagination pagination = 1; 49 | repeated AssetModel data = 2; 50 | } 51 | 52 | AssetPaginateData data = 2; 53 | } 54 | 55 | message CreateFolderRequest { 56 | string name = 1; 57 | optional string parent_id = 2; 58 | } 59 | 60 | message CreateFolderResponse { 61 | bool status = 1; 62 | AssetModel data = 2; 63 | } 64 | 65 | message DeleteAssetRequest { 66 | string asset_id = 1; 67 | } 68 | 69 | message DeleteAssetResponse { 70 | bool status = 1; 71 | } 72 | 73 | 74 | message DeleteFolderRequest { 75 | string folder_id = 1; 76 | } 77 | 78 | message DeleteFolderResponse { 79 | bool status = 1; 80 | } 81 | 82 | message RenameAssetRequest { 83 | string asset_id = 1; 84 | string name = 2; 85 | } 86 | 87 | message RenameAssetResponse { 88 | bool status = 1; 89 | AssetModel data = 2; 90 | } 91 | 92 | service Asset { 93 | rpc Paginate(AssetPaginateRequest) returns (AssetPaginateResponse); 94 | rpc CreateFolder(CreateFolderRequest) returns (CreateFolderResponse); 95 | rpc DeleteAsset(DeleteAssetRequest) returns (DeleteAssetResponse); 96 | rpc DeleteFolder(DeleteFolderRequest) returns (DeleteFolderResponse); 97 | rpc RenameAsset(RenameAssetRequest) returns (RenameAssetResponse); 98 | } 99 | -------------------------------------------------------------------------------- /proto/auth.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package auth; 3 | 4 | // Login Service 5 | message LoginRequest { 6 | string email = 1; 7 | string password = 2; 8 | } 9 | message LoginResponse { 10 | bool status = 1; 11 | string data = 2; 12 | } 13 | 14 | 15 | message ForgotPasswordRequest { 16 | string email = 1; 17 | } 18 | 19 | message ForgotPasswordResponse { 20 | bool status = 1; 21 | } 22 | 23 | message ResetPasswordRequest { 24 | string email = 1; 25 | string password = 2; 26 | string confirm_password = 3; 27 | string token = 4; 28 | } 29 | message ResetPasswordResponse { 30 | bool status = 1; 31 | } 32 | 33 | service Auth { 34 | rpc Login(LoginRequest) returns (LoginResponse); 35 | rpc ForgotPassword(ForgotPasswordRequest) returns (ForgotPasswordResponse); 36 | rpc ResetPassword(ResetPasswordRequest) returns (ResetPasswordResponse); 37 | } 38 | -------------------------------------------------------------------------------- /proto/cms.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package cms; 4 | 5 | import "content.proto"; 6 | 7 | message GetCmsContentRequest { 8 | string content_identifier = 1; 9 | string content_type = 2; 10 | } 11 | 12 | message GetCmsContentResponse { 13 | bool status = 1; 14 | content.ContentModel data = 2; 15 | } 16 | 17 | message SentContactFormRequest { 18 | string first_name = 1; 19 | string last_name = 2; 20 | string email = 3; 21 | string phone = 4; 22 | string message = 5; 23 | } 24 | 25 | message SentContactFormResponse { 26 | bool status = 1; 27 | } 28 | 29 | service Cms { 30 | rpc GetCmsContent(GetCmsContentRequest) returns (GetCmsContentResponse); 31 | rpc SentContactForm(SentContactFormRequest) returns (SentContactFormResponse); 32 | } 33 | -------------------------------------------------------------------------------- /proto/dashboard.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package dashboard; 3 | 4 | // Dashboard API 5 | message DashboardRequest { 6 | } 7 | message DashboardResponse { 8 | bool status = 1; 9 | } 10 | 11 | service Dashboard { 12 | rpc Dashboard(DashboardRequest) returns (DashboardResponse); 13 | } 14 | -------------------------------------------------------------------------------- /proto/echo.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | package echo; 4 | 5 | service Test2 { 6 | rpc test2(Test2Request) returns (Test2Reply); 7 | } 8 | 9 | message Test2Request { } 10 | message Test2Reply { 11 | string message = 1; 12 | } -------------------------------------------------------------------------------- /proto/general.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package general; 3 | 4 | import 'admin_user.proto'; 5 | 6 | message LoggedInUserRequest {} 7 | message LoggedInUserResponse { 8 | bool status =1; 9 | admin_user.AdminUserModel data = 2; 10 | } 11 | 12 | 13 | service GeneralService { 14 | rpc LoggedInUser(LoggedInUserRequest) returns (LoggedInUserResponse); 15 | } 16 | -------------------------------------------------------------------------------- /proto/misc.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package misc; 3 | 4 | // Setup API 5 | message SetupRequest { 6 | string email = 1; 7 | string password = 2; 8 | } 9 | message SetupResponse { 10 | bool status = 1; 11 | } 12 | 13 | 14 | // Health Check API 15 | message HealthCheckRequest {} 16 | message HealthCheckResponse { 17 | bool status = 1; 18 | } 19 | message InstallDemoDataRequest{} 20 | message InstallDemoDataResponse{} 21 | message DeleteDemoDataRequest{} 22 | message DeleteDemoDataResponse{} 23 | 24 | 25 | service Misc { 26 | rpc Setup(SetupRequest) returns (SetupResponse); 27 | 28 | rpc HealthCheck(HealthCheckRequest) returns (HealthCheckResponse); 29 | rpc InstallDemoData(InstallDemoDataRequest) returns (InstallDemoDataResponse); 30 | rpc DeleteDemoData(DeleteDemoDataRequest) returns (DeleteDemoDataResponse); 31 | } 32 | -------------------------------------------------------------------------------- /proto/setting.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | package setting; 3 | 4 | import "google/protobuf/timestamp.proto"; 5 | 6 | message SettingModel { 7 | string id = 1; 8 | string identifier = 2; 9 | string value = 3; 10 | google.protobuf.Timestamp created_at = 4; 11 | google.protobuf.Timestamp updated_at = 5; 12 | string created_by = 6; 13 | string updated_by = 7; 14 | } 15 | 16 | 17 | message SettingSaveModel { 18 | string id = 1; 19 | string identifier = 2; 20 | string value = 3; 21 | } 22 | 23 | 24 | // Setting services 25 | message GetSettingRequest {} 26 | message GetSettingResponse { 27 | bool status = 1; 28 | repeated SettingModel data = 2; 29 | } 30 | 31 | message StoreSettingRequest { 32 | repeated SettingSaveModel data = 1; 33 | } 34 | message StoreSettingResponse { 35 | bool status = 1; 36 | } 37 | 38 | 39 | service Setting { 40 | rpc GetSetting(GetSettingRequest) returns (GetSettingResponse); 41 | rpc StoreSetting(StoreSettingRequest) returns (StoreSettingResponse); 42 | } 43 | -------------------------------------------------------------------------------- /public/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/public/images/favicon.ico -------------------------------------------------------------------------------- /public/images/locales/en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /public/images/locales/fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /public/log/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/public/log/.gitkeep -------------------------------------------------------------------------------- /public/upload/.gitkeep: -------------------------------------------------------------------------------- 1 | .gitkeep 2 | -------------------------------------------------------------------------------- /resources/locales/en.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "name", 3 | "identifier": "identifier", 4 | "full_name": "Full name", 5 | "email": "Email", 6 | "email_address_password_not_match": "Email address and password did not match.", 7 | "not_found": "The given %{attribute} not found in the system.", 8 | "email_address_not_valid": "Email address is not valid", 9 | "confirmation_password": "Confirm password", 10 | "password_did_not_match_confirmation_password": "Password did not match with confirm password.", 11 | "not_valid_password_reset_token": "Token provided to reset the password is not valid.", 12 | "password_match_error": "Your password did not match with current password.", 13 | "password": "Password", 14 | "confirm_password": "Confirm password", 15 | "current_password": "Current password", 16 | "current_not_same_as_new_password": "Current password is not same as new password", 17 | "token": "Token", 18 | "id": "Id", 19 | "value": "Value", 20 | "validation_required": "%{attribute} is a required field.", 21 | "validation_count": "The given %{attribute} has to be unique.", 22 | "email_password_not_matched": "Email and Password did not match.", 23 | "admin_user_forbidden": "You are not allowed to perform this request. Please check with your administrator." 24 | } 25 | -------------------------------------------------------------------------------- /resources/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "french" 3 | } 4 | -------------------------------------------------------------------------------- /src/api/cms_api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tonic::{async_trait, Request, Response, Status}; 3 | use crate::api::proto::cms::cms_server::Cms; 4 | use crate::api::proto::cms::{GetCmsContentRequest, GetCmsContentResponse, SentContactFormRequest, SentContactFormResponse}; 5 | use crate::avored_state::AvoRedState; 6 | 7 | pub struct CmsApi { 8 | pub state: Arc, 9 | } 10 | 11 | #[async_trait] 12 | impl Cms for CmsApi { 13 | async fn get_cms_content( 14 | &self, 15 | request: Request 16 | ) -> Result, Status> { 17 | 18 | println!("->> {:<12} - get_cms_content", "gRPC_Cms_Api_Service"); 19 | 20 | // let claims = request.extensions().get::().cloned().unwrap(); 21 | // let logged_in_user = claims.admin_user_model; 22 | // 23 | // let has_permission_bool = self.state 24 | // .admin_user_service 25 | // .has_permission(logged_in_user, String::from("get_cms_content")) 26 | // .await?; 27 | // if !has_permission_bool { 28 | // let status = Status::permission_denied("You don't have permission to access this resource"); 29 | // return Err(status); 30 | // } 31 | 32 | let req = request.into_inner(); 33 | 34 | match self. 35 | state. 36 | cms_service. 37 | get_cms_content( 38 | req, 39 | &self.state.db 40 | ).await { 41 | Ok(reply) => Ok(Response::new(reply)), 42 | Err(e) => Err(Status::internal(e.to_string())) 43 | } 44 | } 45 | 46 | async fn sent_contact_form( 47 | &self, 48 | request: Request 49 | ) -> Result, Status> { 50 | let req = request.into_inner(); 51 | 52 | match self. 53 | state. 54 | cms_service. 55 | sent_contact_form( 56 | &self.state.template, 57 | req 58 | ).await { 59 | Ok(reply) => Ok(Response::new(reply)), 60 | Err(e) => Err(Status::internal(e.to_string())) 61 | } 62 | } 63 | } -------------------------------------------------------------------------------- /src/api/dashboard_api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tonic::{async_trait, Request, Response, Status}; 3 | use crate::api::proto::dashboard::dashboard_server::Dashboard; 4 | use crate::api::proto::dashboard::{DashboardRequest, DashboardResponse}; 5 | use crate::avored_state::AvoRedState; 6 | use crate::models::token_claim_model::TokenClaims; 7 | 8 | pub struct DashboardApi { 9 | pub state: Arc, 10 | } 11 | 12 | #[async_trait] 13 | impl Dashboard for DashboardApi { 14 | async fn dashboard(&self, request: Request) -> Result, Status> { 15 | 16 | println!("->> {:<12} - dashboard", "gRPC_Dashboard_Api_Service"); 17 | 18 | let claims = request.extensions().get::().cloned().unwrap(); 19 | let logged_in_user = claims.admin_user_model; 20 | 21 | let has_permission_bool = self 22 | .state 23 | .admin_user_service 24 | .has_permission(logged_in_user, String::from("dashboard")) 25 | .await?; 26 | if !has_permission_bool { 27 | let status = 28 | Status::permission_denied("You don't have permission to access this resource"); 29 | return Err(status); 30 | } 31 | 32 | let reply = DashboardResponse { status: true }; 33 | Ok(Response::new(reply)) 34 | } 35 | } -------------------------------------------------------------------------------- /src/api/general_api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tonic::{async_trait, Response, Status}; 3 | use crate::api::proto::general::general_service_server::GeneralService; 4 | use crate::api::proto::general::{LoggedInUserRequest, LoggedInUserResponse}; 5 | use crate::avored_state::AvoRedState; 6 | use crate::models::token_claim_model::TokenClaims; 7 | 8 | pub struct GeneralApi { 9 | pub state: Arc, 10 | } 11 | 12 | #[async_trait] 13 | impl GeneralService for GeneralApi { 14 | async fn logged_in_user( 15 | &self, 16 | request: tonic::Request 17 | ) -> Result, tonic::Status> { 18 | println!("->> {:<12} - logged_in_user", "gRPC_General_Service"); 19 | let claims = request.extensions().get::().cloned().unwrap(); 20 | 21 | match self. 22 | state. 23 | general_service. 24 | logged_in_user( 25 | claims 26 | ).await { 27 | Ok(reply) => Ok(Response::new(reply)), 28 | Err(e) => Err(Status::internal(e.to_string())) 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /src/api/handlers/asset/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod request; 2 | pub mod store_asset_api_handler; 3 | -------------------------------------------------------------------------------- /src/api/handlers/asset/request/mod.rs: -------------------------------------------------------------------------------- 1 | // pub mod create_folder_request; 2 | // pub mod rename_asset_request; 3 | pub mod store_asset_request; 4 | // pub mod asset_table_request; 5 | -------------------------------------------------------------------------------- /src/api/handlers/asset/request/store_asset_request.rs: -------------------------------------------------------------------------------- 1 | use serde::Deserialize; 2 | 3 | #[derive(Deserialize, Debug, Clone)] 4 | pub struct StoreAssetRequest { 5 | pub parent_id: Option, 6 | } 7 | -------------------------------------------------------------------------------- /src/api/handlers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod asset; -------------------------------------------------------------------------------- /src/api/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod handlers; 2 | // pub mod rest_api_routes; 3 | pub mod misc_api; 4 | pub mod proto; 5 | pub mod test_api; 6 | pub mod auth_api; 7 | pub mod dashboard_api; 8 | pub mod admin_user_api; 9 | pub mod content_api; 10 | pub mod setting_api; 11 | 12 | pub mod cms_api; 13 | pub mod general_api; 14 | 15 | pub mod asset_api; 16 | -------------------------------------------------------------------------------- /src/api/proto/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod echo; 2 | pub mod misc; 3 | pub mod auth; 4 | pub mod admin_user; 5 | pub mod dashboard; 6 | pub mod content; 7 | pub mod setting; 8 | pub mod cms; 9 | pub mod general; 10 | 11 | pub mod asset; -------------------------------------------------------------------------------- /src/api/setting_api.rs: -------------------------------------------------------------------------------- 1 | use std::sync::Arc; 2 | use tonic::{async_trait, Response, Status}; 3 | use crate::api::proto::setting::{GetSettingRequest, GetSettingResponse, StoreSettingRequest, StoreSettingResponse}; 4 | use crate::api::proto::setting::setting_server::Setting; 5 | use crate::avored_state::AvoRedState; 6 | use crate::models::token_claim_model::TokenClaims; 7 | 8 | pub struct SettingApi { 9 | pub state: Arc, 10 | } 11 | 12 | #[async_trait] 13 | impl Setting for SettingApi { 14 | async fn get_setting( 15 | &self, 16 | request: tonic::Request 17 | ) -> Result, tonic::Status> { 18 | println!("->> {:<12} - get_setting", "gRPC_Setting_Service"); 19 | 20 | let claims = request.extensions().get::().cloned().unwrap(); 21 | let logged_in_user = claims.admin_user_model; 22 | 23 | let has_permission_bool = self.state 24 | .admin_user_service 25 | .has_permission(logged_in_user, String::from("get_setting")) 26 | .await?; 27 | if !has_permission_bool { 28 | let status = Status::permission_denied("You don't have permission to access this resource"); 29 | return Err(status); 30 | } 31 | 32 | match self. 33 | state. 34 | setting_service. 35 | get_setting( 36 | &self.state.db 37 | ).await { 38 | Ok(reply) => Ok(Response::new(reply)), 39 | Err(e) => Err(Status::internal(e.to_string())) 40 | } 41 | } 42 | 43 | async fn store_setting( 44 | &self, 45 | request: tonic::Request 46 | ) -> Result, tonic::Status> { 47 | println!("->> {:<12} - store_setting", "gRPC_Setting_Service"); 48 | 49 | let claims = request.extensions().get::().cloned().unwrap(); 50 | let logged_in_user = claims.admin_user_model; 51 | 52 | let has_permission_bool = self.state 53 | .admin_user_service 54 | .has_permission(logged_in_user, String::from("store_setting")) 55 | .await?; 56 | if !has_permission_bool { 57 | let status = Status::permission_denied("You don't have permission to access this resource"); 58 | return Err(status); 59 | } 60 | let claims = request.extensions().get::().cloned().unwrap(); 61 | let req = request.into_inner(); 62 | match self. 63 | state. 64 | setting_service. 65 | store_setting( 66 | &self.state.db, 67 | req, 68 | claims.email 69 | ).await { 70 | Ok(reply) => Ok(Response::new(reply)), 71 | Err(e) => Err(Status::internal(e.to_string())) 72 | } 73 | } 74 | } -------------------------------------------------------------------------------- /src/api/test_api.rs: -------------------------------------------------------------------------------- 1 | use tonic::{async_trait, Status}; 2 | use crate::api::proto::echo::test2_server::Test2; 3 | use crate::api::proto::echo::{Test2Reply, Test2Request}; 4 | 5 | pub struct Test2Api; 6 | 7 | #[async_trait] 8 | impl Test2 for Test2Api { 9 | async fn test2(&self, _request: tonic::Request) -> Result, Status> { 10 | Ok(tonic::Response::new(Test2Reply { 11 | message: "Hello, back!".to_string(), 12 | })) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/extensions/email_message_builder.rs: -------------------------------------------------------------------------------- 1 | use lettre::Message; 2 | use lettre::message::{header, MessageBuilder, MultiPart, SinglePart}; 3 | 4 | pub trait EmailMessageBuilder { 5 | fn build_email_message( 6 | &self, 7 | from_address: &str, 8 | to_address: &str, 9 | email_subject: &str, 10 | forgot_password_email_content: String, 11 | ) -> crate::error::Result; 12 | } 13 | 14 | 15 | impl EmailMessageBuilder for MessageBuilder { 16 | fn build_email_message( 17 | &self, 18 | from_address: &str, 19 | to_address: &str, 20 | email_subject: &str, 21 | forgot_password_email_content: String, 22 | ) -> crate::error::Result { 23 | let message = Message::builder() 24 | .from(from_address.parse()?) 25 | .to(to_address.parse()?) 26 | .subject(email_subject) 27 | .multipart( 28 | MultiPart::alternative() 29 | .singlepart( 30 | SinglePart::builder() 31 | .header(header::ContentType::TEXT_HTML) 32 | .body(forgot_password_email_content), 33 | ), 34 | )?; 35 | Ok(message) 36 | } 37 | } -------------------------------------------------------------------------------- /src/extensions/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod email_message_builder; -------------------------------------------------------------------------------- /src/middleware/grpc_auth_middleware.rs: -------------------------------------------------------------------------------- 1 | use std::env; 2 | use jsonwebtoken::{decode, DecodingKey, Validation}; 3 | use tonic::{Request, Status}; 4 | use crate::error::Error; 5 | use crate::models::token_claim_model::TokenClaims; 6 | 7 | pub fn check_auth(mut req: Request<()>) -> Result, Status> { 8 | 9 | match req.metadata().get("authorization") { 10 | Some(t) => { 11 | let auth_value = t.to_str() 12 | .map_err(|_e| Status::unavailable("authorization header value is not valid string"))?; 13 | 14 | let jwt_token = &env::var("AVORED_JWT_SECRET") 15 | .map_err(|_| Error::ConfigMissing("AVORED_JWT_SECRET".to_string()))?; 16 | let token = auth_value.strip_prefix("Bearer ").map(|auth| auth.to_owned()); 17 | let claims = decode::( 18 | &token.unwrap_or_default(), 19 | &DecodingKey::from_secret(jwt_token.as_ref()), 20 | &Validation::default(), 21 | ).map_err(|_| { 22 | Status::unauthenticated("No valid auth token claims found") 23 | })? 24 | .claims; 25 | req.extensions_mut().insert(claims); 26 | 27 | Ok(req) 28 | }, 29 | _ => Err(Status::unauthenticated("No valid auth token")), 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/middleware/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod require_jwt_authentication; 2 | // pub mod validate_cms_authentication; 3 | 4 | pub mod grpc_auth_middleware; 5 | -------------------------------------------------------------------------------- /src/middleware/validate_cms_authentication.rs: -------------------------------------------------------------------------------- 1 | use crate::avored_state::AvoRedState; 2 | use crate::middleware::require_jwt_authentication::ErrorResponse; 3 | use crate::models::setting_model::SettingModel; 4 | use axum::body::Body; 5 | use axum::extract::State; 6 | use axum::http::{header, Request, StatusCode}; 7 | use axum::middleware::Next; 8 | use axum::response::IntoResponse; 9 | use axum::Json; 10 | use axum_extra::extract::CookieJar; 11 | use std::sync::Arc; 12 | 13 | pub async fn validate_cms_authentication( 14 | state: State>, 15 | cookie_jar: CookieJar, 16 | req: Request, 17 | next: Next, 18 | ) -> Result)> { 19 | let token = cookie_jar 20 | .get("token") 21 | .map(|cookie| cookie.value().to_string()) 22 | .or_else(|| { 23 | req.headers() 24 | .get(header::AUTHORIZATION) 25 | .and_then(|auth_header| auth_header.to_str().ok()) 26 | .and_then(|auth_value| { 27 | if auth_value.starts_with("Bearer ") { 28 | match auth_value.strip_prefix("Bearer ") { 29 | Some(auth) => Some(auth.to_owned()), 30 | _ => None, 31 | } 32 | } else { 33 | None 34 | } 35 | }) 36 | }); 37 | 38 | let token = token.ok_or_else(|| { 39 | let json_error = ErrorResponse { 40 | status: false, 41 | message: "please provide token".to_string(), 42 | }; 43 | (StatusCode::UNAUTHORIZED, Json(json_error)) 44 | })?; 45 | let cms_token_setting_model = state 46 | .setting_service 47 | .find_by_identifier(&state.db, String::from("auth_cms_token")) 48 | .await 49 | .unwrap_or_else(|_err| SettingModel::default()); 50 | 51 | if cms_token_setting_model.value.is_empty() | cms_token_setting_model.value.ne(&token) { 52 | let json_error = ErrorResponse { 53 | status: false, 54 | message: "please provide valid token".to_string(), 55 | }; 56 | return Err((StatusCode::UNAUTHORIZED, Json(json_error))); 57 | }; 58 | 59 | Ok(next.run(req).await) 60 | } 61 | -------------------------------------------------------------------------------- /src/models/password_rest_model.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use crate::models::BaseModel; 3 | use serde::{Deserialize, Serialize}; 4 | use surrealdb::sql::{Datetime, Object}; 5 | 6 | #[derive(Serialize, Debug, Deserialize, Clone, Default, PartialEq)] 7 | pub struct PasswordResetModel { 8 | pub id: String, 9 | pub email: String, 10 | pub token: String, 11 | pub status: PasswordResetTokenStatus, 12 | pub created_at: Datetime, 13 | } 14 | 15 | #[derive(Serialize, Default)] 16 | pub struct ForgotPasswordViewModel { 17 | pub link: String, 18 | } 19 | 20 | #[derive(Serialize, Debug, Deserialize, Clone, PartialEq, Default)] 21 | pub enum PasswordResetTokenStatus { 22 | Active, 23 | #[default] 24 | Expire, 25 | } 26 | 27 | impl TryFrom for PasswordResetModel { 28 | type Error = Error; 29 | fn try_from(val: Object) -> Result { 30 | let id = val.get("id").get_id()?; 31 | let email = val.get("email").get_string()?; 32 | let token = val.get("token").get_string()?; 33 | let created_at = val.get("created_at").get_datetime()?; 34 | let status = match val.get("status").get_string()?.as_str() { 35 | "Active" => PasswordResetTokenStatus::Active, 36 | "Expire" => PasswordResetTokenStatus::Active, 37 | _ => PasswordResetTokenStatus::Expire, 38 | }; 39 | 40 | Ok(PasswordResetModel { 41 | id, 42 | email, 43 | token, 44 | created_at, 45 | status, 46 | }) 47 | } 48 | } 49 | 50 | #[derive(Serialize, Debug, Deserialize, Clone, Default)] 51 | pub struct CreatablePasswordResetModel { 52 | pub email: String, 53 | pub token: String, 54 | } 55 | -------------------------------------------------------------------------------- /src/models/setting_model.rs: -------------------------------------------------------------------------------- 1 | use std::time::SystemTime; 2 | use prost_types::Timestamp; 3 | use crate::error::{Error, Result}; 4 | use crate::models::BaseModel; 5 | use serde::{Deserialize, Serialize}; 6 | use surrealdb::sql::{Datetime, Object}; 7 | 8 | #[derive(Serialize, Debug, Deserialize, Clone, Default)] 9 | pub struct SettingModel { 10 | pub id: String, 11 | pub identifier: String, 12 | pub value: String, 13 | pub created_at: Datetime, 14 | pub updated_at: Datetime, 15 | pub created_by: String, 16 | pub updated_by: String, 17 | } 18 | 19 | impl TryFrom for SettingModel { 20 | type Error = Error; 21 | fn try_from(val: Object) -> Result { 22 | let id = val.get("id").get_id()?; 23 | let identifier = val.get("identifier").get_string()?; 24 | let value = val.get("value").get_string()?; 25 | let created_at = val.get("created_at").get_datetime()?; 26 | let updated_at = val.get("updated_at").get_datetime()?; 27 | let created_by = val.get("created_by").get_string()?; 28 | let updated_by = val.get("updated_by").get_string()?; 29 | 30 | Ok(SettingModel { 31 | id, 32 | identifier, 33 | value, 34 | created_at, 35 | updated_at, 36 | created_by, 37 | updated_by, 38 | }) 39 | } 40 | } 41 | 42 | impl TryFrom for crate::api::proto::setting::SettingModel { 43 | type Error = Error; 44 | 45 | fn try_from(val: SettingModel) -> Result { 46 | let chrono_utc_created_at= val.created_at.to_utc(); 47 | let system_time_created_at = SystemTime::from(chrono_utc_created_at); 48 | let created_at = Timestamp::from(system_time_created_at); 49 | 50 | let chrono_utc_updated_at= val.updated_at.to_utc(); 51 | let system_time_updated_at = SystemTime::from(chrono_utc_updated_at); 52 | let updated_at = Timestamp::from(system_time_updated_at); 53 | 54 | let model = crate::api::proto::setting::SettingModel { 55 | id: val.id, 56 | value: val.value, 57 | identifier: val.identifier, 58 | created_at: Option::from(created_at), 59 | updated_at: Option::from(updated_at), 60 | created_by: val.created_by, 61 | updated_by: val.updated_by, 62 | }; 63 | 64 | Ok(model) 65 | } 66 | } 67 | 68 | 69 | // 70 | 71 | 72 | #[derive(Serialize, Debug, Deserialize, Clone, Default)] 73 | pub struct UpdatableSettingModel { 74 | pub id: String, 75 | pub value: String, 76 | pub logged_in_username: String, 77 | } 78 | -------------------------------------------------------------------------------- /src/models/token_claim_model.rs: -------------------------------------------------------------------------------- 1 | use crate::models::admin_user_model::AdminUserModel; 2 | use serde::{Deserialize, Serialize}; 3 | 4 | #[derive(Debug, Serialize, Deserialize, Clone, Default)] 5 | pub struct TokenClaims { 6 | pub sub: String, 7 | pub name: String, 8 | pub email: String, 9 | pub admin_user_model: AdminUserModel, 10 | pub iat: usize, 11 | pub exp: usize, 12 | } 13 | 14 | #[derive(Debug, Serialize, Deserialize, Clone)] 15 | pub struct LoggedInUser { 16 | pub id: String, 17 | pub name: String, 18 | pub email: String, 19 | pub demo_data_status: bool, 20 | pub admin_user_model: AdminUserModel, 21 | } 22 | -------------------------------------------------------------------------------- /src/models/validation_error.rs: -------------------------------------------------------------------------------- 1 | use email_address::EmailAddress; 2 | use serde::Serialize; 3 | 4 | #[derive(Debug, Serialize, Clone)] 5 | pub struct ErrorMessage { 6 | pub key: String, 7 | pub message: String, 8 | } 9 | 10 | #[derive(Debug, Serialize, Clone)] 11 | pub struct ErrorResponse { 12 | pub status: bool, 13 | pub errors: Vec, 14 | } 15 | 16 | pub trait Validate { 17 | fn required(&self) -> crate::error::Result; 18 | 19 | fn validate_email(&self) -> crate::error::Result; 20 | } 21 | 22 | impl Validate for String { 23 | fn required(&self) -> crate::error::Result { 24 | if !self.is_empty() { 25 | return Ok(true); 26 | } 27 | Ok(false) 28 | } 29 | 30 | fn validate_email(&self) -> crate::error::Result { 31 | if !EmailAddress::is_valid(self) { 32 | return Ok(false); 33 | } 34 | Ok(true) 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/providers/avored_config_provider.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use dotenvy::dotenv; 3 | use std::env; 4 | 5 | #[derive(Debug, Clone)] 6 | pub struct AvoRedConfigProvider { 7 | pub database_folder_name: String, 8 | pub database_namespace: String, 9 | pub database_name: String, 10 | pub jwt_secret_key: String, 11 | pub react_admin_app_url: String, 12 | // pub react_frontend_app_url: String, 13 | // pub back_end_app_url: String, 14 | pub cors_allowed_app_url: Vec, 15 | pub password_salt: String, 16 | pub smtp_host: String, 17 | pub smtp_username: String, 18 | pub smtp_password: String, 19 | pub smtp_port: u16, 20 | } 21 | 22 | // pub fn config() -> &'static AvoRedConfigProvider { 23 | // static INSTANCE: OnceLock = OnceLock::new(); 24 | // 25 | // INSTANCE.get_or_init(|| { 26 | // AvoRedConfigProvider::register() 27 | // .unwrap_or_else(|ex| panic!("FATAL - WHILE LOADING CONF - Cause: {ex:?}")) 28 | // }) 29 | // } 30 | 31 | impl AvoRedConfigProvider { 32 | pub fn register() -> Result { 33 | dotenv()?; 34 | 35 | match get_env("APP_ENV")?.as_str() { 36 | "prod" => dotenvy::from_filename_override(".env.prod")?, 37 | "stag" => dotenvy::from_filename_override(".env.stag")?, 38 | "test" => dotenvy::from_filename_override(".env.test")?, 39 | "dev" => dotenvy::from_filename_override(".env.dev")?, 40 | // as if it won't match any we load dev as default 41 | _ => dotenvy::from_filename_override(".env")?, 42 | }; 43 | 44 | let env_str_allowed_cors = get_env("AVORED_CORS_ALLOWED_APP_URL")?; 45 | let vec_cors_urls = env_str_allowed_cors.split(',').collect::>(); 46 | let cors_urls = vec_cors_urls.iter().map(|url| url.to_string()).collect(); 47 | 48 | Ok(AvoRedConfigProvider { 49 | database_folder_name: get_env("AVORED_DATABASE_FOLDER_NAME")?, 50 | database_namespace: get_env("AVORED_DATABASE_NAMESPACE")?, 51 | database_name: get_env("AVORED_DATABASE_NAME")?, 52 | jwt_secret_key: get_env("AVORED_JWT_SECRET")?, 53 | react_admin_app_url: get_env("AVORED_REACT_ADMIN_APP_URL")?, 54 | // react_frontend_app_url: get_env("AVORED_REACT_FRONTEND_APP_URL")?, 55 | // back_end_app_url: get_env("AVORED_BACK_END_APP_URL")?, 56 | cors_allowed_app_url: cors_urls, 57 | password_salt: get_env("AVORED_PASSWORD_SALT")?, 58 | smtp_host: get_env("SMTP_HOST")?, 59 | smtp_username: get_env("SMTP_USERNAME")?, 60 | smtp_password: get_env("SMTP_PASSWORD")?, 61 | smtp_port: get_env("SMTP_PORT")?.parse::()?, 62 | }) 63 | } 64 | } 65 | 66 | fn get_env(name: &'static str) -> Result { 67 | env::var(name).map_err(|_| Error::ConfigMissing(name.to_string())) 68 | } 69 | -------------------------------------------------------------------------------- /src/providers/avored_database_provider.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::providers::avored_config_provider::AvoRedConfigProvider; 3 | use surrealdb::dbs::Session; 4 | use surrealdb::kvs::Datastore; 5 | 6 | pub type DB = (Datastore, Session); 7 | 8 | pub struct AvoRedDatabaseProvider { 9 | pub db: DB, 10 | } 11 | 12 | impl AvoRedDatabaseProvider { 13 | pub async fn register(config: AvoRedConfigProvider) -> Result { 14 | let folder_name = config.database_folder_name; 15 | let datastore = Datastore::new(&folder_name) 16 | .await 17 | .expect("there is issue with connecting with data/avored.db storage"); 18 | 19 | println!( 20 | "ns:{} db: {}", 21 | config.database_namespace.clone(), 22 | config.database_name.clone() 23 | ); 24 | let database_session = Session::default() 25 | .with_ns(&config.database_namespace) 26 | .with_db(&config.database_name); 27 | 28 | let db = (datastore, database_session); 29 | 30 | Ok(AvoRedDatabaseProvider { db }) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/providers/avored_template_provider.rs: -------------------------------------------------------------------------------- 1 | use crate::error::Result; 2 | use crate::providers::avored_config_provider::AvoRedConfigProvider; 3 | use handlebars::Handlebars; 4 | use lettre::transport::smtp::authentication::Credentials; 5 | use lettre::{AsyncSmtpTransport, Tokio1Executor}; 6 | 7 | pub struct AvoRedTemplateProvider { 8 | pub handlebars: Handlebars<'static>, 9 | pub mailer: AsyncSmtpTransport, 10 | } 11 | 12 | impl AvoRedTemplateProvider { 13 | pub async fn register(config: AvoRedConfigProvider) -> Result { 14 | let mut reg = Handlebars::new(); 15 | reg.register_template_file("forgot-password", "./resources/mail/forgot-password.hbs")?; 16 | reg.register_template_file("contact-us-email", "./resources/mail/contact-us-email.hbs")?; 17 | 18 | println!("{:?}", config.smtp_username); 19 | let creds = Credentials::new(config.smtp_username, config.smtp_password); 20 | 21 | let mailer: AsyncSmtpTransport = 22 | AsyncSmtpTransport::::starttls_relay(&config.smtp_host) 23 | .unwrap() 24 | .port(config.smtp_port) 25 | .credentials(creds) 26 | .build(); 27 | 28 | Ok(AvoRedTemplateProvider { 29 | handlebars: reg, 30 | mailer, 31 | }) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/providers/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod avored_config_provider; 2 | pub mod avored_database_provider; 3 | pub mod avored_template_provider; 4 | -------------------------------------------------------------------------------- /src/repositories/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::error::{Error, Result}; 2 | use surrealdb::dbs::Response; 3 | use surrealdb::sql::{Object, Value}; 4 | 5 | pub mod admin_user_repository; 6 | 7 | pub mod password_reset_repository; 8 | pub mod role_repository; 9 | pub mod asset_repository; 10 | pub mod collection_repository; 11 | pub mod setting_repository; 12 | pub mod content_repository; 13 | 14 | pub fn into_iter_objects(responses: Vec) -> Result>> { 15 | let response = responses 16 | .into_iter() 17 | .next() 18 | .map(|rp| rp.result) 19 | .transpose()?; 20 | 21 | match response { 22 | Some(Value::Array(arr)) => { 23 | let it = arr.into_iter().map(|v| match v { 24 | Value::Object(object) => Ok(object), 25 | _ => Err(Error::Generic("empty object".to_string())), 26 | }); 27 | 28 | Ok(it) 29 | } 30 | _ => Err(Error::Generic("No Record found".to_string())), 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/requests/admin_user_request/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod store_admin_user_request; 2 | pub mod store_role_request; 3 | pub mod update_role_request; -------------------------------------------------------------------------------- /src/requests/admin_user_request/store_admin_user_request.rs: -------------------------------------------------------------------------------- 1 | use email_address::EmailAddress; 2 | use rust_i18n::t; 3 | use crate::api::proto::admin_user::StoreAdminUserRequest; 4 | use crate::avored_state::AvoRedState; 5 | use crate::models::validation_error::{ErrorMessage, ErrorResponse}; 6 | 7 | impl StoreAdminUserRequest { 8 | pub async fn validate(&self, state: &AvoRedState) -> crate::error::Result<(bool, String)> { 9 | let mut errors: Vec = vec![]; 10 | let mut valid = true; 11 | 12 | if self.email.is_empty() { 13 | let error_message = ErrorMessage { 14 | key: String::from("email"), 15 | message: t!("validation_required", attribute = t!("email")).to_string(), 16 | }; 17 | valid = false; 18 | errors.push(error_message); 19 | } 20 | 21 | if !EmailAddress::is_valid(&self.email) { 22 | let error_message = ErrorMessage { 23 | key: String::from("email"), 24 | message: t!("email_address_not_valid").to_string(), 25 | }; 26 | 27 | valid = false; 28 | errors.push(error_message); 29 | } 30 | 31 | let admin_user_model = state 32 | .admin_user_service 33 | .count_of_email(&state.db, self.email.clone()) 34 | .await?; 35 | 36 | if admin_user_model.total > 0 { 37 | let error_message = ErrorMessage { 38 | key: String::from("email"), 39 | message: t!("validation_count", attribute = t!("email")).to_string(), 40 | }; 41 | 42 | errors.push(error_message); 43 | } 44 | 45 | // if profile photo exist then certain type of photo is only allowed 46 | 47 | if self.password.is_empty() { 48 | let error_message = ErrorMessage { 49 | key: String::from("password"), 50 | message: t!("validation_required", attribute = t!("password")).to_string(), 51 | }; 52 | 53 | valid = false; 54 | errors.push(error_message); 55 | } 56 | 57 | if self.password != self.confirm_password { 58 | let error_message = ErrorMessage { 59 | key: String::from("password"), 60 | message: t!("password_match_error").to_string(), 61 | }; 62 | 63 | valid = false; 64 | errors.push(error_message); 65 | } 66 | 67 | let error_response = ErrorResponse { 68 | status: valid, 69 | errors, 70 | }; 71 | 72 | let error_string = serde_json::to_string(&error_response)?; 73 | 74 | 75 | Ok((valid ,error_string)) 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/requests/admin_user_request/store_role_request.rs: -------------------------------------------------------------------------------- 1 | use rust_i18n::t; 2 | use crate::api::proto::admin_user::StoreRoleRequest; 3 | use crate::avored_state::AvoRedState; 4 | use crate::models::validation_error::{ErrorMessage, ErrorResponse}; 5 | 6 | impl StoreRoleRequest { 7 | pub async fn validate(&self, state: &AvoRedState) -> crate::error::Result<(bool, String)> { 8 | let mut errors: Vec = vec![]; 9 | let mut valid = true; 10 | 11 | if self.name.is_empty() { 12 | let error_message = ErrorMessage { 13 | key: String::from("name"), 14 | message: t!("validation_required", attribute = t!("name")).to_string(), 15 | }; 16 | 17 | valid = false; 18 | errors.push(error_message); 19 | } 20 | 21 | if self.identifier.is_empty() { 22 | let error_message = ErrorMessage { 23 | key: String::from("identifier"), 24 | message: t!("validation_required", attribute = t!("identifier")).to_string(), 25 | }; 26 | 27 | valid = false; 28 | errors.push(error_message); 29 | } 30 | 31 | let role_identifier_count = state 32 | .admin_user_service 33 | .count_of_role_identifier(&state.db, &self.identifier.clone()) 34 | .await?; 35 | 36 | if role_identifier_count.total > 0 { 37 | let error_message = ErrorMessage { 38 | key: String::from("email"), 39 | message: t!("validation_count", attribute = t!("email")).to_string(), 40 | }; 41 | 42 | valid = false; 43 | errors.push(error_message); 44 | } 45 | 46 | 47 | let error_response = ErrorResponse { 48 | status: valid, 49 | errors, 50 | }; 51 | 52 | let error_string = serde_json::to_string(&error_response)?; 53 | 54 | 55 | Ok((valid ,error_string)) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/requests/admin_user_request/update_role_request.rs: -------------------------------------------------------------------------------- 1 | use rust_i18n::t; 2 | use crate::api::proto::admin_user::UpdateRoleRequest; 3 | use crate::models::validation_error::{ErrorMessage, ErrorResponse}; 4 | 5 | impl UpdateRoleRequest { 6 | pub fn validate(&self) -> crate::error::Result<(bool, String)> { 7 | let mut errors: Vec = vec![]; 8 | let mut valid = true; 9 | 10 | if self.name.is_empty() { 11 | let error_message = ErrorMessage { 12 | key: String::from("name"), 13 | message: t!("validation_required", attribute = t!("name")).to_string(), 14 | }; 15 | 16 | valid = false; 17 | errors.push(error_message); 18 | } 19 | 20 | 21 | let error_response = ErrorResponse { 22 | status: valid, 23 | errors, 24 | }; 25 | 26 | let error_string = serde_json::to_string(&error_response)?; 27 | 28 | 29 | Ok((valid ,error_string)) 30 | } 31 | } -------------------------------------------------------------------------------- /src/requests/auth_request/forgot_password_request.rs: -------------------------------------------------------------------------------- 1 | use email_address::EmailAddress; 2 | use rust_i18n::t; 3 | use crate::api::proto::auth::ForgotPasswordRequest; 4 | use crate::avored_state::AvoRedState; 5 | use crate::models::validation_error::{ErrorMessage, ErrorResponse}; 6 | 7 | impl ForgotPasswordRequest { 8 | pub async fn validate(&self, state: &AvoRedState) -> crate::error::Result<(bool, String)> { 9 | let mut errors: Vec = vec![]; 10 | let mut valid = true; 11 | 12 | if self.email.is_empty() { 13 | let error_message = ErrorMessage { 14 | key: String::from("email"), 15 | message: t!("validation_required", attribute = t!("email")).to_string(), 16 | }; 17 | valid = false; 18 | errors.push(error_message); 19 | } 20 | 21 | if !EmailAddress::is_valid(&self.email) { 22 | let error_message = ErrorMessage { 23 | key: String::from("email"), 24 | message: t!("email_address_not_valid").to_string(), 25 | }; 26 | 27 | valid = false; 28 | errors.push(error_message); 29 | } 30 | 31 | let admin_user_model = state 32 | .admin_user_service 33 | .count_of_email(&state.db, self.email.clone()) 34 | .await?; 35 | 36 | 37 | if admin_user_model.total != 1 { 38 | let error_message = ErrorMessage { 39 | key: String::from("email"), 40 | message: t!("not_found", attribute = t!("email")).to_string(), 41 | }; 42 | valid = false; 43 | errors.push(error_message); 44 | } 45 | 46 | 47 | 48 | let error_response = ErrorResponse { 49 | status: false, 50 | errors, 51 | }; 52 | 53 | let error_string = serde_json::to_string(&error_response)?; 54 | 55 | 56 | Ok((valid ,error_string)) 57 | } 58 | } -------------------------------------------------------------------------------- /src/requests/auth_request/login_request.rs: -------------------------------------------------------------------------------- 1 | use rust_i18n::t; 2 | use crate::models::validation_error::{ErrorMessage, ErrorResponse, Validate}; 3 | use crate::api::proto::auth::LoginRequest; 4 | 5 | impl LoginRequest { 6 | pub fn validate(&self) -> crate::error::Result<(bool, String)> { 7 | let mut errors: Vec = vec![]; 8 | let mut valid = true; 9 | 10 | if !self.email.required()? { 11 | let error_message = ErrorMessage { 12 | key: String::from("email"), 13 | message: t!("validation_required", attribute = t!("email")).to_string(), 14 | }; 15 | valid = false; 16 | errors.push(error_message); 17 | } 18 | 19 | if !self.email.validate_email()? { 20 | let error_message = ErrorMessage { 21 | key: String::from("email"), 22 | message: t!("email_address_not_valid").to_string(), 23 | }; 24 | 25 | valid = false; 26 | errors.push(error_message); 27 | } 28 | 29 | if !self.password.required()? { 30 | let error_message = ErrorMessage { 31 | key: String::from("password"), 32 | message: t!("validation_required", attribute = t!("password")).to_string(), 33 | }; 34 | 35 | valid = false; 36 | errors.push(error_message); 37 | } 38 | 39 | let error_response = ErrorResponse { 40 | status: false, 41 | errors, 42 | }; 43 | 44 | let error_string = serde_json::to_string(&error_response)?; 45 | 46 | 47 | Ok((valid ,error_string)) 48 | } 49 | 50 | } -------------------------------------------------------------------------------- /src/requests/auth_request/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod login_request; 2 | pub mod forgot_password_request; 3 | pub mod reset_password_request; -------------------------------------------------------------------------------- /src/requests/auth_request/reset_password_request.rs: -------------------------------------------------------------------------------- 1 | use email_address::EmailAddress; 2 | use rust_i18n::t; 3 | use crate::api::proto::auth::ResetPasswordRequest; 4 | use crate::avored_state::AvoRedState; 5 | use crate::models::validation_error::{ErrorMessage, ErrorResponse}; 6 | 7 | impl ResetPasswordRequest { 8 | pub async fn validate(&self, state: &AvoRedState) -> crate::error::Result<(bool, String)> { 9 | let mut errors: Vec = vec![]; 10 | let mut valid = true; 11 | 12 | if self.email.is_empty() { 13 | let error_message = ErrorMessage { 14 | key: String::from("email"), 15 | message: t!("validation_required", attribute = t!("email")).to_string(), 16 | }; 17 | valid = false; 18 | errors.push(error_message); 19 | } 20 | 21 | if !EmailAddress::is_valid(&self.email) { 22 | let error_message = ErrorMessage { 23 | key: String::from("email"), 24 | message: t!("email_address_not_valid").to_string(), 25 | }; 26 | 27 | valid = false; 28 | errors.push(error_message); 29 | } 30 | 31 | if self.password.is_empty() { 32 | let error_message = ErrorMessage { 33 | key: String::from("password"), 34 | message: t!("validation_required", attribute = t!("password")).to_string(), 35 | }; 36 | 37 | valid = false; 38 | errors.push(error_message); 39 | } 40 | 41 | if self.password != self.confirm_password { 42 | let error_message = ErrorMessage { 43 | key: String::from("password"), 44 | message: t!("password_match_error").to_string(), 45 | }; 46 | 47 | valid = false; 48 | errors.push(error_message); 49 | } 50 | 51 | let validated_token_result = state 52 | .auth_service 53 | .validate_token( 54 | &self.token, 55 | &self.email, 56 | &state.db, 57 | ).await?; 58 | 59 | if !validated_token_result { 60 | let error_message = ErrorMessage { 61 | key: String::from("email"), 62 | message: t!("not_valid_password_reset_token").to_string(), 63 | }; 64 | 65 | valid = false; 66 | errors.push(error_message); 67 | } 68 | 69 | let error_response = ErrorResponse { 70 | status: false, 71 | errors, 72 | }; 73 | 74 | let error_string = serde_json::to_string(&error_response)?; 75 | 76 | 77 | Ok((valid ,error_string)) 78 | } 79 | } -------------------------------------------------------------------------------- /src/requests/misc_request/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod setup_request; 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/requests/misc_request/setup_request.rs: -------------------------------------------------------------------------------- 1 | use crate::api::proto::misc::SetupRequest; 2 | 3 | impl SetupRequest { 4 | // pub fn validate(&self) -> Result<(bool, String)> { 5 | // let mut errors: Vec = vec![]; 6 | // let mut valid = true; 7 | // 8 | // if self.email.is_empty() { 9 | // let error_message = ErrorMessage { 10 | // key: String::from("email"), 11 | // message: t!("validation_required", attribute = t!("email")).to_string(), 12 | // }; 13 | // valid = false; 14 | // errors.push(error_message); 15 | // } 16 | // 17 | // if !EmailAddress::is_valid(&self.email) { 18 | // let error_message = ErrorMessage { 19 | // key: String::from("email"), 20 | // message: t!("email_address_not_valid").to_string(), 21 | // }; 22 | // 23 | // valid = false; 24 | // errors.push(error_message); 25 | // } 26 | // 27 | // if self.password.is_empty() { 28 | // let error_message = ErrorMessage { 29 | // key: String::from("password"), 30 | // message: t!("validation_required", attribute = t!("password")).to_string(), 31 | // }; 32 | // 33 | // valid = false; 34 | // errors.push(error_message); 35 | // } 36 | // 37 | // let error_response = ErrorResponse { 38 | // status: false, 39 | // errors, 40 | // }; 41 | // 42 | // let error_string = serde_json::to_string(&error_response)?; 43 | // 44 | // 45 | // Ok((valid ,error_string)) 46 | // } 47 | 48 | } -------------------------------------------------------------------------------- /src/requests/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod misc_request; 2 | pub mod auth_request; 3 | pub mod admin_user_request; -------------------------------------------------------------------------------- /src/services/general_service.rs: -------------------------------------------------------------------------------- 1 | use crate::api::proto::admin_user::AdminUserModel as GrpcAdminUserModel; 2 | use crate::api::proto::general::{LoggedInUserResponse}; 3 | use crate::models::token_claim_model::TokenClaims; 4 | 5 | pub struct GeneralService { 6 | 7 | } 8 | 9 | impl GeneralService { 10 | pub async fn logged_in_user( 11 | &self, 12 | claims: TokenClaims, 13 | ) -> crate::error::Result { 14 | let logged_in_user = claims.admin_user_model; 15 | 16 | let model: GrpcAdminUserModel = logged_in_user.try_into()?; 17 | 18 | let logged_in_user = LoggedInUserResponse { 19 | status: true, 20 | data: Some(model) 21 | }; 22 | 23 | Ok(logged_in_user) 24 | } 25 | } 26 | 27 | impl GeneralService { 28 | pub fn new() -> crate::error::Result { 29 | Ok(GeneralService {}) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/services/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod misc_service; 2 | pub mod auth_service; 3 | pub mod admin_user_service; 4 | pub mod content_service; 5 | pub mod asset_service; 6 | pub mod setting_service; 7 | pub mod cms_service; 8 | pub mod general_service; 9 | -------------------------------------------------------------------------------- /src/services/setting_service.rs: -------------------------------------------------------------------------------- 1 | use crate::models::setting_model::{UpdatableSettingModel}; 2 | use crate::api::proto::setting::{SettingModel as SettingModelGrpc, StoreSettingRequest, StoreSettingResponse}; 3 | use crate::providers::avored_database_provider::DB; 4 | use crate::{error::Result, repositories::setting_repository::SettingRepository}; 5 | use crate::api::proto::setting::GetSettingResponse; 6 | 7 | pub struct SettingService { 8 | setting_repository: SettingRepository, 9 | } 10 | 11 | impl SettingService { 12 | pub fn new(setting_repository: SettingRepository) -> Result { 13 | Ok(SettingService { setting_repository }) 14 | } 15 | 16 | pub async fn get_setting(&self, (datastore, database_session): &DB) -> Result { 17 | let models = self.setting_repository 18 | .all(datastore, database_session) 19 | .await?; 20 | 21 | let mut setting_grpc_models = vec![]; 22 | for model in models { 23 | let setting_grpc_model: SettingModelGrpc = model.try_into()?; 24 | setting_grpc_models.push(setting_grpc_model); 25 | } 26 | 27 | let res = GetSettingResponse { 28 | status: true, 29 | data: setting_grpc_models, 30 | }; 31 | 32 | Ok(res) 33 | } 34 | 35 | pub async fn store_setting( 36 | &self, 37 | (datastore, database_session): &DB, 38 | request: StoreSettingRequest, 39 | email: String, 40 | ) -> Result { 41 | 42 | for setting in request.data { 43 | let updatable_setting_model = UpdatableSettingModel { 44 | id: setting.id, 45 | value: setting.value, 46 | logged_in_username: email.clone(), 47 | }; 48 | self.setting_repository 49 | .update_setting(datastore, database_session, updatable_setting_model) 50 | .await?; 51 | } 52 | 53 | let res = StoreSettingResponse { 54 | status: true 55 | }; 56 | 57 | Ok(res) 58 | } 59 | } 60 | impl SettingService {} 61 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/.env: -------------------------------------------------------------------------------- 1 | REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | 4 | 5 | #REACT_APP_FRONTEND_BASE_URL=https://avored.com 6 | #REACT_APP_BACKEND_BASE_URL=https://api.avored.com 7 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/.env.production.local: -------------------------------------------------------------------------------- 1 | #REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | #REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | 4 | 5 | REACT_APP_FRONTEND_BASE_URL=https://demo-admin.avored.com 6 | REACT_APP_BACKEND_BASE_URL=https://demo-api.avored.com 7 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/README.md: -------------------------------------------------------------------------------- 1 | ### Avored rust grpc cms 2 | 3 | Generate as Typescript CLI 4 | 5 | protoc --proto_path=../proto/ auth.proto \ 6 | --grpc-web_out=import_style=typescript,mode=grpcweb:src/grpc_generated \ 7 | --js_out=import_style=commonjs,binary:src/grpc_generated -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.7047f949.css", 4 | "main.js": "/static/js/main.754b7e0e.js", 5 | "static/js/453.ebcd728c.chunk.js": "/static/js/453.ebcd728c.chunk.js", 6 | "static/media/marked.cjs": "/static/media/marked.9d11e79e65a241a47955.cjs", 7 | "static/media/logo_only.svg": "/static/media/logo_only.844fbcaa6b01372b345470d497f050fa.svg", 8 | "index.html": "/index.html", 9 | "static/media/en.svg": "/static/media/en.9acbcf1017616e2140a1935111c50f28.svg", 10 | "main.7047f949.css.map": "/static/css/main.7047f949.css.map", 11 | "main.754b7e0e.js.map": "/static/js/main.754b7e0e.js.map", 12 | "453.ebcd728c.chunk.js.map": "/static/js/453.ebcd728c.chunk.js.map" 13 | }, 14 | "entrypoints": [ 15 | "static/css/main.7047f949.css", 16 | "static/js/main.754b7e0e.js" 17 | ] 18 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/index.html: -------------------------------------------------------------------------------- 1 | Avored rust grpc cms
-------------------------------------------------------------------------------- /ts-grpc-react-admin/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-admin/build/logo192.png -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-admin/build/logo512.png -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/static/js/main.754b7e0e.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * react-table 3 | * 4 | * Copyright (c) TanStack 5 | * 6 | * This source code is licensed under the MIT license found in the 7 | * LICENSE.md file in the root directory of this source tree. 8 | * 9 | * @license MIT 10 | */ 11 | 12 | /** 13 | * @license 14 | * Lodash 15 | * Copyright OpenJS Foundation and other contributors 16 | * Released under MIT license 17 | * Based on Underscore.js 1.8.3 18 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 19 | */ 20 | 21 | /** 22 | * @license React 23 | * react-dom-client.production.js 24 | * 25 | * Copyright (c) Meta Platforms, Inc. and affiliates. 26 | * 27 | * This source code is licensed under the MIT license found in the 28 | * LICENSE file in the root directory of this source tree. 29 | */ 30 | 31 | /** 32 | * @license React 33 | * react-dom.production.js 34 | * 35 | * Copyright (c) Meta Platforms, Inc. and affiliates. 36 | * 37 | * This source code is licensed under the MIT license found in the 38 | * LICENSE file in the root directory of this source tree. 39 | */ 40 | 41 | /** 42 | * @license React 43 | * react-jsx-runtime.production.js 44 | * 45 | * Copyright (c) Meta Platforms, Inc. and affiliates. 46 | * 47 | * This source code is licensed under the MIT license found in the 48 | * LICENSE file in the root directory of this source tree. 49 | */ 50 | 51 | /** 52 | * @license React 53 | * react.production.js 54 | * 55 | * Copyright (c) Meta Platforms, Inc. and affiliates. 56 | * 57 | * This source code is licensed under the MIT license found in the 58 | * LICENSE file in the root directory of this source tree. 59 | */ 60 | 61 | /** 62 | * @license React 63 | * scheduler.production.js 64 | * 65 | * Copyright (c) Meta Platforms, Inc. and affiliates. 66 | * 67 | * This source code is licensed under the MIT license found in the 68 | * LICENSE file in the root directory of this source tree. 69 | */ 70 | 71 | /** 72 | * @license React 73 | * use-sync-external-store-with-selector.production.js 74 | * 75 | * Copyright (c) Meta Platforms, Inc. and affiliates. 76 | * 77 | * This source code is licensed under the MIT license found in the 78 | * LICENSE file in the root directory of this source tree. 79 | */ 80 | 81 | //! moment.js 82 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/build/static/media/en.9acbcf1017616e2140a1935111c50f28.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-grpc-react-admin", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@grpc/grpc-js": "^1.13.0", 7 | "@grpc/proto-loader": "^0.7.13", 8 | "@headlessui/react": "^2.2.2", 9 | "@heroicons/react": "^2.2.0", 10 | "@hookform/resolvers": "^3.3.4", 11 | "@tailwindcss/cli": "^4.0.13", 12 | "@tailwindcss/forms": "^0.5.7", 13 | "@tailwindcss/postcss": "^4.0.13", 14 | "@tanstack/react-query": "^5.68.0", 15 | "@tanstack/react-table": "^8.21.2", 16 | "@testing-library/dom": "^10.4.0", 17 | "@testing-library/jest-dom": "^6.6.3", 18 | "@testing-library/react": "^16.2.0", 19 | "@testing-library/user-event": "^13.5.0", 20 | "@types/jest": "^27.5.2", 21 | "@types/lodash": "^4.17.16", 22 | "@types/luxon": "^3.6.2", 23 | "@types/node": "^16.18.126", 24 | "@types/react": "^19.0.10", 25 | "@types/react-dom": "^19.0.4", 26 | "axios": "^1.9.0", 27 | "concurrently": "^9.1.2", 28 | "easymde": "^2.20.0", 29 | "grpc-web": "^1.5.0", 30 | "grpc-web-client": "^0.7.0", 31 | "i18next": "^23.11.5", 32 | "joi": "^17.12.3", 33 | "lodash": "^4.17.21", 34 | "luxon": "^3.6.1", 35 | "moment": "^2.30.1", 36 | "postcss": "^8.5.3", 37 | "react": "^19.0.0", 38 | "react-datetime": "^3.3.1", 39 | "react-dom": "^19.0.0", 40 | "react-hook-form": "^7.51.2", 41 | "react-hot-toast": "^2.5.2", 42 | "react-i18next": "^14.1.2", 43 | "react-query-grpc-gateway": "^1.4.0", 44 | "react-router-dom": "^7.3.0", 45 | "react-scripts": "5.0.1", 46 | "react-simplemde-editor": "^5.2.0", 47 | "slug": "^9.1.0", 48 | "stream": "^0.0.3", 49 | "stream-browser": "^1.2.0", 50 | "tailwindcss": "^4.0.13", 51 | "tls": "^0.0.1", 52 | "typescript": "^4.9.5", 53 | "util": "^0.12.5", 54 | "web-vitals": "^2.1.4" 55 | }, 56 | "scripts": { 57 | "dev": "concurrently \"npx @tailwindcss/cli -i ./src/index.css -o ./public/output.css --watch\" \" env-cmd -f .env react-scripts start\"", 58 | "build": "env-cmd -f .env.production.local react-scripts build", 59 | "test": "react-scripts test", 60 | "eject": "react-scripts eject" 61 | }, 62 | "eslintConfig": { 63 | "extends": [ 64 | "react-app", 65 | "react-app/jest" 66 | ] 67 | }, 68 | "browserslist": { 69 | "production": [ 70 | ">0.2%", 71 | "not dead", 72 | "not op_mini all" 73 | ], 74 | "development": [ 75 | "last 1 chrome version", 76 | "last 1 firefox version", 77 | "last 1 safari version" 78 | ] 79 | }, 80 | "devDependencies": { 81 | "@protobuf-ts/plugin": "^2.9.6", 82 | "@types/slug": "^5.0.9", 83 | "env-cmd": "^10.1.0" 84 | }, 85 | "browser": { 86 | "fs": false, 87 | "os": false, 88 | "path": false 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/postcss.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | "@tailwindcss/postcss": {}, 4 | } 5 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 19 | Avored rust grpc cms 20 | 21 | 22 | 23 |
24 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-admin/public/logo192.png -------------------------------------------------------------------------------- /ts-grpc-react-admin/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-admin/public/logo512.png -------------------------------------------------------------------------------- /ts-grpc-react-admin/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/assets/images/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-admin/src/assets/images/favicon.ico -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/assets/images/locales/en.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/assets/images/locales/fr.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/AvoRedButton.tsx: -------------------------------------------------------------------------------- 1 | export enum ButtonType { 2 | submit = "submit", 3 | button = "button", 4 | } 5 | 6 | type ButtonPropsType = { 7 | isPending?: boolean; 8 | label: string, 9 | type?: ButtonType, 10 | className?: string, 11 | onClick?: any 12 | } 13 | 14 | const AvoRedButton = (({ 15 | label, 16 | isPending, 17 | type = ButtonType.submit, 18 | className = "", 19 | onClick 20 | }: ButtonPropsType) => { 21 | return ( 22 | <> 23 | 30 | 31 | ) 32 | }) 33 | 34 | export default AvoRedButton 35 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/AvoRedIconButton.tsx: -------------------------------------------------------------------------------- 1 | import {ReactNode} from "react"; 2 | 3 | type ButtonPropsType = { 4 | icon: ReactNode 5 | } 6 | 7 | const AvoRedIconButton = (props: ButtonPropsType) => { 8 | const {icon} = props; 9 | return ( 10 | 14 | ) 15 | } 16 | 17 | export default AvoRedIconButton 18 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/AvoredModal.tsx: -------------------------------------------------------------------------------- 1 | import React, {Fragment, ReactNode} from "react" 2 | import {Dialog, Transition} from '@headlessui/react' 3 | 4 | type AvoRedModalProps = { 5 | modal_header: ReactNode | string; 6 | modal_body: ReactNode; 7 | isOpen: boolean; 8 | closeModal: any; 9 | widthClass?: string 10 | } 11 | const AvoredModal = ({ 12 | modal_header, 13 | modal_body, 14 | isOpen = false, 15 | closeModal, 16 | widthClass 17 | }: AvoRedModalProps) => { 18 | 19 | return ( 20 | <> 21 | 22 | 23 | 32 |
33 | 34 | 35 |
36 |
37 | 46 | 48 | 52 | {modal_header} 53 | 54 |
55 | {modal_body} 56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 | 64 | ); 65 | }; 66 | 67 | export default AvoredModal; 68 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/ErrorMessage.tsx: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import {ErrorMessageType, GrpcErrorCode} from "../types/common/ErrorType"; 3 | 4 | type ErrorMessageProps = { 5 | backendErrors: any; 6 | frontendErrors: any; 7 | identifier: string; 8 | } 9 | 10 | function ErrorMessage(props: ErrorMessageProps) { 11 | const getErrorIndex = (key: string): number => { 12 | 13 | let message = _.get(props.frontendErrors, key + '.message'); 14 | if (message) { 15 | return 1; 16 | } 17 | 18 | let backendErrorMessages = JSON.parse('{"errors": []}'); 19 | switch (props.backendErrors?.code) { 20 | case GrpcErrorCode.InvalidArgument: 21 | backendErrorMessages = JSON.parse(props.backendErrors?.message ?? '{"errors": []}'); 22 | break; 23 | default: 24 | // do nothing as it should be handled by global query client 25 | // 26 | break; 27 | } 28 | 29 | return _.findIndex(_.get(backendErrorMessages, 'errors', []), ((err: ErrorMessageType) => err.key === key)) 30 | } 31 | 32 | const getErrorMessage = (key: string) => { 33 | let message = _.get(props.frontendErrors, key + '.message') 34 | 35 | if (message) { 36 | return message; 37 | } 38 | let backendErrorMessages = JSON.parse('{"errors": []}'); 39 | switch (props.backendErrors?.code) { 40 | case GrpcErrorCode.InvalidArgument: 41 | backendErrorMessages = JSON.parse(props.backendErrors?.message ?? '{"errors": []}'); 42 | break; 43 | default: 44 | // do nothing as it should be handled by global query client 45 | // 46 | break; 47 | } 48 | 49 | return _.get(backendErrorMessages, "errors." + getErrorIndex(props.identifier) + ".message") 50 | } 51 | 52 | return ( 53 | <> 54 | {getErrorIndex(props.identifier) >= 0 ? ( 55 |

56 | {getErrorMessage(props.identifier)} 57 |

58 | ) : ''} 59 | 60 | 61 | ) 62 | 63 | } 64 | 65 | export default ErrorMessage; 66 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/HasPermission.tsx: -------------------------------------------------------------------------------- 1 | import {ReactNode} from "react"; 2 | import {UseLoggedInUserHook} from "../hooks/general/UseLoggedInUserHook"; 3 | import {AdminUserType, RoleType} from "../types/admin_user/AdminUserType"; 4 | import {LoggedInUserRequest} from "../grpc_generated/general_pb"; 5 | 6 | 7 | type HasPermissionProps = { 8 | identifier: string; 9 | children: ReactNode; 10 | displayDenied?: boolean 11 | } 12 | const HasPermission = (props: HasPermissionProps) => { 13 | // @todo fix it 14 | const hasPermission = (identifier: string) => { 15 | const request = new LoggedInUserRequest(); 16 | const auth_user_model = UseLoggedInUserHook(request); 17 | 18 | const logged_in_user: AdminUserType = auth_user_model?.data?.data as unknown as AdminUserType; 19 | 20 | // console.log(logged_in_user) 21 | 22 | if (logged_in_user?.isSuperAdmin) { 23 | return true 24 | } 25 | let has_permission: boolean = false 26 | logged_in_user?.rolesList.forEach((role: RoleType) => { 27 | return role.permissionsList.forEach((permission: string) => { 28 | if(permission === identifier) { 29 | has_permission = true 30 | } 31 | }) 32 | }) 33 | // console.log(has_permission, identifier) 34 | return has_permission 35 | } 36 | 37 | return ( 38 | <> 39 | {hasPermission(props.identifier) ? props.children : (props.displayDenied) ? 40 |
permission denied
: ""} 41 | 42 | ) 43 | } 44 | 45 | export default HasPermission 46 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/InputField.tsx: -------------------------------------------------------------------------------- 1 | import _ from 'lodash'; 2 | import React from "react"; 3 | 4 | type AvoRedInputFieldProps = { 5 | type?: string; 6 | name?: string; 7 | label?: string; 8 | accept?: string; 9 | id?: string; 10 | required?: boolean; 11 | value?: string | number; 12 | onChange?: (e: React.ChangeEvent, ...args: any[]) => void; 13 | onKeyDown?: (e: React.KeyboardEvent, ...args: any[]) => void; 14 | onKeyUp?: (e: React.KeyboardEvent, ...args: any[]) => void; 15 | autoFocus?: boolean; 16 | disabled?: boolean; 17 | placeholder?: string; 18 | register: any; 19 | step?: any 20 | } 21 | 22 | 23 | const InputField = (props: AvoRedInputFieldProps) => { 24 | return ( 25 |
26 | {(props.type !== "hidden" && !_.isEmpty(props.label)) ? ( 27 | 30 | ) : ( 31 | "" 32 | )} 33 | 34 |
35 | 56 |
57 |
58 | ); 59 | }; 60 | 61 | export default InputField; 62 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/components/TextareaField.tsx: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import React from "react"; 3 | 4 | type AvoRedTextareaFieldProps = { 5 | name?: string; 6 | id?: string; 7 | label?: string; 8 | placeholder?: string; 9 | value?: string | number; 10 | register: any; 11 | } 12 | 13 | export const TextareaField = (props: AvoRedTextareaFieldProps) => { 14 | return ( 15 | <> 16 |
17 | {(!_.isEmpty(props.label)) ? ( 18 | 21 | ) : ( 22 | "" 23 | )} 24 | 25 |
26 | 38 |
39 |
40 | 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/context/ThemeContext.tsx: -------------------------------------------------------------------------------- 1 | import {createContext} from "react"; 2 | 3 | export type ThemeContextType = { 4 | isSidebarOpen: boolean, 5 | toggleSidebar: () => void, 6 | } 7 | export const ThemeContext = createContext({ 8 | isSidebarOpen: false, 9 | toggleSidebar: () => {}, 10 | }); 11 | 12 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/grpc_generated/GeneralServiceClientPb.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * @fileoverview gRPC-Web generated client stub for general 3 | * @enhanceable 4 | * @public 5 | */ 6 | 7 | // Code generated by protoc-gen-grpc-web. DO NOT EDIT. 8 | // versions: 9 | // protoc-gen-grpc-web v1.5.0 10 | // protoc v5.29.3 11 | // source: general.proto 12 | 13 | 14 | /* eslint-disable */ 15 | // @ts-nocheck 16 | 17 | 18 | import * as grpcWeb from 'grpc-web'; 19 | 20 | import * as general_pb from './general_pb'; // proto import: "general.proto" 21 | 22 | 23 | export class GeneralServiceClient { 24 | client_: grpcWeb.AbstractClientBase; 25 | hostname_: string; 26 | credentials_: null | { [index: string]: string; }; 27 | options_: null | { [index: string]: any; }; 28 | 29 | constructor (hostname: string, 30 | credentials?: null | { [index: string]: string; }, 31 | options?: null | { [index: string]: any; }) { 32 | if (!options) options = {}; 33 | if (!credentials) credentials = {}; 34 | options['format'] = 'binary'; 35 | 36 | this.client_ = new grpcWeb.GrpcWebClientBase(options); 37 | this.hostname_ = hostname.replace(/\/+$/, ''); 38 | this.credentials_ = credentials; 39 | this.options_ = options; 40 | } 41 | 42 | methodDescriptorLoggedInUser = new grpcWeb.MethodDescriptor( 43 | '/general.GeneralService/LoggedInUser', 44 | grpcWeb.MethodType.UNARY, 45 | general_pb.LoggedInUserRequest, 46 | general_pb.LoggedInUserResponse, 47 | (request: general_pb.LoggedInUserRequest) => { 48 | return request.serializeBinary(); 49 | }, 50 | general_pb.LoggedInUserResponse.deserializeBinary 51 | ); 52 | 53 | loggedInUser( 54 | request: general_pb.LoggedInUserRequest, 55 | metadata?: grpcWeb.Metadata | null): Promise; 56 | 57 | loggedInUser( 58 | request: general_pb.LoggedInUserRequest, 59 | metadata: grpcWeb.Metadata | null, 60 | callback: (err: grpcWeb.RpcError, 61 | response: general_pb.LoggedInUserResponse) => void): grpcWeb.ClientReadableStream; 62 | 63 | loggedInUser( 64 | request: general_pb.LoggedInUserRequest, 65 | metadata?: grpcWeb.Metadata | null, 66 | callback?: (err: grpcWeb.RpcError, 67 | response: general_pb.LoggedInUserResponse) => void) { 68 | if (callback !== undefined) { 69 | return this.client_.rpcCall( 70 | this.hostname_ + 71 | '/general.GeneralService/LoggedInUser', 72 | request, 73 | metadata || {}, 74 | this.methodDescriptorLoggedInUser, 75 | callback); 76 | } 77 | return this.client_.unaryCall( 78 | this.hostname_ + 79 | '/general.GeneralService/LoggedInUser', 80 | request, 81 | metadata || {}, 82 | this.methodDescriptorLoggedInUser); 83 | } 84 | 85 | } 86 | 87 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/grpc_generated/general_pb.d.ts: -------------------------------------------------------------------------------- 1 | import * as jspb from 'google-protobuf' 2 | 3 | import * as admin_user_pb from './admin_user_pb'; // proto import: "admin_user.proto" 4 | 5 | 6 | export class LoggedInUserRequest extends jspb.Message { 7 | serializeBinary(): Uint8Array; 8 | toObject(includeInstance?: boolean): LoggedInUserRequest.AsObject; 9 | static toObject(includeInstance: boolean, msg: LoggedInUserRequest): LoggedInUserRequest.AsObject; 10 | static serializeBinaryToWriter(message: LoggedInUserRequest, writer: jspb.BinaryWriter): void; 11 | static deserializeBinary(bytes: Uint8Array): LoggedInUserRequest; 12 | static deserializeBinaryFromReader(message: LoggedInUserRequest, reader: jspb.BinaryReader): LoggedInUserRequest; 13 | } 14 | 15 | export namespace LoggedInUserRequest { 16 | export type AsObject = { 17 | } 18 | } 19 | 20 | export class LoggedInUserResponse extends jspb.Message { 21 | getStatus(): boolean; 22 | setStatus(value: boolean): LoggedInUserResponse; 23 | 24 | getData(): admin_user_pb.AdminUserModel | undefined; 25 | setData(value?: admin_user_pb.AdminUserModel): LoggedInUserResponse; 26 | hasData(): boolean; 27 | clearData(): LoggedInUserResponse; 28 | 29 | serializeBinary(): Uint8Array; 30 | toObject(includeInstance?: boolean): LoggedInUserResponse.AsObject; 31 | static toObject(includeInstance: boolean, msg: LoggedInUserResponse): LoggedInUserResponse.AsObject; 32 | static serializeBinaryToWriter(message: LoggedInUserResponse, writer: jspb.BinaryWriter): void; 33 | static deserializeBinary(bytes: Uint8Array): LoggedInUserResponse; 34 | static deserializeBinaryFromReader(message: LoggedInUserResponse, reader: jspb.BinaryReader): LoggedInUserResponse; 35 | } 36 | 37 | export namespace LoggedInUserResponse { 38 | export type AsObject = { 39 | status: boolean, 40 | data?: admin_user_pb.AdminUserModel.AsObject, 41 | } 42 | } 43 | 44 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseAdminUserPaginateHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 3 | import {AdminUserPaginateRequest} from "../../grpc_generated/admin_user_pb"; 4 | import {PaginateType} from "../../types/misc/PaginateType"; 5 | 6 | export const UseAdminUserPaginateHook = (request: AdminUserPaginateRequest, query: PaginateType) => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | 10 | return useQuery({ 11 | queryKey: ['admin-user-table', query], 12 | queryFn: async () => { 13 | request.setPage(query.page ?? 0); 14 | request.setOrder(query.order as string) 15 | 16 | let response = await client.paginate(request, { 17 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 18 | }) 19 | if (response.getStatus()) { 20 | // may be map a type and return a proper type 21 | return response.toObject(); 22 | } 23 | console.log('feel like error thrown... ') 24 | }, 25 | }) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseGetAdminUserHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 3 | import {GetAdminUserRequest} from "../../grpc_generated/admin_user_pb"; 4 | 5 | export const UseGetAdminUserHook = (request: GetAdminUserRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new AdminUserClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['admin-user', request.getAdminUserId()], 11 | queryFn: async () => { 12 | let response = await client.getAdminUser(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseGetRoleHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 3 | import {GetRoleRequest} from "../../grpc_generated/admin_user_pb"; 4 | 5 | export const UseGetRoleHook = (request: GetRoleRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new AdminUserClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['role-id', request.getRoleId()], 11 | queryFn: async () => { 12 | let response = await client.getRole(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseGetRoleOption.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 3 | import {RoleOptionRequest} from "../../grpc_generated/admin_user_pb"; 4 | 5 | export const UseGetRoleOptionHook = (request: RoleOptionRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new AdminUserClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['role-option'], 11 | queryFn: async () => { 12 | let response = await client.roleOption(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UsePutRoleIdentifierHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 4 | import {PutRoleIdentifierRequest} from "../../grpc_generated/admin_user_pb"; 5 | 6 | export const UsePutRoleIdentifierHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: PutRoleIdentifierRequest) => { 13 | return client.putRoleIdentifier(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | const role_model = res.getData(); 21 | const id = role_model?.getId() ?? ''; 22 | 23 | redirect("/admin/role" + id); 24 | } 25 | } 26 | }) 27 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseRolePaginateHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 3 | import {RolePaginateRequest} from "../../grpc_generated/admin_user_pb"; 4 | import {PaginateType} from "../../types/misc/PaginateType"; 5 | 6 | export const UseRolePaginateHook = (request: RolePaginateRequest, query: PaginateType) => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | 10 | return useQuery({ 11 | queryKey: ['role-table-paginate', query,], 12 | queryFn: async () => { 13 | request.setPage(query.page ?? 0); 14 | request.setOrder(query.order as string) 15 | 16 | let response = await client.rolePaginate(request, { 17 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 18 | }) 19 | if (response.getStatus()) { 20 | // may be map a type and return a proper type 21 | return response.toObject(); 22 | } 23 | console.log('feel like error thrown... ') 24 | }, 25 | }) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseStoreAdminUserHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 4 | import {StoreAdminUserRequest} from "../../grpc_generated/admin_user_pb"; 5 | 6 | export const UseStoreAdminUserHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: StoreAdminUserRequest) => { 13 | return client.storeAdminUser(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/admin-user"); 21 | } 22 | } 23 | }) 24 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseStoreRoleHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 4 | import {StoreRoleRequest} from "../../grpc_generated/admin_user_pb"; 5 | 6 | export const UseStoreRoleHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: StoreRoleRequest) => { 13 | return client.storeRole(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/role"); 21 | } 22 | } 23 | }) 24 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseUpdateAdminUserHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 4 | import {UpdateAdminUserRequest} from "../../grpc_generated/admin_user_pb"; 5 | 6 | export const UseUpdateAdminUserHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: UpdateAdminUserRequest) => { 13 | return client.updateAdminUser(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/admin-user"); 21 | } 22 | } 23 | }) 24 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/admin_user/UseUpdateRoleHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AdminUserClient} from "../../grpc_generated/Admin_userServiceClientPb"; 4 | import {UpdateRoleRequest} from "../../grpc_generated/admin_user_pb"; 5 | 6 | export const UseUpdateRoleHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AdminUserClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: UpdateRoleRequest) => { 13 | return client.updateRole(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/role"); 21 | } 22 | } 23 | }) 24 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseAssetTableHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {AssetClient} from "../../grpc_generated/AssetServiceClientPb"; 3 | import {AssetPaginateRequest} from "../../grpc_generated/asset_pb"; 4 | 5 | 6 | export const UseAssetTableHook = (request: AssetPaginateRequest) => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AssetClient(backend_url); 9 | 10 | 11 | return useQuery({ 12 | queryKey: ['asset-table'], 13 | queryFn: (async () => { 14 | try { 15 | let response = await client.paginate(request, { 16 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 17 | }) 18 | if (response.getStatus()) { 19 | // may be map a type and return a proper type 20 | return response.toObject(); 21 | } 22 | console.log('feel like error thrown... ') 23 | } catch (error) { 24 | console.log(error) 25 | } 26 | }) 27 | }) 28 | } 29 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseCreateFolderHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {CreateFolderType} from "../../types/asset/AssetType"; 3 | import {isEmpty} from "lodash"; 4 | import {UseAxiosHook} from "../misc/UseAxiosHook"; 5 | 6 | export const UseCreateFolderHook = () => { 7 | const client = UseAxiosHook(); 8 | const queryClient = useQueryClient(); 9 | 10 | return useMutation({ 11 | mutationFn: async (data: CreateFolderType) => { 12 | if (isEmpty(data.parent_id)) { 13 | delete data.parent_id 14 | } 15 | return await client.post("/create-folder", data); 16 | }, 17 | onSuccess: (res) => { 18 | queryClient.invalidateQueries({ queryKey: ["asset-table"] }); 19 | }, 20 | }); 21 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseDeleteAssetHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {AssetClient} from "../../grpc_generated/AssetServiceClientPb"; 3 | import {DeleteAssetRequest} from "../../grpc_generated/asset_pb"; 4 | 5 | export const UseDeleteAssetHook = () => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new AssetClient(backend_url); 8 | const queryClient = useQueryClient(); 9 | 10 | return useMutation({ 11 | mutationFn: async (request: DeleteAssetRequest) => { 12 | return await client.deleteAsset(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }); 15 | }, 16 | onSuccess: (res) => { 17 | queryClient.invalidateQueries({ queryKey: ["asset-table"] }); 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseDeleteFolderHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {AssetClient} from "../../grpc_generated/AssetServiceClientPb"; 3 | import {DeleteFolderRequest} from "../../grpc_generated/asset_pb"; 4 | 5 | export const UseDeleteFolderHook = () => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new AssetClient(backend_url); 8 | const queryClient = useQueryClient(); 9 | 10 | return useMutation({ 11 | mutationFn: async (request: DeleteFolderRequest) => { 12 | return await client.deleteFolder(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }); 15 | }, 16 | onSuccess: (res) => { 17 | queryClient.invalidateQueries({ queryKey: ["asset-table"] }); 18 | }, 19 | }); 20 | } 21 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseRenameAssetHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {AssetClient} from "../../grpc_generated/AssetServiceClientPb"; 3 | import {RenameAssetRequest} from "../../grpc_generated/asset_pb"; 4 | import {useNavigate} from "react-router-dom"; 5 | 6 | export const UseRenameAssetHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AssetClient(backend_url); 9 | const redirect = useNavigate(); 10 | const queryClient = useQueryClient(); 11 | 12 | return useMutation({ 13 | mutationFn: async (request: RenameAssetRequest) => { 14 | return await client.renameAsset(request, { 15 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 16 | }); 17 | }, 18 | onSuccess: async (res) => { 19 | if (res.getStatus()) { 20 | await queryClient.invalidateQueries({ queryKey: ['asset-table'] }); 21 | redirect("/admin/asset") 22 | } 23 | }, 24 | }); 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/asset/UseStoreAssetHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {AssetSaveType} from "../../types/asset/AssetType"; 3 | import {UseAxiosHook} from "../misc/UseAxiosHook"; 4 | import _ from "lodash"; 5 | 6 | export const UseStoreAssetHook = (parent_id: string) => { 7 | const client = UseAxiosHook(); 8 | const queryClient = useQueryClient(); 9 | 10 | return useMutation({ 11 | mutationFn: async (data: AssetSaveType) => { 12 | const assetUrl: string = _.isEmpty(parent_id) ? '/asset' : '/asset?parent_id=' + parent_id; 13 | return await client.post(assetUrl, data, { 14 | headers: { 15 | "Content-Type": "multipart/form-data; boundary=----", 16 | Authorization: "Bearer " + localStorage.getItem("token"), 17 | }, 18 | }); 19 | }, 20 | onSuccess: (res) => { 21 | queryClient.invalidateQueries({ queryKey: ["asset-table"] }); 22 | }, 23 | }); 24 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/auth/UseLoginHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AuthClient} from "../../grpc_generated/AuthServiceClientPb"; 4 | import {LoginRequest} from "../../grpc_generated/auth_pb"; 5 | 6 | export const UseLoginHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AuthClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: LoginRequest) => { 13 | return client.login(request) 14 | }, 15 | onSuccess: (res) => { 16 | if (res.getStatus()) { 17 | let token = res.getData(); 18 | localStorage.setItem("token", token); 19 | redirect("/admin/dashboard"); 20 | } 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/auth/UseResetPasswordHook.ts: -------------------------------------------------------------------------------- 1 | import {AuthClient} from "../../grpc_generated/AuthServiceClientPb"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {useMutation} from "@tanstack/react-query"; 4 | import {ResetPasswordRequest} from "../../grpc_generated/auth_pb"; 5 | 6 | export const UseResetPasswordHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AuthClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: ResetPasswordRequest) => { 13 | return client.resetPassword(request) 14 | }, 15 | onSuccess: (res) => { 16 | // console.log(res, "forgot password response"); 17 | if (res.getStatus()) { 18 | redirect("/admin/login"); 19 | } 20 | }, 21 | onError: (err) => { 22 | console.log(err, "forgot password error"); 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/auth/useForgotPasswordHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {AuthClient} from "../../grpc_generated/AuthServiceClientPb"; 4 | import {ForgotPasswordRequest} from "../../grpc_generated/auth_pb"; 5 | 6 | export const UseForgotPasswordHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new AuthClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: ForgotPasswordRequest) => { 13 | return client.forgotPassword(request) 14 | }, 15 | onSuccess: (res) => { 16 | // console.log(res, "forgot password response"); 17 | if (res.getStatus()) { 18 | redirect("/admin/login"); 19 | } 20 | }, 21 | onError: (err) => { 22 | console.log(err, "forgot password error"); 23 | } 24 | }) 25 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseCollectionAllHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {CollectionAllRequest} from "../../grpc_generated/content_pb"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | 5 | export const UseCollectionAllHook = (request: CollectionAllRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new contentClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['collection-all'], 11 | queryFn: async () => { 12 | let response = await client.collectionAll(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseContentPaginateHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 3 | import {ContentPaginateRequest} from "../../grpc_generated/content_pb"; 4 | import {PaginateType} from "../../types/misc/PaginateType"; 5 | 6 | export const UseContentPaginateHook = (request: ContentPaginateRequest, query: PaginateType) => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new contentClient(backend_url); 9 | 10 | return useQuery({ 11 | queryKey: ['content-table-paginate', 'content-' + request.getContentType(), query], 12 | queryFn: async () => { 13 | request.setPage(query.page ?? 0); 14 | request.setOrder(query.order as string) 15 | 16 | let response = await client.contentPaginate(request, { 17 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 18 | }) 19 | if (response.getStatus()) { 20 | // may be map a type and return a proper type 21 | return response.toObject(); 22 | } 23 | console.log('feel like error thrown... ') 24 | }, 25 | }) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseGetContentHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {GetContentRequest} from "../../grpc_generated/content_pb"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | 5 | export const UseGetContentHook = (request: GetContentRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new contentClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['get-content', request.getContentId()], 11 | queryFn: async () => { 12 | let response = await client.getContent(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UsePutContentIdentifierHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | import {PutContentIdentifierRequest} from "../../grpc_generated/content_pb"; 5 | 6 | export const UsePutContentIdentifierHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new contentClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: PutContentIdentifierRequest) => { 13 | return client.putContentIdentifier(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/content"); 21 | } 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseStoreCollectionHook.ts: -------------------------------------------------------------------------------- 1 | import {QueryClient, useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | import {StoreCollectionRequest} from "../../grpc_generated/content_pb"; 5 | 6 | export const UseStoreCollectionHook = (refetchCollectionAll: any) => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new contentClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: StoreCollectionRequest) => { 13 | return client.storeCollection(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: async (res) => { 18 | if (res.getStatus()) { 19 | const queryClient = new QueryClient(); 20 | await queryClient.invalidateQueries({ queryKey: ['collection-all'] }); 21 | redirect("/admin/content?type=" + res.getData()?.getIdentifier()); 22 | await refetchCollectionAll() 23 | } 24 | } 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseStoreContentHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | import {StoreContentRequest} from "../../grpc_generated/content_pb"; 5 | 6 | export const UseStoreContentHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new contentClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: StoreContentRequest) => { 13 | return client.storeContent(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res, request: StoreContentRequest) => { 18 | if (res.getStatus()) { 19 | redirect("/admin/content?type=" + request.getContentType()); 20 | } 21 | } 22 | }) 23 | } 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseUpdateCollectionHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation, useQueryClient} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | import {UpdateCollectionRequest} from "../../grpc_generated/content_pb"; 5 | 6 | 7 | export const UseUpdateCollectionHook = (refetchCollectionAll: any) => { 8 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 9 | const client = new contentClient(backend_url); 10 | const redirect = useNavigate(); 11 | const queryClient = useQueryClient() 12 | 13 | return useMutation({ 14 | mutationFn: (request: UpdateCollectionRequest) => { 15 | return client.updateCollection(request, { 16 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 17 | }) 18 | }, 19 | onSuccess: async (res) => { 20 | if (res.getStatus()) { 21 | await queryClient.invalidateQueries({ queryKey: ['collection-all'] }); 22 | redirect("/admin/content?type=" + res.getData()?.getIdentifier()); 23 | await refetchCollectionAll() 24 | } 25 | } 26 | }) 27 | } 28 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/content/UseUpdateContentHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {contentClient} from "../../grpc_generated/ContentServiceClientPb"; 4 | import {UpdateContentRequest} from "../../grpc_generated/content_pb"; 5 | 6 | export const UseUpdateContentHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new contentClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: UpdateContentRequest) => { 13 | return client.updateContent(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res, request: UpdateContentRequest) => { 18 | if (res.getStatus()) { 19 | // localStorage.setItem("token", token); 20 | redirect("/admin/content?type=" + request.getContentType()); 21 | } 22 | } 23 | }) 24 | } 25 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/general/UseLoggedInUserHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {LoggedInUserRequest} from "../../grpc_generated/general_pb"; 3 | import {GeneralServiceClient} from "../../grpc_generated/GeneralServiceClientPb"; 4 | 5 | export const UseLoggedInUserHook = (request: LoggedInUserRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new GeneralServiceClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['logged-in-user'], 11 | queryFn: async () => { 12 | let response = await client.loggedInUser(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/misc/UseAxiosHook.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import _ from 'lodash'; 3 | import axios from 'axios'; 4 | 5 | const baseURL = process.env.REACT_APP_BACKEND_BASE_URL + "/api" 6 | 7 | export default axios.create({ 8 | baseURL 9 | }) 10 | 11 | // axios instance with 'withCredentials' flag 12 | export const client = axios.create({ 13 | baseURL, 14 | headers : { 15 | 'Content-Type' : 'application/json' 16 | }, 17 | withCredentials: false 18 | }) 19 | 20 | export const UseAxiosHook = () => { 21 | useEffect(() => { 22 | const reqInterceptor = client.interceptors.request.use( 23 | config => { 24 | const token = localStorage.getItem("token"); 25 | if (!_.isEmpty(token) && !config.headers['Authorization']) { 26 | config.headers['Authorization'] = `Bearer ${token}` 27 | } 28 | 29 | return config; 30 | }, (error) => Promise.reject(error) 31 | ) 32 | 33 | return () => { 34 | client.interceptors.request.eject(reqInterceptor); 35 | } 36 | }, []) 37 | 38 | return client; 39 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/misc/UseHealthCheckHook.ts: -------------------------------------------------------------------------------- 1 | import {MiscClient} from "../../grpc_generated/MiscServiceClientPb"; 2 | import {useQuery} from "@tanstack/react-query"; 3 | import {HealthCheckRequest} from "../../grpc_generated/misc_pb"; 4 | 5 | export const useHealthCheckHook = (request: HealthCheckRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new MiscClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['user'], 11 | queryFn: () => client.healthCheck(request), 12 | }) 13 | }; 14 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/misc/UseSetupPost.ts: -------------------------------------------------------------------------------- 1 | import {MiscClient} from "../../grpc_generated/MiscServiceClientPb"; 2 | import {useMutation} from "@tanstack/react-query"; 3 | import {SetupRequest} from "../../grpc_generated/misc_pb"; 4 | import {useNavigate} from "react-router-dom"; 5 | 6 | export const useSetupPost = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new MiscClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: SetupRequest) => client.setup(request), 13 | onSuccess: (res) => { 14 | if (res.getStatus()) { 15 | redirect("/admin/login") 16 | } 17 | } 18 | }) 19 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/setting/UseSettingHook.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {SettingClient} from "../../grpc_generated/SettingServiceClientPb"; 3 | import {GetSettingRequest} from "../../grpc_generated/setting_pb"; 4 | 5 | export const UseSettingHook = (request: GetSettingRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new SettingClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['settings'], 11 | queryFn: async () => { 12 | let response = await client.getSetting(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | }; 23 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/hooks/setting/UseStoreSettingHook.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {useNavigate} from "react-router-dom"; 3 | import {StoreSettingRequest} from "../../grpc_generated/setting_pb"; 4 | import {SettingClient} from "../../grpc_generated/SettingServiceClientPb"; 5 | 6 | export const UseStoreSettingHook = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new SettingClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: (request: StoreSettingRequest) => { 13 | return client.storeSetting(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | }, 17 | onSuccess: (res) => { 18 | if (res.getStatus()) { 19 | redirect("/admin/setting") 20 | } 21 | } 22 | }) 23 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --color-primary-50: oklch(0.977 0.017 320.058); 5 | --color-primary-100: oklch(0.952 0.037 318.852); 6 | --color-primary-200: oklch(0.903 0.076 319.62); 7 | --color-primary-300: oklch(0.833 0.145 321.434); 8 | --color-primary-400: oklch(0.74 0.238 322.16); 9 | --color-primary-500: oklch(0.667 0.295 322.15); 10 | --color-primary-600: oklch(0.591 0.293 322.896); 11 | --color-primary-700: oklch(0.518 0.253 323.949); 12 | --color-primary-800: oklch(0.452 0.211 324.591); 13 | --color-primary-900: oklch(0.401 0.17 325.612); 14 | --color-primary-950: oklch(0.293 0.136 325.661); 15 | } 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/layouts/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import AppHeader from "./partials/AppHeader"; 2 | import AppSidebar from "./partials/AppSidebar"; 3 | import {useState} from "react"; 4 | // import {useNavigate} from "react-router-dom"; 5 | // import {isEmpty} from "lodash"; 6 | import {ThemeContext} from "../context/ThemeContext"; 7 | 8 | 9 | function AppLayout() { 10 | 11 | // const redirect = useNavigate() 12 | const [isSidebarOpen, setIsSidebarOpen] = useState(false); 13 | 14 | const toggleSidebar = () => { 15 | setIsSidebarOpen((prev) => !prev) 16 | } 17 | const value = { 18 | isSidebarOpen, 19 | toggleSidebar, 20 | } 21 | 22 | // useEffect(() => { 23 | // // @todo permission check here 24 | // const token = localStorage.getItem("AUTH_TOKEN") 25 | // if (isEmpty(token)) { 26 | // return redirect("/admin/login") 27 | // } 28 | // }) 29 | 30 | return ( 31 | 32 |
34 | 35 | 36 |
37 |
38 | 39 | ); 40 | } 41 | 42 | export default AppLayout; 43 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/layouts/partials/DeleteDataConfirmationModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; 2 | import {useTranslation} from "react-i18next"; 3 | // import {useDeleteDemoData} from "../../hooks/useDeleteDemoData"; 4 | 5 | type DeleteDataConfirmationModalProp = { 6 | isOpen: any, 7 | close: any 8 | } 9 | export const DeleteDataConfirmationModal = (({ 10 | isOpen, 11 | close 12 | }: DeleteDataConfirmationModalProp) => { 13 | const [t] = useTranslation("global") 14 | // const { mutate } = useDeleteDemoData() 15 | const onConfirmButtonOnClick = (() => { 16 | // mutate() 17 | close() 18 | }) 19 | return ( 20 | 21 |
22 |
23 | 26 | 27 | {t("are_you_sure")} 28 | 29 |

30 | {t("delete_demo_data_description")} 31 |

32 |
33 | 39 | 45 |
46 |
47 |
48 |
49 |
50 | ) 51 | }) -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/layouts/partials/InstallDataConfirmationModal.tsx: -------------------------------------------------------------------------------- 1 | import { Button, Dialog, DialogPanel, DialogTitle } from "@headlessui/react"; 2 | import {useTranslation} from "react-i18next"; 3 | // import {useInstallDemoData} from "../../hooks/useInstallDemoData"; 4 | 5 | type InstallDataConfirmationModalProp = { 6 | isOpen: any, 7 | close: any 8 | } 9 | export const InstallDataConfirmationModal = (({ 10 | isOpen, 11 | close 12 | }: InstallDataConfirmationModalProp) => { 13 | const [t] = useTranslation("global") 14 | // const { mutate } = useInstallDemoData() 15 | 16 | const onConfirmButtonOnClick = (() => { 17 | // mutate() 18 | close() 19 | }) 20 | 21 | return ( 22 | 23 |
24 |
25 | 28 | 29 | {t("are_you_sure")} 30 | 31 |

32 | {t("install_demo_data_description")} 33 |

34 |
35 | 41 | 47 |
48 |
49 |
50 |
51 |
52 | ) 53 | }) -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/locales/fr.json: -------------------------------------------------------------------------------- 1 | { 2 | "intention_to_remove_page": "Are you sure you want to remove the page {{page_var}}?", 3 | "dashboard_demo_content": "Le contenu du tableau de bord va ici", 4 | "sign_into_your_account": "Connectez-vous à votre compte", 5 | "email_address": "Adresse e-mail", 6 | "password": "Mot de passe", 7 | "forgot_your_password": "Mot de passe oublié?", 8 | "sign_in": "Se connecter", 9 | "need_to_change_language": "Besoin de changer de langue ?", 10 | "profile": "Profile", 11 | "logout": "Logout", 12 | "forgot_password": "Forgot passoword?", 13 | "reset_password": "Reset password", 14 | "common": { 15 | "save": "Save", 16 | "edit": "Edit", 17 | "create": "Create", 18 | "delete": "Delete", 19 | "upload": "Upload", 20 | "cancel": "Cancel", 21 | "select": "Select", 22 | "file": "File", 23 | "id": "ID", 24 | "name": "Name", 25 | "fullname": "Fullname", 26 | "email": "Email", 27 | "action": "Action", 28 | "add_field": "Add Field", 29 | "identifier": "Identifier", 30 | "created_at": "Created at", 31 | "created_by": "Created by", 32 | "updated_at": "Updated at", 33 | "updated_by": "Updated by", 34 | "text": "Text", 35 | "textarea": "Textarea", 36 | "table": "Table", 37 | "permissions": "Permissions", 38 | "page": "Page", 39 | "component": "Component", 40 | "asset": "Asset", 41 | "admin_user": "Admin User", 42 | "role": "Role", 43 | "settings": "Settings", 44 | "general": "General" 45 | }, 46 | "asset": { 47 | "asset_table": "Asset table", 48 | "upload_asset": "Upload Asset", 49 | "asset_file": "Asset File" 50 | }, 51 | "sidebar": { 52 | "dashboard": "Dashboard", 53 | "content_manager": "Content Manager", 54 | "page": "Page", 55 | "components": "Components", 56 | "assets_manager": "Assets Manager", 57 | "management": "Management", 58 | "team": "Team", 59 | "admin_user": "Admin User", 60 | "role": "Role", 61 | "setting": "Setting" 62 | }, 63 | "component": { 64 | "components": "Components", 65 | "information": "Component Information" 66 | }, 67 | "roles": { 68 | "roles": "Roles", 69 | "generics": "Generics", 70 | "information": "Role Information" 71 | }, 72 | "pages": { 73 | "pages": "Pages", 74 | "information": "Page Information" 75 | }, 76 | "admin_user": { 77 | "admin_users": "Admin Users", 78 | "information": "Admin User Information" 79 | }, 80 | "settings": { 81 | "site_name": "Site Name" 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/pages/DashboardPage.tsx: -------------------------------------------------------------------------------- 1 | import {useTranslation} from "react-i18next"; 2 | 3 | export const DashboardPage = (() => { 4 | const [t] = useTranslation("global"); 5 | return ( 6 |
7 | {t('dashboard_demo_content')} 8 |
9 | ) 10 | }) -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/pages/auth/LogoutPage.tsx: -------------------------------------------------------------------------------- 1 | import {useNavigate} from "react-router-dom"; 2 | import {useEffect} from "react"; 3 | 4 | export const LogoutPage = () => { 5 | const redirect = useNavigate(); 6 | useEffect(() => { 7 | localStorage.clear(); 8 | redirect("/admin/login"); 9 | }, [redirect]); // Added "redirect" to the dependency array 10 | 11 | 12 | return( 13 | <> 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | declare module '@heroicons/*'; -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import {ReportHandler} from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({getCLS, getFID, getFCP, getLCP, getTTFB}) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/admin_user/UseAdminUserCreateSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseAdminUserCreateSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | full_name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("full_name")}) 10 | }), 11 | email : Joi.string().email({ tlds: { allow: false } }).required().messages({ 12 | 'string.empty': t("empty_message", {attribute: t("email")}), 13 | 'string.email': t("invalid_email"), 14 | }), 15 | password : Joi.string().required().messages({ 16 | 'string.empty': t("empty_message", {attribute: t("password")}), 17 | }), 18 | confirmation_password: Joi.any().valid(Joi.ref('password')).required().messages({ 19 | 'string.empty': t("empty_message", {attribute: t("confirmation_password")}), 20 | 'any.only': t("confirm_password_does_not_match_with_current_password"), 21 | }) 22 | 23 | }); 24 | }) 25 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/admin_user/UseAdminUserEditSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseAdminUserEditSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | fullName : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("full_name")}), 10 | }) 11 | }); 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/admin_user/UsePutRoleSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import {useTranslation} from "react-i18next"; 3 | 4 | export const UseRolePutSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | identifier : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("identifier")}), 10 | }) 11 | }); 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/admin_user/UseRoleCreateSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseRoleCreateSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | identifier : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("identifier")}), 10 | }), 11 | name : Joi.string().required().messages({ 12 | 'string.empty': t("empty_message", {attribute: t("name")}), 13 | }) 14 | }); 15 | }) 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/admin_user/UserRoleEditSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | import {useTranslation} from "react-i18next"; 3 | 4 | export const UseRoleEditSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("name")}), 10 | }) 11 | }); 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/asset/AssetSaveSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | export const AssetSaveSchema = Joi.object({ 3 | 4 | }); 5 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/asset/UseCreateFolderSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | export const UseCreateFolderSchema = Joi.object({ 3 | 4 | }); 5 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/asset/UseRenameFolderSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | export const UseRenameFolderSchema = Joi.object({ 3 | 4 | }); 5 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/auth/UseForgotPasswordSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseForgotPasswordSchema = (() => { 5 | const [t] = useTranslation("global") 6 | return Joi.object({ 7 | email: Joi.string().email({tlds: {allow: false}}).required().messages({ 8 | 'string.empty': t("empty_message", {attribute: t("email")}), 9 | 'string.email': t("invalid_email"), 10 | }), 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/auth/UseLoginSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | import {useTranslation} from "react-i18next"; 3 | 4 | export const UseLoginSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | email : Joi.string().email({ tlds: { allow: false } }).required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("email")}), 10 | 'string.email': t("invalid_email"), 11 | }), 12 | password : Joi.string().required().messages({ 13 | 'string.empty': t("empty_message", {attribute: t("password")}), 14 | }) 15 | }); 16 | }) 17 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/auth/UseResetPasswordSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | // import {useTranslation} from "react-i18next"; 3 | 4 | export const UseResetPasswordSchema = () => { 5 | // const [t] = useTranslation("global") 6 | return Joi.object({ 7 | // email: Joi.string().email({tlds: {allow: false}}).required().messages({ 8 | // 'string.empty': t("empty_message", {attribute: t("email")}), 9 | // 'string.email': t("invalid_email"), 10 | // }), 11 | }) 12 | } 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/content/UseCollectionCreateSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseCollectionCreateSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("name")}), 10 | }), 11 | identifier : Joi.string().required().messages({ 12 | 'string.empty': t("empty_message", {attribute: t("identifier")}), 13 | }) 14 | }); 15 | }) 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/content/UseCollectionUpdateSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseCollectionUpdateSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("name")}), 10 | }), 11 | identifier : Joi.string().required().messages({ 12 | 'string.empty': t("empty_message", {attribute: t("identifier")}), 13 | }) 14 | }); 15 | }) 16 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/content/UseContentCreateSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseContentCreateSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("name")}), 10 | }) 11 | }); 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/content/UseContentEditSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi' 2 | import {useTranslation} from "react-i18next" 3 | 4 | export const UseContentEditSchema = (() => { 5 | 6 | const [t] = useTranslation("global") 7 | return Joi.object({ 8 | name : Joi.string().required().messages({ 9 | 'string.empty': t("empty_message", {attribute: t("name")}), 10 | }) 11 | }); 12 | }) 13 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/misc/SetupSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from "joi"; 2 | 3 | export const SetupSchema = Joi.object({}); 4 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/schemas/setting/SettingSaveSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | 3 | export const SettingSaveSchema = Joi.object({ 4 | 5 | }); 6 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/admin_user/AdminUserType.ts: -------------------------------------------------------------------------------- 1 | import {GrpcTimeStamp} from "../common/common"; 2 | 3 | export type AdminUserType = { 4 | id: string; 5 | email: string; 6 | fullName: string; 7 | isSuperAdmin: boolean; 8 | profileImage: string; 9 | rolesList: Array; 10 | createdAt: GrpcTimeStamp; 11 | updatedAt: GrpcTimeStamp; 12 | createdBy: string; 13 | updatedBy: string; 14 | action: string; 15 | } 16 | 17 | export type CreateAdminUserType = { 18 | full_name: string; 19 | email: string; 20 | password: string; 21 | confirmation_password: string; 22 | profile_image: FileList; 23 | } 24 | 25 | export type EditAdminUserType = { 26 | id: string; 27 | fullName: string; 28 | profileImage: string; 29 | email: string; 30 | profile_image: FileList; 31 | roles: Array; 32 | isSuperAdmin: boolean; 33 | } 34 | 35 | export type RoleOptionType = { 36 | label: string; 37 | value: string; 38 | } 39 | 40 | 41 | export type RoleType = { 42 | id: string; 43 | name: string; 44 | identifier: string; 45 | createdAt: GrpcTimeStamp; 46 | updatedAt: GrpcTimeStamp; 47 | permissions: Array; 48 | permissionsList: Array; 49 | createdBy: string; 50 | updatedBy: string; 51 | action: string; 52 | } 53 | 54 | export type CreatableRoleType = { 55 | name: string; 56 | identifier: string; 57 | permissions: Array; 58 | } 59 | 60 | 61 | export type EditRoleType = { 62 | id: string; 63 | name: string; 64 | identifier: string; 65 | permissions: Array; 66 | } 67 | 68 | export type PutRoleIdentifierType = { 69 | identifier: String; 70 | } 71 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/asset/AssetType.ts: -------------------------------------------------------------------------------- 1 | export type AssetType = { 2 | id: string; 3 | name: string; 4 | newPath: string; 5 | assetType: string; 6 | metadata: string; 7 | createdAt: string; 8 | createdBy: string; 9 | updatedAt: string; 10 | updatedBy: string; 11 | } 12 | 13 | export type AssetSaveType = { 14 | file_list?: FileList; 15 | file?: File; 16 | } 17 | export type CreateFolderType = { 18 | name: string; 19 | parent_id?: string; 20 | } 21 | 22 | export type DeleteFolderType = { 23 | asset_id: String; 24 | } 25 | 26 | export type RenameFolderType = { 27 | id: string; 28 | name: string; 29 | } 30 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/auth/LoginPostType.ts: -------------------------------------------------------------------------------- 1 | export type LoginPostType = { 2 | email: string; 3 | password: string; 4 | } 5 | 6 | export type ForgotPasswordPostType = { 7 | email: string; 8 | } 9 | 10 | export type ResetPasswordPostType = { 11 | email: string; 12 | password: string; 13 | confirm_password: string; 14 | token: string; 15 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/common/ErrorType.ts: -------------------------------------------------------------------------------- 1 | export type ErrorMessageType = { 2 | key: string; 3 | message: string; 4 | } 5 | 6 | export type GrpcErrorType = { 7 | code: GrpcErrorCode; 8 | message: string; 9 | metadata: any; 10 | stack: string; 11 | } 12 | 13 | export enum GrpcErrorCode { 14 | Ok = 0, 15 | Cancelled = 1, 16 | Unknown= 2, 17 | InvalidArgument = 3, 18 | DeadlineExceeded = 4, 19 | NotFound = 5, 20 | AlreadyExists = 6, 21 | PermissionDenied = 7, 22 | ResourceExhausted = 8, 23 | FailedPrecondition = 9, 24 | Aborted = 10, 25 | OutOfRange = 11, 26 | Unimplemented = 12, 27 | Internal = 13, 28 | Unavailable = 14, 29 | DataLoss = 15, 30 | Unauthenticated = 16, 31 | } 32 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/common/common.ts: -------------------------------------------------------------------------------- 1 | export type GrpcTimeStamp = { 2 | nanos: number; 3 | seconds: number; 4 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/content/ContentType.ts: -------------------------------------------------------------------------------- 1 | import {GrpcTimeStamp} from "../common/common"; 2 | 3 | export type CollectionType = { 4 | id: string; 5 | name: string; 6 | identifier: string; 7 | createdAt: string; 8 | updatedAt: string; 9 | createdBy: string; 10 | updatedBy: string; 11 | action?: string; 12 | } 13 | export enum ContentFieldDataType { 14 | Bool = "Bool", 15 | TEXT = "TEXT", 16 | INT = "INT", 17 | FLOAT = "FLOAT", 18 | Array = "Array" 19 | } 20 | 21 | export enum ContentFieldFieldType { 22 | TEXT = "TEXT", 23 | TEXTAREA = "TEXTAREA", 24 | RICH_TEXT_EDITOR = "RICH_TEXT_EDITOR", 25 | NUMBER_TEXT_FIELD = "NUMBER_TEXT_FIELD", 26 | FLOAT_TEXT_FIELD = "FLOAT_TEXT_FIELD", 27 | SELECT = "Select", 28 | Checkbox = "Checkbox", 29 | Radio = "Radio", 30 | Switch = "Switch", 31 | Date = "Date" 32 | } 33 | 34 | export type ContentSelectFieldData = { 35 | label: string; 36 | value: string; 37 | } 38 | 39 | export type ContentCheckboxFieldData = { 40 | label: string; 41 | value: string; 42 | } 43 | 44 | export type ContentRadioFieldData = { 45 | label: string; 46 | value: string; 47 | } 48 | 49 | export type ContentFieldData = { 50 | content_select_field_options: Array; 51 | content_checkbox_field_data: Array; 52 | content_radio_field_data: Array; 53 | } 54 | 55 | export type ContentFieldType = { 56 | name: string; 57 | identifier: string; 58 | data_type: ContentFieldDataType; 59 | field_type: ContentFieldFieldType; 60 | field_content: ContentFieldFieldContent; 61 | field_data?: ContentFieldData 62 | } 63 | 64 | export type ContentFieldFieldContent = { 65 | text_value?: string, 66 | int_value?: number, 67 | float_value?: number, 68 | array_value?: Array, 69 | bool_value?: boolean, 70 | } 71 | 72 | export type ContentType = { 73 | id: string; 74 | name: string; 75 | identifier: string; 76 | createdAt: GrpcTimeStamp; 77 | createdBy: string; 78 | updatedAt: GrpcTimeStamp; 79 | updatedBy: string; 80 | content_fields: Array; 81 | action: string; 82 | } 83 | 84 | export type SaveContentType = { 85 | id?: String; 86 | name: string; 87 | content_type: string; 88 | identifier: string; 89 | content_fields: Array; 90 | } 91 | 92 | export type SaveContentFieldType = { 93 | name: string; 94 | identifier: string; 95 | data_type: ContentFieldDataType; 96 | field_type: ContentFieldFieldType; 97 | field_content: ContentFieldFieldContent; 98 | field_data?: ContentFieldData; 99 | } 100 | 101 | export type StoreCollectionType = { 102 | name: string; 103 | identifier: string; 104 | } 105 | export type UpdateCollectionType = { 106 | id: string; 107 | name: string; 108 | identifier: string; 109 | } 110 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/misc/PaginateType.ts: -------------------------------------------------------------------------------- 1 | export type PaginateType = { 2 | order?: string; 3 | page?: number; 4 | } 5 | -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/misc/SetupType.ts: -------------------------------------------------------------------------------- 1 | export type SetupType = { 2 | email: string; 3 | password: string; 4 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/src/types/setting/SettingType.ts: -------------------------------------------------------------------------------- 1 | export type SettingType = { 2 | id: string; 3 | identifier: string; 4 | value: string; 5 | created_at: string; 6 | updated_at: string; 7 | created_by: string; 8 | updated_by: string 9 | } 10 | 11 | export type SaveSettingType = { 12 | settings: Array; 13 | } -------------------------------------------------------------------------------- /ts-grpc-react-admin/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /ts-grpc-react-front/.env: -------------------------------------------------------------------------------- 1 | #REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | #REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | 4 | PORT=3010 5 | 6 | REACT_APP_FRONTEND_BASE_URL=http://demo.avored.com 7 | REACT_APP_BACKEND_BASE_URL=https://demo-api.avored.com 8 | -------------------------------------------------------------------------------- /ts-grpc-react-front/.env.example: -------------------------------------------------------------------------------- 1 | REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | -------------------------------------------------------------------------------- /ts-grpc-react-front/.env.production.local: -------------------------------------------------------------------------------- 1 | #REACT_APP_FRONTEND_BASE_URL=http://localhost:3000 2 | #REACT_APP_BACKEND_BASE_URL=http://localhost:50051 3 | 4 | PORT=3010 5 | 6 | REACT_APP_FRONTEND_BASE_URL=http://demo.avored.com 7 | REACT_APP_BACKEND_BASE_URL=https://demo-api.avored.com 8 | -------------------------------------------------------------------------------- /ts-grpc-react-front/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | 13 | # misc 14 | .DS_Store 15 | .env.local 16 | .env.development.local 17 | .env.test.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /ts-grpc-react-front/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `npm start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `npm test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `npm run build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `npm run eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /ts-grpc-react-front/build/asset-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "files": { 3 | "main.css": "/static/css/main.327d90b5.css", 4 | "main.js": "/static/js/main.7fd6454a.js", 5 | "static/media/main-hero.svg": "/static/media/main-hero.d1925a28ed0b905a765459c51e1fa8ff.svg", 6 | "static/media/avored.svg": "/static/media/avored.844fbcaa6b01372b345470d497f050fa.svg", 7 | "index.html": "/index.html", 8 | "main.327d90b5.css.map": "/static/css/main.327d90b5.css.map", 9 | "main.7fd6454a.js.map": "/static/js/main.7fd6454a.js.map" 10 | }, 11 | "entrypoints": [ 12 | "static/css/main.327d90b5.css", 13 | "static/js/main.7fd6454a.js" 14 | ] 15 | } -------------------------------------------------------------------------------- /ts-grpc-react-front/build/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/build/favicon.ico -------------------------------------------------------------------------------- /ts-grpc-react-front/build/index.html: -------------------------------------------------------------------------------- 1 | Avored rust content management (CMS)
-------------------------------------------------------------------------------- /ts-grpc-react-front/build/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/build/logo192.png -------------------------------------------------------------------------------- /ts-grpc-react-front/build/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/build/logo512.png -------------------------------------------------------------------------------- /ts-grpc-react-front/build/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-front/build/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ts-grpc-react-front/build/static/js/main.7fd6454a.js.LICENSE.txt: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Lodash 4 | * Copyright OpenJS Foundation and other contributors 5 | * Released under MIT license 6 | * Based on Underscore.js 1.8.3 7 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 8 | */ 9 | 10 | /** 11 | * @license React 12 | * react-dom-client.production.js 13 | * 14 | * Copyright (c) Meta Platforms, Inc. and affiliates. 15 | * 16 | * This source code is licensed under the MIT license found in the 17 | * LICENSE file in the root directory of this source tree. 18 | */ 19 | 20 | /** 21 | * @license React 22 | * react-dom.production.js 23 | * 24 | * Copyright (c) Meta Platforms, Inc. and affiliates. 25 | * 26 | * This source code is licensed under the MIT license found in the 27 | * LICENSE file in the root directory of this source tree. 28 | */ 29 | 30 | /** 31 | * @license React 32 | * react-jsx-runtime.production.js 33 | * 34 | * Copyright (c) Meta Platforms, Inc. and affiliates. 35 | * 36 | * This source code is licensed under the MIT license found in the 37 | * LICENSE file in the root directory of this source tree. 38 | */ 39 | 40 | /** 41 | * @license React 42 | * react.production.js 43 | * 44 | * Copyright (c) Meta Platforms, Inc. and affiliates. 45 | * 46 | * This source code is licensed under the MIT license found in the 47 | * LICENSE file in the root directory of this source tree. 48 | */ 49 | 50 | /** 51 | * @license React 52 | * scheduler.production.js 53 | * 54 | * Copyright (c) Meta Platforms, Inc. and affiliates. 55 | * 56 | * This source code is licensed under the MIT license found in the 57 | * LICENSE file in the root directory of this source tree. 58 | */ 59 | -------------------------------------------------------------------------------- /ts-grpc-react-front/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ts-grpc-react-front", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@grpc/grpc-js": "^1.13.0", 7 | "@grpc/proto-loader": "^0.7.13", 8 | "@headlessui/react": "^2.2.2", 9 | "@heroicons/react": "^2.2.0", 10 | "@hookform/resolvers": "^3.3.4", 11 | "@tailwindcss/cli": "^4.0.13", 12 | "@tailwindcss/forms": "^0.5.7", 13 | "@tailwindcss/postcss": "^4.0.13", 14 | "@tanstack/react-query": "^5.68.0", 15 | "@tanstack/react-table": "^8.21.2", 16 | "@testing-library/dom": "^10.4.0", 17 | "@testing-library/jest-dom": "^6.6.3", 18 | "@testing-library/react": "^16.2.0", 19 | "@testing-library/user-event": "^13.5.0", 20 | "@types/jest": "^27.5.2", 21 | "@types/lodash": "^4.17.16", 22 | "@types/luxon": "^3.6.2", 23 | "@types/node": "^16.18.126", 24 | "@types/react": "^19.0.10", 25 | "@types/react-dom": "^19.0.4", 26 | "axios": "^1.9.0", 27 | "concurrently": "^9.1.2", 28 | "github-markdown-css": "^5.8.1", 29 | "grpc-web": "^1.5.0", 30 | "grpc-web-client": "^0.7.0", 31 | "i18next": "^23.11.5", 32 | "joi": "^17.12.3", 33 | "lodash": "^4.17.21", 34 | "luxon": "^3.6.1", 35 | "postcss": "^8.5.3", 36 | "react": "^19.0.0", 37 | "react-dom": "^19.0.0", 38 | "react-hook-form": "^7.51.2", 39 | "react-i18next": "^14.1.2", 40 | "react-markdown": "^10.1.0", 41 | "react-query-grpc-gateway": "^1.4.0", 42 | "react-router-dom": "^7.3.0", 43 | "react-scripts": "5.0.1", 44 | "slug": "^9.1.0", 45 | "stream": "^0.0.3", 46 | "stream-browser": "^1.2.0", 47 | "tailwindcss": "^4.0.13", 48 | "tls": "^0.0.1", 49 | "typescript": "^4.9.5", 50 | "util": "^0.12.5", 51 | "web-vitals": "^2.1.4" 52 | }, 53 | "scripts": { 54 | "dev": "concurrently \"npx @tailwindcss/cli -i ./src/index.css -o ./public/output.css --watch\" \"react-scripts start\"", 55 | "build": "react-scripts build", 56 | "test": "react-scripts test", 57 | "eject": "react-scripts eject" 58 | }, 59 | "eslintConfig": { 60 | "extends": [ 61 | "react-app", 62 | "react-app/jest" 63 | ] 64 | }, 65 | "browserslist": { 66 | "production": [ 67 | ">0.2%", 68 | "not dead", 69 | "not op_mini all" 70 | ], 71 | "development": [ 72 | "last 1 chrome version", 73 | "last 1 firefox version", 74 | "last 1 safari version" 75 | ] 76 | }, 77 | "devDependencies": { 78 | "@protobuf-ts/plugin": "^2.9.6", 79 | "@types/slug": "^5.0.9" 80 | }, 81 | "browser": { 82 | "fs": false, 83 | "os": false, 84 | "path": false 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /ts-grpc-react-front/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/public/favicon.ico -------------------------------------------------------------------------------- /ts-grpc-react-front/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | Avored rust content management (CMS) 11 | 12 | 16 | 17 | 18 | 25 | 26 | 27 |
28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /ts-grpc-react-front/public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/public/logo192.png -------------------------------------------------------------------------------- /ts-grpc-react-front/public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/avored/avored-rust-cms/a626c731e26df78375b83e1535592086391fda11/ts-grpc-react-front/public/logo512.png -------------------------------------------------------------------------------- /ts-grpc-react-front/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-front/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {BrowserRouter, Route, Routes} from "react-router-dom"; 3 | import AppLayout from "./layout/AppLayout"; 4 | import HomePage from "./pages/home/HomePage"; 5 | import {PrivacyPage} from "./pages/PrivacyPage"; 6 | 7 | function App() { 8 | return ( 9 | 10 | 11 | }> 12 | } /> 13 | } /> 14 | 15 | 16 | 17 | ); 18 | } 19 | 20 | export default App; 21 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/hooks/useAxios.ts: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import {client} from '../lib/axios'; 3 | import _ from 'lodash'; 4 | 5 | export const useAxios = () => { 6 | useEffect(() => { 7 | const reqInterceptor = client.interceptors.request.use( 8 | config => { 9 | const token = localStorage.getItem("AUTH_TOKEN"); 10 | if (!_.isEmpty(token) && !config.headers['Authorization']) { 11 | 12 | config.headers['Authorization'] = `Bearer ${token}` 13 | } 14 | 15 | return config; 16 | }, (error) => Promise.reject(error) 17 | ) 18 | 19 | return () => { 20 | client.interceptors.request.eject(reqInterceptor); 21 | } 22 | }, []) 23 | 24 | return client; 25 | } 26 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/index.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | 3 | @theme { 4 | --color-primary-50: oklch(0.977 0.017 320.058); 5 | --color-primary-100: oklch(0.952 0.037 318.852); 6 | --color-primary-200: oklch(0.903 0.076 319.62); 7 | --color-primary-300: oklch(0.833 0.145 321.434); 8 | --color-primary-400: oklch(0.74 0.238 322.16); 9 | --color-primary-500: oklch(0.667 0.295 322.15); 10 | --color-primary-600: oklch(0.591 0.293 322.896); 11 | --color-primary-700: oklch(0.518 0.253 323.949); 12 | --color-primary-800: oklch(0.452 0.211 324.591); 13 | --color-primary-900: oklch(0.401 0.17 325.612); 14 | --color-primary-950: oklch(0.293 0.136 325.661); 15 | } 16 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client' 2 | import App from './App' 3 | import './index.css' 4 | import 'github-markdown-css' 5 | import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; 6 | 7 | const queryClient = new QueryClient(); 8 | 9 | ReactDOM.createRoot(document.getElementById('root')!).render( 10 | 11 | 12 | , 13 | ) 14 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/layout/AppLayout.tsx: -------------------------------------------------------------------------------- 1 | import { Outlet } from "react-router-dom"; 2 | import AppHeader from "./partials/AppHeader"; 3 | import AppFooter from "./partials/AppFooter"; 4 | 5 | const AppLayout = () => { 6 | return ( 7 |
8 | 9 | 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default AppLayout; 16 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/layout/partials/AppFooter.tsx: -------------------------------------------------------------------------------- 1 | const AppFooter = () => { 2 | return ( 3 |
4 |
5 |

6 | © AvoRed {new Date().getFullYear()} 7 | 8 | Privacy 9 | 10 |

11 |
12 | ); 13 | }; 14 | 15 | export default AppFooter; 16 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/layout/partials/AppHeader.tsx: -------------------------------------------------------------------------------- 1 | import avoredLogo from '../../assets/avored.svg' 2 | import {Link} from "react-router-dom"; 3 | 4 | const AppHeader = (() => { 5 | return ( 6 |
7 |
8 | 9 | AvoRed CMS 10 | 11 | Avored Rust CMS 12 | 13 | 14 | 15 |
16 |
17 | 18 |
19 |
20 | ) 21 | }) 22 | 23 | export default AppHeader 24 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/lib/axios.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | 3 | // const baseURL = import.meta.env.VITE_AVORED_BACKEND_BASE_URL + "/" 4 | // const token = import.meta.env.VITE_AVORED_CMS_TOKEN 5 | const baseURL = "http://localhost:5173/api" 6 | const token = "" 7 | 8 | export default axios.create({ 9 | baseURL 10 | }) 11 | 12 | // axios instance with 'withCredentials' flag 13 | export const client = axios.create({ 14 | baseURL, 15 | headers : { 16 | 'Content-Type' : 'application/json', 17 | 'Authorization': 'Bearer ' + token 18 | }, 19 | withCredentials: false 20 | }) 21 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/lib/page.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import {ContentFieldType} from "../types/CmsPageType"; 3 | 4 | export const GetElementValue = ((fields: Array, element_identifier: string) => { 5 | // console.log(fields, "fields"); 6 | const field = fields.find((ele: any) => { 7 | return ele.identifier === element_identifier 8 | }) 9 | console.log(field, "field") 10 | return _.get(field, 'field_content.text_value', '') 11 | }) 12 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/PrivacyPage.tsx: -------------------------------------------------------------------------------- 1 | import {GetCmsContentRequest} from "../grpc_generated/cms_pb"; 2 | import {useHomeCmsPage} from "./home/hooks/useHomeCmsPage"; 3 | import {CmsContentType, ContentFieldDataType, ContentFieldFieldType, ContentFieldType} from "../types/CmsPageType"; 4 | import _ from "lodash"; 5 | import {GetElementValue} from "../lib/page"; 6 | import Markdown from "react-markdown"; 7 | 8 | export const PrivacyPage = () => { 9 | const request = new GetCmsContentRequest(); 10 | request.setContentIdentifier("privacy-page"); 11 | request.setContentType("pages"); 12 | const get_cms_content_api_response = useHomeCmsPage(request) 13 | const get_content_model = get_cms_content_api_response?.data?.data ?? []; 14 | 15 | const values: CmsContentType = get_content_model as unknown as CmsContentType; 16 | 17 | const content_content_field_list: Array = get_cms_content_api_response?.data?.data?.contentFieldsList ?? []; 18 | 19 | if (values) { 20 | values.content_fields = []; 21 | content_content_field_list.map(content_field => { 22 | const grpc_content_field: ContentFieldType = { 23 | name: content_field.name, 24 | identifier: content_field.identifier, 25 | data_type: content_field.dataType as ContentFieldDataType, 26 | field_type: content_field.fieldType as ContentFieldFieldType, 27 | field_content: { 28 | text_value: content_field.fieldContent?.textValue ?? '', 29 | } 30 | } 31 | 32 | values.content_fields.push(grpc_content_field) 33 | 34 | return grpc_content_field 35 | }) 36 | } 37 | 38 | const GetPageFields = ((): Array => { 39 | return _.get(values, 'content_fields', []) 40 | }) 41 | 42 | 43 | return ( 44 | <> 45 |
46 | 47 |
48 |
49 | {GetElementValue(GetPageFields(), 'privacy-title')} 50 |
51 | 52 |
53 |
54 | 55 | {GetElementValue(GetPageFields(), 'privacy-content')} 56 | 57 |
58 |
59 | 60 |
61 |
62 | 63 | ) 64 | } 65 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/Test.tsx: -------------------------------------------------------------------------------- 1 | import avoredLogo from '/avored.svg' 2 | import {useState} from "react"; 3 | 4 | const Dashboard = (() => { 5 | const [count, setCount] = useState(0) 6 | return( 7 |
8 |
9 | 10 | AvoRed CMS 11 | 12 | 13 |

14 | AvoRed Cms Test 15 |

16 |
17 | 23 | 24 |
25 |
26 |
27 | ) 28 | }) 29 | 30 | export default Dashboard -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/home/HomePage.tsx: -------------------------------------------------------------------------------- 1 | import {ContactSection} from "./ContactSection"; 2 | import FeaturesSection from "./FeaturesSection"; 3 | import MainHeroSection from "./MainHeroSection"; 4 | import RepositoryInformation from "./RepositoryInformation"; 5 | import {useHomeCmsPage} from "./hooks/useHomeCmsPage"; 6 | import mainHeroImage from "../../assets/main-hero.svg"; 7 | import _ from "lodash"; 8 | import {CmsContentType, ContentFieldDataType, ContentFieldFieldType, ContentFieldType} from "../../types/CmsPageType"; 9 | import {GetCmsContentRequest} from "../../grpc_generated/cms_pb"; 10 | 11 | const HomePage = () => { 12 | const request = new GetCmsContentRequest(); 13 | request.setContentIdentifier("home-page"); 14 | request.setContentType("pages"); 15 | const get_cms_content_api_response = useHomeCmsPage(request) 16 | const get_content_model = get_cms_content_api_response?.data?.data ?? []; 17 | 18 | const values: CmsContentType = get_content_model as unknown as CmsContentType; 19 | 20 | const content_content_field_list: Array = get_cms_content_api_response?.data?.data?.contentFieldsList ?? []; 21 | 22 | if (values) { 23 | values.content_fields = []; 24 | content_content_field_list.map(content_field => { 25 | const grpc_content_field: ContentFieldType = { 26 | name: content_field.name, 27 | identifier: content_field.identifier, 28 | data_type: content_field.dataType as ContentFieldDataType, 29 | field_type: content_field.fieldType as ContentFieldFieldType, 30 | field_content: { 31 | text_value: content_field.fieldContent?.textValue ?? '', 32 | } 33 | } 34 | 35 | values.content_fields.push(grpc_content_field) 36 | 37 | return grpc_content_field 38 | }) 39 | } 40 | 41 | const GetPageFields = ((): Array => { 42 | return _.get(values, 'content_fields', []) 43 | }) 44 | return ( 45 | <> 46 |
47 | 48 | 49 |
50 | avored rust main hero 55 |
56 |
57 | 58 | 59 | 60 | 61 | 62 | ); 63 | }; 64 | export default HomePage; 65 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/home/hooks/useContactUsForm.ts: -------------------------------------------------------------------------------- 1 | import {useMutation} from "@tanstack/react-query"; 2 | import {CmsClient} from "../../../grpc_generated/CmsServiceClientPb"; 3 | import {SentContactFormRequest} from "../../../grpc_generated/cms_pb"; 4 | import {useNavigate} from "react-router-dom"; 5 | 6 | export const useContactUsForm = () => { 7 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 8 | const client = new CmsClient(backend_url); 9 | const redirect = useNavigate(); 10 | 11 | return useMutation({ 12 | mutationFn: async (request: SentContactFormRequest) => { 13 | let response = await client.sentContactForm(request, { 14 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 15 | }) 16 | return response; 17 | }, 18 | onSuccess: (res) => { 19 | if (res.getStatus()) { 20 | // localStorage.setItem("token", token); 21 | redirect("/"); 22 | } 23 | } 24 | }); 25 | }; 26 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/home/hooks/useHomeCmsPage.ts: -------------------------------------------------------------------------------- 1 | import {useQuery} from "@tanstack/react-query"; 2 | import {GetCmsContentRequest} from "../../../grpc_generated/cms_pb"; 3 | import {CmsClient} from "../../../grpc_generated/CmsServiceClientPb"; 4 | 5 | export const useHomeCmsPage = (request: GetCmsContentRequest) => { 6 | const backend_url: string = process.env.REACT_APP_BACKEND_BASE_URL ?? "http://localhost:50051"; 7 | const client = new CmsClient(backend_url); 8 | 9 | return useQuery({ 10 | queryKey: ['home-cms-page'], 11 | queryFn: async () => { 12 | let response = await client.getCmsContent(request, { 13 | 'Authorization': `Bearer ${localStorage.getItem('token')}` 14 | }) 15 | if (response.getStatus()) { 16 | // may be map a type and return a proper type 17 | return response.toObject(); 18 | } 19 | console.log('feel like error thrown... ') 20 | }, 21 | }) 22 | } 23 | 24 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/home/hooks/useRepositoryInformation.ts: -------------------------------------------------------------------------------- 1 | import { useQuery, UseQueryResult } from "@tanstack/react-query"; 2 | import { useAxios } from "../../../hooks/useAxios"; 3 | import { RepositoryInformationType } from "../../../types/RepositoryInformationType" 4 | import { AxiosResponse } from "axios"; 5 | 6 | export const useRepositoryInformation = (): UseQueryResult> => { 7 | const client = useAxios(); 8 | return useQuery({ 9 | queryKey: ['repository-information'], 10 | queryFn: (async () => { 11 | try { 12 | return await client.get("/repository-information") 13 | } catch (error) { 14 | //@todo display error 15 | } 16 | }) 17 | }) 18 | } 19 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/pages/home/schemas/useContactUsFormSchema.ts: -------------------------------------------------------------------------------- 1 | import Joi from 'joi'; 2 | 3 | 4 | export const useContactUsFormSchema = (() => { 5 | 6 | return Joi.object({ 7 | first_name : Joi.string().required().messages({ 8 | 'string.empty': "First name is required", 9 | }), 10 | last_name : Joi.string().required().messages({ 11 | 'string.empty': "Last name is required", 12 | }), 13 | phone : Joi.string().required().messages({ 14 | 'string.empty': "Phone is required", 15 | }), 16 | email : Joi.string().email({tlds: { allow: false }}).messages({ 17 | 'string.empty': "Email is required", 18 | 'string.email': 'Email is not in valid format.' 19 | }), 20 | message : Joi.string().required().messages({ 21 | 'string.empty': "Message is required", 22 | }) 23 | }); 24 | }) -------------------------------------------------------------------------------- /ts-grpc-react-front/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/types/CmsPageType.ts: -------------------------------------------------------------------------------- 1 | export type GrpcTimeStamp = { 2 | nanos: number; 3 | seconds: number; 4 | } 5 | 6 | export type CmsContentType = { 7 | id: string; 8 | name: string; 9 | identifier: string; 10 | createdAt: GrpcTimeStamp; 11 | createdBy: string; 12 | updatedAt: GrpcTimeStamp; 13 | updatedBy: string; 14 | content_fields: Array; 15 | action: string; 16 | } 17 | 18 | export enum ContentFieldDataType { 19 | TEXT = "TEXT", 20 | // INT = "INT", 21 | // Array_Text = "Array_Text" 22 | } 23 | 24 | export enum ContentFieldFieldType { 25 | TEXT = "TEXT", 26 | // INT = "INT", 27 | // Array_Text = "Array_Text" 28 | } 29 | 30 | export type ContentFieldType = { 31 | name: string; 32 | identifier: string; 33 | data_type: ContentFieldDataType; 34 | field_type: ContentFieldFieldType; 35 | field_content: ContentFieldFieldContent; 36 | } 37 | 38 | export type ContentFieldFieldContent = { 39 | text_value?: string, 40 | } 41 | 42 | -------------------------------------------------------------------------------- /ts-grpc-react-front/src/types/ContactUsType.ts: -------------------------------------------------------------------------------- 1 | export type ContactUsType = { 2 | first_name: string; 3 | last_name: string; 4 | email: string; 5 | phone: string; 6 | message: string; 7 | } -------------------------------------------------------------------------------- /ts-grpc-react-front/src/types/RepositoryInformationType.ts: -------------------------------------------------------------------------------- 1 | 2 | export type RepositoryInformationType = { 3 | status: boolean; 4 | data: { 5 | star: number; 6 | contribute: number; 7 | commit: number; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /ts-grpc-react-front/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | --------------------------------------------------------------------------------