├── .github ├── ISSUE_TEMPLATE │ ├── bug-report.yaml │ └── feature-request.yaml └── workflows │ ├── release.yml │ └── release_docker.yml ├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── api ├── chart.go ├── config.go ├── connector.go ├── other.go ├── response.go └── user.go ├── build.sh ├── cmd ├── root.go └── start.go ├── config.yaml ├── config └── config.go ├── connectors ├── connector.go ├── default.go ├── model.go ├── model │ ├── column.go │ ├── query.go │ └── table.go ├── mysql.go ├── oracle.go ├── postgresql.go └── sqlite.go ├── go.mod ├── go.sum ├── main.go ├── model ├── chart.go ├── config.go ├── log.go ├── page.go └── user.go ├── repository ├── chart.go ├── config.go ├── db.go ├── log.go └── user.go ├── router └── router.go ├── service ├── chart.go ├── config.go ├── connector.go ├── log.go ├── other.go └── user.go ├── static └── web.go └── web ├── .env.development ├── .env.release ├── README.md ├── env.d.ts ├── index.html ├── package-lock.json ├── package.json ├── public └── favicon.ico ├── src ├── App.vue ├── api │ ├── chart │ │ ├── index.ts │ │ └── type.ts │ ├── config │ │ ├── index.ts │ │ └── type.ts │ ├── connector │ │ ├── index.ts │ │ └── type.ts │ └── other │ │ └── index.ts ├── assets │ ├── font │ │ ├── iconfont.css │ │ ├── iconfont.js │ │ ├── iconfont.json │ │ └── iconfont.ttf │ └── img │ │ └── logo.png ├── common │ ├── enums │ │ └── index.ts │ └── utils │ │ └── index.ts ├── component.d.ts ├── components │ ├── console │ │ └── index.vue │ ├── form │ │ ├── index.vue │ │ └── type.ts │ ├── layout │ │ └── list │ │ │ ├── index.vue │ │ │ └── type.ts │ ├── navigation │ │ └── index.vue │ ├── resize │ │ └── index.vue │ └── table │ │ ├── index.vue │ │ └── type.ts ├── electron │ ├── main.js │ └── preload.js ├── locales │ ├── en-US.ts │ └── zh-CN.ts ├── main.ts ├── plugins │ ├── axios │ │ └── index.ts │ ├── i18n │ │ └── index.ts │ └── router │ │ └── index.ts ├── service │ ├── base.ts │ ├── database.ts │ ├── index.ts │ ├── model │ │ ├── column.ts │ │ └── sort.ts │ ├── mysql.ts │ └── sqlite.ts ├── stores │ ├── config.ts │ ├── counter.ts │ ├── database.ts │ ├── global.ts │ ├── index.ts │ └── type.ts ├── style │ ├── contextmenu │ │ └── index.scss │ ├── define.scss │ ├── element │ │ └── index.scss │ ├── global.scss │ ├── iconfont │ │ └── index.scss │ └── index.scss └── views │ ├── chart │ ├── form │ │ └── index.vue │ └── index.vue │ ├── connect │ ├── form │ │ └── index.vue │ ├── index.vue │ └── item │ │ └── index.vue │ ├── database │ ├── components │ │ ├── Help.vue │ │ ├── Icon.vue │ │ ├── TabQuery.vue │ │ ├── TabTable.vue │ │ └── Tree.vue │ └── index.vue │ ├── index.vue │ ├── layout │ └── Menu.vue │ └── setting │ ├── base │ └── index.vue │ └── index.vue ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.github/ISSUE_TEMPLATE/bug-report.yaml: -------------------------------------------------------------------------------- 1 | name: Bug Report 2 | description: bug template 3 | labels: bug 4 | body: 5 | - type: input 6 | id: version 7 | attributes: 8 | label: DBLand Version 9 | validations: 10 | required: true 11 | - type: textarea 12 | id: description 13 | attributes: 14 | label: Describe the bug 15 | description: | 16 | If the current behavior is a bug, please provide the steps to reproduce and if possible a minimal demo of the problem and error log. 17 | 清晰的描述遇到的问题,并建议附上错误截图及错误日志。 18 | validations: 19 | required: true 20 | - type: dropdown 21 | id: database 22 | attributes: 23 | label: Database 24 | description: What database system are you running? 25 | options: 26 | - PostgreSQL 27 | - MySQL/MariaDB 28 | - Oracle 29 | - SQLite -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature-request.yaml: -------------------------------------------------------------------------------- 1 | name: Feature Request 2 | description: Got an idea for a feature that Gitea doesn't have currently? Submit your idea here! 3 | labels: ["kind/proposal"] 4 | body: 5 | - type: textarea 6 | id: description 7 | attributes: 8 | label: Feature Description 9 | placeholder: | 10 | I think it would be great if Gitea had... 11 | validations: 12 | required: true 13 | - type: textarea 14 | id: screenshots 15 | attributes: 16 | label: Screenshots 17 | description: If you can, provide screenshots of an implementation on another site e.g. GitHub -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Releases 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | build: 8 | name: Build Go 9 | strategy: 10 | matrix: 11 | os: [ubuntu-latest, macos-latest, windows-latest] 12 | arch: [amd64, arm64] 13 | include: 14 | - os: ubuntu-latest 15 | goos: linux 16 | - os: macos-latest 17 | goos: darwin 18 | - os: windows-latest 19 | goos: windows 20 | env: 21 | NODE_VERSION: 14 22 | GO_VERSION: 1.21.1 23 | PROJECT_NAME: dbland 24 | runs-on: ${{ matrix.os }} 25 | permissions: 26 | contents: write 27 | 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v3 31 | with: 32 | fetch-depth: '0' 33 | 34 | - name: Setup Node.js 35 | uses: actions/setup-node@v2 36 | with: 37 | node-version: ${{ env.NODE_VERSION }} 38 | 39 | - name: Build Vue project 40 | run: | 41 | cd web 42 | npm install 43 | npm run build:release 44 | mkdir static 45 | 46 | - name: Prepare Static 47 | if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} 48 | run: | 49 | ls 50 | mv ./web/dist/* ./static 51 | 52 | - name: Prepare Static 53 | if: ${{ matrix.os == 'windows-latest' }} 54 | run: | 55 | ls 56 | xcopy .\web\dist\* .\static\ /E /I /Y 57 | 58 | - name: Setup Go 59 | uses: actions/setup-go@v2 60 | with: 61 | go-version: ${{ env.GO_VERSION }} 62 | 63 | - name: Build Go Project 64 | if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} 65 | run: go build -o ${{ env.PROJECT_NAME }} . 66 | 67 | - name: Build Go Project 68 | if: ${{ matrix.os == 'windows-latest' }} 69 | run: go build -o ${{ env.PROJECT_NAME }}.exe . 70 | 71 | - name: Compress files 72 | if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} 73 | run: tar -czvf ${{ env.PROJECT_NAME }}-${{ matrix.goos }}-${{ matrix.arch }}.tar.gz ${{ env.PROJECT_NAME }} config.yaml README.md LICENSE 74 | 75 | - name: Compress files 76 | if: ${{ matrix.os == 'windows-latest' }} 77 | run: | 78 | $sourceFiles = "${{ env.PROJECT_NAME }}.exe", "config.yaml", "README.md", "LICENSE" 79 | $zipFile = "dbland-${{ matrix.goos }}-${{ matrix.arch }}.zip" 80 | Compress-Archive -Path $sourceFiles -DestinationPath $zipFile 81 | Write-Host "Files compressed successfully!" 82 | 83 | - name: Create Release 84 | if: ${{ matrix.os == 'ubuntu-latest' || matrix.os == 'macos-latest' }} 85 | uses: ncipollo/release-action@v1 86 | with: 87 | artifacts: ${{ env.PROJECT_NAME }}-${{ matrix.goos }}-${{ matrix.arch }}.tar.gz 88 | allowUpdates: true 89 | 90 | - name: Create Release 91 | if: ${{ matrix.os == 'windows-latest' }} 92 | uses: ncipollo/release-action@v1 93 | with: 94 | artifacts: ${{ env.PROJECT_NAME }}-${{ matrix.goos }}-${{ matrix.arch }}.zip 95 | allowUpdates: true -------------------------------------------------------------------------------- /.github/workflows/release_docker.yml: -------------------------------------------------------------------------------- 1 | name: Releases Docker 2 | on: 3 | push: 4 | tags: 5 | - "v*" 6 | jobs: 7 | build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | with: 13 | fetch-depth: '0' 14 | 15 | - name: Set up QEMU 16 | uses: docker/setup-qemu-action@v2 17 | 18 | - name: Set up Docker Buildx 19 | uses: docker/setup-buildx-action@v2 20 | 21 | - name: Docker meta 22 | id: meta 23 | uses: docker/metadata-action@v4 24 | with: 25 | images: m9d2/dbland 26 | 27 | - name: Login to DockerHub 28 | uses: docker/login-action@v2 29 | with: 30 | username: m9d2 31 | password: ${{ secrets.DOCKERHUB_TOKEN }} 32 | 33 | - name: Build and push 34 | id: docker_build 35 | uses: docker/build-push-action@v4 36 | with: 37 | context: . 38 | push: true 39 | tags: ${{ steps.meta.outputs.tags }} 40 | labels: ${{ steps.meta.outputs.labels }} 41 | platforms: linux/amd64,linux/arm64 -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Build and Release Folders 2 | bin-debug/ 3 | bin-release/ 4 | [Oo]bj/ 5 | [Bb]in/ 6 | 7 | # Other files and folders 8 | .settings/ 9 | 10 | # Executables 11 | *.swf 12 | *.air 13 | *.ipa 14 | *.apk 15 | .idea/ 16 | .vscode/ 17 | 18 | 19 | # Project files, i.e. `.project`, `.actionScriptProperties` and `.flexProperties` 20 | # should NOT be excluded as they contain compiler settings and other important 21 | # information for Eclipse / Flash Builder. 22 | dbland.db 23 | web/node_modules/ 24 | web/dist 25 | .DS_Store 26 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 AS frontend 2 | WORKDIR /app/frontend 3 | COPY web . 4 | RUN npm install 5 | RUN npm run build:release 6 | 7 | FROM alpine:3.18 as backend 8 | ENV GO_VERSION 1.21.1 9 | ENV CGO_ENABLED=1 10 | ENV GOPROXY=https://goproxy.cn 11 | WORKDIR /app/backend 12 | COPY ./ ./ 13 | COPY --from=frontend /app/frontend/dist ./static 14 | RUN set -eux; \ 15 | apk add --no-cache bash ca-certificates curl gcc libc-dev; \ 16 | OS=$(uname -s | tr '[:upper:]' '[:lower:]') && \ 17 | ARCH=$(uname -m | tr '[:upper:]' '[:lower:]') && \ 18 | if [ "$ARCH" = "x86_64" ]; then \ 19 | TARGET="go$GO_VERSION.linux-amd64.tar.gz"; \ 20 | elif [ "$ARCH" = "aarch64" ]; then \ 21 | TARGET="go$GO_VERSION.linux-arm64.tar.gz"; \ 22 | else \ 23 | exit 1; \ 24 | fi; \ 25 | wget -O go.tgz https://go.dev/dl/$TARGET; \ 26 | tar -C /usr/local -xzf go.tgz; \ 27 | export PATH="/usr/local/go/bin:$PATH"; \ 28 | chmod a+x build.sh; \ 29 | bash build.sh docker 30 | 31 | FROM alpine:3.18 32 | WORKDIR /app 33 | COPY --from=backend /app/backend/dbland . 34 | COPY config.yaml README.md LICENSE ./ 35 | EXPOSE 2023 36 | CMD ./dbland start -p 2023 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |Web数据库连接工具
3 | 11 | 12 | 13 | 14 | ## 简介 15 | Go语言实现的Web版本数据库连接工具,内存占用少,启动快,支持MySQL,Oracle,SQLite等 16 | 17 | ## UI界面 18 | 19 |  20 | 21 | ## 快速开始 22 | ### 使用Docker安装 23 | 24 | ``` 25 | docker run -d --restart=always -p 2023:2023 --name dbland m9d2/dbland:latest 26 | ``` 27 | UI: http://127.0.0.1:2023/ui 28 | -------------------------------------------------------------------------------- /api/chart.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "dbland/service" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type ChartHandler struct { 10 | service service.ChartService 11 | } 12 | 13 | func (s ChartHandler) InitRouter(g *gin.RouterGroup) { 14 | g.POST("chart", s.add) 15 | g.PUT("chart", s.update) 16 | g.GET("chart", s.list) 17 | g.DELETE("chart/:id", s.delete) 18 | } 19 | 20 | func (s ChartHandler) list(c *gin.Context) { 21 | data := s.service.List(c) 22 | JSON(c, data) 23 | } 24 | 25 | func (s ChartHandler) add(c *gin.Context) { 26 | err := s.service.Save(c) 27 | JSON(c, err) 28 | } 29 | 30 | func (s ChartHandler) update(c *gin.Context) { 31 | err := s.service.Update(c) 32 | JSON(c, err) 33 | } 34 | 35 | func (s ChartHandler) delete(c *gin.Context) { 36 | err := s.service.Delete(c) 37 | JSON(c, err) 38 | } 39 | -------------------------------------------------------------------------------- /api/config.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "dbland/service" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | type ConnectionConfigHandler struct { 9 | connectionConfigService service.ConnectionConfigService 10 | } 11 | 12 | func (s ConnectionConfigHandler) InitRouter(g *gin.RouterGroup) { 13 | g.POST("config", s.add) 14 | g.PUT("config", s.update) 15 | g.GET("config", s.list) 16 | g.DELETE("config/:id", s.delete) 17 | } 18 | 19 | func (s ConnectionConfigHandler) list(c *gin.Context) { 20 | data := s.connectionConfigService.List(c) 21 | JSON(c, data) 22 | } 23 | 24 | func (s ConnectionConfigHandler) add(c *gin.Context) { 25 | err := s.connectionConfigService.Save(c) 26 | JSON(c, err) 27 | } 28 | 29 | func (s ConnectionConfigHandler) update(c *gin.Context) { 30 | err := s.connectionConfigService.Update(c) 31 | JSON(c, err) 32 | } 33 | 34 | func (s ConnectionConfigHandler) delete(c *gin.Context) { 35 | err := s.connectionConfigService.Delete(c) 36 | JSON(c, err) 37 | } 38 | -------------------------------------------------------------------------------- /api/connector.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "dbland/service" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | type ConnectorHandler struct { 9 | service service.ConnectorService 10 | } 11 | 12 | func (h ConnectorHandler) InitRouter(g *gin.RouterGroup) { 13 | g.POST("ping", h.ping) 14 | g.POST("databases", h.showDatabases) 15 | g.POST("tables", h.showTables) 16 | g.POST("columns", h.column) 17 | g.POST("ddl", h.ddl) 18 | g.POST("query", h.query) 19 | g.POST("execute", h.execute) 20 | } 21 | 22 | func (h ConnectorHandler) ping(c *gin.Context) { 23 | err := h.service.Ping(c) 24 | JSON(c, err) 25 | } 26 | 27 | func (h ConnectorHandler) showDatabases(c *gin.Context) { 28 | data, err := h.service.ShowDatabases(c) 29 | if err != nil { 30 | JSON(c, err) 31 | } else { 32 | JSON(c, data) 33 | } 34 | } 35 | 36 | func (h ConnectorHandler) showTables(c *gin.Context) { 37 | data, err := h.service.ShowTables(c) 38 | if err != nil { 39 | JSON(c, err) 40 | } else { 41 | JSON(c, data) 42 | } 43 | } 44 | 45 | func (h ConnectorHandler) column(c *gin.Context) { 46 | data, err := h.service.Column(c) 47 | if err != nil { 48 | JSON(c, err) 49 | } else { 50 | JSON(c, data) 51 | } 52 | } 53 | 54 | func (h ConnectorHandler) ddl(c *gin.Context) { 55 | data, err := h.service.Ddl(c) 56 | if err != nil { 57 | JSON(c, err) 58 | } else { 59 | JSON(c, data) 60 | } 61 | } 62 | 63 | func (h ConnectorHandler) query(c *gin.Context) { 64 | data, err := h.service.Query(c) 65 | if err != nil { 66 | JSON(c, err) 67 | } else { 68 | JSON(c, data) 69 | } 70 | } 71 | 72 | func (h ConnectorHandler) execute(c *gin.Context) { 73 | data, err := h.service.Execute(c) 74 | if err != nil { 75 | JSON(c, err) 76 | } else { 77 | JSON(c, data) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /api/other.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "dbland/service" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | type OtherHandler struct { 9 | otherService service.OtherService 10 | } 11 | 12 | func (h OtherHandler) InitRouter(g *gin.RouterGroup) { 13 | g.GET("readme", h.loadReadme) 14 | } 15 | 16 | func (h OtherHandler) loadReadme(c *gin.Context) { 17 | content, err := h.otherService.LoadReadme() 18 | if err != nil { 19 | JSON(c, err) 20 | } else { 21 | JSON(c, content) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /api/response.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "net/http" 5 | 6 | "github.com/gin-gonic/gin" 7 | ) 8 | 9 | type Result struct { 10 | Code int `json:"code"` 11 | Message string `json:"message"` 12 | Data interface{} `json:"data"` 13 | } 14 | 15 | func Ok(c *gin.Context, data interface{}) { 16 | res := &Result{ 17 | Code: http.StatusOK, 18 | Message: "ok", 19 | Data: data, 20 | } 21 | c.JSON(http.StatusOK, res) 22 | } 23 | 24 | func Fail(c *gin.Context, err error) { 25 | res := &Result{ 26 | Code: http.StatusBadRequest, 27 | Message: err.Error(), 28 | } 29 | c.JSON(http.StatusOK, res) 30 | } 31 | 32 | func Unauthorized(c *gin.Context) { 33 | res := &Result{ 34 | Code: http.StatusUnauthorized, 35 | Message: "Unauthorized", 36 | } 37 | c.JSON(http.StatusOK, res) 38 | } 39 | 40 | func JSON(c *gin.Context, data interface{}) { 41 | if err, ok := data.(error); ok { 42 | Fail(c, err) 43 | } else { 44 | Ok(c, data) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /api/user.go: -------------------------------------------------------------------------------- 1 | package api 2 | 3 | import ( 4 | "dbland/service" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | type UserHandler struct { 9 | userService service.UserService 10 | } 11 | 12 | func (h UserHandler) InitRouter(g *gin.RouterGroup) { 13 | g.POST("login", h.login) 14 | g.POST("user", h.add) 15 | g.GET("logout", h.logout) 16 | } 17 | 18 | // login user login 19 | func (h UserHandler) login(c *gin.Context) { 20 | err := h.userService.Login(c) 21 | JSON(c, err) 22 | } 23 | 24 | // login add user 25 | func (h UserHandler) add(c *gin.Context) { 26 | err := h.userService.Add(c) 27 | JSON(c, err) 28 | } 29 | 30 | func (h UserHandler) logout(c *gin.Context) { 31 | h.userService.Logout(c) 32 | JSON(c, nil) 33 | } 34 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | BuildDocker() { 4 | go build -o dbland . 5 | } 6 | 7 | BuildExecutableFile() { 8 | ehco "build file" 9 | } 10 | 11 | if [ "$1" = "docker" ]; then 12 | BuildDocker 13 | elif [ "$1" = "file" ]; then 14 | BuildExecutableFile 15 | else 16 | echo -e "Parameter error" 17 | fi 18 | -------------------------------------------------------------------------------- /cmd/root.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/spf13/cobra" 8 | ) 9 | 10 | var ( 11 | RootCmd = &cobra.Command{ 12 | Use: "dbland", 13 | Short: "dbland", 14 | Long: "A web database tool", 15 | } 16 | ) 17 | 18 | func Execute() { 19 | err := RootCmd.Execute() 20 | if err != nil { 21 | _, err := fmt.Fprintln(os.Stderr, err) 22 | if err != nil { 23 | return 24 | } 25 | os.Exit(1) 26 | } 27 | } 28 | 29 | func init() { 30 | RootCmd.PersistentFlags().IntVarP(&port, "port", "p", 8080, "Port to listen on") 31 | } 32 | -------------------------------------------------------------------------------- /cmd/start.go: -------------------------------------------------------------------------------- 1 | package cmd 2 | 3 | import ( 4 | "dbland/config" 5 | "dbland/repository" 6 | "dbland/router" 7 | "log/slog" 8 | "strconv" 9 | 10 | "github.com/spf13/cobra" 11 | ) 12 | 13 | var ( 14 | port = 0 15 | 16 | startCmd = &cobra.Command{ 17 | Use: "start", 18 | Short: "Start the server", 19 | Long: "Start the running web server gracefully.", 20 | Run: func(cmd *cobra.Command, args []string) { 21 | start() 22 | }, 23 | } 24 | ) 25 | 26 | func start() { 27 | slog.Info("Initialize config") 28 | config.InitConfig() 29 | 30 | slog.Info("Initialize router") 31 | r := router.InitRouter() 32 | 33 | slog.Info("Initialize db") 34 | repository.Initialize() 35 | 36 | slog.Info("Start server", "port", port) 37 | var err error 38 | go func() { 39 | err = r.Run(":" + strconv.Itoa(port)) 40 | if err != nil { 41 | slog.Error(err.Error()) 42 | return 43 | } else { 44 | slog.Info("Service started successfully!") 45 | } 46 | }() 47 | select {} 48 | } 49 | 50 | func init() { 51 | RootCmd.AddCommand(startCmd) 52 | } 53 | -------------------------------------------------------------------------------- /config.yaml: -------------------------------------------------------------------------------- 1 | # default sqlite database file is dbland.db 2 | #sqlite: 3 | # data-path: data 4 | 5 | init-schema: true 6 | 7 | server: 8 | environment: release 9 | path: v1 10 | -------------------------------------------------------------------------------- /config/config.go: -------------------------------------------------------------------------------- 1 | package config 2 | 3 | import ( 4 | "github.com/gin-gonic/gin" 5 | "gopkg.in/yaml.v3" 6 | "log" 7 | "os" 8 | ) 9 | 10 | var ( 11 | Conf = Config{} 12 | dbFile = "dbland.db" 13 | dataPath = "./data" 14 | serverPath = "v1" 15 | ) 16 | 17 | type Config struct { 18 | Sqlite struct { 19 | DataPath string `yaml:"data-path"` 20 | } 21 | 22 | Server struct { 23 | Environment string `yaml:"environment"` 24 | Path string `yaml:"path"` 25 | } 26 | InitSchema bool `yaml:"init-schema"` 27 | } 28 | 29 | func InitConfig() { 30 | file, _ := os.ReadFile("./config.yaml") 31 | 32 | err := yaml.Unmarshal(file, &Conf) 33 | if err != nil { 34 | log.Fatal(err.Error()) 35 | } 36 | fillDefaultValues() 37 | } 38 | 39 | func fillDefaultValues() { 40 | if Conf.Sqlite.DataPath == "" { 41 | Conf.Sqlite.DataPath = dataPath 42 | } 43 | _ = os.MkdirAll(Conf.Sqlite.DataPath, os.ModePerm) 44 | Conf.Sqlite.DataPath += "/" + dbFile 45 | 46 | if Conf.Server.Environment == "" { 47 | Conf.Server.Environment = gin.ReleaseMode 48 | } 49 | if Conf.Server.Path == "" { 50 | Conf.Server.Path = serverPath 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /connectors/connector.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "dbland/model" 5 | "dbland/repository" 6 | "fmt" 7 | "github.com/jmoiron/sqlx" 8 | ) 9 | 10 | const ( 11 | Mysql = "mysql" 12 | Oracle = "oracle" 13 | SQLite = "sqlite" 14 | PostgreSQL = "postgresql" 15 | ) 16 | 17 | type Connector interface { 18 | Ping(config *model.Config) error 19 | Connect(config *model.Config) (*sqlx.DB, error) 20 | ShowDatabases(db *sqlx.DB) ([]string, error) 21 | Use(db *sqlx.DB, database string) error 22 | ShowTables(db *sqlx.DB, database string) (*[]Table, error) 23 | // Column params order - 24 | // mysql: schema, table 25 | // 26 | // sqlite: table 27 | Column(db *sqlx.DB, params ...string) (*[]Column, error) 28 | Query(db *sqlx.DB, sqlStr string) (*Query, error) 29 | Count(db *sqlx.DB, sqlStr string) (int, error) 30 | Ddl(db *sqlx.DB, table string) (string, error) 31 | Execute(db *sqlx.DB, sqlStr string) (int, error) 32 | } 33 | 34 | type ConnectorFactory struct { 35 | connectionConfigRepository repository.ConnectionConfigRepository 36 | mysqlConnector MysqlConnector 37 | oracleConnector OracleConnector 38 | sqLiteConnector SQLiteConnector 39 | postgreSQLConnector PostgreSQLConnector 40 | } 41 | 42 | func (c ConnectorFactory) GetInstance(dbType string) (Connector, error) { 43 | var connector Connector 44 | switch dbType { 45 | case Mysql: 46 | connector = c.mysqlConnector 47 | case Oracle: 48 | connector = c.oracleConnector 49 | case SQLite: 50 | connector = c.sqLiteConnector 51 | case PostgreSQL: 52 | connector = c.postgreSQLConnector 53 | 54 | default: 55 | err := fmt.Errorf("database not support") 56 | return nil, err 57 | } 58 | return connector, nil 59 | } 60 | -------------------------------------------------------------------------------- /connectors/default.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "database/sql" 5 | _ "github.com/go-sql-driver/mysql" 6 | "github.com/jmoiron/sqlx" 7 | "log/slog" 8 | "strconv" 9 | ) 10 | 11 | type DefaultConnector struct { 12 | } 13 | 14 | func (c DefaultConnector) ShowDatabases(db *sqlx.DB) ([]string, error) { 15 | var result []string 16 | sqlStr := "show databases" 17 | rows, err := db.Queryx(sqlStr) 18 | defer func() { 19 | _ = rows.Close() 20 | _ = db.Close() 21 | }() 22 | for rows.Next() { 23 | var row string 24 | err = rows.Scan(&row) 25 | if err != nil { 26 | return nil, err 27 | } 28 | result = append(result, row) 29 | } 30 | return result, err 31 | } 32 | 33 | func (c DefaultConnector) ShowTables(db *sqlx.DB, database string) (*[]Table, error) { 34 | var tables []Table 35 | sqlStr := `SELECT TABLE_NAME, TABLE_ROWS, TABLE_COLLATION, CREATE_TIME, UPDATE_TIME 36 | FROM INFORMATION_SCHEMA.TABLES 37 | WHERE TABLE_SCHEMA = ?` 38 | 39 | rows, err := db.Queryx(sqlStr, database) 40 | if err != nil { 41 | return nil, err 42 | } 43 | 44 | defer func() { 45 | _ = rows.Close() 46 | _ = db.Close() 47 | }() 48 | 49 | for rows.Next() { 50 | var table Table 51 | var tableName sql.NullString 52 | var tableRows sql.NullString 53 | var tableCollation sql.NullString 54 | var createTime sql.NullString 55 | var updateTime sql.NullString 56 | var err = rows.Scan(&tableName, &tableRows, &tableCollation, &createTime, &updateTime) 57 | if err != nil { 58 | return nil, err 59 | } 60 | 61 | if tableName.Valid { 62 | table.Name = tableName.String 63 | } 64 | if tableRows.Valid { 65 | table.Rows = tableRows.String 66 | } 67 | if tableCollation.Valid { 68 | table.Collation = tableCollation.String 69 | } 70 | if createTime.Valid { 71 | table.CreateTime = createTime.String 72 | } 73 | if updateTime.Valid { 74 | table.UpdateTime = updateTime.String 75 | } 76 | 77 | tables = append(tables, table) 78 | } 79 | return &tables, err 80 | } 81 | 82 | func (c DefaultConnector) Column(db *sqlx.DB, params ...string) (*[]Column, error) { 83 | var columns []Column 84 | schema := params[0] 85 | table := params[1] 86 | sqlStr := `SELECT 87 | COLUMN_NAME as field, 88 | COLUMN_TYPE as type, 89 | CHARACTER_MAXIMUM_LENGTH AS 'length', 90 | IS_NULLABLE AS 'nullable', 91 | COLUMN_KEY AS 'key', 92 | COLUMN_COMMENT as comment, 93 | COLUMN_DEFAULT AS 'default' 94 | FROM INFORMATION_SCHEMA.COLUMNS 95 | WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?` 96 | 97 | rows, err := db.Queryx(sqlStr, schema, table) 98 | if err != nil { 99 | return nil, err 100 | } 101 | defer func() { 102 | _ = rows.Close() 103 | _ = db.Close() 104 | }() 105 | 106 | for rows.Next() { 107 | var column Column 108 | var columnName sql.NullString 109 | var columnType sql.NullString 110 | var columnComment sql.NullString 111 | var length sql.NullString 112 | var nullable sql.NullString 113 | var columnKey sql.NullString 114 | var columnDefault sql.NullString 115 | err = rows.Scan(&columnName, &columnType, &length, &nullable, &columnKey, &columnComment, &columnDefault) 116 | if err != nil { 117 | return nil, err 118 | } 119 | 120 | if columnName.Valid { 121 | column.Field = columnName.String 122 | } 123 | if columnType.Valid { 124 | column.Type = columnType.String 125 | } 126 | if columnComment.Valid { 127 | column.Comment = columnComment.String 128 | } 129 | if length.Valid { 130 | column.Length = length.String 131 | } 132 | if nullable.Valid { 133 | if nullable.String == "YES" { 134 | column.Nullable = true 135 | } else { 136 | column.Nullable = false 137 | } 138 | } 139 | if columnKey.Valid { 140 | if columnKey.String == "PRI" { 141 | column.Key = Primary 142 | } else { 143 | column.Key = columnKey.String 144 | } 145 | } 146 | if columnDefault.Valid { 147 | column.Default = columnDefault.String 148 | } 149 | 150 | columns = append(columns, column) 151 | } 152 | return &columns, err 153 | } 154 | 155 | func (c DefaultConnector) Query(db *sqlx.DB, sqlStr string) (*Query, error) { 156 | rows, err := db.Queryx(sqlStr) 157 | if err != nil { 158 | return nil, err 159 | } 160 | defer func() { 161 | _ = rows.Close() 162 | }() 163 | 164 | columnNames, err := rows.Columns() 165 | if err != nil { 166 | return nil, err 167 | } 168 | 169 | columnTypes, err := rows.ColumnTypes() 170 | 171 | var result []map[string]interface{} 172 | values := make([][]byte, len(columnNames)) 173 | scans := make([]interface{}, len(columnNames)) 174 | columns := make([]Column, len(columnNames)) 175 | 176 | for i, _ := range columns { 177 | scans[i] = &values[i] 178 | columnType, err := convertColumnType(columnTypes[i].DatabaseTypeName()) 179 | if err != nil { 180 | return nil, err 181 | } 182 | columns[i] = Column{ 183 | Field: columnNames[i], 184 | Type: columnType, 185 | } 186 | } 187 | 188 | for rows.Next() { 189 | err = rows.Scan(scans...) 190 | if err != nil { 191 | return nil, err 192 | } 193 | row := make(map[string]interface{}) 194 | for i, value := range values { 195 | columnType := columnTypes[i] 196 | typeName := columnType.DatabaseTypeName() 197 | if value == nil { 198 | row[columnType.Name()] = nil 199 | continue 200 | } 201 | 202 | cColumnType, err := convertColumnType(columnTypes[i].DatabaseTypeName()) 203 | if err != nil { 204 | return nil, err 205 | } 206 | if cColumnType == Text { 207 | row[columnType.Name()] = string(value) 208 | } else if cColumnType == Number { 209 | val, err := strconv.ParseFloat(string(value), 64) 210 | if err != nil { 211 | return nil, err 212 | } 213 | row[columnType.Name()] = val 214 | } else { 215 | slog.Error("dont support type", "type name", typeName) 216 | break 217 | } 218 | } 219 | result = append(result, row) 220 | 221 | } 222 | 223 | query := &Query{ 224 | Rows: result, 225 | Columns: columns, 226 | Total: len(result), 227 | } 228 | 229 | return query, err 230 | } 231 | 232 | func (c DefaultConnector) Count(db *sqlx.DB, sqlStr string) (int, error) { 233 | rows, err := db.Queryx(sqlStr) 234 | if err != nil { 235 | return 0, err 236 | } 237 | defer func() { 238 | _ = rows.Close() 239 | _ = db.Close() 240 | }() 241 | for rows.Next() { 242 | var total int 243 | err = rows.Scan(&total) 244 | if err != nil { 245 | return 0, err 246 | } 247 | return total, err 248 | } 249 | return 0, err 250 | } 251 | 252 | func (c DefaultConnector) Ddl(db *sqlx.DB, table string) (string, error) { 253 | sqlStr := "SHOW CREATE TABLE " + table 254 | row := db.QueryRowx(sqlStr) 255 | var tableName string 256 | var ddl string 257 | err := row.Scan(&tableName, &ddl) 258 | return ddl, err 259 | } 260 | 261 | func (c DefaultConnector) Execute(db *sqlx.DB, sqlStr string) (int, error) { 262 | result, err := db.Exec(sqlStr) 263 | if err != nil { 264 | return 0, err 265 | } 266 | rows, err := result.RowsAffected() 267 | if err != nil { 268 | return 0, err 269 | } 270 | return int(rows), err 271 | } 272 | 273 | func (c DefaultConnector) getColumnType(key string, columnTypes []*sql.ColumnType) *sql.ColumnType { 274 | for _, columnType := range columnTypes { 275 | if columnType.Name() == key { 276 | return columnType 277 | } 278 | } 279 | return nil 280 | } 281 | 282 | func convertColumnType(columnType string) (string, error) { 283 | switch columnType { 284 | case "TINYINT", "SMALLINT", "INT", "INTEGER", "BIGINT", "UNSIGNED BIGINT", "UNSIGNED TINYINT": 285 | return Number, nil 286 | case "TEXT", "VARCHAR", "DATETIME", "BIT", "DATE", "CHAR", "TIMESTAMP", "JSON": 287 | return Text, nil 288 | default: 289 | return Text, nil 290 | } 291 | } 292 | -------------------------------------------------------------------------------- /connectors/model.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | const ( 4 | Text = "text" 5 | Number = "number" 6 | Primary = "primary" 7 | ) 8 | 9 | type Column struct { 10 | Field string `db:"field" json:"field"` 11 | Type string `db:"type" json:"type"` 12 | Length string `db:"length" json:"length"` 13 | Nullable bool `db:"nullable" json:"nullable"` 14 | Key string `db:"key" json:"key"` 15 | Default string `db:"default" json:"default"` 16 | Comment string `db:"comment" json:"comment"` 17 | } 18 | 19 | type Query struct { 20 | Columns []Column `json:"columns"` 21 | TableName string `json:"table_name"` 22 | Rows []map[string]any `json:"rows"` 23 | Total int `json:"total"` 24 | ElapsedTime float64 `json:"elapsed_time"` 25 | TotalPage int `json:"total_page"` 26 | } 27 | 28 | type Table struct { 29 | Name string `json:"name,omitempty"` 30 | Rows string `json:"rows,omitempty"` 31 | Collation string `json:"collation,omitempty"` 32 | CreateTime string `json:"create_time,omitempty"` 33 | UpdateTime string `json:"update_time,omitempty"` 34 | } 35 | -------------------------------------------------------------------------------- /connectors/model/column.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Column struct { 4 | ColumnName string `db:"COLUMN_NAME" json:"column_name"` 5 | ColumnType string `db:"COLUMN_TYPE" json:"column_type"` 6 | ColumnComment string `db:"COLUMN_COMMENT" json:"column_comment"` 7 | } 8 | -------------------------------------------------------------------------------- /connectors/model/query.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Query struct { 4 | Columns []string `json:"columns"` 5 | TableName string `json:"table_name"` 6 | Rows []map[string]any `json:"rows"` 7 | Total int `json:"total"` 8 | } 9 | -------------------------------------------------------------------------------- /connectors/model/table.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Table struct { 4 | Name string `json:"name,omitempty"` 5 | Rows string `json:"rows,omitempty"` 6 | Collation string `json:"collation,omitempty"` 7 | CreateTime string `json:"create_time,omitempty"` 8 | UpdateTime string `json:"update_time,omitempty"` 9 | } 10 | -------------------------------------------------------------------------------- /connectors/mysql.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "dbland/model" 5 | "fmt" 6 | _ "github.com/go-sql-driver/mysql" 7 | "github.com/jmoiron/sqlx" 8 | ) 9 | 10 | type MysqlConnector struct { 11 | DefaultConnector 12 | } 13 | 14 | func (c MysqlConnector) Ping(config *model.Config) error { 15 | db, err := sqlx.Open("mysql", c.dsn(config)) 16 | if err != nil { 17 | return err 18 | } 19 | err = db.Ping() 20 | return err 21 | } 22 | 23 | func (c MysqlConnector) Connect(config *model.Config) (*sqlx.DB, error) { 24 | return sqlx.Open("mysql", c.dsn(config)) 25 | } 26 | 27 | func (c DefaultConnector) Use(db *sqlx.DB, database string) error { 28 | sqlStr := "use " + database 29 | _, err := db.Exec(sqlStr) 30 | return err 31 | } 32 | 33 | func (c MysqlConnector) dsn(database *model.Config) string { 34 | return fmt.Sprintf("%s:%s@tcp(%s:%s)/", *database.Username, *database.Password, *database.Host, *database.Port) 35 | } 36 | -------------------------------------------------------------------------------- /connectors/oracle.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "database/sql" 5 | "dbland/model" 6 | "github.com/jmoiron/sqlx" 7 | "github.com/sijms/go-ora/v2" 8 | "log/slog" 9 | "strconv" 10 | ) 11 | 12 | type OracleConnector struct { 13 | } 14 | 15 | func (c OracleConnector) Ping(config *model.Config) error { 16 | dsn := c.dsn(config) 17 | db, err := sqlx.Open("oracle", dsn) 18 | if err != nil { 19 | return err 20 | } 21 | err = db.Ping() 22 | return err 23 | } 24 | 25 | func (c OracleConnector) Connect(config *model.Config) (*sqlx.DB, error) { 26 | dsn := c.dsn(config) 27 | return sqlx.Open("oracle", dsn) 28 | } 29 | 30 | func (c OracleConnector) ShowDatabases(db *sqlx.DB) ([]string, error) { 31 | return nil, nil 32 | } 33 | 34 | func (c OracleConnector) Use(db *sqlx.DB, database string) error { 35 | return nil 36 | } 37 | 38 | func (c OracleConnector) ShowTables(db *sqlx.DB, database string) (*[]Table, error) { 39 | var tables []Table 40 | sqlStr := "SELECT table_name FROM user_tables" 41 | 42 | rows, err := db.Queryx(sqlStr) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | defer func() { 48 | _ = rows.Close() 49 | _ = db.Close() 50 | }() 51 | 52 | for rows.Next() { 53 | var table Table 54 | var tableName sql.NullString 55 | var err = rows.Scan(&tableName) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | if tableName.Valid { 61 | table.Name = tableName.String 62 | } 63 | 64 | tables = append(tables, table) 65 | } 66 | return &tables, err 67 | } 68 | 69 | func (c OracleConnector) Column(db *sqlx.DB, params ...string) (*[]Column, error) { 70 | return nil, nil 71 | } 72 | 73 | func (c OracleConnector) Query(db *sqlx.DB, sqlStr string) (*Query, error) { 74 | rows, err := db.Queryx(sqlStr) 75 | if err != nil { 76 | return nil, err 77 | } 78 | defer func() { 79 | _ = rows.Close() 80 | _ = db.Close() 81 | }() 82 | 83 | columns, err := rows.Columns() 84 | if err != nil { 85 | return nil, err 86 | } 87 | 88 | columnTypes, err := rows.ColumnTypes() 89 | 90 | var result []map[string]interface{} 91 | values := make([][]byte, len(columns)) 92 | scans := make([]interface{}, len(columns)) 93 | 94 | for i, _ := range columns { 95 | scans[i] = &values[i] 96 | } 97 | 98 | for rows.Next() { 99 | err = rows.Scan(scans...) 100 | if err != nil { 101 | return nil, err 102 | } 103 | row := make(map[string]interface{}) 104 | for i, value := range values { 105 | columnType := columnTypes[i] 106 | typeName := columnType.DatabaseTypeName() 107 | 108 | switch typeName { 109 | case "TINYINT", "SMALLINT", "INT", "INTEGER", "BIGINT", "UNSIGNED BIGINT", "UNSIGNED TINYINT": 110 | val, err := strconv.Atoi(string(value)) 111 | if err != nil { 112 | return nil, err 113 | } 114 | row[columnType.Name()] = val 115 | break 116 | case "NCHAR", "TimeStampDTY", "CHAR": 117 | row[columnType.Name()] = string(value) 118 | break 119 | default: 120 | slog.Error("dont support type", "type name", typeName) 121 | break 122 | } 123 | } 124 | result = append(result, row) 125 | 126 | } 127 | 128 | query := &Query{ 129 | Rows: result, 130 | Columns: nil, 131 | Total: len(result), 132 | } 133 | 134 | return query, err 135 | } 136 | 137 | func (c OracleConnector) Count(db *sqlx.DB, sqlStr string) (int, error) { 138 | rows, err := db.Queryx(sqlStr) 139 | if err != nil { 140 | return 0, err 141 | } 142 | defer func() { 143 | _ = rows.Close() 144 | _ = db.Close() 145 | }() 146 | for rows.Next() { 147 | var total int 148 | err = rows.Scan(total) 149 | if err != nil { 150 | return total, nil 151 | } 152 | } 153 | return 0, err 154 | } 155 | 156 | func (c OracleConnector) Ddl(db *sqlx.DB, table string) (string, error) { 157 | sqlStr := "SELECT DBMS_METADATA.GET_DDL('TABLE', 'SYS_USER') AS TABLE_DDL FROM DUAL" 158 | row := db.QueryRowx(sqlStr, table) 159 | var result string 160 | err := row.Scan(&result) 161 | return result, err 162 | } 163 | 164 | func (c OracleConnector) Execute(db *sqlx.DB, sqlStr string) (int, error) { 165 | result, err := db.Exec(sqlStr) 166 | if err != nil { 167 | return 0, err 168 | } 169 | rows, err := result.RowsAffected() 170 | if err != nil { 171 | return 0, err 172 | } 173 | return int(rows), err 174 | } 175 | 176 | func (c OracleConnector) dsn(database *model.Config) string { 177 | port, _ := strconv.Atoi(*database.Port) 178 | connStr := go_ora.BuildUrl(*database.Host, port, "TRADEVL", *database.Username, *database.Password, nil) 179 | return connStr 180 | } 181 | -------------------------------------------------------------------------------- /connectors/postgresql.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "database/sql" 5 | "dbland/model" 6 | "fmt" 7 | "github.com/jmoiron/sqlx" 8 | _ "github.com/lib/pq" 9 | ) 10 | 11 | type PostgreSQLConnector struct { 12 | DefaultConnector 13 | } 14 | 15 | func (c PostgreSQLConnector) Ping(config *model.Config) error { 16 | db, err := sqlx.Open("postgres", c.dsn(config)) 17 | if err != nil { 18 | return err 19 | } 20 | err = db.Ping() 21 | return err 22 | } 23 | 24 | func (c PostgreSQLConnector) Connect(config *model.Config) (*sqlx.DB, error) { 25 | return sqlx.Open("postgres", c.dsn(config)) 26 | } 27 | 28 | func (c PostgreSQLConnector) ShowDatabases(db *sqlx.DB) ([]string, error) { 29 | var result []string 30 | sqlStr := "SELECT datname FROM pg_database WHERE datistemplate = false" 31 | rows, err := db.Queryx(sqlStr) 32 | defer func() { 33 | _ = rows.Close() 34 | _ = db.Close() 35 | }() 36 | for rows.Next() { 37 | var row string 38 | err = rows.Scan(&row) 39 | if err != nil { 40 | return nil, err 41 | } 42 | result = append(result, row) 43 | } 44 | return result, err 45 | } 46 | 47 | func (c PostgreSQLConnector) ShowTables(db *sqlx.DB, database string) (*[]Table, error) { 48 | var tables []Table 49 | sqlStr := "SELECT table_name FROM information_schema.tables WHERE table_schema = 'public'" 50 | rows, err := db.Queryx(sqlStr, database) 51 | if err != nil { 52 | return nil, err 53 | } 54 | 55 | defer func() { 56 | _ = rows.Close() 57 | _ = db.Close() 58 | }() 59 | 60 | for rows.Next() { 61 | var table Table 62 | var tableName sql.NullString 63 | var err = rows.Scan(&tableName) 64 | if err != nil { 65 | return nil, err 66 | } 67 | 68 | if tableName.Valid { 69 | table.Name = tableName.String 70 | } 71 | 72 | tables = append(tables, table) 73 | } 74 | return &tables, err 75 | } 76 | 77 | func (c PostgreSQLConnector) dsn(database *model.Config) string { 78 | return fmt.Sprintf("user=%s password=%s host=%s port=%s sslmode=disable ", *database.Username, *database.Password, *database.Host, *database.Port) 79 | } 80 | -------------------------------------------------------------------------------- /connectors/sqlite.go: -------------------------------------------------------------------------------- 1 | package connectors 2 | 3 | import ( 4 | "database/sql" 5 | "dbland/model" 6 | "errors" 7 | "fmt" 8 | "github.com/jmoiron/sqlx" 9 | ) 10 | 11 | type SQLiteConnector struct { 12 | DefaultConnector 13 | } 14 | 15 | func (c SQLiteConnector) Ping(config *model.Config) error { 16 | if config.DbFile == nil { 17 | return errors.New("database file cannot be empty") 18 | } 19 | db, err := sqlx.Open("sqlite3", c.dsn(config)) 20 | if err != nil { 21 | return err 22 | } 23 | return db.Ping() 24 | } 25 | 26 | func (c SQLiteConnector) Connect(config *model.Config) (*sqlx.DB, error) { 27 | return sqlx.Open("sqlite3", c.dsn(config)) 28 | } 29 | 30 | func (c SQLiteConnector) ShowDatabases(db *sqlx.DB) ([]string, error) { 31 | result := []string{"main"} 32 | return result, nil 33 | } 34 | 35 | func (c SQLiteConnector) Use(db *sqlx.DB, database string) error { 36 | return nil 37 | } 38 | 39 | func (c SQLiteConnector) ShowTables(db *sqlx.DB, database string) (*[]Table, error) { 40 | var tables []Table 41 | sqlStr := "SELECT name FROM sqlite_master WHERE type = 'table' AND name != 'sqlite_sequence'" 42 | rows, err := db.Queryx(sqlStr, database) 43 | if err != nil { 44 | return nil, err 45 | } 46 | 47 | defer func() { 48 | _ = rows.Close() 49 | _ = db.Close() 50 | }() 51 | 52 | for rows.Next() { 53 | var table Table 54 | var tableName sql.NullString 55 | var err = rows.Scan(&tableName) 56 | if err != nil { 57 | return nil, err 58 | } 59 | 60 | if tableName.Valid { 61 | table.Name = tableName.String 62 | } 63 | 64 | tables = append(tables, table) 65 | } 66 | return &tables, err 67 | } 68 | 69 | func (c SQLiteConnector) Column(db *sqlx.DB, params ...string) (*[]Column, error) { 70 | var columns []Column 71 | table := params[1] 72 | sqlStr := fmt.Sprintf("PRAGMA table_info(%v)", table) 73 | 74 | rows, err := db.Query(sqlStr) 75 | if err != nil { 76 | return nil, err 77 | } 78 | defer func() { 79 | _ = rows.Close() 80 | _ = db.Close() 81 | }() 82 | 83 | for rows.Next() { 84 | var column Column 85 | var id sql.NullString 86 | var columnName sql.NullString 87 | var columnType sql.NullString 88 | var notnull sql.NullBool 89 | var dfltValue sql.NullString 90 | var pk sql.NullBool 91 | err = rows.Scan(&id, &columnName, &columnType, ¬null, &dfltValue, &pk) 92 | if err != nil { 93 | return nil, err 94 | } 95 | 96 | if columnName.Valid { 97 | column.Field = columnName.String 98 | } 99 | if columnType.Valid { 100 | column.Type = columnType.String 101 | } 102 | if notnull.Valid { 103 | column.Nullable = notnull.Bool 104 | } 105 | if dfltValue.Valid { 106 | column.Default = dfltValue.String 107 | } 108 | if pk.Valid { 109 | if pk.Bool { 110 | column.Key = Primary 111 | } 112 | } 113 | columns = append(columns, column) 114 | } 115 | return &columns, err 116 | } 117 | 118 | func (c SQLiteConnector) Ddl(db *sqlx.DB, table string) (string, error) { 119 | return "", nil 120 | } 121 | 122 | func (c SQLiteConnector) dsn(config *model.Config) string { 123 | return fmt.Sprintf(*config.DbFile) 124 | } 125 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module dbland 2 | 3 | go 1.21.1 4 | 5 | require ( 6 | github.com/gin-gonic/gin v1.9.1 7 | github.com/go-sql-driver/mysql v1.7.1 8 | github.com/jmoiron/sqlx v1.3.5 9 | github.com/lib/pq v1.10.9 10 | github.com/mattn/go-sqlite3 v2.0.3+incompatible 11 | github.com/sijms/go-ora/v2 v2.7.14 12 | github.com/spf13/cobra v1.7.0 13 | golang.org/x/crypto v0.14.0 14 | gopkg.in/yaml.v3 v3.0.1 15 | ) 16 | 17 | replace github.com/mattn/go-sqlite3 => github.com/mattn/go-sqlite3 v1.14.17 18 | 19 | require ( 20 | github.com/bytedance/sonic v1.9.1 // indirect 21 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect 22 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect 23 | github.com/gabriel-vasile/mimetype v1.4.2 // indirect 24 | github.com/gin-contrib/sse v0.1.0 // indirect 25 | github.com/go-playground/locales v0.14.1 // indirect 26 | github.com/go-playground/universal-translator v0.18.1 // indirect 27 | github.com/go-playground/validator/v10 v10.14.0 // indirect 28 | github.com/goccy/go-json v0.10.2 // indirect 29 | github.com/google/go-cmp v0.5.9 // indirect 30 | github.com/inconshreveable/mousetrap v1.1.0 // indirect 31 | github.com/jinzhu/inflection v1.0.0 // indirect 32 | github.com/jinzhu/now v1.1.5 // indirect 33 | github.com/json-iterator/go v1.1.12 // indirect 34 | github.com/klauspost/cpuid/v2 v2.2.4 // indirect 35 | github.com/kr/pretty v0.3.1 // indirect 36 | github.com/leodido/go-urn v1.2.4 // indirect 37 | github.com/mattn/go-isatty v0.0.19 // indirect 38 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect 39 | github.com/modern-go/reflect2 v1.0.2 // indirect 40 | github.com/pelletier/go-toml/v2 v2.1.0 // indirect 41 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect 42 | github.com/rogpeppe/go-internal v1.11.0 // indirect 43 | github.com/spf13/pflag v1.0.5 // indirect 44 | github.com/twitchyliquid64/golang-asm v0.15.1 // indirect 45 | github.com/ugorji/go/codec v1.2.11 // indirect 46 | golang.org/x/arch v0.3.0 // indirect 47 | golang.org/x/net v0.16.0 // indirect 48 | golang.org/x/sys v0.13.0 // indirect 49 | golang.org/x/text v0.13.0 // indirect 50 | google.golang.org/protobuf v1.31.0 // indirect 51 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect 52 | gorm.io/driver/sqlite v1.5.5 // indirect 53 | gorm.io/gorm v1.25.7 // indirect 54 | ) 55 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= 2 | github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= 3 | github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= 4 | github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= 5 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= 6 | github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= 7 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 8 | github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 9 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 11 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= 12 | github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 13 | github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= 14 | github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= 15 | github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= 16 | github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= 17 | github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= 18 | github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= 19 | github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= 20 | github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= 21 | github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= 22 | github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= 23 | github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= 24 | github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= 25 | github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= 26 | github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= 27 | github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= 28 | github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= 29 | github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= 30 | github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= 31 | github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= 32 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 33 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 34 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 35 | github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= 36 | github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= 37 | github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= 38 | github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= 39 | github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= 40 | github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= 41 | github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= 42 | github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= 43 | github.com/jmoiron/sqlx v1.3.5 h1:vFFPA71p1o5gAeqtEAwLU4dnX2napprKtHr7PYIcN3g= 44 | github.com/jmoiron/sqlx v1.3.5/go.mod h1:nRVWtLre0KfCLJvgxzCsLVMogSvQ1zNJtpYr2Ccp0mQ= 45 | github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= 46 | github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= 47 | github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= 48 | github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= 49 | github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= 50 | github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= 51 | github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= 52 | github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= 53 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 54 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 55 | github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= 56 | github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= 57 | github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= 58 | github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= 59 | github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= 60 | github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= 61 | github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= 62 | github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= 63 | github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= 64 | github.com/mattn/go-sqlite3 v1.14.17 h1:mCRHCLDUBXgpKAqIKsaAaAsrAlbkeomtRFKXh2L6YIM= 65 | github.com/mattn/go-sqlite3 v1.14.17/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= 66 | github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 67 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= 68 | github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= 69 | github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= 70 | github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= 71 | github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4= 72 | github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 73 | github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= 74 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 75 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= 76 | github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 77 | github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= 78 | github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= 79 | github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= 80 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 81 | github.com/sijms/go-ora/v2 v2.7.14 h1:yFiM8xkwAtssXeiVS8s+IXtr+z3cMVvzSRyXUPoaVgE= 82 | github.com/sijms/go-ora/v2 v2.7.14/go.mod h1:EHxlY6x7y9HAsdfumurRfTd+v8NrEOTR3Xl4FWlH6xk= 83 | github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= 84 | github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= 85 | github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= 86 | github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= 87 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 88 | github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= 89 | github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= 90 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 91 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 92 | github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 93 | github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= 94 | github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 95 | github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= 96 | github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= 97 | github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= 98 | github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= 99 | github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= 100 | github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= 101 | github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= 102 | golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 103 | golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= 104 | golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= 105 | golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= 106 | golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= 107 | golang.org/x/net v0.16.0 h1:7eBu7KsSvFDtSXUIDbh3aqlK4DPsZ1rByC8PFfBThos= 108 | golang.org/x/net v0.16.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 109 | golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 110 | golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 111 | golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= 112 | golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 113 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 114 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 115 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 116 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 117 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 118 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 119 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 120 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= 121 | gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= 122 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 123 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 124 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 125 | gorm.io/driver/sqlite v1.5.5 h1:7MDMtUZhV065SilG62E0MquljeArQZNfJnjd9i9gx3E= 126 | gorm.io/driver/sqlite v1.5.5/go.mod h1:6NgQ7sQWAIFsPrJJl1lSNSu2TABh0ZZ/zm5fosATavE= 127 | gorm.io/gorm v1.25.7 h1:VsD6acwRjz2zFxGO50gPO6AkNs7KKnvfzUjHQhZDz/A= 128 | gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= 129 | rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= 130 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "dbland/cmd" 5 | ) 6 | 7 | func main() { 8 | cmd.Execute() 9 | } 10 | -------------------------------------------------------------------------------- /model/chart.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type Chart struct { 6 | Id *uint `json:"id"` 7 | Title *string `json:"title"` 8 | Sql *string `json:"sql"` 9 | Type *string `json:"type"` 10 | CreatedTime *time.Time `json:"created_time"` 11 | } 12 | 13 | func NewChart() *Chart { 14 | return &Chart{ 15 | Id: nil, 16 | Title: nil, 17 | Sql: nil, 18 | Type: nil, 19 | CreatedTime: nil, 20 | } 21 | } 22 | 23 | func (c Chart) TableName() string { 24 | return "sys_chart" 25 | } 26 | -------------------------------------------------------------------------------- /model/config.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Config struct { 4 | Id *uint `json:"id,omitempty"` 5 | Type *string `json:"type,omitempty" binding:"required"` 6 | Name *string `json:"name,omitempty" binding:"required"` 7 | Host *string `json:"host,omitempty" binding:"required"` 8 | Port *string `json:"port,omitempty" binding:"required"` 9 | Username *string `json:"username,omitempty" binding:"required"` 10 | Password *string `json:"password,omitempty" binding:"required"` 11 | Charset *string `json:"charset,omitempty"` 12 | Timeout *uint64 `json:"timeout,omitempty"` 13 | ReadTimeout *uint64 `json:"read_timeout,omitempty"` 14 | WriteTimeout *uint64 `json:"write_timeout,omitempty"` 15 | DbFile *string `json:"dbFile,omitempty"` 16 | } 17 | 18 | func NewConfig() *Config { 19 | return &Config{ 20 | Id: nil, 21 | Type: nil, 22 | Name: nil, 23 | Host: nil, 24 | Port: nil, 25 | Username: nil, 26 | Password: nil, 27 | Charset: nil, 28 | Timeout: nil, 29 | ReadTimeout: nil, 30 | WriteTimeout: nil, 31 | DbFile: nil, 32 | } 33 | } 34 | 35 | func (c Config) TableName() string { 36 | return "sys_config" 37 | } 38 | -------------------------------------------------------------------------------- /model/log.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | const ( 6 | ExecutionLogSuccess = 1 7 | ExecutionLogFail = 0 8 | ) 9 | 10 | type Log struct { 11 | Id *uint `json:"id,omitempty"` 12 | Source string `json:"source,omitempty"` 13 | Database string `json:"database,omitempty"` 14 | Sql string `json:"sql,omitempty"` 15 | Status int `json:"status,omitempty"` 16 | Cost float64 `json:"cost,omitempty"` 17 | Ip string `json:"ip"` 18 | UserAgent string `json:"user_agent"` 19 | CreatedTime time.Time `json:"created_time,omitempty"` 20 | } 21 | 22 | func NewLog() *Log { 23 | return &Log{ 24 | Id: nil, 25 | Source: "", 26 | Sql: "", 27 | Status: ExecutionLogSuccess, 28 | Cost: 0, 29 | CreatedTime: time.Now(), 30 | } 31 | } 32 | 33 | func (l Log) TableName() string { 34 | return "sys_log" 35 | } 36 | -------------------------------------------------------------------------------- /model/page.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | type Page struct { 4 | Columns []string `json:"columns"` 5 | TableName string `json:"table_name"` 6 | Rows []map[string]interface{} `json:"rows"` 7 | Total uint `json:"total"` 8 | PageNo int `json:"page_no"` 9 | } 10 | -------------------------------------------------------------------------------- /model/user.go: -------------------------------------------------------------------------------- 1 | package model 2 | 3 | import "time" 4 | 5 | type User struct { 6 | Id *uint `json:"id"` 7 | Username *string `json:"username"` 8 | Password *string `json:"password"` 9 | Email *string `json:"email"` 10 | Name *string `json:"name"` 11 | Avatar *string `json:"avatar"` 12 | Status int `json:"status"` 13 | LastLoginIp *string `json:"last_login_ip"` 14 | LastLoginTime *time.Time `json:"last_login_time"` 15 | } 16 | 17 | func NewUser() *User { 18 | return &User{ 19 | Id: nil, 20 | Username: nil, 21 | Password: nil, 22 | Email: nil, 23 | Name: nil, 24 | Avatar: nil, 25 | Status: 1, 26 | LastLoginIp: nil, 27 | LastLoginTime: nil, 28 | } 29 | } 30 | 31 | func (u User) TableName() string { 32 | return "sys_user" 33 | } 34 | -------------------------------------------------------------------------------- /repository/chart.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import "dbland/model" 4 | 5 | type ChartRepository struct { 6 | } 7 | 8 | func (r ChartRepository) List() *[]model.Chart { 9 | var charts []model.Chart 10 | DB.Find(&charts) 11 | return &charts 12 | } 13 | 14 | func (r ChartRepository) Save(chart *model.Chart) error { 15 | return nil 16 | } 17 | 18 | func (r ChartRepository) Update(chart *model.Chart) error { 19 | return nil 20 | } 21 | 22 | func (r ChartRepository) DeleteById(id int) error { 23 | return nil 24 | } 25 | -------------------------------------------------------------------------------- /repository/config.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "dbland/model" 5 | ) 6 | 7 | type ConnectionConfigRepository struct { 8 | } 9 | 10 | func (r ConnectionConfigRepository) List() *[]model.Config { 11 | var configs []model.Config 12 | DB.Find(&configs) 13 | return &configs 14 | } 15 | 16 | func (r ConnectionConfigRepository) GetById(id uint) *model.Config { 17 | var config model.Config 18 | DB.Where("id = ?", id).First(&config) 19 | return &config 20 | } 21 | 22 | func (r ConnectionConfigRepository) Save(config *model.Config) { 23 | DB.Save(config) 24 | } 25 | 26 | func (r ConnectionConfigRepository) Update(config *model.Config) { 27 | DB.Updates(config) 28 | } 29 | 30 | func (r ConnectionConfigRepository) DeleteById(id uint) { 31 | DB.Delete(model.Config{}, id) 32 | } 33 | -------------------------------------------------------------------------------- /repository/db.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "dbland/config" 5 | "dbland/model" 6 | "gorm.io/driver/sqlite" 7 | "gorm.io/gorm" 8 | "log/slog" 9 | ) 10 | 11 | var DB *gorm.DB 12 | 13 | func Initialize() { 14 | var err error 15 | DB, err = gorm.Open(sqlite.Open(config.Conf.Sqlite.DataPath), &gorm.Config{}) 16 | if err != nil { 17 | slog.Error(err.Error()) 18 | return 19 | } 20 | err = DB.AutoMigrate(&model.Chart{}, &model.Config{}, &model.Log{}, &model.User{}) 21 | if err != nil { 22 | slog.Error(err.Error()) 23 | return 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /repository/log.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "dbland/model" 5 | ) 6 | 7 | type ExecutionLogRepository struct { 8 | } 9 | 10 | func (r ExecutionLogRepository) Save(log *model.Log) { 11 | DB.Save(log) 12 | } 13 | -------------------------------------------------------------------------------- /repository/user.go: -------------------------------------------------------------------------------- 1 | package repository 2 | 3 | import ( 4 | "dbland/model" 5 | ) 6 | 7 | type UserRepository struct { 8 | } 9 | 10 | func (r UserRepository) FindByUsername(username string) *model.User { 11 | var user model.User 12 | DB.Where("username = ?", username).First(&user) 13 | return &user 14 | } 15 | 16 | func (r UserRepository) FindAll() (*[]model.User, error) { 17 | return nil, nil 18 | } 19 | 20 | func (r UserRepository) Save(user *model.User) { 21 | 22 | } 23 | 24 | func (r UserRepository) UpdateLastLoginTime(id *uint) error { 25 | return nil 26 | } 27 | -------------------------------------------------------------------------------- /router/router.go: -------------------------------------------------------------------------------- 1 | package router 2 | 3 | import ( 4 | "dbland/api" 5 | "dbland/config" 6 | "dbland/static" 7 | "github.com/gin-gonic/gin" 8 | "log/slog" 9 | "net" 10 | "net/http" 11 | "strings" 12 | "time" 13 | ) 14 | 15 | func InitRouter() *gin.Engine { 16 | gin.SetMode(config.Conf.Server.Environment) 17 | r := gin.New() 18 | 19 | r.Use(Logger()) 20 | r.Use(corsMiddleware()) 21 | 22 | r.StaticFS("/ui", http.FS(static.Static)) 23 | 24 | g := r.Group(config.Conf.Server.Path) 25 | { 26 | query := api.ConnectorHandler{} 27 | query.InitRouter(g) 28 | 29 | user := api.UserHandler{} 30 | user.InitRouter(g) 31 | 32 | connectionConfig := api.ConnectionConfigHandler{} 33 | connectionConfig.InitRouter(g) 34 | 35 | other := api.OtherHandler{} 36 | other.InitRouter(g) 37 | 38 | chart := api.ChartHandler{} 39 | chart.InitRouter(g) 40 | } 41 | return r 42 | } 43 | 44 | func corsMiddleware() gin.HandlerFunc { 45 | return func(c *gin.Context) { 46 | c.Writer.Header().Set("Access-Control-Allow-Origin", "*") 47 | c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, GET, OPTIONS, PUT, DELETE") 48 | c.Writer.Header().Set("Access-Control-Allow-Headers", "*") 49 | c.Writer.Header().Set("Access-Control-Max-Age", "86400") 50 | 51 | if c.Request.Method == "OPTIONS" { 52 | c.AbortWithStatus(200) 53 | return 54 | } 55 | 56 | c.Next() 57 | } 58 | } 59 | 60 | func Logger() gin.HandlerFunc { 61 | return func(c *gin.Context) { 62 | t := time.Now() 63 | c.Next() 64 | latency := time.Since(t) 65 | elapsedMilliseconds := latency.Milliseconds() 66 | status := c.Writer.Status() 67 | url := c.Request.RequestURI 68 | method := c.Request.Method 69 | ip := getClientIP(c) 70 | slog.Info("Request", "code", status, "times", elapsedMilliseconds, "method", method, "url", url, "ip", ip) 71 | } 72 | } 73 | 74 | func getClientIP(c *gin.Context) string { 75 | if xForwardedFor := c.Request.Header.Get("X-Forwarded-For"); xForwardedFor != "" { 76 | ips := strings.Split(xForwardedFor, ",") 77 | for _, ip := range ips { 78 | if parsedIP := net.ParseIP(strings.TrimSpace(ip)); parsedIP != nil { 79 | return parsedIP.String() 80 | } 81 | } 82 | } 83 | 84 | remoteIP, _, _ := net.SplitHostPort(c.Request.RemoteAddr) 85 | return remoteIP 86 | } 87 | -------------------------------------------------------------------------------- /service/chart.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "dbland/model" 5 | "dbland/repository" 6 | "github.com/gin-gonic/gin" 7 | "strconv" 8 | ) 9 | 10 | type ChartService struct { 11 | repository repository.ChartRepository 12 | } 13 | 14 | func (s ChartService) List(g *gin.Context) *[]model.Chart { 15 | return s.repository.List() 16 | } 17 | 18 | func (s ChartService) Save(g *gin.Context) error { 19 | chart := model.NewChart() 20 | err := g.ShouldBind(&chart) 21 | if err != nil { 22 | return err 23 | } 24 | return s.repository.Save(chart) 25 | } 26 | 27 | func (s ChartService) Update(g *gin.Context) error { 28 | chart := model.NewChart() 29 | err := g.ShouldBind(&chart) 30 | if err != nil { 31 | return err 32 | } 33 | return s.repository.Update(chart) 34 | } 35 | 36 | func (s ChartService) Delete(g *gin.Context) error { 37 | idStr := g.Param("id") 38 | id, err := strconv.Atoi(idStr) 39 | if err != nil { 40 | return err 41 | } 42 | return s.repository.DeleteById(id) 43 | } 44 | -------------------------------------------------------------------------------- /service/config.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "dbland/model" 5 | "dbland/repository" 6 | "github.com/gin-gonic/gin" 7 | "strconv" 8 | ) 9 | 10 | type ConnectionConfigService struct { 11 | connectionConfigRepository repository.ConnectionConfigRepository 12 | } 13 | 14 | func (s ConnectionConfigService) List(g *gin.Context) *[]model.Config { 15 | return s.connectionConfigRepository.List() 16 | } 17 | 18 | func (s ConnectionConfigService) Save(g *gin.Context) error { 19 | config := model.NewConfig() 20 | err := g.ShouldBind(&config) 21 | if err != nil { 22 | return err 23 | } 24 | s.connectionConfigRepository.Save(config) 25 | return nil 26 | } 27 | 28 | func (s ConnectionConfigService) Update(g *gin.Context) error { 29 | config := model.NewConfig() 30 | err := g.ShouldBind(&config) 31 | if err != nil { 32 | return err 33 | } 34 | s.connectionConfigRepository.Update(config) 35 | return nil 36 | } 37 | 38 | func (s ConnectionConfigService) Delete(g *gin.Context) error { 39 | idStr := g.Param("id") 40 | id, err := strconv.Atoi(idStr) 41 | if err != nil { 42 | return err 43 | } 44 | 45 | s.connectionConfigRepository.DeleteById(uint(id)) 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /service/connector.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "dbland/connectors" 5 | "dbland/model" 6 | "dbland/repository" 7 | "fmt" 8 | "log/slog" 9 | "strconv" 10 | "strings" 11 | "sync" 12 | "time" 13 | 14 | "github.com/gin-gonic/gin" 15 | "github.com/jmoiron/sqlx" 16 | ) 17 | 18 | type ConnectorService struct { 19 | connectorFactory connectors.ConnectorFactory 20 | connectionConfigRepository repository.ConnectionConfigRepository 21 | executionLogRepository repository.ExecutionLogRepository 22 | } 23 | 24 | type QueryReq struct { 25 | Cid uint `json:"cid" binding:"required"` 26 | Sql string `json:"sql"` 27 | Database string `json:"database"` 28 | Table string `json:"table"` 29 | } 30 | 31 | type Sort struct { 32 | Prop string `json:"prop"` 33 | Order string `json:"order"` 34 | } 35 | 36 | func (s ConnectorService) Ping(c *gin.Context) error { 37 | var config model.Config 38 | if err := c.ShouldBindJSON(&config); err != nil { 39 | return err 40 | } 41 | 42 | connector, err := s.connectorFactory.GetInstance(*config.Type) 43 | if err != nil { 44 | return err 45 | } 46 | 47 | err = connector.Ping(&config) 48 | return err 49 | } 50 | 51 | func (s ConnectorService) ShowDatabases(c *gin.Context) ([]string, error) { 52 | var req QueryReq 53 | if err := c.ShouldBindJSON(&req); err != nil { 54 | return nil, err 55 | } 56 | 57 | db, connector, err := s.getConnector(&req) 58 | if err != nil { 59 | return nil, err 60 | } 61 | return connector.ShowDatabases(db) 62 | } 63 | 64 | func (s ConnectorService) ShowTables(c *gin.Context) (interface{}, error) { 65 | var req QueryReq 66 | if err := c.ShouldBindJSON(&req); err != nil { 67 | return nil, err 68 | } 69 | 70 | db, connector, err := s.getConnector(&req) 71 | if err != nil { 72 | return nil, err 73 | } 74 | return connector.ShowTables(db, req.Database) 75 | } 76 | 77 | func (s ConnectorService) Column(c *gin.Context) (interface{}, error) { 78 | var req QueryReq 79 | if err := c.ShouldBindJSON(&req); err != nil { 80 | return nil, err 81 | } 82 | 83 | db, connector, err := s.getConnector(&req) 84 | if err != nil { 85 | return nil, err 86 | } 87 | return connector.Column(db, req.Database, req.Table) 88 | } 89 | 90 | func (s ConnectorService) Ddl(c *gin.Context) (interface{}, error) { 91 | var req QueryReq 92 | if err := c.ShouldBindJSON(&req); err != nil { 93 | return nil, err 94 | } 95 | 96 | slog.Info("ddl", "table", req.Table) 97 | 98 | db, connector, err := s.getConnector(&req) 99 | if err != nil { 100 | return nil, err 101 | } 102 | return connector.Ddl(db, req.Table) 103 | } 104 | 105 | func (s ConnectorService) Query(c *gin.Context) (interface{}, error) { 106 | var req QueryReq 107 | if err := c.ShouldBindJSON(&req); err != nil { 108 | return nil, err 109 | } 110 | 111 | db, connector, err := s.getConnector(&req) 112 | if err != nil { 113 | return nil, err 114 | } 115 | 116 | defer func() { 117 | err = db.Close() 118 | }() 119 | 120 | err = connector.Use(db, req.Database) 121 | 122 | startTime := time.Now() 123 | // exec sql 124 | var result *connectors.Query 125 | 126 | var sqlStr = req.Sql 127 | slog.Info("Query", "sql", sqlStr) 128 | result, err = connector.Query(db, sqlStr) 129 | if err != nil { 130 | return nil, err 131 | } 132 | 133 | elapsedTime := time.Since(startTime) 134 | cost := elapsedTime.Seconds() 135 | str := fmt.Sprintf("%.3f", cost) 136 | cost, err = strconv.ParseFloat(str, 64) 137 | if err != nil { 138 | return nil, err 139 | } 140 | result.ElapsedTime = cost 141 | 142 | var wg sync.WaitGroup 143 | wg.Add(1) 144 | 145 | go func() { 146 | defer wg.Done() 147 | if req.Table != "" { 148 | columns, err := connector.Column(db, req.Database, req.Table) 149 | if err != nil { 150 | slog.Error(err.Error()) 151 | } 152 | result.Columns = *columns 153 | } 154 | }() 155 | wg.Wait() 156 | 157 | // save log 158 | req.Sql = sqlStr 159 | println(c.Request.UserAgent()) 160 | go s.saveExecutionLog(&req, cost, c.ClientIP(), c.Request.UserAgent(), err) 161 | return result, err 162 | } 163 | 164 | func (s ConnectorService) Execute(c *gin.Context) (int, error) { 165 | var req QueryReq 166 | if err := c.ShouldBindJSON(&req); err != nil { 167 | return 0, err 168 | } 169 | 170 | slog.Info("query", "sql", req.Sql) 171 | db, connector, err := s.getConnector(&req) 172 | if err != nil { 173 | return 0, err 174 | } 175 | return connector.Execute(db, req.Sql) 176 | } 177 | 178 | func (s ConnectorService) saveExecutionLog(queryReq *QueryReq, cost float64, ip string, userAgent string, err error) { 179 | var status int 180 | if err != nil { 181 | status = model.ExecutionLogFail 182 | } else { 183 | status = model.ExecutionLogSuccess 184 | } 185 | 186 | var config *model.Config 187 | config = s.connectionConfigRepository.GetById(queryReq.Cid) 188 | if err != nil { 189 | slog.Error(err.Error()) 190 | return 191 | } 192 | log := &model.Log{ 193 | Source: *config.Name, 194 | Status: status, 195 | Database: queryReq.Database, 196 | Sql: queryReq.Sql, 197 | Cost: cost, 198 | Ip: ip, 199 | UserAgent: userAgent, 200 | CreatedTime: time.Now(), 201 | } 202 | s.executionLogRepository.Save(log) 203 | } 204 | 205 | func (s ConnectorService) getConnector(req *QueryReq) (*sqlx.DB, connectors.Connector, error) { 206 | config := s.connectionConfigRepository.GetById(req.Cid) 207 | 208 | connector, err := s.connectorFactory.GetInstance(*config.Type) 209 | if err != nil { 210 | return nil, nil, err 211 | } 212 | 213 | var db *sqlx.DB 214 | db, err = connector.Connect(config) 215 | if err != nil { 216 | return nil, nil, err 217 | } 218 | 219 | return db, connector, nil 220 | } 221 | 222 | func containsKeywords(str string, keywords ...string) bool { 223 | for _, keyword := range keywords { 224 | keywordLower := strings.ToLower(keyword) 225 | index := strings.Index(str, keywordLower) 226 | if index != -1 { 227 | return true 228 | } 229 | } 230 | return false 231 | } 232 | -------------------------------------------------------------------------------- /service/log.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "dbland/repository" 5 | "github.com/gin-gonic/gin" 6 | ) 7 | 8 | type ExecutionLogService struct { 9 | executionLogRepository repository.ExecutionLogRepository 10 | } 11 | 12 | func (r ExecutionLogService) Save(c *gin.Context) error { 13 | return nil 14 | } 15 | -------------------------------------------------------------------------------- /service/other.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "os" 5 | ) 6 | 7 | type OtherService struct { 8 | } 9 | 10 | func (s OtherService) LoadReadme() (string, error) { 11 | b, err := os.ReadFile("README.md") 12 | if err != nil { 13 | return "", err 14 | } 15 | return string(b), nil 16 | } 17 | -------------------------------------------------------------------------------- /service/user.go: -------------------------------------------------------------------------------- 1 | package service 2 | 3 | import ( 4 | "dbland/model" 5 | "dbland/repository" 6 | "fmt" 7 | "github.com/gin-gonic/gin" 8 | "golang.org/x/crypto/bcrypt" 9 | "log/slog" 10 | ) 11 | 12 | type UserService struct { 13 | userRepository repository.UserRepository 14 | } 15 | 16 | func (s UserService) Add(c *gin.Context) error { 17 | user := model.NewUser() 18 | err := c.ShouldBind(&user) 19 | if err != nil { 20 | return err 21 | } 22 | var pwd []byte 23 | pwd, err = bcrypt.GenerateFromPassword([]byte(*user.Password), bcrypt.DefaultCost) 24 | hashedPassword := string(pwd) 25 | user.Password = &hashedPassword 26 | 27 | s.userRepository.Save(user) 28 | return nil 29 | } 30 | 31 | // Login user login 32 | func (s UserService) Login(c *gin.Context) error { 33 | var err error 34 | username := c.PostForm("username") 35 | password := c.PostForm("password") 36 | 37 | var user *model.User 38 | user = s.userRepository.FindByUsername(username) 39 | if err != nil { 40 | return err 41 | } 42 | 43 | if user == nil { 44 | slog.Error("User does not exist", "username", username) 45 | return fmt.Errorf("user does not exist") 46 | } 47 | 48 | err = s.userRepository.UpdateLastLoginTime(user.Id) 49 | if err != nil { 50 | return err 51 | } 52 | 53 | // verify password 54 | err = bcrypt.CompareHashAndPassword([]byte(*user.Password), []byte(password)) 55 | if err != nil { 56 | slog.Error("Password error", "username", username, "password", password) 57 | return fmt.Errorf("password error") 58 | } 59 | 60 | return err 61 | } 62 | 63 | func (s UserService) Logout(c *gin.Context) { 64 | } 65 | -------------------------------------------------------------------------------- /static/web.go: -------------------------------------------------------------------------------- 1 | package static 2 | 3 | import "embed" 4 | 5 | //go:embed * 6 | var Static embed.FS 7 | -------------------------------------------------------------------------------- /web/.env.development: -------------------------------------------------------------------------------- 1 | // host 2 | VITE_SERVER='http://127.0.0.1:8080' -------------------------------------------------------------------------------- /web/.env.release: -------------------------------------------------------------------------------- 1 | // host 2 | VITE_SERVER = "" -------------------------------------------------------------------------------- /web/README.md: -------------------------------------------------------------------------------- 1 | # DBLAND 2 | -------------------------------------------------------------------------------- /web/env.d.ts: -------------------------------------------------------------------------------- 1 | ///