├── .changeset ├── README.md └── config.json ├── .github └── workflows │ └── release.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── commitlint.config.js ├── docs ├── .editorconfig ├── .gitignore ├── CHANGELOG.md ├── README.md ├── babel.config.js ├── docs │ ├── concepts │ │ ├── _category_.json │ │ ├── labels.md │ │ ├── properties.md │ │ ├── records.md │ │ ├── relationships.md │ │ ├── search │ │ │ ├── _category_.json │ │ │ ├── aggregations.md │ │ │ ├── introduction.md │ │ │ ├── labels.md │ │ │ ├── pagination-order.md │ │ │ └── where.md │ │ ├── storage.md │ │ └── transactions.mdx │ ├── get-started │ │ ├── _category_.json │ │ ├── get-api-token.mdx │ │ └── quick-tutorial.mdx │ ├── index.mdx │ ├── python-sdk │ │ ├── _category_.json │ │ ├── introduction.md │ │ ├── labels.md │ │ ├── properties.md │ │ ├── python-reference │ │ │ ├── _category_.json │ │ │ ├── record.md │ │ │ └── transaction.md │ │ ├── records │ │ │ ├── _category_.json │ │ │ ├── create-records.md │ │ │ ├── delete-records.md │ │ │ ├── get-records.md │ │ │ ├── import-data.md │ │ │ └── update-records.md │ │ ├── relationships.md │ │ └── transactions.md │ ├── rest-api │ │ ├── _category_.json │ │ ├── introduction.md │ │ ├── labels.md │ │ ├── properties.md │ │ ├── records │ │ │ ├── _category_.json │ │ │ ├── create-records.md │ │ │ ├── delete-records.md │ │ │ ├── export-data.md │ │ │ ├── get-records.md │ │ │ ├── import-data.md │ │ │ └── update-records.md │ │ ├── relationships.md │ │ └── transactions.md │ ├── tutorials │ │ ├── _category_.json │ │ ├── configuring-dashboard.md │ │ ├── deployment.md │ │ ├── local-setup.md │ │ └── reusable-search-query.mdx │ └── typescript-sdk │ │ ├── _category_.json │ │ ├── introduction.md │ │ ├── labels.md │ │ ├── models.md │ │ ├── properties.md │ │ ├── records │ │ ├── _category_.json │ │ ├── create-records.md │ │ ├── delete-records.md │ │ ├── get-records.md │ │ ├── import-data.md │ │ └── update-records.md │ │ ├── relationships.md │ │ ├── transactions.md │ │ └── typescript-reference │ │ ├── DBRecord.md │ │ ├── DBRecordInstance.md │ │ ├── DBRecordTarget.md │ │ ├── DBRecordsArrayInstance.md │ │ ├── Model.md │ │ ├── RelationTarget.md │ │ ├── RushDB.md │ │ ├── SearchQuery.md │ │ ├── Transaction.md │ │ └── _category_.json ├── docusaurus.config.ts ├── package.json ├── plugins │ └── tailwind-config.cjs ├── sidebars.ts ├── src │ ├── components │ │ └── ui │ │ │ ├── lib │ │ │ └── utils.ts │ │ │ ├── method.tsx │ │ │ └── tabs.tsx │ └── css │ │ └── custom.css ├── static │ ├── .nojekyll │ └── img │ │ ├── favicon.ico │ │ ├── favicon.png │ │ ├── favicon.svg │ │ ├── logo.svg │ │ ├── og.png │ │ ├── quick-start │ │ ├── create-project-screen.png │ │ └── create-token-screen.png │ │ └── social-card.png ├── tailwind.config.js └── tsconfig.json ├── jest.config.js ├── package.json ├── packages └── javascript-sdk │ ├── .eslintrc │ ├── .gitignore │ ├── .npmignore │ ├── CHANGELOG.md │ ├── CONTIBUTING.md │ ├── README.md │ ├── esbuild.config.cjs │ ├── package.json │ ├── src │ ├── api │ │ ├── api.ts │ │ ├── constants.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils.ts │ ├── common │ │ ├── constants.ts │ │ └── utils.ts │ ├── index.node.ts │ ├── index.worker.ts │ ├── network │ │ ├── FetchHttpClient.ts │ │ ├── HttpClient.ts │ │ ├── NodeHttpClient.ts │ │ ├── index.ts │ │ └── types.ts │ ├── sdk │ │ ├── constants.ts │ │ ├── errors.ts │ │ ├── index.ts │ │ ├── model.ts │ │ ├── record.ts │ │ ├── sdk.ts │ │ ├── transaction.ts │ │ ├── types.ts │ │ └── utils.ts │ └── types │ │ ├── expressions.ts │ │ ├── index.ts │ │ ├── query.ts │ │ ├── schema.ts │ │ ├── utils.ts │ │ └── value.ts │ └── tsconfig.json ├── platform ├── .dockerignore ├── Dockerfile ├── core │ ├── .env.example │ ├── .eslintrc.js │ ├── .gitignore │ ├── .terraform.lock.hcl │ ├── CHANGELOG.md │ ├── README.md │ ├── commitlint.config.js │ ├── docker-compose.yml │ ├── main.tf │ ├── neo4j-plugins │ │ ├── apoc-5.25.1-core.jar │ │ └── graph-data-science-2.12.0.jar │ ├── nest-cli.json │ ├── package.json │ ├── src │ │ ├── app-settings.controller.ts │ │ ├── app.controller.ts │ │ ├── app.module.ts │ │ ├── app.service.ts │ │ ├── backup │ │ │ ├── backup.module.ts │ │ │ └── backup.service.ts │ │ ├── cli.ts │ │ ├── cli │ │ │ └── cli.service.ts │ │ ├── common │ │ │ ├── QueryBuilder.ts │ │ │ ├── constants.ts │ │ │ ├── decorators │ │ │ │ └── common-response.decorator.ts │ │ │ ├── global-exception.filter.ts │ │ │ ├── interceptors │ │ │ │ ├── errors.interceptor.ts │ │ │ │ ├── exclude-null-response.interceptor.ts │ │ │ │ ├── not-found.interceptor.ts │ │ │ │ ├── run-side-effect.interceptor.ts │ │ │ │ └── transform-response.interceptor.ts │ │ │ ├── middlewares │ │ │ │ └── db-context.middleware.ts │ │ │ ├── types │ │ │ │ ├── request.ts │ │ │ │ └── utils.ts │ │ │ ├── utils │ │ │ │ ├── arrayIsConsistent.ts │ │ │ │ ├── asyncAssertExistsOrThrow.ts │ │ │ │ ├── checkTypeAndNameUniqueness.ts │ │ │ │ ├── collectValuesByKeysFromObject.ts │ │ │ │ ├── containsAllowedKeys.ts │ │ │ │ ├── fetchRetry.ts │ │ │ │ ├── getCurrentISO.ts │ │ │ │ ├── getISOWithMicrosecond.ts │ │ │ │ ├── isArray.ts │ │ │ │ ├── isDevMode.ts │ │ │ │ ├── isEmptyObject.ts │ │ │ │ ├── isNumeric.ts │ │ │ │ ├── isObject.ts │ │ │ │ ├── isPrimitive.ts │ │ │ │ ├── isPrimitiveArray.ts │ │ │ │ ├── isProductionMode.ts │ │ │ │ ├── omit.ts │ │ │ │ ├── pickPrimitives.ts │ │ │ │ ├── randomString.ts │ │ │ │ ├── toBolean.ts │ │ │ │ └── uniqArray.ts │ │ │ └── validation │ │ │ │ ├── utils.ts │ │ │ │ └── validation.pipe.ts │ │ ├── core │ │ │ ├── common │ │ │ │ ├── constants.ts │ │ │ │ ├── normalizeRecord.ts │ │ │ │ └── types.ts │ │ │ ├── core.module.ts │ │ │ ├── entity │ │ │ │ ├── dto │ │ │ │ │ ├── create-entity.dto.ts │ │ │ │ │ └── edit-entity.dto.ts │ │ │ │ ├── entity-query.service.ts │ │ │ │ ├── entity-write.guard.ts │ │ │ │ ├── entity.constants.ts │ │ │ │ ├── entity.controller.ts │ │ │ │ ├── entity.module.ts │ │ │ │ ├── entity.service.ts │ │ │ │ ├── entity.types.ts │ │ │ │ ├── import-export │ │ │ │ │ ├── dto │ │ │ │ │ │ ├── import-csv.dto.ts │ │ │ │ │ │ └── import-json.dto.ts │ │ │ │ │ ├── export.controller.ts │ │ │ │ │ ├── export.service.ts │ │ │ │ │ ├── import-export.module.ts │ │ │ │ │ ├── import.controller.ts │ │ │ │ │ ├── import.service.ts │ │ │ │ │ ├── import.types.ts │ │ │ │ │ └── validation │ │ │ │ │ │ └── schemas │ │ │ │ │ │ └── import.schema.ts │ │ │ │ ├── model │ │ │ │ │ ├── entity.interface.ts │ │ │ │ │ ├── entity.model.ts │ │ │ │ │ └── entity.repository.ts │ │ │ │ └── validation │ │ │ │ │ └── schemas │ │ │ │ │ ├── create-entity.schema.ts │ │ │ │ │ └── edit-entity.schema.ts │ │ │ ├── labels │ │ │ │ └── controller.ts │ │ │ ├── property │ │ │ │ ├── dto │ │ │ │ │ ├── delete-property.dto.ts │ │ │ │ │ ├── property.dto.ts │ │ │ │ │ ├── records-by-property.dto.ts │ │ │ │ │ ├── update-property-value.dto.ts │ │ │ │ │ └── update-property.dto.ts │ │ │ │ ├── entity │ │ │ │ │ └── property.entity.ts │ │ │ │ ├── model │ │ │ │ │ ├── property.interface.ts │ │ │ │ │ ├── property.model.ts │ │ │ │ │ └── property.repository.ts │ │ │ │ ├── property-query.service.ts │ │ │ │ ├── property.constants.ts │ │ │ │ ├── property.controller.ts │ │ │ │ ├── property.module.ts │ │ │ │ ├── property.service.ts │ │ │ │ ├── property.types.ts │ │ │ │ ├── property.utils.ts │ │ │ │ └── validation │ │ │ │ │ ├── property-values.pipe.ts │ │ │ │ │ └── schemas │ │ │ │ │ └── property.schema.ts │ │ │ ├── relationships │ │ │ │ ├── controller.ts │ │ │ │ ├── dto │ │ │ │ │ ├── attach.dto.ts │ │ │ │ │ └── detach.dto.ts │ │ │ │ └── validation │ │ │ │ │ └── schemas │ │ │ │ │ └── relations.schema.ts │ │ │ ├── search │ │ │ │ ├── dto │ │ │ │ │ └── search.dto.ts │ │ │ │ ├── parser │ │ │ │ │ ├── aggregate.ts │ │ │ │ │ ├── buildQuery.ts │ │ │ │ │ ├── buildRelatedRecordQueryPart.ts │ │ │ │ │ ├── buildWhereClause.ts │ │ │ │ │ ├── constants.ts │ │ │ │ │ ├── errors.ts │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── orderBy.ts │ │ │ │ │ ├── pagination.ts │ │ │ │ │ ├── parseComparison.ts │ │ │ │ │ ├── parseCurrentLevel.ts │ │ │ │ │ ├── parseSubQuery.ts │ │ │ │ │ ├── pickRecordLabel.ts │ │ │ │ │ ├── processCriteria.ts │ │ │ │ │ ├── processLogicalGroupedCriteria.ts │ │ │ │ │ ├── projectIdInline.ts │ │ │ │ │ ├── singleLabelPart.ts │ │ │ │ │ ├── tests │ │ │ │ │ │ ├── aggregate.spec.ts │ │ │ │ │ │ ├── buildCompleteQuery.spec.ts │ │ │ │ │ │ ├── isCurrentLevelCriteria.spec.ts │ │ │ │ │ │ ├── isPropertyCriteria.spec.ts │ │ │ │ │ │ ├── parse-condition.spec.ts │ │ │ │ │ │ ├── parseQuery.spec.ts │ │ │ │ │ │ ├── prepareQuery.spec.ts │ │ │ │ │ │ └── splitQuery.spec.ts │ │ │ │ │ ├── types.ts │ │ │ │ │ └── utils.ts │ │ │ │ ├── search.constants.ts │ │ │ │ ├── search.types.ts │ │ │ │ └── validation │ │ │ │ │ └── schemas │ │ │ │ │ └── search.schema.ts │ │ │ └── transactions │ │ │ │ ├── transaction-context.ts │ │ │ │ ├── transaction.controller.ts │ │ │ │ ├── transaction.module.ts │ │ │ │ ├── transaction.service.ts │ │ │ │ └── transaction.types.ts │ │ ├── dashboard │ │ │ ├── auth │ │ │ │ ├── auth.controller.ts │ │ │ │ ├── auth.module.ts │ │ │ │ ├── auth.service.ts │ │ │ │ ├── auth.types.ts │ │ │ │ ├── captcha │ │ │ │ │ ├── captcha.controller.ts │ │ │ │ │ ├── captcha.module.ts │ │ │ │ │ ├── captcha.service.ts │ │ │ │ │ └── constants.ts │ │ │ │ ├── dto │ │ │ │ │ ├── get-oauth.dto.ts │ │ │ │ │ ├── login.dto.ts │ │ │ │ │ └── reset-password-auth.dto.ts │ │ │ │ ├── email-confirmation │ │ │ │ │ └── email-confirmation.service.ts │ │ │ │ ├── encryption │ │ │ │ │ └── encryption.service.ts │ │ │ │ ├── guards │ │ │ │ │ ├── email-confirmed.guard.ts │ │ │ │ │ ├── global-auth.guard.ts │ │ │ │ │ └── is-related-to-project.guard.ts │ │ │ │ └── providers │ │ │ │ │ ├── github │ │ │ │ │ ├── github.controller.ts │ │ │ │ │ └── github.service.ts │ │ │ │ │ └── google │ │ │ │ │ ├── google.controller.ts │ │ │ │ │ ├── google.module.ts │ │ │ │ │ └── google.service.ts │ │ │ ├── billing │ │ │ │ ├── billing.module.ts │ │ │ │ ├── guards │ │ │ │ │ ├── custom-db-availability.guard.ts │ │ │ │ │ ├── custom-db-write-restriction.guard.ts │ │ │ │ │ └── plan-limits.guard.ts │ │ │ │ └── stripe │ │ │ │ │ ├── interfaces │ │ │ │ │ ├── stripe.constans.ts │ │ │ │ │ └── stripe.types.ts │ │ │ │ │ ├── plans.dto.ts │ │ │ │ │ ├── stripe.controller.ts │ │ │ │ │ ├── stripe.module.ts │ │ │ │ │ ├── stripe.service.ts │ │ │ │ │ └── stripe.utils.ts │ │ │ ├── common │ │ │ │ ├── constants.ts │ │ │ │ └── interceptors │ │ │ │ │ └── change-cors.interceptor.ts │ │ │ ├── dashboard.module.ts │ │ │ ├── mail │ │ │ │ ├── mail.module.ts │ │ │ │ ├── mail.service.ts │ │ │ │ └── templates │ │ │ │ │ ├── accept-invite.ejs │ │ │ │ │ ├── forgot-password.ejs │ │ │ │ │ └── welcome.ejs │ │ │ ├── project │ │ │ │ ├── dto │ │ │ │ │ ├── create-project.dto.ts │ │ │ │ │ └── update-project.dto.ts │ │ │ │ ├── entity │ │ │ │ │ └── project.entity.ts │ │ │ │ ├── model │ │ │ │ │ ├── project.interface.ts │ │ │ │ │ ├── project.model.ts │ │ │ │ │ └── project.repository.ts │ │ │ │ ├── project-query.service.ts │ │ │ │ ├── project.controller.ts │ │ │ │ ├── project.module.ts │ │ │ │ ├── project.service.ts │ │ │ │ └── project.types.ts │ │ │ ├── throttle │ │ │ │ ├── guards │ │ │ │ │ └── throttle-by-token.guard.ts │ │ │ │ └── throttle.service.ts │ │ │ ├── token │ │ │ │ ├── dto │ │ │ │ │ └── create-token.dto.ts │ │ │ │ ├── entity │ │ │ │ │ └── token.entity.ts │ │ │ │ ├── model │ │ │ │ │ ├── token.interface.ts │ │ │ │ │ ├── token.model.ts │ │ │ │ │ └── token.repository.ts │ │ │ │ ├── token-query.service.ts │ │ │ │ ├── token.constants.ts │ │ │ │ ├── token.controller.ts │ │ │ │ ├── token.module.ts │ │ │ │ └── token.service.ts │ │ │ ├── user │ │ │ │ ├── decorators │ │ │ │ │ └── user.decorator.ts │ │ │ │ ├── dto │ │ │ │ │ ├── create-user.dto.ts │ │ │ │ │ ├── get-user.dto.ts │ │ │ │ │ └── update-user.dto.ts │ │ │ │ ├── interfaces │ │ │ │ │ ├── authenticated-user.interface.ts │ │ │ │ │ ├── user-claims.interface.ts │ │ │ │ │ ├── user-properties.interface.ts │ │ │ │ │ └── user.constants.ts │ │ │ │ ├── model │ │ │ │ │ ├── user.interface.ts │ │ │ │ │ ├── user.model.ts │ │ │ │ │ └── user.repository.ts │ │ │ │ ├── user.controller.ts │ │ │ │ ├── user.entity.ts │ │ │ │ ├── user.module.ts │ │ │ │ ├── user.service.ts │ │ │ │ └── user.utils.ts │ │ │ └── workspace │ │ │ │ ├── dto │ │ │ │ ├── create-workspace.dto.ts │ │ │ │ ├── edit-workspace.dto.ts │ │ │ │ ├── invite-to-workspace.dto.ts │ │ │ │ ├── recompute-access-list.dto.ts │ │ │ │ ├── remove-pending-invite.dto.ts │ │ │ │ └── revoke-access.dto.ts │ │ │ │ ├── entity │ │ │ │ └── workspace.entity.ts │ │ │ │ ├── guards │ │ │ │ └── workspaces-count.guard.ts │ │ │ │ ├── model │ │ │ │ ├── workspace.interface.ts │ │ │ │ ├── workspace.model.ts │ │ │ │ └── workspace.repository.ts │ │ │ │ ├── workspace-query.service.ts │ │ │ │ ├── workspace.constants.ts │ │ │ │ ├── workspace.controller.ts │ │ │ │ ├── workspace.module.ts │ │ │ │ ├── workspace.service.ts │ │ │ │ └── workspace.types.ts │ │ ├── database │ │ │ ├── database.module.ts │ │ │ ├── db-connection │ │ │ │ ├── db-connection.module.ts │ │ │ │ └── db-connection.service.ts │ │ │ ├── db-context.ts │ │ │ ├── neogma-dynamic │ │ │ │ ├── composite-neogma.service.ts │ │ │ │ ├── custom-transaction.decorator.ts │ │ │ │ ├── custom-transaction.interceptor.ts │ │ │ │ ├── neogma-dynamic.module.ts │ │ │ │ ├── neogma-dynamic.service.ts │ │ │ │ └── preferred-transaction.decorator.ts │ │ │ └── neogma │ │ │ │ ├── neo4j-error.filter.ts │ │ │ │ ├── neogma-config.interface.ts │ │ │ │ ├── neogma-data.interceptor.ts │ │ │ │ ├── neogma-transaction.interceptor.ts │ │ │ │ ├── neogma.constants.ts │ │ │ │ ├── neogma.module.ts │ │ │ │ ├── neogma.service.ts │ │ │ │ ├── neogma.util.ts │ │ │ │ ├── repository │ │ │ │ ├── repository.module.ts │ │ │ │ ├── repository.service.ts │ │ │ │ └── types.ts │ │ │ │ └── transaction.decorator.ts │ │ └── main.ts │ ├── test │ │ ├── app.e2e-spec.ts │ │ └── jest-e2e.json │ ├── tsconfig.build.json │ └── tsconfig.json ├── dashboard │ ├── .env.example │ ├── .eslintrc │ ├── .gitignore │ ├── CHANGELOG.md │ ├── README.md │ ├── config │ │ └── colors.ts │ ├── global.d.ts │ ├── index.html │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ ├── android-chrome-192x192.png │ │ ├── android-chrome-512x512.png │ │ ├── apple-touch-icon.png │ │ ├── browserconfig.xml │ │ ├── favicon-150x150.png │ │ ├── favicon-16x16.png │ │ ├── favicon-32x32.png │ │ ├── favicon-96x96.png │ │ ├── favicon.ico │ │ ├── favicon.svg │ │ ├── site.webmanifest │ │ ├── web-app-manifest-192x192.png │ │ └── web-app-manifest-512x512.png │ ├── src │ │ ├── App.tsx │ │ ├── assets │ │ │ └── icons │ │ │ │ ├── approx_equals.svg │ │ │ │ ├── approx_not_equals.svg │ │ │ │ ├── arrows-grid.svg │ │ │ │ ├── contains.svg │ │ │ │ ├── ends_with.svg │ │ │ │ ├── equals.svg │ │ │ │ ├── greater.svg │ │ │ │ ├── greater_or_equals.svg │ │ │ │ ├── in_range.svg │ │ │ │ ├── less.svg │ │ │ │ ├── less_or_equals.svg │ │ │ │ ├── not_equals.svg │ │ │ │ ├── not_in_range.svg │ │ │ │ └── starts_with.svg │ │ ├── config.ts │ │ ├── elements │ │ │ ├── Badge.tsx │ │ │ ├── Banner.tsx │ │ │ ├── Button.tsx │ │ │ ├── ButtonGroup.module.css │ │ │ ├── ButtonGroup.tsx │ │ │ ├── Calendar.tsx │ │ │ ├── Card.tsx │ │ │ ├── Checkbox.tsx │ │ │ ├── Combobox.tsx │ │ │ ├── Command.tsx │ │ │ ├── ConfirmDialog.tsx │ │ │ ├── Dialog.tsx │ │ │ ├── Disclosure.tsx │ │ │ ├── Divider.tsx │ │ │ ├── Editor.tsx │ │ │ ├── FormField.tsx │ │ │ ├── GlobalNotification.tsx │ │ │ ├── GraphIcon.tsx │ │ │ ├── HoverCard.tsx │ │ │ ├── IconButton │ │ │ │ └── index.tsx │ │ │ ├── Input.tsx │ │ │ ├── Kbd.tsx │ │ │ ├── Label.tsx │ │ │ ├── Link.tsx │ │ │ ├── Logo │ │ │ │ └── index.tsx │ │ │ ├── Menu.tsx │ │ │ ├── Message.tsx │ │ │ ├── NothingFound.tsx │ │ │ ├── PageHeader.tsx │ │ │ ├── Paginator.tsx │ │ │ ├── Popover.tsx │ │ │ ├── RadioGroup.tsx │ │ │ ├── SearchSelect.tsx │ │ │ ├── Select.tsx │ │ │ ├── Setting.tsx │ │ │ ├── Sheet.tsx │ │ │ ├── Skeleton.tsx │ │ │ ├── Slider.tsx │ │ │ ├── Spinner.tsx │ │ │ ├── StatusDialog.tsx │ │ │ ├── SvgIcon.tsx │ │ │ ├── Switch.tsx │ │ │ ├── Table │ │ │ │ └── index.tsx │ │ │ ├── Tabs.tsx │ │ │ ├── Toast.tsx │ │ │ └── Tooltip.tsx │ │ ├── features │ │ │ ├── auth │ │ │ │ ├── components │ │ │ │ │ ├── ConfirmEmailNotification.tsx │ │ │ │ │ ├── GitHubButton.tsx │ │ │ │ │ ├── GoogleButton.tsx │ │ │ │ │ └── UserMenu.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── stores │ │ │ │ │ ├── auth.ts │ │ │ │ │ ├── settings.ts │ │ │ │ │ ├── token.ts │ │ │ │ │ └── user.ts │ │ │ │ └── types.ts │ │ │ ├── billing │ │ │ │ ├── components │ │ │ │ │ ├── CheckoutButton.tsx │ │ │ │ │ ├── CurrentSubscriptionInfo.tsx │ │ │ │ │ ├── LimitReachedDialog.tsx │ │ │ │ │ └── PaymentCallbackDialog.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── stores │ │ │ │ │ ├── checkout.ts │ │ │ │ │ └── plans.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── labels │ │ │ │ ├── components │ │ │ │ │ ├── FilterLabel.tsx │ │ │ │ │ ├── LabelColorIcon.tsx │ │ │ │ │ ├── LabelName.tsx │ │ │ │ │ └── SelectLabels.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── onboarding │ │ │ │ ├── assets │ │ │ │ │ ├── js-logo.png │ │ │ │ │ ├── python-logo.png │ │ │ │ │ ├── ruby-logo.png │ │ │ │ │ └── ts-logo.png │ │ │ │ ├── components │ │ │ │ │ ├── OnboardingStep.tsx │ │ │ │ │ ├── SelectSdkLanguage.tsx │ │ │ │ │ └── steps │ │ │ │ │ │ ├── ClientLibrariesStep.tsx │ │ │ │ │ │ ├── ExploreDocsStep.tsx │ │ │ │ │ │ ├── UseSdkStep.tsx │ │ │ │ │ │ ├── WelcomeStep.tsx │ │ │ │ │ │ └── index.tsx │ │ │ │ ├── constants.ts │ │ │ │ └── types.ts │ │ │ ├── projects │ │ │ │ ├── components │ │ │ │ │ ├── ChangeProjectMenu.tsx │ │ │ │ │ ├── DeleteProjectDialog.tsx │ │ │ │ │ ├── GraphView.tsx │ │ │ │ │ ├── ProjectMenu.tsx │ │ │ │ │ ├── ProjectRecords.tsx │ │ │ │ │ ├── ProjectTabs.tsx │ │ │ │ │ ├── RawApiView.tsx │ │ │ │ │ ├── RecordDataTab.tsx │ │ │ │ │ ├── RecordSheet.tsx │ │ │ │ │ ├── RecordsHeader.tsx │ │ │ │ │ ├── RelatedRecordsTab.tsx │ │ │ │ │ ├── SearchBox.tsx │ │ │ │ │ ├── SelectCombineFiltersMode.tsx │ │ │ │ │ ├── SelectEntityApi.tsx │ │ │ │ │ ├── SelectViewMode.tsx │ │ │ │ │ └── index.tsx │ │ │ │ ├── stores │ │ │ │ │ ├── current-project.ts │ │ │ │ │ ├── current-record.ts │ │ │ │ │ ├── hidden-fields.ts │ │ │ │ │ ├── id.ts │ │ │ │ │ ├── project.ts │ │ │ │ │ └── raw-api.ts │ │ │ │ ├── types.ts │ │ │ │ └── utils.ts │ │ │ ├── properties │ │ │ │ ├── components │ │ │ │ │ ├── PropertiesList.tsx │ │ │ │ │ ├── PropertyName.tsx │ │ │ │ │ ├── PropertyTypeIcon.tsx │ │ │ │ │ ├── PropertyValue.tsx │ │ │ │ │ └── PropertyValueTooltip.tsx │ │ │ │ └── utils.ts │ │ │ ├── records │ │ │ │ ├── batchData.json │ │ │ │ ├── components │ │ │ │ │ ├── ApiRecordsModal.tsx │ │ │ │ │ ├── ImportRecords.tsx │ │ │ │ │ ├── RecordTitle.tsx │ │ │ │ │ ├── RecordsBatchActionsBar.tsx │ │ │ │ │ └── RecordsTable.tsx │ │ │ │ ├── constants.ts │ │ │ │ ├── stores │ │ │ │ │ ├── actionbar.ts │ │ │ │ │ ├── batch.ts │ │ │ │ │ ├── mutations.ts │ │ │ │ │ └── related-actionbar.ts │ │ │ │ └── types.ts │ │ │ ├── search │ │ │ │ ├── components │ │ │ │ │ ├── FilterPopover.tsx │ │ │ │ │ └── SearchOperationIcon.tsx │ │ │ │ ├── constants.ts │ │ │ │ └── types.ts │ │ │ ├── tokens │ │ │ │ ├── components │ │ │ │ │ ├── AddToken.tsx │ │ │ │ │ └── TokensList.tsx │ │ │ │ ├── stores │ │ │ │ │ └── tokens.ts │ │ │ │ └── types.ts │ │ │ ├── tour │ │ │ │ ├── components │ │ │ │ │ ├── CustomTooltip.tsx │ │ │ │ │ ├── ErrorBoundary.tsx │ │ │ │ │ ├── OnboardingInit.tsx │ │ │ │ │ └── OnboardingTour.tsx │ │ │ │ ├── config │ │ │ │ │ └── steps.tsx │ │ │ │ ├── hooks │ │ │ │ │ └── useWaitForSelector.tsx │ │ │ │ ├── stores │ │ │ │ │ └── tour.ts │ │ │ │ └── types.ts │ │ │ └── workspaces │ │ │ │ ├── components │ │ │ │ ├── ChangeWorkspaceMenu.tsx │ │ │ │ └── DeleteWorkspaceDialog.tsx │ │ │ │ ├── layout │ │ │ │ └── WorkspacesLayout.tsx │ │ │ │ ├── stores │ │ │ │ ├── current-workspace.ts │ │ │ │ ├── current.ts │ │ │ │ ├── invitation.ts │ │ │ │ ├── invite.ts │ │ │ │ ├── mutations.ts │ │ │ │ ├── projects.ts │ │ │ │ ├── users.ts │ │ │ │ └── workspaces.ts │ │ │ │ └── types.ts │ │ ├── hooks │ │ │ ├── useClickOutside.ts │ │ │ ├── useControllableState.ts │ │ │ ├── useDebounce.ts │ │ │ ├── useFocusOutside.ts │ │ │ ├── useHotkeys.ts │ │ │ ├── useIntersection.ts │ │ │ ├── useSize │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── useSize.ts │ │ │ └── useTimeout.ts │ │ ├── index.css │ │ ├── layout │ │ │ ├── AuthLayout │ │ │ │ └── index.tsx │ │ │ ├── ProjectLayout │ │ │ │ └── index.tsx │ │ │ ├── PublicLayout │ │ │ │ └── index.tsx │ │ │ └── RootLayout │ │ │ │ ├── PageTabs.tsx │ │ │ │ └── index.tsx │ │ ├── lib │ │ │ ├── api.ts │ │ │ ├── fetcher.ts │ │ │ ├── form.ts │ │ │ ├── formatters.ts │ │ │ ├── logger.ts │ │ │ ├── mouse.ts │ │ │ ├── nanorequest.ts │ │ │ ├── router.ts │ │ │ ├── sdk.ts │ │ │ ├── utils.ts │ │ │ └── window.ts │ │ ├── main.tsx │ │ ├── pages │ │ │ ├── 404.tsx │ │ │ ├── auth │ │ │ │ ├── confirmEmail.tsx │ │ │ │ ├── github.tsx │ │ │ │ └── google.tsx │ │ │ ├── forgot-password.tsx │ │ │ ├── profile.tsx │ │ │ ├── project │ │ │ │ ├── help.tsx │ │ │ │ ├── new.tsx │ │ │ │ ├── records.tsx │ │ │ │ ├── settings.tsx │ │ │ │ └── tokens.tsx │ │ │ ├── signin.tsx │ │ │ ├── signup.tsx │ │ │ └── workspace │ │ │ │ ├── billing.tsx │ │ │ │ ├── join.tsx │ │ │ │ ├── new.tsx │ │ │ │ ├── projects.tsx │ │ │ │ ├── settings.tsx │ │ │ │ └── users.tsx │ │ ├── types.ts │ │ └── vite-env.d.ts │ ├── tailwind.config.ts │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts └── package.json ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── rushdb-logo.svg ├── tsconfig.json └── website ├── .eslintrc ├── .gitignore ├── .prettierignore ├── CHANGELOG.md ├── README.md ├── next-sitemap.config.js ├── next.config.js ├── package.json ├── postcss.config.js ├── public ├── android-chrome-192x192.png ├── android-chrome-512x512.png ├── apple-touch-icon.png ├── browserconfig.xml ├── favicon-150x150.png ├── favicon-16x16.png ├── favicon-32x32.png ├── favicon-96x96.png ├── favicon.ico ├── favicon.png ├── favicon.svg ├── images │ ├── blog │ │ └── default-cover.png │ ├── hero-bg.svg │ ├── python-logo.svg │ └── typescript-logo.svg ├── logo.png ├── mstile-150x150.png ├── opengraph-image.png ├── robots.txt ├── site.webmanifest ├── sitemap-0.xml ├── sitemap.xml ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png ├── src ├── components │ ├── Button.tsx │ ├── CallToAction.tsx │ ├── Chip.tsx │ ├── CodeBlock.tsx │ ├── CodeBlockWithLanguageSelector.tsx │ ├── CookiesConsent.tsx │ ├── CopyButton.tsx │ ├── Faq.tsx │ ├── Grid.tsx │ ├── IconButton.tsx │ ├── Icons │ │ ├── Discord.tsx │ │ └── GitHub.tsx │ ├── Layout │ │ ├── Footer │ │ │ └── index.tsx │ │ ├── IconDiscord.tsx │ │ ├── IconX.tsx │ │ ├── header │ │ │ └── index.tsx │ │ └── index.tsx │ ├── LetterTypingText.tsx │ ├── Link.tsx │ ├── Logo.tsx │ ├── Meta.tsx │ ├── RoundedGrid.tsx │ ├── Section.tsx │ ├── Switch.tsx │ ├── Tabs.tsx │ └── VisuallyHidden.tsx ├── config │ ├── theme.ts │ └── urls.ts ├── contexts │ └── CodingLanguage.tsx ├── hooks │ ├── useBillingData.ts │ ├── useIntersectionByQuery.ts │ ├── useMediaQuery.ts │ └── useSize.ts ├── images │ ├── dashboard.png │ └── team │ │ ├── as.jpg │ │ └── av.png ├── pages │ ├── 404.tsx │ ├── [slug].tsx │ ├── _app.tsx │ ├── _document.tsx │ ├── blog │ │ ├── [slug].tsx │ │ └── index.tsx │ ├── index.tsx │ └── pricing.tsx ├── posts │ ├── .prettierrc │ ├── blog │ │ ├── rushdb-selfhosted-quick-setup.mdx │ │ └── rushdb-the-zero-config-database-for-modern-apps-and-ai-solutions.mdx │ ├── cookie-policy.mdx │ ├── privacy-policy.mdx │ └── terms-of-service.mdx ├── sections │ ├── Hero │ │ └── index.tsx │ ├── HowItWorks │ │ ├── demo.tsx │ │ └── index.tsx │ ├── Mission │ │ └── index.tsx │ ├── Pricing.tsx │ └── blog │ │ ├── MDXRenderer.tsx │ │ ├── PostCard.tsx │ │ ├── types.ts │ │ └── utils.ts ├── styles │ ├── fonts │ │ ├── index.ts │ │ └── jet-brains-mono │ │ │ ├── JetBrainsMono-Bold.woff2 │ │ │ ├── JetBrainsMono-BoldItalic.woff2 │ │ │ ├── JetBrainsMono-ExtraBold.woff2 │ │ │ ├── JetBrainsMono-ExtraBoldItalic.woff2 │ │ │ ├── JetBrainsMono-ExtraLight.woff2 │ │ │ ├── JetBrainsMono-ExtraLightItalic.woff2 │ │ │ ├── JetBrainsMono-Italic.woff2 │ │ │ ├── JetBrainsMono-Light.woff2 │ │ │ ├── JetBrainsMono-LightItalic.woff2 │ │ │ ├── JetBrainsMono-Medium.woff2 │ │ │ ├── JetBrainsMono-MediumItalic.woff2 │ │ │ ├── JetBrainsMono-Regular.woff2 │ │ │ ├── JetBrainsMono-SemiBold.woff2 │ │ │ ├── JetBrainsMono-SemiBoldItalic.woff2 │ │ │ ├── JetBrainsMono-Thin.woff2 │ │ │ └── JetBrainsMono-ThinItalic.woff2 │ └── globals.css └── utils │ ├── composeRefs.ts │ ├── copyToClipboard.ts │ ├── index.ts │ └── jsonLd.ts ├── tailwind.config.js ├── tsconfig.json └── typings.d.ts /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick find of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@3.0.4/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": false, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | *.tgz 5 | .env 6 | .next 7 | .DS_Store 8 | .idea 9 | .vscode 10 | .eslintcache 11 | examples/**/yarn.lock 12 | package-lock.json 13 | *.tsbuildinfo 14 | coverage 15 | .rollup.cache 16 | cjs 17 | esm 18 | packages/javascript-sdk/types -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | node-linker=hoisted 2 | shamefully-hoist=true 3 | strict-peer-dependencies=false 4 | auto-install-peers=true 5 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "semi": false, 4 | "useTabs": false, 5 | "singleQuote": true, 6 | "trailingComma": "none", 7 | "arrowParens": "always", 8 | "printWidth": 110, 9 | "parser": "typescript", 10 | "experimentalTernaries": true, 11 | "plugins": ["prettier-plugin-tailwindcss"] 12 | } 13 | -------------------------------------------------------------------------------- /commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { extends: ['@commitlint/config-conventional'] } 2 | -------------------------------------------------------------------------------- /docs/.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | # Unix-style newlines with a newline ending every file 5 | [*] 6 | end_of_line = lf 7 | insert_final_newline = true 8 | 9 | # 2 space indentation 10 | [{*.mdx,*.md}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | # Matches the exact files either package.json or .travis.yml 15 | [{package.json,.travis.yml}] 16 | indent_style = space 17 | indent_size = 2 18 | -------------------------------------------------------------------------------- /docs/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # ide 12 | .idea 13 | 14 | # Misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /docs/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /docs/docs/concepts/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Concepts", 3 | "position": 3, 4 | "collapsed": false, 5 | "collapsible": false 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/concepts/search/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Search", 3 | "position": 2, 4 | "collapsible": true, 5 | "collapsed": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/get-started/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Get Started", 3 | "position": 1, 4 | "collapsed": false, 5 | "collapsible": false 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/python-sdk/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Python SDK", 3 | "position": 10, 4 | "collapsed": true, 5 | "collapsible": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/python-sdk/python-reference/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Python Reference", 3 | "position": 10, 4 | "collapsed": true, 5 | "collapsible": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/python-sdk/records/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Records", 3 | "position": 1, 4 | "collapsible": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/rest-api/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "REST API", 3 | "position": 12, 4 | "collapsed": true, 5 | "collapsible": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/rest-api/records/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Records API", 3 | "position": 1, 4 | "collapsible": false 5 | } 6 | -------------------------------------------------------------------------------- /docs/docs/tutorials/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Tutorials", 3 | "position": 2, 4 | "collapsed": false, 5 | "collapsible": false 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/typescript-sdk/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "TypeScript SDK", 3 | "position": 10, 4 | "collapsed": true, 5 | "collapsible": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/typescript-sdk/records/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Records", 3 | "position": 1, 4 | "collapsed": false, 5 | "collapsible": false 6 | } 7 | -------------------------------------------------------------------------------- /docs/docs/typescript-sdk/typescript-reference/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "Typescript Reference", 3 | "position": 6, 4 | "collapsed": true, 5 | "collapsible": true 6 | } 7 | -------------------------------------------------------------------------------- /docs/plugins/tailwind-config.cjs: -------------------------------------------------------------------------------- 1 | function tailwindPlugin(context, options) { 2 | return { 3 | name: 'tailwind-plugin', 4 | configurePostCss(postcssOptions) { 5 | postcssOptions.plugins = [ 6 | require('postcss-import'), 7 | require('tailwindcss'), 8 | require('autoprefixer'), 9 | ]; 10 | return postcssOptions; 11 | }, 12 | }; 13 | } 14 | 15 | module.exports = tailwindPlugin; -------------------------------------------------------------------------------- /docs/src/components/ui/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { type ClassValue, clsx } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /docs/src/components/ui/method.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | 3 | export const Method = ({ method, color }: { method: string; color: string }) => ( 4 | 5 | {method} 6 | 7 | ) 8 | -------------------------------------------------------------------------------- /docs/static/.nojekyll: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/.nojekyll -------------------------------------------------------------------------------- /docs/static/img/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/favicon.ico -------------------------------------------------------------------------------- /docs/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/favicon.png -------------------------------------------------------------------------------- /docs/static/img/og.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/og.png -------------------------------------------------------------------------------- /docs/static/img/quick-start/create-project-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/quick-start/create-project-screen.png -------------------------------------------------------------------------------- /docs/static/img/quick-start/create-token-screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/quick-start/create-token-screen.png -------------------------------------------------------------------------------- /docs/static/img/social-card.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/docs/static/img/social-card.png -------------------------------------------------------------------------------- /docs/tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | corePlugins: { 4 | preflight: false, 5 | container: false 6 | }, 7 | darkMode: ['class', '[data-theme="dark"]'], 8 | content: ['./src/**/*.{jsx,tsx,html}', './docs/**/*.{md,mdx}'], 9 | theme: { 10 | extend: { 11 | borderRadius: { 12 | sm: '4px' 13 | }, 14 | screens: { 15 | sm: '0px', 16 | lg: '997px' 17 | }, 18 | colors: { 19 | accent: { 20 | DEFAULT: '#3f81ff', // Base color 21 | contrast: 'hsl(0, 0%, 100%)', // White for contrast on buttons, etc. 22 | hover: '#3c78ef', // Slightly darker for hover state 23 | focus: '#346ccd', // Same as hover for consistency 24 | ring: 'rgba(63, 129, 255, 0.24)' // Transparent version of base for focus ring 25 | } 26 | } 27 | } 28 | }, 29 | plugins: [] 30 | } 31 | -------------------------------------------------------------------------------- /docs/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@docusaurus/tsconfig", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('ts-jest').JestConfigWithTsJest} */ 2 | module.exports = { 3 | resolver: 'jest-ts-webcompat-resolver', 4 | collectCoverageFrom: ['tests/**/*.{ts,tsx,js,jsx}'], 5 | moduleFileExtensions: ['ts', 'tsx', 'js', 'json', 'jsx'], 6 | roots: [''], 7 | transform: { 8 | '^.+\\.(t|j)sx?$': '@swc/jest' 9 | }, 10 | transformIgnorePatterns: ['[/\\\\]node_modules[/\\\\].+\\.(t|j)sx?$'] 11 | } 12 | -------------------------------------------------------------------------------- /packages/javascript-sdk/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "parser": "@typescript-eslint/parser", 4 | "parserOptions": { 5 | "ecmaVersion": 8, 6 | "sourceType": "module", 7 | "ecmaFeatures": { 8 | "impliedStrict": true, 9 | "experimentalObjectRestSpread": true 10 | }, 11 | "allowImportExportEverywhere": true, 12 | "project": ["**/tsconfig.json"] 13 | }, 14 | "plugins": [ 15 | "@typescript-eslint" 16 | ], 17 | "extends": [ 18 | "eslint:recommended", 19 | "plugin:@typescript-eslint/recommended", 20 | "prettier" 21 | ], 22 | "env": { 23 | "es6": true, 24 | "browser": true, 25 | "node": true, 26 | "jest": true 27 | }, 28 | "rules": { 29 | "prefer-const": "off", 30 | "@typescript-eslint/no-explicit-any": "off", 31 | "@typescript-eslint/consistent-type-imports": [ 32 | 2, 33 | { "prefer": "type-imports" } 34 | ] 35 | }, 36 | "ignorePatterns": ["dist/", "node_modules", "scripts", "examples"] 37 | } 38 | -------------------------------------------------------------------------------- /packages/javascript-sdk/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | *.log 4 | *.tgz 5 | .env 6 | .next 7 | .DS_Store 8 | .idea 9 | .vscode 10 | .eslintcache 11 | examples/**/yarn.lock 12 | package-lock.json 13 | *.tsbuildinfo 14 | coverage 15 | .rollup.cache 16 | cjs 17 | esm 18 | packages/javascript-sdk/types -------------------------------------------------------------------------------- /packages/javascript-sdk/.npmignore: -------------------------------------------------------------------------------- 1 | # This file is written to be a whitelist instead of a blacklist. Start by 2 | # ignoring everything, then add back the files we want to be included in the 3 | # final NPM package. 4 | * 5 | 6 | # And these are the files that are allowed. 7 | !/CHANGELOG.md 8 | !/LICENSE 9 | !/README.md 10 | !/VERSION 11 | !/package.json 12 | !/cjs/**/* 13 | !/esm/**/* 14 | !/dist/**/* 15 | !/types/**/* -------------------------------------------------------------------------------- /packages/javascript-sdk/src/api/constants.ts: -------------------------------------------------------------------------------- 1 | export const DEFAULT_HOST = 'api.rushdb.com' 2 | export const DEFAULT_PORT = 443 3 | export const DEFAULT_PROTOCOL = 'https' 4 | export const DEFAULT_BASE_PATH = '/api/v1' 5 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/api/index.ts: -------------------------------------------------------------------------------- 1 | export { RestAPI } from './api.js' 2 | export { ApiResponse } from './types.js' 3 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/api/types.ts: -------------------------------------------------------------------------------- 1 | export type ApiResponse> = { 2 | data: T 3 | success: boolean 4 | total?: number 5 | } & E 6 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const ISO_8601_FULL = 2 | /^(?:\d{4}-(?:(?:0[1-9]|1[0-2])-(?:0[1-9]|1\d|2[0-8])|(?:0[13-9]|1[0-2])-(?:29|30)|(?:0[13578]|1[02])-31)|(?:\d{2}(?:[02468][048]|[13579][26])-02-29))Schema(0[0-9]|1[0-9]|2[0-3]):(0[0-9]|[1-5][0-9]):(0[0-9]|[1-5][0-9])?(?:\.([0-9]{1,9}))?([zZ]?|([\+-])(((([0][0-9])|([1][0-3])):?(([03][0])|([14][5])))|14:00)?)$/ 3 | 4 | export const DEFAULT_TIMEOUT = 80000 5 | 6 | export const PROPERTY_TYPE_STRING = 'string' as const 7 | export const PROPERTY_TYPE_DATETIME = 'datetime' as const 8 | export const PROPERTY_TYPE_BOOLEAN = 'boolean' as const 9 | export const PROPERTY_TYPE_NUMBER = 'number' as const 10 | export const PROPERTY_TYPE_NULL = 'null' as const 11 | export const PROPERTY_TYPE_VECTOR = 'vector' as const 12 | 13 | export const PROPERTY_TYPES = [ 14 | PROPERTY_TYPE_STRING, 15 | PROPERTY_TYPE_DATETIME, 16 | PROPERTY_TYPE_BOOLEAN, 17 | PROPERTY_TYPE_NUMBER, 18 | PROPERTY_TYPE_NULL, 19 | PROPERTY_TYPE_VECTOR 20 | ] 21 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/index.node.ts: -------------------------------------------------------------------------------- 1 | import { HttpClient, HttpClientResponse } from './network/HttpClient.js' 2 | import { NodeHttpClient } from './network/NodeHttpClient.js' 3 | import { initSDK, RushDB } from './sdk/index.js' 4 | import { type ApiResponse, RestAPI } from './api/index.js' 5 | 6 | initSDK(new NodeHttpClient()) 7 | 8 | export { RushDB, HttpClient, HttpClientResponse, RestAPI, type ApiResponse } 9 | export * from './types/index.js' 10 | export * from './sdk/index.js' 11 | 12 | export default RushDB 13 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/index.worker.ts: -------------------------------------------------------------------------------- 1 | import { FetchHttpClient } from './network/FetchHttpClient.js' 2 | import { HttpClient, HttpClientResponse } from './network/HttpClient.js' 3 | import { initSDK, RushDB } from './sdk/index.js' 4 | import { type ApiResponse, RestAPI } from './api/index.js' 5 | 6 | initSDK(new FetchHttpClient()) 7 | 8 | export { RushDB, HttpClient, HttpClientResponse, RestAPI, type ApiResponse } 9 | export * from './types/index.js' 10 | export * from './sdk/index.js' 11 | 12 | export default RushDB 13 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/network/types.ts: -------------------------------------------------------------------------------- 1 | import type { MaybeArray } from '../types' 2 | 3 | export type RequestData = Record> = T 4 | export type RequestHeaders = Record> 5 | export type ResponseHeaderValue = MaybeArray 6 | export type ResponseHeaders = Record 7 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/sdk/constants.ts: -------------------------------------------------------------------------------- 1 | export const ALLOWED_CONFIG_PROPERTIES = [ 2 | 'httpClient', 3 | 'timeout', 4 | 'host', 5 | 'port', 6 | 'protocol', 7 | 'url', 8 | 'logger', 9 | 'options' 10 | ] 11 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/sdk/errors.ts: -------------------------------------------------------------------------------- 1 | import type { SearchQuery } from '../types' 2 | 3 | export class UniquenessError extends Error { 4 | constructor(label: string, properties: any) { 5 | super(`Record with label "${label}" and properties ${JSON.stringify(properties)} already exists`) 6 | this.name = 'UniquenessError' 7 | Object.setPrototypeOf(this, UniquenessError.prototype) 8 | } 9 | } 10 | 11 | export class EmptyTargetError extends Error { 12 | constructor(message: string) { 13 | super(message) 14 | this.name = 'EmptyTarget' 15 | Object.setPrototypeOf(this, EmptyTargetError.prototype) 16 | } 17 | } 18 | 19 | export class NonUniqueResultError extends Error { 20 | constructor(duplicateCount: number, searchQuery: SearchQuery) { 21 | super( 22 | `Expected a unique result but found ${duplicateCount} matches for the provided SearchQuery: ${JSON.stringify( 23 | searchQuery 24 | )}. Ensure your search parameters uniquely identify a single result.` 25 | ) 26 | this.name = 'NonUniqueResultError' 27 | Object.setPrototypeOf(this, NonUniqueResultError.prototype) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/sdk/index.ts: -------------------------------------------------------------------------------- 1 | export * from './sdk.js' 2 | export * from './types.js' 3 | export * from './errors.js' 4 | export * from './transaction.js' 5 | export * from './utils.js' 6 | export * from './record.js' 7 | export * from './model.js' 8 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/sdk/types.ts: -------------------------------------------------------------------------------- 1 | import type { HttpClientInterface } from '../network/HttpClient.js' 2 | 3 | type ApiConnectionConfig = 4 | | { 5 | host?: string 6 | port?: number 7 | protocol?: string 8 | } 9 | | { 10 | url?: string 11 | } 12 | 13 | export type State = { 14 | debug: boolean 15 | timeout: number 16 | token?: string 17 | initialized: boolean 18 | serverSettings?: { 19 | selfHosted: boolean 20 | dashboardUrl: string 21 | googleOAuthEnabled: boolean 22 | githubOAuthEnabled: boolean 23 | customDB?: boolean 24 | } 25 | } & Partial 26 | 27 | export type Logger = (payload: any) => void 28 | 29 | export type SDKConfig = { 30 | httpClient?: HttpClientInterface 31 | timeout?: number 32 | logger?: Logger 33 | options?: { 34 | /** 35 | * @description 36 | * Defaults to `false`. 37 | * Allows using the `delete()` method without a specified criteria, 38 | * which results in deleting all Records in the project. 39 | */ 40 | allowForceDelete?: boolean 41 | } 42 | } & ApiConnectionConfig 43 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from './value.js' 2 | export * from './query.js' 3 | export * from './utils.js' 4 | export * from './schema.js' 5 | export * from './expressions.js' 6 | -------------------------------------------------------------------------------- /packages/javascript-sdk/src/types/utils.ts: -------------------------------------------------------------------------------- 1 | import type { PropertyValue } from './value' 2 | 3 | export type RequireAtLeastOne = { 4 | [K in keyof T]-?: Required> & Partial>> 5 | }[keyof T] 6 | 7 | export type MaybeArray = Array | T 8 | 9 | export type FlattenTypes = T extends object ? { [K in keyof T]: FlattenTypes } : T 10 | 11 | export type AnyObject = Record 12 | 13 | export type FlatObject = Record 14 | -------------------------------------------------------------------------------- /packages/javascript-sdk/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "outDir": "./dist", 4 | "rootDir": "./src", 5 | "module": "ESNext", // Use ESNext for ES modules 6 | "target": "ES2015", // Target ES2015 for compatibility 7 | "moduleResolution": "node", 8 | "declaration": true, // Generate `.d.ts` files 9 | "sourceMap": true, // Generate source maps 10 | "strict": true, // Enable strict type checking 11 | "esModuleInterop": true, // Enable ES module interop 12 | "skipLibCheck": true 13 | }, 14 | "include": ["src/**/*"] 15 | } -------------------------------------------------------------------------------- /platform/.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .pnpm-store 3 | dist 4 | .git 5 | .gitignore 6 | .env 7 | *.log 8 | coverage 9 | .next 10 | .nuxt 11 | .cache 12 | .turbo 13 | 14 | # Don't ignore the built files we need 15 | !packages/javascript-sdk/esm 16 | !packages/javascript-sdk/dist 17 | !packages/javascript-sdk/cjs 18 | !packages/javascript-sdk/types 19 | !packages/javascript-sdk/package.json 20 | !packages/javascript-sdk/index.d.ts -------------------------------------------------------------------------------- /platform/core/commitlint.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ['@commitlint/config-conventional'], 3 | }; 4 | -------------------------------------------------------------------------------- /platform/core/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.8" 2 | services: 3 | neo4j: 4 | image: neo4j:5.25.1 5 | healthcheck: 6 | test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider localhost:7474 || exit 1" ] 7 | interval: 5s 8 | retries: 30 9 | start_period: 10s 10 | ports: 11 | - "7474:7474" 12 | - "7687:7687" 13 | environment: 14 | - NEO4J_ACCEPT_LICENSE_AGREEMENT=yes 15 | - NEO4J_AUTH=${NEO4J_USERNAME}/${NEO4J_PASSWORD} 16 | # Uncomment line below to fetch plugins from the internet instead of local volume 17 | # - NEO4J_PLUGINS=["apoc", "graph-data-science"] 18 | volumes: 19 | - ./neo4j-plugins:/var/lib/neo4j/plugins 20 | - neo4j-data:/data 21 | - neo4j-logs:/logs 22 | - neo4j-conf:/var/lib/neo4j/conf 23 | 24 | volumes: 25 | neo4j-data: 26 | neo4j-logs: 27 | neo4j-conf: -------------------------------------------------------------------------------- /platform/core/neo4j-plugins/apoc-5.25.1-core.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/core/neo4j-plugins/apoc-5.25.1-core.jar -------------------------------------------------------------------------------- /platform/core/neo4j-plugins/graph-data-science-2.12.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/core/neo4j-plugins/graph-data-science-2.12.0.jar -------------------------------------------------------------------------------- /platform/core/nest-cli.json: -------------------------------------------------------------------------------- 1 | { 2 | "collection": "@nestjs/schematics", 3 | "sourceRoot": "src", 4 | "compilerOptions": { 5 | "assets": ["**/*.ejs"], 6 | "watchAssets": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /platform/core/src/app.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, UseInterceptors } from '@nestjs/common' 2 | 3 | import { NotFoundInterceptor } from '@/common/interceptors/not-found.interceptor' 4 | import { TransformResponseInterceptor } from '@/common/interceptors/transform-response.interceptor' 5 | import { NeogmaDataInterceptor } from '@/database/neogma/neogma-data.interceptor' 6 | 7 | @Controller('') 8 | @UseInterceptors(TransformResponseInterceptor, NotFoundInterceptor, NeogmaDataInterceptor) 9 | export class AppController { 10 | @Get('') 11 | root(): string { 12 | return 'RushDB is running' 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/app.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | @Injectable() 4 | export class AppService {} 5 | -------------------------------------------------------------------------------- /platform/core/src/backup/backup.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { CoreModule } from '@/core/core.module' 4 | import { DashboardModule } from '@/dashboard/dashboard.module' 5 | 6 | import { BackupService } from './backup.service' 7 | 8 | @Module({ 9 | imports: [CoreModule, DashboardModule], 10 | exports: [BackupService], 11 | providers: [BackupService] 12 | }) 13 | export class BackupModule {} 14 | -------------------------------------------------------------------------------- /platform/core/src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { BootstrapConsole } from 'nestjs-console' 4 | 5 | import { AppModule } from '@/app.module' 6 | 7 | const bootstrap = new BootstrapConsole({ 8 | module: AppModule, 9 | useDecorators: true 10 | }) 11 | bootstrap.init().then(async (app) => { 12 | try { 13 | await app.init() 14 | await bootstrap.boot() 15 | await app.close() 16 | } catch (e) { 17 | console.error(e) 18 | await app.close() 19 | process.exit(1) 20 | } 21 | }) 22 | -------------------------------------------------------------------------------- /platform/core/src/common/QueryBuilder.ts: -------------------------------------------------------------------------------- 1 | export class QueryBuilder { 2 | queryParts: string[] = [] 3 | 4 | append(part: string) { 5 | if (part) { 6 | this.queryParts.push(part) 7 | } 8 | 9 | return this // Allow method chaining 10 | } 11 | 12 | build(separator = '\n') { 13 | return this.queryParts.join(separator) 14 | } 15 | 16 | getQuery() { 17 | return this.build() 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /platform/core/src/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const TOKEN_EXPIRES_IN = '30d' 2 | -------------------------------------------------------------------------------- /platform/core/src/common/decorators/common-response.decorator.ts: -------------------------------------------------------------------------------- 1 | import { applyDecorators, Type } from '@nestjs/common' 2 | import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger' 3 | 4 | export const CommonResponseDecorator = >(model?: TModel) => { 5 | const apiResponse = ApiOkResponse({ 6 | status: 200, 7 | description: '', 8 | content: { 9 | 'application-json': { 10 | schema: { 11 | properties: { 12 | ...(model && { 13 | data: { 14 | $ref: getSchemaPath(model) 15 | } 16 | }), 17 | ...(!model && { 18 | data: { 19 | type: 'boolean' 20 | } 21 | }), 22 | success: { 23 | type: 'boolean' 24 | } 25 | } 26 | } 27 | } 28 | } 29 | }) 30 | 31 | return model ? applyDecorators(ApiExtraModels(model), apiResponse) : applyDecorators(apiResponse) 32 | } 33 | -------------------------------------------------------------------------------- /platform/core/src/common/interceptors/errors.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Injectable, 3 | NestInterceptor, 4 | ExecutionContext, 5 | BadGatewayException, 6 | CallHandler 7 | } from '@nestjs/common' 8 | import { Observable, throwError } from 'rxjs' 9 | import { catchError } from 'rxjs/operators' 10 | 11 | @Injectable() 12 | export class ErrorsInterceptor implements NestInterceptor { 13 | intercept(context: ExecutionContext, next: CallHandler): Observable { 14 | return next.handle().pipe(catchError((err) => throwError(() => new BadGatewayException()))) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /platform/core/src/common/interceptors/exclude-null-response.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common' 2 | import { Observable } from 'rxjs' 3 | import { map } from 'rxjs/operators' 4 | 5 | @Injectable() 6 | export class ExcludeNullInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | // const request = context.switchToHttp().getRequest(); 9 | return next.handle().pipe(map((value) => (value === null ? '' : value))) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /platform/core/src/common/interceptors/not-found.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { CallHandler, ExecutionContext, Injectable, NestInterceptor, NotFoundException } from '@nestjs/common' 2 | import { Observable } from 'rxjs' 3 | import { tap } from 'rxjs/operators' 4 | 5 | @Injectable() 6 | export class NotFoundInterceptor implements NestInterceptor { 7 | intercept(context: ExecutionContext, next: CallHandler): Observable { 8 | return next.handle().pipe( 9 | tap((data) => { 10 | if (data === undefined || data === null) { 11 | throw new NotFoundException() 12 | } 13 | }) 14 | ) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /platform/core/src/common/interceptors/transform-response.interceptor.ts: -------------------------------------------------------------------------------- 1 | import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common' 2 | import { Observable } from 'rxjs' 3 | import { map } from 'rxjs/operators' 4 | 5 | export interface IResponse { 6 | data: T 7 | } 8 | 9 | @Injectable() 10 | export class TransformResponseInterceptor implements NestInterceptor> { 11 | intercept(context: ExecutionContext, next: CallHandler): Observable> { 12 | return next.handle().pipe( 13 | map((data) => ({ 14 | data: data && data.data !== undefined ? data.data : data, 15 | total: data && data.total !== undefined ? data.total : undefined, 16 | success: true 17 | })) 18 | ) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /platform/core/src/common/types/request.ts: -------------------------------------------------------------------------------- 1 | import { FastifyRequest } from 'fastify' 2 | 3 | import { IUserClaims } from '@/dashboard/user/interfaces/user-claims.interface' 4 | 5 | export type PlatformRequest = FastifyRequest & { 6 | projectId: string 7 | user: IUserClaims 8 | } 9 | -------------------------------------------------------------------------------- /platform/core/src/common/types/utils.ts: -------------------------------------------------------------------------------- 1 | export type TGetFirstArgument = 2 | T extends (first: infer FirstArgument, ...args: any[]) => any ? FirstArgument : never 3 | 4 | export type TAnyObject = Record 5 | 6 | export type TFlattenTypes = T extends object ? { [K in keyof T]: TFlattenTypes } : T 7 | 8 | export type TUnionToIntersection = 9 | (U extends unknown ? (k: U) => void : never) extends (k: infer I) => void ? I : never 10 | 11 | export type TIsUnion = [T] extends [TUnionToIntersection] ? false : true 12 | 13 | export type TReplace = 14 | TIsUnion extends false ? TFlattenTypes & { [key in T as K]: R }> : never 15 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/arrayIsConsistent.ts: -------------------------------------------------------------------------------- 1 | import { isPrimitive } from '@/common/utils/isPrimitive' 2 | 3 | export const arrayIsConsistent = (arr: Array) => { 4 | if (arr.length === 0) { 5 | // @TODO: Should it be true in this case? 6 | return true 7 | } 8 | 9 | const firstElement = arr[0] 10 | 11 | return arr.every( 12 | (el) => isPrimitive(el) && (firstElement === null ? el === null : typeof el === typeof firstElement) 13 | ) 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/asyncAssertExistsOrThrow.ts: -------------------------------------------------------------------------------- 1 | export async function asyncAssertExistsOrThrow( 2 | valueProvider: () => Promise, 3 | error: Error | string 4 | ): Promise { 5 | const value = await valueProvider() 6 | if (value === null || value === undefined) { 7 | if (typeof error === 'string') { 8 | throw new Error(error) 9 | } 10 | throw error 11 | } 12 | return value 13 | } 14 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/checkTypeAndNameUniqueness.ts: -------------------------------------------------------------------------------- 1 | import { PropertyDto } from '@/core/property/dto/property.dto' 2 | 3 | export const checkTypeAndNameUniqueness = (properties: PropertyDto[]) => { 4 | const nameMap = new Map() 5 | 6 | for (const property of properties) { 7 | if (nameMap.has(property.name)) { 8 | const existingType = nameMap.get(property.name) 9 | if (existingType !== property.type) { 10 | // throw new Error( 11 | // `Duplicate name '${property.name}' found with different types: '${existingType}' and '${property.type}'.` 12 | // ); 13 | return false 14 | } 15 | } else { 16 | nameMap.set(property.name, property.type) 17 | } 18 | } 19 | 20 | return true 21 | } 22 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/collectValuesByKeysFromObject.ts: -------------------------------------------------------------------------------- 1 | import { TAnyObject } from '@/common/types/utils' 2 | import { isObject } from '@/common/utils/isObject' 3 | import { uniqArray } from '@/common/utils/uniqArray' 4 | 5 | export const collectValuesByKeysFromObject = (object: TAnyObject, keys: string[]) => { 6 | const res: string[] = [] 7 | const getKeys = (object: TAnyObject) => 8 | isObject(object) && 9 | Object.entries(object).forEach(([key, value]: [string, unknown]) => { 10 | if (keys.includes(key)) { 11 | res.push(value as string) 12 | } 13 | 14 | // if (Array.isArray(value)) { 15 | // value.forEach(getKeys); 16 | // } 17 | // 18 | // if (isObject(value)) { 19 | // getKeys(value as TAnyObject); 20 | // } 21 | }) 22 | getKeys(object) 23 | return uniqArray(res) 24 | } 25 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/containsAllowedKeys.ts: -------------------------------------------------------------------------------- 1 | export const containsAllowedKeys = (obj: any, allowedKeys: string[]) => { 2 | const keys = Object.keys(obj) 3 | for (const key of keys) { 4 | if (!allowedKeys.includes(key)) { 5 | return false 6 | } 7 | } 8 | return true 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/fetchRetry.ts: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | 3 | export async function sleep(delayMs: number) { 4 | return new Promise((resolve) => setTimeout(resolve, delayMs)) 5 | } 6 | 7 | export async function fetchRetry(url: string, delayMs: number, tries: number) { 8 | function onError(err) { 9 | const triesLeft = tries - 1 10 | console.log(`Attempting to fetch ${url}. Tries left: ${triesLeft}...`) 11 | if (!triesLeft) { 12 | throw err 13 | } 14 | return sleep(delayMs).then(() => fetchRetry(url, delayMs, triesLeft)) 15 | } 16 | return axios.get(url).catch(onError) 17 | } 18 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/getCurrentISO.ts: -------------------------------------------------------------------------------- 1 | export const getCurrentISO = () => new Date().toISOString() 2 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/getISOWithMicrosecond.ts: -------------------------------------------------------------------------------- 1 | export const getISOWithMicrosecond = () => { 2 | let [, n] = process.hrtime() 3 | const s = new Date().getTime() / 1000 4 | 5 | return ( 6 | // eslint-disable-next-line @typescript-eslint/ban-ts-comment 7 | // @ts-ignore 8 | new Date(s * 1e3).toJSON((n = 0 | (n / 1e3))).slice(0, 20 - !n) + `${n + 1e6}`.slice(n ? 1 : 7) 9 | ) 10 | } 11 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isArray.ts: -------------------------------------------------------------------------------- 1 | export const isArray = (item: any): item is any[] => 2 | typeof item === 'object' && Array.isArray(item) && item !== null 3 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isDevMode.ts: -------------------------------------------------------------------------------- 1 | export const isDevMode = (callback?: () => void) => { 2 | const __DEV__ = process.env.NODE_ENV === 'development' 3 | if (typeof callback !== 'undefined' && __DEV__) { 4 | callback() 5 | } 6 | return __DEV__ 7 | } 8 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isEmptyObject.ts: -------------------------------------------------------------------------------- 1 | export const isEmptyObject = (input: object) => Object.keys(input).length === 0 2 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isNumeric.ts: -------------------------------------------------------------------------------- 1 | export const isNumeric = (value: unknown): value is number => 2 | typeof value === 'number' ? 3 | Number.isFinite(value) 4 | : typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value)) 5 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isObject.ts: -------------------------------------------------------------------------------- 1 | export const isObject = (input: unknown): input is T => 2 | input !== null && Object.prototype.toString.call(input) === '[object Object]' 3 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isPrimitive.ts: -------------------------------------------------------------------------------- 1 | export const isPrimitive = (value: unknown) => { 2 | return ( 3 | typeof value === 'number' || typeof value === 'string' || typeof value === 'boolean' || value === null 4 | ) 5 | } 6 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isPrimitiveArray.ts: -------------------------------------------------------------------------------- 1 | import { isPrimitive } from '@/common/utils/isPrimitive' 2 | 3 | export const isPrimitiveArray = (value: unknown) => (value as Array).every(isPrimitive) 4 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/isProductionMode.ts: -------------------------------------------------------------------------------- 1 | export const isProductionMode = () => { 2 | return process.env.NODE_ENV === 'production' 3 | } 4 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/omit.ts: -------------------------------------------------------------------------------- 1 | export function omit(obj: T, keys: K[]): Omit { 2 | const result = { ...obj } 3 | keys.forEach((key) => { 4 | delete result[key] 5 | }) 6 | return result 7 | } 8 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/pickPrimitives.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '@/common/utils/isArray' 2 | import { isPrimitive } from '@/common/utils/isPrimitive' 3 | import { isPrimitiveArray } from '@/common/utils/isPrimitiveArray' 4 | 5 | const isPrimitiveRushDBValue = (value: any) => { 6 | if (isArray(value)) { 7 | return isPrimitiveArray(value) 8 | } 9 | 10 | return isPrimitive(value) 11 | } 12 | 13 | export const pickPrimitives = (input: any) => { 14 | const result = {} 15 | 16 | Object.entries(input).forEach(([key, value]) => { 17 | if (isPrimitiveRushDBValue(value)) { 18 | result[key] = value 19 | } 20 | }) 21 | 22 | return result 23 | } 24 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/randomString.ts: -------------------------------------------------------------------------------- 1 | export const randomString = (length: number): string => { 2 | let result = '' 3 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789' 4 | const charactersLength = characters.length 5 | for (let i = 0; i < length; i++) { 6 | result += characters.charAt(Math.floor(Math.random() * charactersLength)) 7 | } 8 | return result 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/toBolean.ts: -------------------------------------------------------------------------------- 1 | import { isArray } from '@/common/utils/isArray' 2 | import { isObject } from '@/common/utils/isObject' 3 | 4 | export const toBoolean = (value: any): boolean => { 5 | if (typeof value === 'string') { 6 | if (value.toLowerCase() === 'true') { 7 | return true 8 | } else if (value.toLowerCase() === 'false') { 9 | return false 10 | } 11 | } 12 | 13 | if (isArray(value) && value.length === 0) { 14 | return false 15 | } 16 | if (isObject(value) && Object.keys(value).length === 0) { 17 | return false 18 | } 19 | 20 | return Boolean(value) 21 | } 22 | -------------------------------------------------------------------------------- /platform/core/src/common/utils/uniqArray.ts: -------------------------------------------------------------------------------- 1 | export const uniqArray = (arr: Array) => [...new Set(arr)] 2 | -------------------------------------------------------------------------------- /platform/core/src/common/validation/utils.ts: -------------------------------------------------------------------------------- 1 | import { ArgumentMetadata } from '@nestjs/common' 2 | import Joi = require('joi') 3 | 4 | export const formatErrorMessage = (error: Joi.ValidationError, metadata: ArgumentMetadata) => { 5 | const reasons = error.details.map((detail: { message: string }) => detail.message).join(', ') 6 | 7 | return ( 8 | `Request validation of ${metadata.type} ` + 9 | (metadata.data ? `item '${metadata.data}' ` : '') + 10 | `failed, because: ${reasons}` 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /platform/core/src/core/core.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { EntityModule } from '@/core/entity/entity.module' 4 | import { ImportExportModule } from '@/core/entity/import-export/import-export.module' 5 | import { PropertyModule } from '@/core/property/property.module' 6 | import { TransactionModule } from '@/core/transactions/transaction.module' 7 | 8 | @Module({ 9 | imports: [EntityModule, PropertyModule, ImportExportModule, TransactionModule], 10 | exports: [EntityModule, PropertyModule, ImportExportModule, TransactionModule] 11 | }) 12 | export class CoreModule {} 13 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/dto/create-entity.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { TCreateRecordSchema } from '@/core/entity/entity.types' 4 | import { TImportOptions } from '@/core/entity/import-export/import.types' 5 | import { PropertyDto } from '@/core/property/dto/property.dto' 6 | import { TPropertyValue } from '@/core/property/property.types' 7 | 8 | export class CreateEntityDto { 9 | @ApiProperty({ default: '' }) 10 | label: string 11 | 12 | @ApiPropertyOptional({ type: [PropertyDto] }) 13 | properties?: Array 14 | } 15 | 16 | export class CreateEntityDtoSimple { 17 | @ApiProperty({ default: '' }) 18 | label: string 19 | 20 | @ApiProperty() 21 | data: Record 22 | 23 | @ApiPropertyOptional() 24 | options?: Omit 25 | 26 | @ApiPropertyOptional() 27 | schema?: TCreateRecordSchema 28 | } 29 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/dto/edit-entity.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { TCreateRecordSchema } from '@/core/entity/entity.types' 4 | import { TImportOptions } from '@/core/entity/import-export/import.types' 5 | import { PropertyDto } from '@/core/property/dto/property.dto' 6 | import { TPropertyValue } from '@/core/property/property.types' 7 | 8 | export class EditEntityDto { 9 | @ApiPropertyOptional({ default: '' }) 10 | label: string 11 | 12 | @ApiPropertyOptional({ type: [PropertyDto] }) 13 | properties?: Array 14 | } 15 | 16 | export class EditEntityDtoSimple { 17 | @ApiPropertyOptional({ default: '' }) 18 | label: string 19 | 20 | @ApiPropertyOptional() 21 | data: Record 22 | 23 | @ApiPropertyOptional() 24 | options?: Omit 25 | 26 | @ApiPropertyOptional() 27 | schema?: TCreateRecordSchema 28 | } 29 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/entity.constants.ts: -------------------------------------------------------------------------------- 1 | export const RELATION_DIRECTION_IN = 'in' as const 2 | export const RELATION_DIRECTION_OUT = 'out' as const 3 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/import-export/dto/import-csv.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { TImportOptions, TImportJsonPayload } from '@/core/entity/import-export/import.types' 4 | 5 | export class ImportCsvDto { 6 | @ApiPropertyOptional() 7 | parentId?: string 8 | 9 | @ApiProperty({ default: '' }) 10 | data: string 11 | 12 | @ApiPropertyOptional({ default: '' }) 13 | label: string 14 | 15 | @ApiPropertyOptional({ default: {} }) 16 | options?: TImportOptions 17 | } 18 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/import-export/dto/import-json.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { TImportOptions, TImportJsonPayload } from '@/core/entity/import-export/import.types' 4 | 5 | export class ImportJsonDto { 6 | @ApiPropertyOptional() 7 | parentId?: string 8 | 9 | @ApiPropertyOptional({ default: {} }) 10 | data?: TImportJsonPayload 11 | 12 | @ApiPropertyOptional({ default: '' }) 13 | label: string 14 | 15 | @ApiPropertyOptional({ default: {} }) 16 | options?: TImportOptions 17 | } 18 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/import-export/import.types.ts: -------------------------------------------------------------------------------- 1 | import { CreateEntityDto } from '@/core/entity/dto/create-entity.dto' 2 | 3 | export type TImportRecordsRelation = { 4 | source: string 5 | target: string 6 | type?: string 7 | } 8 | export type TImportJsonPayload = Record | Array> 9 | export type TImportOptions = { 10 | suggestTypes?: boolean 11 | castNumberArraysToVectors?: boolean 12 | convertNumericValuesToNumbers?: boolean 13 | capitalizeLabels?: boolean 14 | relationshipType?: string 15 | returnResult?: boolean 16 | } 17 | export type WithId = T & { 18 | id: string 19 | } 20 | 21 | export type TImportQueue = { 22 | key: string 23 | value: TImportJsonPayload 24 | parentId?: string 25 | target: WithId 26 | skip?: boolean 27 | } & TImportOptions 28 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/import-export/validation/schemas/import.schema.ts: -------------------------------------------------------------------------------- 1 | import Joi = require('joi') 2 | 3 | export const importJsonSchema = Joi.object({ 4 | label: Joi.string(), 5 | data: Joi.alternatives().try(Joi.object(), Joi.array().items(Joi.object())), 6 | options: Joi.object({ 7 | suggestTypes: Joi.boolean().optional() 8 | }).optional() 9 | }) 10 | 11 | export const importCsvSchema = Joi.object({ 12 | label: Joi.string(), 13 | data: Joi.string(), 14 | options: Joi.object({ 15 | suggestTypes: Joi.boolean().optional() 16 | }).optional() 17 | }) 18 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/model/entity.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { Entity } from '@/core/entity/model/entity.model' 4 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 5 | 6 | import { TEntityModel } from './entity.interface' 7 | 8 | @Injectable() 9 | export class EntityRepository { 10 | constructor(private readonly repositoryService: RepositoryService) {} 11 | 12 | get model() { 13 | return this.repositoryService.getModelByConfig(Entity) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/validation/schemas/create-entity.schema.ts: -------------------------------------------------------------------------------- 1 | import Joi = require('joi') 2 | 3 | import { 4 | booleanValueSchema, 5 | datetimeValueSchema, 6 | nullValueSchema, 7 | numberValueSchema, 8 | propertySchema, 9 | stringValueSchema 10 | } from '@/core/property/validation/schemas/property.schema' 11 | 12 | export const createEntitySchema = Joi.alternatives().try( 13 | Joi.object({ 14 | label: Joi.string(), 15 | properties: Joi.array().items(propertySchema).optional() 16 | }), 17 | Joi.object({ 18 | label: Joi.string(), 19 | data: Joi.object().pattern( 20 | Joi.string().min(1).max(100), 21 | Joi.alternatives().try( 22 | nullValueSchema, 23 | booleanValueSchema, 24 | datetimeValueSchema, 25 | numberValueSchema, 26 | stringValueSchema 27 | ) 28 | ), 29 | options: Joi.object({ suggestTypes: Joi.boolean().optional() }).optional() 30 | }) 31 | ) 32 | -------------------------------------------------------------------------------- /platform/core/src/core/entity/validation/schemas/edit-entity.schema.ts: -------------------------------------------------------------------------------- 1 | import Joi = require('joi') 2 | 3 | import { 4 | booleanValueSchema, 5 | datetimeValueSchema, 6 | nullValueSchema, 7 | numberValueSchema, 8 | propertySchema, 9 | stringValueSchema 10 | } from '@/core/property/validation/schemas/property.schema' 11 | 12 | export const editEntitySchema = Joi.alternatives().try( 13 | Joi.object({ 14 | label: Joi.string(), 15 | properties: Joi.array().items(propertySchema).optional() 16 | }), 17 | Joi.object({ 18 | label: Joi.string(), 19 | data: Joi.object().pattern( 20 | Joi.string().min(1).max(100), 21 | Joi.alternatives().try( 22 | nullValueSchema, 23 | booleanValueSchema, 24 | datetimeValueSchema, 25 | numberValueSchema, 26 | stringValueSchema 27 | ) 28 | ), 29 | options: Joi.object({ suggestTypes: Joi.boolean().optional() }).optional() 30 | }) 31 | ) 32 | -------------------------------------------------------------------------------- /platform/core/src/core/property/dto/delete-property.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | 4 | export class DeletePropertyDto { 5 | @ApiPropertyOptional() 6 | @ApiModelProperty() 7 | entityIds?: string[] 8 | } 9 | -------------------------------------------------------------------------------- /platform/core/src/core/property/dto/property.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { IsNotEmpty } from 'class-validator' 3 | 4 | import { TPropertyType, TPropertyValue } from '@/core/property/property.types' 5 | 6 | export class PropertyDto { 7 | @ApiPropertyOptional() 8 | id?: string 9 | 10 | @IsNotEmpty() 11 | @ApiProperty() 12 | name: string 13 | 14 | @IsNotEmpty() 15 | @ApiProperty() 16 | type: TPropertyType 17 | 18 | @ApiPropertyOptional() 19 | value?: TPropertyValue 20 | 21 | @ApiPropertyOptional() 22 | valueSeparator?: string 23 | 24 | @ApiPropertyOptional() 25 | metadata?: string 26 | } 27 | -------------------------------------------------------------------------------- /platform/core/src/core/property/dto/records-by-property.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { TPropertyType } from '@/core/property/property.types' 4 | import { SearchDto } from '@/core/search/dto/search.dto' 5 | 6 | export class RecordsByPropertyDto extends SearchDto { 7 | @ApiPropertyOptional() 8 | forceType?: TPropertyType 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/core/property/dto/update-property-value.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | 4 | import { TPropertyValue } from '@/core/property/property.types' 5 | 6 | export class UpdatePropertyValueDto { 7 | @ApiPropertyOptional() 8 | @ApiModelProperty() 9 | entityIds?: string[] 10 | 11 | @ApiPropertyOptional() 12 | @ApiModelProperty({ example: 5 }) 13 | newValue?: TPropertyValue 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/core/property/dto/update-property.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | export class UpdatePropertyDto { 4 | @ApiPropertyOptional() 5 | @ApiModelProperty({ example: 'NewPropertyName' }) 6 | name?: string 7 | 8 | @ApiPropertyOptional() 9 | @ApiModelProperty({ example: 'string' }) 10 | type?: 'string' 11 | } 12 | -------------------------------------------------------------------------------- /platform/core/src/core/property/entity/property.entity.ts: -------------------------------------------------------------------------------- 1 | import { TPropertyValue } from '@/core/property/property.types' 2 | 3 | import { TPropertyInstance, TPropertyPropertiesNormalized } from '../model/property.interface' 4 | 5 | export class Property { 6 | constructor( 7 | private readonly instance: TPropertyInstance, 8 | readonly value?: TPropertyValue 9 | ) {} 10 | 11 | getProperties(): TPropertyPropertiesNormalized { 12 | return { 13 | id: this.instance.dataValues.id, 14 | name: this.instance.dataValues.name, 15 | type: this.instance.dataValues.type, 16 | projectId: this.instance.dataValues.projectId, 17 | metadata: this.instance.dataValues.metadata, 18 | value: this.value 19 | } 20 | } 21 | 22 | toJson() { 23 | return this.getProperties() 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /platform/core/src/core/property/model/property.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { Property } from '@/core/property/model/property.model' 4 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 5 | 6 | import { TPropertyFactory } from './property.interface' 7 | 8 | @Injectable() 9 | export class PropertyRepository { 10 | constructor(private readonly repositoryService: RepositoryService) {} 11 | 12 | get model() { 13 | return this.repositoryService.getModelByConfig(Property) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /platform/core/src/core/property/property.constants.ts: -------------------------------------------------------------------------------- 1 | export const PROPERTY_TYPE_STRING = 'string' as const 2 | export const PROPERTY_TYPE_DATETIME = 'datetime' as const 3 | export const PROPERTY_TYPE_BOOLEAN = 'boolean' as const 4 | export const PROPERTY_TYPE_NUMBER = 'number' as const 5 | export const PROPERTY_TYPE_NULL = 'null' as const 6 | 7 | // Special type for embeddings and vector search. Strictly for holding number[] values. 8 | export const PROPERTY_TYPE_VECTOR = 'vector' as const 9 | -------------------------------------------------------------------------------- /platform/core/src/core/relationships/dto/attach.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { MaybeArray } from '@/core/common/types' 4 | import { TRelationDirection } from '@/core/entity/entity.types' 5 | 6 | export class AttachDto { 7 | @ApiProperty() 8 | targetIds: MaybeArray 9 | 10 | @ApiPropertyOptional() 11 | type?: string 12 | 13 | @ApiPropertyOptional() 14 | direction?: TRelationDirection 15 | } 16 | -------------------------------------------------------------------------------- /platform/core/src/core/relationships/dto/detach.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { MaybeArray } from '@/core/common/types' 4 | import { TRelationDirection } from '@/core/entity/entity.types' 5 | 6 | export class DetachDto { 7 | @ApiProperty() 8 | targetIds: MaybeArray 9 | 10 | @ApiPropertyOptional() 11 | typeOrTypes?: MaybeArray 12 | 13 | @ApiPropertyOptional() 14 | direction?: TRelationDirection 15 | } 16 | -------------------------------------------------------------------------------- /platform/core/src/core/relationships/validation/schemas/relations.schema.ts: -------------------------------------------------------------------------------- 1 | import Joi = require('joi') 2 | 3 | export const targetIdsSchema = Joi.alternatives() 4 | .try(Joi.string().required().not(''), Joi.array().items(Joi.string().required().not(''))) 5 | .required() 6 | .not('') 7 | 8 | export const createRelationSchema = Joi.object({ 9 | targetIds: targetIdsSchema, 10 | type: Joi.string().optional().not('') 11 | }) 12 | 13 | export const deleteRelationsSchema = Joi.object({ 14 | targetIds: targetIdsSchema, 15 | typeOrTypes: Joi.alternatives() 16 | .try(Joi.string().optional().not(''), Joi.array().items(Joi.string().optional().not(''))) 17 | .optional() 18 | .not('') 19 | }) 20 | -------------------------------------------------------------------------------- /platform/core/src/core/search/dto/search.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | import { Aggregate, Where } from '@/core/common/types' 4 | import { TSearchSort } from '@/core/search/search.types' 5 | 6 | export class SearchDto { 7 | @ApiPropertyOptional({ default: 100 }) 8 | limit?: number 9 | 10 | @ApiPropertyOptional({ default: 0 }) 11 | skip?: number 12 | 13 | @ApiPropertyOptional() 14 | orderBy?: TSearchSort 15 | 16 | @ApiPropertyOptional({ default: {} }) 17 | where?: Where 18 | 19 | @ApiPropertyOptional({ default: {} }) 20 | aggregate?: Aggregate 21 | 22 | @ApiPropertyOptional({ default: [] }) 23 | labels?: string[] 24 | } 25 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/constants.ts: -------------------------------------------------------------------------------- 1 | import { 2 | comparisonOperators, 3 | datetimeOperators, 4 | logicalOperators, 5 | vectorOperators 6 | } from '@/core/search/search.constants' 7 | 8 | export const allowedKeys = [ 9 | ...comparisonOperators, 10 | ...datetimeOperators, 11 | ...logicalOperators, 12 | ...vectorOperators 13 | ] 14 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/errors.ts: -------------------------------------------------------------------------------- 1 | import { BadRequestException } from '@nestjs/common' 2 | 3 | export class QueryCriteriaParsingError extends BadRequestException { 4 | constructor(operator: string, value: unknown) { 5 | super( 6 | `Value ${JSON.stringify(value)} of type ${typeof value} couldn't be used with '${operator}' operator.` 7 | ) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/index.ts: -------------------------------------------------------------------------------- 1 | export { buildQuery } from './buildQuery' 2 | export { buildAggregation } from './aggregate' 3 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/orderBy.ts: -------------------------------------------------------------------------------- 1 | import { toBoolean } from '@/common/utils/toBolean' 2 | import { RUSHDB_KEY_ID, DEFAULT_RECORD_ALIAS } from '@/core/common/constants' 3 | 4 | import { SORT_DESC, SORT_ASC } from '../search.constants' 5 | import { TSearchSort, TSearchSortMap } from '../search.types' 6 | 7 | export const buildSortCriteria = (orderBy: TSearchSort) => { 8 | let sortCriteria: TSearchSortMap 9 | 10 | if (!toBoolean(orderBy)) { 11 | sortCriteria = { [RUSHDB_KEY_ID]: SORT_DESC } 12 | } else if (typeof orderBy === 'string' && (orderBy === SORT_ASC || orderBy === SORT_DESC)) { 13 | sortCriteria = { [RUSHDB_KEY_ID]: orderBy } 14 | } else { 15 | sortCriteria = orderBy 16 | } 17 | 18 | return sortCriteria 19 | } 20 | 21 | export const buildOrderByClause = (orderBy: TSearchSort, alias: string | null = DEFAULT_RECORD_ALIAS) => { 22 | const sortCriteria = buildSortCriteria(orderBy) 23 | 24 | return Object.entries(sortCriteria).map(([property, direction]) => { 25 | if (alias === null) { 26 | return `\`${property}\` ${direction.toUpperCase()}` 27 | } 28 | 29 | return `${alias}.\`${property}\` ${direction.toUpperCase()}` 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/pagination.ts: -------------------------------------------------------------------------------- 1 | // @TODO: Configure default and max with env 2 | export const pagination = (skipRaw = 0, limitRaw = 100) => { 3 | const limit = limitRaw > 0 && limitRaw <= 1000 ? limitRaw : 100 4 | const skip = skipRaw > 0 ? skipRaw : 0 5 | 6 | return { limit, skip } 7 | } 8 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/pickRecordLabel.ts: -------------------------------------------------------------------------------- 1 | import { RUSHDB_KEY_LABEL, RUSHDB_LABEL_RECORD, DEFAULT_RECORD_ALIAS } from '@/core/common/constants' 2 | 3 | export const label = (alias = DEFAULT_RECORD_ALIAS, key: string = RUSHDB_KEY_LABEL) => 4 | `${key}: [label IN labels(${alias}) WHERE label <> "${RUSHDB_LABEL_RECORD}"][0]` 5 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/projectIdInline.ts: -------------------------------------------------------------------------------- 1 | import { RUSHDB_KEY_PROJECT_ID } from '@/core/common/constants' 2 | 3 | export const projectIdInline = () => `${RUSHDB_KEY_PROJECT_ID}: $projectId` 4 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/singleLabelPart.ts: -------------------------------------------------------------------------------- 1 | import { SearchDto } from '@/core/search/dto/search.dto' 2 | 3 | export const singleLabelPart = (labels?: SearchDto['labels']) => 4 | labels && labels.length === 1 ? `:\`${labels?.[0]}\`` : '' 5 | -------------------------------------------------------------------------------- /platform/core/src/core/search/parser/types.ts: -------------------------------------------------------------------------------- 1 | import { AliasesMap } from '@/core/common/types' 2 | 3 | export type ParseContext = { 4 | nodeAliases: string[] 5 | level: number 6 | result: Record 7 | aliasesMap: AliasesMap 8 | withQueryQueue?: Record 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/core/search/search.constants.ts: -------------------------------------------------------------------------------- 1 | // Sort directions 2 | export const SORT_ASC = 'asc' as const 3 | export const SORT_DESC = 'desc' as const 4 | 5 | export const COMPARISON_OPERATORS_MAP = { 6 | $gt: '>', 7 | $gte: '>=', 8 | $lt: '<', 9 | $lte: '<=', 10 | $ne: '<>' 11 | } as const 12 | 13 | export const comparisonOperators = [ 14 | '$in', 15 | '$ne', 16 | '$nin', 17 | '$gte', 18 | '$gt', 19 | '$lte', 20 | '$lt', 21 | '$contains', 22 | '$startsWith', 23 | '$endsWith' 24 | ] 25 | 26 | export const datetimeOperators = [ 27 | '$day', 28 | '$hour', 29 | '$microsecond', 30 | '$millisecond', 31 | '$minute', 32 | '$month', 33 | '$nanosecond', 34 | '$second', 35 | '$year' 36 | ] 37 | export const vectorOperators = ['$vector'] 38 | 39 | export const logicalOperators = ['$and', '$or', '$xor', '$nor', '$not'] 40 | export const RELATION_CLAUSE_OPERATOR = '$relation' 41 | export const ALIAS_CLAUSE_OPERATOR = '$alias' 42 | export const ID_CLAUSE_OPERATOR = '$id' 43 | -------------------------------------------------------------------------------- /platform/core/src/core/search/search.types.ts: -------------------------------------------------------------------------------- 1 | import { SORT_ASC, SORT_DESC } from '@/core/search/search.constants' 2 | 3 | export type TSearchSortDirection = typeof SORT_ASC | typeof SORT_DESC 4 | export type TSearchSortMap = Record 5 | export type TSearchSort = TSearchSortMap | TSearchSortDirection 6 | 7 | export type TSearchQueryBuilderOptions = { 8 | nodeAlias: string 9 | joinOperator?: string 10 | } 11 | -------------------------------------------------------------------------------- /platform/core/src/core/search/validation/schemas/search.schema.ts: -------------------------------------------------------------------------------- 1 | import Joi = require('joi') 2 | 3 | import { SORT_DESC, SORT_ASC } from '@/core/search/search.constants' 4 | 5 | const searchDtoSchema = Joi.object({ 6 | limit: Joi.number().min(1).max(1000).optional(), 7 | skip: Joi.number().min(0).optional(), 8 | orderBy: Joi.alternatives().try( 9 | Joi.string().valid(SORT_ASC, SORT_DESC), 10 | Joi.object().pattern(Joi.string(), Joi.string().valid(SORT_ASC, SORT_DESC)).optional() 11 | ), 12 | labels: Joi.array().items(Joi.string().allow(null)).optional(), 13 | where: Joi.object() 14 | }) 15 | 16 | export const searchSchema = searchDtoSchema 17 | -------------------------------------------------------------------------------- /platform/core/src/core/transactions/transaction-context.ts: -------------------------------------------------------------------------------- 1 | import { AsyncLocalStorage } from 'async_hooks' 2 | import { Transaction } from 'neo4j-driver' 3 | 4 | export interface TransactionContext { 5 | transaction: Transaction 6 | } 7 | 8 | export const transactionStorage = new AsyncLocalStorage() 9 | -------------------------------------------------------------------------------- /platform/core/src/core/transactions/transaction.types.ts: -------------------------------------------------------------------------------- 1 | import { Session, Transaction } from 'neo4j-driver' 2 | 3 | export type TTransactionObject = { 4 | id: string 5 | projectId: string 6 | startTime: string 7 | session: Session 8 | transaction: Transaction 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/auth.types.ts: -------------------------------------------------------------------------------- 1 | export interface IOauthUrl { 2 | url: string 3 | } 4 | 5 | export interface IDecodedResetToken { 6 | login: string 7 | id: string 8 | } 9 | 10 | export type TVerifyOwnershipConfig = { 11 | nodeProperty: string 12 | projectIdProperty: string 13 | } 14 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/captcha/captcha.controller.ts: -------------------------------------------------------------------------------- 1 | import { Controller, Get, Query, UseInterceptors } from '@nestjs/common' 2 | import { ApiBearerAuth, ApiExcludeController, ApiTags } from '@nestjs/swagger' 3 | 4 | import { CommonResponseDecorator } from '@/common/decorators/common-response.decorator' 5 | import { NotFoundInterceptor } from '@/common/interceptors/not-found.interceptor' 6 | import { TransformResponseInterceptor } from '@/common/interceptors/transform-response.interceptor' 7 | import { CaptchaService } from '@/dashboard/auth/captcha/captcha.service' 8 | 9 | @Controller('captcha') 10 | @UseInterceptors(TransformResponseInterceptor, NotFoundInterceptor) 11 | @ApiExcludeController() 12 | export class CaptchaController { 13 | constructor(private readonly captchaService: CaptchaService) {} 14 | 15 | @Get() 16 | @ApiTags('Captcha') 17 | @CommonResponseDecorator() 18 | @ApiBearerAuth() 19 | async validateCaptcha(@Query('token') token?: string): Promise { 20 | return await this.captchaService.isCaptchaTokenValid(token) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/captcha/captcha.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { ConfigModule, ConfigService } from '@nestjs/config' 3 | import { GoogleRecaptchaModule } from '@nestlab/google-recaptcha' 4 | 5 | import { CaptchaController } from '@/dashboard/auth/captcha/captcha.controller' 6 | import { CaptchaService } from '@/dashboard/auth/captcha/captcha.service' 7 | import { RECAPTCHA_SCORE } from '@/dashboard/auth/captcha/constants' 8 | 9 | import { IncomingMessage } from 'http' 10 | 11 | @Module({ 12 | imports: [ 13 | GoogleRecaptchaModule.forRootAsync({ 14 | imports: [ConfigModule], 15 | useFactory: (configService: ConfigService) => ({ 16 | response: (request: IncomingMessage) => (request.headers['x-captcha'] || '').toString(), 17 | score: RECAPTCHA_SCORE, 18 | secretKey: configService.get('SERVICE_CAPTCHA_KEY') || 'SERVICE_CAPTCHA_KEY' 19 | }), 20 | inject: [ConfigService] 21 | }) 22 | ], 23 | controllers: [CaptchaController], 24 | providers: [CaptchaService], 25 | exports: [CaptchaService] 26 | }) 27 | export class CaptchaModule {} 28 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/captcha/captcha.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { ConfigService } from '@nestjs/config' 3 | import { GoogleRecaptchaException, GoogleRecaptchaValidator } from '@nestlab/google-recaptcha' 4 | 5 | import { RECAPTCHA_SCORE } from '@/dashboard/auth/captcha/constants' 6 | 7 | @Injectable() 8 | export class CaptchaService { 9 | constructor( 10 | private readonly recaptchaValidator: GoogleRecaptchaValidator, 11 | private readonly configService: ConfigService 12 | ) {} 13 | 14 | async isCaptchaTokenValid(recaptchaToken: string): Promise { 15 | const result = await this.recaptchaValidator.validate({ 16 | response: recaptchaToken, 17 | score: RECAPTCHA_SCORE 18 | }) 19 | 20 | if (!result.success) { 21 | throw new GoogleRecaptchaException(result.errors) 22 | } 23 | 24 | return true 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/captcha/constants.ts: -------------------------------------------------------------------------------- 1 | export const RECAPTCHA_SCORE = 0.7 2 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/dto/get-oauth.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiResponseProperty } from '@nestjs/swagger' 2 | 3 | import { IOauthUrl } from '@/dashboard/auth/auth.types' 4 | 5 | export class GetOauthDto implements IOauthUrl { 6 | @ApiResponseProperty() 7 | url: string 8 | } 9 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/dto/login.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | 5 | export class LoginDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | @ApiModelProperty() 9 | login: string 10 | 11 | @IsNotEmpty() 12 | @ApiProperty() 13 | @ApiModelProperty() 14 | password: string 15 | } 16 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/dto/reset-password-auth.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 2 | import { IsNotEmpty } from 'class-validator' 3 | 4 | export class ResetPasswordAuthDto { 5 | @IsNotEmpty() 6 | @ApiModelProperty() 7 | login: string 8 | 9 | @IsNotEmpty() 10 | @ApiModelProperty() 11 | password: string 12 | 13 | @IsNotEmpty() 14 | @ApiModelProperty() 15 | token: string 16 | } 17 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/encryption/encryption.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { ConfigService } from '@nestjs/config' 3 | import { hash, compare } from 'bcryptjs' 4 | 5 | const HASH_ROUNDS = 10 6 | 7 | @Injectable() 8 | export class EncryptionService { 9 | constructor(private readonly config: ConfigService) {} 10 | 11 | async hash(plain: string): Promise { 12 | return hash(plain, HASH_ROUNDS) 13 | } 14 | 15 | async compare(plain: string, encrypted: string): Promise { 16 | return compare(plain, encrypted) 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/guards/email-confirmed.guard.ts: -------------------------------------------------------------------------------- 1 | import { CanActivate, ExecutionContext, Injectable, UnauthorizedException } from '@nestjs/common' 2 | import { FastifyRequest } from 'fastify' 3 | 4 | import { TUserProperties } from '@/dashboard/user/model/user.interface' 5 | 6 | @Injectable() 7 | export class EmailConfirmationGuard implements CanActivate { 8 | canActivate(context: ExecutionContext) { 9 | const request: FastifyRequest & { user: TUserProperties } = context.switchToHttp().getRequest() 10 | 11 | if (!request.user?.confirmed) { 12 | throw new UnauthorizedException('Confirm your email first') 13 | } 14 | 15 | return true 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/auth/providers/google/google.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | import { ConfigModule, ConfigService } from '@nestjs/config' 3 | import { JwtModule } from '@nestjs/jwt' 4 | 5 | import { TOKEN_EXPIRES_IN } from '@/common/constants' 6 | import { EntityModule } from '@/core/entity/entity.module' 7 | import { GoogleOAuthController } from '@/dashboard/auth/providers/google/google.controller' 8 | import { GoogleOAuthService } from '@/dashboard/auth/providers/google/google.service' 9 | import { UserModule } from '@/dashboard/user/user.module' 10 | 11 | @Module({ 12 | imports: [ 13 | JwtModule.registerAsync({ 14 | imports: [ConfigModule], 15 | inject: [ConfigService], 16 | useFactory: (configService: ConfigService) => ({ 17 | secret: configService.get('RUSHDB_AES_256_ENCRYPTION_KEY'), 18 | signOptions: { 19 | expiresIn: TOKEN_EXPIRES_IN 20 | } 21 | }) 22 | }), 23 | UserModule, 24 | EntityModule 25 | ], 26 | providers: [GoogleOAuthService], 27 | controllers: [GoogleOAuthController], 28 | exports: [GoogleOAuthService] 29 | }) 30 | export class GoogleOAuthModule {} 31 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/billing.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { StripeModule } from '@/dashboard/billing/stripe/stripe.module' 4 | 5 | @Module({ 6 | imports: [StripeModule], 7 | exports: [StripeModule] 8 | }) 9 | export class BillingModule {} 10 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/stripe/interfaces/stripe.constans.ts: -------------------------------------------------------------------------------- 1 | export enum EConfigKeyByPlan { 2 | pro = '_PRO', 3 | start = '_START' 4 | } 5 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/stripe/interfaces/stripe.types.ts: -------------------------------------------------------------------------------- 1 | export type PlanName = 'pro' | 'start' 2 | export type PlanPeriod = 'month' | 'annual' 3 | 4 | type TPlanPayload = { 5 | amount: number 6 | priceId: string 7 | productId: string 8 | } 9 | 10 | export type TPlan = Record> 11 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/stripe/plans.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | import { PlanName, PlanPeriod } from '@/dashboard/billing/stripe/interfaces/stripe.types' 5 | 6 | export class PlansDto { 7 | @IsNotEmpty() 8 | @ApiProperty() 9 | @ApiModelProperty({ example: 'start' }) 10 | id: PlanName 11 | 12 | @IsNotEmpty() 13 | @ApiProperty() 14 | @ApiModelProperty({ example: 'month' }) 15 | period: PlanPeriod 16 | 17 | @IsNotEmpty() 18 | @ApiProperty() 19 | @ApiModelProperty({ example: 'http://localhost:3005/' }) 20 | returnUrl: string 21 | } 22 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/stripe/stripe.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common' 2 | 3 | import { TransactionModule } from '@/core/transactions/transaction.module' 4 | import { ProjectModule } from '@/dashboard/project/project.module' 5 | import { TokenModule } from '@/dashboard/token/token.module' 6 | import { WorkspaceModule } from '@/dashboard/workspace/workspace.module' 7 | 8 | import { StripeController } from './stripe.controller' 9 | import { StripeService } from './stripe.service' 10 | 11 | @Module({ 12 | imports: [ 13 | forwardRef(() => WorkspaceModule), 14 | forwardRef(() => ProjectModule), 15 | TokenModule, 16 | forwardRef(() => TransactionModule) 17 | ], 18 | controllers: [StripeController], 19 | providers: [StripeService], 20 | exports: [StripeService] 21 | }) 22 | export class StripeModule {} 23 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/billing/stripe/stripe.utils.ts: -------------------------------------------------------------------------------- 1 | import { TPlan } from '@/dashboard/billing/stripe/interfaces/stripe.types' 2 | 3 | export function getPlanKeyByPriceId(targetId: string, planMap: TPlan): string | null { 4 | const currentPlanKey = Object.keys(planMap).find((planKey) => 5 | Object.values(planMap[planKey]).some((period) => { 6 | const price = period.priceId 7 | return price === targetId 8 | }) 9 | ) 10 | 11 | return currentPlanKey || null 12 | } 13 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/common/constants.ts: -------------------------------------------------------------------------------- 1 | export const RUSHDB_LABEL_USER = '__RUSHDB__LABEL__USER__' as const 2 | export const RUSHDB_LABEL_WORKSPACE = '__RUSHDB__LABEL__WORKSPACE__' as const 3 | export const RUSHDB_LABEL_PROJECT = '__RUSHDB__LABEL__PROJECT__' as const 4 | export const RUSHDB_LABEL_TOKEN = '__RUSHDB__LABEL__TOKEN__' as const 5 | 6 | export const RUSHDB_RELATION_CONTAINS = '__RUSHDB__RELATION__CONTAINS__' as const 7 | export const RUSHDB_RELATION_HAS_ACCESS = '__RUSHDB__RELATION__HAS_ACCESS__' as const 8 | export const RUSHDB_RELATION_MEMBER_OF = '__RUSHDB__RELATION__MEMBER_OF__' as const 9 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/dashboard.module.ts: -------------------------------------------------------------------------------- 1 | import { Module } from '@nestjs/common' 2 | 3 | import { AuthModule } from '@/dashboard/auth/auth.module' 4 | import { BillingModule } from '@/dashboard/billing/billing.module' 5 | import { MailModule } from '@/dashboard/mail/mail.module' 6 | import { ProjectModule } from '@/dashboard/project/project.module' 7 | import { TokenModule } from '@/dashboard/token/token.module' 8 | import { UserModule } from '@/dashboard/user/user.module' 9 | import { WorkspaceModule } from '@/dashboard/workspace/workspace.module' 10 | 11 | @Module({ 12 | imports: [WorkspaceModule, ProjectModule, TokenModule, AuthModule, MailModule, UserModule, BillingModule], 13 | exports: [WorkspaceModule, ProjectModule, TokenModule, AuthModule, MailModule, UserModule, BillingModule] 14 | }) 15 | export class DashboardModule {} 16 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/project/dto/create-project.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | import { TProjectCustomDbPayload } from '@/dashboard/project/project.types' 5 | 6 | export class CreateProjectDto { 7 | @IsNotEmpty() 8 | @ApiProperty() 9 | @ApiModelProperty({ example: 'Reddit' }) 10 | name: string 11 | 12 | @ApiPropertyOptional({ example: 'Simple forum' }) 13 | description: string 14 | 15 | @ApiPropertyOptional({ 16 | example: { 17 | url: 'bolt://custom-neo4j-host:7687', 18 | username: 'neo4j', 19 | password: 'password' 20 | } 21 | }) 22 | customDb: TProjectCustomDbPayload 23 | } 24 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/project/dto/update-project.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { TProjectCustomDbPayload } from '@/dashboard/project/project.types' 4 | 5 | // @TODO: Optional Props and more options 6 | export class UpdateProjectDto { 7 | @ApiProperty() 8 | @ApiModelProperty({ example: 'Booking.com' }) 9 | name: string 10 | 11 | @ApiPropertyOptional({ example: 'Simple booking app' }) 12 | description: string 13 | 14 | @ApiPropertyOptional({ 15 | example: { 16 | url: 'bolt://custom-neo4j-host:7687', 17 | username: 'neo4j', 18 | password: 'password' 19 | } 20 | }) 21 | customDb: TProjectCustomDbPayload 22 | } 23 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/project/entity/project.entity.ts: -------------------------------------------------------------------------------- 1 | import { IProjectProperties } from '@/dashboard/project/model/project.interface' 2 | 3 | export class ProjectEntity { 4 | constructor( 5 | private readonly id: string, 6 | private readonly name: string, 7 | private readonly created: string, 8 | private readonly description?: string, 9 | private readonly edited?: string, 10 | private readonly customDb?: string 11 | ) {} 12 | 13 | getProperties(): IProjectProperties { 14 | return { 15 | id: this.id, 16 | name: this.name, 17 | created: this.created, 18 | edited: this.edited, 19 | description: this.description, 20 | customDb: this.customDb 21 | } 22 | } 23 | 24 | toJson() { 25 | return this.getProperties() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/project/model/project.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { TProjectModel } from '@/dashboard/project/model/project.interface' 4 | import { Project } from '@/dashboard/project/model/project.model' 5 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 6 | 7 | @Injectable() 8 | export class ProjectRepository { 9 | constructor(private readonly repositoryService: RepositoryService) {} 10 | 11 | get model() { 12 | return this.repositoryService.getModelByConfig(Project) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/project/project.types.ts: -------------------------------------------------------------------------------- 1 | export type TProjectStats = { 2 | records: number 3 | properties: number 4 | } 5 | 6 | export type TProjectCustomDbPayload = { 7 | url: string 8 | username: string 9 | password: string 10 | } 11 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/throttle/throttle.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | import { ConfigService } from '@nestjs/config' 3 | import { ThrottlerOptionsFactory, ThrottlerModuleOptions } from '@nestjs/throttler' 4 | 5 | import { toBoolean } from '@/common/utils/toBolean' 6 | 7 | @Injectable() 8 | export class ThrottleService implements ThrottlerOptionsFactory { 9 | constructor(private readonly configService: ConfigService) {} 10 | 11 | // @TODO: Make it sensitive to current plan 12 | createThrottlerOptions(): ThrottlerModuleOptions { 13 | const ttl = this.configService.get('RATE_LIMITER_TTL') || 1000 14 | const limit = 15 | toBoolean(this.configService.get('RUSHDB_SELF_HOSTED')) ? 1000 : ( 16 | this.configService.get('RATE_LIMITER_REQUESTS_LIMIT') || 10 17 | ) 18 | 19 | return { 20 | throttlers: [ 21 | { 22 | ttl, 23 | limit 24 | } 25 | ] 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/dto/create-token.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | 5 | export class CreateTokenDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | @ApiModelProperty({ example: 'PhotoApp Token' }) 9 | name: string 10 | 11 | @ApiPropertyOptional({ example: 'PhotoApp Token' }) 12 | description: string 13 | 14 | @IsNotEmpty() 15 | @ApiProperty({ example: '30d' }) 16 | expiration: string | '*' 17 | } 18 | 19 | export class VerifyTokenDto { 20 | @IsNotEmpty() 21 | @ApiProperty() 22 | token: string 23 | } 24 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/entity/token.entity.ts: -------------------------------------------------------------------------------- 1 | import { ITokenProperties } from '@/dashboard/token/model/token.interface' 2 | 3 | export class TokenEntity { 4 | constructor( 5 | private readonly id: string, 6 | private readonly name: string, 7 | private readonly created: string, 8 | private readonly expiration: number, 9 | private readonly value: string, 10 | private readonly description?: string 11 | ) {} 12 | 13 | getProperties(): ITokenProperties & { value: string } { 14 | return { 15 | id: this.id, 16 | name: this.name, 17 | created: this.created, 18 | expiration: this.expiration, 19 | value: this.value, 20 | description: this.description 21 | } 22 | } 23 | 24 | toJson() { 25 | return this.getProperties() 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/model/token.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { TTokenModel } from '@/dashboard/token/model/token.interface' 4 | import { Token } from '@/dashboard/token/model/token.model' 5 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 6 | 7 | @Injectable() 8 | export class TokenRepository { 9 | constructor(private readonly repositoryService: RepositoryService) {} 10 | 11 | get model() { 12 | return this.repositoryService.getModelByConfig(Token) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/token-query.service.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { QueryBuilder } from '@/common/QueryBuilder' 4 | import { 5 | RUSHDB_LABEL_PROJECT, 6 | RUSHDB_LABEL_TOKEN, 7 | RUSHDB_LABEL_WORKSPACE 8 | } from '@/dashboard/common/constants' 9 | 10 | @Injectable() 11 | export class TokenQueryService { 12 | traverseTokenData() { 13 | const queryBuilder = new QueryBuilder() 14 | 15 | queryBuilder 16 | .append( 17 | `MATCH (token:${RUSHDB_LABEL_TOKEN} { id: $tokenId })-[relation]->(project:${RUSHDB_LABEL_PROJECT})--(workspace:${RUSHDB_LABEL_WORKSPACE})` 18 | ) 19 | .append(`RETURN token, project, workspace, relation.level as level`) 20 | 21 | return queryBuilder.build() 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/token.constants.ts: -------------------------------------------------------------------------------- 1 | export const WRITE_ACCESS = 'write' as const 2 | export const READ_ACCESS = 'read' as const 3 | 4 | export const ACCESS_WEIGHT = { 5 | [WRITE_ACCESS]: 1, 6 | [READ_ACCESS]: 0 7 | } 8 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/token/token.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common' 2 | 3 | import { ProjectModule } from '@/dashboard/project/project.module' 4 | import { TokenRepository } from '@/dashboard/token/model/token.repository' 5 | import { TokenQueryService } from '@/dashboard/token/token-query.service' 6 | import { TokenController } from '@/dashboard/token/token.controller' 7 | import { TokenService } from '@/dashboard/token/token.service' 8 | 9 | @Module({ 10 | imports: [forwardRef(() => ProjectModule)], 11 | providers: [TokenRepository, TokenService, TokenQueryService], 12 | exports: [TokenRepository, TokenService, TokenQueryService], 13 | controllers: [TokenController] 14 | }) 15 | export class TokenModule {} 16 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/decorators/user.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | 3 | import { IUserClaims } from '@/dashboard/user/interfaces/user-claims.interface' 4 | 5 | export const AuthUser = createParamDecorator((data, ctx) => { 6 | const request = ctx.switchToHttp().getRequest() 7 | return request.user 8 | }) 9 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/dto/create-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | 5 | export class CreateUserDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | @ApiModelProperty() 9 | login: string 10 | 11 | @IsNotEmpty() 12 | @ApiProperty() 13 | @ApiModelProperty() 14 | password: string 15 | 16 | @ApiProperty() 17 | @ApiModelProperty() 18 | firstName: string 19 | 20 | @ApiProperty() 21 | @ApiModelProperty() 22 | lastName: string 23 | } 24 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/dto/update-user.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | 4 | // @TODO: Optional Props and more options 5 | export class UpdateUserDto { 6 | @ApiProperty() 7 | @ApiModelProperty() 8 | firstName?: string 9 | 10 | @ApiModelProperty() 11 | @ApiProperty() 12 | lastName?: string 13 | 14 | @ApiModelProperty() 15 | @ApiProperty() 16 | settings?: string 17 | } 18 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/interfaces/authenticated-user.interface.ts: -------------------------------------------------------------------------------- 1 | import { User } from '@/dashboard/user/user.entity' 2 | 3 | import { IUserProperties } from './user-properties.interface' 4 | import { TUserRoles } from '@/dashboard/user/model/user.interface' 5 | import { IUserClaims } from '@/dashboard/user/interfaces/user-claims.interface' 6 | 7 | export interface IAuthenticatedUser extends IUserProperties { 8 | token: string 9 | } 10 | 11 | export interface IAuthenticatedUserWithAccess extends IAuthenticatedUser { 12 | currentScope: { 13 | role: TUserRoles 14 | } 15 | } 16 | 17 | export type TShortUserDataWithRole = Pick & { 18 | role: TUserRoles 19 | } 20 | 21 | export interface ICreatedUserData { 22 | userData: User 23 | workspaceId?: string 24 | } 25 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/interfaces/user-claims.interface.ts: -------------------------------------------------------------------------------- 1 | import { TUserProperties } from '../model/user.interface' 2 | 3 | type TIUserClaims = 'login' | 'firstName' | 'lastName' | 'id' | 'confirmed' 4 | 5 | export interface IUserClaims extends Pick {} 6 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/interfaces/user-properties.interface.ts: -------------------------------------------------------------------------------- 1 | import { TUserProperties } from '../model/user.interface' 2 | 3 | type TIUserAuthProperties = 'googleAuth' | 'githubAuth' | 'password' 4 | 5 | export type AcceptWorkspaceInvitationParams = { 6 | inviteToken: string 7 | authUserLogin: string 8 | } 9 | export interface IUserProperties extends Omit {} 10 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/interfaces/user.constants.ts: -------------------------------------------------------------------------------- 1 | export const USER_STATUS_ACTIVE = 'active' as const 2 | export const USER_STATUS_BLOCKED = 'blocked' as const 3 | export const USER_STATUS_DEACTIVATED = 'deactivated' as const 4 | export const USER_STATUS_DELETED = 'deleted' as const 5 | 6 | export const USER_ROLE_OWNER = 'owner' as const 7 | export const USER_ROLE_ADMIN = 'admin' as const 8 | export const USER_ROLE_EDITOR = 'developer' as const 9 | export const USER_ROLE_VIEWER = 'viewer' as const 10 | 11 | export const USER_ROLE_WEIGHT = { 12 | [USER_ROLE_VIEWER]: 0, 13 | [USER_ROLE_EDITOR]: 1, 14 | [USER_ROLE_ADMIN]: 2, 15 | [USER_ROLE_OWNER]: 3 16 | } as const 17 | 18 | export const USER_ROLE_LIST = [USER_ROLE_OWNER, USER_ROLE_ADMIN, USER_ROLE_EDITOR, USER_ROLE_VIEWER] as const 19 | export const USER_STATUS_LIST = [ 20 | USER_STATUS_ACTIVE, 21 | USER_STATUS_BLOCKED, 22 | USER_STATUS_DEACTIVATED, 23 | USER_STATUS_DELETED 24 | ] as const 25 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/model/user.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { TUserFactory } from '@/dashboard/user/model/user.interface' 4 | import { User } from '@/dashboard/user/model/user.model' 5 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 6 | 7 | @Injectable() 8 | export class UserRepository { 9 | constructor(private readonly repositoryService: RepositoryService) {} 10 | 11 | get model() { 12 | return this.repositoryService.getModelByConfig(User) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/user/user.utils.ts: -------------------------------------------------------------------------------- 1 | export const sanitizeSettings = (settings: string | undefined) => { 2 | if (!settings) { 3 | return 4 | } 5 | 6 | let parsedPayload = {} 7 | 8 | try { 9 | parsedPayload = JSON.parse(settings) 10 | } catch (e) { 11 | return 12 | } 13 | 14 | const clearedSettings = {} 15 | 16 | for (const propName in parsedPayload) { 17 | const validProp = 18 | parsedPayload[propName] !== null && 19 | parsedPayload[propName] !== undefined && 20 | (typeof parsedPayload[propName] === 'string' || 21 | typeof parsedPayload[propName] === 'boolean' || 22 | typeof parsedPayload[propName] === 'number') 23 | 24 | if (validProp) { 25 | clearedSettings[propName] = parsedPayload[propName] 26 | } 27 | } 28 | 29 | return JSON.stringify(clearedSettings) 30 | } 31 | 32 | export const validateEmail = (email: string) => { 33 | return String(email) 34 | .toLowerCase() 35 | .match( 36 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|.(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ 37 | ) 38 | } 39 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/create-workspace.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | 5 | export class CreateWorkspaceDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | @ApiModelProperty() 9 | name: string 10 | } 11 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/edit-workspace.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiPropertyOptional } from '@nestjs/swagger' 2 | 3 | export class EditWorkspaceDto { 4 | @ApiPropertyOptional() 5 | name?: string 6 | } 7 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/invite-to-workspace.dto.ts: -------------------------------------------------------------------------------- 1 | import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger' 2 | import { ApiModelProperty } from '@nestjs/swagger/dist/decorators/api-model-property.decorator' 3 | import { IsNotEmpty } from 'class-validator' 4 | 5 | export class InviteToWorkspaceDto { 6 | @IsNotEmpty() 7 | @ApiProperty() 8 | @ApiModelProperty() 9 | email: string 10 | 11 | @ApiPropertyOptional() 12 | projectIds: string[] 13 | } 14 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/recompute-access-list.dto.ts: -------------------------------------------------------------------------------- 1 | export class RecomputeAccessListDto { 2 | [projectId: string]: string[] 3 | } 4 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/remove-pending-invite.dto.ts: -------------------------------------------------------------------------------- 1 | import { IsEmail } from 'class-validator' 2 | 3 | export class RemovePendingInviteDto { 4 | @IsEmail() 5 | email!: string 6 | } 7 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/dto/revoke-access.dto.ts: -------------------------------------------------------------------------------- 1 | import { ArrayNotEmpty, IsArray, IsString } from 'class-validator' 2 | 3 | export class RevokeAccessDto { 4 | @IsArray() 5 | @ArrayNotEmpty() 6 | @IsString({ each: true }) 7 | userIds: string[] 8 | } 9 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/entity/workspace.entity.ts: -------------------------------------------------------------------------------- 1 | import { IWorkspaceProperties, TWorkspaceInstance } from '@/dashboard/workspace/model/workspace.interface' 2 | 3 | export class Workspace { 4 | constructor(private readonly node: TWorkspaceInstance) {} 5 | 6 | getProperties(): IWorkspaceProperties { 7 | return { 8 | id: this.node.dataValues.id, 9 | name: this.node.dataValues.name, 10 | created: this.node.dataValues.created, 11 | edited: this.node.dataValues.edited, 12 | limits: this.node.dataValues.limits, 13 | planId: this.node.dataValues.planId, 14 | validTill: this.node.dataValues.validTill, 15 | isSubscriptionCancelled: this.node.dataValues.isSubscriptionCancelled 16 | } 17 | } 18 | 19 | toJson() { 20 | return this.getProperties() 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/model/workspace.repository.ts: -------------------------------------------------------------------------------- 1 | import { Injectable } from '@nestjs/common' 2 | 3 | import { TWorkspaceModel } from '@/dashboard/workspace/model/workspace.interface' 4 | import { Workspace } from '@/dashboard/workspace/model/workspace.model' 5 | import { RepositoryService } from '@/database/neogma/repository/repository.service' 6 | 7 | @Injectable() 8 | export class WorkspaceRepository { 9 | constructor(private readonly repositoryService: RepositoryService) {} 10 | 11 | get model() { 12 | return this.repositoryService.getModelByConfig(Workspace) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/workspace.constants.ts: -------------------------------------------------------------------------------- 1 | import { TWorkspaceLimits } from '@/dashboard/workspace/model/workspace.interface' 2 | 3 | export const WORKSPACE_LIMITS_SELF_HOSTED: TWorkspaceLimits = { 4 | records: null, 5 | projects: null, 6 | importSize: null, 7 | users: null 8 | } 9 | 10 | export const WORKSPACE_LIMITS_DEFAULT: TWorkspaceLimits = { 11 | records: 10_000, 12 | projects: 2, 13 | importSize: 5 * 1024 * 1024, // 5 MB 14 | users: 1 15 | } 16 | 17 | export const WORKSPACE_LIMITS_START: TWorkspaceLimits = { 18 | records: 100_000, 19 | projects: null, // UNLIMITED 20 | importSize: 32 * 1024 * 1024, // 32 MB 21 | users: 3 22 | } 23 | 24 | export const WORKSPACE_LIMITS_PRO: TWorkspaceLimits = { 25 | records: 1_000_000, 26 | projects: null, // UNLIMITED 27 | importSize: 32 * 1024 * 1024, // 32 MB 28 | users: 10 29 | } 30 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/workspace.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common' 2 | 3 | import { EntityModule } from '@/core/entity/entity.module' 4 | import { BillingModule } from '@/dashboard/billing/billing.module' 5 | import { ProjectModule } from '@/dashboard/project/project.module' 6 | import { TokenModule } from '@/dashboard/token/token.module' 7 | import { WorkspaceRepository } from '@/dashboard/workspace/model/workspace.repository' 8 | import { WorkspaceController } from '@/dashboard/workspace/workspace.controller' 9 | import { WorkspaceService } from '@/dashboard/workspace/workspace.service' 10 | import { WorkspaceQueryService } from '@/dashboard/workspace/workspace-query.service' 11 | 12 | @Module({ 13 | imports: [ 14 | forwardRef(() => ProjectModule), 15 | forwardRef(() => BillingModule), 16 | forwardRef(() => TokenModule), 17 | forwardRef(() => EntityModule) 18 | ], 19 | providers: [WorkspaceRepository, WorkspaceService, WorkspaceQueryService], 20 | exports: [WorkspaceRepository, WorkspaceService, WorkspaceQueryService], 21 | controllers: [WorkspaceController] 22 | }) 23 | export class WorkspaceModule {} 24 | -------------------------------------------------------------------------------- /platform/core/src/dashboard/workspace/workspace.types.ts: -------------------------------------------------------------------------------- 1 | import { TUserRoles } from '@/dashboard/user/model/user.interface' 2 | import { TWorkspaceProperties } from '@/dashboard/workspace/model/workspace.interface' 3 | 4 | export type TWorkSpaceInviteToken = { 5 | workspaceId: string 6 | email: string 7 | projectIds?: string[] 8 | } 9 | 10 | export type TWorkspaceInvitation = TWorkSpaceInviteToken & { 11 | workspaceName: string 12 | senderEmail: string 13 | } 14 | 15 | export type TExtendedWorkspaceProperties = TWorkspaceProperties & { 16 | role: TUserRoles 17 | } 18 | 19 | export type TNormalizedPendingInvite = { 20 | email: string 21 | createdAt: string 22 | } 23 | -------------------------------------------------------------------------------- /platform/core/src/database/db-connection/db-connection.module.ts: -------------------------------------------------------------------------------- 1 | import { Module, forwardRef } from '@nestjs/common' 2 | 3 | import { ProjectModule } from '@/dashboard/project/project.module' 4 | 5 | import { NeogmaModule } from '../neogma/neogma.module' 6 | import { NeogmaDynamicModule } from '../neogma-dynamic/neogma-dynamic.module' 7 | 8 | import { DbConnectionService } from './db-connection.service' 9 | 10 | @Module({ 11 | imports: [NeogmaModule, NeogmaDynamicModule, forwardRef(() => ProjectModule)], 12 | providers: [DbConnectionService], 13 | exports: [DbConnectionService] 14 | }) 15 | export class DbConnectionModule {} 16 | -------------------------------------------------------------------------------- /platform/core/src/database/db-context.ts: -------------------------------------------------------------------------------- 1 | import { Neogma } from 'neogma' 2 | 3 | import { AsyncLocalStorage } from 'async_hooks' 4 | 5 | export interface DbContext { 6 | projectId: string 7 | connection: Neogma 8 | } 9 | // @ts-expect-error outdated ts declrations 10 | export const dbContextStorage = new AsyncLocalStorage({}) 11 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma-dynamic/custom-transaction.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | import { Transaction } from 'neo4j-driver' 3 | 4 | export const CustomTransactionDecorator = createParamDecorator( 5 | (data, ctx) => { 6 | const request = ctx.switchToHttp().getRequest() 7 | return request.customTransaction 8 | } 9 | ) 10 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma-dynamic/neogma-dynamic.module.ts: -------------------------------------------------------------------------------- 1 | import { forwardRef, Module } from '@nestjs/common' 2 | 3 | import { NeogmaModule } from '@/database/neogma/neogma.module' 4 | import { NeogmaDynamicService } from '@/database/neogma-dynamic/neogma-dynamic.service' 5 | import { CompositeNeogmaService } from '@/database/neogma-dynamic/composite-neogma.service' 6 | 7 | @Module({ 8 | imports: [forwardRef(() => NeogmaModule)], 9 | providers: [NeogmaDynamicService, CompositeNeogmaService], 10 | exports: [NeogmaDynamicService, CompositeNeogmaService] 11 | }) 12 | export class NeogmaDynamicModule {} 13 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma-dynamic/preferred-transaction.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | import { Transaction } from 'neo4j-driver' 3 | import { transactionStorage } from '@/core/transactions/transaction-context' 4 | 5 | export const PreferredTransactionDecorator = createParamDecorator( 6 | (data, ctx) => { 7 | const request = ctx.switchToHttp().getRequest() 8 | const context = transactionStorage.getStore() 9 | return request.customTransaction || context?.transaction || request.transaction 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma/neogma-config.interface.ts: -------------------------------------------------------------------------------- 1 | export interface INeogmaConfig { 2 | url: string 3 | username: string 4 | password: string 5 | mode?: 'production' | 'development' 6 | } 7 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma/neogma.constants.ts: -------------------------------------------------------------------------------- 1 | export const NEOGMA_CONFIG = 'NEOGMA_CONFIG' as const 2 | export const NEOGMA_INSTANCE = 'NEOGMA_INSTANCE' as const 3 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma/neogma.util.ts: -------------------------------------------------------------------------------- 1 | import { Logger } from '@nestjs/common' 2 | import { Neogma } from 'neogma' 3 | 4 | import { INeogmaConfig } from './neogma-config.interface' 5 | 6 | export const createInstance = async (config: INeogmaConfig): Promise => { 7 | return new Neogma( 8 | { 9 | url: config.url, 10 | username: config.username, 11 | password: config.password 12 | }, 13 | { 14 | maxConnectionPoolSize: 0 15 | // logging: { 16 | // level: 'debug', 17 | // logger: (level, message) => Logger.debug(new Date().toISOString() + ': ' + level + ' ' + message) 18 | // } 19 | } 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma/repository/types.ts: -------------------------------------------------------------------------------- 1 | export type TModelName = { name: string } 2 | 3 | export type TModelSourceType = { canUseExternalSource?: boolean } 4 | -------------------------------------------------------------------------------- /platform/core/src/database/neogma/transaction.decorator.ts: -------------------------------------------------------------------------------- 1 | import { createParamDecorator, ExecutionContext } from '@nestjs/common' 2 | import { transactionStorage } from '@/core/transactions/transaction-context' 3 | import { Transaction } from 'neo4j-driver' 4 | 5 | export const TransactionDecorator = createParamDecorator( 6 | (data, ctx) => { 7 | const request = ctx.switchToHttp().getRequest() 8 | const context = transactionStorage.getStore() 9 | return context?.transaction || request.transaction 10 | } 11 | ) 12 | -------------------------------------------------------------------------------- /platform/core/test/app.e2e-spec.ts: -------------------------------------------------------------------------------- 1 | import { INestApplication } from '@nestjs/common' 2 | import { Test, TestingModule } from '@nestjs/testing' 3 | import * as request from 'supertest' 4 | 5 | import { AppModule } from '@/app.module' 6 | 7 | describe('AppController (e2e)', () => { 8 | let app: INestApplication 9 | 10 | beforeEach(async () => { 11 | const moduleFixture: TestingModule = await Test.createTestingModule({ 12 | imports: [AppModule] 13 | }).compile() 14 | 15 | app = moduleFixture.createNestApplication() 16 | await app.init() 17 | }) 18 | 19 | it('/ (GET)', () => { 20 | return request(app.getHttpServer()).get('/').expect(200).expect('Hello World!') 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /platform/core/test/jest-e2e.json: -------------------------------------------------------------------------------- 1 | { 2 | "moduleFileExtensions": ["js", "json", "ts"], 3 | "rootDir": ".", 4 | "testEnvironment": "node", 5 | "testRegex": ".e2e-spec.ts$", 6 | "transform": { 7 | "^.+\\.(t|j)s$": "ts-jest" 8 | }, 9 | "moduleNameMapper": { 10 | "^@/(.*)$": "/../src/$1" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /platform/core/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "exclude": [ 4 | "node_modules", 5 | "test", 6 | "dist", 7 | "**/*spec.ts" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /platform/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "watchOptions": { 3 | // Use polling instead of the built-in file watchers that are based on OS 4 | "watchFile": "priorityPollingInterval", 5 | "watchDirectory": "dynamicprioritypolling", 6 | "fallbackPolling": "dynamicPriority", 7 | "synchronousWatchDirectory": true, 8 | "excludeDirectories": ["**/node_modules", "dist"] 9 | }, 10 | "compilerOptions": { 11 | "module": "commonjs", 12 | "declaration": true, 13 | "removeComments": true, 14 | "emitDecoratorMetadata": true, 15 | "experimentalDecorators": true, 16 | "allowSyntheticDefaultImports": true, 17 | "target": "esnext", 18 | "sourceMap": true, 19 | "outDir": "./dist", 20 | "baseUrl": "./", 21 | "incremental": true, 22 | "skipLibCheck": true, 23 | "paths": { 24 | "@/*": [ 25 | "src/*" 26 | ] 27 | } 28 | }, 29 | "exclude": [ 30 | "node_modules" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /platform/dashboard/.env.example: -------------------------------------------------------------------------------- 1 | VITE_BACKEND_BASE_URL= 2 | -------------------------------------------------------------------------------- /platform/dashboard/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /platform/dashboard/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | RushDB 13 | 14 | 15 |
16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /platform/dashboard/postcss.config.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-anonymous-default-export */ 2 | export default { 3 | plugins: { 4 | autoprefixer: {}, 5 | 'postcss-preset-env': {}, 6 | tailwindcss: {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /platform/dashboard/public/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/android-chrome-192x192.png -------------------------------------------------------------------------------- /platform/dashboard/public/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/android-chrome-512x512.png -------------------------------------------------------------------------------- /platform/dashboard/public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/apple-touch-icon.png -------------------------------------------------------------------------------- /platform/dashboard/public/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | #3f81ff 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /platform/dashboard/public/favicon-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/favicon-150x150.png -------------------------------------------------------------------------------- /platform/dashboard/public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/favicon-16x16.png -------------------------------------------------------------------------------- /platform/dashboard/public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/favicon-32x32.png -------------------------------------------------------------------------------- /platform/dashboard/public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/favicon-96x96.png -------------------------------------------------------------------------------- /platform/dashboard/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/favicon.ico -------------------------------------------------------------------------------- /platform/dashboard/public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "RushDB", 3 | "short_name": "RushDB", 4 | "icons": [ 5 | { 6 | "src": "/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#3f81ff", 19 | "background_color": "#3f81ff", 20 | "display": "standalone" 21 | } 22 | -------------------------------------------------------------------------------- /platform/dashboard/public/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /platform/dashboard/public/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rush-db/rushdb/ebc238af3710ee5373dfc14b63984a66f4ce235b/platform/dashboard/public/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/contains.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/ends_with.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/equals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/greater.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/greater_or_equals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/less.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/less_or_equals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/not_equals.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/assets/icons/starts_with.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /platform/dashboard/src/config.ts: -------------------------------------------------------------------------------- 1 | /** Default limit for pagination */ 2 | export const DEFAULT_LIMIT = 100 3 | 4 | export const BASE_URL = import.meta.env.VITE_BACKEND_BASE_URL 5 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Badge.tsx: -------------------------------------------------------------------------------- 1 | import { VariantProps, cva } from 'class-variance-authority' 2 | 3 | export const badgeVariants = cva('flex items-center gap-1', { 4 | defaultVariants: { size: 'medium', variant: 'primary' }, 5 | variants: { 6 | size: { 7 | medium: 'h-5 px-2 text-2xs font-medium rounded-full' 8 | }, 9 | variant: { 10 | primary: 'bg-content3/10 opacity-70' 11 | } 12 | } 13 | }) 14 | 15 | type BadgeProps = { 16 | children: React.ReactNode 17 | className?: string 18 | } & VariantProps 19 | 20 | export function Badge({ children, size, variant, className }: BadgeProps) { 21 | return ( 22 |
23 | {children} 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Banner.tsx: -------------------------------------------------------------------------------- 1 | import type { ReactNode } from 'react' 2 | 3 | import { cn } from '~/lib/utils' 4 | 5 | export function Banner({ 6 | className, 7 | title, 8 | action, 9 | image 10 | }: TPolymorphicComponentProps< 11 | 'div', 12 | { action?: ReactNode; image?: ReactNode; title?: string } 13 | >) { 14 | return ( 15 |
21 | {image &&
{image}
} 22 | {title &&

{title}

} 23 | {action &&
{action}
} 24 |
25 | ) 26 | } 27 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/ButtonGroup.module.css: -------------------------------------------------------------------------------- 1 | .wrapper > *:not(:first-child):not(:last-child) { 2 | @apply rounded-none; 3 | } 4 | 5 | .wrapper > *:not(:first-child) { 6 | @apply !border-l; 7 | } 8 | 9 | .wrapper > *:not(:last-child) { 10 | @apply !border-r-0; 11 | } 12 | 13 | .wrapper > :first-child:not(:only-child) { 14 | @apply rounded-r-none; 15 | } 16 | 17 | .wrapper > :last-child:not(:only-child) { 18 | @apply rounded-l-none; 19 | } 20 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/ButtonGroup.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '~/lib/utils' 2 | 3 | // tailwind idi nahui 4 | import styles from './ButtonGroup.module.css' 5 | 6 | export function ButtonGroup({ 7 | className, 8 | ...props 9 | }: TPolymorphicComponentProps<'div'>) { 10 | return ( 11 |
16 | ) 17 | } 18 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Divider.tsx: -------------------------------------------------------------------------------- 1 | import { cn } from '~/lib/utils' 2 | 3 | export function Divider({ 4 | className, 5 | vertical, 6 | ...props 7 | }: TPolymorphicComponentProps<'div', { vertical?: boolean }>) { 8 | return ( 9 |
21 | ) 22 | } 23 | 24 | export function AngledSeparator({ size = 24 }: { size?: number | string }) { 25 | return ( 26 | 27 | 28 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Label.tsx: -------------------------------------------------------------------------------- 1 | import type { PropsWithChildren } from 'react' 2 | 3 | import { cn } from '~/lib/utils' 4 | 5 | export const Label = ({ 6 | children, 7 | className, 8 | ...props 9 | }: PropsWithChildren & TPolymorphicComponentProps<'div'>) => ( 10 |
17 | {children} 18 |
19 | ) 20 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Link.tsx: -------------------------------------------------------------------------------- 1 | // @ts-nocheck 2 | import { forwardRef } from 'react' 3 | 4 | import { cn } from '~/lib/utils' 5 | 6 | const variants = { 7 | accent: 'text-accent hover:opacity-80 disabled:opacity-50', 8 | text: 'text-content hover:opacity-80 disabled:opacity-50' 9 | } 10 | 11 | const sizes = { 12 | medium: '[&>svg]:w-[24px] [&>svg]:h-[24px]', 13 | small: '[&>svg]:w-[16px] [&>svg]:h-[16px] text-sm', 14 | xsmall: '[&>svg]:w-[16px] [&>svg]:h-[16px] text-xs' 15 | } 16 | 17 | export const Link: TPolymorphicComponent< 18 | { 19 | size?: keyof typeof sizes 20 | variant?: keyof typeof variants 21 | disabled?: boolean 22 | }, 23 | 'a' 24 | > = forwardRef(({ as = 'a', className, size = 'medium', variant = 'accent', disabled, ...props }, ref) => { 25 | const Element = as 26 | 27 | return ( 28 | 34 | ) 35 | }) 36 | 37 | Link.displayName = 'Link' 38 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/NothingFound.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentProps } from 'react' 2 | 3 | import { SearchX } from 'lucide-react' 4 | 5 | import { Banner } from './Banner' 6 | 7 | export function NothingFound({ 8 | className, 9 | title = 'Nothing found...', 10 | action 11 | }: TPolymorphicComponentProps<'div', ComponentProps>) { 12 | return ( 13 | } 17 | title={title} 18 | /> 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/Skeleton.tsx: -------------------------------------------------------------------------------- 1 | import { forwardRef } from 'react' 2 | 3 | import { cn } from '~/lib/utils' 4 | 5 | export const Skeleton = forwardRef< 6 | HTMLDivElement, 7 | React.HTMLAttributes & { enabled?: boolean } 8 | >(({ className, enabled, ...props }, ref) => { 9 | return ( 10 |
15 | ) 16 | }) 17 | Skeleton.displayName = 'Skeleton' 18 | -------------------------------------------------------------------------------- /platform/dashboard/src/elements/SvgIcon.tsx: -------------------------------------------------------------------------------- 1 | export function SvgIcon({ 2 | name, 3 | prefix = 'icon', 4 | color = 'currentColor', 5 | width = 24, 6 | height = 24, 7 | ...props 8 | }: TPolymorphicComponentProps< 9 | 'svg', 10 | { 11 | prefix?: string 12 | } 13 | >) { 14 | const symbolId = `#${prefix}-${name}` 15 | 16 | return ( 17 | 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/components/ConfirmEmailNotification.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | 3 | import { Button } from '~/elements/Button' 4 | import { GlobalNotification } from '~/elements/GlobalNotification' 5 | import { resendConfirmationLink } from '~/features/auth/stores/auth' 6 | import { useUser } from '~/features/auth/stores/user' 7 | 8 | export function ConfirmEmailNotification() { 9 | const [didResend, setResent] = useState(false) 10 | const user = useUser() 11 | 12 | if (user.confirmed) { 13 | return null 14 | } 15 | 16 | return ( 17 | resendConfirmationLink().then(() => setResent(true))} 22 | size="small" 23 | variant="ghost" 24 | > 25 | {didResend ? 'Sent' : 'Resend'} 26 | 27 | } 28 | description={'Please follow a link in your inbox to confirm it'} 29 | title={'Your e-mail needs confirmation.'} 30 | /> 31 | ) 32 | } 33 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/components/GitHubButton.tsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react' 2 | import { BASE_URL } from '~/config' 3 | import { Button } from '~/elements/Button' 4 | import { DialogLoadingOverlay } from '~/elements/Dialog' 5 | import { getRoutePath } from '~/lib/router' 6 | import { Github } from 'lucide-react' 7 | 8 | const GITHUB_URL = `${BASE_URL}/api/v1/auth/github` 9 | 10 | export function GithubButton() { 11 | const [loading, setLoading] = useState(false) 12 | 13 | return ( 14 | <> 15 | 25 | 26 | {loading && } 27 | 28 | ) 29 | } 30 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/constants.ts: -------------------------------------------------------------------------------- 1 | export enum ConfirmEmailErrorCodes { 2 | EmailTokenExpired = 400 3 | } 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/stores/settings.ts: -------------------------------------------------------------------------------- 1 | import { persistentMap } from '@nanostores/persistent' 2 | import { AvailableSdkLanguage } from '~/features/onboarding/types' 3 | import { createAsyncStore } from '~/lib/fetcher.ts' 4 | import { api } from '~/lib/api.ts' 5 | 6 | export type UserSettings = { 7 | showUnits: 'false' | 'true' 8 | sdkLanguage: AvailableSdkLanguage 9 | } 10 | 11 | export const $settings = persistentMap('settings:', { 12 | showUnits: 'true', 13 | sdkLanguage: 'typescript' 14 | }) 15 | 16 | export const $platformSettings = createAsyncStore({ 17 | key: '$platformSettings', 18 | async fetcher(init) { 19 | return await api.settings.get({ init }) 20 | }, 21 | deps: [] 22 | }) 23 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/stores/token.ts: -------------------------------------------------------------------------------- 1 | import { persistentAtom } from '@nanostores/persistent' 2 | 3 | export const $token = persistentAtom('auth:token', undefined) 4 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/auth/types.ts: -------------------------------------------------------------------------------- 1 | import type { GenericApiResponse, ISO8601 } from '~/types' 2 | 3 | export type AuthorizedUser = { 4 | created: ISO8601 5 | login: string 6 | confirmed: boolean 7 | id: string 8 | settings: string 9 | token: string 10 | isEmail?: boolean 11 | firstName?: string 12 | lastName?: string 13 | currentScope?: { 14 | role: 'owner' | 'developer' 15 | } 16 | } 17 | 18 | export type GetUserResponse = GenericApiResponse & { settings: string }> 19 | export type InvitedGetUserResponse = GenericApiResponse< 20 | Omit & { settings: string } & { workspaceId?: string } 21 | > 22 | 23 | export type User = { isLoggedIn: boolean } & Partial 24 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/billing/components/CheckoutButton.tsx: -------------------------------------------------------------------------------- 1 | import type { ComponentPropsWithoutRef } from 'react' 2 | 3 | import { useStore } from '@nanostores/react' 4 | 5 | import type { PlanId } from '~/features/billing/types' 6 | 7 | import { Button } from '~/elements/Button' 8 | import { $checkout } from '~/features/billing/stores/checkout' 9 | import { $currentPeriod } from '~/features/billing/stores/plans' 10 | 11 | export const CheckoutButton = ({ 12 | children = 'Checkout', 13 | id, 14 | loading: loadingProp, 15 | ...props 16 | }: ComponentPropsWithoutRef & { 17 | id: PlanId 18 | }) => { 19 | const { mutate: checkout, loading: checkoutInProgress } = useStore($checkout) 20 | 21 | const period = useStore($currentPeriod) 22 | 23 | const loading = loadingProp || checkoutInProgress 24 | 25 | return ( 26 | 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/billing/constants.ts: -------------------------------------------------------------------------------- 1 | import type { FreePlan, PlanPeriod } from '~/features/billing/types' 2 | 3 | export const FREE_PLAN: FreePlan = { 4 | id: 'free', 5 | name: 'Free' 6 | } 7 | 8 | export const PLAN_PERIODS: Array<{ name: string; value: PlanPeriod }> = [ 9 | { value: 'month', name: 'monthly' }, 10 | { value: 'annual', name: 'annual' } 11 | ] 12 | 13 | export enum BillingErrorCodes { 14 | PaymentRequired = 402 15 | } 16 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/billing/stores/checkout.ts: -------------------------------------------------------------------------------- 1 | import { loadStripe } from '@stripe/stripe-js' 2 | 3 | import type { ApiParams } from '~/lib/api' 4 | 5 | import { $currentWorkspace } from '~/features/workspaces/stores/current-workspace' 6 | import { api } from '~/lib/api' 7 | import { createMutator } from '~/lib/fetcher' 8 | import { $platformSettings } from '~/features/auth/stores/settings.ts' 9 | 10 | export const $checkout = createMutator({ 11 | async fetcher(body: ApiParams) { 12 | if (!$platformSettings.get()?.data?.selfHosted) { 13 | const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLIC_KEY) 14 | 15 | const stripe = await stripePromise 16 | 17 | if (!stripe) { 18 | // TODO: change error message 19 | throw new Error('Stripe failed to initialize') 20 | } 21 | 22 | const session = await api.billing.createSession(body) 23 | 24 | const { error } = await stripe.redirectToCheckout({ 25 | sessionId: session.id 26 | }) 27 | 28 | if (error) { 29 | throw error 30 | } 31 | } 32 | }, 33 | invalidates: [$currentWorkspace] 34 | }) 35 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/billing/types.ts: -------------------------------------------------------------------------------- 1 | export type PaidStartPlanId = 'start' 2 | export type PaidPlanId = 'pro' 3 | export type FreePlanId = 'free' 4 | export type PlanId = FreePlanId | PaidStartPlanId | PaidPlanId 5 | 6 | export type PlanPeriod = 'annual' | 'month' 7 | 8 | type IncomingPlanData = { 9 | amount: number 10 | priceId: string 11 | productId: string 12 | } 13 | 14 | export type IncomingBillingData = Record> 15 | 16 | export type PaidPlan = { 17 | id: PaidPlanId | PaidStartPlanId 18 | name: string 19 | monthlyPrice: number 20 | yearlyPrice: number 21 | } 22 | 23 | export type FreePlan = { 24 | id: FreePlanId 25 | name: string 26 | } 27 | 28 | export type Plan = FreePlan | PaidPlan 29 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/billing/utils.ts: -------------------------------------------------------------------------------- 1 | import type { FreePlan, Plan } from '~/features/billing/types' 2 | 3 | export const isFreePlan = (plan: Pick): plan is FreePlan => plan.id === 'free' 4 | 5 | export async function sleep(delayMs: number) { 6 | return new Promise((resolve) => setTimeout(resolve, delayMs)) 7 | } 8 | -------------------------------------------------------------------------------- /platform/dashboard/src/features/labels/components/LabelColorIcon.tsx: -------------------------------------------------------------------------------- 1 | import { getLabelColor } from '../utils' 2 | import { filterLabel } from './FilterLabel' 3 | 4 | export function LabelColorIcon({ label, idx = 0 }: { idx?: number; label: string }) { 5 | return ( 6 |