From fc3ae4b1ee5f10c90355b585e237508550216d27 Mon Sep 17 00:00:00 2001 From: bitxeno <137328844+bitxeno@users.noreply.github.com> Date: Fri, 21 Jun 2024 18:47:32 +0800 Subject: [PATCH] refactor: update code structure --- .air.toml | 4 +- .github/workflows/beta.yml | 131 ---------- .github/workflows/release-nightly.yml | 240 ++++++++++++++++++ .github/workflows/release.yml | 30 ++- .gitignore | 3 +- {internal/cmd => cmd/gen}/gen.go | 12 +- cmd/server/server.go | 80 ++++++ config/config.go | 8 - config/custom.go | 25 -- fs_dev.go | 12 - fs_prod.go | 20 -- go.mod | 1 + go.sum | 2 + internal/app/app.go | 125 +-------- internal/app/booter.go | 45 ---- internal/app/bootstrap.go | 80 ++++-- internal/app/build/var.go | 11 + internal/app/config.go | 26 ++ internal/app/const.go | 3 + internal/app/mode.go | 25 ++ internal/app/router.go | 5 - {config => internal/app}/settings.go | 29 +-- internal/app/version.go | 16 ++ internal/cfg/cfg.go | 89 +++---- internal/cfg/cfg_darwin.go | 7 +- internal/cfg/cfg_dev.go | 15 -- internal/cfg/cfg_linux.go | 7 +- internal/cfg/cfg_prod.go | 7 - internal/cfg/cfg_windows.go | 4 +- internal/cfg/entity.go | 73 ------ internal/cmd/main.go | 27 -- internal/cmd/server.go | 32 --- internal/db/config.go | 7 + internal/db/db.go | 22 +- internal/mode/mode.go | 31 --- {model => internal/model}/model.go | 0 internal/version/build.go | 25 -- main.go | 45 ++-- {router => web}/api_result.go | 2 +- web/fs_dev.go | 12 + web/fs_prod.go | 20 ++ {router => web}/router.go | 28 +- {view => web/static}/index.html | 0 {view => web/static}/package-lock.json | 8 +- {view => web/static}/package.json | 2 +- {view => web/static}/postcss.config.js | 0 {view => web/static}/src/App.vue | 0 {view => web/static}/src/api/api.js | 0 {view => web/static}/src/app.css | 0 .../static}/src/assets/icons/welcome.svg | 0 {view => web/static}/src/main.js | 0 {view => web/static}/src/page/about/index.vue | 0 {view => web/static}/src/page/home/index.vue | 0 {view => web/static}/src/page/layout.vue | 0 {view => web/static}/src/router/index.js | 0 {view => web/static}/src/utils/request.js | 0 {view => web/static}/tailwind.config.js | 0 {view => web/static}/vite.config.js | 0 {view => web/static}/yarn.lock | 8 +- web/web.go | 31 +++ 60 files changed, 682 insertions(+), 753 deletions(-) delete mode 100644 .github/workflows/beta.yml create mode 100644 .github/workflows/release-nightly.yml rename {internal/cmd => cmd/gen}/gen.go (82%) create mode 100644 cmd/server/server.go delete mode 100644 config/config.go delete mode 100644 config/custom.go delete mode 100644 fs_dev.go delete mode 100644 fs_prod.go delete mode 100644 internal/app/booter.go create mode 100644 internal/app/build/var.go create mode 100644 internal/app/config.go create mode 100644 internal/app/const.go create mode 100644 internal/app/mode.go delete mode 100644 internal/app/router.go rename {config => internal/app}/settings.go (60%) create mode 100644 internal/app/version.go delete mode 100644 internal/cfg/cfg_dev.go delete mode 100644 internal/cfg/cfg_prod.go delete mode 100644 internal/cfg/entity.go delete mode 100644 internal/cmd/main.go delete mode 100644 internal/cmd/server.go create mode 100644 internal/db/config.go delete mode 100644 internal/mode/mode.go rename {model => internal/model}/model.go (100%) delete mode 100644 internal/version/build.go rename {router => web}/api_result.go (95%) create mode 100644 web/fs_dev.go create mode 100644 web/fs_prod.go rename {router => web}/router.go (70%) rename {view => web/static}/index.html (100%) rename {view => web/static}/package-lock.json (99%) rename {view => web/static}/package.json (96%) rename {view => web/static}/postcss.config.js (100%) rename {view => web/static}/src/App.vue (100%) rename {view => web/static}/src/api/api.js (100%) rename {view => web/static}/src/app.css (100%) rename {view => web/static}/src/assets/icons/welcome.svg (100%) rename {view => web/static}/src/main.js (100%) rename {view => web/static}/src/page/about/index.vue (100%) rename {view => web/static}/src/page/home/index.vue (100%) rename {view => web/static}/src/page/layout.vue (100%) rename {view => web/static}/src/router/index.js (100%) rename {view => web/static}/src/utils/request.js (100%) rename {view => web/static}/tailwind.config.js (100%) rename {view => web/static}/vite.config.js (100%) rename {view => web/static}/yarn.lock (99%) create mode 100644 web/web.go diff --git a/.air.toml b/.air.toml index 1641df7..2154641 100644 --- a/.air.toml +++ b/.air.toml @@ -7,7 +7,7 @@ tmp_dir = "tmp" [build] # Just plain old shell command. You could use `make` as well. -cmd = "go build -tags dev -o ./tmp/main ." +cmd = "go build -o ./tmp/main ." # Binary file yields from `cmd`. bin = "tmp/main" # Customize binary. @@ -15,7 +15,7 @@ full_bin = "APP_ENV=dev APP_USER=air ./tmp/main server -vv" # Watch these filename extensions. include_ext = ["go", "tpl", "tmpl", "html", "vue", "js", "css", "woff", "ttf"] # Ignore these filename extensions or directories. -exclude_dir = ["tmp", "libs", "vendor", "view"] +exclude_dir = ["tmp", "libs", "vendor", "view", "web/static"] # Watch these directories if you specified. include_dir = [] # Exclude files. diff --git a/.github/workflows/beta.yml b/.github/workflows/beta.yml deleted file mode 100644 index 7a82ece..0000000 --- a/.github/workflows/beta.yml +++ /dev/null @@ -1,131 +0,0 @@ -name: "🚀 Beta" - -# on events -on: - workflow_dispatch: - -# jobs -jobs: - build: - name: Generate cross-platform builds - permissions: - contents: write - strategy: - matrix: - go_version: [1.21.x] - runs-on: ubuntu-latest - outputs: - VERSION: ${{steps.vars.outputs.VERSION}} - BUILDDATE: ${{steps.vars.outputs.BUILDDATE}} - COMMIT: ${{steps.vars.outputs.COMMIT}} - APP_NAME: ${{steps.vars.outputs.APP_NAME}} - PUSH_DOCKERHUB: ${{steps.vars.outputs.PUSH_DOCKERHUB}} - steps: - # step 1: checkout repository code - - name: Checkout the repository - uses: actions/checkout@v3 - - # step 2: setup build envirement - - uses: actions/setup-go@v3 - with: - go-version: ${{ matrix.go_version }} - - uses: actions/setup-node@v2 - with: - node-version: "16" - - name: Set up QEMU - uses: docker/setup-qemu-action@v2 - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v2 - - # step 3: set workflow variables - - id: metadata - uses: ahmadnassri/action-metadata@v2 - - name: Initialize workflow environments variables - id: vars - run: | - echo "VERSION=${{ github.ref_name }}" >> $GITHUB_OUTPUT - echo "BUILDDATE=$(date '+%F-%T')" >> $GITHUB_OUTPUT - echo "COMMIT=$(git rev-parse --verify HEAD)" >> $GITHUB_OUTPUT - echo "APP_NAME=${{ steps.metadata.outputs.repository_name }}" >> $GITHUB_OUTPUT - echo "REPO=$(echo 'github.com/${{ github.repository }}')" >> $GITHUB_OUTPUT - echo "BRANCH=${{ steps.metadata.outputs.repository_default_branch }}" >> $GITHUB_OUTPUT - - # step 4: generate build files - - name: build frontend - run: cd ./view && npm install && npm run build - - name: Generate build files - uses: crazy-max/ghaction-xgo@v2 - env: - CGO_ENABLED: "0" - with: - xgo_version: latest - go_version: ${{ matrix.go_version }} - dest: build - prefix: ${{steps.vars.outputs.APP_NAME}} - targets: windows/amd64,linux/amd64,darwin/amd64,linux/arm64 - v: true - x: false - ldflags: -w -s -X ${{steps.vars.outputs.REPO}}/internal/version.Version=${{steps.vars.outputs.VERSION}} -X ${{steps.vars.outputs.REPO}}/internal/version.BuildDate=${{steps.vars.outputs.BUILDDATE}} -X ${{steps.vars.outputs.REPO}}/internal/version.Commit=${{steps.vars.outputs.COMMIT}} -X ${{steps.vars.outputs.REPO}}/internal/mode.Mode=production - - - name: Upload artifact - uses: actions/upload-artifact@v3 - with: - name: ${{steps.vars.outputs.APP_NAME}} - path: build - retention-days: 1 - - ghcr: - name: Push to GitHub Container Registry - needs: [build] - permissions: - contents: read - packages: write - runs-on: ubuntu-latest - steps: - - name: Checkout the repository - uses: actions/checkout@v3 - - name: Download artifact - uses: actions/download-artifact@v3 - with: - name: ${{needs.build.outputs.APP_NAME}} - path: build - - name: Display structure of downloaded files - run: ls -R - working-directory: build - - name: Login to GitHub Container Registry - uses: docker/login-action@v2 - with: - registry: ghcr.io - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - name: Extract metadata (tags, labels) for Docker - id: meta - uses: docker/metadata-action@v4 - with: - images: ghcr.io/${{ github.repository }} - - name: Build and push Docker images to ghci - uses: docker/build-push-action@v4 - with: - context: . - push: true - provenance: false - platforms: linux/amd64 - tags: ghcr.io/${{ github.repository }}:beta - labels: ${{ steps.meta.outputs.labels }} - build-args: | - APP_NAME=${{needs.build.outputs.APP_NAME}} - VERSION=${{needs.build.outputs.VERSION}} - BUILDDATE=${{needs.build.outputs.BUILDDATE}} - COMMIT=${{needs.build.outputs.COMMIT}} - - clean: - name: Delete temp artifacts - needs: [build, ghcr] - permissions: - contents: write - runs-on: ubuntu-latest - steps: - - uses: geekyeggo/delete-artifact@v2 - with: - name: ${{needs.build.outputs.APP_NAME}} - failOnError: false diff --git a/.github/workflows/release-nightly.yml b/.github/workflows/release-nightly.yml new file mode 100644 index 0000000..c81320a --- /dev/null +++ b/.github/workflows/release-nightly.yml @@ -0,0 +1,240 @@ +name: "🚀 Release Nightly" + +# on events +on: + schedule: + - cron: '0 0 * * *' # runs daily at 00:00 + workflow_dispatch: + push: + branches: [main, master, release/v*] + +# jobs +jobs: + check: + name: Check has new commits today + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Get new commits + run: echo "NEW_COMMIT_COUNT=$(git log --oneline --since '24 hours ago' | wc -l)" >> $GITHUB_OUTPUT + + build: + name: Generate cross-platform builds + if: ${{needs.check.outputs.NEW_COMMIT_COUNT > 0}} + permissions: + contents: write + strategy: + matrix: + go_version: [1.21.x] + runs-on: ubuntu-latest + outputs: + VERSION: ${{steps.vars.outputs.VERSION}} + BUILDDATE: ${{steps.vars.outputs.BUILDDATE}} + COMMIT: ${{steps.vars.outputs.COMMIT}} + APP_NAME: ${{steps.vars.outputs.APP_NAME}} + PUSH_DOCKERHUB: ${{steps.vars.outputs.PUSH_DOCKERHUB}} + steps: + # step 1: checkout repository code + - name: Checkout the repository + uses: actions/checkout@v3 + with: + fetch-depth: 0 + + # step 2: setup build envirement + - uses: actions/setup-go@v3 + with: + go-version: ${{ matrix.go_version }} + - uses: actions/setup-node@v2 + with: + node-version: "16" + - name: Set up QEMU + uses: docker/setup-qemu-action@v2 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v2 + + # step 3: set workflow variables + - id: metadata + uses: ahmadnassri/action-metadata@v2 + - name: Initialize workflow environments variables + id: vars + run: | + echo "VERSION=${{ github.ref_name }}" >> $GITHUB_OUTPUT + echo "BUILDDATE=$(date '+%F-%T')" >> $GITHUB_OUTPUT + echo "COMMIT=$(git rev-parse --verify HEAD)" >> $GITHUB_OUTPUT + echo "APP_NAME=${{ steps.metadata.outputs.repository_name }}" >> $GITHUB_OUTPUT + echo "REPO=$(echo 'github.com/${{ github.repository }}')" >> $GITHUB_OUTPUT + echo "BRANCH=${{ steps.metadata.outputs.repository_default_branch }}" >> $GITHUB_OUTPUT + + if [ ! -z $DOCKER_TOKEN ]; then echo "PUSH_DOCKERHUB=1" >> $GITHUB_OUTPUT; fi + env: + DOCKER_TOKEN: "${{ secrets.DOCKER_TOKEN }}" + + # step 4: generate build files + - name: build frontend + run: cd ./view && npm install && npm run build + - name: Generate build files + uses: crazy-max/ghaction-xgo@v2 + env: + CGO_ENABLED: "0" + with: + xgo_version: latest + go_version: ${{ matrix.go_version }} + dest: build + prefix: ${{steps.vars.outputs.APP_NAME}} + targets: windows/386,windows/amd64,linux/386,linux/amd64,darwin/386,darwin/amd64,linux/386,linux/arm64 + v: true + x: false + ldflags: -w -s -X ${{steps.vars.outputs.REPO}}/internal/app/build.Version=${{steps.vars.outputs.VERSION}} -X ${{steps.vars.outputs.REPO}}/internal/app/build.BuildDate=${{steps.vars.outputs.BUILDDATE}} -X ${{steps.vars.outputs.REPO}}/internal/app/build.Commit=${{steps.vars.outputs.COMMIT}} -X ${{steps.vars.outputs.REPO}}/internal/mode.Mode=production + + # step 5: Upload binary to artifact + - name: Upload artifact + uses: actions/upload-artifact@v3 + with: + name: ${{steps.vars.outputs.APP_NAME}} + path: build + retention-days: 1 + + # step 6: Generate Changelog + - name: Generate Changelog + id: changelog + uses: bitxeno/changelogithub-action@v1 + with: + token: ${{ secrets.GITHUB_TOKEN }} + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} + # output-file: ./docs/CHANGELOG.md + types: | + feat + fix + perf + refactor + chore + docs + # build + # test + # style + # ci + # - name: Git commit changelog + # uses: EndBug/add-and-commit@v9 + # with: + # default_author: github_actions + # add: "docs/" + # message: "docs: release notes for ${{ github.ref_name }}" + # push: "origin HEAD:${{ steps.vars.outputs.BRANCH }}" + + # step 7: Upload binary to GitHub Release + - name: Compress build files + run: cd ./build && for i in *; do tar -czf $i.tar.gz $i; done && cd .. + - name: Upload binary to GitHub Release + uses: softprops/action-gh-release@v2 + if: "startsWith(github.ref, 'refs/tags/')" + with: + files: | + ./build/*.tar.gz + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} + body: ${{ steps.changelog.outputs.changelog }} + fail_on_unmatched_files: true + + dockerhub: + name: Push to DockerHub + if: ${{needs.build.outputs.PUSH_DOCKERHUB}} + needs: [build] + permissions: + contents: read + packages: write + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: ${{needs.build.outputs.APP_NAME}} + path: build + - name: Login to DockerHub + if: ${{ steps.vars.outputs.HAS_DOCKER_TOKEN }} + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY }} + - name: Build and push Docker images to DockerHub + if: ${{ steps.vars.outputs.HAS_DOCKER_TOKEN }} + uses:docker/build-push-action@v5 + with: + context: . + push: true + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + APP_NAME=${{needs.build.outputs.APP_NAME}} + VERSION=${{needs.build.outputs.VERSION}} + BUILDDATE=${{needs.build.outputs.BUILDDATE}} + COMMIT=${{needs.build.outputs.COMMIT}} + + ghcr: + name: Push to GitHub Container Registry + needs: [build] + permissions: + contents: read + packages: write + runs-on: ubuntu-latest + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + - name: Download artifact + uses: actions/download-artifact@v3 + with: + name: ${{needs.build.outputs.APP_NAME}} + path: build + - name: Display structure of downloaded files + run: ls -R + working-directory: build + - name: Login to GitHub Container Registry + uses: docker/login-action@v2 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ghcr.io/${{ github.repository }} + - name: Build and push Docker images to ghci + uses: docker/build-push-action@v5 + with: + context: . + push: true + provenance: false + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + build-args: | + APP_NAME=${{needs.build.outputs.APP_NAME}} + VERSION=${{needs.build.outputs.VERSION}} + BUILDDATE=${{needs.build.outputs.BUILDDATE}} + COMMIT=${{needs.build.outputs.COMMIT}} + + clean: + name: Delete temp artifacts + # ignore dockerhub job skipped + if: always() && (needs.dockerhub.result == 'success' || needs.dockerhub.result == 'skipped') && (needs.ghcr.result == 'success') + needs: [build, dockerhub, ghcr] + permissions: + contents: write + runs-on: ubuntu-latest + steps: + - uses: geekyeggo/delete-artifact@v2 + with: + name: ${{needs.build.outputs.APP_NAME}} + failOnError: false diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index b8e5290..0a64c7a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -72,7 +72,7 @@ jobs: targets: windows/386,windows/amd64,linux/386,linux/amd64,darwin/386,darwin/amd64,linux/386,linux/arm64 v: true x: false - ldflags: -w -s -X ${{steps.vars.outputs.REPO}}/internal/version.Version=${{steps.vars.outputs.VERSION}} -X ${{steps.vars.outputs.REPO}}/internal/version.BuildDate=${{steps.vars.outputs.BUILDDATE}} -X ${{steps.vars.outputs.REPO}}/internal/version.Commit=${{steps.vars.outputs.COMMIT}} -X ${{steps.vars.outputs.REPO}}/internal/mode.Mode=production + ldflags: -w -s -X ${{steps.vars.outputs.REPO}}/internal/app/build.Version=${{steps.vars.outputs.VERSION}} -X ${{steps.vars.outputs.REPO}}/internal/app/build.BuildDate=${{steps.vars.outputs.BUILDDATE}} -X ${{steps.vars.outputs.REPO}}/internal/app/build.Commit=${{steps.vars.outputs.COMMIT}} -X ${{steps.vars.outputs.REPO}}/internal/mode.Mode=production # step 5: Upload binary to artifact - name: Upload artifact @@ -95,14 +95,12 @@ jobs: fix perf refactor - tweak - # docs + chore + docs # build # test # style # ci - # chore - # improve # - name: Git commit changelog # uses: EndBug/add-and-commit@v9 # with: @@ -115,12 +113,12 @@ jobs: - name: Compress build files run: cd ./build && for i in *; do tar -czf $i.tar.gz $i; done && cd .. - name: Upload binary to GitHub Release - uses: softprops/action-gh-release@v1 + uses: softprops/action-gh-release@v2 if: "startsWith(github.ref, 'refs/tags/')" with: files: | ./build/*.tar.gz - prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }}\ + prerelease: ${{ contains(github.ref, 'alpha') || contains(github.ref, 'beta') }} body: ${{ steps.changelog.outputs.changelog }} fail_on_unmatched_files: true @@ -146,14 +144,20 @@ jobs: with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_TOKEN }} + - name: Extract metadata (tags, labels) for Docker + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY }} - name: Build and push Docker images to DockerHub if: ${{ steps.vars.outputs.HAS_DOCKER_TOKEN }} - uses: docker/build-push-action@v4 + uses:docker/build-push-action@v5 with: context: . push: true - platforms: linux/amd64 - tags: ${{ secrets.DOCKER_USERNAME }}/${{ secrets.DOCKER_REPOSITORY }}:latest + platforms: linux/amd64,linux/arm64 + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} build-args: | APP_NAME=${{needs.build.outputs.APP_NAME}} VERSION=${{needs.build.outputs.VERSION}} @@ -186,16 +190,16 @@ jobs: password: ${{ secrets.GITHUB_TOKEN }} - name: Extract metadata (tags, labels) for Docker id: meta - uses: docker/metadata-action@v4 + uses: docker/metadata-action@v5 with: images: ghcr.io/${{ github.repository }} - name: Build and push Docker images to ghci - uses: docker/build-push-action@v4 + uses: docker/build-push-action@v5 with: context: . push: true provenance: false - platforms: linux/amd64 + platforms: linux/amd64,linux/arm64 tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} build-args: | diff --git a/.gitignore b/.gitignore index 7f858af..04bd1dc 100644 --- a/.gitignore +++ b/.gitignore @@ -17,7 +17,8 @@ vendor/ tmp/ dist/ -build/ +view/build/ +web/build/ node_modules/ go-docker-skeleton main \ No newline at end of file diff --git a/internal/cmd/gen.go b/cmd/gen/gen.go similarity index 82% rename from internal/cmd/gen.go rename to cmd/gen/gen.go index 1cbba0f..d60b060 100644 --- a/internal/cmd/gen.go +++ b/cmd/gen/gen.go @@ -1,11 +1,11 @@ -package cmd +package gen import ( "github.com/urfave/cli/v2" ) var ( - genFlags = []cli.Flag{ + flags = []cli.Flag{ &cli.StringFlag{ Name: "config", Aliases: []string{"c"}, @@ -14,15 +14,15 @@ var ( }, } - genCommand = &cli.Command{ + Command = &cli.Command{ Name: "gen", Usage: "Generate example server code", - Flags: genFlags, - Action: generateAction, + Flags: flags, + Action: action, } ) -func generateAction(c *cli.Context) error { +func action(c *cli.Context) error { // if utils.Exists(app.ConfigFilePath) { // fmt.Printf("Config has exist. >>> %s\n", app.ConfigFilePath) // return nil diff --git a/cmd/server/server.go b/cmd/server/server.go new file mode 100644 index 0000000..2deaaac --- /dev/null +++ b/cmd/server/server.go @@ -0,0 +1,80 @@ +package server + +import ( + "github.com/bitxeno/go-docker-skeleton/internal/app" + "github.com/bitxeno/go-docker-skeleton/web" + "github.com/urfave/cli/v2" +) + +var ( + flags = []cli.Flag{ + &cli.IntFlag{ + Name: "port", + Aliases: []string{"p"}, + Usage: "Define an alternate web server port", + }, + &cli.StringFlag{ + Name: "config", + Aliases: []string{"c"}, + Usage: "Load configuration from `FILE`", + }, + &cli.BoolFlag{ + Name: "debug", + Aliases: []string{"vv"}, + Usage: "Enable debug output", + Value: false, + }, + &cli.BoolFlag{ + Name: "verbose", + Aliases: []string{"vvv"}, + Usage: "Enable verbose output", + Value: false, + }, + } + + Command = &cli.Command{ + Name: "server", + Usage: "Run web server", + Flags: flags, + Action: action, + } +) + +func action(c *cli.Context) error { + // init config + debug := false + if c.Bool("debug") || c.Bool("verbose") { + debug = true + } + conf, err := app.InitConfig(c.String("config"), debug) + if err != nil { + return err + } + if err = app.InitSettings(conf, debug); err != nil { + return err + } + + // init logger + if c.Bool("debug") { + conf.Log.Level = "debug" + conf.Db.Debug = true + } + if c.Bool("verbose") { + conf.Log.Level = "trace" + conf.Db.Debug = true + } + if err := app.InitLogger(conf); err != nil { + return err + } + + // init db + if err := app.InitDb(conf); err != nil { + return err + } + + port := conf.Server.Port + if c.Int("port") > 0 { + port = c.Int("port") + } + return web.Run(conf.Server.ListenAddr, port) +} diff --git a/config/config.go b/config/config.go deleted file mode 100644 index b0c1286..0000000 --- a/config/config.go +++ /dev/null @@ -1,8 +0,0 @@ -package config - -func Load() error { - if err := loadCustom(); err != nil { - return err - } - return loadSettings() -} diff --git a/config/custom.go b/config/custom.go deleted file mode 100644 index 1fe29b4..0000000 --- a/config/custom.go +++ /dev/null @@ -1,25 +0,0 @@ -package config - -import ( - "github.com/bitxeno/go-docker-skeleton/internal/app" - "github.com/creasty/defaults" -) - -var Custom CustomConfiguration - -type CustomConfiguration struct { - Test string `koanf:"test" default:"test"` -} - -func loadCustom() error { - // set default value - if err := defaults.Set(&Custom); err != nil { - return err - } - - // load from default config.yaml - if err := app.Cfg().BindStruct("custom", &Custom); err != nil { - return err - } - return nil -} diff --git a/fs_dev.go b/fs_dev.go deleted file mode 100644 index 6a4d980..0000000 --- a/fs_dev.go +++ /dev/null @@ -1,12 +0,0 @@ -//go:build dev - -package main - -import ( - "io/fs" - "os" -) - -func ViewAssets() fs.FS { - return os.DirFS("view/dist") -} diff --git a/fs_prod.go b/fs_prod.go deleted file mode 100644 index 8552fbb..0000000 --- a/fs_prod.go +++ /dev/null @@ -1,20 +0,0 @@ -//go:build !dev - -package main - -import ( - "embed" - "io/fs" -) - -//go:embed all:view/dist/* -var view embed.FS - -func ViewAssets() fs.FS { - embed, err := fs.Sub(view, "view/dist") - if err != nil { - panic(err) - } - - return embed -} diff --git a/go.mod b/go.mod index 22f4fba..d4e5b49 100644 --- a/go.mod +++ b/go.mod @@ -6,6 +6,7 @@ require ( github.com/creasty/defaults v1.5.2 github.com/fatih/color v1.9.0 github.com/glebarez/sqlite v1.10.0 + github.com/go-errors/errors v1.5.1 github.com/go-resty/resty/v2 v2.6.0 github.com/gofiber/contrib/websocket v1.0.0 github.com/gofiber/fiber/v2 v2.46.0 diff --git a/go.sum b/go.sum index 02992aa..6a47173 100644 --- a/go.sum +++ b/go.sum @@ -65,6 +65,8 @@ github.com/glebarez/go-sqlite v1.21.2 h1:3a6LFC4sKahUunAmynQKLZceZCOzUthkRkEAl9g github.com/glebarez/go-sqlite v1.21.2/go.mod h1:sfxdZyhQjTM2Wry3gVYWaW072Ri1WMdWJi0k6+3382k= github.com/glebarez/sqlite v1.10.0 h1:u4gt8y7OND/cCei/NMHmfbLxF6xP2wgKcT/BJf2pYkc= github.com/glebarez/sqlite v1.10.0/go.mod h1:IJ+lfSOmiekhQsFTJRx/lHtGYmCdtAiTaf5wI9u5uHA= +github.com/go-errors/errors v1.5.1 h1:ZwEMSLRCapFLflTpT7NKaAc7ukJ8ZPEjzlxt8rPN8bk= +github.com/go-errors/errors v1.5.1/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= diff --git a/internal/app/app.go b/internal/app/app.go index e37d405..c4526d3 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1,136 +1,19 @@ package app import ( - "fmt" - - "github.com/bitxeno/go-docker-skeleton/internal/cfg" - "github.com/bitxeno/go-docker-skeleton/internal/cmd" "github.com/bitxeno/go-docker-skeleton/internal/db" - "github.com/bitxeno/go-docker-skeleton/internal/log" - "github.com/bitxeno/go-docker-skeleton/internal/mode" - "github.com/bitxeno/go-docker-skeleton/internal/version" - "github.com/fatih/color" - "github.com/gofiber/fiber/v2" - "github.com/urfave/cli/v2" "gorm.io/gorm" ) -var instance *Application - -type Application struct { - Name string - Desc string - Version version.Info - Mode mode.AppMode // 用于加载不同环境变量和配置文件 - Port int - DebugLog bool - VerboseLog bool - - server *fiber.App - routers []RouteFunc -} - -func New(name string, desc string) *Application { - instance = &Application{ - Name: name, - Desc: desc, - Version: version.Get(), - Mode: mode.Get(), - DebugLog: false, - VerboseLog: false, - server: fiber.New(), - routers: []RouteFunc{}, - } - - return instance -} - -func (a *Application) AddBoot(fn BootFunc) { - addBoot(fn) -} - -func (a *Application) AddBoots(boots ...Bootstrapper) { - addBoots(boots...) -} - -func (a *Application) Route(fn RouteFunc) { - a.routers = append(a.routers, fn) -} - -func (a *Application) Run(arguments []string) (err error) { - return cmd.Run(a.Name, a.Desc, arguments, func(c *cli.Context) error { - a.Port = c.Int("port") - a.DebugLog = c.Bool("debug") - a.VerboseLog = c.Bool("verbose") - - a.runWeb(c.String("config")) - return nil - }) -} - -func (a *Application) runWeb(configFile string) { - // load config. 配置优先级:命令行参数 > 环境变量 > 配置文件 - if configFile != "" { - cfg.Server.SetPath(configFile) - } - cfg.Server.Load() - - // run bootstrap middleware - bootLauncher.Prepend( - BootFunc(initLogger), - BootFunc(initDb), - ) - bootLauncher.Run() - - // add web router - for _, router := range a.routers { - router(a.server) - } - - // run web server - listenPort := a.Port - if listenPort <= 0 { - listenPort = cfg.Server.Port - } - - a.printAppVersion() - err := a.server.Listen(fmt.Sprintf("%s:%d", cfg.Server.ListenAddr, listenPort)) - if err != nil { - log.Error(err.Error()) - } -} - -func (a *Application) printAppVersion() { - color.New(color.FgGreen).Printf("Starting %s version: ", a.Name) - color.New(color.FgCyan).Printf("%s@%s@%v\n", a.Version.Version, a.Version.BuildDate, a.Mode) -} - -func (a *Application) SetConfigPath(path string) { - cfg.Server.SetPath(path) -} - -func Name() string { - return instance.Name -} - -func Version() version.Info { - return instance.Version -} - -func Environment() mode.AppMode { - return instance.Mode -} - -func DevelopmentMode() bool { - return mode.IsDevelopmentMode() +func Environment() AppMode { + return Mode } func ReloadConfig() { - cfg.Server.Reload() } -func Cfg() *cfg.ServerConfiguration { - return cfg.Server +func Cfg() *Configuration { + return Config } func Db() *gorm.DB { diff --git a/internal/app/booter.go b/internal/app/booter.go deleted file mode 100644 index 4b41126..0000000 --- a/internal/app/booter.go +++ /dev/null @@ -1,45 +0,0 @@ -package app - -import "github.com/bitxeno/go-docker-skeleton/internal/log" - -var bootLauncher = &Launcher{} - -type Bootstrapper interface { - // Name() string - Boot() error -} - -type BootFunc func() error - -func (bf BootFunc) Boot() error { - return bf() -} - -type Launcher struct { - Boots []Bootstrapper -} - -func (l *Launcher) Add(boots ...Bootstrapper) { - l.Boots = append(l.Boots, boots...) -} - -func (l *Launcher) Prepend(boots ...Bootstrapper) { - l.Boots = append(boots, l.Boots...) -} - -func (l *Launcher) Run() { - for _, boot := range l.Boots { - err := boot.Boot() - if err != nil { - log.Panic(err.Error()) - } - } -} - -func addBoots(boots ...Bootstrapper) { - bootLauncher.Boots = append(bootLauncher.Boots, boots...) -} - -func addBoot(fn BootFunc) { - bootLauncher.Boots = append(bootLauncher.Boots, BootFunc(fn)) -} diff --git a/internal/app/bootstrap.go b/internal/app/bootstrap.go index f7ea76b..c733983 100644 --- a/internal/app/bootstrap.go +++ b/internal/app/bootstrap.go @@ -1,34 +1,80 @@ package app import ( + "path/filepath" + "github.com/bitxeno/go-docker-skeleton/internal/cfg" "github.com/bitxeno/go-docker-skeleton/internal/log" - "github.com/bitxeno/go-docker-skeleton/internal/mode" - "github.com/gofiber/fiber/v2/middleware/logger" + "github.com/creasty/defaults" ) -func initLogger() error { +// load config from file +func InitConfig(path string, debug bool) (*Configuration, error) { + var configuration Configuration + if err := defaults.Set(&configuration); err != nil { + return nil, err + } + c, err := cfg.Load(path) + if err != nil { + return nil, err + } + if err := c.BindStruct(&configuration); err != nil { + return nil, err + } + Config = &configuration + + if debug { + c.PrintConfig() + } + + return &configuration, nil +} + +// load settings from file +func InitSettings(conf *Configuration, debug bool) error { + var settings SettingsConfiguration + if err := defaults.Set(&settings); err != nil { + return err + } + + confDir := conf.Server.DataDir + if confDir == "" { + confDir = cfg.DefaultConfigDir() + } + path := filepath.Join(confDir, "settings.json") + c, err := cfg.Load(path) + if err != nil { + return err + } + if err := c.BindStruct(&settings); err != nil { + return err + } + go startSaveSettingsJob(path) + Settings = &settings + + if debug { + c.PrintConfig() + } + + return nil +} + +func InitLogger(conf *Configuration) error { // set normal log - log.AddFileOutput(cfg.Server.Log) - if instance.DebugLog || mode.IsDevelopmentMode() { + log.AddFileOutput(conf.Log.LogFile) + if conf.Log.Level == "debug" { log.SetDebugLevel() } - if instance.VerboseLog { + if conf.Log.Level == "trace" { log.SetTraceLevel() } - - // set fiber web server access log - instance.server.Use(logger.New()) - accessWriter := log.CreateRollingLogFile(cfg.Server.AccessLog) - if accessWriter != nil { - instance.server.Use(logger.New(logger.Config{ - Output: accessWriter, - })) - log.Infof("Web access log file path: %s", cfg.Server.AccessLog) - } return nil } -func initDb() error { +func InitDb(conf *Configuration) error { + // if err := db.Open(conf.Db).AutoMigrate(&model.User{}); err != nil { + // return err + // } + return nil } diff --git a/internal/app/build/var.go b/internal/app/build/var.go new file mode 100644 index 0000000..438b743 --- /dev/null +++ b/internal/app/build/var.go @@ -0,0 +1,11 @@ +package build + +var ( + /*********Will auto update by ci build *********/ + Version = "unknown" + Commit = "unknown" + BuildDate = "unknown" + + Mode = "development" + /*********Will auto update by ci build *********/ +) diff --git a/internal/app/config.go b/internal/app/config.go new file mode 100644 index 0000000..185ebde --- /dev/null +++ b/internal/app/config.go @@ -0,0 +1,26 @@ +package app + +import "github.com/bitxeno/go-docker-skeleton/internal/db" + +var ( + Config *Configuration +) + +// configuration holds any kind of configuration that comes from the outside world and +// is necessary for running the application. +type Configuration struct { + Log struct { + Level string `koanf:"level" default:"info"` + TimeFormat string `koanf:"time_format" default:"2006-01-02 15:04:05.000"` + LogFile string `koanf:"log_file"` + AccessLog string `koanf:"access_log"` + } `koanf:"log" json:"log"` + + Server struct { + ListenAddr string `koanf:"listen_addr" default:"0.0.0.0"` + Port int `koanf:"port" default:"9000"` + DataDir string `koanf:"data_dir"` + } `koanf:"app" json:"app"` + + Db db.Config `koanf:"db" json:"db"` +} diff --git a/internal/app/const.go b/internal/app/const.go new file mode 100644 index 0000000..1161a2a --- /dev/null +++ b/internal/app/const.go @@ -0,0 +1,3 @@ +package app + +const () diff --git a/internal/app/mode.go b/internal/app/mode.go new file mode 100644 index 0000000..9238c4a --- /dev/null +++ b/internal/app/mode.go @@ -0,0 +1,25 @@ +package app + +import "github.com/bitxeno/go-docker-skeleton/internal/app/build" + +const ( + DevelopmentMode AppMode = "development" + ProductionMode AppMode = "production" + TestMode AppMode = "test" +) + +var Mode = AppMode(build.Mode) + +type AppMode string + +func IsDevelopmentMode() bool { + return build.Mode == string(DevelopmentMode) +} + +func IsTestMode() bool { + return build.Mode == string(TestMode) +} + +func IsProductionMode() bool { + return build.Mode == string(ProductionMode) +} diff --git a/internal/app/router.go b/internal/app/router.go deleted file mode 100644 index 6eaab8a..0000000 --- a/internal/app/router.go +++ /dev/null @@ -1,5 +0,0 @@ -package app - -import "github.com/gofiber/fiber/v2" - -type RouteFunc func(*fiber.App) diff --git a/config/settings.go b/internal/app/settings.go similarity index 60% rename from config/settings.go rename to internal/app/settings.go index e452de4..9a6197a 100644 --- a/config/settings.go +++ b/internal/app/settings.go @@ -1,44 +1,25 @@ -package config +package app import ( "io/ioutil" "math" "os" - "path/filepath" "time" - "github.com/bitxeno/go-docker-skeleton/internal/cfg" "github.com/bitxeno/go-docker-skeleton/internal/log" "github.com/bitxeno/go-docker-skeleton/internal/utils" - "github.com/creasty/defaults" ) -var Settings SettingsConfiguration +var ( + Settings *SettingsConfiguration +) var saveTimer *time.Timer = time.NewTimer(math.MaxInt64) +// Settings holds any kind of configuration that can modify by admin web ui. type SettingsConfiguration struct { Test string `koanf:"test" default:"test"` } -func loadSettings() error { - // load from settings.json file - conf := cfg.New() - conf.SetPath(filepath.Join(cfg.Server.WorkDir, "settings.json")) - conf.Load() - - // set default value - if err := defaults.Set(&Settings); err != nil { - return err - } - if err := conf.BindStruct("", &Settings); err != nil { - return err - } - - saveTimer.Stop() - go startSaveSettingsJob(conf.Path()) - return nil -} - func SaveSettings() { saveTimer.Reset(100 * time.Millisecond) } diff --git a/internal/app/version.go b/internal/app/version.go new file mode 100644 index 0000000..ecfb3b9 --- /dev/null +++ b/internal/app/version.go @@ -0,0 +1,16 @@ +package app + +import "github.com/bitxeno/go-docker-skeleton/internal/app/build" + +var Version = AppVersion{ + Commit: build.Commit, + Version: build.Version, + BuildDate: build.BuildDate, +} + +// Info holds build information +type AppVersion struct { + Commit string `json:"commit"` + Version string `json:"version"` + BuildDate string `json:"build_date"` +} diff --git a/internal/cfg/cfg.go b/internal/cfg/cfg.go index 1791144..fbfb8b8 100644 --- a/internal/cfg/cfg.go +++ b/internal/cfg/cfg.go @@ -2,88 +2,83 @@ package cfg import ( "fmt" + "log" "path/filepath" - "github.com/bitxeno/go-docker-skeleton/internal/log" "github.com/bitxeno/go-docker-skeleton/internal/utils" + "github.com/creasty/defaults" + "github.com/go-errors/errors" "github.com/knadh/koanf" "github.com/knadh/koanf/parsers/json" "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/providers/file" ) -type Configuration struct { +type configuration struct { ko *koanf.Koanf path string } -func New() *Configuration { - return &Configuration{ +func New() *configuration { + return &configuration{ ko: koanf.New("."), - path: filepath.Join(defaultConfigDir(), "config.yaml"), + path: filepath.Join(DefaultConfigDir(), "config.yaml"), } } -func (c *Configuration) SetPath(path string) { - if filepath.IsAbs(path) { +func (c *configuration) load(path string) error { + if path != "" { c.path = path - } else { - c.path = filepath.Join(defaultConfigDir(), path) } -} - -func (c *Configuration) Path() string { - return c.path -} - -func (c *Configuration) Load() { - // read config - if utils.Exists(c.path) { - ext := filepath.Ext(c.path) - if ext == ".yaml" || ext == ".yml" { - if err := c.ko.Load(file.Provider(c.path), yaml.Parser()); err != nil { - log.Panicf("Yaml config file read failed. error: %s \n", err) - } - } - if ext == ".json" { - if err := c.ko.Load(file.Provider(c.path), json.Parser()); err != nil { - log.Panicf("Json config file read failed. error: %s \n", err) - } - } - } else { - fmt.Printf("Config file not exists. file: %s\n", c.path) + // set default value + if err := defaults.Set(c); err != nil { + return errors.New(err) } - c.printConfig() -} - -func (c *Configuration) MustLoad() { + // read config from file + ko := koanf.New(".") if !utils.Exists(c.path) { - log.Panicf("Config file not exists. file: %s \n", c.path) + fmt.Printf("[WARN] Config file not exists. path: %s\n", c.path) + return nil } - // read config + fmt.Printf("Load config from path: %s\n", c.path) ext := filepath.Ext(c.path) if ext == ".yaml" || ext == ".yml" { - if err := c.ko.Load(file.Provider(c.path), yaml.Parser()); err != nil { - log.Panicf("Config file read failed. error: %s \n", err) + if err := ko.Load(file.Provider(c.path), yaml.Parser()); err != nil { + log.Panicf("Yaml config file read failed. error: %s \n", err) } } - if ext == ".json" { - if err := c.ko.Load(file.Provider(c.path), json.Parser()); err != nil { - log.Panicf("Config file read failed. error: %s \n", err) + if err := ko.Load(file.Provider(c.path), json.Parser()); err != nil { + log.Panicf("Json config file read failed. error: %s \n", err) } } - c.printConfig() + return ko.Unmarshal("", &c) +} + +func (c *configuration) BindStruct(dst any) error { + return c.ko.Unmarshal("", dst) } -func (c *Configuration) BindStruct(key string, dst any) error { - return c.ko.Unmarshal(key, dst) +func (c *configuration) Reload() { + c.load("") } -func (c *Configuration) Reload() { - c.Load() +func (c *configuration) PrintConfig() { + configName := filepath.Base(c.path) + fmt.Printf("##################### Load %s begin #####################\n", configName) + c.ko.Print() + fmt.Printf("##################### Load %s end #####################\n", configName) +} + +func Load(path string) (*configuration, error) { + conf := New() + if err := conf.load(path); err != nil { + return nil, err + } + + return conf, nil } diff --git a/internal/cfg/cfg_darwin.go b/internal/cfg/cfg_darwin.go index 229dc0f..7681c2d 100644 --- a/internal/cfg/cfg_darwin.go +++ b/internal/cfg/cfg_darwin.go @@ -1,14 +1,17 @@ +//go:build darwin + package cfg import ( + "log" "os" "path/filepath" ) -func defaultConfigDir() string { +func DefaultConfigDir() string { execPath, err := os.Executable() if err != nil { - panic(err) + log.Panic(err) } execName := filepath.Base(execPath) diff --git a/internal/cfg/cfg_dev.go b/internal/cfg/cfg_dev.go deleted file mode 100644 index 2543062..0000000 --- a/internal/cfg/cfg_dev.go +++ /dev/null @@ -1,15 +0,0 @@ -//go:build dev - -package cfg - -import ( - "fmt" - "path/filepath" -) - -func (c *Configuration) printConfig() { - configName := filepath.Base(c.path) - fmt.Printf("##################### Load %s begin #####################\n", configName) - c.ko.Print() - fmt.Printf("##################### Load %s end #####################\n", configName) -} diff --git a/internal/cfg/cfg_linux.go b/internal/cfg/cfg_linux.go index 229dc0f..0ce99eb 100644 --- a/internal/cfg/cfg_linux.go +++ b/internal/cfg/cfg_linux.go @@ -1,14 +1,17 @@ +//go:build linux + package cfg import ( + "log" "os" "path/filepath" ) -func defaultConfigDir() string { +func DefaultConfigDir() string { execPath, err := os.Executable() if err != nil { - panic(err) + log.Panic(err) } execName := filepath.Base(execPath) diff --git a/internal/cfg/cfg_prod.go b/internal/cfg/cfg_prod.go deleted file mode 100644 index 252e95b..0000000 --- a/internal/cfg/cfg_prod.go +++ /dev/null @@ -1,7 +0,0 @@ -//go:build !dev - -package cfg - -func (c *Configuration) printConfig() { - -} diff --git a/internal/cfg/cfg_windows.go b/internal/cfg/cfg_windows.go index 48dd5d7..024a5f9 100644 --- a/internal/cfg/cfg_windows.go +++ b/internal/cfg/cfg_windows.go @@ -1,5 +1,7 @@ +//go:build windows + package cfg -func defaultConfigDir() string { +func DefaultConfigDir() string { return "." } diff --git a/internal/cfg/entity.go b/internal/cfg/entity.go deleted file mode 100644 index cc7238e..0000000 --- a/internal/cfg/entity.go +++ /dev/null @@ -1,73 +0,0 @@ -package cfg - -import ( - "path/filepath" - - "github.com/bitxeno/go-docker-skeleton/internal/log" - "github.com/creasty/defaults" -) - -var Server = newServerConfiguration() - -type ServerConfiguration struct { - Configuration - - ListenAddr string `koanf:"listen_addr"` - Port int `koanf:"port" default:"9000"` - TimeFormat string `koanf:"time_format" default:"2006-01-02 15:04:05"` - LogTimeFormat string `koanf:"log_time_format" default:"2006-01-02 15:04:05.000"` - WorkDir string `koanf:"work_dir"` - Log string `koanf:"log"` - AccessLog string `koanf:"access_log"` -} - -func newServerConfiguration() *ServerConfiguration { - return &ServerConfiguration{ - Configuration: *New(), - } -} - -func (c *ServerConfiguration) Load() { - c.Configuration.Load() - - // set default value - if err := defaults.Set(c); err != nil { - log.Panicf("Config set default failed. error: %s \n", err) - } - - if err := c.ko.Unmarshal("server", c); err != nil { - log.Panicf("Config unable to decode into struct, %v\n", err) - } - - // set default data dir - if c.WorkDir == "" { - c.WorkDir = defaultConfigDir() - } -} - -func (c *ServerConfiguration) MustLoad() { - c.Configuration.MustLoad() - - // set default value - if err := defaults.Set(c); err != nil { - log.Panicf("Config set default failed. error: %s \n", err) - } - - if err := c.ko.Unmarshal("server", c); err != nil { - log.Panicf("Config unable to decode into struct, %v\n", err) - } - - // set default data dir - if c.WorkDir == "" { - c.WorkDir = defaultConfigDir() - } -} - -func (c *ServerConfiguration) Reload() { - c.Load() -} - -func (c *ServerConfiguration) DbPath() string { - return filepath.Join(c.WorkDir, "app.db") - -} diff --git a/internal/cmd/main.go b/internal/cmd/main.go deleted file mode 100644 index b0bb992..0000000 --- a/internal/cmd/main.go +++ /dev/null @@ -1,27 +0,0 @@ -package cmd - -import ( - "os" - - "github.com/bitxeno/go-docker-skeleton/internal/version" - "github.com/urfave/cli/v2" -) - -func Run(name string, desc string, arguments []string, webServerAction cli.ActionFunc) (err error) { - cliApp := &cli.App{ - Name: name, - Usage: desc, - Version: version.Version, - Commands: []*cli.Command{ - genCommand, - { - Name: "server", - Usage: "Run web server", - Flags: serverFlags, - Action: webServerAction, - }, - }, - } - - return cliApp.Run(os.Args) -} diff --git a/internal/cmd/server.go b/internal/cmd/server.go deleted file mode 100644 index 589c411..0000000 --- a/internal/cmd/server.go +++ /dev/null @@ -1,32 +0,0 @@ -package cmd - -import ( - "github.com/urfave/cli/v2" -) - -var ( - serverFlags = []cli.Flag{ - &cli.IntFlag{ - Name: "port", - Aliases: []string{"p"}, - Usage: "web server port", - }, - &cli.StringFlag{ - Name: "config", - Aliases: []string{"c"}, - Usage: "Load configuration from `FILE`", - }, - &cli.BoolFlag{ - Name: "debug", - Aliases: []string{"vv"}, - Usage: "Change to debug log level", - Value: false, - }, - &cli.BoolFlag{ - Name: "verbose", - Aliases: []string{"vvv"}, - Usage: "Change to verbose log level", - Value: false, - }, - } -) diff --git a/internal/db/config.go b/internal/db/config.go new file mode 100644 index 0000000..4531278 --- /dev/null +++ b/internal/db/config.go @@ -0,0 +1,7 @@ +package db + +type Config struct { + Path string `koanf:"path"` + FileName string `koanf:"filename" default:"app.db"` + Debug bool `koanf:"debug" default:"false"` +} diff --git a/internal/db/db.go b/internal/db/db.go index abd5e2f..764e63a 100644 --- a/internal/db/db.go +++ b/internal/db/db.go @@ -5,9 +5,7 @@ import ( "os" "path/filepath" - "github.com/bitxeno/go-docker-skeleton/internal/cfg" "github.com/bitxeno/go-docker-skeleton/internal/log" - "github.com/bitxeno/go-docker-skeleton/internal/mode" "github.com/glebarez/sqlite" "gorm.io/gorm" "gorm.io/gorm/logger" @@ -18,10 +16,11 @@ var instance *sqliteDb type sqliteDb struct { db *gorm.DB path string + conf Config } -func new() *sqliteDb { - dbDir := cfg.Server.WorkDir +func new(conf Config) *sqliteDb { + dbDir := conf.Path if _, err := os.Stat(dbDir); err != nil { if err := os.MkdirAll(dbDir, os.ModePerm); err != nil { panic("failed to create database directory") @@ -29,7 +28,8 @@ func new() *sqliteDb { } return &sqliteDb{ - path: filepath.Join(dbDir, "app.db"), + path: filepath.Join(dbDir, conf.FileName), + conf: conf, } } @@ -44,7 +44,7 @@ func (s *sqliteDb) Open() *sqliteDb { } conf := &gorm.Config{Logger: logger.Default.LogMode(logger.Silent)} - if mode.Get() == mode.DevelopmentMode { + if s.conf.Debug { conf.Logger = logger.Default.LogMode(logger.Info) } @@ -79,14 +79,8 @@ func Store() *gorm.DB { return instance.db } -func Open() *sqliteDb { - instance = new().Open() - - return instance -} - -func OpenDb(path string) *sqliteDb { - instance = new().SetPath(path).Open() +func Open(conf Config) *sqliteDb { + instance = new(conf).Open() return instance } diff --git a/internal/mode/mode.go b/internal/mode/mode.go deleted file mode 100644 index 46e37c3..0000000 --- a/internal/mode/mode.go +++ /dev/null @@ -1,31 +0,0 @@ -package mode - -var ( - /*********Will auto update by ci build *********/ - Mode = "development" - /*********Will auto update by ci build *********/ -) - -const ( - DevelopmentMode AppMode = "development" - ProductionMode AppMode = "production" - TestMode AppMode = "test" -) - -type AppMode string - -func Get() AppMode { - return AppMode(Mode) -} - -func IsDevelopmentMode() bool { - return Mode == string(DevelopmentMode) -} - -func IsTestMode() bool { - return Mode == string(TestMode) -} - -func IsProductionMode() bool { - return Mode == string(ProductionMode) -} diff --git a/model/model.go b/internal/model/model.go similarity index 100% rename from model/model.go rename to internal/model/model.go diff --git a/internal/version/build.go b/internal/version/build.go deleted file mode 100644 index 808dd50..0000000 --- a/internal/version/build.go +++ /dev/null @@ -1,25 +0,0 @@ -package version - -var ( - /*********Will auto update by ci build *********/ - Version = "unknown" - Commit = "unknown" - BuildDate = "unknown" - /*********Will auto update by ci build *********/ -) - -// Info holds build information -type Info struct { - Commit string `json:"commit"` - Version string `json:"version"` - BuildDate string `json:"build_date"` -} - -// Get creates and initialized Info object -func Get() Info { - return Info{ - Commit: Commit, - Version: Version, - BuildDate: BuildDate, - } -} diff --git a/main.go b/main.go index 996fe1e..9b60757 100644 --- a/main.go +++ b/main.go @@ -4,13 +4,11 @@ import ( "fmt" "os" - "github.com/bitxeno/go-docker-skeleton/config" - "github.com/bitxeno/go-docker-skeleton/internal/app" - "github.com/bitxeno/go-docker-skeleton/internal/db" - _ "github.com/bitxeno/go-docker-skeleton/internal/log" - "github.com/bitxeno/go-docker-skeleton/model" - "github.com/bitxeno/go-docker-skeleton/router" - "github.com/gofiber/fiber/v2" + "github.com/bitxeno/go-docker-skeleton/cmd/gen" + "github.com/bitxeno/go-docker-skeleton/cmd/server" + "github.com/bitxeno/go-docker-skeleton/internal/app/build" + "github.com/go-errors/errors" + "github.com/urfave/cli/v2" ) const ( @@ -21,23 +19,22 @@ const ( ) func main() { - app := app.New(AppName, AppDesc) - app.Route(func(f *fiber.App) { - router.Create(f, ViewAssets()) - }) - app.AddBoot(func() error { - // init config - if err := config.Load(); err != nil { - return err - } - - // init db - return db.Open().AutoMigrate(&model.User{}) - }) + cliApp := &cli.App{ + Name: AppName, + Usage: AppDesc, + Version: build.Version, + Commands: []*cli.Command{ + gen.Command, + server.Command, + }, + } - if err := app.Run(os.Args); err != nil { - code := 1 - fmt.Fprintln(os.Stderr, err) - os.Exit(code) + if err := cliApp.Run(os.Args); err != nil { + if e, ok := err.(*errors.Error); ok { + fmt.Fprintln(os.Stderr, e.ErrorStack()) + } else { + fmt.Fprintln(os.Stderr, err) + } + os.Exit(1) } } diff --git a/router/api_result.go b/web/api_result.go similarity index 95% rename from router/api_result.go rename to web/api_result.go index 7a10311..c3b7027 100644 --- a/router/api_result.go +++ b/web/api_result.go @@ -1,4 +1,4 @@ -package router +package web type ApiResult struct { Code int `json:"code"` diff --git a/web/fs_dev.go b/web/fs_dev.go new file mode 100644 index 0000000..5c13a2a --- /dev/null +++ b/web/fs_dev.go @@ -0,0 +1,12 @@ +//go:build dev + +package web + +import ( + "io/fs" + "os" +) + +func StaticAssets() fs.FS { + return os.DirFS("static/dist") +} diff --git a/web/fs_prod.go b/web/fs_prod.go new file mode 100644 index 0000000..50cdfc3 --- /dev/null +++ b/web/fs_prod.go @@ -0,0 +1,20 @@ +//go:build !dev + +package web + +import ( + "embed" + "io/fs" +) + +//go:embed all:static/dist/* +var static embed.FS + +func StaticAssets() fs.FS { + embed, err := fs.Sub(static, "static/dist") + if err != nil { + panic(err) + } + + return embed +} diff --git a/router/router.go b/web/router.go similarity index 70% rename from router/router.go rename to web/router.go index 983ff66..8f35e7c 100644 --- a/router/router.go +++ b/web/router.go @@ -1,26 +1,25 @@ -package router +package web import ( - "io/fs" "log" "net/http" - "github.com/bitxeno/go-docker-skeleton/config" + "github.com/bitxeno/go-docker-skeleton/internal/app" "github.com/gofiber/contrib/websocket" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/filesystem" ) -func Create(app *fiber.App, f fs.FS) { - app.Use("/", filesystem.New(filesystem.Config{ - Root: http.FS(f), +func route(fi *fiber.App) { + fi.Use("/", filesystem.New(filesystem.Config{ + Root: http.FS(StaticAssets()), })) - app.Get("/hello", func(c *fiber.Ctx) error { + fi.Get("/hello", func(c *fiber.Ctx) error { return c.Status(200).SendString("hello world.") }) // websocket router - app.Use("/ws", func(c *fiber.Ctx) error { + fi.Use("/ws", func(c *fiber.Ctx) error { // IsWebSocketUpgrade returns true if the client // requested upgrade to the WebSocket protocol. if websocket.IsWebSocketUpgrade(c) { @@ -29,7 +28,7 @@ func Create(app *fiber.App, f fs.FS) { } return fiber.ErrUpgradeRequired }) - app.Get("/ws/:id", websocket.New(func(c *websocket.Conn) { + fi.Get("/ws/:id", websocket.New(func(c *websocket.Conn) { log.Println(c.Params("id")) // 123 log.Println(c.Query("v")) // 1.0 @@ -53,20 +52,23 @@ func Create(app *fiber.App, f fs.FS) { })) // api router - api := app.Group("/api") + api := fi.Group("/api") api.Get("/hello", func(c *fiber.Ctx) error { return c.SendString("hello world.") }) api.Get("/setting", func(c *fiber.Ctx) error { - return c.JSON(apiSuccess(config.Settings)) + return c.JSON(apiSuccess(app.Settings)) }) api.Post("/setting", func(c *fiber.Ctx) error { - var setting config.SettingsConfiguration + var setting app.SettingsConfiguration if err := c.BodyParser(&setting); err != nil { return c.JSON(apiError("Invalid argument. error: " + err.Error())) } - config.SaveSettings() + // update settings + app.Settings.Test = setting.Test + app.SaveSettings() + return c.JSON(apiSuccess(true)) }) } diff --git a/view/index.html b/web/static/index.html similarity index 100% rename from view/index.html rename to web/static/index.html diff --git a/view/package-lock.json b/web/static/package-lock.json similarity index 99% rename from view/package-lock.json rename to web/static/package-lock.json index a84af37..f12b48c 100644 --- a/view/package-lock.json +++ b/web/static/package-lock.json @@ -19,7 +19,7 @@ "@vitejs/plugin-vue": "^2.2.4", "@vue/compiler-sfc": "^3.2.31", "autoprefixer": "^10.4.2", - "daisyui": "^4.0.3", + "daisyui": "^4.12.2", "postcss": "^8.4.7", "tailwindcss": "^3.0.23", "unplugin-icons": "^0.16.5", @@ -756,9 +756,9 @@ } }, "node_modules/daisyui": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.0.3.tgz", - "integrity": "sha512-mG6PsdIA6MEHzdJwBlJxc1rqsIAAlcfhj2O8g0ol1uWg5y6C5zTcqfG8vKBqK4y2YfCxGIVgMsMWRTSm32N1Ow==", + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/daisyui/-/daisyui-4.12.2.tgz", + "integrity": "sha512-ed3EFwPRLN+9+/MYPRB1pYjk6plRCBMobfBdSeB3voAS81KdL2pCKtbwJfUUpDdOnJ0F8T6oRdVX02P6UCD0Hg==", "dev": true, "dependencies": { "css-selector-tokenizer": "^0.8", diff --git a/view/package.json b/web/static/package.json similarity index 96% rename from view/package.json rename to web/static/package.json index ebd6900..cab2d33 100644 --- a/view/package.json +++ b/web/static/package.json @@ -18,7 +18,7 @@ "@vitejs/plugin-vue": "^2.2.4", "@vue/compiler-sfc": "^3.2.31", "autoprefixer": "^10.4.2", - "daisyui": "^4.0.3", + "daisyui": "^4.12.2", "postcss": "^8.4.7", "tailwindcss": "^3.0.23", "unplugin-icons": "^0.16.5", diff --git a/view/postcss.config.js b/web/static/postcss.config.js similarity index 100% rename from view/postcss.config.js rename to web/static/postcss.config.js diff --git a/view/src/App.vue b/web/static/src/App.vue similarity index 100% rename from view/src/App.vue rename to web/static/src/App.vue diff --git a/view/src/api/api.js b/web/static/src/api/api.js similarity index 100% rename from view/src/api/api.js rename to web/static/src/api/api.js diff --git a/view/src/app.css b/web/static/src/app.css similarity index 100% rename from view/src/app.css rename to web/static/src/app.css diff --git a/view/src/assets/icons/welcome.svg b/web/static/src/assets/icons/welcome.svg similarity index 100% rename from view/src/assets/icons/welcome.svg rename to web/static/src/assets/icons/welcome.svg diff --git a/view/src/main.js b/web/static/src/main.js similarity index 100% rename from view/src/main.js rename to web/static/src/main.js diff --git a/view/src/page/about/index.vue b/web/static/src/page/about/index.vue similarity index 100% rename from view/src/page/about/index.vue rename to web/static/src/page/about/index.vue diff --git a/view/src/page/home/index.vue b/web/static/src/page/home/index.vue similarity index 100% rename from view/src/page/home/index.vue rename to web/static/src/page/home/index.vue diff --git a/view/src/page/layout.vue b/web/static/src/page/layout.vue similarity index 100% rename from view/src/page/layout.vue rename to web/static/src/page/layout.vue diff --git a/view/src/router/index.js b/web/static/src/router/index.js similarity index 100% rename from view/src/router/index.js rename to web/static/src/router/index.js diff --git a/view/src/utils/request.js b/web/static/src/utils/request.js similarity index 100% rename from view/src/utils/request.js rename to web/static/src/utils/request.js diff --git a/view/tailwind.config.js b/web/static/tailwind.config.js similarity index 100% rename from view/tailwind.config.js rename to web/static/tailwind.config.js diff --git a/view/vite.config.js b/web/static/vite.config.js similarity index 100% rename from view/vite.config.js rename to web/static/vite.config.js diff --git a/view/yarn.lock b/web/static/yarn.lock similarity index 99% rename from view/yarn.lock rename to web/static/yarn.lock index 907c3c0..329d749 100644 --- a/view/yarn.lock +++ b/web/static/yarn.lock @@ -429,10 +429,10 @@ culori@^3: resolved "https://registry.npmjs.org/culori/-/culori-3.2.0.tgz" integrity sha512-HIEbTSP7vs1mPq/2P9In6QyFE0Tkpevh0k9a+FkjhD+cwsYm9WRSbn4uMdW9O0yXlNYC3ppxL3gWWPOcvEl57w== -daisyui@^4.0.3: - version "4.0.3" - resolved "https://registry.npmjs.org/daisyui/-/daisyui-4.0.3.tgz" - integrity sha512-mG6PsdIA6MEHzdJwBlJxc1rqsIAAlcfhj2O8g0ol1uWg5y6C5zTcqfG8vKBqK4y2YfCxGIVgMsMWRTSm32N1Ow== +daisyui@^4.12.2: + version "4.12.2" + resolved "https://registry.npmjs.org/daisyui/-/daisyui-4.12.2.tgz" + integrity sha512-ed3EFwPRLN+9+/MYPRB1pYjk6plRCBMobfBdSeB3voAS81KdL2pCKtbwJfUUpDdOnJ0F8T6oRdVX02P6UCD0Hg== dependencies: css-selector-tokenizer "^0.8" culori "^3" diff --git a/web/web.go b/web/web.go new file mode 100644 index 0000000..9d80881 --- /dev/null +++ b/web/web.go @@ -0,0 +1,31 @@ +package web + +import ( + "fmt" + + "github.com/bitxeno/go-docker-skeleton/internal/app" + "github.com/bitxeno/go-docker-skeleton/internal/log" + "github.com/gofiber/fiber/v2" + "github.com/gofiber/fiber/v2/middleware/logger" +) + +func Run(addr string, port int) error { + server := fiber.New() + route(server) + + // set fiber web server access log + server.Use(logger.New()) + accessWriter := log.CreateRollingLogFile(app.Config.Log.AccessLog) + if accessWriter != nil { + server.Use(logger.New(logger.Config{ + Output: accessWriter, + })) + log.Infof("Web access log file path: %s", app.Config.Log.AccessLog) + } + + if err := server.Listen(fmt.Sprintf("%s:%d", addr, port)); err != nil { + log.Error(err.Error()) + return err + } + return nil +}