├── .github ├── ISSUE_TEMPLATE │ ├── anything-else.md │ ├── bug_report.md │ └── feature_request.md ├── dependabot.yml └── workflows │ └── claude.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── NOTICE ├── README.md ├── SECURITY.md ├── core ├── .gitignore ├── Dockerfile ├── go.mod ├── go.sum ├── gqlgen.yml ├── graph │ ├── generated.go │ ├── http.resolvers.go │ ├── model │ │ └── models_gen.go │ ├── resolver.go │ ├── schema.graphqls │ └── schema.resolvers.go ├── server.go ├── src │ ├── auth │ │ ├── auth.go │ │ ├── login.go │ │ └── logout.go │ ├── common │ │ ├── chat.go │ │ ├── context.go │ │ └── utils.go │ ├── engine │ │ ├── engine.go │ │ └── plugin.go │ ├── env │ │ └── env.go │ ├── llm │ │ ├── anthropic_client.go │ │ ├── chatgpt_client.go │ │ ├── http_client.go │ │ ├── llm_client.go │ │ ├── llm_instance.go │ │ └── ollama_client.go │ ├── log │ │ └── log.go │ ├── plugins │ │ ├── clickhouse │ │ │ ├── add.go │ │ │ ├── clickhouse.go │ │ │ ├── db.go │ │ │ ├── graph.go │ │ │ └── utils.go │ │ ├── common.go │ │ ├── elasticsearch │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── delete.go │ │ │ ├── elasticsearch.go │ │ │ ├── graph.go │ │ │ └── update.go │ │ ├── gorm │ │ │ ├── add.go │ │ │ ├── chat.go │ │ │ ├── db.go │ │ │ ├── delete.go │ │ │ ├── graph.go │ │ │ ├── plugin.go │ │ │ ├── update.go │ │ │ └── utils.go │ │ ├── mongodb │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── delete.go │ │ │ ├── graph.go │ │ │ ├── mongodb.go │ │ │ └── update.go │ │ ├── mysql │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── graph.go │ │ │ ├── mysql.go │ │ │ └── utils.go │ │ ├── postgres │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── graph.go │ │ │ ├── postgres.go │ │ │ └── utils.go │ │ ├── redis │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── delete.go │ │ │ ├── redis.go │ │ │ └── update.go │ │ └── sqlite3 │ │ │ ├── add.go │ │ │ ├── db.go │ │ │ ├── graph.go │ │ │ ├── sqlite3.go │ │ │ └── utils.go │ ├── router │ │ ├── file_server.go │ │ ├── middleware.go │ │ ├── playground.go │ │ └── router.go │ ├── settings │ │ └── settings.go │ └── src.go └── tools │ └── tools.go ├── dev ├── cleanup.sh ├── docker-compose.e2e.yaml ├── docker-compose.yml └── sample-data │ ├── clickhouse │ └── data.sql │ ├── elasticsearch │ └── upload.sh │ ├── mariadb │ └── data.sql │ ├── mongo │ └── data.js │ ├── mysql │ └── data.sql │ ├── postgres │ └── data.sql │ └── sqlite3 │ └── data.sql ├── docs ├── docs.md └── images │ ├── add-row.png │ ├── add-storage-unit.png │ ├── album_table_view.png │ ├── change-login-profiles.png │ ├── chat-example-1.png │ ├── chat-example-2.png │ ├── chat-example-3.png │ ├── chat-example-4.png │ ├── chat.png │ ├── database-types.png │ ├── demo-thumbnail.png │ ├── edit_view.png │ ├── export-data.png │ ├── export_delete_btns.png │ ├── global-theme.png │ ├── graph-node.png │ ├── graph-tools.png │ ├── graph.png │ ├── login-profiles.png │ ├── login.png │ ├── logo.png │ ├── schema-change.png │ ├── scratchpad.png │ ├── scratchpad2.png │ ├── search-all.png │ ├── table-cell-inline-edit-action.png │ ├── table-cell-inline-edit-input.png │ ├── table-cell-preview.png │ ├── table-data.png │ ├── table-details.png │ ├── table-search-configuration.png │ ├── table-smart-filter.png │ ├── table_view.png │ ├── tables.png │ └── trailer.png ├── frontend ├── .gitignore ├── .postcssrc.json ├── codegen.yml ├── cypress.config.js ├── cypress │ ├── e2e │ │ ├── clickhouse.cy.js │ │ ├── mariadb.cy.js │ │ ├── mongo.cy.js │ │ ├── mysql.cy.js │ │ ├── postgres.cy.js │ │ └── sqlite.cy.js │ └── support │ │ ├── commands.js │ │ └── e2e.js ├── package.json ├── pnpm-lock.yaml ├── public │ ├── images │ │ └── logo.png │ ├── index.html │ ├── manifest.json │ └── robots.txt ├── src │ ├── app.tsx │ ├── components │ │ ├── breadcrumbs.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ ├── classes.ts │ │ ├── common.tsx │ │ ├── dropdown.tsx │ │ ├── editor.tsx │ │ ├── graph │ │ │ ├── constants.ts │ │ │ ├── edge.tsx │ │ │ ├── graph.tsx │ │ │ ├── layouts.ts │ │ │ └── utils.ts │ │ ├── hooks.tsx │ │ ├── icons.tsx │ │ ├── input.tsx │ │ ├── loading.tsx │ │ ├── notifications.tsx │ │ ├── page.tsx │ │ ├── profile-info-tooltip.tsx │ │ ├── search.tsx │ │ ├── sidebar │ │ │ ├── get-login-profiles.graphql │ │ │ ├── get-schema.graphql │ │ │ ├── get-version.graphql │ │ │ └── sidebar.tsx │ │ └── table.tsx │ ├── config │ │ ├── graphql-client.ts │ │ ├── posthog.tsx │ │ └── routes.tsx │ ├── generated │ │ └── graphql.tsx │ ├── index.css │ ├── index.tsx │ ├── pages │ │ ├── auth │ │ │ ├── database.graphql │ │ │ ├── login-with-profile.mutation.graphql │ │ │ ├── login.mutation.graphql │ │ │ ├── login.tsx │ │ │ ├── logout.mutation.graphql │ │ │ └── logout.tsx │ │ ├── chat │ │ │ ├── chat.tsx │ │ │ ├── default-chat-route.tsx │ │ │ ├── examples.ts │ │ │ ├── get-ai-providers.graphql │ │ │ ├── get-chat.graphql │ │ │ └── get-models.graphql │ │ ├── contact-us │ │ │ └── contact-us.tsx │ │ ├── graph │ │ │ ├── graph.graphql │ │ │ └── graph.tsx │ │ ├── raw-execute │ │ │ ├── raw-execute.graphql │ │ │ └── raw-execute.tsx │ │ ├── settings │ │ │ ├── settings.graphql │ │ │ ├── settings.tsx │ │ │ └── update-settings.graphql │ │ └── storage-unit │ │ │ ├── add-storage-unit-row.graphql │ │ │ ├── add-storage-unit.graphql │ │ │ ├── delete-storage-unit-row.graphql │ │ │ ├── explore-storage-unit-where-condition.tsx │ │ │ ├── explore-storage-unit.tsx │ │ │ ├── get-storage-unit-rows.graphql │ │ │ ├── storage-unit.tsx │ │ │ ├── storage-units.graphql │ │ │ └── update-storage-unit.graphql │ ├── react-app-env.d.ts │ ├── store │ │ ├── auth.ts │ │ ├── chat.ts │ │ ├── common.ts │ │ ├── database.ts │ │ ├── function.ts │ │ ├── global.ts │ │ ├── hooks.ts │ │ ├── index.ts │ │ └── settings.ts │ └── utils │ │ └── functions.ts └── tsconfig.json ├── go.work └── go.work.sum /.github/ISSUE_TEMPLATE/anything-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Anything else 3 | about: Use this for anything that doesn't fall under the categories above. 4 | title: "[QUESTION/MISC] - {your title here}" 5 | labels: question 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve WhoDB. 4 | title: "[BUG] - {your title here}" 5 | labels: bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS that WhoDB is running on: [e.g. Ubuntu] 28 | - How you're running WhoDB: [e.g, Docker, exe, manually] 29 | - Browser: [e.g. chrome, safari] 30 | - Version: [e.g. 22] 31 | 32 | **Smartphone (please complete the following information):** 33 | - Device: [e.g. iPhone6] 34 | - OS: [e.g. iOS8.1] 35 | - Browser: [e.g. stock browser, safari] 36 | - Version: [e.g. 22] 37 | 38 | **Additional context** 39 | Add any other context about the problem here. 40 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project. 4 | title: "[FR] - {your title here}" 5 | labels: enhancement 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "gomod" # See documentation for possible values 9 | directory: "/core" # Location of package manifests 10 | schedule: 11 | interval: "daily" 12 | - package-ecosystem: "npm" 13 | directory: "/frontend" 14 | schedule: 15 | interval: "daily" 16 | -------------------------------------------------------------------------------- /.github/workflows/claude.yml: -------------------------------------------------------------------------------- 1 | name: Claude Code 2 | 3 | on: 4 | issue_comment: 5 | types: [created] 6 | pull_request_review_comment: 7 | types: [created] 8 | issues: 9 | types: [opened, assigned] 10 | pull_request_review: 11 | types: [submitted] 12 | 13 | jobs: 14 | claude: 15 | if: | 16 | (github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude')) || 17 | (github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude')) || 18 | (github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude')) || 19 | (github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude'))) 20 | runs-on: ubuntu-latest 21 | permissions: 22 | contents: read 23 | pull-requests: read 24 | issues: read 25 | id-token: write 26 | steps: 27 | - name: Checkout repository 28 | uses: actions/checkout@v4 29 | with: 30 | fetch-depth: 1 31 | 32 | - name: Run Claude Code 33 | id: claude 34 | uses: anthropics/claude-code-action@beta 35 | with: 36 | anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }} 37 | 38 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea 2 | /.vscode 3 | /dev/elasticsearch 4 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | ## Supported Versions 4 | 5 | Currently the latest version of WhoDB is supported. We advise you to upgrade whenever possible to receive the latest security updates. 6 | 7 | ## Reporting a Vulnerability 8 | 9 | Please use the repo's Report A Vulnerability option to report. 10 | 11 | The team will send a response indicating the next steps in handling your report. After the initial reply to your report, the security team will keep you informed of the progress towards a fix, and may ask for additional information or guidance. 12 | -------------------------------------------------------------------------------- /core/.gitignore: -------------------------------------------------------------------------------- 1 | # If you prefer the allow list template instead of the deny list, see community template: 2 | # https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore 3 | # 4 | # Binaries for programs and plugins 5 | *.exe 6 | *.exe~ 7 | *.dll 8 | *.so 9 | *.dylib 10 | 11 | # Test binary, built with `go test -c` 12 | *.test 13 | 14 | # Output of the go coverage tool, specifically when used with LiteIDE 15 | *.out 16 | 17 | # Dependency directories (remove the comment below to include it) 18 | # vendor/ 19 | 20 | # Go workspace file 21 | go.work 22 | 23 | build/ 24 | .env 25 | *.pb.go 26 | 27 | gorm.db 28 | 29 | tmp/ -------------------------------------------------------------------------------- /core/Dockerfile: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Clidey, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | FROM node:18.12.1-alpine AS build-stage 16 | RUN npm i -g pnpm 17 | WORKDIR /app 18 | COPY ./frontend/package.json ./frontend/pnpm-lock.yaml ./ 19 | RUN pnpm install 20 | COPY ./frontend/ ./ 21 | RUN pnpm run build 22 | 23 | FROM golang:1.22.5-alpine3.19 AS backend-stage 24 | RUN apk update && apk add --no-cache gcc musl-dev 25 | WORKDIR /app 26 | COPY ./core/go.mod ./core/go.sum ./ 27 | RUN go mod download 28 | COPY ./core/ ./ 29 | COPY --from=build-stage /app/build/ ./build/ 30 | RUN CGO_ENABLED=1 GOOS=linux go build -o /core 31 | 32 | FROM alpine:3.19 33 | RUN apk update && apk add --no-cache ca-certificates 34 | WORKDIR /app 35 | COPY --from=backend-stage /core /core 36 | 37 | CMD ["/core"] -------------------------------------------------------------------------------- /core/go.mod: -------------------------------------------------------------------------------- 1 | module github.com/clidey/whodb/core 2 | 3 | go 1.24.1 4 | 5 | require ( 6 | github.com/99designs/gqlgen v0.17.71 7 | github.com/ClickHouse/clickhouse-go/v2 v2.34.0 8 | github.com/deckarep/golang-set/v2 v2.8.0 9 | github.com/elastic/go-elasticsearch/v8 v8.18.0 10 | github.com/go-chi/chi/v5 v5.2.1 11 | github.com/go-chi/cors v1.2.1 12 | github.com/go-redis/redis/v8 v8.11.5 13 | github.com/go-sql-driver/mysql v1.9.2 14 | github.com/google/uuid v1.6.0 15 | github.com/pkg/errors v0.9.1 16 | github.com/sirupsen/logrus v1.9.3 17 | github.com/vektah/gqlparser/v2 v2.5.25 18 | go.mongodb.org/mongo-driver v1.17.3 19 | gorm.io/driver/clickhouse v0.6.1 20 | gorm.io/driver/mysql v1.5.7 21 | gorm.io/driver/postgres v1.5.11 22 | gorm.io/driver/sqlite v1.5.7 23 | gorm.io/gorm v1.25.12 24 | ) 25 | 26 | require ( 27 | filippo.io/edwards25519 v1.1.0 // indirect 28 | github.com/ClickHouse/ch-go v0.65.1 // indirect 29 | github.com/agnivade/levenshtein v1.2.1 // indirect 30 | github.com/andybalholm/brotli v1.1.1 // indirect 31 | github.com/cespare/xxhash/v2 v2.3.0 // indirect 32 | github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect 33 | github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect 34 | github.com/elastic/elastic-transport-go/v8 v8.7.0 // indirect 35 | github.com/fsnotify/fsnotify v1.7.0 // indirect 36 | github.com/go-faster/city v1.0.1 // indirect 37 | github.com/go-faster/errors v0.7.1 // indirect 38 | github.com/go-logr/logr v1.4.2 // indirect 39 | github.com/go-logr/stdr v1.2.2 // indirect 40 | github.com/go-viper/mapstructure/v2 v2.2.1 // indirect 41 | github.com/golang/snappy v0.0.4 // indirect 42 | github.com/gorilla/websocket v1.5.0 // indirect 43 | github.com/hashicorp/go-version v1.7.0 // indirect 44 | github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect 45 | github.com/jackc/pgpassfile v1.0.0 // indirect 46 | github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect 47 | github.com/jackc/pgx/v5 v5.5.5 // indirect 48 | github.com/jackc/puddle/v2 v2.2.2 // indirect 49 | github.com/jinzhu/inflection v1.0.0 // indirect 50 | github.com/jinzhu/now v1.1.5 // indirect 51 | github.com/klauspost/compress v1.17.11 // indirect 52 | github.com/mattn/go-sqlite3 v1.14.22 // indirect 53 | github.com/montanaflynn/stats v0.7.1 // indirect 54 | github.com/paulmach/orb v0.11.1 // indirect 55 | github.com/pierrec/lz4/v4 v4.1.22 // indirect 56 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 57 | github.com/segmentio/asm v1.2.0 // indirect 58 | github.com/shopspring/decimal v1.4.0 // indirect 59 | github.com/sosodev/duration v1.3.1 // indirect 60 | github.com/urfave/cli/v2 v2.27.6 // indirect 61 | github.com/xdg-go/pbkdf2 v1.0.0 // indirect 62 | github.com/xdg-go/scram v1.1.2 // indirect 63 | github.com/xdg-go/stringprep v1.0.4 // indirect 64 | github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect 65 | github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect 66 | go.opentelemetry.io/auto/sdk v1.1.0 // indirect 67 | go.opentelemetry.io/otel v1.35.0 // indirect 68 | go.opentelemetry.io/otel/metric v1.35.0 // indirect 69 | go.opentelemetry.io/otel/trace v1.35.0 // indirect 70 | golang.org/x/crypto v0.36.0 // indirect 71 | golang.org/x/mod v0.24.0 // indirect 72 | golang.org/x/sync v0.13.0 // indirect 73 | golang.org/x/sys v0.32.0 // indirect 74 | golang.org/x/text v0.24.0 // indirect 75 | golang.org/x/tools v0.32.0 // indirect 76 | gopkg.in/yaml.v3 v3.0.1 // indirect 77 | ) 78 | -------------------------------------------------------------------------------- /core/graph/resolver.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package graph 16 | 17 | // This file will not be regenerated automatically. 18 | // 19 | // It serves as dependency injection for your app, add any dependencies you require here. 20 | 21 | //go:generate go run github.com/99designs/gqlgen generate 22 | 23 | type Resolver struct{} 24 | -------------------------------------------------------------------------------- /core/graph/schema.graphqls: -------------------------------------------------------------------------------- 1 | enum DatabaseType { 2 | Postgres, 3 | MySQL, 4 | Sqlite3, 5 | MongoDB, 6 | Redis, 7 | ElasticSearch, 8 | MariaDB, 9 | ClickHouse, 10 | } 11 | 12 | type Column { 13 | Type: String! 14 | Name: String! 15 | } 16 | 17 | enum WhereConditionType { 18 | Atomic 19 | And 20 | Or 21 | } 22 | 23 | input AtomicWhereCondition { 24 | ColumnType: String! 25 | Key: String! 26 | Operator: String! 27 | Value: String! 28 | } 29 | 30 | input OperationWhereCondition { 31 | Children: [WhereCondition!]! 32 | } 33 | 34 | input WhereCondition { 35 | Type: WhereConditionType! 36 | Atomic: AtomicWhereCondition 37 | And: OperationWhereCondition 38 | Or: OperationWhereCondition 39 | } 40 | 41 | type RowsResult { 42 | Columns: [Column!]! 43 | Rows: [[String!]!]! 44 | DisableUpdate: Boolean! 45 | } 46 | 47 | type Record { 48 | Key: String! 49 | Value: String! 50 | } 51 | 52 | input RecordInput { 53 | Key: String! 54 | Value: String! 55 | Extra: [RecordInput!] 56 | } 57 | 58 | type StorageUnit { 59 | Name: String! 60 | Attributes: [Record!]! 61 | } 62 | 63 | enum GraphUnitRelationshipType { 64 | OneToOne, 65 | OneToMany, 66 | ManyToOne, 67 | ManyToMany, 68 | Unknown, 69 | } 70 | 71 | type GraphUnitRelationship { 72 | Name: String! 73 | Relationship: GraphUnitRelationshipType! 74 | } 75 | 76 | type GraphUnit { 77 | Unit: StorageUnit! 78 | Relations: [GraphUnitRelationship!]! 79 | } 80 | 81 | input LoginCredentials { 82 | Id: String 83 | Type: String! 84 | Hostname: String! 85 | Username: String! 86 | Password: String! 87 | Database: String! 88 | Advanced: [RecordInput!] 89 | } 90 | 91 | type SettingsConfig { 92 | MetricsEnabled: Boolean 93 | } 94 | 95 | input SettingsConfigInput { 96 | MetricsEnabled: String 97 | } 98 | 99 | input LoginProfileInput { 100 | Id: String! 101 | Type: DatabaseType! 102 | Database: String 103 | } 104 | 105 | type LoginProfile { 106 | Alias: String 107 | Id: String! 108 | Type: DatabaseType! 109 | Database: String 110 | } 111 | 112 | type StatusResponse { 113 | Status: Boolean! 114 | } 115 | 116 | input ChatInput { 117 | PreviousConversation: String! 118 | Query: String! 119 | Model: String! 120 | Token: String 121 | } 122 | 123 | type AIChatMessage { 124 | Type: String! 125 | Result: RowsResult 126 | Text: String! 127 | } 128 | 129 | type AIProvider { 130 | Type: String! 131 | ProviderId: String! 132 | } 133 | 134 | type Query { 135 | Version: String! 136 | Profiles: [LoginProfile!]! 137 | Database(type: String!): [String!]! 138 | Schema: [String!]! 139 | StorageUnit(schema: String!): [StorageUnit!]! 140 | Row(schema: String!, storageUnit: String!, where: WhereCondition, pageSize: Int!, pageOffset: Int!): RowsResult! 141 | RawExecute(query: String!): RowsResult! 142 | Graph(schema: String!): [GraphUnit!]! 143 | AIProviders: [AIProvider!]! 144 | AIModel(providerId: String, modelType: String!, token: String): [String!]! 145 | AIChat(providerId: String, modelType: String!, token: String, schema: String!, input: ChatInput!): [AIChatMessage!]! 146 | SettingsConfig: SettingsConfig! 147 | } 148 | 149 | type Mutation { 150 | Login(credentials: LoginCredentials!): StatusResponse! 151 | LoginWithProfile(profile: LoginProfileInput!): StatusResponse! 152 | Logout: StatusResponse! 153 | UpdateSettings(newSettings: SettingsConfigInput!): StatusResponse! 154 | 155 | AddStorageUnit(schema: String!, storageUnit: String!, fields: [RecordInput!]!): StatusResponse! 156 | UpdateStorageUnit(schema: String!, storageUnit: String!, values: [RecordInput!]!, updatedColumns: [String!]!): StatusResponse! 157 | AddRow(schema: String!, storageUnit: String!, values: [RecordInput!]!): StatusResponse! 158 | DeleteRow(schema: String!, storageUnit: String!, values: [RecordInput!]!): StatusResponse! 159 | } 160 | -------------------------------------------------------------------------------- /core/server.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package main 18 | 19 | import ( 20 | "context" 21 | "embed" 22 | "fmt" 23 | "github.com/clidey/whodb/core/src" 24 | "github.com/clidey/whodb/core/src/common" 25 | "github.com/clidey/whodb/core/src/env" 26 | "github.com/clidey/whodb/core/src/log" 27 | "github.com/clidey/whodb/core/src/router" 28 | "github.com/clidey/whodb/core/src/settings" 29 | "github.com/pkg/errors" 30 | "net/http" 31 | "os" 32 | "os/signal" 33 | "syscall" 34 | "time" 35 | ) 36 | 37 | //go:embed build/* 38 | var staticFiles embed.FS 39 | 40 | const defaultPort = "8080" 41 | 42 | func main() { 43 | log.Logger.Info("Starting WhoDB...") 44 | 45 | settingsCfg := settings.Get() 46 | if settingsCfg.MetricsEnabled { 47 | } 48 | src.InitializeEngine() 49 | r := router.InitializeRouter(staticFiles) 50 | 51 | port := os.Getenv("PORT") 52 | if port == "" { 53 | port = defaultPort 54 | } 55 | 56 | srv := &http.Server{ 57 | Addr: fmt.Sprintf(":%s", port), 58 | Handler: r, 59 | ReadHeaderTimeout: 5 * time.Second, 60 | ReadTimeout: 10 * time.Second, 61 | WriteTimeout: 1 * time.Minute, 62 | IdleTimeout: 30 * time.Second, 63 | } 64 | 65 | serverStarted := make(chan bool, 1) 66 | 67 | go func() { 68 | log.Logger.Info("Almost there...") 69 | if err := srv.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) { 70 | log.Logger.Fatalf("listen: %s\n", err) 71 | serverStarted <- false 72 | } 73 | }() 74 | 75 | select { 76 | case success := <-serverStarted: 77 | if !success { 78 | log.Logger.Println("Server failed to start. Exiting...") 79 | os.Exit(1) 80 | } 81 | case <-time.After(2 * time.Second): 82 | log.Logger.Infof("🎉 Welcome to WhoDB! 🎉") 83 | log.Logger.Infof("Get started by visiting:") 84 | log.Logger.Infof("http://0.0.0.0:%s", port) 85 | log.Logger.Info("Explore and enjoy working with your databases!") 86 | if !env.IsAPIGatewayEnabled && !common.IsRunningInsideDocker() { 87 | //common.OpenBrowser(fmt.Sprintf("http://localhost:%v", port)) 88 | } 89 | } 90 | 91 | quit := make(chan os.Signal, 1) 92 | signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) 93 | <-quit 94 | log.Logger.Println("Shutting down server, 30 second timeout started...") 95 | 96 | // Create a deadline to wait for 97 | ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 98 | defer cancel() 99 | 100 | // Doesn't block if no connections, but will otherwise wait 101 | // until the timeout deadline. 102 | if err := srv.Shutdown(ctx); err != nil { 103 | log.Logger.Fatalf("Server forced to shutdown: %v, resources might be left hanging", err) 104 | } 105 | 106 | close(serverStarted) 107 | close(quit) 108 | 109 | log.Logger.Println("Server exiting") 110 | } 111 | -------------------------------------------------------------------------------- /core/src/auth/login.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "context" 19 | "encoding/base64" 20 | "encoding/json" 21 | "net/http" 22 | "time" 23 | 24 | "github.com/clidey/whodb/core/graph/model" 25 | "github.com/clidey/whodb/core/src/common" 26 | ) 27 | 28 | func Login(ctx context.Context, input *model.LoginCredentials) (*model.StatusResponse, error) { 29 | loginInfoJSON, err := json.Marshal(input) 30 | if err != nil { 31 | return nil, err 32 | } 33 | 34 | cookieValue := base64.StdEncoding.EncodeToString(loginInfoJSON) 35 | 36 | cookie := &http.Cookie{ 37 | Name: string(AuthKey_Token), 38 | Value: cookieValue, 39 | Path: "/", 40 | HttpOnly: true, 41 | Expires: time.Now().Add(24 * time.Hour), 42 | } 43 | 44 | http.SetCookie(ctx.Value(common.RouterKey_ResponseWriter).(http.ResponseWriter), cookie) 45 | 46 | return &model.StatusResponse{ 47 | Status: true, 48 | }, nil 49 | } 50 | -------------------------------------------------------------------------------- /core/src/auth/logout.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package auth 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | 21 | "github.com/clidey/whodb/core/graph/model" 22 | "github.com/clidey/whodb/core/src/common" 23 | ) 24 | 25 | func Logout(ctx context.Context) (*model.StatusResponse, error) { 26 | http.SetCookie(ctx.Value(common.RouterKey_ResponseWriter).(http.ResponseWriter), nil) 27 | return &model.StatusResponse{ 28 | Status: true, 29 | }, nil 30 | } 31 | -------------------------------------------------------------------------------- /core/src/common/context.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | type RouterKey string 18 | 19 | const ( 20 | RouterKey_ResponseWriter RouterKey = "ResponseWriter" 21 | ) 22 | -------------------------------------------------------------------------------- /core/src/common/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package common 16 | 17 | import ( 18 | "fmt" 19 | "os" 20 | "os/exec" 21 | "runtime" 22 | "strings" 23 | 24 | "github.com/clidey/whodb/core/src/engine" 25 | "github.com/clidey/whodb/core/src/log" 26 | ) 27 | 28 | func ContainsString(slice []string, element string) bool { 29 | for _, item := range slice { 30 | if item == element { 31 | return true 32 | } 33 | } 34 | return false 35 | } 36 | 37 | func GetRecordValueOrDefault(records []engine.Record, key string, defaultValue string) string { 38 | for _, record := range records { 39 | if record.Key == key && len(record.Value) > 0 { 40 | return record.Value 41 | } 42 | } 43 | return defaultValue 44 | } 45 | 46 | func JoinWithQuotes(arr []string) string { 47 | quotedStrings := make([]string, len(arr)) 48 | 49 | for i, str := range arr { 50 | quotedStrings[i] = fmt.Sprintf("\"%s\"", str) 51 | } 52 | 53 | return strings.Join(quotedStrings, ", ") 54 | } 55 | 56 | func IsRunningInsideDocker() bool { 57 | _, err := os.Stat("/.dockerenv") 58 | return !os.IsNotExist(err) 59 | } 60 | 61 | func FilterList[T any](items []T, by func(input T) bool) []T { 62 | filteredItems := []T{} 63 | for _, item := range items { 64 | if by(item) { 65 | filteredItems = append(filteredItems, item) 66 | } 67 | } 68 | return filteredItems 69 | } 70 | 71 | func OpenBrowser(url string) { 72 | var err error 73 | switch runtime.GOOS { 74 | case "windows": 75 | err = exec.Command("rundll32", "url.dll,FileProtocolHandler", url).Start() 76 | case "darwin": 77 | err = exec.Command("open", url).Start() 78 | case "linux": 79 | err = exec.Command("xdg-open", url).Start() 80 | default: 81 | log.Logger.Warnf("Unsupported platform. Please open the URL manually: %s\n", url) 82 | } 83 | if err != nil { 84 | log.Logger.Warnf("Failed to open browser: %v\n", err) 85 | } 86 | } 87 | 88 | func StrPtrToBool(s *string) bool { 89 | if s == nil { 90 | return false 91 | } 92 | value := strings.ToLower(*s) 93 | return value == "true" 94 | } 95 | -------------------------------------------------------------------------------- /core/src/engine/engine.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package engine 16 | 17 | import "github.com/clidey/whodb/core/graph/model" 18 | 19 | type DatabaseType string 20 | 21 | const ( 22 | DatabaseType_Postgres = "Postgres" 23 | DatabaseType_MySQL = "MySQL" 24 | DatabaseType_MariaDB = "MariaDB" 25 | DatabaseType_Sqlite3 = "Sqlite3" 26 | DatabaseType_MongoDB = "MongoDB" 27 | DatabaseType_Redis = "Redis" 28 | DatabaseType_ElasticSearch = "ElasticSearch" 29 | DatabaseType_ClickHouse = "ClickHouse" 30 | ) 31 | 32 | type Engine struct { 33 | Plugins []*Plugin 34 | } 35 | 36 | func (e *Engine) RegistryPlugin(plugin *Plugin) { 37 | if e.Plugins == nil { 38 | e.Plugins = []*Plugin{} 39 | } 40 | e.Plugins = append(e.Plugins, plugin) 41 | } 42 | 43 | func (e *Engine) Choose(databaseType DatabaseType) *Plugin { 44 | for _, plugin := range e.Plugins { 45 | if plugin.Type == databaseType { 46 | return plugin 47 | } 48 | } 49 | return nil 50 | } 51 | 52 | func GetStorageUnitModel(unit StorageUnit) *model.StorageUnit { 53 | attributes := []*model.Record{} 54 | for _, attribute := range unit.Attributes { 55 | attributes = append(attributes, &model.Record{ 56 | Key: attribute.Key, 57 | Value: attribute.Value, 58 | }) 59 | } 60 | return &model.StorageUnit{ 61 | Name: unit.Name, 62 | Attributes: attributes, 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /core/src/engine/plugin.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package engine 18 | 19 | import "github.com/clidey/whodb/core/graph/model" 20 | 21 | type Credentials struct { 22 | Id *string 23 | Type string 24 | Hostname string 25 | Username string 26 | Password string 27 | Database string 28 | Advanced []Record 29 | AccessToken *string 30 | IsProfile bool 31 | } 32 | 33 | type ExternalModel struct { 34 | Type string 35 | Token string 36 | } 37 | 38 | type PluginConfig struct { 39 | Credentials *Credentials 40 | ExternalModel *ExternalModel 41 | } 42 | 43 | type Record struct { 44 | Key string 45 | Value string 46 | Extra map[string]string 47 | } 48 | 49 | type StorageUnit struct { 50 | Name string 51 | Attributes []Record 52 | } 53 | 54 | type Column struct { 55 | Type string 56 | Name string 57 | } 58 | 59 | type GetRowsResult struct { 60 | Columns []Column 61 | Rows [][]string 62 | DisableUpdate bool 63 | } 64 | 65 | type GraphUnitRelationshipType string 66 | 67 | const ( 68 | GraphUnitRelationshipType_OneToOne = "OneToOne" 69 | GraphUnitRelationshipType_OneToMany = "OneToMany" 70 | GraphUnitRelationshipType_ManyToOne = "ManyToOne" 71 | GraphUnitRelationshipType_ManyToMany = "ManyToMany" 72 | GraphUnitRelationshipType_Unknown = "Unknown" 73 | ) 74 | 75 | type GraphUnitRelationship struct { 76 | Name string 77 | RelationshipType GraphUnitRelationshipType 78 | } 79 | 80 | type GraphUnit struct { 81 | Unit StorageUnit 82 | Relations []GraphUnitRelationship 83 | } 84 | 85 | type ChatMessage struct { 86 | Type string 87 | Result *GetRowsResult 88 | Text string 89 | } 90 | 91 | type PluginFunctions interface { 92 | GetDatabases(config *PluginConfig) ([]string, error) 93 | IsAvailable(config *PluginConfig) bool 94 | GetAllSchemas(config *PluginConfig) ([]string, error) 95 | GetStorageUnits(config *PluginConfig, schema string) ([]StorageUnit, error) 96 | AddStorageUnit(config *PluginConfig, schema string, storageUnit string, fields []Record) (bool, error) 97 | UpdateStorageUnit(config *PluginConfig, schema string, storageUnit string, values map[string]string, updatedColumns []string) (bool, error) 98 | AddRow(config *PluginConfig, schema string, storageUnit string, values []Record) (bool, error) 99 | DeleteRow(config *PluginConfig, schema string, storageUnit string, values map[string]string) (bool, error) 100 | GetRows(config *PluginConfig, schema string, storageUnit string, where *model.WhereCondition, pageSize int, pageOffset int) (*GetRowsResult, error) 101 | GetGraph(config *PluginConfig, schema string) ([]GraphUnit, error) 102 | RawExecute(config *PluginConfig, query string) (*GetRowsResult, error) 103 | Chat(config *PluginConfig, schema string, model string, previousConversation string, query string) ([]*ChatMessage, error) 104 | } 105 | 106 | type Plugin struct { 107 | PluginFunctions 108 | Type DatabaseType 109 | } 110 | 111 | func NewPluginConfig(credentials *Credentials) *PluginConfig { 112 | return &PluginConfig{ 113 | Credentials: credentials, 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /core/src/llm/anthropic_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package llm 18 | 19 | import ( 20 | "bufio" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "strings" 25 | 26 | "github.com/clidey/whodb/core/src/env" 27 | ) 28 | 29 | func prepareAnthropicRequest(c *LLMClient, prompt string, model LLMModel) (string, []byte, map[string]string, error) { 30 | maxTokens := 64000 // this is for claude-3-7-sonnet-20250219 31 | modelName := string(model) 32 | 33 | switch modelName { 34 | case "claude-3-5-sonnet-20241022", "claude-3-5-sonnet-20240620", "claude-3-5-haiku-20241022": 35 | maxTokens = 8192 36 | case "claude-3-opus-20240229", "claude-3-haiku-20240307": 37 | maxTokens = 4096 38 | } 39 | 40 | requestBody, err := json.Marshal(map[string]interface{}{ 41 | "model": modelName, 42 | "max_tokens": maxTokens, 43 | "messages": []map[string]string{ 44 | {"role": "user", "content": prompt}, 45 | }, 46 | }) 47 | if err != nil { 48 | return "", nil, nil, err 49 | } 50 | 51 | url := fmt.Sprintf("%v/messages", env.GetAnthropicEndpoint()) 52 | 53 | headers := map[string]string{ 54 | "x-api-key": c.APIKey, 55 | "anthropic-version": "2023-06-01", 56 | "content-type": "application/json", 57 | } 58 | 59 | return url, requestBody, headers, nil 60 | } 61 | 62 | func getAnthropicModels(_ string) ([]string, error) { 63 | models := []string{ 64 | "claude-3-7-sonnet-20250219", 65 | "claude-3-5-sonnet-20241022", 66 | "claude-3-5-sonnet-20240620", 67 | "claude-3-5-haiku-20241022", 68 | "claude-3-haiku-20240307", 69 | "claude-3-opus-20240229", 70 | } 71 | return models, nil 72 | } 73 | 74 | func parseAnthropicResponse(body io.ReadCloser, receiverChan *chan string, responseBuilder *strings.Builder) (*string, error) { 75 | defer body.Close() 76 | reader := bufio.NewReader(body) 77 | 78 | for { 79 | line, err := reader.ReadString('\n') 80 | if err != nil { 81 | if err != io.EOF { 82 | return nil, err 83 | } 84 | } 85 | 86 | var anthropicResponse struct { 87 | Content []struct { 88 | Text string `json:"text"` 89 | Type string `json:"type"` 90 | } `json:"content"` 91 | StopReason string `json:"stop_reason"` 92 | Usage struct { 93 | InputTokens int `json:"input_tokens"` 94 | OutputTokens int `json:"output_tokens"` 95 | } `json:"usage"` 96 | Role string `json:"role"` 97 | Model string `json:"model"` 98 | ID string `json:"id"` 99 | Type string `json:"type"` 100 | StopSequence *string `json:"stop_sequence,omitempty"` 101 | } 102 | 103 | if err := json.Unmarshal([]byte(line), &anthropicResponse); err != nil { 104 | return nil, err 105 | } 106 | 107 | for _, content := range anthropicResponse.Content { 108 | if receiverChan != nil { 109 | *receiverChan <- content.Text 110 | } 111 | if _, err := responseBuilder.WriteString(content.Text); err != nil { 112 | return nil, err 113 | } 114 | } 115 | 116 | if anthropicResponse.StopReason == "end_turn" { 117 | response := responseBuilder.String() 118 | return &response, nil 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /core/src/llm/http_client.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llm 16 | 17 | import ( 18 | "bytes" 19 | "net/http" 20 | ) 21 | 22 | func sendHTTPRequest(method, url string, body []byte, headers map[string]string) (*http.Response, error) { 23 | req, err := http.NewRequest(method, url, bytes.NewBuffer(body)) 24 | if err != nil { 25 | return nil, err 26 | } 27 | 28 | for key, value := range headers { 29 | req.Header.Set(key, value) 30 | } 31 | 32 | client := &http.Client{} 33 | return client.Do(req) 34 | } 35 | -------------------------------------------------------------------------------- /core/src/llm/llm_instance.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package llm 16 | 17 | import ( 18 | "github.com/clidey/whodb/core/src/engine" 19 | ) 20 | 21 | var llmInstance map[LLMType]*LLMClient 22 | 23 | func Instance(config *engine.PluginConfig) *LLMClient { 24 | if llmInstance == nil { 25 | llmInstance = make(map[LLMType]*LLMClient) 26 | } 27 | 28 | llmType := LLMType(config.ExternalModel.Type) 29 | 30 | if _, ok := llmInstance[llmType]; ok { 31 | return llmInstance[llmType] 32 | } 33 | instance := &LLMClient{ 34 | Type: llmType, 35 | APIKey: config.ExternalModel.Token, 36 | } 37 | llmInstance[llmType] = instance 38 | return instance 39 | } 40 | -------------------------------------------------------------------------------- /core/src/llm/ollama_client.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package llm 18 | 19 | import ( 20 | "bufio" 21 | "encoding/json" 22 | "fmt" 23 | "io" 24 | "strings" 25 | 26 | "github.com/clidey/whodb/core/src/env" 27 | ) 28 | 29 | func prepareOllamaRequest(prompt string, model LLMModel) (string, []byte, map[string]string, error) { 30 | requestBody, err := json.Marshal(map[string]interface{}{ 31 | "model": string(model), 32 | "prompt": prompt, 33 | }) 34 | if err != nil { 35 | return "", nil, nil, err 36 | } 37 | url := fmt.Sprintf("%v/generate", env.GetOllamaEndpoint()) 38 | return url, requestBody, nil, nil 39 | } 40 | 41 | func prepareOllamaModelsRequest() (string, map[string]string) { 42 | url := fmt.Sprintf("%v/tags", env.GetOllamaEndpoint()) 43 | return url, nil 44 | } 45 | 46 | func parseOllamaResponse(body io.ReadCloser, receiverChan *chan string, responseBuilder *strings.Builder) (*string, error) { 47 | defer body.Close() 48 | reader := bufio.NewReader(body) 49 | 50 | for { 51 | line, err := reader.ReadString('\n') 52 | if err != nil { 53 | if err == io.EOF { 54 | break 55 | } 56 | return nil, err 57 | } 58 | 59 | var completionResponse struct { 60 | Response string `json:"response"` 61 | Done bool `json:"done"` 62 | } 63 | if err := json.Unmarshal([]byte(line), &completionResponse); err != nil { 64 | return nil, err 65 | } 66 | 67 | if receiverChan != nil { 68 | *receiverChan <- completionResponse.Response 69 | } 70 | 71 | if _, err := responseBuilder.WriteString(completionResponse.Response); err != nil { 72 | return nil, err 73 | } 74 | 75 | if completionResponse.Done { 76 | response := responseBuilder.String() 77 | return &response, nil 78 | } 79 | } 80 | 81 | return nil, nil 82 | } 83 | 84 | func parseOllamaModelsResponse(body io.ReadCloser) ([]string, error) { 85 | defer body.Close() 86 | 87 | var modelsResp struct { 88 | Models []struct { 89 | Name string `json:"model"` 90 | } `json:"models"` 91 | } 92 | if err := json.NewDecoder(body).Decode(&modelsResp); err != nil { 93 | return nil, err 94 | } 95 | 96 | models := []string{} 97 | for _, model := range modelsResp.Models { 98 | models = append(models, model.Name) 99 | } 100 | return models, nil 101 | } 102 | -------------------------------------------------------------------------------- /core/src/log/log.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package log 16 | 17 | import ( 18 | "github.com/sirupsen/logrus" 19 | ) 20 | 21 | type Fields logrus.Fields 22 | 23 | var Logger *logrus.Logger 24 | 25 | func init() { 26 | Logger = logrus.New() 27 | } 28 | 29 | func LogFields(fields Fields) *logrus.Entry { 30 | return Logger.WithFields(logrus.Fields(fields)) 31 | } 32 | -------------------------------------------------------------------------------- /core/src/plugins/clickhouse/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package clickhouse 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/engine" 22 | "strings" 23 | ) 24 | 25 | func (p *ClickHousePlugin) GetCreateTableQuery(schema string, storageUnit string, columns []engine.Record) string { 26 | var columnDefs []string 27 | var primaryKeys []string 28 | 29 | for _, column := range columns { 30 | parts := []string{column.Key, column.Value} 31 | 32 | if nullable, ok := column.Extra["nullable"]; ok && nullable == "false" { 33 | parts = append(parts, "NOT NULL") 34 | } 35 | 36 | columnDefs = append(columnDefs, strings.Join(parts, " ")) 37 | 38 | if primary, ok := column.Extra["primary"]; ok && primary == "true" { 39 | primaryKeys = append(primaryKeys, column.Key) 40 | } 41 | } 42 | 43 | // Determine ORDER BY clause 44 | orderByClause := "" 45 | if len(primaryKeys) > 0 { 46 | orderByClause = strings.Join(primaryKeys, ", ") 47 | } else if len(columnDefs) > 0 { 48 | firstColParts := strings.SplitN(columnDefs[0], " ", 2) 49 | if len(firstColParts) > 0 { 50 | orderByClause = firstColParts[0] 51 | } 52 | } 53 | 54 | return fmt.Sprintf(` 55 | CREATE TABLE %s.%s 56 | (%s) 57 | ENGINE = MergeTree() 58 | ORDER BY (%s)`, schema, storageUnit, strings.Join(columnDefs, ", "), orderByClause) 59 | } 60 | -------------------------------------------------------------------------------- /core/src/plugins/clickhouse/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import ( 18 | "context" 19 | "crypto/tls" 20 | "github.com/ClickHouse/clickhouse-go/v2" 21 | "gorm.io/gorm" 22 | "net" 23 | "strconv" 24 | "time" 25 | 26 | "github.com/clidey/whodb/core/src/engine" 27 | gorm_clickhouse "gorm.io/driver/clickhouse" 28 | ) 29 | 30 | func (p *ClickHousePlugin) DB(config *engine.PluginConfig) (*gorm.DB, error) { 31 | connectionInput, err := p.ParseConnectionConfig(config) 32 | if err != nil { 33 | return nil, err 34 | } 35 | 36 | auth := clickhouse.Auth{ 37 | Database: connectionInput.Database, 38 | Username: connectionInput.Username, 39 | Password: connectionInput.Password, 40 | } 41 | 42 | address := []string{net.JoinHostPort(connectionInput.Hostname, strconv.Itoa(connectionInput.Port))} 43 | options := &clickhouse.Options{ 44 | Addr: address, 45 | Auth: auth, 46 | DialTimeout: time.Second * 30, 47 | ConnOpenStrategy: clickhouse.ConnOpenInOrder, 48 | Compression: &clickhouse.Compression{ 49 | Method: clickhouse.CompressionLZ4, 50 | }, 51 | } 52 | 53 | if connectionInput.HTTPProtocol != "disable" { 54 | options.Protocol = clickhouse.HTTP 55 | options.Compression = &clickhouse.Compression{ 56 | Method: clickhouse.CompressionGZIP, 57 | } 58 | } 59 | 60 | if connectionInput.Debug != "disable" { 61 | options.Debug = true 62 | } 63 | if connectionInput.ReadOnly == "disable" { 64 | options.Settings = clickhouse.Settings{ 65 | "max_execution_time": 60, 66 | } 67 | } 68 | if connectionInput.SSLMode != "disable" { 69 | options.TLS = &tls.Config{InsecureSkipVerify: connectionInput.SSLMode == "relaxed" || connectionInput.SSLMode == "none"} 70 | } 71 | 72 | conn := clickhouse.OpenDB(options) 73 | 74 | conn.SetMaxOpenConns(5) 75 | conn.SetMaxOpenConns(5) 76 | conn.SetConnMaxLifetime(time.Hour) 77 | 78 | err = conn.PingContext(context.Background()) 79 | if err != nil { 80 | return nil, err 81 | } 82 | 83 | return gorm.Open(gorm_clickhouse.New(gorm_clickhouse.Config{ 84 | Conn: conn, 85 | })) 86 | } 87 | -------------------------------------------------------------------------------- /core/src/plugins/clickhouse/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import "gorm.io/gorm" 18 | 19 | const graphQuery = ` 20 | WITH dependencies AS ( 21 | SELECT 22 | table AS Table1, 23 | referenced_table AS Table2, 24 | CASE 25 | WHEN constraint_type IN ('PRIMARY KEY', 'UNIQUE') 26 | AND referenced_constraint_type IN ('PRIMARY KEY', 'UNIQUE') 27 | THEN 'OneToOne' 28 | WHEN constraint_type IN ('PRIMARY KEY', 'UNIQUE') 29 | THEN 'OneToMany' 30 | WHEN referenced_constraint_type IN ('PRIMARY KEY', 'UNIQUE') 31 | THEN 'ManyToOne' 32 | ELSE 'ManyToMany' 33 | END AS Relation 34 | FROM system.tables t 35 | JOIN system.columns c ON t.database = c.database AND t.name = c.table 36 | WHERE c.type LIKE '%Nullable%' 37 | AND t.database = ? 38 | ) 39 | SELECT DISTINCT * FROM dependencies 40 | WHERE Table1 != Table2; 41 | ` 42 | 43 | func (p *ClickHousePlugin) GetGraphQueryDB(db *gorm.DB, schema string) *gorm.DB { 44 | return db.Raw(graphQuery, schema) 45 | } 46 | -------------------------------------------------------------------------------- /core/src/plugins/clickhouse/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package clickhouse 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | ) 21 | 22 | func (p *ClickHousePlugin) ConvertStringValueDuringMap(value, columnType string) (interface{}, error) { 23 | return p.ConvertStringValue(value, columnType) 24 | } 25 | 26 | func (p *ClickHousePlugin) EscapeSpecificIdentifier(identifier string) string { 27 | identifier = strings.Replace(identifier, "`", "``", -1) 28 | return fmt.Sprintf("`%s`", identifier) 29 | } 30 | 31 | func (p *ClickHousePlugin) GetPrimaryKeyColQuery() string { 32 | return ` 33 | SELECT name 34 | FROM system.columns 35 | WHERE database = ? AND table = ? AND is_in_primary_key = 1 36 | ` 37 | } 38 | 39 | func (p *ClickHousePlugin) GetColTypeQuery() string { 40 | return ` 41 | SELECT 42 | name, 43 | type 44 | FROM system.columns 45 | WHERE database = ? AND table = ?` 46 | } 47 | -------------------------------------------------------------------------------- /core/src/plugins/common.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package plugins 16 | 17 | import ( 18 | "github.com/clidey/whodb/core/src/engine" 19 | "gorm.io/gorm" 20 | ) 21 | 22 | type DBOperation[T any] func(*gorm.DB) (T, error) 23 | type DBCreationFunc func(pluginConfig *engine.PluginConfig) (*gorm.DB, error) 24 | 25 | // WithConnection handles database connection lifecycle and executes the operation 26 | func WithConnection[T any](config *engine.PluginConfig, DB DBCreationFunc, operation DBOperation[T]) (T, error) { 27 | db, err := DB(config) 28 | if err != nil { 29 | var zero T 30 | return zero, err 31 | } 32 | sqlDb, err := db.DB() 33 | if err != nil { 34 | var zero T 35 | return zero, err 36 | } 37 | defer sqlDb.Close() 38 | return operation(db) 39 | } 40 | -------------------------------------------------------------------------------- /core/src/plugins/elasticsearch/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package elasticsearch 18 | 19 | import ( 20 | "bytes" 21 | "encoding/json" 22 | "fmt" 23 | 24 | "github.com/clidey/whodb/core/src/engine" 25 | ) 26 | 27 | func (p *ElasticSearchPlugin) AddStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, fields []engine.Record) (bool, error) { 28 | client, err := DB(config) 29 | if err != nil { 30 | return false, err 31 | } 32 | 33 | res, err := client.Indices.Create(storageUnit) 34 | if err != nil { 35 | return false, err 36 | } 37 | 38 | defer res.Body.Close() 39 | 40 | if res.IsError() { 41 | return false, fmt.Errorf("failed to create index: %s", res.String()) 42 | } 43 | 44 | return true, nil 45 | } 46 | 47 | func (p *ElasticSearchPlugin) AddRow(config *engine.PluginConfig, schema string, storageUnit string, values []engine.Record) (bool, error) { 48 | client, err := DB(config) 49 | if err != nil { 50 | return false, err 51 | } 52 | 53 | jsonValue := map[string]string{} 54 | for _, value := range values { 55 | jsonValue[value.Key] = value.Value 56 | } 57 | 58 | documentBytes, err := json.Marshal(jsonValue) 59 | if err != nil { 60 | return false, fmt.Errorf("error marshaling document to JSON: %v", err) 61 | } 62 | 63 | documentReader := bytes.NewReader(documentBytes) 64 | 65 | res, err := client.Index(storageUnit, documentReader) 66 | if err != nil { 67 | return false, fmt.Errorf("error indexing document: %v", err) 68 | } 69 | defer res.Body.Close() 70 | 71 | if res.IsError() { 72 | return false, fmt.Errorf("failed to index document: %s", res.String()) 73 | } 74 | 75 | return true, nil 76 | } 77 | -------------------------------------------------------------------------------- /core/src/plugins/elasticsearch/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package elasticsearch 16 | 17 | import ( 18 | "fmt" 19 | "net" 20 | "net/url" 21 | "strconv" 22 | 23 | "github.com/clidey/whodb/core/src/common" 24 | "github.com/clidey/whodb/core/src/engine" 25 | "github.com/elastic/go-elasticsearch/v8" 26 | ) 27 | 28 | func DB(config *engine.PluginConfig) (*elasticsearch.Client, error) { 29 | var addresses []string 30 | port, err := strconv.Atoi(common.GetRecordValueOrDefault(config.Credentials.Advanced, "Port", "9200")) 31 | if err != nil { 32 | return nil, err 33 | } 34 | sslMode := common.GetRecordValueOrDefault(config.Credentials.Advanced, "SSL Mode", "disable") 35 | 36 | hostName := url.QueryEscape(config.Credentials.Hostname) 37 | 38 | scheme := "https" 39 | if sslMode == "disable" { 40 | scheme = "http" 41 | } 42 | 43 | addressUrl := url.URL{ 44 | Scheme: scheme, 45 | Host: net.JoinHostPort(hostName, strconv.Itoa(port)), 46 | } 47 | 48 | addresses = []string{ 49 | addressUrl.String(), 50 | } 51 | 52 | cfg := elasticsearch.Config{ 53 | Addresses: addresses, 54 | Username: url.QueryEscape(config.Credentials.Username), 55 | Password: url.QueryEscape(config.Credentials.Password), 56 | } 57 | 58 | client, err := elasticsearch.NewClient(cfg) 59 | if err != nil { 60 | return nil, err 61 | } 62 | 63 | res, err := client.Info() 64 | if err != nil || res.IsError() { 65 | return nil, fmt.Errorf("error pinging Elasticsearch: %v", err) 66 | } 67 | 68 | return client, nil 69 | } 70 | -------------------------------------------------------------------------------- /core/src/plugins/elasticsearch/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package elasticsearch 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "errors" 21 | "fmt" 22 | 23 | "github.com/clidey/whodb/core/src/engine" 24 | ) 25 | 26 | func (p *ElasticSearchPlugin) DeleteRow(config *engine.PluginConfig, database string, storageUnit string, values map[string]string) (bool, error) { 27 | client, err := DB(config) 28 | if err != nil { 29 | return false, err 30 | } 31 | 32 | // Extract the document JSON 33 | documentJSON, ok := values["document"] 34 | if !ok { 35 | return false, errors.New("missing 'document' key in values map") 36 | } 37 | 38 | // Unmarshal the JSON to extract the _id field 39 | var jsonValues map[string]interface{} 40 | if err := json.Unmarshal([]byte(documentJSON), &jsonValues); err != nil { 41 | return false, err 42 | } 43 | 44 | // Get the _id from the document 45 | id, ok := jsonValues["_id"] 46 | if !ok { 47 | return false, errors.New("missing '_id' field in the document") 48 | } 49 | 50 | // Delete the document by ID 51 | res, err := client.Delete( 52 | storageUnit, 53 | id.(string), 54 | client.Delete.WithContext(context.Background()), 55 | client.Delete.WithRefresh("true"), // Ensure the deletion is immediately visible 56 | ) 57 | if err != nil { 58 | return false, fmt.Errorf("failed to execute delete: %w", err) 59 | } 60 | defer res.Body.Close() 61 | 62 | // Check if the response indicates an error 63 | if res.IsError() { 64 | return false, fmt.Errorf("error deleting document: %s", res.String()) 65 | } 66 | 67 | // Decode the response to check the result 68 | var deleteResponse map[string]interface{} 69 | if err := json.NewDecoder(res.Body).Decode(&deleteResponse); err != nil { 70 | return false, err 71 | } 72 | 73 | // Check if the deletion was successful 74 | if result, ok := deleteResponse["result"].(string); ok && result != "deleted" { 75 | return false, errors.New("no documents were deleted") 76 | } 77 | 78 | return true, nil 79 | } 80 | -------------------------------------------------------------------------------- /core/src/plugins/elasticsearch/update.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package elasticsearch 16 | 17 | import ( 18 | "bytes" 19 | "context" 20 | "encoding/json" 21 | "errors" 22 | "fmt" 23 | 24 | "github.com/clidey/whodb/core/src/engine" 25 | ) 26 | 27 | var script = ` 28 | for (entry in params.entrySet()) { 29 | ctx._source[entry.getKey()] = entry.getValue(); 30 | } 31 | for (key in ctx._source.keySet().toArray()) { 32 | if (!params.containsKey(key)) { 33 | ctx._source.remove(key); 34 | } 35 | } 36 | ` 37 | 38 | func (p *ElasticSearchPlugin) UpdateStorageUnit(config *engine.PluginConfig, database string, storageUnit string, values map[string]string, updatedColumns []string) (bool, error) { 39 | client, err := DB(config) 40 | if err != nil { 41 | return false, err 42 | } 43 | 44 | documentJSON, ok := values["document"] 45 | if !ok { 46 | return false, errors.New("missing 'document' key in values map") 47 | } 48 | 49 | var jsonValues map[string]interface{} 50 | if err := json.Unmarshal([]byte(documentJSON), &jsonValues); err != nil { 51 | return false, err 52 | } 53 | 54 | id, ok := jsonValues["_id"] 55 | if !ok { 56 | return false, errors.New("missing '_id' field in the document") 57 | } 58 | 59 | delete(jsonValues, "_id") 60 | 61 | var buf bytes.Buffer 62 | if err := json.NewEncoder(&buf).Encode(map[string]interface{}{ 63 | "script": map[string]interface{}{ 64 | "source": script, 65 | "lang": "painless", 66 | "params": jsonValues, 67 | }, 68 | "upsert": jsonValues, 69 | }); err != nil { 70 | return false, err 71 | } 72 | 73 | res, err := client.Update( 74 | storageUnit, 75 | id.(string), 76 | &buf, 77 | client.Update.WithContext(context.Background()), 78 | client.Update.WithRefresh("true"), 79 | ) 80 | if err != nil { 81 | return false, fmt.Errorf("failed to execute update: %w", err) 82 | } 83 | defer res.Body.Close() 84 | 85 | if res.IsError() { 86 | return false, fmt.Errorf("error updating document: %s", res.String()) 87 | } 88 | 89 | var updateResponse map[string]interface{} 90 | if err := json.NewDecoder(res.Body).Decode(&updateResponse); err != nil { 91 | return false, err 92 | } 93 | 94 | if result, ok := updateResponse["result"].(string); ok && result == "noop" { 95 | return false, errors.New("no documents were updated") 96 | } 97 | 98 | return true, nil 99 | } 100 | -------------------------------------------------------------------------------- /core/src/plugins/gorm/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gorm_plugin 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "github.com/clidey/whodb/core/src/engine" 23 | "github.com/clidey/whodb/core/src/plugins" 24 | "gorm.io/gorm" 25 | "strconv" 26 | ) 27 | 28 | func (p *GormPlugin) AddStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, fields []engine.Record) (bool, error) { 29 | return plugins.WithConnection[bool](config, p.DB, func(db *gorm.DB) (bool, error) { 30 | if len(fields) == 0 { 31 | return false, errors.New("no fields provided for table creation") 32 | } 33 | 34 | schema = p.EscapeIdentifier(schema) 35 | storageUnit = p.EscapeIdentifier(storageUnit) 36 | 37 | columns := []engine.Record{} 38 | for _, fieldType := range fields { 39 | if !p.GetSupportedColumnDataTypes().Contains(fieldType.Value) { 40 | return false, fmt.Errorf("data type: %s not supported by: %s", fieldType.Value, p.Plugin.Type) 41 | } 42 | 43 | fieldName := p.EscapeIdentifier(fieldType.Key) 44 | primaryKey, err := strconv.ParseBool(fieldType.Extra["Primary"]) 45 | if err != nil { 46 | primaryKey = false 47 | } 48 | nullable, err := strconv.ParseBool(fieldType.Extra["Nullable"]) 49 | if err != nil { 50 | nullable = false 51 | } 52 | 53 | columns = append(columns, engine.Record{ 54 | Key: fieldName, 55 | Value: fieldType.Value, 56 | Extra: map[string]string{ 57 | "primary": strconv.FormatBool(primaryKey), 58 | "nullable": strconv.FormatBool(nullable), 59 | }, 60 | }) 61 | } 62 | 63 | createTableQuery := p.GetCreateTableQuery(schema, storageUnit, columns) 64 | 65 | if err := db.Exec(createTableQuery).Error; err != nil { 66 | return false, err 67 | } 68 | return true, nil 69 | }) 70 | } 71 | 72 | func (p *GormPlugin) AddRow(config *engine.PluginConfig, schema string, storageUnit string, values []engine.Record) (bool, error) { 73 | return plugins.WithConnection[bool](config, p.DB, func(db *gorm.DB) (bool, error) { 74 | if len(values) == 0 { 75 | return false, errors.New("no values provided to insert into the table") 76 | } 77 | 78 | schema = p.EscapeIdentifier(schema) 79 | storageUnit = p.EscapeIdentifier(storageUnit) 80 | fullTableName := p.FormTableName(schema, storageUnit) 81 | 82 | valuesToAdd, err := p.ConvertRecordValuesToMap(values) 83 | if err != nil { 84 | return false, err 85 | } 86 | 87 | result := db.Table(fullTableName).Create(valuesToAdd) 88 | 89 | if result.Error != nil { 90 | return false, result.Error 91 | } 92 | 93 | return true, nil 94 | }) 95 | } 96 | -------------------------------------------------------------------------------- /core/src/plugins/gorm/chat.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gorm_plugin 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | 21 | "github.com/clidey/whodb/core/src/common" 22 | "github.com/clidey/whodb/core/src/engine" 23 | "github.com/clidey/whodb/core/src/llm" 24 | "github.com/clidey/whodb/core/src/plugins" 25 | "gorm.io/gorm" 26 | ) 27 | 28 | func (p *GormPlugin) Chat(config *engine.PluginConfig, schema string, model string, previousConversation string, query string) ([]*engine.ChatMessage, error) { 29 | return plugins.WithConnection(config, p.DB, func(db *gorm.DB) ([]*engine.ChatMessage, error) { 30 | tableFields, err := p.GetTableSchema(db, schema) 31 | if err != nil { 32 | return nil, err 33 | } 34 | 35 | tableDetails := strings.Builder{} 36 | for tableName, fields := range tableFields { 37 | tableDetails.WriteString(fmt.Sprintf("table: %v\n", tableName)) 38 | for _, field := range fields { 39 | tableDetails.WriteString(fmt.Sprintf("- %v (%v)\n", field.Key, field.Value)) 40 | } 41 | } 42 | 43 | context := tableDetails.String() 44 | 45 | completeQuery := fmt.Sprintf(common.RawSQLQueryPrompt, p.Type, schema, context, previousConversation, query) 46 | 47 | response, err := llm.Instance(config).Complete(completeQuery, llm.LLMModel(model), nil) 48 | if err != nil { 49 | return nil, err 50 | } 51 | 52 | return common.SQLChat(*response, config, p) 53 | }) 54 | } 55 | -------------------------------------------------------------------------------- /core/src/plugins/gorm/delete.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gorm_plugin 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "github.com/clidey/whodb/core/src/common" 23 | "github.com/clidey/whodb/core/src/engine" 24 | "github.com/clidey/whodb/core/src/plugins" 25 | "gorm.io/gorm" 26 | ) 27 | 28 | func (p *GormPlugin) DeleteRow(config *engine.PluginConfig, schema string, storageUnit string, values map[string]string) (bool, error) { 29 | return plugins.WithConnection[bool](config, p.DB, func(db *gorm.DB) (bool, error) { 30 | pkColumns, err := p.GetPrimaryKeyColumns(db, schema, storageUnit) 31 | if err != nil { 32 | pkColumns = []string{} 33 | } 34 | 35 | columnTypes, err := p.GetColumnTypes(db, schema, storageUnit) 36 | if err != nil { 37 | return false, err 38 | } 39 | 40 | conditions := make(map[string]interface{}) 41 | convertedValues := make(map[string]interface{}) 42 | for column, strValue := range values { 43 | columnType, exists := columnTypes[column] 44 | if !exists { 45 | return false, fmt.Errorf("column '%s' does not exist in table %s", column, storageUnit) 46 | } 47 | 48 | convertedValue, err := p.ConvertStringValue(strValue, columnType) 49 | if err != nil { 50 | convertedValue = strValue // use string value if conversion fails? 51 | } 52 | 53 | targetColumn := column 54 | if common.ContainsString(pkColumns, column) { 55 | conditions[targetColumn] = convertedValue 56 | } else { 57 | convertedValues[targetColumn] = convertedValue 58 | } 59 | } 60 | 61 | schema = p.EscapeIdentifier(schema) 62 | storageUnit = p.EscapeIdentifier(storageUnit) 63 | tableName := p.FormTableName(schema, storageUnit) 64 | 65 | var result *gorm.DB 66 | if len(conditions) == 0 { 67 | result = db.Table(tableName).Where(convertedValues).Delete(nil) 68 | } else { 69 | result = db.Table(tableName).Where(conditions).Delete(nil) 70 | } 71 | 72 | if result.Error != nil { 73 | return false, result.Error 74 | } 75 | 76 | // todo: investigate why the clickhouse driver doesnt show any updated rows after a delete 77 | if p.Type != engine.DatabaseType_ClickHouse && result.RowsAffected == 0 { 78 | return false, errors.New("no rows were deleted") 79 | } 80 | 81 | return true, nil 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /core/src/plugins/gorm/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package gorm_plugin 16 | 17 | import ( 18 | "github.com/clidey/whodb/core/src/engine" 19 | "github.com/clidey/whodb/core/src/plugins" 20 | "gorm.io/gorm" 21 | ) 22 | 23 | type tableRelations struct { 24 | Table1 string 25 | Table2 string 26 | Relation string 27 | } 28 | 29 | func (p *GormPlugin) GetGraph(config *engine.PluginConfig, schema string) ([]engine.GraphUnit, error) { 30 | return plugins.WithConnection(config, p.DB, func(db *gorm.DB) ([]engine.GraphUnit, error) { 31 | tableRelations := []tableRelations{} 32 | 33 | if p.Type == engine.DatabaseType_ClickHouse { 34 | schema = config.Credentials.Database 35 | } 36 | if err := p.GetGraphQueryDB(db, schema).Scan(&tableRelations).Error; err != nil { 37 | return nil, err 38 | } 39 | 40 | tableMap := make(map[string][]engine.GraphUnitRelationship) 41 | for _, tr := range tableRelations { 42 | tableMap[tr.Table1] = append(tableMap[tr.Table1], engine.GraphUnitRelationship{Name: tr.Table2, RelationshipType: engine.GraphUnitRelationshipType(tr.Relation)}) 43 | } 44 | 45 | storageUnits, err := p.GetStorageUnits(config, schema) 46 | if err != nil { 47 | return nil, err 48 | } 49 | 50 | storageUnitsMap := map[string]engine.StorageUnit{} 51 | for _, storageUnit := range storageUnits { 52 | storageUnitsMap[storageUnit.Name] = storageUnit 53 | } 54 | 55 | tables := []engine.GraphUnit{} 56 | for _, storageUnit := range storageUnits { 57 | foundTable, ok := tableMap[storageUnit.Name] 58 | var relations []engine.GraphUnitRelationship 59 | if ok { 60 | relations = foundTable 61 | } 62 | tables = append(tables, engine.GraphUnit{Unit: storageUnit, Relations: relations}) 63 | } 64 | 65 | return tables, nil 66 | }) 67 | } 68 | -------------------------------------------------------------------------------- /core/src/plugins/gorm/update.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package gorm_plugin 18 | 19 | import ( 20 | "errors" 21 | "fmt" 22 | "github.com/clidey/whodb/core/src/common" 23 | "github.com/clidey/whodb/core/src/engine" 24 | "github.com/clidey/whodb/core/src/plugins" 25 | "gorm.io/gorm" 26 | ) 27 | 28 | func (p *GormPlugin) UpdateStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, values map[string]string, updatedColumns []string) (bool, error) { 29 | return plugins.WithConnection(config, p.DB, func(db *gorm.DB) (bool, error) { 30 | pkColumns, err := p.GetPrimaryKeyColumns(db, schema, storageUnit) 31 | if err != nil { 32 | pkColumns = []string{} 33 | } 34 | 35 | columnTypes, err := p.GetColumnTypes(db, schema, storageUnit) 36 | if err != nil { 37 | return false, err 38 | } 39 | 40 | conditions := make(map[string]interface{}) 41 | convertedValues := make(map[string]interface{}) 42 | unchangedValues := make(map[string]interface{}) 43 | 44 | for column, strValue := range values { 45 | columnType, exists := columnTypes[column] 46 | if !exists { 47 | return false, fmt.Errorf("column '%s' does not exist in table %s", column, storageUnit) 48 | } 49 | 50 | convertedValue, err := p.ConvertStringValue(strValue, columnType) 51 | if err != nil { 52 | convertedValue = strValue // use the original value if conversion fails? 53 | } 54 | 55 | targetColumn := column 56 | if common.ContainsString(pkColumns, column) { 57 | conditions[targetColumn] = convertedValue 58 | } else if common.ContainsString(updatedColumns, column) { 59 | convertedValues[targetColumn] = convertedValue 60 | } else { 61 | // Store unchanged values for WHERE clause if no PKs 62 | unchangedValues[targetColumn] = convertedValue 63 | } 64 | } 65 | 66 | // If no columns to update, return early 67 | if len(convertedValues) == 0 { 68 | return true, nil 69 | } 70 | 71 | schema = p.EscapeIdentifier(schema) 72 | storageUnit = p.EscapeIdentifier(storageUnit) 73 | tableName := p.FormTableName(schema, storageUnit) 74 | 75 | var result *gorm.DB 76 | if len(conditions) == 0 { 77 | result = db.Table(tableName).Where(unchangedValues).Updates(convertedValues) 78 | } else { 79 | result = db.Table(tableName).Where(conditions, nil).Updates(convertedValues) 80 | } 81 | 82 | if result.Error != nil { 83 | return false, result.Error 84 | } 85 | 86 | // todo: investigate why the clickhouse driver doesnt show any updated rows after an update 87 | if p.Type != engine.DatabaseType_ClickHouse && result.RowsAffected == 0 { 88 | return false, errors.New("no rows were updated") 89 | } 90 | 91 | return true, nil 92 | }) 93 | } 94 | -------------------------------------------------------------------------------- /core/src/plugins/mongodb/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package mongodb 18 | 19 | import ( 20 | "context" 21 | "errors" 22 | 23 | "github.com/clidey/whodb/core/src/engine" 24 | "go.mongodb.org/mongo-driver/bson" 25 | "go.mongodb.org/mongo-driver/mongo" 26 | ) 27 | 28 | func (p *MongoDBPlugin) AddStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, fields []engine.Record) (bool, error) { 29 | client, err := DB(config) 30 | if err != nil { 31 | return false, err 32 | } 33 | defer client.Disconnect(context.Background()) 34 | 35 | database := client.Database(schema) 36 | 37 | err = createCollectionIfNotExists(database, storageUnit) 38 | if err != nil { 39 | return false, err 40 | } 41 | 42 | return true, nil 43 | } 44 | 45 | func (p *MongoDBPlugin) AddRow(config *engine.PluginConfig, schema string, storageUnit string, values []engine.Record) (bool, error) { 46 | client, err := DB(config) 47 | if err != nil { 48 | return false, err 49 | } 50 | defer client.Disconnect(context.Background()) 51 | 52 | collection := client.Database(schema).Collection(storageUnit) 53 | 54 | document := make(map[string]interface{}) 55 | for _, value := range values { 56 | document[value.Key] = value.Value 57 | } 58 | 59 | _, err = collection.InsertOne(context.Background(), document) 60 | if err != nil { 61 | return false, err 62 | } 63 | 64 | return true, nil 65 | } 66 | 67 | func createCollectionIfNotExists(database *mongo.Database, collectionName string) error { 68 | collections, err := database.ListCollectionNames(context.Background(), bson.D{}) 69 | if err != nil { 70 | return err 71 | } 72 | 73 | for _, col := range collections { 74 | if col == collectionName { 75 | return errors.New("collection already exists") 76 | } 77 | } 78 | 79 | err = database.CreateCollection(context.Background(), collectionName) 80 | if err != nil { 81 | return err 82 | } 83 | 84 | return nil 85 | } 86 | -------------------------------------------------------------------------------- /core/src/plugins/mongodb/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodb 16 | 17 | import ( 18 | "context" 19 | "fmt" 20 | "net/url" 21 | "strconv" 22 | "strings" 23 | 24 | "github.com/clidey/whodb/core/src/common" 25 | "github.com/clidey/whodb/core/src/engine" 26 | "go.mongodb.org/mongo-driver/mongo" 27 | "go.mongodb.org/mongo-driver/mongo/options" 28 | ) 29 | 30 | func DB(config *engine.PluginConfig) (*mongo.Client, error) { 31 | ctx := context.Background() 32 | port, err := strconv.Atoi(common.GetRecordValueOrDefault(config.Credentials.Advanced, "Port", "27017")) 33 | if err != nil { 34 | return nil, err 35 | } 36 | queryParams := common.GetRecordValueOrDefault(config.Credentials.Advanced, "URL Params", "") 37 | dnsEnabled, err := strconv.ParseBool(common.GetRecordValueOrDefault(config.Credentials.Advanced, "DNS Enabled", "false")) 38 | if err != nil { 39 | return nil, err 40 | } 41 | 42 | connectionURI := strings.Builder{} 43 | clientOptions := options.Client() 44 | 45 | if dnsEnabled { 46 | connectionURI.WriteString("mongodb+srv://") 47 | connectionURI.WriteString(fmt.Sprintf("%s/", config.Credentials.Hostname)) 48 | } else { 49 | connectionURI.WriteString("mongodb://") 50 | connectionURI.WriteString(fmt.Sprintf("%s:%d/", config.Credentials.Hostname, port)) 51 | } 52 | 53 | connectionURI.WriteString(config.Credentials.Database) 54 | connectionURI.WriteString(queryParams) 55 | 56 | clientOptions.ApplyURI(connectionURI.String()) 57 | clientOptions.SetAuth(options.Credential{ 58 | Username: url.QueryEscape(config.Credentials.Username), 59 | Password: url.QueryEscape(config.Credentials.Password), 60 | }) 61 | 62 | client, err := mongo.Connect(ctx, clientOptions) 63 | if err != nil { 64 | return nil, err 65 | } 66 | err = client.Ping(ctx, nil) 67 | if err != nil { 68 | return nil, err 69 | } 70 | return client, nil 71 | } 72 | -------------------------------------------------------------------------------- /core/src/plugins/mongodb/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodb 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "errors" 21 | 22 | "github.com/clidey/whodb/core/src/engine" 23 | "go.mongodb.org/mongo-driver/bson" 24 | "go.mongodb.org/mongo-driver/bson/primitive" 25 | ) 26 | 27 | func (p *MongoDBPlugin) DeleteRow(config *engine.PluginConfig, database string, storageUnit string, values map[string]string) (bool, error) { 28 | ctx := context.Background() 29 | client, err := DB(config) 30 | if err != nil { 31 | return false, err 32 | } 33 | defer client.Disconnect(ctx) 34 | 35 | db := client.Database(database) 36 | collection := db.Collection(storageUnit) 37 | 38 | documentJSON, ok := values["document"] 39 | if !ok { 40 | return false, errors.New("missing 'document' key in values map") 41 | } 42 | 43 | var jsonValues bson.M 44 | if err := json.Unmarshal([]byte(documentJSON), &jsonValues); err != nil { 45 | return false, err 46 | } 47 | 48 | id, ok := jsonValues["_id"] 49 | if !ok { 50 | return false, errors.New("missing '_id' field in the document") 51 | } 52 | 53 | objectID, err := primitive.ObjectIDFromHex(id.(string)) 54 | if err != nil { 55 | return false, errors.New("invalid '_id' field; not a valid ObjectID") 56 | } 57 | 58 | delete(jsonValues, "_id") 59 | 60 | filter := bson.M{"_id": objectID} 61 | 62 | result, err := collection.DeleteOne(ctx, filter) 63 | if err != nil { 64 | return false, err 65 | } 66 | 67 | if result.DeletedCount == 0 { 68 | return false, errors.New("no documents were deleted") 69 | } 70 | 71 | return true, nil 72 | } 73 | -------------------------------------------------------------------------------- /core/src/plugins/mongodb/update.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mongodb 16 | 17 | import ( 18 | "context" 19 | "encoding/json" 20 | "errors" 21 | 22 | "github.com/clidey/whodb/core/src/engine" 23 | "go.mongodb.org/mongo-driver/bson" 24 | "go.mongodb.org/mongo-driver/bson/primitive" 25 | ) 26 | 27 | func (p *MongoDBPlugin) UpdateStorageUnit(config *engine.PluginConfig, database string, storageUnit string, values map[string]string, updatedColumns []string) (bool, error) { 28 | ctx := context.Background() 29 | client, err := DB(config) 30 | if err != nil { 31 | return false, err 32 | } 33 | defer client.Disconnect(ctx) 34 | 35 | db := client.Database(database) 36 | collection := db.Collection(storageUnit) 37 | 38 | documentJSON, ok := values["document"] 39 | if !ok { 40 | return false, errors.New("missing 'document' key in values map") 41 | } 42 | 43 | var jsonValues bson.M 44 | if err := json.Unmarshal([]byte(documentJSON), &jsonValues); err != nil { 45 | return false, err 46 | } 47 | 48 | id, ok := jsonValues["_id"] 49 | if !ok { 50 | return false, errors.New("missing '_id' field in the document") 51 | } 52 | 53 | objectID, err := primitive.ObjectIDFromHex(id.(string)) 54 | if err != nil { 55 | return false, errors.New("invalid '_id' field; not a valid ObjectID") 56 | } 57 | 58 | delete(jsonValues, "_id") 59 | 60 | filter := bson.M{"_id": objectID} 61 | update := bson.M{"$set": jsonValues} 62 | 63 | result, err := collection.UpdateOne(ctx, filter, update) 64 | if err != nil { 65 | return false, err 66 | } 67 | 68 | if result.MatchedCount == 0 { 69 | return false, errors.New("no documents matched the filter") 70 | } 71 | if result.ModifiedCount == 0 { 72 | return false, errors.New("no documents were updated") 73 | } 74 | 75 | return true, nil 76 | } 77 | -------------------------------------------------------------------------------- /core/src/plugins/mysql/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package mysql 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/engine" 22 | "strings" 23 | ) 24 | 25 | func (p *MySQLPlugin) GetCreateTableQuery(schema string, storageUnit string, columns []engine.Record) string { 26 | var columnDefs []string 27 | for _, column := range columns { 28 | columnDef := fmt.Sprintf("%s %s", column.Key, column.Value) 29 | 30 | if primary, ok := column.Extra["primary"]; ok && primary == "true" { 31 | lowerType := strings.ToLower(column.Value) 32 | if strings.Contains(lowerType, "int") { 33 | columnDef = fmt.Sprintf("%s PRIMARY KEY AUTO_INCREMENT", columnDef) 34 | } else { 35 | columnDef = fmt.Sprintf("%s PRIMARY KEY", columnDef) 36 | } 37 | } else { 38 | if nullable, ok := column.Extra["nullable"]; ok && nullable == "false" { 39 | columnDef = fmt.Sprintf("%s NOT NULL", columnDef) 40 | } 41 | } 42 | 43 | columnDefs = append(columnDefs, columnDef) 44 | } 45 | 46 | columnDefsStr := strings.Join(columnDefs, ", ") 47 | 48 | // MySQL/MariaDB syntax uses database.table rather than schema.table 49 | createTableQuery := "CREATE TABLE %s.%s (%s)" 50 | return fmt.Sprintf(createTableQuery, schema, storageUnit, columnDefsStr) 51 | } 52 | -------------------------------------------------------------------------------- /core/src/plugins/mysql/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mysql 16 | 17 | import ( 18 | "github.com/clidey/whodb/core/src/engine" 19 | mysqldriver "github.com/go-sql-driver/mysql" 20 | "gorm.io/driver/mysql" 21 | "gorm.io/gorm" 22 | "net" 23 | "strconv" 24 | ) 25 | 26 | func (p *MySQLPlugin) DB(config *engine.PluginConfig) (*gorm.DB, error) { 27 | connectionInput, err := p.ParseConnectionConfig(config) 28 | if err != nil { 29 | return nil, err 30 | } 31 | 32 | mysqlConfig := mysqldriver.NewConfig() 33 | mysqlConfig.User = connectionInput.Username 34 | mysqlConfig.Passwd = connectionInput.Password 35 | mysqlConfig.Net = "tcp" 36 | mysqlConfig.Addr = net.JoinHostPort(connectionInput.Hostname, strconv.Itoa(connectionInput.Port)) 37 | mysqlConfig.DBName = connectionInput.Database 38 | mysqlConfig.AllowCleartextPasswords = connectionInput.AllowClearTextPasswords 39 | mysqlConfig.ParseTime = connectionInput.ParseTime 40 | mysqlConfig.Loc = connectionInput.Loc 41 | mysqlConfig.Params = connectionInput.ExtraOptions 42 | 43 | db, err := gorm.Open(mysql.Open(mysqlConfig.FormatDSN()), &gorm.Config{}) 44 | if err != nil { 45 | return nil, err 46 | } 47 | return db, nil 48 | } 49 | -------------------------------------------------------------------------------- /core/src/plugins/mysql/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mysql 16 | 17 | import "gorm.io/gorm" 18 | 19 | const graphQuery = ` 20 | WITH ForeignKeyRelations AS ( 21 | SELECT 22 | rc.TABLE_NAME AS Table1, 23 | rc.REFERENCED_TABLE_NAME AS Table2 24 | FROM 25 | INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc 26 | WHERE 27 | rc.CONSTRAINT_SCHEMA = ? 28 | ) 29 | SELECT DISTINCT 30 | fkr.Table1, 31 | fkr.Table2, 32 | CASE 33 | WHEN pk2.CONSTRAINT_TYPE = 'PRIMARY KEY' THEN 'ManyToOne' -- Table1 has a FK referencing a PK in Table2 34 | ELSE 'Unknown' 35 | END AS Relation 36 | FROM 37 | ForeignKeyRelations fkr 38 | JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk1 39 | ON fkr.Table1 = pk1.TABLE_NAME 40 | AND pk1.CONSTRAINT_TYPE = 'FOREIGN KEY' 41 | JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk2 42 | ON fkr.Table2 = pk2.TABLE_NAME 43 | AND pk2.CONSTRAINT_TYPE = 'PRIMARY KEY' 44 | WHERE 45 | fkr.Table1 <> fkr.Table2; 46 | ` 47 | 48 | func (p *MySQLPlugin) GetGraphQueryDB(db *gorm.DB, schema string) *gorm.DB { 49 | return db.Raw(graphQuery, schema) 50 | } 51 | -------------------------------------------------------------------------------- /core/src/plugins/mysql/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package mysql 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | ) 21 | 22 | func (p *MySQLPlugin) ConvertStringValueDuringMap(value, columnType string) (interface{}, error) { 23 | return value, nil 24 | } 25 | 26 | func (p *MySQLPlugin) EscapeSpecificIdentifier(identifier string) string { 27 | identifier = strings.Replace(identifier, "`", "``", -1) 28 | return fmt.Sprintf("`%s`", identifier) 29 | } 30 | 31 | func (p *MySQLPlugin) GetPrimaryKeyColQuery() string { 32 | return ` 33 | SELECT k.column_name 34 | FROM information_schema.table_constraints t 35 | JOIN information_schema.key_column_usage k 36 | USING (constraint_name, table_schema, table_name) 37 | WHERE t.constraint_type = 'PRIMARY KEY' 38 | AND t.table_schema = ? 39 | AND t.table_name = ?; 40 | ` 41 | } 42 | 43 | func (p *MySQLPlugin) GetColTypeQuery() string { 44 | return ` 45 | SELECT column_name, data_type 46 | FROM information_schema.columns 47 | WHERE table_schema = ? AND table_name = ?; 48 | ` 49 | } 50 | -------------------------------------------------------------------------------- /core/src/plugins/postgres/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/engine" 22 | "strings" 23 | ) 24 | 25 | func (p *PostgresPlugin) GetCreateTableQuery(schema string, storageUnit string, columns []engine.Record) string { 26 | var columnDefs []string 27 | for _, column := range columns { 28 | columnDef := fmt.Sprintf("%s %s", column.Key, column.Value) 29 | 30 | if primary, ok := column.Extra["primary"]; ok && primary == "true" { 31 | lowerType := strings.ToLower(column.Value) 32 | if strings.Contains(lowerType, "int") || strings.Contains(lowerType, "integer") { 33 | columnDef = fmt.Sprintf("%s %s", columnDef, "PRIMARY KEY GENERATED ALWAYS AS IDENTITY") 34 | } else { 35 | columnDef = fmt.Sprintf("%s %s", columnDef, "PRIMARY KEY") 36 | } 37 | } else { 38 | if nullable, ok := column.Extra["nullable"]; ok && nullable == "false" { 39 | columnDef = fmt.Sprintf("%s %s", columnDef, "NOT NULL") 40 | } 41 | } 42 | 43 | columnDefs = append(columnDefs, columnDef) 44 | } 45 | 46 | columnDefsStr := strings.Join(columnDefs, ", ") 47 | 48 | createTableQuery := "CREATE TABLE %s.%s (%s)" 49 | return fmt.Sprintf(createTableQuery, schema, storageUnit, columnDefsStr) 50 | } 51 | -------------------------------------------------------------------------------- /core/src/plugins/postgres/db.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package postgres 18 | 19 | import ( 20 | "fmt" 21 | "strings" 22 | 23 | "github.com/clidey/whodb/core/src/engine" 24 | "gorm.io/driver/postgres" 25 | "gorm.io/gorm" 26 | ) 27 | 28 | func escape(x string) string { 29 | return strings.ReplaceAll(x, "'", "\\'") 30 | } 31 | 32 | func (p *PostgresPlugin) DB(config *engine.PluginConfig) (*gorm.DB, error) { 33 | connectionInput, err := p.ParseConnectionConfig(config) 34 | if err != nil { 35 | return nil, err 36 | } 37 | 38 | host := escape(connectionInput.Hostname) 39 | username := escape(connectionInput.Username) 40 | password := escape(connectionInput.Password) 41 | database := escape(connectionInput.Database) 42 | 43 | params := strings.Builder{} 44 | if connectionInput.ExtraOptions != nil { 45 | for key, value := range connectionInput.ExtraOptions { 46 | params.WriteString(fmt.Sprintf("%v='%v' ", strings.ToLower(key), escape(value))) 47 | } 48 | } 49 | 50 | dsn := fmt.Sprintf("host='%v' user='%v' password='%v' dbname='%v' port='%v'", 51 | host, username, password, database, connectionInput.Port) 52 | 53 | if params.Len() > 0 { 54 | dsn = fmt.Sprintf("%v %v", dsn, params.String()) 55 | } 56 | 57 | db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{}) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return db, nil 62 | } 63 | -------------------------------------------------------------------------------- /core/src/plugins/postgres/utils.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package postgres 16 | 17 | import ( 18 | "fmt" 19 | "strings" 20 | ) 21 | 22 | func (p *PostgresPlugin) ConvertStringValueDuringMap(value, columnType string) (interface{}, error) { 23 | return value, nil 24 | } 25 | 26 | func (p *PostgresPlugin) GetPrimaryKeyColQuery() string { 27 | return ` 28 | SELECT a.attname 29 | FROM pg_index i 30 | JOIN pg_attribute a ON a.attrelid = i.indrelid AND a.attnum = ANY(i.indkey) 31 | JOIN pg_class c ON c.oid = i.indrelid 32 | JOIN pg_namespace n ON n.oid = c.relnamespace 33 | WHERE n.nspname = ? AND c.relname = ? AND i.indisprimary; 34 | ` 35 | } 36 | 37 | func (p *PostgresPlugin) GetColTypeQuery() string { 38 | return ` 39 | SELECT column_name, data_type 40 | FROM information_schema.columns 41 | WHERE table_schema = ? AND table_name = ?; 42 | ` 43 | } 44 | 45 | func (p *PostgresPlugin) EscapeSpecificIdentifier(identifier string) string { 46 | identifier = strings.Replace(identifier, "\"", "\"\"", -1) 47 | return fmt.Sprintf("\"%s\"", identifier) 48 | } 49 | -------------------------------------------------------------------------------- /core/src/plugins/redis/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package redis 18 | 19 | import ( 20 | "errors" 21 | 22 | "github.com/clidey/whodb/core/src/engine" 23 | ) 24 | 25 | func (p *RedisPlugin) AddStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, fields []engine.Record) (bool, error) { 26 | return false, errors.ErrUnsupported 27 | } 28 | 29 | func (p *RedisPlugin) AddRow(config *engine.PluginConfig, schema string, storageUnit string, values []engine.Record) (bool, error) { 30 | return false, errors.ErrUnsupported 31 | } 32 | -------------------------------------------------------------------------------- /core/src/plugins/redis/db.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "context" 19 | "net" 20 | "strconv" 21 | 22 | "github.com/clidey/whodb/core/src/common" 23 | "github.com/clidey/whodb/core/src/engine" 24 | "github.com/go-redis/redis/v8" 25 | ) 26 | 27 | func DB(config *engine.PluginConfig) (*redis.Client, error) { 28 | ctx := context.Background() 29 | port, err := strconv.Atoi(common.GetRecordValueOrDefault(config.Credentials.Advanced, "Port", "6379")) 30 | if err != nil { 31 | return nil, err 32 | } 33 | database := 0 34 | if config.Credentials.Database != "" { 35 | var err error 36 | database, err = strconv.Atoi(config.Credentials.Database) 37 | if err != nil { 38 | return nil, err 39 | } 40 | } 41 | addr := net.JoinHostPort(config.Credentials.Hostname, strconv.Itoa(port)) 42 | client := redis.NewClient(&redis.Options{ 43 | Addr: addr, 44 | Password: config.Credentials.Password, 45 | DB: database, 46 | }) 47 | if _, err := client.Ping(ctx).Result(); err != nil { 48 | return nil, err 49 | } 50 | return client, nil 51 | } 52 | -------------------------------------------------------------------------------- /core/src/plugins/redis/delete.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/engine" 22 | "strconv" 23 | ) 24 | 25 | func (p *RedisPlugin) DeleteRow(config *engine.PluginConfig, schema string, storageUnit string, values map[string]string) (bool, error) { 26 | client, err := DB(config) 27 | if err != nil { 28 | return false, err 29 | } 30 | defer client.Close() 31 | 32 | ctx := context.Background() 33 | 34 | keyType, err := client.Type(ctx, storageUnit).Result() 35 | if err != nil { 36 | return false, err 37 | } 38 | 39 | switch keyType { 40 | case "string": 41 | // Deleting the entire string key 42 | err := client.Del(ctx, storageUnit).Err() 43 | if err != nil { 44 | return false, err 45 | } 46 | case "hash": 47 | // Deleting a specific field from a hash 48 | field, ok := values["field"] 49 | if !ok { 50 | return false, errors.New("missing 'field' for hash deletion") 51 | } 52 | err := client.HDel(ctx, storageUnit, field).Err() 53 | if err != nil { 54 | return false, err 55 | } 56 | case "list": 57 | // Removing an element from a list 58 | indexStr, ok := values["index"] 59 | if !ok { 60 | return false, errors.New("missing 'index' for list deletion") 61 | } 62 | index, err := strconv.ParseInt(indexStr, 10, 64) 63 | if err != nil { 64 | return false, errors.New("unable to convert index to int") 65 | } 66 | value := client.LIndex(ctx, storageUnit, index).Val() 67 | if err := client.LRem(ctx, storageUnit, 1, value).Err(); err != nil { 68 | return false, errors.New("unable to remove the list item") 69 | } 70 | case "set": 71 | // Removing a specific member from a set 72 | member, ok := values["member"] 73 | if !ok { 74 | return false, errors.New("missing 'member' for set deletion") 75 | } 76 | err := client.SRem(ctx, storageUnit, member).Err() 77 | if err != nil { 78 | return false, err 79 | } 80 | case "zset": 81 | // Removing a specific member from a sorted set 82 | member, ok := values["member"] 83 | if !ok { 84 | return false, errors.New("missing 'member' for sorted set deletion") 85 | } 86 | err := client.ZRem(ctx, storageUnit, member).Err() 87 | if err != nil { 88 | return false, err 89 | } 90 | default: 91 | return false, fmt.Errorf("unsupported Redis data type: %s", keyType) 92 | } 93 | 94 | return true, nil 95 | } 96 | -------------------------------------------------------------------------------- /core/src/plugins/redis/update.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package redis 16 | 17 | import ( 18 | "context" 19 | "errors" 20 | "fmt" 21 | "strconv" 22 | 23 | "github.com/clidey/whodb/core/src/engine" 24 | ) 25 | 26 | func (p *RedisPlugin) UpdateStorageUnit(config *engine.PluginConfig, schema string, storageUnit string, values map[string]string, updatedColumns []string) (bool, error) { 27 | client, err := DB(config) 28 | if err != nil { 29 | return false, err 30 | } 31 | defer client.Close() 32 | 33 | ctx := context.Background() 34 | 35 | keyType, err := client.Type(ctx, storageUnit).Result() 36 | if err != nil { 37 | return false, err 38 | } 39 | 40 | switch keyType { 41 | case "string": 42 | if len(values) != 1 { 43 | return false, errors.New("invalid number of fields for a string key") 44 | } 45 | err := client.Set(ctx, storageUnit, values["value"], 0).Err() 46 | if err != nil { 47 | return false, err 48 | } 49 | case "hash": 50 | err := client.HSet(ctx, storageUnit, values["field"], values["value"]).Err() 51 | if err != nil { 52 | return false, err 53 | } 54 | case "list": 55 | indexInt, err := strconv.ParseInt(values["index"], 10, 64) 56 | if err != nil { 57 | return false, errors.New("unable to convert to int") 58 | } 59 | if err := client.LSet(ctx, storageUnit, indexInt, values["value"]).Err(); err != nil { 60 | return false, errors.New("unable to update the list item") 61 | } 62 | case "set": 63 | return false, errors.New("updating specific values in a set is not supported") 64 | default: 65 | return false, fmt.Errorf("unsupported Redis data type: %s", keyType) 66 | } 67 | 68 | return true, nil 69 | } 70 | -------------------------------------------------------------------------------- /core/src/plugins/sqlite3/add.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package sqlite3 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/engine" 22 | "strings" 23 | ) 24 | 25 | func (p *Sqlite3Plugin) GetCreateTableQuery(schema string, storageUnit string, columns []engine.Record) string { 26 | var columnDefs []string 27 | 28 | for _, column := range columns { 29 | parts := []string{column.Key} 30 | 31 | // Handle primary key with INTEGER type for auto-increment 32 | if primary, ok := column.Extra["primary"]; ok && primary == "true" { 33 | if strings.Contains(strings.ToLower(column.Value), "int") { 34 | parts = append(parts, "INTEGER PRIMARY KEY") 35 | } else { 36 | parts = append(parts, column.Value, "PRIMARY KEY") 37 | } 38 | } else { 39 | parts = append(parts, column.Value) 40 | 41 | // Add NOT NULL constraint if specified 42 | if nullable, ok := column.Extra["nullable"]; ok && nullable == "false" { 43 | parts = append(parts, "NOT NULL") 44 | } 45 | } 46 | 47 | columnDefs = append(columnDefs, strings.Join(parts, " ")) 48 | } 49 | 50 | return fmt.Sprintf("CREATE TABLE %s (%s)", storageUnit, strings.Join(columnDefs, ", ")) 51 | } 52 | -------------------------------------------------------------------------------- /core/src/plugins/sqlite3/db.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package sqlite3 18 | 19 | import ( 20 | "errors" 21 | "os" 22 | "path/filepath" 23 | "strings" 24 | 25 | "github.com/clidey/whodb/core/src/engine" 26 | "github.com/clidey/whodb/core/src/env" 27 | "gorm.io/driver/sqlite" 28 | "gorm.io/gorm" 29 | ) 30 | 31 | func getDefaultDirectory() string { 32 | directory := "/db/" 33 | if env.IsDevelopment { 34 | directory = "tmp/" 35 | } 36 | return directory 37 | } 38 | 39 | var errDoesNotExist = errors.New("unauthorized or the database doesn't exist") 40 | 41 | func (p *Sqlite3Plugin) DB(config *engine.PluginConfig) (*gorm.DB, error) { 42 | connectionInput, err := p.ParseConnectionConfig(config) 43 | if err != nil { 44 | return nil, err 45 | } 46 | database := connectionInput.Database 47 | fileNameDatabase := filepath.Join(getDefaultDirectory(), database) 48 | if !strings.HasPrefix(fileNameDatabase, getDefaultDirectory()) { 49 | return nil, errDoesNotExist 50 | } 51 | if _, err := os.Stat(fileNameDatabase); errors.Is(err, os.ErrNotExist) { 52 | return nil, errDoesNotExist 53 | } 54 | db, err := gorm.Open(sqlite.Open(fileNameDatabase), &gorm.Config{}) 55 | if err != nil { 56 | return nil, err 57 | } 58 | return db, nil 59 | } 60 | -------------------------------------------------------------------------------- /core/src/plugins/sqlite3/graph.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package sqlite3 16 | 17 | import ( 18 | "gorm.io/gorm" 19 | ) 20 | 21 | const graphQuery = ` 22 | WITH fk_constraints AS ( 23 | SELECT DISTINCT 24 | p.name AS table1, 25 | f."table" AS table2, 26 | 'OneToMany' AS relation 27 | FROM 28 | sqlite_master p 29 | JOIN 30 | (SELECT m.name AS "table", f."table" AS "table2" 31 | FROM sqlite_master m, pragma_foreign_key_list(m.name) f) f 32 | ON 33 | p.name = f."table2" 34 | ), 35 | pk_constraints AS ( 36 | SELECT DISTINCT 37 | p.name AS table1, 38 | m.name AS table2, 39 | 'OneToOne' AS relation 40 | FROM 41 | sqlite_master p, 42 | pragma_table_info(p.name) t 43 | JOIN 44 | sqlite_master m 45 | ON 46 | t.name = m.name 47 | WHERE 48 | t.pk = 1 49 | AND p.name != m.name 50 | ), 51 | unique_constraints AS ( 52 | SELECT DISTINCT 53 | p.name AS table1, 54 | i."table" AS table2, 55 | 'ManyToOne' AS relation 56 | FROM 57 | sqlite_master p 58 | JOIN 59 | (SELECT m.name AS "table", i."unique" AS "unique" 60 | FROM sqlite_master m, pragma_index_list(m.name) i) i 61 | ON 62 | p.name = i."table" 63 | WHERE 64 | i."unique" = 1 65 | AND p.name != i."table" 66 | ), 67 | many_to_many_constraints AS ( 68 | SELECT DISTINCT 69 | k1."table" AS table1, 70 | k2."table" AS table2, 71 | 'ManyToMany' AS relation 72 | FROM 73 | (SELECT f."table", f.seq 74 | FROM sqlite_master m, pragma_foreign_key_list(m.name) f) k1 75 | JOIN 76 | (SELECT f."table", f.seq 77 | FROM sqlite_master m, pragma_foreign_key_list(m.name) f) k2 78 | ON 79 | k1."table" = k2."table" 80 | WHERE 81 | k1.seq = 0 AND k2.seq = 1 82 | ) 83 | SELECT * FROM fk_constraints 84 | UNION ALL 85 | SELECT * FROM pk_constraints 86 | UNION ALL 87 | SELECT * FROM unique_constraints 88 | UNION ALL 89 | SELECT * FROM many_to_many_constraints; 90 | ` 91 | 92 | func (p *Sqlite3Plugin) GetGraphQueryDB(db *gorm.DB, schema string) *gorm.DB { 93 | return db.Raw(graphQuery) 94 | } 95 | -------------------------------------------------------------------------------- /core/src/plugins/sqlite3/utils.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package sqlite3 18 | 19 | import ( 20 | "strings" 21 | ) 22 | 23 | func (p *Sqlite3Plugin) ConvertStringValueDuringMap(value, columnType string) (interface{}, error) { 24 | return value, nil 25 | } 26 | 27 | func (p *Sqlite3Plugin) GetPrimaryKeyColQuery() string { 28 | return ` 29 | SELECT p.name AS pk_column 30 | FROM sqlite_master m, 31 | pragma_table_info(m.name) p 32 | WHERE m.type = 'table' 33 | AND m.name = ? 34 | AND m.name NOT LIKE 'sqlite_%' 35 | AND p.pk > 0 36 | ORDER BY m.name, p.pk;` 37 | } 38 | 39 | func (p *Sqlite3Plugin) GetColTypeQuery() string { 40 | return ` 41 | SELECT p.name AS column_name, 42 | p.type AS data_type 43 | FROM sqlite_master m, 44 | pragma_table_info(m.name) p 45 | WHERE m.type = 'table' 46 | AND m.name NOT LIKE 'sqlite_%'; 47 | ` 48 | } 49 | 50 | func (p *Sqlite3Plugin) EscapeSpecificIdentifier(identifier string) string { 51 | identifier = strings.Replace(identifier, "\"", "\"\"", -1) 52 | return identifier 53 | } 54 | -------------------------------------------------------------------------------- /core/src/router/file_server.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package router 16 | 17 | import ( 18 | "embed" 19 | "io" 20 | "io/fs" 21 | "net/http" 22 | "path" 23 | "strings" 24 | 25 | "github.com/clidey/whodb/core/src/log" 26 | "github.com/go-chi/chi/v5" 27 | ) 28 | 29 | func fileServer(r chi.Router, staticFiles embed.FS) { 30 | staticFS, err := fs.Sub(staticFiles, "build") 31 | if err != nil { 32 | log.Logger.Fatal("Failed to create sub filesystem:", err) 33 | } 34 | 35 | server := http.FileServer(http.FS(staticFS)) 36 | 37 | r.Handle("/*", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 38 | if hasExtension(r.URL.Path) { 39 | server.ServeHTTP(w, r) 40 | } else { 41 | file, err := staticFS.Open("index.html") 42 | if err != nil { 43 | http.Error(w, "index.html not found", http.StatusNotFound) 44 | log.Logger.Error("Failed to open index.html:", err) 45 | return 46 | } 47 | defer func(file fs.File) { 48 | err := file.Close() 49 | if err != nil { 50 | log.Logger.Error("Failed to close file:", err) 51 | } 52 | }(file) 53 | 54 | data, err := io.ReadAll(file) 55 | if err != nil { 56 | http.Error(w, "Failed to read index.html", http.StatusInternalServerError) 57 | log.Logger.Error("Failed to read index.html:", err) 58 | return 59 | } 60 | 61 | w.Header().Set("Content-Type", "text/html") 62 | _, err = w.Write(data) 63 | if err != nil { 64 | return 65 | } 66 | } 67 | })) 68 | } 69 | 70 | func hasExtension(pathFile string) bool { 71 | ext := strings.ToLower(path.Ext(pathFile)) 72 | return ext != "" 73 | } 74 | -------------------------------------------------------------------------------- /core/src/router/middleware.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package router 16 | 17 | import ( 18 | "context" 19 | "net/http" 20 | 21 | "github.com/clidey/whodb/core/src/common" 22 | ) 23 | 24 | func contextMiddleware(next http.Handler) http.Handler { 25 | return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 26 | ctx := context.WithValue(r.Context(), common.RouterKey_ResponseWriter, w) 27 | next.ServeHTTP(w, r.WithContext(ctx)) 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /core/src/router/playground.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package router 16 | 17 | import ( 18 | "net/http" 19 | 20 | "github.com/99designs/gqlgen/graphql/handler" 21 | "github.com/99designs/gqlgen/graphql/playground" 22 | "github.com/clidey/whodb/core/src/env" 23 | "github.com/go-chi/chi/v5" 24 | ) 25 | 26 | func setupPlaygroundHandler(router chi.Router, server *handler.Server) { 27 | var pathHandler http.HandlerFunc 28 | if env.IsDevelopment { 29 | pathHandler = playground.Handler("API Gateway", "/api/query") 30 | } 31 | router.HandleFunc("/api/query", func(w http.ResponseWriter, r *http.Request) { 32 | if r.Method == "POST" || r.Header.Get("Connection") == "upgrade" { 33 | server.ServeHTTP(w, r) 34 | } else if env.IsDevelopment { 35 | pathHandler.ServeHTTP(w, r) 36 | } 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /core/src/router/router.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | package router 16 | 17 | import ( 18 | "embed" 19 | "github.com/99designs/gqlgen/graphql/handler/extension" 20 | "time" 21 | 22 | "github.com/99designs/gqlgen/graphql" 23 | "github.com/99designs/gqlgen/graphql/handler" 24 | "github.com/99designs/gqlgen/graphql/handler/transport" 25 | "github.com/clidey/whodb/core/graph" 26 | "github.com/clidey/whodb/core/src/auth" 27 | "github.com/clidey/whodb/core/src/env" 28 | "github.com/go-chi/chi/v5" 29 | "github.com/go-chi/chi/v5/middleware" 30 | "github.com/go-chi/cors" 31 | ) 32 | 33 | type OAuthLoginUrl struct { 34 | Url string `json:"url"` 35 | } 36 | 37 | func NewGraphQLServer(es graphql.ExecutableSchema) *handler.Server { 38 | srv := handler.New(es) 39 | 40 | srv.AddTransport(&transport.Websocket{ 41 | KeepAlivePingInterval: 10 * time.Second, 42 | }) 43 | srv.AddTransport(&transport.Options{}) 44 | srv.AddTransport(&transport.GET{}) 45 | srv.AddTransport(&transport.POST{}) 46 | srv.AddTransport(&transport.MultipartForm{}) 47 | 48 | srv.Use(extension.FixedComplexityLimit(100)) 49 | 50 | if env.IsDevelopment { 51 | srv.Use(extension.Introspection{}) 52 | } 53 | 54 | return srv 55 | } 56 | 57 | func setupServer(router *chi.Mux, staticFiles embed.FS) { 58 | if !env.IsAPIGatewayEnabled { 59 | fileServer(router, staticFiles) 60 | } 61 | 62 | server := NewGraphQLServer(graph.NewExecutableSchema(graph.Config{Resolvers: &graph.Resolver{}})) 63 | server.AddTransport(&transport.Websocket{}) 64 | graph.SetupHTTPServer(router) 65 | setupPlaygroundHandler(router, server) 66 | } 67 | 68 | func setupMiddlewares(router *chi.Mux) { 69 | allowedOrigins := env.AllowedOrigins 70 | if len(allowedOrigins) == 0 { 71 | allowedOrigins = append(allowedOrigins, "https://*", "http://*") 72 | } 73 | 74 | router.Use( 75 | middleware.ThrottleBacklog(100, 50, time.Second*5), 76 | 77 | middleware.RequestID, 78 | middleware.RealIP, 79 | middleware.Logger, 80 | 81 | middleware.RedirectSlashes, 82 | middleware.Recoverer, 83 | middleware.Timeout(30*time.Second), 84 | 85 | cors.Handler(cors.Options{ 86 | AllowedOrigins: allowedOrigins, 87 | AllowedMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, 88 | AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"}, 89 | ExposedHeaders: []string{}, 90 | AllowCredentials: true, 91 | MaxAge: 300, 92 | }), 93 | 94 | contextMiddleware, 95 | auth.AuthMiddleware, 96 | ) 97 | } 98 | 99 | func InitializeRouter(staticFiles embed.FS) *chi.Mux { 100 | router := chi.NewRouter() 101 | 102 | setupMiddlewares(router) 103 | setupServer(router, staticFiles) 104 | 105 | return router 106 | } 107 | -------------------------------------------------------------------------------- /core/src/settings/settings.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package settings 18 | 19 | type Settings struct { 20 | MetricsEnabled bool `json:"metricsEnabled"` 21 | } 22 | 23 | type ISettingsField interface { 24 | Apply(*Settings) bool 25 | } 26 | 27 | type MetricsEnabledField bool 28 | 29 | var currentSettings = Settings{MetricsEnabled: false} 30 | 31 | func Get() Settings { 32 | return currentSettings 33 | } 34 | 35 | func (m MetricsEnabledField) Apply(s *Settings) bool { 36 | return false 37 | } 38 | 39 | // UpdateSettings todo: this isn't a good idea when your settings are larger. you'll end up pushing more data than is needed back and forth. refactor so it's more flexible 40 | func UpdateSettings(fields ...ISettingsField) bool { 41 | changed := false 42 | for _, field := range fields { 43 | if field.Apply(¤tSettings) { 44 | changed = true 45 | } 46 | } 47 | return changed 48 | } 49 | -------------------------------------------------------------------------------- /core/src/src.go: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package src 18 | 19 | import ( 20 | "fmt" 21 | "github.com/clidey/whodb/core/src/plugins/clickhouse" 22 | "github.com/clidey/whodb/core/src/plugins/elasticsearch" 23 | "github.com/clidey/whodb/core/src/plugins/mongodb" 24 | "github.com/clidey/whodb/core/src/plugins/mysql" 25 | "github.com/clidey/whodb/core/src/plugins/redis" 26 | "github.com/clidey/whodb/core/src/plugins/sqlite3" 27 | 28 | "github.com/clidey/whodb/core/src/engine" 29 | "github.com/clidey/whodb/core/src/env" 30 | "github.com/clidey/whodb/core/src/plugins/postgres" 31 | ) 32 | 33 | var MainEngine *engine.Engine 34 | 35 | func InitializeEngine() *engine.Engine { 36 | MainEngine = &engine.Engine{} 37 | MainEngine.RegistryPlugin(postgres.NewPostgresPlugin()) 38 | MainEngine.RegistryPlugin(mysql.NewMySQLPlugin()) 39 | MainEngine.RegistryPlugin(mysql.NewMyMariaDBPlugin()) 40 | MainEngine.RegistryPlugin(sqlite3.NewSqlite3Plugin()) 41 | MainEngine.RegistryPlugin(mongodb.NewMongoDBPlugin()) 42 | MainEngine.RegistryPlugin(redis.NewRedisPlugin()) 43 | MainEngine.RegistryPlugin(elasticsearch.NewElasticSearchPlugin()) 44 | MainEngine.RegistryPlugin(clickhouse.NewClickHousePlugin()) 45 | return MainEngine 46 | } 47 | 48 | func GetLoginProfiles() []env.DatabaseCredentials { 49 | profiles := []env.DatabaseCredentials{} 50 | for _, plugin := range MainEngine.Plugins { 51 | databaseProfiles := env.GetDefaultDatabaseCredentials(string(plugin.Type)) 52 | for _, databaseProfile := range databaseProfiles { 53 | databaseProfile.Type = string(plugin.Type) 54 | databaseProfile.IsProfile = true 55 | profiles = append(profiles, databaseProfile) 56 | } 57 | } 58 | return profiles 59 | } 60 | 61 | func GetLoginProfileId(index int, profile env.DatabaseCredentials) string { 62 | if len(profile.Alias) > 0 { 63 | return profile.Alias 64 | } 65 | return fmt.Sprintf("#%v - %v@%v [%v]", index+1, profile.Username, profile.Hostname, profile.Database) 66 | } 67 | 68 | func GetLoginCredentials(profile env.DatabaseCredentials) *engine.Credentials { 69 | advanced := []engine.Record{ 70 | { 71 | Key: "Port", 72 | Value: profile.Port, 73 | }, 74 | } 75 | 76 | for key, value := range profile.Config { 77 | advanced = append(advanced, engine.Record{ 78 | Key: key, 79 | Value: value, 80 | }) 81 | } 82 | 83 | return &engine.Credentials{ 84 | Type: profile.Type, 85 | Hostname: profile.Hostname, 86 | Username: profile.Username, 87 | Password: profile.Password, 88 | Database: profile.Database, 89 | Advanced: advanced, 90 | IsProfile: profile.IsProfile, 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /core/tools/tools.go: -------------------------------------------------------------------------------- 1 | // Copyright 2025 Clidey, Inc. 2 | // 3 | // Licensed under the Apache License, Version 2.0 (the "License"); 4 | // you may not use this file except in compliance with the License. 5 | // You may obtain a copy of the License at 6 | // 7 | // http://www.apache.org/licenses/LICENSE-2.0 8 | // 9 | // Unless required by applicable law or agreed to in writing, software 10 | // distributed under the License is distributed on an "AS IS" BASIS, 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | // See the License for the specific language governing permissions and 13 | // limitations under the License. 14 | 15 | //go:build tools 16 | 17 | package tools 18 | 19 | import ( 20 | _ "github.com/99designs/gqlgen" 21 | _ "github.com/99designs/gqlgen/graphql/introspection" 22 | ) 23 | -------------------------------------------------------------------------------- /dev/cleanup.sh: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Clidey, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | docker rm dev-e2e_sqlite3-1 16 | docker rm dev-e2e_postgres-1 17 | docker rm dev-e2e_mysql-1 18 | docker rm dev-e2e_mariadb-1 19 | docker rm dev-e2e_mongo-1 20 | docker rm dev-e2e_clickhouse-1 21 | 22 | docker volume rm dev_e2e_sqlite3 23 | docker volume rm dev_e2e_postgres 24 | docker volume rm dev_e2e_mysql 25 | docker volume rm dev_e2e_mariadb 26 | docker volume rm dev_e2e_mongo 27 | docker volume rm dev_e2e_clickhouse -------------------------------------------------------------------------------- /dev/docker-compose.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Clidey, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | version: "3.8" 16 | 17 | services: 18 | postgres: 19 | image: postgres 20 | environment: 21 | POSTGRES_USER: user 22 | POSTGRES_PASSWORD: password 23 | PGDATA: /data/postgres 24 | POSTGRES_DB: postgres 25 | volumes: 26 | - postgres:/data/postgres 27 | ports: 28 | - "5432:5432" 29 | networks: 30 | - db 31 | mysql: 32 | image: mysql 33 | environment: 34 | MYSQL_USER: user 35 | MYSQL_PASSWORD: password 36 | MYSQL_DATABASE: mysql 37 | MYSQL_ROOT_PASSWORD: password 38 | volumes: 39 | - mysql:/var/lib/mysql 40 | ports: 41 | - "3306:3306" 42 | networks: 43 | - db 44 | mariadb: 45 | image: mariadb 46 | environment: 47 | MARIADB_USER: user 48 | MARIADB_PASSWORD: password 49 | MARIADB_DATABASE: mariadb 50 | MARIADB_ROOT_PASSWORD: password 51 | volumes: 52 | - mariadb:/var/lib/mysql 53 | ports: 54 | - "3307:3306" 55 | networks: 56 | - db 57 | mongo: 58 | image: mongo 59 | environment: 60 | MONGO_INITDB_ROOT_USERNAME: user 61 | MONGO_INITDB_ROOT_PASSWORD: password 62 | volumes: 63 | - mongo:/data/db 64 | ports: 65 | - "27017:27017" 66 | networks: 67 | - db 68 | redis: 69 | image: bitnami/redis 70 | ports: 71 | - '6379:6379' 72 | environment: 73 | REDIS_PASSWORD: password 74 | volumes: 75 | - redis:/bitnami 76 | networks: 77 | - db 78 | elasticsearch: 79 | container_name: elasticsearch 80 | image: docker.elastic.co/elasticsearch/elasticsearch:8.14.1 81 | environment: 82 | - node.name=elasticsearch 83 | - discovery.type=single-node 84 | - bootstrap.memory_lock=true 85 | - ES_JAVA_OPTS=-Xms100m -Xmx100m 86 | - xpack.security.enabled=false 87 | - ELASTIC_PASSWORD=password 88 | ulimits: 89 | memlock: 90 | soft: -1 91 | hard: -1 92 | volumes: 93 | - elasticsearch:/usr/share/elasticsearch/data 94 | ports: 95 | - "9200:9200" 96 | redis_without_password: 97 | image: bitnami/redis 98 | ports: 99 | - '6380:6379' 100 | environment: 101 | ALLOW_EMPTY_PASSWORD: yes 102 | volumes: 103 | - redis:/bitnami 104 | networks: 105 | - db 106 | clickhouse: 107 | image: clickhouse/clickhouse-server 108 | ports: 109 | - '8123:8123' 110 | - '9000:9000' 111 | environment: 112 | CLICKHOUSE_USER: user 113 | CLICKHOUSE_PASSWORD: password 114 | CLICKHOUSE_DB: database 115 | CLICKHOUSE_ALWAYS_RUN_INITDB_SCRIPTS: true 116 | volumes: 117 | - clickhouse:/var/lib/clickhouse 118 | networks: 119 | - db 120 | networks: 121 | db: 122 | driver: bridge 123 | 124 | volumes: 125 | postgres: 126 | mysql: 127 | mariadb: 128 | mongo: 129 | redis: 130 | elasticsearch: 131 | clickhouse: -------------------------------------------------------------------------------- /docs/images/add-row.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/add-row.png -------------------------------------------------------------------------------- /docs/images/add-storage-unit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/add-storage-unit.png -------------------------------------------------------------------------------- /docs/images/album_table_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/album_table_view.png -------------------------------------------------------------------------------- /docs/images/change-login-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/change-login-profiles.png -------------------------------------------------------------------------------- /docs/images/chat-example-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/chat-example-1.png -------------------------------------------------------------------------------- /docs/images/chat-example-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/chat-example-2.png -------------------------------------------------------------------------------- /docs/images/chat-example-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/chat-example-3.png -------------------------------------------------------------------------------- /docs/images/chat-example-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/chat-example-4.png -------------------------------------------------------------------------------- /docs/images/chat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/chat.png -------------------------------------------------------------------------------- /docs/images/database-types.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/database-types.png -------------------------------------------------------------------------------- /docs/images/demo-thumbnail.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/demo-thumbnail.png -------------------------------------------------------------------------------- /docs/images/edit_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/edit_view.png -------------------------------------------------------------------------------- /docs/images/export-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/export-data.png -------------------------------------------------------------------------------- /docs/images/export_delete_btns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/export_delete_btns.png -------------------------------------------------------------------------------- /docs/images/global-theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/global-theme.png -------------------------------------------------------------------------------- /docs/images/graph-node.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/graph-node.png -------------------------------------------------------------------------------- /docs/images/graph-tools.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/graph-tools.png -------------------------------------------------------------------------------- /docs/images/graph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/graph.png -------------------------------------------------------------------------------- /docs/images/login-profiles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/login-profiles.png -------------------------------------------------------------------------------- /docs/images/login.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/login.png -------------------------------------------------------------------------------- /docs/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/logo.png -------------------------------------------------------------------------------- /docs/images/schema-change.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/schema-change.png -------------------------------------------------------------------------------- /docs/images/scratchpad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/scratchpad.png -------------------------------------------------------------------------------- /docs/images/scratchpad2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/scratchpad2.png -------------------------------------------------------------------------------- /docs/images/search-all.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/search-all.png -------------------------------------------------------------------------------- /docs/images/table-cell-inline-edit-action.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-cell-inline-edit-action.png -------------------------------------------------------------------------------- /docs/images/table-cell-inline-edit-input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-cell-inline-edit-input.png -------------------------------------------------------------------------------- /docs/images/table-cell-preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-cell-preview.png -------------------------------------------------------------------------------- /docs/images/table-data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-data.png -------------------------------------------------------------------------------- /docs/images/table-details.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-details.png -------------------------------------------------------------------------------- /docs/images/table-search-configuration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-search-configuration.png -------------------------------------------------------------------------------- /docs/images/table-smart-filter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table-smart-filter.png -------------------------------------------------------------------------------- /docs/images/table_view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/table_view.png -------------------------------------------------------------------------------- /docs/images/tables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/tables.png -------------------------------------------------------------------------------- /docs/images/trailer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/docs/images/trailer.png -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 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 | 25 | 26 | .parcel-cache 27 | dist/ -------------------------------------------------------------------------------- /frontend/.postcssrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": { 3 | "@tailwindcss/postcss": {} 4 | } 5 | } -------------------------------------------------------------------------------- /frontend/codegen.yml: -------------------------------------------------------------------------------- 1 | # Copyright 2025 Clidey, Inc. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | schema: http://localhost:8080/api/query 16 | documents: src/**/*.graphql 17 | generates: 18 | src/generated/graphql.tsx: 19 | plugins: 20 | - typescript 21 | - typescript-operations 22 | - typescript-react-apollo 23 | -------------------------------------------------------------------------------- /frontend/cypress.config.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | const { defineConfig } = require("cypress"); 18 | const { execSync } = require("child_process"); 19 | 20 | module.exports = defineConfig({ 21 | e2e: { 22 | setupNodeEvents(on, config) { 23 | on('task', { 24 | execCommand(command) { 25 | try { 26 | const result = execSync(command, { stdio: 'inherit' }); 27 | return { success: true, output: result.toString() }; 28 | } catch (error) { 29 | return { success: false, error: error.toString() }; 30 | } 31 | } 32 | }); 33 | }, 34 | }, 35 | }); 36 | -------------------------------------------------------------------------------- /frontend/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | // *********************************************************** 18 | // This example support/e2e.js is processed and 19 | // loaded automatically before your test files. 20 | // 21 | // This is a great place to put global configuration and 22 | // behavior that modifies Cypress. 23 | // 24 | // You can change the location of this file or turn off 25 | // automatically serving support files with the 26 | // 'supportFile' configuration option. 27 | // 28 | // You can read more here: 29 | // https://on.cypress.io/configuration 30 | // *********************************************************** 31 | 32 | // Import commands.js using ES2015 syntax: 33 | import './commands' -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "frontend", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@apollo/client": "^3.13.8", 7 | "@codemirror/lang-json": "^6.0.1", 8 | "@codemirror/lang-markdown": "^6.3.2", 9 | "@codemirror/lang-sql": "^6.8.0", 10 | "@codemirror/state": "^6.5.2", 11 | "@codemirror/theme-one-dark": "^6.1.2", 12 | "@codemirror/view": "^6.36.5", 13 | "@dagrejs/dagre": "^1.1.4", 14 | "@emotion/is-prop-valid": "^1.3.1", 15 | "@reduxjs/toolkit": "^2.7.0", 16 | "@types/react": "^18.3.12", 17 | "@types/react-dom": "^18.3.5", 18 | "@types/react-table": "^7.7.20", 19 | "classnames": "^2.5.1", 20 | "codemirror": "^6.0.1", 21 | "framer-motion": "^12.8.0", 22 | "graphql": "^16.9.0", 23 | "html-to-image": "^1.11.13", 24 | "lodash": "^4.17.21", 25 | "posthog-js": "^1.239.0", 26 | "react": "^18.3.1", 27 | "react-dom": "^18.3.1", 28 | "react-json-view": "^1.21.3", 29 | "react-markdown": "^10.1.0", 30 | "react-redux": "^9.1.2", 31 | "react-router-dom": "^7.5.1", 32 | "react-table": "^7.8.0", 33 | "react-window": "^1.8.10", 34 | "reactflow": "^11.11.4", 35 | "redux-persist": "^6.0.0", 36 | "remark-gfm": "^4.0.1", 37 | "tailwind-merge": "^3.2.0", 38 | "uuid": "^11.1.0" 39 | }, 40 | "scripts": { 41 | "start": "parcel public/index.html --open --port=3000", 42 | "build": "parcel build public/index.html --dist-dir build", 43 | "test": "pnpm cypress open", 44 | "test:headless": "npx cypress run --browser chrome", 45 | "generate": "npx graphql-codegen" 46 | }, 47 | "browserslist": { 48 | "production": [ 49 | ">0.2%", 50 | "not dead", 51 | "not op_mini all" 52 | ], 53 | "development": [ 54 | "last 1 chrome version", 55 | "last 1 firefox version", 56 | "last 1 safari version" 57 | ] 58 | }, 59 | "devDependencies": { 60 | "@graphql-codegen/cli": "^5.0.5", 61 | "@graphql-codegen/typescript": "^4.1.6", 62 | "@graphql-codegen/typescript-operations": "^4.6.0", 63 | "@graphql-codegen/typescript-react-apollo": "^4.3.2", 64 | "@parcel/packager-raw-url": "2.14.4", 65 | "@parcel/transformer-webmanifest": "2.14.4", 66 | "@tailwindcss/postcss": "^4.1.4", 67 | "@types/lodash": "^4.17.16", 68 | "@types/react-window": "^1.8.8", 69 | "@types/uuid": "^10.0.0", 70 | "autoprefixer": "^10.4.21", 71 | "buffer": "^6.0.3", 72 | "cypress": "^14.3.2", 73 | "parcel": "^2.14.4", 74 | "postcss": "^8.5.3", 75 | "process": "^0.11.10", 76 | "tailwindcss": "^4.1.4" 77 | }, 78 | "@parcel/resolver-default": { 79 | "packageExports": true 80 | }, 81 | "packageManager": "pnpm@9.14.4+sha512.c8180b3fbe4e4bca02c94234717896b5529740a6cbadf19fa78254270403ea2f27d4e1d46a08a0f56c89b63dc8ebfd3ee53326da720273794e6200fcf0d184ab" 82 | } 83 | -------------------------------------------------------------------------------- /frontend/public/images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/clidey/whodb/3bc03138f2a2f61ad795b3be8cf3a4bc3c0b3f67/frontend/public/images/logo.png -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 28 | 29 | 30 | Clidey WhoDB 31 | 32 | 33 | 34 |
35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /frontend/public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "Clidey WhoDB", 3 | "name": "WhoDB is the next-generation database explorer", 4 | "icons": [ 5 | { 6 | "src": "url:images/logo.png", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "url:images/logo.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "url:images/logo.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /frontend/public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /frontend/src/app.tsx: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import classNames from "classnames"; 18 | import { map } from "lodash"; 19 | import { Route, Routes } from "react-router-dom"; 20 | import { Notifications } from './components/notifications'; 21 | import { PrivateRoute, PublicRoutes, getRoutes } from './config/routes'; 22 | import { NavigateToDefault } from "./pages/chat/default-chat-route"; 23 | import { useAppSelector } from "./store/hooks"; 24 | import { useCallback, useEffect } from "react"; 25 | import { useUpdateSettingsMutation } from "./generated/graphql"; 26 | import { optInUser, optOutUser } from "./config/posthog"; 27 | 28 | export const App = () => { 29 | const [updateSettings, ] = useUpdateSettingsMutation(); 30 | const darkModeEnabled = useAppSelector(state => state.global.theme === "dark"); 31 | const metricsEnabled = useAppSelector(state => state.settings.metricsEnabled); 32 | 33 | useEffect(() => { 34 | if (metricsEnabled) { 35 | optInUser(); 36 | } else { 37 | optOutUser(); 38 | } 39 | }, [metricsEnabled]); 40 | 41 | const updateBackendWithSettings = useCallback(() => { 42 | updateSettings({ 43 | variables: { 44 | newSettings: { 45 | MetricsEnabled: String(metricsEnabled) 46 | } 47 | } 48 | }); 49 | }, [updateSettings, metricsEnabled]) 50 | 51 | useEffect(() => { 52 | updateBackendWithSettings() 53 | }, [updateBackendWithSettings]); 54 | 55 | return ( 56 |
59 | 60 | 61 | }> 62 | {map(getRoutes(), route => ( 63 | 64 | ))} 65 | } /> 66 | 67 | 68 | 69 |
70 | ); 71 | } 72 | -------------------------------------------------------------------------------- /frontend/src/components/breadcrumbs.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import classNames from "classnames"; 18 | import { FC, cloneElement } from "react"; 19 | import { useNavigate } from "react-router-dom"; 20 | import { twMerge } from "tailwind-merge"; 21 | import { IInternalRoute } from "../config/routes"; 22 | import { ClassNames } from "./classes"; 23 | import { Icons } from "./icons"; 24 | 25 | export type IBreadcrumbRoute = Omit; 26 | 27 | type IBreadcrumbProps = { 28 | routes: IBreadcrumbRoute[]; 29 | active?: IBreadcrumbRoute; 30 | } 31 | 32 | export const Breadcrumb: FC = ({ routes, active }) => { 33 | const handleNavigate = useNavigate(); 34 | return ( 35 | 67 | ) 68 | } 69 | -------------------------------------------------------------------------------- /frontend/src/components/button.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2025 Clidey, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | import classNames from "classnames"; 18 | import { motion } from "framer-motion"; 19 | import { FC, MouseEvent, ReactElement, ReactNode, cloneElement } from "react"; 20 | import { twMerge } from "tailwind-merge"; 21 | import { ClassNames } from "./classes"; 22 | 23 | export type IButtonProps = { 24 | className?: string; 25 | label: string; 26 | icon: ReactElement; 27 | iconClassName?: string; 28 | labelClassName?: string; 29 | onClick?: (e: MouseEvent, ...args: any) => void; 30 | disabled?: boolean; 31 | type?: "lg" | "sm"; 32 | testId?: string; 33 | } 34 | 35 | export const Button: FC = (props) => { 36 | return 40 |
41 | {props.label} 42 |
43 | {cloneElement(props.icon, { 44 | className: twMerge(classNames("w-4 h-4", props.iconClassName)), 45 | })} 46 |
47 | } 48 | 49 | export const AnimatedButton: FC = (props) => { 50 | return