diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..bacee595 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,7 @@ +如果你遇到的问题不是 Sonic 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/go-sonic/discussion/issues)进行讨论。 + +此 Issue 会被立即关闭。 + +If you are not sure if your question is truely a bug in Sonic, please discuss it [here](https://github.com/go-sonic/discussion/issues) first. + +This issue will be closed immediately. diff --git a/.github/ISSUE_TEMPLATE/bug_cn.md b/.github/ISSUE_TEMPLATE/bug_cn.md new file mode 100644 index 00000000..24486850 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_cn.md @@ -0,0 +1,87 @@ +--- +name: V2Ray 程序问题 +about: "提交一个 V2Ray 的程序问题报告。" +--- + + + +## 你正在使用哪个版本的 V2Ray? + + + + +## 你的使用场景是什么? + + + + +## 你看到的异常现象是什么? + + + + +## 你期待看到的正常表现是怎样的? + + + +## 请附上你的配置 + + + +**服务端配置:** + +```javascript +// 在这里附上服务器端配置文件 + +``` + +**客户端配置:** + +```javascript +// 在这里附上客户端配置 + +``` + +## 请附上出错时软件输出的错误日志 + + + +**服务器端错误日志:** + +```javascript +// 在这里附上服务器端日志 + +``` + +**客户端错误日志:** + +```javascript +// 在这里附上客户端日志 + +``` + +## 请附上访问日志 + + + +```javascript +// 在这里附上服务器端日志 + +``` + +## 其它相关的配置文件(如 Nginx)和相关日志 + + + +## 如果 V2Ray 无法启动,请附上 `--test` 命令的输出 + + + +## 如果 V2Ray 服务运行异常,请附上 journal 日志 + + + + diff --git a/.github/ISSUE_TEMPLATE/bug_en.md b/.github/ISSUE_TEMPLATE/bug_en.md new file mode 100644 index 00000000..a734fe65 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_en.md @@ -0,0 +1,90 @@ +--- +name: Bug report +about: "Create a bug report to help us improve" +--- + + + +## What version of V2Ray are you using? + + + + +## What's your scenario of using V2Ray? + + + + +## What problems have you encountered? + + + + +## What's your expectation? + + + +## Please attach your configuration here + + + +**Server configuration:** + +```javascript +// Please attach your server configuration here. + +``` + +**Client configuration:** + +```javascript +// Please attach your client configuration here. + +``` + +## Please attach error logs here + + + +**Server error log:** + +```javascript +// Please attach your server error log here. + +``` + +**Client error log:** + +```javascript +// Please attach your client error log here. + +``` + +## Please attach access log here + + + +```javascript +// Please attach your server access log here. + +``` + +## Other configurations (such as Nginx) and logs here + + + +## If V2Ray cannot start up, please attach output from `--test` command + + + +## If V2Ray service is abnormal, please attach journal log here + + + + diff --git a/.github/ISSUE_TEMPLATE/other_en.md b/.github/ISSUE_TEMPLATE/other_en.md new file mode 100644 index 00000000..8b6b04f3 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/other_en.md @@ -0,0 +1,12 @@ +--- +name: Other +about: "其它问题请使用 https://github.com/v2fly/v2ray-core/discussions 进行讨论 / Please discuss other issues at https://github.com/v2fly/v2ray-core/discussions" +--- + +如果你遇到的问题不是 V2Ray 的 bug,比如你不清楚要如何配置,请使用[Discussion](https://github.com/v2fly/v2ray-core/discussions)进行讨论。 + +此 Issue 会被立即关闭。 + +If you are not sure if your question is truely a bug in V2Ray, please discuss it [here](https://github.com/v2fly/v2ray-core/discussions) first. + +This issue will be closed immediately. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000..d85c6373 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +# To get started with Dependabot version updates, you'll need to specify which +# package ecosystems to update and where the package manifests are located. +# Please see the documentation for all configuration options: +# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates + +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "daily" + open-pull-requests-limit: 10 + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "daily" diff --git a/.github/linters/.golangci.yml b/.github/linters/.golangci.yml new file mode 100644 index 00000000..4b5cd908 --- /dev/null +++ b/.github/linters/.golangci.yml @@ -0,0 +1,54 @@ +run: + timeout: 5m + skip-files: + - .gen.go + - .pb.go + +issues: + new: true + exclude-rules: + - linters: + - staticcheck + text: "SA1019:" + - linters: + - stylecheck + text: "ST1016:" + +linters: + enable: + - asciicheck + - bodyclose + - depguard + - gocritic + - gofmt + - gofumpt + - goimports + - goprintffuncname + - gosimple + - govet + - ineffassign + - misspell + - nakedret + - revive + - rowserrcheck + - staticcheck + - structcheck + - stylecheck + - typecheck + - unconvert + - unparam + - varcheck + - whitespace + disable: + - deadcode + - errcheck + - unused + +linters-settings: + goimports: + local-prefixes: github.com/go-sonic/sonic + revive: + rules: + - name: blank-imports + severity: warning + disabled: true diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..f564cc0e --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,47 @@ +name: CodeQL + +on: + push: + branches: [master] + pull_request: + branches: [master] + types: [opened, synchronize, reopened] + paths-ignore: + - '**/*.md' + - '**/*.txt' + +jobs: + analyze: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + language: ["go"] + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000..73b50af6 --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,35 @@ +name: Linter + +on: + push: + branches: + - master + - v* + paths: + - "**/*.go" + - ".github/workflows/linter.yml" + pull_request: + types: [opened, synchronize, reopened] + paths: + - "**/*.go" + - ".github/workflows/linter.yml" + +jobs: + lint: + if: github.repository == 'go-sonic/sonic' + runs-on: ubuntu-latest + steps: + - name: Set up Go 1.x + uses: actions/setup-go@v3 + with: + go-version: ^1.19 + + - name: Checkout codebase + uses: actions/checkout@v3 + + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + version: latest + args: --config=.github/linters/.golangci.yml + only-new-issues: true diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 00000000..49e3e237 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,133 @@ +name: Release + +on: + release: + types: [prereleased] + push: + branches: + - master + - v* + paths: + - "**/*.go" + - "go.mod" + - "go.sum" + - ".github/workflows/*.yml" + pull_request: + types: [opened, synchronize, reopened] + paths: + - "**/*.go" + - "go.mod" + - "go.sum" + - ".github/workflows/*.yml" + +jobs: + build: + runs-on: ubuntu-latest + strategy: + matrix: + # Include amd64 on all platforms. + goos: [windows, linux, darwin] + goarch: [amd64, 386] + exclude: + - goarch: 386 + goos: darwin + include: + # BEGIN Linux ARM 5 6 7 + - goos: linux + goarch: arm-7 + - goos: linux + goarch: arm-6 + - goos: linux + goarch: arm-5 + # END Linux ARM 5 6 7 + # BEGIN Other architectures + - goos: darwin + goarch: arm64 + - goos: linux + goarch: arm64 + # BEGIN MIPS + - goos: linux + goarch: mips64 + - goos: linux + goarch: mips64le + - goos: linux + goarch: mipsle + - goos: linux + goarch: mips + # END MIPS + fail-fast: false + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Show workflow information + run: | + echo "GOOS: $GOOS, GOARCH: $GOARCH" + + - name: Build + uses: crazy-max/ghaction-xgo@v2 + with: + xgo_version: latest + go_version: 1.19 + dest: build + prefix: sonic + targets: ${{matrix.goos}}/${{matrix.goarch}} + v: true + x: false + race: false + ldflags: -s -w + buildmode: default + trimpath: true + + - name: Rename executable file + run: | + cd ./build || exit 1 + mv sonic-$GOOS-$GOARCH* sonic + + - name: Rename Windows ecutable file + if: matrix.goos == 'windows' + run: | + cd ./build || exit 1 + mv sonic sonic.exe + + - name: Prepare package + run: | + cp -rv ./conf ./build + cp -rv ./resources ./build + + + - name: Prepare package for Linux + if: matrix.goos == 'linux' + run: cp -rv ./release/systemd ./build/ + + - name: Create ZIP archive + run: | + pushd build || exit 1 + zip -9vr ../sonic-$GOOS-$GOARCH.zip . + popd || exit 1 + FILE=./sonic-$GOOS-$GOARCH.zip + DGST=$FILE.dgst + openssl dgst -md5 $FILE | sed 's/([^)]*)//g' >>$DGST + openssl dgst -sha1 $FILE | sed 's/([^)]*)//g' >>$DGST + openssl dgst -sha256 $FILE | sed 's/([^)]*)//g' >>$DGST + openssl dgst -sha512 $FILE | sed 's/([^)]*)//g' >>$DGST + + - name: Upload ZIP file to Artifacts + uses: actions/upload-artifact@v3 + with: + name: sonic-${{matrix.goos}}-${{matrix.goarch}}.zip + path: sonic-${{matrix.goos}}-${{matrix.goarch}}.zip + + - name: Upload files to GitHub release + uses: svenstaro/upload-release-action@v2 + if: github.event_name == 'release' + with: + repo_token: ${{ secrets.GITHUB_TOKEN }} + file_glob: true + file: ./sonic-${{matrix.goos}}-${{matrix.goarch}}.zip* + tag: ${{ github.ref }} diff --git a/.github/workflows/semgrep.yml b/.github/workflows/semgrep.yml new file mode 100644 index 00000000..3c1c5cad --- /dev/null +++ b/.github/workflows/semgrep.yml @@ -0,0 +1,20 @@ +name: Semgrep +on: [pull_request] +jobs: + semgrep: + name: Scan + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: returntocorp/semgrep-action@v1 + env: # Optional environment variable for inline PR comments (beta) + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + config: | + p/golang + p/r2c-ci + p/r2c-security-audit + p/insecure-transport + p/secrets + publishToken: ${{ secrets.SEMGREP_APP_TOKEN }} + publishDeployment: 241 diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml new file mode 100644 index 00000000..6e0315d2 --- /dev/null +++ b/.github/workflows/stale.yml @@ -0,0 +1,17 @@ +name: Mark stale issues and pull requests + +on: + schedule: + - cron: "30 1 * * *" + +jobs: + stale: + runs-on: ubuntu-latest + steps: + - uses: actions/stale@v6 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + stale-issue-message: "This issue is stale because it has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days" + stale-pr-message: 'It has been open 120 days with no activity. Remove stale label or comment or this will be closed in 5 days' + days-before-stale: 120 + days-before-close: 5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..69afbc3c --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +/.idea +/logs +.DS_Store +/upload +/.vscode +sonic.db +__debug_bin \ No newline at end of file diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..608e46b9 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "resources/template/theme/default-theme-anatole"] + path = resources/template/theme/default-theme-anatole + url = git@github.com:go-sonic/default-theme-anatole.git diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 00000000..f737be53 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2022 1379Monitor + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 00000000..7c9dd1ff --- /dev/null +++ b/README.md @@ -0,0 +1,73 @@ +

+ +

+ +

Sonic [ˈsɒnɪk] ,Sonic is a Go Blogging Platform. Simple and Powerful.

+ +

+GitHub release +GitHub All Releases +Docker pulls +GitHub last commit +GitHub Workflow Status +
+Website +Telegram Channel +

+ + +English | [中文](doc/README_ZH.md) + +## 📖 Introduction + +Sonic means as fast as sound speed. Like its name, sonic is a high-performance blog system developed using golang + +Thanks [Halo](https://github.com/halo-dev) project team,this project is inspired by Halo. Front end project fork from Halo + +## 🚀 Features: +- Support multiple types of databases: SQLite、MySQL(TODO: PostgreSQL) +- Small: The installation file is only 10mb size +- High-performance: Post details page can withstand 900qps(Enviroment: Intel Xeon Platinum 8260 4C 8G ,SQLite3) +- Support changing theme +- Support Linux、Windows、Mac OS. And Support x86、x64、Arm、Arm64、MIPS +- Object storage(MINIO、Google Cloud、AWS、AliYun) + + +## 🧰 How to install + +### Download the latest installation package +> Please pay attention to the operating system and instruction set +```bash +wget https://github.com/go-sonic/sonic/releases/download/v1.0.0/sonic-linux-64.zip -O sonic.zip +``` +### Decompression +```bash +unzip sonic.zip +``` +### Launch +```bash +cd sonic +./sonic -config conf/config.yaml +``` + +### Initialization +**The default port is 8080** + +Open http://ip:port/admin#install + +Next, you can access sonic through the browser. + +The URL of the admin console is http://ip:port/admin + + +## TODO +- [ ] i18n +- [ ] PostgreSQL +- [ ] Better error handling +- [ ] Plugin(base on Wasm) +- [ ] Use new web framework([Hertz](https://github.com/cloudwego/hertz)) + +## 📄 License + +Source code in `sonic` is available under the [MIT License](/LICENSE.md). + diff --git a/cache/cache.go b/cache/cache.go new file mode 100644 index 00000000..bb74c6ae --- /dev/null +++ b/cache/cache.go @@ -0,0 +1,52 @@ +package cache + +import ( + "time" + + goCache "github.com/patrickmn/go-cache" +) + +type Cache interface { + SetDefault(key string, value interface{}) + Set(key string, value interface{}, expiration time.Duration) + Get(key string) (interface{}, bool) + Delete(key string) + BatchDelete(keys []string) +} + +var _ Cache = &cacheImpl{} + +type cacheImpl struct { + goCache *goCache.Cache +} + +func NewCache() Cache { + return &cacheImpl{ + goCache: goCache.New(time.Hour, time.Hour), + } +} + +// SetDefault to cache with defaultExpiration time +func (c *cacheImpl) SetDefault(key string, value interface{}) { + c.goCache.SetDefault(key, value) +} + +// Set to cache with expiration in params +func (c *cacheImpl) Set(key string, value interface{}, expiration time.Duration) { + c.goCache.Set(key, value, expiration) +} + +// Get key's value +func (c *cacheImpl) Get(key string) (interface{}, bool) { + return c.goCache.Get(key) +} + +func (c *cacheImpl) Delete(key string) { + c.goCache.Delete(key) +} + +func (c *cacheImpl) BatchDelete(keys []string) { + for _, key := range keys { + c.Delete(key) + } +} diff --git a/cache/key.go b/cache/key.go new file mode 100644 index 00000000..28827c1b --- /dev/null +++ b/cache/key.go @@ -0,0 +1,45 @@ +package cache + +import ( + "context" + "strconv" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/util/xerr" +) + +func BuildTokenAccessKey(accessToken string) string { + return consts.TokenAccessCachePrefix + accessToken +} + +func BuildTokenRefreshKey(refreshToken string) string { + return consts.TokenRefreshCachePrefix + refreshToken +} + +func BuildAccessTokenKey(userID int32) string { + return consts.TokenAccessCachePrefix + strconv.Itoa(int(userID)) +} + +func BuildRefreshTokenKey(userID int32) string { + return consts.TokenRefreshCachePrefix + strconv.Itoa(int(userID)) +} + +func BuildCodeCacheKey(userID int32) string { + return consts.CodePrefix + strconv.Itoa(int(userID)) +} + +func BuildAccessPermissionKey(ctx context.Context) (string, error) { + sessionID := ctx.Value(consts.SessionID) + if sessionID == nil { + return "", xerr.NoType.New("session_id not exist").WithStatus(xerr.StatusInternalServerError) + } + sessionIDStr, ok := sessionID.(string) + if !ok || sessionIDStr == "" { + return "", xerr.NoType.New("session_id not exist").WithStatus(xerr.StatusInternalServerError) + } + return consts.AccessPermissionKeyPrefix + sessionIDStr, nil +} + +func BuildCategoryPermissionKey(categoryID int32) string { + return strconv.Itoa(int(categoryID)) +} diff --git a/cmd/generate/generate.go b/cmd/generate/generate.go new file mode 100644 index 00000000..9d2db6e5 --- /dev/null +++ b/cmd/generate/generate.go @@ -0,0 +1,67 @@ +package main + +import ( + "go.uber.org/fx" + "gorm.io/gen" + "gorm.io/gorm" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/log" +) + +// generate code +func main() { + var DB *gorm.DB + _ = fx.New( + fx.Provide(log.NewLogger), + fx.Provide(dal.NewGormDB), + fx.Provide(log.NewGormLogger), + fx.Provide(config.NewConfig), + fx.Populate(&DB), + ) + // specify the output directory (default: "./query") + // ### if you want to query without context constrain, set mode gen.WithoutContext ### + g := gen.NewGenerator(gen.Config{ + Mode: gen.WithDefaultQuery, + OutPath: "./dal", + ModelPkgPath: "./model/entity", + /* Mode: gen.WithoutContext,*/ + //if you want the nullable field generation property to be pointer type, set FieldNullable true + FieldNullable: true, + FieldWithIndexTag: true, + FieldWithTypeTag: true, + }) + + // reuse the database connection in Project or create a connection here + // if you want to use GenerateModel/GenerateModelAs, UseDB is necessary, or it will panic + g.UseDB(DB) + + // apply basic crud api on structs or table models which is specified by table name with function + // GenerateModel/GenerateModelAs. And generator will generate table models' code when calling Excute. + g.ApplyBasic(g.GenerateModel("attachment", gen.FieldType("type", "consts.AttachmentType")), + g.GenerateModel("category", gen.FieldType("type", "consts.CategoryType")), + g.GenerateModel("comment", gen.FieldType("type", "consts.CommentType"), gen.FieldType("status", "consts.CommentStatus")), + g.GenerateModel("comment_black"), + g.GenerateModel("flyway_schema_history"), + g.GenerateModel("journal", gen.FieldType("type", "consts.JournalType")), + g.GenerateModel("link"), + g.GenerateModel("log", gen.FieldType("type", "consts.LogType")), + g.GenerateModel("menu"), + g.GenerateModelAs("meta", "Meta", gen.FieldType("type", "consts.MetaType")), + g.GenerateModel("option", gen.FieldType("type", "consts.OptionType")), + g.GenerateModel("photo"), + g.GenerateModel("post", gen.FieldType("type", "consts.PostType"), gen.FieldType("status", "consts.PostStatus"), gen.FieldType("editor_type", "consts.EditorType")), + g.GenerateModel("post_category"), + g.GenerateModel("post_tag"), + g.GenerateModel("tag"), + g.GenerateModel("theme_setting"), + g.GenerateModel("user", gen.FieldType("mfa_type", "consts.MFAType")), + ) + + // apply diy interfaces on structs or table models + // g.ApplyInterface(func(method model.Method) {}, model.User{}, g.GenerateModel("company")) + + // execute the action of code generation + g.Execute() +} diff --git a/conf/config.dev.yaml b/conf/config.dev.yaml new file mode 100644 index 00000000..6d06523a --- /dev/null +++ b/conf/config.dev.yaml @@ -0,0 +1,33 @@ +server: + host: 127.0.0.1 + port: 8080 + +logging: + filename: sonic.log + level: + app: debug # debug,info,warn,error + gorm: info # info,warn,error,silent + maxsize: 10 # 单位 megabytes + maxage: 30 #单位 天 + compress: false # 是否对旧日志使用gzip进行压缩 + + +### 数据库配置, MySQL 和 SQLite3 二选一 .如果同时配置了MySQL 或 SQLite3 ,优先使用 Sqlite3 +### The Database configuration,You should choose one between MySQL and SQLite3,if both MySQL and SQLite3 are configured ,use Sqlite3 first + +sqlite3: + enable: true + +mysql: + host: 127.0.0.1 + port: 3306 + db: sonicdb + username: "root" + password: "12345678" + + + +sonic: + mode: "development" + work_dir: "./" # 不填默认为当前路径,用来存放日志文件、数据库文件、模板、上传的附件等(The default is the current directory. Used to store log files, database files, templates, upload files) + log_dir: "./logs" # 不填则使用work_dir 路径下的log路径 (If it is empty, use the "log" path under work_dir) \ No newline at end of file diff --git a/conf/config.yaml b/conf/config.yaml new file mode 100644 index 00000000..0d337c25 --- /dev/null +++ b/conf/config.yaml @@ -0,0 +1,32 @@ +server: + host: 0.0.0.0 + port: 8080 + +logging: + filename: sonic.log + level: + app: info # debug,info,warn,error + gorm: warn # info,warn,error,silent + maxsize: 10 # 单位 megabytes + maxage: 30 #单位 天 + compress: false # 是否对旧日志使用gzip进行压缩 + + +### 数据库配置, MySQL 和 SQLite3 二选一 .如果同时配置了MySQL 或 SQLite3 ,优先使用 Sqlite3 +### The Database configuration,You should choose one between MySQL and SQLite3,if bothMySQL and SQLite3 are configured ,use Sqlite3 first +sqlite3: + enable: true + + +mysql: + host: 127.0.0.1 + port: 3306 + db: sonicdb + username: "root" + password: "12345678" + + +sonic: + mode: "production" + work_dir: "./" # 不填默认为当前路径,用来存放日志文件、数据库文件、模板、上传的附件等(The default is the current directory. Used to store log files, database files, templates, upload files) + log_dir: "./logs" # 不填则使用work_dir 路径下的log路径 (If it is empty, use the "log" path under work_dir) \ No newline at end of file diff --git a/config/config.go b/config/config.go new file mode 100644 index 00000000..dc961974 --- /dev/null +++ b/config/config.go @@ -0,0 +1,103 @@ +package config + +import ( + "flag" + "fmt" + "os" + "path/filepath" + + "github.com/pkg/errors" + "github.com/spf13/viper" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/util" +) + +func NewConfig() *Config { + var configFile string + flag.StringVar(&configFile, "config", "", "") + flag.Parse() + viper.SetConfigType("yaml") + if configFile != "" { + viper.SetConfigFile(configFile) + } else { + viper.AddConfigPath("./conf/") + viper.SetConfigName("config.yaml") + } + + conf := &Config{} + if err := viper.ReadInConfig(); err != nil { + panic(err) + } + if err := viper.Unmarshal(conf); err != nil { + panic(err) + } + + if conf.Sonic.WorkDir == "" { + pwd, err := os.Getwd() + if err != nil { + panic(errors.Wrap(err, "init config: get current dir")) + } + conf.Sonic.WorkDir, _ = filepath.Abs(pwd) + } else { + workDir, err := filepath.Abs(conf.Sonic.WorkDir) + if err != nil { + panic(err) + } + conf.Sonic.WorkDir = workDir + } + normalizeDir := func(path *string, subDir string) { + if *path == "" { + *path = filepath.Join(conf.Sonic.WorkDir, subDir) + } else { + temp, err := filepath.Abs(*path) + if err != nil { + panic(err) + } + *path = temp + } + } + normalizeDir(&conf.Sonic.LogDir, "log") + normalizeDir(&conf.Sonic.TemplateDir, "resources/template") + normalizeDir(&conf.Sonic.AdminResourcesDir, "resources/admin") + normalizeDir(&conf.Sonic.UploadDir, consts.SonicUploadDir) + normalizeDir(&conf.Sonic.ThemeDir, "resources/template/theme") + if conf.SQLite3 != nil && conf.SQLite3.Enable { + normalizeDir(&conf.SQLite3.File, "sonic.db") + } + if !util.FileIsExisted(conf.Sonic.TemplateDir) { + panic("template dir: " + conf.Sonic.TemplateDir + " not exist") + } + if !util.FileIsExisted(conf.Sonic.AdminResourcesDir) { + panic("AdminResourcesDir: " + conf.Sonic.AdminResourcesDir + "not exist") + } + if !util.FileIsExisted(conf.Sonic.ThemeDir) { + panic("theme dir: " + conf.Sonic.ThemeDir + " not exist") + } + + initDirectory(conf) + mode = conf.Sonic.Mode + return conf +} + +func initDirectory(conf *Config) { + mkdirFunc := func(dir string, err error) error { + if err == nil { + if _, err = os.Stat(dir); os.IsNotExist(err) { + err = os.MkdirAll(dir, os.ModePerm) + } + } + return err + } + err := mkdirFunc(conf.Sonic.LogDir, nil) + err = mkdirFunc(conf.Sonic.UploadDir, err) + if err != nil { + panic(fmt.Errorf("initDirectory err=%v", err)) + } +} + +var mode string + +func IsDev() bool { + return mode == "development" +} diff --git a/config/default_config.go b/config/default_config.go new file mode 100644 index 00000000..2907830d --- /dev/null +++ b/config/default_config.go @@ -0,0 +1,14 @@ +package config + +import ( + "os" + "path/filepath" +) + +var ( + TempDir = os.TempDir() + BackupDir = filepath.Join(TempDir, "sonic-backup") + string(os.PathSeparator) + BackupMarkdownDir = filepath.Join(TempDir, "sonic-backup-markdown") + string(os.PathSeparator) + DataExportDir = filepath.Join(TempDir, "sonic-data-export") + string(os.PathSeparator) + ResourcesDir, _ = filepath.Abs("./resources") +) diff --git a/config/model.go b/config/model.go new file mode 100644 index 00000000..5d3d81fb --- /dev/null +++ b/config/model.go @@ -0,0 +1,54 @@ +package config + +type Config struct { + Server Server `mapstructure:"server"` + Log Log `mapstructure:"logging"` + PostgreSQL *PostgreSQL `mapstructure:"postgre"` + MySQL *MySQL `mapstructure:"mysql"` + SQLite3 *SQLite3 `mapstructure:"sqlite3"` + Sonic Sonic `mapstructure:"sonic"` +} + +type PostgreSQL struct { + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` + DB string `mapstructure:"db"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} +type MySQL struct { + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` + DB string `mapstructure:"db"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` +} +type SQLite3 struct { + Enable bool `mapstructure:"enable"` + File string +} +type Server struct { + Host string `mapstructure:"host"` + Port string `mapstructure:"port"` +} +type Log struct { + FileName string `mapstructure:"filename"` + Levels Levels `mapstructure:"level"` + MaxSize int `mapstructure:"maxsize"` + MaxAge int `mapstructure:"maxage"` + Compress bool `mapstructure:"compress"` +} +type Levels struct { + App string `mapstructure:"app"` + Gorm string `mapstructure:"gorm"` +} + +type Sonic struct { + Mode string `mapstructure:"mode"` + WorkDir string `mapstructure:"work_dir"` + UploadDir string + LogDir string `mapstructure:"log_dir"` + TemplateDir string `mapstructure:"template_dir"` + ThemeDir string + AdminResourcesDir string +} diff --git a/consts/consts.go b/consts/consts.go new file mode 100644 index 00000000..f480729e --- /dev/null +++ b/consts/consts.go @@ -0,0 +1,46 @@ +package consts + +import ( + "time" +) + +const ( + AccessTokenExpiredSeconds = 24 * 3600 + RefreshTokenExpiredDays = 30 + TokenAccessCachePrefix = "admin_access_token_" + TokenRefreshCachePrefix = "admin_refresh_token_" + AdminTokenHeaderName = "Admin-Authorization" + AuthorizedUser = "authorized_user" + CodePrefix = "code_" + CodeValidDuration = time.Second + OneTimeTokenQueryName = "ott" + SessionID = "session_id" + AccessPermissionKeyPrefix = "access_permission_" +) + +const ( + SonicVersion = "1.0.0" + SonicBackupPrefix = "sonic-backup-" + SonicDataExportPrefix = "sonic-data-export-" + SonicBackupMarkdownPrefix = "sonic-backup-markdown-" + SonicDefaultTagColor = "#cfd3d7" + SonicUploadDir = "upload" + SonicDefaultThemeDirName = "default-theme-anatole" +) + +var ( + ThemePropertyFilenames = [2]string{"theme.yaml", "theme.yml"} + ThemeSettingFilenames = [2]string{"settings.yaml", "settings.yml"} +) + +const ( + DefaultThemeId = "caicai_anatole" + ThemeScreenshotsName = "screenshot" + ThemeCustomSheetPrefix = "sheet_" + ThemeCustomPostPrefix = "post_" +) + +// StartTime 系统启动时间 +var StartTime time.Time + +var DatabaseVersion string diff --git a/consts/enum.go b/consts/enum.go new file mode 100644 index 00000000..46c656da --- /dev/null +++ b/consts/enum.go @@ -0,0 +1,990 @@ +package consts + +import ( + "database/sql/driver" + "strconv" + "strings" + + "github.com/go-sonic/sonic/util/xerr" +) + +type DBType string + +const ( + DBTypeMySQL = "MySQL" + DBTypeSQLite = "SQLite" +) + +type AttachmentType int32 + +const ( + AttachmentTypeLocal AttachmentType = iota + // AttachmentTypeUpOSS 又拍云 + AttachmentTypeUpOSS + // AttachmentTypeQiNiuOSS 七牛云 + AttachmentTypeQiNiuOSS + // AttachmentTypeSMMS sm.ms + AttachmentTypeSMMS + // AttachmentTypeAliOSS 阿里云OSS + AttachmentTypeAliOSS + // AttachmentTypeBaiDuOSS 百度云OSS + AttachmentTypeBaiDuOSS + // AttachmentTypeTencentCOS 腾讯COS + AttachmentTypeTencentCOS + // AttachmentTypeHuaweiOBS 华为OBS + AttachmentTypeHuaweiOBS + // AttachmentTypeMinIO AttachmentTypeMinIO + AttachmentTypeMinIO +) + +func (a AttachmentType) String() string { + switch a { + case AttachmentTypeLocal: + return "LOCAL" + case AttachmentTypeUpOSS: + return "UPOSS" + case AttachmentTypeQiNiuOSS: + return "QINIUOSS" + case AttachmentTypeSMMS: + return "AttachmentTypeSMMS" + case AttachmentTypeAliOSS: + return "ALIOSS" + case AttachmentTypeBaiDuOSS: + return "BAIDUOSS" + case AttachmentTypeTencentCOS: + return "TENCENTOSS" + case AttachmentTypeHuaweiOBS: + return "HUAWEIOBS" + case AttachmentTypeMinIO: + return "MINIO" + default: + return "UNKNOWN" + } +} + +func (a AttachmentType) MarshalJSON() ([]byte, error) { + switch a { + case AttachmentTypeLocal: + return []byte(`"LOCAL"`), nil + case AttachmentTypeUpOSS: + return []byte(`"UPOSS"`), nil + case AttachmentTypeQiNiuOSS: + return []byte(`"QINIUOSS"`), nil + case AttachmentTypeSMMS: + return []byte(`"AttachmentTypeSMMS"`), nil + case AttachmentTypeAliOSS: + return []byte(`"ALIOSS"`), nil + case AttachmentTypeBaiDuOSS: + return []byte(`"BAIDUOSS"`), nil + case AttachmentTypeTencentCOS: + return []byte(`"TENCENTOSS"`), nil + case AttachmentTypeHuaweiOBS: + return []byte(`"HUAWEIOBS"`), nil + case AttachmentTypeMinIO: + return []byte(`"MINIO"`), nil + default: + return []byte(`"UNKNOWN"`), nil + } +} + +func (a *AttachmentType) UnmarshalJSON(data []byte) error { + str := string(data) + switch str { + case `"LOCAL"`: + *a = AttachmentTypeLocal + case `"UPOSS"`: + *a = AttachmentTypeUpOSS + case `"QINIUOSS"`: + *a = AttachmentTypeQiNiuOSS + case `"AttachmentTypeSMMS"`: + *a = AttachmentTypeSMMS + case `"ALIOSS"`: + *a = AttachmentTypeAliOSS + case `"BAIDUBOS"`: + *a = AttachmentTypeBaiDuOSS + case `"TENCENTCOS"`: + *a = AttachmentTypeTencentCOS + case `"HUAWEIOBS"`: + *a = AttachmentTypeHuaweiOBS + case `"MINIO"`: + *a = AttachmentTypeMinIO + default: + return xerr.BadParam.New("").WithMsg("unknown AttachmentType") + } + return nil +} + +func (a *AttachmentType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *a = AttachmentType(data) + case int32: + *a = AttachmentType(data) + case int: + *a = AttachmentType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (a AttachmentType) Value() (driver.Value, error) { + return int64(a), nil +} + +type LogType int32 + +const ( + LogTypeBlogInitialized LogType = iota + LogTypePostPublished + LogTypePostEdited + LogTypePostDeleted + LogTypeLoggedIn + LogTypeLoggedOut + LogTypeLoginFailed + LogTypePasswordUpdated + LogTypeProfileUpdated + LogTypeSheetPublished + LogTypeSheetEdited + LogTypeSheetDeleted + LogTypeMfaUpdated + LogTypeLoggedPreCheck +) + +func (l LogType) MarshalJSON() ([]byte, error) { + switch l { + case LogTypeBlogInitialized: + return []byte(`"BLOG_INITIALIZED"`), nil + case LogTypePostPublished: + return []byte(`"POST_PUBLISHED"`), nil + case LogTypePostEdited: + return []byte(`"POST_EDITED"`), nil + case LogTypePostDeleted: + return []byte(`"POST_DELETED"`), nil + case LogTypeLoggedIn: + return []byte(`"LOGGED_IN"`), nil + case LogTypeLoggedOut: + return []byte(`"LOGGED_OUT"`), nil + case LogTypeLoginFailed: + return []byte(`"LOGIN_FAILED"`), nil + case LogTypePasswordUpdated: + return []byte(`"PASSWORD_UPDATED"`), nil + case LogTypeProfileUpdated: + return []byte(`"PROFILE_UPDATED"`), nil + case LogTypeSheetPublished: + return []byte(`"SHEET_PUBLISHED"`), nil + case LogTypeSheetEdited: + return []byte(`"SHEET_EDITED"`), nil + case LogTypeSheetDeleted: + return []byte(`"SHEET_DELETED"`), nil + case LogTypeMfaUpdated: + return []byte(`"MFA_UPDATED"`), nil + case LogTypeLoggedPreCheck: + return []byte(`"LOGGED_PRE_CHECK"`), nil + } + return nil, nil +} + +func (l *LogType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *l = LogType(data) + case int32: + *l = LogType(data) + case int: + *l = LogType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (l LogType) Value() (driver.Value, error) { + return int64(l), nil +} + +type MFAType int32 + +const ( + MFANone MFAType = iota + // MFATFATotp Time-based One-time Password (rfc6238). + // see: https://tools.ietf.org/html/rfc6238 + MFATFATotp +) + +func (m MFAType) MarshalJSON() ([]byte, error) { + if m == MFANone { + return []byte(`"NONE"`), nil + } else if m == MFATFATotp { + return []byte(`"TFA_TOTP"`), nil + } + return nil, nil +} + +func (m *MFAType) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"NONE"` { + *m = MFANone + } else if str == `"TFA_TOTP"` { + *m = MFATFATotp + } else { + return xerr.BadParam.New("").WithMsg("unknown MFAType") + } + return nil +} + +func (m *MFAType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *m = MFAType(data) + case int32: + *m = MFAType(data) + case int: + *m = MFAType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (m MFAType) Value() (driver.Value, error) { + return int64(m), nil +} + +type PostStatus int32 + +const ( + PostStatusPublished PostStatus = iota + PostStatusDraft + PostStatusRecycle + PostStatusIntimate +) + +func (c PostStatus) MarshalJSON() ([]byte, error) { + if c == PostStatusPublished { + return []byte(`"PUBLISHED"`), nil + } else if c == PostStatusDraft { + return []byte(`"DRAFT"`), nil + } else if c == PostStatusRecycle { + return []byte(`"RECYCLE"`), nil + } else if c == PostStatusIntimate { + return []byte(`"INTIMATE"`), nil + } + return nil, nil +} + +func (c *PostStatus) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"PUBLISHED"` { + *c = PostStatusPublished + } else if str == `"DRAFT"` { + *c = PostStatusDraft + } else if str == `"RECYCLE"` { + *c = PostStatusRecycle + } else if str == `"INTIMATE"` { + *c = PostStatusIntimate + } else if str == "" { + *c = PostStatusDraft + } else { + return xerr.BadParam.New("").WithMsg("unknown PostStatus") + } + return nil +} + +func (c *PostStatus) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *c = PostStatus(data) + case int32: + *c = PostStatus(data) + case int: + *c = PostStatus(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (c PostStatus) Value() (driver.Value, error) { + return int64(c), nil +} + +func (c PostStatus) Ptr() *PostStatus { + return &c +} + +type CommentStatus int32 + +const ( + CommentStatusPublished CommentStatus = iota + CommentStatusAuditing + CommentStatusRecycle +) + +func (c CommentStatus) MarshalJSON() ([]byte, error) { + if c == CommentStatusPublished { + return []byte(`"PUBLISHED"`), nil + } else if c == CommentStatusAuditing { + return []byte(`"AUDITING"`), nil + } else if c == CommentStatusRecycle { + return []byte(`"RECYCLE"`), nil + } + return nil, nil +} + +func (c *CommentStatus) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"PUBLISHED"` { + *c = CommentStatusPublished + } else if str == `"AUDITING"` { + *c = CommentStatusAuditing + } else if str == `"RECYCLE"` { + *c = CommentStatusRecycle + } else { + return xerr.BadParam.New("").WithMsg("unknown CommentStatus") + } + return nil +} + +func (c *CommentStatus) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *c = CommentStatus(data) + case int32: + *c = CommentStatus(data) + case int: + *c = CommentStatus(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} +func CommentStatusFromString(str string) (CommentStatus, error) { + if str == "PUBLISHED" { + return CommentStatusPublished, nil + } else if str == "AUDITING" { + return CommentStatusAuditing, nil + } else if str == "RECYCLE" { + return CommentStatusRecycle, nil + } else { + return CommentStatusPublished, xerr.BadParam.New("").WithMsg("unknown CommentStatus") + } +} + +func (c CommentStatus) Value() (driver.Value, error) { + return int64(c), nil +} + +func (c CommentStatus) Ptr() *CommentStatus { + return &c +} + +type PostPermalinkType string + +const ( + PostPermalinkTypeDefault PostPermalinkType = "DEFAULT" + PostPermalinkTypeDate PostPermalinkType = "DATE" + PostPermalinkTypeDay PostPermalinkType = "DAY" + PostPermalinkTypeID PostPermalinkType = "ID" + PostPermalinkTypeYear PostPermalinkType = "YEAR" + PostPermalinkTypeIDSlug PostPermalinkType = "ID_SLUG" +) + +type EditorType int32 + +const ( + EditorTypeMarkdown = iota + EditorTypeRichText +) + +func (e *EditorType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *e = EditorType(data) + case int32: + *e = EditorType(data) + case int: + *e = EditorType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (e EditorType) Value() (driver.Value, error) { + return int64(e), nil +} + +func (e EditorType) MarshalJSON() ([]byte, error) { + if e == EditorTypeMarkdown { + return []byte(`"MARKDOWN"`), nil + } else if e == EditorTypeRichText { + return []byte(`"RICHTEXT"`), nil + } + return nil, nil +} + +func (e *EditorType) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"MARKDOWN"` { + *e = EditorTypeMarkdown + } else if str == `"RICHTEXT"` { + *e = EditorTypeRichText + } else if str == "" { + *e = EditorTypeMarkdown + } else { + return xerr.BadParam.New("").WithMsg("unknown editorType") + } + return nil +} + +type OptionType int32 + +const ( + OptionTypeInternal = iota + OptionTypeCustom +) + +func (o OptionType) MarshalJSON() ([]byte, error) { + if o == OptionTypeInternal { + return []byte(`"INTERNAL"`), nil + } else if o == OptionTypeCustom { + return []byte(`"CUSTOM"`), nil + } + return nil, nil +} + +func (o *OptionType) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"INTERNAL"` { + *o = OptionTypeInternal + } else if str == `"CUSTOM"` { + *o = OptionTypeCustom + } else { + return xerr.BadParam.New("").WithMsg("unknown OptionType") + } + return nil +} + +func (o *OptionType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *o = OptionType(data) + case int32: + *o = OptionType(data) + case int: + *o = OptionType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (o OptionType) Value() (driver.Value, error) { + return int64(o), nil +} + +type PostType int32 + +const ( + PostTypePost PostType = iota + PostTypeSheet +) + +func (p *PostType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *p = PostType(data) + case int32: + *p = PostType(data) + case int: + *p = PostType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (p PostType) Value() (driver.Value, error) { + return int64(p), nil +} + +type CommentType int32 + +const ( + CommentTypePost CommentType = iota + CommentTypeSheet + CommentTypeJournal +) + +func (ct *CommentType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("unknown OptionType") + } + switch data := src.(type) { + case int64: + *ct = CommentType(data) + case int32: + *ct = CommentType(data) + case int: + *ct = CommentType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (ct CommentType) Value() (driver.Value, error) { + return int64(ct), nil +} + +type SheetPermaLinkType string + +const ( + SheetPermaLinkTypeSecondary = "SECONDARY" + SheetPermaLinkTypeRoot = "ROOT" +) + +type JournalType int32 + +const ( + JournalTypePublic JournalType = iota + JournalTypeIntimate +) + +func (j JournalType) Ptr() *JournalType { + return &j +} + +func (j JournalType) MarshalJSON() ([]byte, error) { + if j == JournalTypePublic { + return []byte(`"PUBLIC"`), nil + } else if j == JournalTypeIntimate { + return []byte(`"INTIMATE"`), nil + } + return nil, nil +} + +func (j *JournalType) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"PUBLIC"` { + *j = JournalTypePublic + } else if str == `"INTIMATE"` { + *j = JournalTypeIntimate + } else { + return xerr.BadParam.New("").WithMsg("unknown JournalType") + } + return nil +} + +func (j *JournalType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *j = JournalType(data) + case int32: + *j = JournalType(data) + case int: + *j = JournalType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (j JournalType) Value() (driver.Value, error) { + return int64(j), nil +} + +type MetaType int32 + +const ( + MetaTypePost = iota + MetaTypeSheet = iota +) + +func (m *MetaType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *m = MetaType(data) + case int32: + *m = MetaType(data) + case int: + *m = MetaType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (m MetaType) Value() (driver.Value, error) { + return int64(m), nil +} + +type ThemeUpdateStrategy int32 + +const ( + ThemeUpdateStrategyBranch = iota + ThemeUpdateStrategyRelease +) + +type ThemeConfigInputType int32 + +const ( + // ThemeConfigInputTypeTEXT Text input type + ThemeConfigInputTypeTEXT = iota + + // ThemeConfigInputTypeNUMBER Number input type + ThemeConfigInputTypeNUMBER + + // ThemeConfigInputTypeRADIO Radio box input type + ThemeConfigInputTypeRADIO + + // ThemeConfigInputTypeSELECT Select input type + ThemeConfigInputTypeSELECT + + // ThemeConfigInputTypeTEXTAREA Textarea input type + ThemeConfigInputTypeTEXTAREA + + // ThemeConfigInputTypeCOLOR Color picker input type + ThemeConfigInputTypeCOLOR + + // ThemeConfigInputTypeATTACHMENT Attachment picker input type + ThemeConfigInputTypeATTACHMENT + + // ThemeConfigInputTypeSWITCH Switch input type, only true or false + ThemeConfigInputTypeSWITCH +) + +func (t ThemeConfigInputType) MarshalJSON() ([]byte, error) { + switch t { + case ThemeConfigInputTypeTEXT: + return []byte(`"TEXT"`), nil + case ThemeConfigInputTypeNUMBER: + return []byte(`"NUMBER"`), nil + case ThemeConfigInputTypeRADIO: + return []byte(`"RADIO"`), nil + case ThemeConfigInputTypeSELECT: + return []byte(`"SELECT"`), nil + case ThemeConfigInputTypeTEXTAREA: + return []byte(`"TEXTAREA"`), nil + case ThemeConfigInputTypeCOLOR: + return []byte(`"COLOR"`), nil + case ThemeConfigInputTypeATTACHMENT: + return []byte(`"ATTACHMENT"`), nil + case ThemeConfigInputTypeSWITCH: + return []byte(`"SWITCH"`), nil + default: + return nil, xerr.BadParam.New("").WithMsg("unknown ThemeConfigInputType") + } +} + +func (t *ThemeConfigInputType) UnmarshalJSON(data []byte) error { + str := string(data) + switch str { + case `"TEXT"`: + *t = ThemeConfigInputTypeTEXT + return nil + case `"NUMBER"`: + *t = ThemeConfigInputTypeNUMBER + return nil + case `"RADIO"`: + *t = ThemeConfigInputTypeRADIO + return nil + case `"SELECT"`: + *t = ThemeConfigInputTypeSELECT + return nil + case `"TEXTAREA"`: + *t = ThemeConfigInputTypeTEXTAREA + return nil + case `"COLOR"`: + *t = ThemeConfigInputTypeCOLOR + return nil + case `"SWITCH"`: + *t = ThemeConfigInputTypeSWITCH + return nil + case `"ATTACHMENT"`: + *t = ThemeConfigInputTypeATTACHMENT + return nil + default: + return xerr.BadParam.New("").WithMsg("unknown ThemeConfigInputType") + } +} + +func (t *ThemeConfigInputType) UnmarshalYAML(unmarshal func(interface{}) error) error { + strType := "" + err := unmarshal(&strType) + if err != nil { + return xerr.BadParam.New("").WithMsg("ThemeConfigInputType yaml unmarshal err") + } + strType = strings.ToUpper(strType) + switch strType { + case "TEXT": + *t = ThemeConfigInputTypeTEXT + return nil + case "NUMBER": + *t = ThemeConfigInputTypeNUMBER + return nil + case "RADIO": + *t = ThemeConfigInputTypeRADIO + return nil + case "SELECT": + *t = ThemeConfigInputTypeSELECT + return nil + case "TEXTAREA": + *t = ThemeConfigInputTypeTEXTAREA + return nil + case "COLOR": + *t = ThemeConfigInputTypeCOLOR + return nil + case "SWITCH": + *t = ThemeConfigInputTypeSWITCH + return nil + case "ATTACHMENT": + *t = ThemeConfigInputTypeATTACHMENT + return nil + default: + return xerr.BadParam.New("").WithMsg("unknown ThemeConfigInputType") + } +} + +type ThemeConfigDataType int32 + +const ( + ThemeConfigDataTypeString ThemeConfigDataType = iota + ThemeConfigDataTypeLong + ThemeConfigDataTypeDouble + ThemeConfigDataTypeBool +) + +func (t ThemeConfigDataType) Convert(value string) (interface{}, error) { + switch t { + case ThemeConfigDataTypeString: + return value, nil + case ThemeConfigDataTypeLong: + result, err := strconv.ParseInt(value, 10, 64) + if err != nil { + return nil, xerr.WithErrMsgf(err, "value invalid ThemeConfigDataType") + } + return result, nil + case ThemeConfigDataTypeDouble: + result, err := strconv.ParseFloat(value, 64) + if err != nil { + return nil, xerr.WithErrMsgf(err, "value invalid ThemeConfigDataType") + } + return result, nil + case ThemeConfigDataTypeBool: + result, err := strconv.ParseBool(value) + if err != nil { + return nil, xerr.WithErrMsgf(err, "value invalid ThemeConfigDataType") + } + return result, nil + default: + return nil, xerr.WithErrMsgf(nil, "invalid ThemeConfigDataType") + } +} + +func (t ThemeConfigDataType) FormatToStr(value interface{}) (string, error) { + switch t { + case ThemeConfigDataTypeString: + valueStr, ok := value.(string) + if !ok { + return "", xerr.WithErrMsgf(nil, "value invalid ThemeConfigDataType") + } + return valueStr, nil + case ThemeConfigDataTypeLong: + var valueStr string + switch data := value.(type) { + case int: + valueStr = strconv.FormatInt(int64(data), 10) + case int64: + valueStr = strconv.FormatInt(data, 10) + case int32: + valueStr = strconv.FormatInt(int64(data), 10) + default: + return "", xerr.WithErrMsgf(nil, "value invalid ThemeConfigDataType") + } + return valueStr, nil + case ThemeConfigDataTypeDouble: + var valueStr string + switch data := value.(type) { + case float32: + valueStr = strconv.FormatFloat(float64(data), 'f', 5, 32) + case float64: + valueStr = strconv.FormatFloat(float64(data), 'f', 5, 64) + default: + return "", xerr.WithErrMsgf(nil, "value invalid ThemeConfigDataType") + } + return valueStr, nil + case ThemeConfigDataTypeBool: + var valueStr string + switch data := value.(type) { + case bool: + valueStr = strconv.FormatBool(data) + default: + return "", xerr.WithErrMsgf(nil, "value invalid ThemeConfigDataType") + } + return valueStr, nil + default: + return "", xerr.WithErrMsgf(nil, "invalid ThemeConfigDataType") + } +} + +func (t ThemeConfigDataType) MarshalJSON() ([]byte, error) { + switch t { + case ThemeConfigDataTypeString: + return []byte(`"STRING"`), nil + case ThemeConfigDataTypeLong: + return []byte(`"LONG"`), nil + case ThemeConfigDataTypeDouble: + return []byte(`"DOUBLE"`), nil + case ThemeConfigDataTypeBool: + return []byte(`"BOOL"`), nil + default: + return nil, xerr.BadParam.New("").WithMsg("unknown ThemeConfigDataType") + } +} + +func (t *ThemeConfigDataType) UnmarshalJSON(data []byte) error { + str := string(data) + switch str { + case `"STRING"`: + *t = ThemeConfigDataTypeString + return nil + case `"LONG"`: + *t = ThemeConfigDataTypeLong + return nil + case `"DOUBLE"`: + *t = ThemeConfigDataTypeDouble + return nil + case `"BOOL"`: + *t = ThemeConfigDataTypeBool + return nil + default: + return xerr.BadParam.New("").WithMsg("unknown ThemeConfigInputType") + } +} + +func (t *ThemeConfigDataType) UnmarshalYAML(unmarshal func(interface{}) error) error { + strType := "" + err := unmarshal(&strType) + if err != nil { + return xerr.BadParam.New("").WithMsg("ThemeConfigDataType yaml unmarshal err") + } + strType = strings.ToUpper(strType) + switch strType { + case "STRING": + *t = ThemeConfigDataTypeString + return nil + case "LONG": + *t = ThemeConfigDataTypeLong + return nil + case "DOUBLE": + *t = ThemeConfigDataTypeDouble + return nil + case "BOOL": + *t = ThemeConfigDataTypeBool + return nil + default: + return xerr.BadParam.New("").WithMsg("unknown ThemeConfigDataType") + } +} + +type EncryptType int32 + +const ( + EncryptTypePost EncryptType = iota + EncryptTypeCategory +) + +func (e EncryptType) Name() string { + if e == EncryptTypePost { + return "post" + } + if e == EncryptTypeCategory { + return "category" + } + return "" +} + +type CategoryType int32 + +const ( + CategoryTypeNormal CategoryType = iota + CategoryTypeIntimate +) + +func (c CategoryType) MarshalJSON() ([]byte, error) { + if c == CategoryTypeNormal { + return []byte(`"NORMAL"`), nil + } else if c == CategoryTypeIntimate { + return []byte(`"INTIMATE"`), nil + } + return nil, nil +} + +func (c *CategoryType) UnmarshalJSON(data []byte) error { + str := string(data) + if str == `"NORMAL"` { + *c = CategoryTypeNormal + } else if str == `"INTIMATE"` { + *c = CategoryTypeIntimate + } else { + return xerr.BadParam.New("").WithMsg("unknown PostStatus") + } + return nil +} + +func (c *CategoryType) Scan(src interface{}) error { + if src == nil { + return xerr.BadParam.New("").WithMsg("field nil") + } + switch data := src.(type) { + case int64: + *c = CategoryType(data) + case int32: + *c = CategoryType(data) + case int: + *c = CategoryType(data) + default: + return xerr.BadParam.New("").WithMsg("bad type") + } + return nil +} + +func (c CategoryType) Value() (driver.Value, error) { + return int64(c), nil +} + +func (c CategoryType) Ptr() *CategoryType { + return &c +} diff --git a/dal/attachment.gen.go b/dal/attachment.gen.go new file mode 100644 index 00000000..572bcb9a --- /dev/null +++ b/dal/attachment.gen.go @@ -0,0 +1,368 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newAttachment(db *gorm.DB) attachment { + _attachment := attachment{} + + _attachment.attachmentDo.UseDB(db) + _attachment.attachmentDo.UseModel(&entity.Attachment{}) + + tableName := _attachment.attachmentDo.TableName() + _attachment.ALL = field.NewAsterisk(tableName) + _attachment.ID = field.NewInt32(tableName, "id") + _attachment.CreateTime = field.NewTime(tableName, "create_time") + _attachment.UpdateTime = field.NewTime(tableName, "update_time") + _attachment.FileKey = field.NewString(tableName, "file_key") + _attachment.Height = field.NewInt32(tableName, "height") + _attachment.MediaType = field.NewString(tableName, "media_type") + _attachment.Name = field.NewString(tableName, "name") + _attachment.Path = field.NewString(tableName, "path") + _attachment.Size = field.NewInt64(tableName, "size") + _attachment.Suffix = field.NewString(tableName, "suffix") + _attachment.ThumbPath = field.NewString(tableName, "thumb_path") + _attachment.Type = field.NewField(tableName, "type") + _attachment.Width = field.NewInt32(tableName, "width") + + _attachment.fillFieldMap() + + return _attachment +} + +type attachment struct { + attachmentDo attachmentDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + FileKey field.String + Height field.Int32 + MediaType field.String + Name field.String + Path field.String + Size field.Int64 + Suffix field.String + ThumbPath field.String + Type field.Field + Width field.Int32 + + fieldMap map[string]field.Expr +} + +func (a attachment) Table(newTableName string) *attachment { + a.attachmentDo.UseTable(newTableName) + return a.updateTableName(newTableName) +} + +func (a attachment) As(alias string) *attachment { + a.attachmentDo.DO = *(a.attachmentDo.As(alias).(*gen.DO)) + return a.updateTableName(alias) +} + +func (a *attachment) updateTableName(table string) *attachment { + a.ALL = field.NewAsterisk(table) + a.ID = field.NewInt32(table, "id") + a.CreateTime = field.NewTime(table, "create_time") + a.UpdateTime = field.NewTime(table, "update_time") + a.FileKey = field.NewString(table, "file_key") + a.Height = field.NewInt32(table, "height") + a.MediaType = field.NewString(table, "media_type") + a.Name = field.NewString(table, "name") + a.Path = field.NewString(table, "path") + a.Size = field.NewInt64(table, "size") + a.Suffix = field.NewString(table, "suffix") + a.ThumbPath = field.NewString(table, "thumb_path") + a.Type = field.NewField(table, "type") + a.Width = field.NewInt32(table, "width") + + a.fillFieldMap() + + return a +} + +func (a *attachment) WithContext(ctx context.Context) *attachmentDo { + return a.attachmentDo.WithContext(ctx) +} + +func (a attachment) TableName() string { return a.attachmentDo.TableName() } + +func (a attachment) Alias() string { return a.attachmentDo.Alias() } + +func (a *attachment) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := a.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (a *attachment) fillFieldMap() { + a.fieldMap = make(map[string]field.Expr, 13) + a.fieldMap["id"] = a.ID + a.fieldMap["create_time"] = a.CreateTime + a.fieldMap["update_time"] = a.UpdateTime + a.fieldMap["file_key"] = a.FileKey + a.fieldMap["height"] = a.Height + a.fieldMap["media_type"] = a.MediaType + a.fieldMap["name"] = a.Name + a.fieldMap["path"] = a.Path + a.fieldMap["size"] = a.Size + a.fieldMap["suffix"] = a.Suffix + a.fieldMap["thumb_path"] = a.ThumbPath + a.fieldMap["type"] = a.Type + a.fieldMap["width"] = a.Width +} + +func (a attachment) clone(db *gorm.DB) attachment { + a.attachmentDo.ReplaceDB(db) + return a +} + +type attachmentDo struct{ gen.DO } + +func (a attachmentDo) Debug() *attachmentDo { + return a.withDO(a.DO.Debug()) +} + +func (a attachmentDo) WithContext(ctx context.Context) *attachmentDo { + return a.withDO(a.DO.WithContext(ctx)) +} + +func (a attachmentDo) ReadDB() *attachmentDo { + return a.Clauses(dbresolver.Read) +} + +func (a attachmentDo) WriteDB() *attachmentDo { + return a.Clauses(dbresolver.Write) +} + +func (a attachmentDo) Clauses(conds ...clause.Expression) *attachmentDo { + return a.withDO(a.DO.Clauses(conds...)) +} + +func (a attachmentDo) Returning(value interface{}, columns ...string) *attachmentDo { + return a.withDO(a.DO.Returning(value, columns...)) +} + +func (a attachmentDo) Not(conds ...gen.Condition) *attachmentDo { + return a.withDO(a.DO.Not(conds...)) +} + +func (a attachmentDo) Or(conds ...gen.Condition) *attachmentDo { + return a.withDO(a.DO.Or(conds...)) +} + +func (a attachmentDo) Select(conds ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Select(conds...)) +} + +func (a attachmentDo) Where(conds ...gen.Condition) *attachmentDo { + return a.withDO(a.DO.Where(conds...)) +} + +func (a attachmentDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *attachmentDo { + return a.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (a attachmentDo) Order(conds ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Order(conds...)) +} + +func (a attachmentDo) Distinct(cols ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Distinct(cols...)) +} + +func (a attachmentDo) Omit(cols ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Omit(cols...)) +} + +func (a attachmentDo) Join(table schema.Tabler, on ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Join(table, on...)) +} + +func (a attachmentDo) LeftJoin(table schema.Tabler, on ...field.Expr) *attachmentDo { + return a.withDO(a.DO.LeftJoin(table, on...)) +} + +func (a attachmentDo) RightJoin(table schema.Tabler, on ...field.Expr) *attachmentDo { + return a.withDO(a.DO.RightJoin(table, on...)) +} + +func (a attachmentDo) Group(cols ...field.Expr) *attachmentDo { + return a.withDO(a.DO.Group(cols...)) +} + +func (a attachmentDo) Having(conds ...gen.Condition) *attachmentDo { + return a.withDO(a.DO.Having(conds...)) +} + +func (a attachmentDo) Limit(limit int) *attachmentDo { + return a.withDO(a.DO.Limit(limit)) +} + +func (a attachmentDo) Offset(offset int) *attachmentDo { + return a.withDO(a.DO.Offset(offset)) +} + +func (a attachmentDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *attachmentDo { + return a.withDO(a.DO.Scopes(funcs...)) +} + +func (a attachmentDo) Unscoped() *attachmentDo { + return a.withDO(a.DO.Unscoped()) +} + +func (a attachmentDo) Create(values ...*entity.Attachment) error { + if len(values) == 0 { + return nil + } + return a.DO.Create(values) +} + +func (a attachmentDo) CreateInBatches(values []*entity.Attachment, batchSize int) error { + return a.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (a attachmentDo) Save(values ...*entity.Attachment) error { + if len(values) == 0 { + return nil + } + return a.DO.Save(values) +} + +func (a attachmentDo) First() (*entity.Attachment, error) { + if result, err := a.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Attachment), nil + } +} + +func (a attachmentDo) Take() (*entity.Attachment, error) { + if result, err := a.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Attachment), nil + } +} + +func (a attachmentDo) Last() (*entity.Attachment, error) { + if result, err := a.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Attachment), nil + } +} + +func (a attachmentDo) Find() ([]*entity.Attachment, error) { + result, err := a.DO.Find() + return result.([]*entity.Attachment), err +} + +func (a attachmentDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Attachment, err error) { + buf := make([]*entity.Attachment, 0, batchSize) + err = a.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (a attachmentDo) FindInBatches(result *[]*entity.Attachment, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return a.DO.FindInBatches(result, batchSize, fc) +} + +func (a attachmentDo) Attrs(attrs ...field.AssignExpr) *attachmentDo { + return a.withDO(a.DO.Attrs(attrs...)) +} + +func (a attachmentDo) Assign(attrs ...field.AssignExpr) *attachmentDo { + return a.withDO(a.DO.Assign(attrs...)) +} + +func (a attachmentDo) Joins(fields ...field.RelationField) *attachmentDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Joins(_f)) + } + return &a +} + +func (a attachmentDo) Preload(fields ...field.RelationField) *attachmentDo { + for _, _f := range fields { + a = *a.withDO(a.DO.Preload(_f)) + } + return &a +} + +func (a attachmentDo) FirstOrInit() (*entity.Attachment, error) { + if result, err := a.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Attachment), nil + } +} + +func (a attachmentDo) FirstOrCreate() (*entity.Attachment, error) { + if result, err := a.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Attachment), nil + } +} + +func (a attachmentDo) FindByPage(offset int, limit int) (result []*entity.Attachment, count int64, err error) { + result, err = a.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = a.Offset(-1).Limit(-1).Count() + return +} + +func (a attachmentDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = a.Count() + if err != nil { + return + } + + err = a.Offset(offset).Limit(limit).Scan(result) + return +} + +func (a attachmentDo) Scan(result interface{}) (err error) { + return a.DO.Scan(result) +} + +func (a attachmentDo) Delete(models ...*entity.Attachment) (result gen.ResultInfo, err error) { + return a.DO.Delete(models) +} + +func (a *attachmentDo) withDO(do gen.Dao) *attachmentDo { + a.DO = *do.(*gen.DO) + return a +} diff --git a/dal/category.gen.go b/dal/category.gen.go new file mode 100644 index 00000000..f8e100fc --- /dev/null +++ b/dal/category.gen.go @@ -0,0 +1,358 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newCategory(db *gorm.DB) category { + _category := category{} + + _category.categoryDo.UseDB(db) + _category.categoryDo.UseModel(&entity.Category{}) + + tableName := _category.categoryDo.TableName() + _category.ALL = field.NewAsterisk(tableName) + _category.ID = field.NewInt32(tableName, "id") + _category.CreateTime = field.NewTime(tableName, "create_time") + _category.UpdateTime = field.NewTime(tableName, "update_time") + _category.Description = field.NewString(tableName, "description") + _category.Name = field.NewString(tableName, "name") + _category.ParentID = field.NewInt32(tableName, "parent_id") + _category.Password = field.NewString(tableName, "password") + _category.Slug = field.NewString(tableName, "slug") + _category.Thumbnail = field.NewString(tableName, "thumbnail") + _category.Priority = field.NewInt32(tableName, "priority") + _category.Type = field.NewField(tableName, "type") + + _category.fillFieldMap() + + return _category +} + +type category struct { + categoryDo categoryDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Description field.String + Name field.String + ParentID field.Int32 + Password field.String + Slug field.String + Thumbnail field.String + Priority field.Int32 + Type field.Field + + fieldMap map[string]field.Expr +} + +func (c category) Table(newTableName string) *category { + c.categoryDo.UseTable(newTableName) + return c.updateTableName(newTableName) +} + +func (c category) As(alias string) *category { + c.categoryDo.DO = *(c.categoryDo.As(alias).(*gen.DO)) + return c.updateTableName(alias) +} + +func (c *category) updateTableName(table string) *category { + c.ALL = field.NewAsterisk(table) + c.ID = field.NewInt32(table, "id") + c.CreateTime = field.NewTime(table, "create_time") + c.UpdateTime = field.NewTime(table, "update_time") + c.Description = field.NewString(table, "description") + c.Name = field.NewString(table, "name") + c.ParentID = field.NewInt32(table, "parent_id") + c.Password = field.NewString(table, "password") + c.Slug = field.NewString(table, "slug") + c.Thumbnail = field.NewString(table, "thumbnail") + c.Priority = field.NewInt32(table, "priority") + c.Type = field.NewField(table, "type") + + c.fillFieldMap() + + return c +} + +func (c *category) WithContext(ctx context.Context) *categoryDo { return c.categoryDo.WithContext(ctx) } + +func (c category) TableName() string { return c.categoryDo.TableName() } + +func (c category) Alias() string { return c.categoryDo.Alias() } + +func (c *category) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := c.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (c *category) fillFieldMap() { + c.fieldMap = make(map[string]field.Expr, 11) + c.fieldMap["id"] = c.ID + c.fieldMap["create_time"] = c.CreateTime + c.fieldMap["update_time"] = c.UpdateTime + c.fieldMap["description"] = c.Description + c.fieldMap["name"] = c.Name + c.fieldMap["parent_id"] = c.ParentID + c.fieldMap["password"] = c.Password + c.fieldMap["slug"] = c.Slug + c.fieldMap["thumbnail"] = c.Thumbnail + c.fieldMap["priority"] = c.Priority + c.fieldMap["type"] = c.Type +} + +func (c category) clone(db *gorm.DB) category { + c.categoryDo.ReplaceDB(db) + return c +} + +type categoryDo struct{ gen.DO } + +func (c categoryDo) Debug() *categoryDo { + return c.withDO(c.DO.Debug()) +} + +func (c categoryDo) WithContext(ctx context.Context) *categoryDo { + return c.withDO(c.DO.WithContext(ctx)) +} + +func (c categoryDo) ReadDB() *categoryDo { + return c.Clauses(dbresolver.Read) +} + +func (c categoryDo) WriteDB() *categoryDo { + return c.Clauses(dbresolver.Write) +} + +func (c categoryDo) Clauses(conds ...clause.Expression) *categoryDo { + return c.withDO(c.DO.Clauses(conds...)) +} + +func (c categoryDo) Returning(value interface{}, columns ...string) *categoryDo { + return c.withDO(c.DO.Returning(value, columns...)) +} + +func (c categoryDo) Not(conds ...gen.Condition) *categoryDo { + return c.withDO(c.DO.Not(conds...)) +} + +func (c categoryDo) Or(conds ...gen.Condition) *categoryDo { + return c.withDO(c.DO.Or(conds...)) +} + +func (c categoryDo) Select(conds ...field.Expr) *categoryDo { + return c.withDO(c.DO.Select(conds...)) +} + +func (c categoryDo) Where(conds ...gen.Condition) *categoryDo { + return c.withDO(c.DO.Where(conds...)) +} + +func (c categoryDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *categoryDo { + return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (c categoryDo) Order(conds ...field.Expr) *categoryDo { + return c.withDO(c.DO.Order(conds...)) +} + +func (c categoryDo) Distinct(cols ...field.Expr) *categoryDo { + return c.withDO(c.DO.Distinct(cols...)) +} + +func (c categoryDo) Omit(cols ...field.Expr) *categoryDo { + return c.withDO(c.DO.Omit(cols...)) +} + +func (c categoryDo) Join(table schema.Tabler, on ...field.Expr) *categoryDo { + return c.withDO(c.DO.Join(table, on...)) +} + +func (c categoryDo) LeftJoin(table schema.Tabler, on ...field.Expr) *categoryDo { + return c.withDO(c.DO.LeftJoin(table, on...)) +} + +func (c categoryDo) RightJoin(table schema.Tabler, on ...field.Expr) *categoryDo { + return c.withDO(c.DO.RightJoin(table, on...)) +} + +func (c categoryDo) Group(cols ...field.Expr) *categoryDo { + return c.withDO(c.DO.Group(cols...)) +} + +func (c categoryDo) Having(conds ...gen.Condition) *categoryDo { + return c.withDO(c.DO.Having(conds...)) +} + +func (c categoryDo) Limit(limit int) *categoryDo { + return c.withDO(c.DO.Limit(limit)) +} + +func (c categoryDo) Offset(offset int) *categoryDo { + return c.withDO(c.DO.Offset(offset)) +} + +func (c categoryDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *categoryDo { + return c.withDO(c.DO.Scopes(funcs...)) +} + +func (c categoryDo) Unscoped() *categoryDo { + return c.withDO(c.DO.Unscoped()) +} + +func (c categoryDo) Create(values ...*entity.Category) error { + if len(values) == 0 { + return nil + } + return c.DO.Create(values) +} + +func (c categoryDo) CreateInBatches(values []*entity.Category, batchSize int) error { + return c.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (c categoryDo) Save(values ...*entity.Category) error { + if len(values) == 0 { + return nil + } + return c.DO.Save(values) +} + +func (c categoryDo) First() (*entity.Category, error) { + if result, err := c.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Category), nil + } +} + +func (c categoryDo) Take() (*entity.Category, error) { + if result, err := c.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Category), nil + } +} + +func (c categoryDo) Last() (*entity.Category, error) { + if result, err := c.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Category), nil + } +} + +func (c categoryDo) Find() ([]*entity.Category, error) { + result, err := c.DO.Find() + return result.([]*entity.Category), err +} + +func (c categoryDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Category, err error) { + buf := make([]*entity.Category, 0, batchSize) + err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (c categoryDo) FindInBatches(result *[]*entity.Category, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return c.DO.FindInBatches(result, batchSize, fc) +} + +func (c categoryDo) Attrs(attrs ...field.AssignExpr) *categoryDo { + return c.withDO(c.DO.Attrs(attrs...)) +} + +func (c categoryDo) Assign(attrs ...field.AssignExpr) *categoryDo { + return c.withDO(c.DO.Assign(attrs...)) +} + +func (c categoryDo) Joins(fields ...field.RelationField) *categoryDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Joins(_f)) + } + return &c +} + +func (c categoryDo) Preload(fields ...field.RelationField) *categoryDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Preload(_f)) + } + return &c +} + +func (c categoryDo) FirstOrInit() (*entity.Category, error) { + if result, err := c.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Category), nil + } +} + +func (c categoryDo) FirstOrCreate() (*entity.Category, error) { + if result, err := c.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Category), nil + } +} + +func (c categoryDo) FindByPage(offset int, limit int) (result []*entity.Category, count int64, err error) { + result, err = c.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = c.Offset(-1).Limit(-1).Count() + return +} + +func (c categoryDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = c.Count() + if err != nil { + return + } + + err = c.Offset(offset).Limit(limit).Scan(result) + return +} + +func (c categoryDo) Scan(result interface{}) (err error) { + return c.DO.Scan(result) +} + +func (c categoryDo) Delete(models ...*entity.Category) (result gen.ResultInfo, err error) { + return c.DO.Delete(models) +} + +func (c *categoryDo) withDO(do gen.Dao) *categoryDo { + c.DO = *do.(*gen.DO) + return c +} diff --git a/dal/comment.gen.go b/dal/comment.gen.go new file mode 100644 index 00000000..64e5dd49 --- /dev/null +++ b/dal/comment.gen.go @@ -0,0 +1,382 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newComment(db *gorm.DB) comment { + _comment := comment{} + + _comment.commentDo.UseDB(db) + _comment.commentDo.UseModel(&entity.Comment{}) + + tableName := _comment.commentDo.TableName() + _comment.ALL = field.NewAsterisk(tableName) + _comment.ID = field.NewInt64(tableName, "id") + _comment.Type = field.NewField(tableName, "type") + _comment.CreateTime = field.NewTime(tableName, "create_time") + _comment.UpdateTime = field.NewTime(tableName, "update_time") + _comment.AllowNotification = field.NewBool(tableName, "allow_notification") + _comment.Author = field.NewString(tableName, "author") + _comment.AuthorURL = field.NewString(tableName, "author_url") + _comment.Content = field.NewString(tableName, "content") + _comment.Email = field.NewString(tableName, "email") + _comment.GravatarMd5 = field.NewString(tableName, "gravatar_md5") + _comment.IPAddress = field.NewString(tableName, "ip_address") + _comment.IsAdmin = field.NewBool(tableName, "is_admin") + _comment.ParentID = field.NewInt64(tableName, "parent_id") + _comment.PostID = field.NewInt32(tableName, "post_id") + _comment.Status = field.NewField(tableName, "status") + _comment.TopPriority = field.NewInt32(tableName, "top_priority") + _comment.UserAgent = field.NewString(tableName, "user_agent") + + _comment.fillFieldMap() + + return _comment +} + +type comment struct { + commentDo commentDo + + ALL field.Asterisk + ID field.Int64 + Type field.Field + CreateTime field.Time + UpdateTime field.Time + AllowNotification field.Bool + Author field.String + AuthorURL field.String + Content field.String + Email field.String + GravatarMd5 field.String + IPAddress field.String + IsAdmin field.Bool + ParentID field.Int64 + PostID field.Int32 + Status field.Field + TopPriority field.Int32 + UserAgent field.String + + fieldMap map[string]field.Expr +} + +func (c comment) Table(newTableName string) *comment { + c.commentDo.UseTable(newTableName) + return c.updateTableName(newTableName) +} + +func (c comment) As(alias string) *comment { + c.commentDo.DO = *(c.commentDo.As(alias).(*gen.DO)) + return c.updateTableName(alias) +} + +func (c *comment) updateTableName(table string) *comment { + c.ALL = field.NewAsterisk(table) + c.ID = field.NewInt64(table, "id") + c.Type = field.NewField(table, "type") + c.CreateTime = field.NewTime(table, "create_time") + c.UpdateTime = field.NewTime(table, "update_time") + c.AllowNotification = field.NewBool(table, "allow_notification") + c.Author = field.NewString(table, "author") + c.AuthorURL = field.NewString(table, "author_url") + c.Content = field.NewString(table, "content") + c.Email = field.NewString(table, "email") + c.GravatarMd5 = field.NewString(table, "gravatar_md5") + c.IPAddress = field.NewString(table, "ip_address") + c.IsAdmin = field.NewBool(table, "is_admin") + c.ParentID = field.NewInt64(table, "parent_id") + c.PostID = field.NewInt32(table, "post_id") + c.Status = field.NewField(table, "status") + c.TopPriority = field.NewInt32(table, "top_priority") + c.UserAgent = field.NewString(table, "user_agent") + + c.fillFieldMap() + + return c +} + +func (c *comment) WithContext(ctx context.Context) *commentDo { return c.commentDo.WithContext(ctx) } + +func (c comment) TableName() string { return c.commentDo.TableName() } + +func (c comment) Alias() string { return c.commentDo.Alias() } + +func (c *comment) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := c.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (c *comment) fillFieldMap() { + c.fieldMap = make(map[string]field.Expr, 17) + c.fieldMap["id"] = c.ID + c.fieldMap["type"] = c.Type + c.fieldMap["create_time"] = c.CreateTime + c.fieldMap["update_time"] = c.UpdateTime + c.fieldMap["allow_notification"] = c.AllowNotification + c.fieldMap["author"] = c.Author + c.fieldMap["author_url"] = c.AuthorURL + c.fieldMap["content"] = c.Content + c.fieldMap["email"] = c.Email + c.fieldMap["gravatar_md5"] = c.GravatarMd5 + c.fieldMap["ip_address"] = c.IPAddress + c.fieldMap["is_admin"] = c.IsAdmin + c.fieldMap["parent_id"] = c.ParentID + c.fieldMap["post_id"] = c.PostID + c.fieldMap["status"] = c.Status + c.fieldMap["top_priority"] = c.TopPriority + c.fieldMap["user_agent"] = c.UserAgent +} + +func (c comment) clone(db *gorm.DB) comment { + c.commentDo.ReplaceDB(db) + return c +} + +type commentDo struct{ gen.DO } + +func (c commentDo) Debug() *commentDo { + return c.withDO(c.DO.Debug()) +} + +func (c commentDo) WithContext(ctx context.Context) *commentDo { + return c.withDO(c.DO.WithContext(ctx)) +} + +func (c commentDo) ReadDB() *commentDo { + return c.Clauses(dbresolver.Read) +} + +func (c commentDo) WriteDB() *commentDo { + return c.Clauses(dbresolver.Write) +} + +func (c commentDo) Clauses(conds ...clause.Expression) *commentDo { + return c.withDO(c.DO.Clauses(conds...)) +} + +func (c commentDo) Returning(value interface{}, columns ...string) *commentDo { + return c.withDO(c.DO.Returning(value, columns...)) +} + +func (c commentDo) Not(conds ...gen.Condition) *commentDo { + return c.withDO(c.DO.Not(conds...)) +} + +func (c commentDo) Or(conds ...gen.Condition) *commentDo { + return c.withDO(c.DO.Or(conds...)) +} + +func (c commentDo) Select(conds ...field.Expr) *commentDo { + return c.withDO(c.DO.Select(conds...)) +} + +func (c commentDo) Where(conds ...gen.Condition) *commentDo { + return c.withDO(c.DO.Where(conds...)) +} + +func (c commentDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *commentDo { + return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (c commentDo) Order(conds ...field.Expr) *commentDo { + return c.withDO(c.DO.Order(conds...)) +} + +func (c commentDo) Distinct(cols ...field.Expr) *commentDo { + return c.withDO(c.DO.Distinct(cols...)) +} + +func (c commentDo) Omit(cols ...field.Expr) *commentDo { + return c.withDO(c.DO.Omit(cols...)) +} + +func (c commentDo) Join(table schema.Tabler, on ...field.Expr) *commentDo { + return c.withDO(c.DO.Join(table, on...)) +} + +func (c commentDo) LeftJoin(table schema.Tabler, on ...field.Expr) *commentDo { + return c.withDO(c.DO.LeftJoin(table, on...)) +} + +func (c commentDo) RightJoin(table schema.Tabler, on ...field.Expr) *commentDo { + return c.withDO(c.DO.RightJoin(table, on...)) +} + +func (c commentDo) Group(cols ...field.Expr) *commentDo { + return c.withDO(c.DO.Group(cols...)) +} + +func (c commentDo) Having(conds ...gen.Condition) *commentDo { + return c.withDO(c.DO.Having(conds...)) +} + +func (c commentDo) Limit(limit int) *commentDo { + return c.withDO(c.DO.Limit(limit)) +} + +func (c commentDo) Offset(offset int) *commentDo { + return c.withDO(c.DO.Offset(offset)) +} + +func (c commentDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *commentDo { + return c.withDO(c.DO.Scopes(funcs...)) +} + +func (c commentDo) Unscoped() *commentDo { + return c.withDO(c.DO.Unscoped()) +} + +func (c commentDo) Create(values ...*entity.Comment) error { + if len(values) == 0 { + return nil + } + return c.DO.Create(values) +} + +func (c commentDo) CreateInBatches(values []*entity.Comment, batchSize int) error { + return c.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (c commentDo) Save(values ...*entity.Comment) error { + if len(values) == 0 { + return nil + } + return c.DO.Save(values) +} + +func (c commentDo) First() (*entity.Comment, error) { + if result, err := c.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Comment), nil + } +} + +func (c commentDo) Take() (*entity.Comment, error) { + if result, err := c.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Comment), nil + } +} + +func (c commentDo) Last() (*entity.Comment, error) { + if result, err := c.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Comment), nil + } +} + +func (c commentDo) Find() ([]*entity.Comment, error) { + result, err := c.DO.Find() + return result.([]*entity.Comment), err +} + +func (c commentDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Comment, err error) { + buf := make([]*entity.Comment, 0, batchSize) + err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (c commentDo) FindInBatches(result *[]*entity.Comment, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return c.DO.FindInBatches(result, batchSize, fc) +} + +func (c commentDo) Attrs(attrs ...field.AssignExpr) *commentDo { + return c.withDO(c.DO.Attrs(attrs...)) +} + +func (c commentDo) Assign(attrs ...field.AssignExpr) *commentDo { + return c.withDO(c.DO.Assign(attrs...)) +} + +func (c commentDo) Joins(fields ...field.RelationField) *commentDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Joins(_f)) + } + return &c +} + +func (c commentDo) Preload(fields ...field.RelationField) *commentDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Preload(_f)) + } + return &c +} + +func (c commentDo) FirstOrInit() (*entity.Comment, error) { + if result, err := c.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Comment), nil + } +} + +func (c commentDo) FirstOrCreate() (*entity.Comment, error) { + if result, err := c.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Comment), nil + } +} + +func (c commentDo) FindByPage(offset int, limit int) (result []*entity.Comment, count int64, err error) { + result, err = c.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = c.Offset(-1).Limit(-1).Count() + return +} + +func (c commentDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = c.Count() + if err != nil { + return + } + + err = c.Offset(offset).Limit(limit).Scan(result) + return +} + +func (c commentDo) Scan(result interface{}) (err error) { + return c.DO.Scan(result) +} + +func (c commentDo) Delete(models ...*entity.Comment) (result gen.ResultInfo, err error) { + return c.DO.Delete(models) +} + +func (c *commentDo) withDO(do gen.Dao) *commentDo { + c.DO = *do.(*gen.DO) + return c +} diff --git a/dal/comment_black.gen.go b/dal/comment_black.gen.go new file mode 100644 index 00000000..1ab248be --- /dev/null +++ b/dal/comment_black.gen.go @@ -0,0 +1,336 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newCommentBlack(db *gorm.DB) commentBlack { + _commentBlack := commentBlack{} + + _commentBlack.commentBlackDo.UseDB(db) + _commentBlack.commentBlackDo.UseModel(&entity.CommentBlack{}) + + tableName := _commentBlack.commentBlackDo.TableName() + _commentBlack.ALL = field.NewAsterisk(tableName) + _commentBlack.ID = field.NewInt64(tableName, "id") + _commentBlack.CreateTime = field.NewTime(tableName, "create_time") + _commentBlack.UpdateTime = field.NewTime(tableName, "update_time") + _commentBlack.BanTime = field.NewTime(tableName, "ban_time") + _commentBlack.IPAddress = field.NewString(tableName, "ip_address") + + _commentBlack.fillFieldMap() + + return _commentBlack +} + +type commentBlack struct { + commentBlackDo commentBlackDo + + ALL field.Asterisk + ID field.Int64 + CreateTime field.Time + UpdateTime field.Time + BanTime field.Time + IPAddress field.String + + fieldMap map[string]field.Expr +} + +func (c commentBlack) Table(newTableName string) *commentBlack { + c.commentBlackDo.UseTable(newTableName) + return c.updateTableName(newTableName) +} + +func (c commentBlack) As(alias string) *commentBlack { + c.commentBlackDo.DO = *(c.commentBlackDo.As(alias).(*gen.DO)) + return c.updateTableName(alias) +} + +func (c *commentBlack) updateTableName(table string) *commentBlack { + c.ALL = field.NewAsterisk(table) + c.ID = field.NewInt64(table, "id") + c.CreateTime = field.NewTime(table, "create_time") + c.UpdateTime = field.NewTime(table, "update_time") + c.BanTime = field.NewTime(table, "ban_time") + c.IPAddress = field.NewString(table, "ip_address") + + c.fillFieldMap() + + return c +} + +func (c *commentBlack) WithContext(ctx context.Context) *commentBlackDo { + return c.commentBlackDo.WithContext(ctx) +} + +func (c commentBlack) TableName() string { return c.commentBlackDo.TableName() } + +func (c commentBlack) Alias() string { return c.commentBlackDo.Alias() } + +func (c *commentBlack) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := c.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (c *commentBlack) fillFieldMap() { + c.fieldMap = make(map[string]field.Expr, 5) + c.fieldMap["id"] = c.ID + c.fieldMap["create_time"] = c.CreateTime + c.fieldMap["update_time"] = c.UpdateTime + c.fieldMap["ban_time"] = c.BanTime + c.fieldMap["ip_address"] = c.IPAddress +} + +func (c commentBlack) clone(db *gorm.DB) commentBlack { + c.commentBlackDo.ReplaceDB(db) + return c +} + +type commentBlackDo struct{ gen.DO } + +func (c commentBlackDo) Debug() *commentBlackDo { + return c.withDO(c.DO.Debug()) +} + +func (c commentBlackDo) WithContext(ctx context.Context) *commentBlackDo { + return c.withDO(c.DO.WithContext(ctx)) +} + +func (c commentBlackDo) ReadDB() *commentBlackDo { + return c.Clauses(dbresolver.Read) +} + +func (c commentBlackDo) WriteDB() *commentBlackDo { + return c.Clauses(dbresolver.Write) +} + +func (c commentBlackDo) Clauses(conds ...clause.Expression) *commentBlackDo { + return c.withDO(c.DO.Clauses(conds...)) +} + +func (c commentBlackDo) Returning(value interface{}, columns ...string) *commentBlackDo { + return c.withDO(c.DO.Returning(value, columns...)) +} + +func (c commentBlackDo) Not(conds ...gen.Condition) *commentBlackDo { + return c.withDO(c.DO.Not(conds...)) +} + +func (c commentBlackDo) Or(conds ...gen.Condition) *commentBlackDo { + return c.withDO(c.DO.Or(conds...)) +} + +func (c commentBlackDo) Select(conds ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Select(conds...)) +} + +func (c commentBlackDo) Where(conds ...gen.Condition) *commentBlackDo { + return c.withDO(c.DO.Where(conds...)) +} + +func (c commentBlackDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *commentBlackDo { + return c.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (c commentBlackDo) Order(conds ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Order(conds...)) +} + +func (c commentBlackDo) Distinct(cols ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Distinct(cols...)) +} + +func (c commentBlackDo) Omit(cols ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Omit(cols...)) +} + +func (c commentBlackDo) Join(table schema.Tabler, on ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Join(table, on...)) +} + +func (c commentBlackDo) LeftJoin(table schema.Tabler, on ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.LeftJoin(table, on...)) +} + +func (c commentBlackDo) RightJoin(table schema.Tabler, on ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.RightJoin(table, on...)) +} + +func (c commentBlackDo) Group(cols ...field.Expr) *commentBlackDo { + return c.withDO(c.DO.Group(cols...)) +} + +func (c commentBlackDo) Having(conds ...gen.Condition) *commentBlackDo { + return c.withDO(c.DO.Having(conds...)) +} + +func (c commentBlackDo) Limit(limit int) *commentBlackDo { + return c.withDO(c.DO.Limit(limit)) +} + +func (c commentBlackDo) Offset(offset int) *commentBlackDo { + return c.withDO(c.DO.Offset(offset)) +} + +func (c commentBlackDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *commentBlackDo { + return c.withDO(c.DO.Scopes(funcs...)) +} + +func (c commentBlackDo) Unscoped() *commentBlackDo { + return c.withDO(c.DO.Unscoped()) +} + +func (c commentBlackDo) Create(values ...*entity.CommentBlack) error { + if len(values) == 0 { + return nil + } + return c.DO.Create(values) +} + +func (c commentBlackDo) CreateInBatches(values []*entity.CommentBlack, batchSize int) error { + return c.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (c commentBlackDo) Save(values ...*entity.CommentBlack) error { + if len(values) == 0 { + return nil + } + return c.DO.Save(values) +} + +func (c commentBlackDo) First() (*entity.CommentBlack, error) { + if result, err := c.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.CommentBlack), nil + } +} + +func (c commentBlackDo) Take() (*entity.CommentBlack, error) { + if result, err := c.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.CommentBlack), nil + } +} + +func (c commentBlackDo) Last() (*entity.CommentBlack, error) { + if result, err := c.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.CommentBlack), nil + } +} + +func (c commentBlackDo) Find() ([]*entity.CommentBlack, error) { + result, err := c.DO.Find() + return result.([]*entity.CommentBlack), err +} + +func (c commentBlackDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.CommentBlack, err error) { + buf := make([]*entity.CommentBlack, 0, batchSize) + err = c.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (c commentBlackDo) FindInBatches(result *[]*entity.CommentBlack, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return c.DO.FindInBatches(result, batchSize, fc) +} + +func (c commentBlackDo) Attrs(attrs ...field.AssignExpr) *commentBlackDo { + return c.withDO(c.DO.Attrs(attrs...)) +} + +func (c commentBlackDo) Assign(attrs ...field.AssignExpr) *commentBlackDo { + return c.withDO(c.DO.Assign(attrs...)) +} + +func (c commentBlackDo) Joins(fields ...field.RelationField) *commentBlackDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Joins(_f)) + } + return &c +} + +func (c commentBlackDo) Preload(fields ...field.RelationField) *commentBlackDo { + for _, _f := range fields { + c = *c.withDO(c.DO.Preload(_f)) + } + return &c +} + +func (c commentBlackDo) FirstOrInit() (*entity.CommentBlack, error) { + if result, err := c.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.CommentBlack), nil + } +} + +func (c commentBlackDo) FirstOrCreate() (*entity.CommentBlack, error) { + if result, err := c.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.CommentBlack), nil + } +} + +func (c commentBlackDo) FindByPage(offset int, limit int) (result []*entity.CommentBlack, count int64, err error) { + result, err = c.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = c.Offset(-1).Limit(-1).Count() + return +} + +func (c commentBlackDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = c.Count() + if err != nil { + return + } + + err = c.Offset(offset).Limit(limit).Scan(result) + return +} + +func (c commentBlackDo) Scan(result interface{}) (err error) { + return c.DO.Scan(result) +} + +func (c commentBlackDo) Delete(models ...*entity.CommentBlack) (result gen.ResultInfo, err error) { + return c.DO.Delete(models) +} + +func (c *commentBlackDo) withDO(do gen.Dao) *commentBlackDo { + c.DO = *do.(*gen.DO) + return c +} diff --git a/dal/dal.go b/dal/dal.go new file mode 100644 index 00000000..ebad0178 --- /dev/null +++ b/dal/dal.go @@ -0,0 +1,131 @@ +package dal + +import ( + "context" + "fmt" + "time" + + "go.uber.org/zap" + "gorm.io/driver/mysql" + "gorm.io/driver/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/consts" + sonicLog "github.com/go-sonic/sonic/log" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/util/xerr" +) + +// mysqlDsn example user:password@tcp(127.0.0.1:3306)/dbname?charset=utf8mb4&parseTime=True&loc=Local +const mysqlDsn = "%s:%s@tcp(%s:%s)/%s?charset=utf8mb4&parseTime=True&loc=Local&timeout=3s&readTimeout=1s&writeTimeout=1s&interpolateParams=true" + +var ( + DB *gorm.DB + DBType consts.DBType +) + +func NewGormDB(conf *config.Config, gormLogger logger.Interface) *gorm.DB { + var err error + if conf.SQLite3 != nil && conf.SQLite3.Enable { + DB, err = initSQLite(conf, gormLogger) + if err != nil { + sonicLog.Fatal("open SQLite3 error", zap.Error(err)) + } + DBType = consts.DBTypeSQLite + } else if conf.MySQL != nil { + DB, err = initMySQL(conf, gormLogger) + if err != nil { + sonicLog.Fatal("connect to MySQL error", zap.Error(err)) + } + DBType = consts.DBTypeMySQL + } else { + sonicLog.Fatal("No database available") + } + if DB == nil { + sonicLog.Fatal("no available database") + } + sonicLog.Info("connect database success") + sqlDB, err := DB.DB() + if err != nil { + sonicLog.Fatal("get database connection error") + } + sqlDB.SetMaxIdleConns(200) + sqlDB.SetMaxOpenConns(300) + sqlDB.SetConnMaxIdleTime(time.Hour) + dbMigrate() + return DB +} + +func initMySQL(conf *config.Config, gormLogger logger.Interface) (*gorm.DB, error) { + mysqlConfig := conf.MySQL + if mysqlConfig == nil { + return nil, xerr.WithMsg(nil, "nil MySQL config") + } + dsn := fmt.Sprintf(mysqlDsn, mysqlConfig.Username, mysqlConfig.Password, mysqlConfig.Host, mysqlConfig.Port, mysqlConfig.DB) + sonicLog.Info("try connect to MySQL", zap.String("dsn", fmt.Sprintf(mysqlDsn, mysqlConfig.Username, "***", mysqlConfig.Host, mysqlConfig.Port, mysqlConfig.DB))) + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{ + Logger: gormLogger, + PrepareStmt: true, + SkipDefaultTransaction: true, + DisableNestedTransaction: true, + }) + return db, err +} + +func initSQLite(conf *config.Config, gormLogger logger.Interface) (*gorm.DB, error) { + sqliteConfig := conf.SQLite3 + if sqliteConfig == nil { + return nil, xerr.WithMsg(nil, "nil SQLite config") + } + sonicLog.Info("try to open SQLite3 db", zap.String("path", sqliteConfig.File)) + db, err := gorm.Open(sqlite.Open(sqliteConfig.File), &gorm.Config{ + Logger: gormLogger, + PrepareStmt: true, + SkipDefaultTransaction: true, + DisableNestedTransaction: true, + }) + return db, err +} + +func dbMigrate() { + db := DB.Session(&gorm.Session{ + Logger: DB.Logger.LogMode(logger.Warn), + }) + err := db.AutoMigrate(&entity.Attachment{}, &entity.Category{}, &entity.Comment{}, &entity.CommentBlack{}, &entity.Journal{}, + &entity.Link{}, &entity.Log{}, &entity.Menu{}, &entity.Meta{}, &entity.Option{}, &entity.Photo{}, &entity.Post{}, + &entity.PostCategory{}, &entity.PostTag{}, &entity.Tag{}, &entity.ThemeSetting{}, &entity.User{}) + if err != nil { + sonicLog.Fatal("failed auto migrate db", zap.Error(err)) + } +} + +type ctxTransaction struct{} + +func GetDBByCtx(ctx context.Context) *gorm.DB { + dbI := ctx.Value(ctxTransaction{}) + + if dbI != nil { + db, ok := dbI.(*gorm.DB) + if !ok { + panic("unexpected context db value type") + } + if db != nil { + return db + } + } + return DB.WithContext(ctx) +} + +func SetCtxDB(ctx context.Context, tx *gorm.DB) context.Context { + return context.WithValue(ctx, ctxTransaction{}, tx) +} + +func Transaction(ctx context.Context, fn func(txCtx context.Context) error) error { + db := GetDBByCtx(ctx) + return db.Transaction(func(tx *gorm.DB) error { + txCtx := SetCtxDB(ctx, tx) + return fn(txCtx) + }) +} diff --git a/dal/flyway_schema_history.gen.go b/dal/flyway_schema_history.gen.go new file mode 100644 index 00000000..6d67fec5 --- /dev/null +++ b/dal/flyway_schema_history.gen.go @@ -0,0 +1,356 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newFlywaySchemaHistory(db *gorm.DB) flywaySchemaHistory { + _flywaySchemaHistory := flywaySchemaHistory{} + + _flywaySchemaHistory.flywaySchemaHistoryDo.UseDB(db) + _flywaySchemaHistory.flywaySchemaHistoryDo.UseModel(&entity.FlywaySchemaHistory{}) + + tableName := _flywaySchemaHistory.flywaySchemaHistoryDo.TableName() + _flywaySchemaHistory.ALL = field.NewAsterisk(tableName) + _flywaySchemaHistory.InstalledRank = field.NewInt32(tableName, "installed_rank") + _flywaySchemaHistory.Version = field.NewString(tableName, "version") + _flywaySchemaHistory.Description = field.NewString(tableName, "description") + _flywaySchemaHistory.Type = field.NewString(tableName, "type") + _flywaySchemaHistory.Script = field.NewString(tableName, "script") + _flywaySchemaHistory.Checksum = field.NewInt32(tableName, "checksum") + _flywaySchemaHistory.InstalledBy = field.NewString(tableName, "installed_by") + _flywaySchemaHistory.InstalledOn = field.NewTime(tableName, "installed_on") + _flywaySchemaHistory.ExecutionTime = field.NewInt32(tableName, "execution_time") + _flywaySchemaHistory.Success = field.NewBool(tableName, "success") + + _flywaySchemaHistory.fillFieldMap() + + return _flywaySchemaHistory +} + +type flywaySchemaHistory struct { + flywaySchemaHistoryDo flywaySchemaHistoryDo + + ALL field.Asterisk + InstalledRank field.Int32 + Version field.String + Description field.String + Type field.String + Script field.String + Checksum field.Int32 + InstalledBy field.String + InstalledOn field.Time + ExecutionTime field.Int32 + Success field.Bool + + fieldMap map[string]field.Expr +} + +func (f flywaySchemaHistory) Table(newTableName string) *flywaySchemaHistory { + f.flywaySchemaHistoryDo.UseTable(newTableName) + return f.updateTableName(newTableName) +} + +func (f flywaySchemaHistory) As(alias string) *flywaySchemaHistory { + f.flywaySchemaHistoryDo.DO = *(f.flywaySchemaHistoryDo.As(alias).(*gen.DO)) + return f.updateTableName(alias) +} + +func (f *flywaySchemaHistory) updateTableName(table string) *flywaySchemaHistory { + f.ALL = field.NewAsterisk(table) + f.InstalledRank = field.NewInt32(table, "installed_rank") + f.Version = field.NewString(table, "version") + f.Description = field.NewString(table, "description") + f.Type = field.NewString(table, "type") + f.Script = field.NewString(table, "script") + f.Checksum = field.NewInt32(table, "checksum") + f.InstalledBy = field.NewString(table, "installed_by") + f.InstalledOn = field.NewTime(table, "installed_on") + f.ExecutionTime = field.NewInt32(table, "execution_time") + f.Success = field.NewBool(table, "success") + + f.fillFieldMap() + + return f +} + +func (f *flywaySchemaHistory) WithContext(ctx context.Context) *flywaySchemaHistoryDo { + return f.flywaySchemaHistoryDo.WithContext(ctx) +} + +func (f flywaySchemaHistory) TableName() string { return f.flywaySchemaHistoryDo.TableName() } + +func (f flywaySchemaHistory) Alias() string { return f.flywaySchemaHistoryDo.Alias() } + +func (f *flywaySchemaHistory) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := f.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (f *flywaySchemaHistory) fillFieldMap() { + f.fieldMap = make(map[string]field.Expr, 10) + f.fieldMap["installed_rank"] = f.InstalledRank + f.fieldMap["version"] = f.Version + f.fieldMap["description"] = f.Description + f.fieldMap["type"] = f.Type + f.fieldMap["script"] = f.Script + f.fieldMap["checksum"] = f.Checksum + f.fieldMap["installed_by"] = f.InstalledBy + f.fieldMap["installed_on"] = f.InstalledOn + f.fieldMap["execution_time"] = f.ExecutionTime + f.fieldMap["success"] = f.Success +} + +func (f flywaySchemaHistory) clone(db *gorm.DB) flywaySchemaHistory { + f.flywaySchemaHistoryDo.ReplaceDB(db) + return f +} + +type flywaySchemaHistoryDo struct{ gen.DO } + +func (f flywaySchemaHistoryDo) Debug() *flywaySchemaHistoryDo { + return f.withDO(f.DO.Debug()) +} + +func (f flywaySchemaHistoryDo) WithContext(ctx context.Context) *flywaySchemaHistoryDo { + return f.withDO(f.DO.WithContext(ctx)) +} + +func (f flywaySchemaHistoryDo) ReadDB() *flywaySchemaHistoryDo { + return f.Clauses(dbresolver.Read) +} + +func (f flywaySchemaHistoryDo) WriteDB() *flywaySchemaHistoryDo { + return f.Clauses(dbresolver.Write) +} + +func (f flywaySchemaHistoryDo) Clauses(conds ...clause.Expression) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Clauses(conds...)) +} + +func (f flywaySchemaHistoryDo) Returning(value interface{}, columns ...string) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Returning(value, columns...)) +} + +func (f flywaySchemaHistoryDo) Not(conds ...gen.Condition) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Not(conds...)) +} + +func (f flywaySchemaHistoryDo) Or(conds ...gen.Condition) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Or(conds...)) +} + +func (f flywaySchemaHistoryDo) Select(conds ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Select(conds...)) +} + +func (f flywaySchemaHistoryDo) Where(conds ...gen.Condition) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Where(conds...)) +} + +func (f flywaySchemaHistoryDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *flywaySchemaHistoryDo { + return f.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (f flywaySchemaHistoryDo) Order(conds ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Order(conds...)) +} + +func (f flywaySchemaHistoryDo) Distinct(cols ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Distinct(cols...)) +} + +func (f flywaySchemaHistoryDo) Omit(cols ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Omit(cols...)) +} + +func (f flywaySchemaHistoryDo) Join(table schema.Tabler, on ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Join(table, on...)) +} + +func (f flywaySchemaHistoryDo) LeftJoin(table schema.Tabler, on ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.LeftJoin(table, on...)) +} + +func (f flywaySchemaHistoryDo) RightJoin(table schema.Tabler, on ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.RightJoin(table, on...)) +} + +func (f flywaySchemaHistoryDo) Group(cols ...field.Expr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Group(cols...)) +} + +func (f flywaySchemaHistoryDo) Having(conds ...gen.Condition) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Having(conds...)) +} + +func (f flywaySchemaHistoryDo) Limit(limit int) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Limit(limit)) +} + +func (f flywaySchemaHistoryDo) Offset(offset int) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Offset(offset)) +} + +func (f flywaySchemaHistoryDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Scopes(funcs...)) +} + +func (f flywaySchemaHistoryDo) Unscoped() *flywaySchemaHistoryDo { + return f.withDO(f.DO.Unscoped()) +} + +func (f flywaySchemaHistoryDo) Create(values ...*entity.FlywaySchemaHistory) error { + if len(values) == 0 { + return nil + } + return f.DO.Create(values) +} + +func (f flywaySchemaHistoryDo) CreateInBatches(values []*entity.FlywaySchemaHistory, batchSize int) error { + return f.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (f flywaySchemaHistoryDo) Save(values ...*entity.FlywaySchemaHistory) error { + if len(values) == 0 { + return nil + } + return f.DO.Save(values) +} + +func (f flywaySchemaHistoryDo) First() (*entity.FlywaySchemaHistory, error) { + if result, err := f.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.FlywaySchemaHistory), nil + } +} + +func (f flywaySchemaHistoryDo) Take() (*entity.FlywaySchemaHistory, error) { + if result, err := f.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.FlywaySchemaHistory), nil + } +} + +func (f flywaySchemaHistoryDo) Last() (*entity.FlywaySchemaHistory, error) { + if result, err := f.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.FlywaySchemaHistory), nil + } +} + +func (f flywaySchemaHistoryDo) Find() ([]*entity.FlywaySchemaHistory, error) { + result, err := f.DO.Find() + return result.([]*entity.FlywaySchemaHistory), err +} + +func (f flywaySchemaHistoryDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.FlywaySchemaHistory, err error) { + buf := make([]*entity.FlywaySchemaHistory, 0, batchSize) + err = f.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (f flywaySchemaHistoryDo) FindInBatches(result *[]*entity.FlywaySchemaHistory, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return f.DO.FindInBatches(result, batchSize, fc) +} + +func (f flywaySchemaHistoryDo) Attrs(attrs ...field.AssignExpr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Attrs(attrs...)) +} + +func (f flywaySchemaHistoryDo) Assign(attrs ...field.AssignExpr) *flywaySchemaHistoryDo { + return f.withDO(f.DO.Assign(attrs...)) +} + +func (f flywaySchemaHistoryDo) Joins(fields ...field.RelationField) *flywaySchemaHistoryDo { + for _, _f := range fields { + f = *f.withDO(f.DO.Joins(_f)) + } + return &f +} + +func (f flywaySchemaHistoryDo) Preload(fields ...field.RelationField) *flywaySchemaHistoryDo { + for _, _f := range fields { + f = *f.withDO(f.DO.Preload(_f)) + } + return &f +} + +func (f flywaySchemaHistoryDo) FirstOrInit() (*entity.FlywaySchemaHistory, error) { + if result, err := f.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.FlywaySchemaHistory), nil + } +} + +func (f flywaySchemaHistoryDo) FirstOrCreate() (*entity.FlywaySchemaHistory, error) { + if result, err := f.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.FlywaySchemaHistory), nil + } +} + +func (f flywaySchemaHistoryDo) FindByPage(offset int, limit int) (result []*entity.FlywaySchemaHistory, count int64, err error) { + result, err = f.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = f.Offset(-1).Limit(-1).Count() + return +} + +func (f flywaySchemaHistoryDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = f.Count() + if err != nil { + return + } + + err = f.Offset(offset).Limit(limit).Scan(result) + return +} + +func (f flywaySchemaHistoryDo) Scan(result interface{}) (err error) { + return f.DO.Scan(result) +} + +func (f flywaySchemaHistoryDo) Delete(models ...*entity.FlywaySchemaHistory) (result gen.ResultInfo, err error) { + return f.DO.Delete(models) +} + +func (f *flywaySchemaHistoryDo) withDO(do gen.Dao) *flywaySchemaHistoryDo { + f.DO = *do.(*gen.DO) + return f +} diff --git a/dal/gen.go b/dal/gen.go new file mode 100644 index 00000000..08671531 --- /dev/null +++ b/dal/gen.go @@ -0,0 +1,199 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + "database/sql" + + "gorm.io/gorm" +) + +var ( + Q = new(Query) + Attachment *attachment + Category *category + Comment *comment + CommentBlack *commentBlack + FlywaySchemaHistory *flywaySchemaHistory + Journal *journal + Link *link + Log *log + Menu *menu + Meta *meta + Option *option + Photo *photo + Post *post + PostCategory *postCategory + PostTag *postTag + Tag *tag + ThemeSetting *themeSetting + User *user +) + +func SetDefault(db *gorm.DB) { + *Q = *Use(db) + Attachment = &Q.Attachment + Category = &Q.Category + Comment = &Q.Comment + CommentBlack = &Q.CommentBlack + FlywaySchemaHistory = &Q.FlywaySchemaHistory + Journal = &Q.Journal + Link = &Q.Link + Log = &Q.Log + Menu = &Q.Menu + Meta = &Q.Meta + Option = &Q.Option + Photo = &Q.Photo + Post = &Q.Post + PostCategory = &Q.PostCategory + PostTag = &Q.PostTag + Tag = &Q.Tag + ThemeSetting = &Q.ThemeSetting + User = &Q.User +} + +func Use(db *gorm.DB) *Query { + return &Query{ + db: db, + Attachment: newAttachment(db), + Category: newCategory(db), + Comment: newComment(db), + CommentBlack: newCommentBlack(db), + FlywaySchemaHistory: newFlywaySchemaHistory(db), + Journal: newJournal(db), + Link: newLink(db), + Log: newLog(db), + Menu: newMenu(db), + Meta: newMeta(db), + Option: newOption(db), + Photo: newPhoto(db), + Post: newPost(db), + PostCategory: newPostCategory(db), + PostTag: newPostTag(db), + Tag: newTag(db), + ThemeSetting: newThemeSetting(db), + User: newUser(db), + } +} + +type Query struct { + db *gorm.DB + + Attachment attachment + Category category + Comment comment + CommentBlack commentBlack + FlywaySchemaHistory flywaySchemaHistory + Journal journal + Link link + Log log + Menu menu + Meta meta + Option option + Photo photo + Post post + PostCategory postCategory + PostTag postTag + Tag tag + ThemeSetting themeSetting + User user +} + +func (q *Query) Available() bool { return q.db != nil } + +func (q *Query) clone(db *gorm.DB) *Query { + return &Query{ + db: db, + Attachment: q.Attachment.clone(db), + Category: q.Category.clone(db), + Comment: q.Comment.clone(db), + CommentBlack: q.CommentBlack.clone(db), + FlywaySchemaHistory: q.FlywaySchemaHistory.clone(db), + Journal: q.Journal.clone(db), + Link: q.Link.clone(db), + Log: q.Log.clone(db), + Menu: q.Menu.clone(db), + Meta: q.Meta.clone(db), + Option: q.Option.clone(db), + Photo: q.Photo.clone(db), + Post: q.Post.clone(db), + PostCategory: q.PostCategory.clone(db), + PostTag: q.PostTag.clone(db), + Tag: q.Tag.clone(db), + ThemeSetting: q.ThemeSetting.clone(db), + User: q.User.clone(db), + } +} + +type queryCtx struct { + Attachment *attachmentDo + Category *categoryDo + Comment *commentDo + CommentBlack *commentBlackDo + FlywaySchemaHistory *flywaySchemaHistoryDo + Journal *journalDo + Link *linkDo + Log *logDo + Menu *menuDo + Meta *metaDo + Option *optionDo + Photo *photoDo + Post *postDo + PostCategory *postCategoryDo + PostTag *postTagDo + Tag *tagDo + ThemeSetting *themeSettingDo + User *userDo +} + +func (q *Query) WithContext(ctx context.Context) *queryCtx { + return &queryCtx{ + Attachment: q.Attachment.WithContext(ctx), + Category: q.Category.WithContext(ctx), + Comment: q.Comment.WithContext(ctx), + CommentBlack: q.CommentBlack.WithContext(ctx), + FlywaySchemaHistory: q.FlywaySchemaHistory.WithContext(ctx), + Journal: q.Journal.WithContext(ctx), + Link: q.Link.WithContext(ctx), + Log: q.Log.WithContext(ctx), + Menu: q.Menu.WithContext(ctx), + Meta: q.Meta.WithContext(ctx), + Option: q.Option.WithContext(ctx), + Photo: q.Photo.WithContext(ctx), + Post: q.Post.WithContext(ctx), + PostCategory: q.PostCategory.WithContext(ctx), + PostTag: q.PostTag.WithContext(ctx), + Tag: q.Tag.WithContext(ctx), + ThemeSetting: q.ThemeSetting.WithContext(ctx), + User: q.User.WithContext(ctx), + } +} + +func (q *Query) Transaction(fc func(tx *Query) error, opts ...*sql.TxOptions) error { + return q.db.Transaction(func(tx *gorm.DB) error { return fc(q.clone(tx)) }, opts...) +} + +func (q *Query) Begin(opts ...*sql.TxOptions) *QueryTx { + return &QueryTx{q.clone(q.db.Begin(opts...))} +} + +type QueryTx struct{ *Query } + +func (q *QueryTx) Commit() error { + return q.db.Commit().Error +} + +func (q *QueryTx) Rollback() error { + return q.db.Rollback().Error +} + +func (q *QueryTx) SavePoint(name string) error { + return q.db.SavePoint(name).Error +} + +func (q *QueryTx) RollbackTo(name string) error { + return q.db.RollbackTo(name).Error +} diff --git a/dal/journal.gen.go b/dal/journal.gen.go new file mode 100644 index 00000000..6688c4f0 --- /dev/null +++ b/dal/journal.gen.go @@ -0,0 +1,342 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newJournal(db *gorm.DB) journal { + _journal := journal{} + + _journal.journalDo.UseDB(db) + _journal.journalDo.UseModel(&entity.Journal{}) + + tableName := _journal.journalDo.TableName() + _journal.ALL = field.NewAsterisk(tableName) + _journal.ID = field.NewInt32(tableName, "id") + _journal.CreateTime = field.NewTime(tableName, "create_time") + _journal.UpdateTime = field.NewTime(tableName, "update_time") + _journal.Content = field.NewString(tableName, "content") + _journal.Likes = field.NewInt64(tableName, "likes") + _journal.SourceContent = field.NewString(tableName, "source_content") + _journal.Type = field.NewField(tableName, "type") + + _journal.fillFieldMap() + + return _journal +} + +type journal struct { + journalDo journalDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Content field.String + Likes field.Int64 + SourceContent field.String + Type field.Field + + fieldMap map[string]field.Expr +} + +func (j journal) Table(newTableName string) *journal { + j.journalDo.UseTable(newTableName) + return j.updateTableName(newTableName) +} + +func (j journal) As(alias string) *journal { + j.journalDo.DO = *(j.journalDo.As(alias).(*gen.DO)) + return j.updateTableName(alias) +} + +func (j *journal) updateTableName(table string) *journal { + j.ALL = field.NewAsterisk(table) + j.ID = field.NewInt32(table, "id") + j.CreateTime = field.NewTime(table, "create_time") + j.UpdateTime = field.NewTime(table, "update_time") + j.Content = field.NewString(table, "content") + j.Likes = field.NewInt64(table, "likes") + j.SourceContent = field.NewString(table, "source_content") + j.Type = field.NewField(table, "type") + + j.fillFieldMap() + + return j +} + +func (j *journal) WithContext(ctx context.Context) *journalDo { return j.journalDo.WithContext(ctx) } + +func (j journal) TableName() string { return j.journalDo.TableName() } + +func (j journal) Alias() string { return j.journalDo.Alias() } + +func (j *journal) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := j.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (j *journal) fillFieldMap() { + j.fieldMap = make(map[string]field.Expr, 7) + j.fieldMap["id"] = j.ID + j.fieldMap["create_time"] = j.CreateTime + j.fieldMap["update_time"] = j.UpdateTime + j.fieldMap["content"] = j.Content + j.fieldMap["likes"] = j.Likes + j.fieldMap["source_content"] = j.SourceContent + j.fieldMap["type"] = j.Type +} + +func (j journal) clone(db *gorm.DB) journal { + j.journalDo.ReplaceDB(db) + return j +} + +type journalDo struct{ gen.DO } + +func (j journalDo) Debug() *journalDo { + return j.withDO(j.DO.Debug()) +} + +func (j journalDo) WithContext(ctx context.Context) *journalDo { + return j.withDO(j.DO.WithContext(ctx)) +} + +func (j journalDo) ReadDB() *journalDo { + return j.Clauses(dbresolver.Read) +} + +func (j journalDo) WriteDB() *journalDo { + return j.Clauses(dbresolver.Write) +} + +func (j journalDo) Clauses(conds ...clause.Expression) *journalDo { + return j.withDO(j.DO.Clauses(conds...)) +} + +func (j journalDo) Returning(value interface{}, columns ...string) *journalDo { + return j.withDO(j.DO.Returning(value, columns...)) +} + +func (j journalDo) Not(conds ...gen.Condition) *journalDo { + return j.withDO(j.DO.Not(conds...)) +} + +func (j journalDo) Or(conds ...gen.Condition) *journalDo { + return j.withDO(j.DO.Or(conds...)) +} + +func (j journalDo) Select(conds ...field.Expr) *journalDo { + return j.withDO(j.DO.Select(conds...)) +} + +func (j journalDo) Where(conds ...gen.Condition) *journalDo { + return j.withDO(j.DO.Where(conds...)) +} + +func (j journalDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *journalDo { + return j.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (j journalDo) Order(conds ...field.Expr) *journalDo { + return j.withDO(j.DO.Order(conds...)) +} + +func (j journalDo) Distinct(cols ...field.Expr) *journalDo { + return j.withDO(j.DO.Distinct(cols...)) +} + +func (j journalDo) Omit(cols ...field.Expr) *journalDo { + return j.withDO(j.DO.Omit(cols...)) +} + +func (j journalDo) Join(table schema.Tabler, on ...field.Expr) *journalDo { + return j.withDO(j.DO.Join(table, on...)) +} + +func (j journalDo) LeftJoin(table schema.Tabler, on ...field.Expr) *journalDo { + return j.withDO(j.DO.LeftJoin(table, on...)) +} + +func (j journalDo) RightJoin(table schema.Tabler, on ...field.Expr) *journalDo { + return j.withDO(j.DO.RightJoin(table, on...)) +} + +func (j journalDo) Group(cols ...field.Expr) *journalDo { + return j.withDO(j.DO.Group(cols...)) +} + +func (j journalDo) Having(conds ...gen.Condition) *journalDo { + return j.withDO(j.DO.Having(conds...)) +} + +func (j journalDo) Limit(limit int) *journalDo { + return j.withDO(j.DO.Limit(limit)) +} + +func (j journalDo) Offset(offset int) *journalDo { + return j.withDO(j.DO.Offset(offset)) +} + +func (j journalDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *journalDo { + return j.withDO(j.DO.Scopes(funcs...)) +} + +func (j journalDo) Unscoped() *journalDo { + return j.withDO(j.DO.Unscoped()) +} + +func (j journalDo) Create(values ...*entity.Journal) error { + if len(values) == 0 { + return nil + } + return j.DO.Create(values) +} + +func (j journalDo) CreateInBatches(values []*entity.Journal, batchSize int) error { + return j.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (j journalDo) Save(values ...*entity.Journal) error { + if len(values) == 0 { + return nil + } + return j.DO.Save(values) +} + +func (j journalDo) First() (*entity.Journal, error) { + if result, err := j.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Journal), nil + } +} + +func (j journalDo) Take() (*entity.Journal, error) { + if result, err := j.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Journal), nil + } +} + +func (j journalDo) Last() (*entity.Journal, error) { + if result, err := j.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Journal), nil + } +} + +func (j journalDo) Find() ([]*entity.Journal, error) { + result, err := j.DO.Find() + return result.([]*entity.Journal), err +} + +func (j journalDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Journal, err error) { + buf := make([]*entity.Journal, 0, batchSize) + err = j.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (j journalDo) FindInBatches(result *[]*entity.Journal, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return j.DO.FindInBatches(result, batchSize, fc) +} + +func (j journalDo) Attrs(attrs ...field.AssignExpr) *journalDo { + return j.withDO(j.DO.Attrs(attrs...)) +} + +func (j journalDo) Assign(attrs ...field.AssignExpr) *journalDo { + return j.withDO(j.DO.Assign(attrs...)) +} + +func (j journalDo) Joins(fields ...field.RelationField) *journalDo { + for _, _f := range fields { + j = *j.withDO(j.DO.Joins(_f)) + } + return &j +} + +func (j journalDo) Preload(fields ...field.RelationField) *journalDo { + for _, _f := range fields { + j = *j.withDO(j.DO.Preload(_f)) + } + return &j +} + +func (j journalDo) FirstOrInit() (*entity.Journal, error) { + if result, err := j.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Journal), nil + } +} + +func (j journalDo) FirstOrCreate() (*entity.Journal, error) { + if result, err := j.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Journal), nil + } +} + +func (j journalDo) FindByPage(offset int, limit int) (result []*entity.Journal, count int64, err error) { + result, err = j.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = j.Offset(-1).Limit(-1).Count() + return +} + +func (j journalDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = j.Count() + if err != nil { + return + } + + err = j.Offset(offset).Limit(limit).Scan(result) + return +} + +func (j journalDo) Scan(result interface{}) (err error) { + return j.DO.Scan(result) +} + +func (j journalDo) Delete(models ...*entity.Journal) (result gen.ResultInfo, err error) { + return j.DO.Delete(models) +} + +func (j *journalDo) withDO(do gen.Dao) *journalDo { + j.DO = *do.(*gen.DO) + return j +} diff --git a/dal/link.gen.go b/dal/link.gen.go new file mode 100644 index 00000000..5719d282 --- /dev/null +++ b/dal/link.gen.go @@ -0,0 +1,350 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newLink(db *gorm.DB) link { + _link := link{} + + _link.linkDo.UseDB(db) + _link.linkDo.UseModel(&entity.Link{}) + + tableName := _link.linkDo.TableName() + _link.ALL = field.NewAsterisk(tableName) + _link.ID = field.NewInt32(tableName, "id") + _link.CreateTime = field.NewTime(tableName, "create_time") + _link.UpdateTime = field.NewTime(tableName, "update_time") + _link.Description = field.NewString(tableName, "description") + _link.Logo = field.NewString(tableName, "logo") + _link.Name = field.NewString(tableName, "name") + _link.Priority = field.NewInt32(tableName, "priority") + _link.Team = field.NewString(tableName, "team") + _link.URL = field.NewString(tableName, "url") + + _link.fillFieldMap() + + return _link +} + +type link struct { + linkDo linkDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Description field.String + Logo field.String + Name field.String + Priority field.Int32 + Team field.String + URL field.String + + fieldMap map[string]field.Expr +} + +func (l link) Table(newTableName string) *link { + l.linkDo.UseTable(newTableName) + return l.updateTableName(newTableName) +} + +func (l link) As(alias string) *link { + l.linkDo.DO = *(l.linkDo.As(alias).(*gen.DO)) + return l.updateTableName(alias) +} + +func (l *link) updateTableName(table string) *link { + l.ALL = field.NewAsterisk(table) + l.ID = field.NewInt32(table, "id") + l.CreateTime = field.NewTime(table, "create_time") + l.UpdateTime = field.NewTime(table, "update_time") + l.Description = field.NewString(table, "description") + l.Logo = field.NewString(table, "logo") + l.Name = field.NewString(table, "name") + l.Priority = field.NewInt32(table, "priority") + l.Team = field.NewString(table, "team") + l.URL = field.NewString(table, "url") + + l.fillFieldMap() + + return l +} + +func (l *link) WithContext(ctx context.Context) *linkDo { return l.linkDo.WithContext(ctx) } + +func (l link) TableName() string { return l.linkDo.TableName() } + +func (l link) Alias() string { return l.linkDo.Alias() } + +func (l *link) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := l.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (l *link) fillFieldMap() { + l.fieldMap = make(map[string]field.Expr, 9) + l.fieldMap["id"] = l.ID + l.fieldMap["create_time"] = l.CreateTime + l.fieldMap["update_time"] = l.UpdateTime + l.fieldMap["description"] = l.Description + l.fieldMap["logo"] = l.Logo + l.fieldMap["name"] = l.Name + l.fieldMap["priority"] = l.Priority + l.fieldMap["team"] = l.Team + l.fieldMap["url"] = l.URL +} + +func (l link) clone(db *gorm.DB) link { + l.linkDo.ReplaceDB(db) + return l +} + +type linkDo struct{ gen.DO } + +func (l linkDo) Debug() *linkDo { + return l.withDO(l.DO.Debug()) +} + +func (l linkDo) WithContext(ctx context.Context) *linkDo { + return l.withDO(l.DO.WithContext(ctx)) +} + +func (l linkDo) ReadDB() *linkDo { + return l.Clauses(dbresolver.Read) +} + +func (l linkDo) WriteDB() *linkDo { + return l.Clauses(dbresolver.Write) +} + +func (l linkDo) Clauses(conds ...clause.Expression) *linkDo { + return l.withDO(l.DO.Clauses(conds...)) +} + +func (l linkDo) Returning(value interface{}, columns ...string) *linkDo { + return l.withDO(l.DO.Returning(value, columns...)) +} + +func (l linkDo) Not(conds ...gen.Condition) *linkDo { + return l.withDO(l.DO.Not(conds...)) +} + +func (l linkDo) Or(conds ...gen.Condition) *linkDo { + return l.withDO(l.DO.Or(conds...)) +} + +func (l linkDo) Select(conds ...field.Expr) *linkDo { + return l.withDO(l.DO.Select(conds...)) +} + +func (l linkDo) Where(conds ...gen.Condition) *linkDo { + return l.withDO(l.DO.Where(conds...)) +} + +func (l linkDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *linkDo { + return l.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (l linkDo) Order(conds ...field.Expr) *linkDo { + return l.withDO(l.DO.Order(conds...)) +} + +func (l linkDo) Distinct(cols ...field.Expr) *linkDo { + return l.withDO(l.DO.Distinct(cols...)) +} + +func (l linkDo) Omit(cols ...field.Expr) *linkDo { + return l.withDO(l.DO.Omit(cols...)) +} + +func (l linkDo) Join(table schema.Tabler, on ...field.Expr) *linkDo { + return l.withDO(l.DO.Join(table, on...)) +} + +func (l linkDo) LeftJoin(table schema.Tabler, on ...field.Expr) *linkDo { + return l.withDO(l.DO.LeftJoin(table, on...)) +} + +func (l linkDo) RightJoin(table schema.Tabler, on ...field.Expr) *linkDo { + return l.withDO(l.DO.RightJoin(table, on...)) +} + +func (l linkDo) Group(cols ...field.Expr) *linkDo { + return l.withDO(l.DO.Group(cols...)) +} + +func (l linkDo) Having(conds ...gen.Condition) *linkDo { + return l.withDO(l.DO.Having(conds...)) +} + +func (l linkDo) Limit(limit int) *linkDo { + return l.withDO(l.DO.Limit(limit)) +} + +func (l linkDo) Offset(offset int) *linkDo { + return l.withDO(l.DO.Offset(offset)) +} + +func (l linkDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *linkDo { + return l.withDO(l.DO.Scopes(funcs...)) +} + +func (l linkDo) Unscoped() *linkDo { + return l.withDO(l.DO.Unscoped()) +} + +func (l linkDo) Create(values ...*entity.Link) error { + if len(values) == 0 { + return nil + } + return l.DO.Create(values) +} + +func (l linkDo) CreateInBatches(values []*entity.Link, batchSize int) error { + return l.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (l linkDo) Save(values ...*entity.Link) error { + if len(values) == 0 { + return nil + } + return l.DO.Save(values) +} + +func (l linkDo) First() (*entity.Link, error) { + if result, err := l.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Link), nil + } +} + +func (l linkDo) Take() (*entity.Link, error) { + if result, err := l.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Link), nil + } +} + +func (l linkDo) Last() (*entity.Link, error) { + if result, err := l.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Link), nil + } +} + +func (l linkDo) Find() ([]*entity.Link, error) { + result, err := l.DO.Find() + return result.([]*entity.Link), err +} + +func (l linkDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Link, err error) { + buf := make([]*entity.Link, 0, batchSize) + err = l.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (l linkDo) FindInBatches(result *[]*entity.Link, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return l.DO.FindInBatches(result, batchSize, fc) +} + +func (l linkDo) Attrs(attrs ...field.AssignExpr) *linkDo { + return l.withDO(l.DO.Attrs(attrs...)) +} + +func (l linkDo) Assign(attrs ...field.AssignExpr) *linkDo { + return l.withDO(l.DO.Assign(attrs...)) +} + +func (l linkDo) Joins(fields ...field.RelationField) *linkDo { + for _, _f := range fields { + l = *l.withDO(l.DO.Joins(_f)) + } + return &l +} + +func (l linkDo) Preload(fields ...field.RelationField) *linkDo { + for _, _f := range fields { + l = *l.withDO(l.DO.Preload(_f)) + } + return &l +} + +func (l linkDo) FirstOrInit() (*entity.Link, error) { + if result, err := l.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Link), nil + } +} + +func (l linkDo) FirstOrCreate() (*entity.Link, error) { + if result, err := l.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Link), nil + } +} + +func (l linkDo) FindByPage(offset int, limit int) (result []*entity.Link, count int64, err error) { + result, err = l.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = l.Offset(-1).Limit(-1).Count() + return +} + +func (l linkDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = l.Count() + if err != nil { + return + } + + err = l.Offset(offset).Limit(limit).Scan(result) + return +} + +func (l linkDo) Scan(result interface{}) (err error) { + return l.DO.Scan(result) +} + +func (l linkDo) Delete(models ...*entity.Link) (result gen.ResultInfo, err error) { + return l.DO.Delete(models) +} + +func (l *linkDo) withDO(do gen.Dao) *linkDo { + l.DO = *do.(*gen.DO) + return l +} diff --git a/dal/log.gen.go b/dal/log.gen.go new file mode 100644 index 00000000..9538c4b1 --- /dev/null +++ b/dal/log.gen.go @@ -0,0 +1,342 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newLog(db *gorm.DB) log { + _log := log{} + + _log.logDo.UseDB(db) + _log.logDo.UseModel(&entity.Log{}) + + tableName := _log.logDo.TableName() + _log.ALL = field.NewAsterisk(tableName) + _log.ID = field.NewInt64(tableName, "id") + _log.CreateTime = field.NewTime(tableName, "create_time") + _log.UpdateTime = field.NewTime(tableName, "update_time") + _log.Content = field.NewString(tableName, "content") + _log.IPAddress = field.NewString(tableName, "ip_address") + _log.LogKey = field.NewString(tableName, "log_key") + _log.Type = field.NewField(tableName, "type") + + _log.fillFieldMap() + + return _log +} + +type log struct { + logDo logDo + + ALL field.Asterisk + ID field.Int64 + CreateTime field.Time + UpdateTime field.Time + Content field.String + IPAddress field.String + LogKey field.String + Type field.Field + + fieldMap map[string]field.Expr +} + +func (l log) Table(newTableName string) *log { + l.logDo.UseTable(newTableName) + return l.updateTableName(newTableName) +} + +func (l log) As(alias string) *log { + l.logDo.DO = *(l.logDo.As(alias).(*gen.DO)) + return l.updateTableName(alias) +} + +func (l *log) updateTableName(table string) *log { + l.ALL = field.NewAsterisk(table) + l.ID = field.NewInt64(table, "id") + l.CreateTime = field.NewTime(table, "create_time") + l.UpdateTime = field.NewTime(table, "update_time") + l.Content = field.NewString(table, "content") + l.IPAddress = field.NewString(table, "ip_address") + l.LogKey = field.NewString(table, "log_key") + l.Type = field.NewField(table, "type") + + l.fillFieldMap() + + return l +} + +func (l *log) WithContext(ctx context.Context) *logDo { return l.logDo.WithContext(ctx) } + +func (l log) TableName() string { return l.logDo.TableName() } + +func (l log) Alias() string { return l.logDo.Alias() } + +func (l *log) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := l.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (l *log) fillFieldMap() { + l.fieldMap = make(map[string]field.Expr, 7) + l.fieldMap["id"] = l.ID + l.fieldMap["create_time"] = l.CreateTime + l.fieldMap["update_time"] = l.UpdateTime + l.fieldMap["content"] = l.Content + l.fieldMap["ip_address"] = l.IPAddress + l.fieldMap["log_key"] = l.LogKey + l.fieldMap["type"] = l.Type +} + +func (l log) clone(db *gorm.DB) log { + l.logDo.ReplaceDB(db) + return l +} + +type logDo struct{ gen.DO } + +func (l logDo) Debug() *logDo { + return l.withDO(l.DO.Debug()) +} + +func (l logDo) WithContext(ctx context.Context) *logDo { + return l.withDO(l.DO.WithContext(ctx)) +} + +func (l logDo) ReadDB() *logDo { + return l.Clauses(dbresolver.Read) +} + +func (l logDo) WriteDB() *logDo { + return l.Clauses(dbresolver.Write) +} + +func (l logDo) Clauses(conds ...clause.Expression) *logDo { + return l.withDO(l.DO.Clauses(conds...)) +} + +func (l logDo) Returning(value interface{}, columns ...string) *logDo { + return l.withDO(l.DO.Returning(value, columns...)) +} + +func (l logDo) Not(conds ...gen.Condition) *logDo { + return l.withDO(l.DO.Not(conds...)) +} + +func (l logDo) Or(conds ...gen.Condition) *logDo { + return l.withDO(l.DO.Or(conds...)) +} + +func (l logDo) Select(conds ...field.Expr) *logDo { + return l.withDO(l.DO.Select(conds...)) +} + +func (l logDo) Where(conds ...gen.Condition) *logDo { + return l.withDO(l.DO.Where(conds...)) +} + +func (l logDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *logDo { + return l.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (l logDo) Order(conds ...field.Expr) *logDo { + return l.withDO(l.DO.Order(conds...)) +} + +func (l logDo) Distinct(cols ...field.Expr) *logDo { + return l.withDO(l.DO.Distinct(cols...)) +} + +func (l logDo) Omit(cols ...field.Expr) *logDo { + return l.withDO(l.DO.Omit(cols...)) +} + +func (l logDo) Join(table schema.Tabler, on ...field.Expr) *logDo { + return l.withDO(l.DO.Join(table, on...)) +} + +func (l logDo) LeftJoin(table schema.Tabler, on ...field.Expr) *logDo { + return l.withDO(l.DO.LeftJoin(table, on...)) +} + +func (l logDo) RightJoin(table schema.Tabler, on ...field.Expr) *logDo { + return l.withDO(l.DO.RightJoin(table, on...)) +} + +func (l logDo) Group(cols ...field.Expr) *logDo { + return l.withDO(l.DO.Group(cols...)) +} + +func (l logDo) Having(conds ...gen.Condition) *logDo { + return l.withDO(l.DO.Having(conds...)) +} + +func (l logDo) Limit(limit int) *logDo { + return l.withDO(l.DO.Limit(limit)) +} + +func (l logDo) Offset(offset int) *logDo { + return l.withDO(l.DO.Offset(offset)) +} + +func (l logDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *logDo { + return l.withDO(l.DO.Scopes(funcs...)) +} + +func (l logDo) Unscoped() *logDo { + return l.withDO(l.DO.Unscoped()) +} + +func (l logDo) Create(values ...*entity.Log) error { + if len(values) == 0 { + return nil + } + return l.DO.Create(values) +} + +func (l logDo) CreateInBatches(values []*entity.Log, batchSize int) error { + return l.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (l logDo) Save(values ...*entity.Log) error { + if len(values) == 0 { + return nil + } + return l.DO.Save(values) +} + +func (l logDo) First() (*entity.Log, error) { + if result, err := l.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Log), nil + } +} + +func (l logDo) Take() (*entity.Log, error) { + if result, err := l.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Log), nil + } +} + +func (l logDo) Last() (*entity.Log, error) { + if result, err := l.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Log), nil + } +} + +func (l logDo) Find() ([]*entity.Log, error) { + result, err := l.DO.Find() + return result.([]*entity.Log), err +} + +func (l logDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Log, err error) { + buf := make([]*entity.Log, 0, batchSize) + err = l.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (l logDo) FindInBatches(result *[]*entity.Log, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return l.DO.FindInBatches(result, batchSize, fc) +} + +func (l logDo) Attrs(attrs ...field.AssignExpr) *logDo { + return l.withDO(l.DO.Attrs(attrs...)) +} + +func (l logDo) Assign(attrs ...field.AssignExpr) *logDo { + return l.withDO(l.DO.Assign(attrs...)) +} + +func (l logDo) Joins(fields ...field.RelationField) *logDo { + for _, _f := range fields { + l = *l.withDO(l.DO.Joins(_f)) + } + return &l +} + +func (l logDo) Preload(fields ...field.RelationField) *logDo { + for _, _f := range fields { + l = *l.withDO(l.DO.Preload(_f)) + } + return &l +} + +func (l logDo) FirstOrInit() (*entity.Log, error) { + if result, err := l.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Log), nil + } +} + +func (l logDo) FirstOrCreate() (*entity.Log, error) { + if result, err := l.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Log), nil + } +} + +func (l logDo) FindByPage(offset int, limit int) (result []*entity.Log, count int64, err error) { + result, err = l.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = l.Offset(-1).Limit(-1).Count() + return +} + +func (l logDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = l.Count() + if err != nil { + return + } + + err = l.Offset(offset).Limit(limit).Scan(result) + return +} + +func (l logDo) Scan(result interface{}) (err error) { + return l.DO.Scan(result) +} + +func (l logDo) Delete(models ...*entity.Log) (result gen.ResultInfo, err error) { + return l.DO.Delete(models) +} + +func (l *logDo) withDO(do gen.Dao) *logDo { + l.DO = *do.(*gen.DO) + return l +} diff --git a/dal/menu.gen.go b/dal/menu.gen.go new file mode 100644 index 00000000..66c12fc4 --- /dev/null +++ b/dal/menu.gen.go @@ -0,0 +1,354 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newMenu(db *gorm.DB) menu { + _menu := menu{} + + _menu.menuDo.UseDB(db) + _menu.menuDo.UseModel(&entity.Menu{}) + + tableName := _menu.menuDo.TableName() + _menu.ALL = field.NewAsterisk(tableName) + _menu.ID = field.NewInt32(tableName, "id") + _menu.CreateTime = field.NewTime(tableName, "create_time") + _menu.UpdateTime = field.NewTime(tableName, "update_time") + _menu.Icon = field.NewString(tableName, "icon") + _menu.Name = field.NewString(tableName, "name") + _menu.ParentID = field.NewInt32(tableName, "parent_id") + _menu.Priority = field.NewInt32(tableName, "priority") + _menu.Target = field.NewString(tableName, "target") + _menu.Team = field.NewString(tableName, "team") + _menu.URL = field.NewString(tableName, "url") + + _menu.fillFieldMap() + + return _menu +} + +type menu struct { + menuDo menuDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Icon field.String + Name field.String + ParentID field.Int32 + Priority field.Int32 + Target field.String + Team field.String + URL field.String + + fieldMap map[string]field.Expr +} + +func (m menu) Table(newTableName string) *menu { + m.menuDo.UseTable(newTableName) + return m.updateTableName(newTableName) +} + +func (m menu) As(alias string) *menu { + m.menuDo.DO = *(m.menuDo.As(alias).(*gen.DO)) + return m.updateTableName(alias) +} + +func (m *menu) updateTableName(table string) *menu { + m.ALL = field.NewAsterisk(table) + m.ID = field.NewInt32(table, "id") + m.CreateTime = field.NewTime(table, "create_time") + m.UpdateTime = field.NewTime(table, "update_time") + m.Icon = field.NewString(table, "icon") + m.Name = field.NewString(table, "name") + m.ParentID = field.NewInt32(table, "parent_id") + m.Priority = field.NewInt32(table, "priority") + m.Target = field.NewString(table, "target") + m.Team = field.NewString(table, "team") + m.URL = field.NewString(table, "url") + + m.fillFieldMap() + + return m +} + +func (m *menu) WithContext(ctx context.Context) *menuDo { return m.menuDo.WithContext(ctx) } + +func (m menu) TableName() string { return m.menuDo.TableName() } + +func (m menu) Alias() string { return m.menuDo.Alias() } + +func (m *menu) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := m.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (m *menu) fillFieldMap() { + m.fieldMap = make(map[string]field.Expr, 10) + m.fieldMap["id"] = m.ID + m.fieldMap["create_time"] = m.CreateTime + m.fieldMap["update_time"] = m.UpdateTime + m.fieldMap["icon"] = m.Icon + m.fieldMap["name"] = m.Name + m.fieldMap["parent_id"] = m.ParentID + m.fieldMap["priority"] = m.Priority + m.fieldMap["target"] = m.Target + m.fieldMap["team"] = m.Team + m.fieldMap["url"] = m.URL +} + +func (m menu) clone(db *gorm.DB) menu { + m.menuDo.ReplaceDB(db) + return m +} + +type menuDo struct{ gen.DO } + +func (m menuDo) Debug() *menuDo { + return m.withDO(m.DO.Debug()) +} + +func (m menuDo) WithContext(ctx context.Context) *menuDo { + return m.withDO(m.DO.WithContext(ctx)) +} + +func (m menuDo) ReadDB() *menuDo { + return m.Clauses(dbresolver.Read) +} + +func (m menuDo) WriteDB() *menuDo { + return m.Clauses(dbresolver.Write) +} + +func (m menuDo) Clauses(conds ...clause.Expression) *menuDo { + return m.withDO(m.DO.Clauses(conds...)) +} + +func (m menuDo) Returning(value interface{}, columns ...string) *menuDo { + return m.withDO(m.DO.Returning(value, columns...)) +} + +func (m menuDo) Not(conds ...gen.Condition) *menuDo { + return m.withDO(m.DO.Not(conds...)) +} + +func (m menuDo) Or(conds ...gen.Condition) *menuDo { + return m.withDO(m.DO.Or(conds...)) +} + +func (m menuDo) Select(conds ...field.Expr) *menuDo { + return m.withDO(m.DO.Select(conds...)) +} + +func (m menuDo) Where(conds ...gen.Condition) *menuDo { + return m.withDO(m.DO.Where(conds...)) +} + +func (m menuDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *menuDo { + return m.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (m menuDo) Order(conds ...field.Expr) *menuDo { + return m.withDO(m.DO.Order(conds...)) +} + +func (m menuDo) Distinct(cols ...field.Expr) *menuDo { + return m.withDO(m.DO.Distinct(cols...)) +} + +func (m menuDo) Omit(cols ...field.Expr) *menuDo { + return m.withDO(m.DO.Omit(cols...)) +} + +func (m menuDo) Join(table schema.Tabler, on ...field.Expr) *menuDo { + return m.withDO(m.DO.Join(table, on...)) +} + +func (m menuDo) LeftJoin(table schema.Tabler, on ...field.Expr) *menuDo { + return m.withDO(m.DO.LeftJoin(table, on...)) +} + +func (m menuDo) RightJoin(table schema.Tabler, on ...field.Expr) *menuDo { + return m.withDO(m.DO.RightJoin(table, on...)) +} + +func (m menuDo) Group(cols ...field.Expr) *menuDo { + return m.withDO(m.DO.Group(cols...)) +} + +func (m menuDo) Having(conds ...gen.Condition) *menuDo { + return m.withDO(m.DO.Having(conds...)) +} + +func (m menuDo) Limit(limit int) *menuDo { + return m.withDO(m.DO.Limit(limit)) +} + +func (m menuDo) Offset(offset int) *menuDo { + return m.withDO(m.DO.Offset(offset)) +} + +func (m menuDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *menuDo { + return m.withDO(m.DO.Scopes(funcs...)) +} + +func (m menuDo) Unscoped() *menuDo { + return m.withDO(m.DO.Unscoped()) +} + +func (m menuDo) Create(values ...*entity.Menu) error { + if len(values) == 0 { + return nil + } + return m.DO.Create(values) +} + +func (m menuDo) CreateInBatches(values []*entity.Menu, batchSize int) error { + return m.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (m menuDo) Save(values ...*entity.Menu) error { + if len(values) == 0 { + return nil + } + return m.DO.Save(values) +} + +func (m menuDo) First() (*entity.Menu, error) { + if result, err := m.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Menu), nil + } +} + +func (m menuDo) Take() (*entity.Menu, error) { + if result, err := m.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Menu), nil + } +} + +func (m menuDo) Last() (*entity.Menu, error) { + if result, err := m.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Menu), nil + } +} + +func (m menuDo) Find() ([]*entity.Menu, error) { + result, err := m.DO.Find() + return result.([]*entity.Menu), err +} + +func (m menuDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Menu, err error) { + buf := make([]*entity.Menu, 0, batchSize) + err = m.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (m menuDo) FindInBatches(result *[]*entity.Menu, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return m.DO.FindInBatches(result, batchSize, fc) +} + +func (m menuDo) Attrs(attrs ...field.AssignExpr) *menuDo { + return m.withDO(m.DO.Attrs(attrs...)) +} + +func (m menuDo) Assign(attrs ...field.AssignExpr) *menuDo { + return m.withDO(m.DO.Assign(attrs...)) +} + +func (m menuDo) Joins(fields ...field.RelationField) *menuDo { + for _, _f := range fields { + m = *m.withDO(m.DO.Joins(_f)) + } + return &m +} + +func (m menuDo) Preload(fields ...field.RelationField) *menuDo { + for _, _f := range fields { + m = *m.withDO(m.DO.Preload(_f)) + } + return &m +} + +func (m menuDo) FirstOrInit() (*entity.Menu, error) { + if result, err := m.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Menu), nil + } +} + +func (m menuDo) FirstOrCreate() (*entity.Menu, error) { + if result, err := m.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Menu), nil + } +} + +func (m menuDo) FindByPage(offset int, limit int) (result []*entity.Menu, count int64, err error) { + result, err = m.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = m.Offset(-1).Limit(-1).Count() + return +} + +func (m menuDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = m.Count() + if err != nil { + return + } + + err = m.Offset(offset).Limit(limit).Scan(result) + return +} + +func (m menuDo) Scan(result interface{}) (err error) { + return m.DO.Scan(result) +} + +func (m menuDo) Delete(models ...*entity.Menu) (result gen.ResultInfo, err error) { + return m.DO.Delete(models) +} + +func (m *menuDo) withDO(do gen.Dao) *menuDo { + m.DO = *do.(*gen.DO) + return m +} diff --git a/dal/meta.gen.go b/dal/meta.gen.go new file mode 100644 index 00000000..c90cc396 --- /dev/null +++ b/dal/meta.gen.go @@ -0,0 +1,342 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newMeta(db *gorm.DB) meta { + _meta := meta{} + + _meta.metaDo.UseDB(db) + _meta.metaDo.UseModel(&entity.Meta{}) + + tableName := _meta.metaDo.TableName() + _meta.ALL = field.NewAsterisk(tableName) + _meta.ID = field.NewInt64(tableName, "id") + _meta.Type = field.NewField(tableName, "type") + _meta.CreateTime = field.NewTime(tableName, "create_time") + _meta.UpdateTime = field.NewTime(tableName, "update_time") + _meta.MetaKey = field.NewString(tableName, "meta_key") + _meta.PostID = field.NewInt32(tableName, "post_id") + _meta.MetaValue = field.NewString(tableName, "meta_value") + + _meta.fillFieldMap() + + return _meta +} + +type meta struct { + metaDo metaDo + + ALL field.Asterisk + ID field.Int64 + Type field.Field + CreateTime field.Time + UpdateTime field.Time + MetaKey field.String + PostID field.Int32 + MetaValue field.String + + fieldMap map[string]field.Expr +} + +func (m meta) Table(newTableName string) *meta { + m.metaDo.UseTable(newTableName) + return m.updateTableName(newTableName) +} + +func (m meta) As(alias string) *meta { + m.metaDo.DO = *(m.metaDo.As(alias).(*gen.DO)) + return m.updateTableName(alias) +} + +func (m *meta) updateTableName(table string) *meta { + m.ALL = field.NewAsterisk(table) + m.ID = field.NewInt64(table, "id") + m.Type = field.NewField(table, "type") + m.CreateTime = field.NewTime(table, "create_time") + m.UpdateTime = field.NewTime(table, "update_time") + m.MetaKey = field.NewString(table, "meta_key") + m.PostID = field.NewInt32(table, "post_id") + m.MetaValue = field.NewString(table, "meta_value") + + m.fillFieldMap() + + return m +} + +func (m *meta) WithContext(ctx context.Context) *metaDo { return m.metaDo.WithContext(ctx) } + +func (m meta) TableName() string { return m.metaDo.TableName() } + +func (m meta) Alias() string { return m.metaDo.Alias() } + +func (m *meta) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := m.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (m *meta) fillFieldMap() { + m.fieldMap = make(map[string]field.Expr, 7) + m.fieldMap["id"] = m.ID + m.fieldMap["type"] = m.Type + m.fieldMap["create_time"] = m.CreateTime + m.fieldMap["update_time"] = m.UpdateTime + m.fieldMap["meta_key"] = m.MetaKey + m.fieldMap["post_id"] = m.PostID + m.fieldMap["meta_value"] = m.MetaValue +} + +func (m meta) clone(db *gorm.DB) meta { + m.metaDo.ReplaceDB(db) + return m +} + +type metaDo struct{ gen.DO } + +func (m metaDo) Debug() *metaDo { + return m.withDO(m.DO.Debug()) +} + +func (m metaDo) WithContext(ctx context.Context) *metaDo { + return m.withDO(m.DO.WithContext(ctx)) +} + +func (m metaDo) ReadDB() *metaDo { + return m.Clauses(dbresolver.Read) +} + +func (m metaDo) WriteDB() *metaDo { + return m.Clauses(dbresolver.Write) +} + +func (m metaDo) Clauses(conds ...clause.Expression) *metaDo { + return m.withDO(m.DO.Clauses(conds...)) +} + +func (m metaDo) Returning(value interface{}, columns ...string) *metaDo { + return m.withDO(m.DO.Returning(value, columns...)) +} + +func (m metaDo) Not(conds ...gen.Condition) *metaDo { + return m.withDO(m.DO.Not(conds...)) +} + +func (m metaDo) Or(conds ...gen.Condition) *metaDo { + return m.withDO(m.DO.Or(conds...)) +} + +func (m metaDo) Select(conds ...field.Expr) *metaDo { + return m.withDO(m.DO.Select(conds...)) +} + +func (m metaDo) Where(conds ...gen.Condition) *metaDo { + return m.withDO(m.DO.Where(conds...)) +} + +func (m metaDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *metaDo { + return m.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (m metaDo) Order(conds ...field.Expr) *metaDo { + return m.withDO(m.DO.Order(conds...)) +} + +func (m metaDo) Distinct(cols ...field.Expr) *metaDo { + return m.withDO(m.DO.Distinct(cols...)) +} + +func (m metaDo) Omit(cols ...field.Expr) *metaDo { + return m.withDO(m.DO.Omit(cols...)) +} + +func (m metaDo) Join(table schema.Tabler, on ...field.Expr) *metaDo { + return m.withDO(m.DO.Join(table, on...)) +} + +func (m metaDo) LeftJoin(table schema.Tabler, on ...field.Expr) *metaDo { + return m.withDO(m.DO.LeftJoin(table, on...)) +} + +func (m metaDo) RightJoin(table schema.Tabler, on ...field.Expr) *metaDo { + return m.withDO(m.DO.RightJoin(table, on...)) +} + +func (m metaDo) Group(cols ...field.Expr) *metaDo { + return m.withDO(m.DO.Group(cols...)) +} + +func (m metaDo) Having(conds ...gen.Condition) *metaDo { + return m.withDO(m.DO.Having(conds...)) +} + +func (m metaDo) Limit(limit int) *metaDo { + return m.withDO(m.DO.Limit(limit)) +} + +func (m metaDo) Offset(offset int) *metaDo { + return m.withDO(m.DO.Offset(offset)) +} + +func (m metaDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *metaDo { + return m.withDO(m.DO.Scopes(funcs...)) +} + +func (m metaDo) Unscoped() *metaDo { + return m.withDO(m.DO.Unscoped()) +} + +func (m metaDo) Create(values ...*entity.Meta) error { + if len(values) == 0 { + return nil + } + return m.DO.Create(values) +} + +func (m metaDo) CreateInBatches(values []*entity.Meta, batchSize int) error { + return m.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (m metaDo) Save(values ...*entity.Meta) error { + if len(values) == 0 { + return nil + } + return m.DO.Save(values) +} + +func (m metaDo) First() (*entity.Meta, error) { + if result, err := m.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Meta), nil + } +} + +func (m metaDo) Take() (*entity.Meta, error) { + if result, err := m.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Meta), nil + } +} + +func (m metaDo) Last() (*entity.Meta, error) { + if result, err := m.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Meta), nil + } +} + +func (m metaDo) Find() ([]*entity.Meta, error) { + result, err := m.DO.Find() + return result.([]*entity.Meta), err +} + +func (m metaDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Meta, err error) { + buf := make([]*entity.Meta, 0, batchSize) + err = m.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (m metaDo) FindInBatches(result *[]*entity.Meta, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return m.DO.FindInBatches(result, batchSize, fc) +} + +func (m metaDo) Attrs(attrs ...field.AssignExpr) *metaDo { + return m.withDO(m.DO.Attrs(attrs...)) +} + +func (m metaDo) Assign(attrs ...field.AssignExpr) *metaDo { + return m.withDO(m.DO.Assign(attrs...)) +} + +func (m metaDo) Joins(fields ...field.RelationField) *metaDo { + for _, _f := range fields { + m = *m.withDO(m.DO.Joins(_f)) + } + return &m +} + +func (m metaDo) Preload(fields ...field.RelationField) *metaDo { + for _, _f := range fields { + m = *m.withDO(m.DO.Preload(_f)) + } + return &m +} + +func (m metaDo) FirstOrInit() (*entity.Meta, error) { + if result, err := m.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Meta), nil + } +} + +func (m metaDo) FirstOrCreate() (*entity.Meta, error) { + if result, err := m.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Meta), nil + } +} + +func (m metaDo) FindByPage(offset int, limit int) (result []*entity.Meta, count int64, err error) { + result, err = m.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = m.Offset(-1).Limit(-1).Count() + return +} + +func (m metaDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = m.Count() + if err != nil { + return + } + + err = m.Offset(offset).Limit(limit).Scan(result) + return +} + +func (m metaDo) Scan(result interface{}) (err error) { + return m.DO.Scan(result) +} + +func (m metaDo) Delete(models ...*entity.Meta) (result gen.ResultInfo, err error) { + return m.DO.Delete(models) +} + +func (m *metaDo) withDO(do gen.Dao) *metaDo { + m.DO = *do.(*gen.DO) + return m +} diff --git a/dal/option.gen.go b/dal/option.gen.go new file mode 100644 index 00000000..0a9e5748 --- /dev/null +++ b/dal/option.gen.go @@ -0,0 +1,338 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newOption(db *gorm.DB) option { + _option := option{} + + _option.optionDo.UseDB(db) + _option.optionDo.UseModel(&entity.Option{}) + + tableName := _option.optionDo.TableName() + _option.ALL = field.NewAsterisk(tableName) + _option.ID = field.NewInt32(tableName, "id") + _option.CreateTime = field.NewTime(tableName, "create_time") + _option.UpdateTime = field.NewTime(tableName, "update_time") + _option.OptionKey = field.NewString(tableName, "option_key") + _option.Type = field.NewField(tableName, "type") + _option.OptionValue = field.NewString(tableName, "option_value") + + _option.fillFieldMap() + + return _option +} + +type option struct { + optionDo optionDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + OptionKey field.String + Type field.Field + OptionValue field.String + + fieldMap map[string]field.Expr +} + +func (o option) Table(newTableName string) *option { + o.optionDo.UseTable(newTableName) + return o.updateTableName(newTableName) +} + +func (o option) As(alias string) *option { + o.optionDo.DO = *(o.optionDo.As(alias).(*gen.DO)) + return o.updateTableName(alias) +} + +func (o *option) updateTableName(table string) *option { + o.ALL = field.NewAsterisk(table) + o.ID = field.NewInt32(table, "id") + o.CreateTime = field.NewTime(table, "create_time") + o.UpdateTime = field.NewTime(table, "update_time") + o.OptionKey = field.NewString(table, "option_key") + o.Type = field.NewField(table, "type") + o.OptionValue = field.NewString(table, "option_value") + + o.fillFieldMap() + + return o +} + +func (o *option) WithContext(ctx context.Context) *optionDo { return o.optionDo.WithContext(ctx) } + +func (o option) TableName() string { return o.optionDo.TableName() } + +func (o option) Alias() string { return o.optionDo.Alias() } + +func (o *option) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := o.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (o *option) fillFieldMap() { + o.fieldMap = make(map[string]field.Expr, 6) + o.fieldMap["id"] = o.ID + o.fieldMap["create_time"] = o.CreateTime + o.fieldMap["update_time"] = o.UpdateTime + o.fieldMap["option_key"] = o.OptionKey + o.fieldMap["type"] = o.Type + o.fieldMap["option_value"] = o.OptionValue +} + +func (o option) clone(db *gorm.DB) option { + o.optionDo.ReplaceDB(db) + return o +} + +type optionDo struct{ gen.DO } + +func (o optionDo) Debug() *optionDo { + return o.withDO(o.DO.Debug()) +} + +func (o optionDo) WithContext(ctx context.Context) *optionDo { + return o.withDO(o.DO.WithContext(ctx)) +} + +func (o optionDo) ReadDB() *optionDo { + return o.Clauses(dbresolver.Read) +} + +func (o optionDo) WriteDB() *optionDo { + return o.Clauses(dbresolver.Write) +} + +func (o optionDo) Clauses(conds ...clause.Expression) *optionDo { + return o.withDO(o.DO.Clauses(conds...)) +} + +func (o optionDo) Returning(value interface{}, columns ...string) *optionDo { + return o.withDO(o.DO.Returning(value, columns...)) +} + +func (o optionDo) Not(conds ...gen.Condition) *optionDo { + return o.withDO(o.DO.Not(conds...)) +} + +func (o optionDo) Or(conds ...gen.Condition) *optionDo { + return o.withDO(o.DO.Or(conds...)) +} + +func (o optionDo) Select(conds ...field.Expr) *optionDo { + return o.withDO(o.DO.Select(conds...)) +} + +func (o optionDo) Where(conds ...gen.Condition) *optionDo { + return o.withDO(o.DO.Where(conds...)) +} + +func (o optionDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *optionDo { + return o.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (o optionDo) Order(conds ...field.Expr) *optionDo { + return o.withDO(o.DO.Order(conds...)) +} + +func (o optionDo) Distinct(cols ...field.Expr) *optionDo { + return o.withDO(o.DO.Distinct(cols...)) +} + +func (o optionDo) Omit(cols ...field.Expr) *optionDo { + return o.withDO(o.DO.Omit(cols...)) +} + +func (o optionDo) Join(table schema.Tabler, on ...field.Expr) *optionDo { + return o.withDO(o.DO.Join(table, on...)) +} + +func (o optionDo) LeftJoin(table schema.Tabler, on ...field.Expr) *optionDo { + return o.withDO(o.DO.LeftJoin(table, on...)) +} + +func (o optionDo) RightJoin(table schema.Tabler, on ...field.Expr) *optionDo { + return o.withDO(o.DO.RightJoin(table, on...)) +} + +func (o optionDo) Group(cols ...field.Expr) *optionDo { + return o.withDO(o.DO.Group(cols...)) +} + +func (o optionDo) Having(conds ...gen.Condition) *optionDo { + return o.withDO(o.DO.Having(conds...)) +} + +func (o optionDo) Limit(limit int) *optionDo { + return o.withDO(o.DO.Limit(limit)) +} + +func (o optionDo) Offset(offset int) *optionDo { + return o.withDO(o.DO.Offset(offset)) +} + +func (o optionDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *optionDo { + return o.withDO(o.DO.Scopes(funcs...)) +} + +func (o optionDo) Unscoped() *optionDo { + return o.withDO(o.DO.Unscoped()) +} + +func (o optionDo) Create(values ...*entity.Option) error { + if len(values) == 0 { + return nil + } + return o.DO.Create(values) +} + +func (o optionDo) CreateInBatches(values []*entity.Option, batchSize int) error { + return o.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (o optionDo) Save(values ...*entity.Option) error { + if len(values) == 0 { + return nil + } + return o.DO.Save(values) +} + +func (o optionDo) First() (*entity.Option, error) { + if result, err := o.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Option), nil + } +} + +func (o optionDo) Take() (*entity.Option, error) { + if result, err := o.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Option), nil + } +} + +func (o optionDo) Last() (*entity.Option, error) { + if result, err := o.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Option), nil + } +} + +func (o optionDo) Find() ([]*entity.Option, error) { + result, err := o.DO.Find() + return result.([]*entity.Option), err +} + +func (o optionDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Option, err error) { + buf := make([]*entity.Option, 0, batchSize) + err = o.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (o optionDo) FindInBatches(result *[]*entity.Option, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return o.DO.FindInBatches(result, batchSize, fc) +} + +func (o optionDo) Attrs(attrs ...field.AssignExpr) *optionDo { + return o.withDO(o.DO.Attrs(attrs...)) +} + +func (o optionDo) Assign(attrs ...field.AssignExpr) *optionDo { + return o.withDO(o.DO.Assign(attrs...)) +} + +func (o optionDo) Joins(fields ...field.RelationField) *optionDo { + for _, _f := range fields { + o = *o.withDO(o.DO.Joins(_f)) + } + return &o +} + +func (o optionDo) Preload(fields ...field.RelationField) *optionDo { + for _, _f := range fields { + o = *o.withDO(o.DO.Preload(_f)) + } + return &o +} + +func (o optionDo) FirstOrInit() (*entity.Option, error) { + if result, err := o.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Option), nil + } +} + +func (o optionDo) FirstOrCreate() (*entity.Option, error) { + if result, err := o.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Option), nil + } +} + +func (o optionDo) FindByPage(offset int, limit int) (result []*entity.Option, count int64, err error) { + result, err = o.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = o.Offset(-1).Limit(-1).Count() + return +} + +func (o optionDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = o.Count() + if err != nil { + return + } + + err = o.Offset(offset).Limit(limit).Scan(result) + return +} + +func (o optionDo) Scan(result interface{}) (err error) { + return o.DO.Scan(result) +} + +func (o optionDo) Delete(models ...*entity.Option) (result gen.ResultInfo, err error) { + return o.DO.Delete(models) +} + +func (o *optionDo) withDO(do gen.Dao) *optionDo { + o.DO = *do.(*gen.DO) + return o +} diff --git a/dal/photo.gen.go b/dal/photo.gen.go new file mode 100644 index 00000000..0ec3e191 --- /dev/null +++ b/dal/photo.gen.go @@ -0,0 +1,358 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newPhoto(db *gorm.DB) photo { + _photo := photo{} + + _photo.photoDo.UseDB(db) + _photo.photoDo.UseModel(&entity.Photo{}) + + tableName := _photo.photoDo.TableName() + _photo.ALL = field.NewAsterisk(tableName) + _photo.ID = field.NewInt32(tableName, "id") + _photo.CreateTime = field.NewTime(tableName, "create_time") + _photo.UpdateTime = field.NewTime(tableName, "update_time") + _photo.Description = field.NewString(tableName, "description") + _photo.Location = field.NewString(tableName, "location") + _photo.Name = field.NewString(tableName, "name") + _photo.TakeTime = field.NewTime(tableName, "take_time") + _photo.Team = field.NewString(tableName, "team") + _photo.Thumbnail = field.NewString(tableName, "thumbnail") + _photo.URL = field.NewString(tableName, "url") + _photo.Likes = field.NewInt64(tableName, "likes") + + _photo.fillFieldMap() + + return _photo +} + +type photo struct { + photoDo photoDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Description field.String + Location field.String + Name field.String + TakeTime field.Time + Team field.String + Thumbnail field.String + URL field.String + Likes field.Int64 + + fieldMap map[string]field.Expr +} + +func (p photo) Table(newTableName string) *photo { + p.photoDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p photo) As(alias string) *photo { + p.photoDo.DO = *(p.photoDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *photo) updateTableName(table string) *photo { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewInt32(table, "id") + p.CreateTime = field.NewTime(table, "create_time") + p.UpdateTime = field.NewTime(table, "update_time") + p.Description = field.NewString(table, "description") + p.Location = field.NewString(table, "location") + p.Name = field.NewString(table, "name") + p.TakeTime = field.NewTime(table, "take_time") + p.Team = field.NewString(table, "team") + p.Thumbnail = field.NewString(table, "thumbnail") + p.URL = field.NewString(table, "url") + p.Likes = field.NewInt64(table, "likes") + + p.fillFieldMap() + + return p +} + +func (p *photo) WithContext(ctx context.Context) *photoDo { return p.photoDo.WithContext(ctx) } + +func (p photo) TableName() string { return p.photoDo.TableName() } + +func (p photo) Alias() string { return p.photoDo.Alias() } + +func (p *photo) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *photo) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 11) + p.fieldMap["id"] = p.ID + p.fieldMap["create_time"] = p.CreateTime + p.fieldMap["update_time"] = p.UpdateTime + p.fieldMap["description"] = p.Description + p.fieldMap["location"] = p.Location + p.fieldMap["name"] = p.Name + p.fieldMap["take_time"] = p.TakeTime + p.fieldMap["team"] = p.Team + p.fieldMap["thumbnail"] = p.Thumbnail + p.fieldMap["url"] = p.URL + p.fieldMap["likes"] = p.Likes +} + +func (p photo) clone(db *gorm.DB) photo { + p.photoDo.ReplaceDB(db) + return p +} + +type photoDo struct{ gen.DO } + +func (p photoDo) Debug() *photoDo { + return p.withDO(p.DO.Debug()) +} + +func (p photoDo) WithContext(ctx context.Context) *photoDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p photoDo) ReadDB() *photoDo { + return p.Clauses(dbresolver.Read) +} + +func (p photoDo) WriteDB() *photoDo { + return p.Clauses(dbresolver.Write) +} + +func (p photoDo) Clauses(conds ...clause.Expression) *photoDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p photoDo) Returning(value interface{}, columns ...string) *photoDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p photoDo) Not(conds ...gen.Condition) *photoDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p photoDo) Or(conds ...gen.Condition) *photoDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p photoDo) Select(conds ...field.Expr) *photoDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p photoDo) Where(conds ...gen.Condition) *photoDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p photoDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *photoDo { + return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (p photoDo) Order(conds ...field.Expr) *photoDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p photoDo) Distinct(cols ...field.Expr) *photoDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p photoDo) Omit(cols ...field.Expr) *photoDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p photoDo) Join(table schema.Tabler, on ...field.Expr) *photoDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p photoDo) LeftJoin(table schema.Tabler, on ...field.Expr) *photoDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p photoDo) RightJoin(table schema.Tabler, on ...field.Expr) *photoDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p photoDo) Group(cols ...field.Expr) *photoDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p photoDo) Having(conds ...gen.Condition) *photoDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p photoDo) Limit(limit int) *photoDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p photoDo) Offset(offset int) *photoDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p photoDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *photoDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p photoDo) Unscoped() *photoDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p photoDo) Create(values ...*entity.Photo) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p photoDo) CreateInBatches(values []*entity.Photo, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p photoDo) Save(values ...*entity.Photo) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p photoDo) First() (*entity.Photo, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Photo), nil + } +} + +func (p photoDo) Take() (*entity.Photo, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Photo), nil + } +} + +func (p photoDo) Last() (*entity.Photo, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Photo), nil + } +} + +func (p photoDo) Find() ([]*entity.Photo, error) { + result, err := p.DO.Find() + return result.([]*entity.Photo), err +} + +func (p photoDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Photo, err error) { + buf := make([]*entity.Photo, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p photoDo) FindInBatches(result *[]*entity.Photo, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p photoDo) Attrs(attrs ...field.AssignExpr) *photoDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p photoDo) Assign(attrs ...field.AssignExpr) *photoDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p photoDo) Joins(fields ...field.RelationField) *photoDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p photoDo) Preload(fields ...field.RelationField) *photoDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p photoDo) FirstOrInit() (*entity.Photo, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Photo), nil + } +} + +func (p photoDo) FirstOrCreate() (*entity.Photo, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Photo), nil + } +} + +func (p photoDo) FindByPage(offset int, limit int) (result []*entity.Photo, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p photoDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p photoDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p photoDo) Delete(models ...*entity.Photo) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *photoDo) withDO(do gen.Dao) *photoDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/post.gen.go b/dal/post.gen.go new file mode 100644 index 00000000..d62764b7 --- /dev/null +++ b/dal/post.gen.go @@ -0,0 +1,406 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newPost(db *gorm.DB) post { + _post := post{} + + _post.postDo.UseDB(db) + _post.postDo.UseModel(&entity.Post{}) + + tableName := _post.postDo.TableName() + _post.ALL = field.NewAsterisk(tableName) + _post.ID = field.NewInt32(tableName, "id") + _post.Type = field.NewField(tableName, "type") + _post.CreateTime = field.NewTime(tableName, "create_time") + _post.UpdateTime = field.NewTime(tableName, "update_time") + _post.DisallowComment = field.NewBool(tableName, "disallow_comment") + _post.EditTime = field.NewTime(tableName, "edit_time") + _post.EditorType = field.NewField(tableName, "editor_type") + _post.Likes = field.NewInt64(tableName, "likes") + _post.MetaDescription = field.NewString(tableName, "meta_description") + _post.MetaKeywords = field.NewString(tableName, "meta_keywords") + _post.Password = field.NewString(tableName, "password") + _post.Slug = field.NewString(tableName, "slug") + _post.Status = field.NewField(tableName, "status") + _post.Summary = field.NewString(tableName, "summary") + _post.Template = field.NewString(tableName, "template") + _post.Thumbnail = field.NewString(tableName, "thumbnail") + _post.Title = field.NewString(tableName, "title") + _post.TopPriority = field.NewInt32(tableName, "top_priority") + _post.Visits = field.NewInt64(tableName, "visits") + _post.WordCount = field.NewInt64(tableName, "word_count") + _post.Version = field.NewInt32(tableName, "version") + _post.FormatContent = field.NewString(tableName, "format_content") + _post.OriginalContent = field.NewString(tableName, "original_content") + + _post.fillFieldMap() + + return _post +} + +type post struct { + postDo postDo + + ALL field.Asterisk + ID field.Int32 + Type field.Field + CreateTime field.Time + UpdateTime field.Time + DisallowComment field.Bool + EditTime field.Time + EditorType field.Field + Likes field.Int64 + MetaDescription field.String + MetaKeywords field.String + Password field.String + Slug field.String + Status field.Field + Summary field.String + Template field.String + Thumbnail field.String + Title field.String + TopPriority field.Int32 + Visits field.Int64 + WordCount field.Int64 + Version field.Int32 + FormatContent field.String + OriginalContent field.String + + fieldMap map[string]field.Expr +} + +func (p post) Table(newTableName string) *post { + p.postDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p post) As(alias string) *post { + p.postDo.DO = *(p.postDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *post) updateTableName(table string) *post { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewInt32(table, "id") + p.Type = field.NewField(table, "type") + p.CreateTime = field.NewTime(table, "create_time") + p.UpdateTime = field.NewTime(table, "update_time") + p.DisallowComment = field.NewBool(table, "disallow_comment") + p.EditTime = field.NewTime(table, "edit_time") + p.EditorType = field.NewField(table, "editor_type") + p.Likes = field.NewInt64(table, "likes") + p.MetaDescription = field.NewString(table, "meta_description") + p.MetaKeywords = field.NewString(table, "meta_keywords") + p.Password = field.NewString(table, "password") + p.Slug = field.NewString(table, "slug") + p.Status = field.NewField(table, "status") + p.Summary = field.NewString(table, "summary") + p.Template = field.NewString(table, "template") + p.Thumbnail = field.NewString(table, "thumbnail") + p.Title = field.NewString(table, "title") + p.TopPriority = field.NewInt32(table, "top_priority") + p.Visits = field.NewInt64(table, "visits") + p.WordCount = field.NewInt64(table, "word_count") + p.Version = field.NewInt32(table, "version") + p.FormatContent = field.NewString(table, "format_content") + p.OriginalContent = field.NewString(table, "original_content") + + p.fillFieldMap() + + return p +} + +func (p *post) WithContext(ctx context.Context) *postDo { return p.postDo.WithContext(ctx) } + +func (p post) TableName() string { return p.postDo.TableName() } + +func (p post) Alias() string { return p.postDo.Alias() } + +func (p *post) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *post) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 23) + p.fieldMap["id"] = p.ID + p.fieldMap["type"] = p.Type + p.fieldMap["create_time"] = p.CreateTime + p.fieldMap["update_time"] = p.UpdateTime + p.fieldMap["disallow_comment"] = p.DisallowComment + p.fieldMap["edit_time"] = p.EditTime + p.fieldMap["editor_type"] = p.EditorType + p.fieldMap["likes"] = p.Likes + p.fieldMap["meta_description"] = p.MetaDescription + p.fieldMap["meta_keywords"] = p.MetaKeywords + p.fieldMap["password"] = p.Password + p.fieldMap["slug"] = p.Slug + p.fieldMap["status"] = p.Status + p.fieldMap["summary"] = p.Summary + p.fieldMap["template"] = p.Template + p.fieldMap["thumbnail"] = p.Thumbnail + p.fieldMap["title"] = p.Title + p.fieldMap["top_priority"] = p.TopPriority + p.fieldMap["visits"] = p.Visits + p.fieldMap["word_count"] = p.WordCount + p.fieldMap["version"] = p.Version + p.fieldMap["format_content"] = p.FormatContent + p.fieldMap["original_content"] = p.OriginalContent +} + +func (p post) clone(db *gorm.DB) post { + p.postDo.ReplaceDB(db) + return p +} + +type postDo struct{ gen.DO } + +func (p postDo) Debug() *postDo { + return p.withDO(p.DO.Debug()) +} + +func (p postDo) WithContext(ctx context.Context) *postDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p postDo) ReadDB() *postDo { + return p.Clauses(dbresolver.Read) +} + +func (p postDo) WriteDB() *postDo { + return p.Clauses(dbresolver.Write) +} + +func (p postDo) Clauses(conds ...clause.Expression) *postDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p postDo) Returning(value interface{}, columns ...string) *postDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p postDo) Not(conds ...gen.Condition) *postDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p postDo) Or(conds ...gen.Condition) *postDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p postDo) Select(conds ...field.Expr) *postDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p postDo) Where(conds ...gen.Condition) *postDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p postDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *postDo { + return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (p postDo) Order(conds ...field.Expr) *postDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p postDo) Distinct(cols ...field.Expr) *postDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p postDo) Omit(cols ...field.Expr) *postDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p postDo) Join(table schema.Tabler, on ...field.Expr) *postDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p postDo) LeftJoin(table schema.Tabler, on ...field.Expr) *postDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p postDo) RightJoin(table schema.Tabler, on ...field.Expr) *postDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p postDo) Group(cols ...field.Expr) *postDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p postDo) Having(conds ...gen.Condition) *postDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p postDo) Limit(limit int) *postDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p postDo) Offset(offset int) *postDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p postDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *postDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p postDo) Unscoped() *postDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p postDo) Create(values ...*entity.Post) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p postDo) CreateInBatches(values []*entity.Post, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p postDo) Save(values ...*entity.Post) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p postDo) First() (*entity.Post, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Post), nil + } +} + +func (p postDo) Take() (*entity.Post, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Post), nil + } +} + +func (p postDo) Last() (*entity.Post, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Post), nil + } +} + +func (p postDo) Find() ([]*entity.Post, error) { + result, err := p.DO.Find() + return result.([]*entity.Post), err +} + +func (p postDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Post, err error) { + buf := make([]*entity.Post, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p postDo) FindInBatches(result *[]*entity.Post, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p postDo) Attrs(attrs ...field.AssignExpr) *postDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p postDo) Assign(attrs ...field.AssignExpr) *postDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p postDo) Joins(fields ...field.RelationField) *postDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p postDo) Preload(fields ...field.RelationField) *postDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p postDo) FirstOrInit() (*entity.Post, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Post), nil + } +} + +func (p postDo) FirstOrCreate() (*entity.Post, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Post), nil + } +} + +func (p postDo) FindByPage(offset int, limit int) (result []*entity.Post, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p postDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p postDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p postDo) Delete(models ...*entity.Post) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *postDo) withDO(do gen.Dao) *postDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/post_category.gen.go b/dal/post_category.gen.go new file mode 100644 index 00000000..25d60ea5 --- /dev/null +++ b/dal/post_category.gen.go @@ -0,0 +1,336 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newPostCategory(db *gorm.DB) postCategory { + _postCategory := postCategory{} + + _postCategory.postCategoryDo.UseDB(db) + _postCategory.postCategoryDo.UseModel(&entity.PostCategory{}) + + tableName := _postCategory.postCategoryDo.TableName() + _postCategory.ALL = field.NewAsterisk(tableName) + _postCategory.ID = field.NewInt32(tableName, "id") + _postCategory.CreateTime = field.NewTime(tableName, "create_time") + _postCategory.UpdateTime = field.NewTime(tableName, "update_time") + _postCategory.CategoryID = field.NewInt32(tableName, "category_id") + _postCategory.PostID = field.NewInt32(tableName, "post_id") + + _postCategory.fillFieldMap() + + return _postCategory +} + +type postCategory struct { + postCategoryDo postCategoryDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + CategoryID field.Int32 + PostID field.Int32 + + fieldMap map[string]field.Expr +} + +func (p postCategory) Table(newTableName string) *postCategory { + p.postCategoryDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p postCategory) As(alias string) *postCategory { + p.postCategoryDo.DO = *(p.postCategoryDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *postCategory) updateTableName(table string) *postCategory { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewInt32(table, "id") + p.CreateTime = field.NewTime(table, "create_time") + p.UpdateTime = field.NewTime(table, "update_time") + p.CategoryID = field.NewInt32(table, "category_id") + p.PostID = field.NewInt32(table, "post_id") + + p.fillFieldMap() + + return p +} + +func (p *postCategory) WithContext(ctx context.Context) *postCategoryDo { + return p.postCategoryDo.WithContext(ctx) +} + +func (p postCategory) TableName() string { return p.postCategoryDo.TableName() } + +func (p postCategory) Alias() string { return p.postCategoryDo.Alias() } + +func (p *postCategory) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *postCategory) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 5) + p.fieldMap["id"] = p.ID + p.fieldMap["create_time"] = p.CreateTime + p.fieldMap["update_time"] = p.UpdateTime + p.fieldMap["category_id"] = p.CategoryID + p.fieldMap["post_id"] = p.PostID +} + +func (p postCategory) clone(db *gorm.DB) postCategory { + p.postCategoryDo.ReplaceDB(db) + return p +} + +type postCategoryDo struct{ gen.DO } + +func (p postCategoryDo) Debug() *postCategoryDo { + return p.withDO(p.DO.Debug()) +} + +func (p postCategoryDo) WithContext(ctx context.Context) *postCategoryDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p postCategoryDo) ReadDB() *postCategoryDo { + return p.Clauses(dbresolver.Read) +} + +func (p postCategoryDo) WriteDB() *postCategoryDo { + return p.Clauses(dbresolver.Write) +} + +func (p postCategoryDo) Clauses(conds ...clause.Expression) *postCategoryDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p postCategoryDo) Returning(value interface{}, columns ...string) *postCategoryDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p postCategoryDo) Not(conds ...gen.Condition) *postCategoryDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p postCategoryDo) Or(conds ...gen.Condition) *postCategoryDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p postCategoryDo) Select(conds ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p postCategoryDo) Where(conds ...gen.Condition) *postCategoryDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p postCategoryDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *postCategoryDo { + return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (p postCategoryDo) Order(conds ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p postCategoryDo) Distinct(cols ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p postCategoryDo) Omit(cols ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p postCategoryDo) Join(table schema.Tabler, on ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p postCategoryDo) LeftJoin(table schema.Tabler, on ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p postCategoryDo) RightJoin(table schema.Tabler, on ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p postCategoryDo) Group(cols ...field.Expr) *postCategoryDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p postCategoryDo) Having(conds ...gen.Condition) *postCategoryDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p postCategoryDo) Limit(limit int) *postCategoryDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p postCategoryDo) Offset(offset int) *postCategoryDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p postCategoryDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *postCategoryDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p postCategoryDo) Unscoped() *postCategoryDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p postCategoryDo) Create(values ...*entity.PostCategory) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p postCategoryDo) CreateInBatches(values []*entity.PostCategory, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p postCategoryDo) Save(values ...*entity.PostCategory) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p postCategoryDo) First() (*entity.PostCategory, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.PostCategory), nil + } +} + +func (p postCategoryDo) Take() (*entity.PostCategory, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.PostCategory), nil + } +} + +func (p postCategoryDo) Last() (*entity.PostCategory, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.PostCategory), nil + } +} + +func (p postCategoryDo) Find() ([]*entity.PostCategory, error) { + result, err := p.DO.Find() + return result.([]*entity.PostCategory), err +} + +func (p postCategoryDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.PostCategory, err error) { + buf := make([]*entity.PostCategory, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p postCategoryDo) FindInBatches(result *[]*entity.PostCategory, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p postCategoryDo) Attrs(attrs ...field.AssignExpr) *postCategoryDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p postCategoryDo) Assign(attrs ...field.AssignExpr) *postCategoryDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p postCategoryDo) Joins(fields ...field.RelationField) *postCategoryDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p postCategoryDo) Preload(fields ...field.RelationField) *postCategoryDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p postCategoryDo) FirstOrInit() (*entity.PostCategory, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.PostCategory), nil + } +} + +func (p postCategoryDo) FirstOrCreate() (*entity.PostCategory, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.PostCategory), nil + } +} + +func (p postCategoryDo) FindByPage(offset int, limit int) (result []*entity.PostCategory, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p postCategoryDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p postCategoryDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p postCategoryDo) Delete(models ...*entity.PostCategory) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *postCategoryDo) withDO(do gen.Dao) *postCategoryDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/post_tag.gen.go b/dal/post_tag.gen.go new file mode 100644 index 00000000..de009460 --- /dev/null +++ b/dal/post_tag.gen.go @@ -0,0 +1,334 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newPostTag(db *gorm.DB) postTag { + _postTag := postTag{} + + _postTag.postTagDo.UseDB(db) + _postTag.postTagDo.UseModel(&entity.PostTag{}) + + tableName := _postTag.postTagDo.TableName() + _postTag.ALL = field.NewAsterisk(tableName) + _postTag.ID = field.NewInt32(tableName, "id") + _postTag.CreateTime = field.NewTime(tableName, "create_time") + _postTag.UpdateTime = field.NewTime(tableName, "update_time") + _postTag.PostID = field.NewInt32(tableName, "post_id") + _postTag.TagID = field.NewInt32(tableName, "tag_id") + + _postTag.fillFieldMap() + + return _postTag +} + +type postTag struct { + postTagDo postTagDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + PostID field.Int32 + TagID field.Int32 + + fieldMap map[string]field.Expr +} + +func (p postTag) Table(newTableName string) *postTag { + p.postTagDo.UseTable(newTableName) + return p.updateTableName(newTableName) +} + +func (p postTag) As(alias string) *postTag { + p.postTagDo.DO = *(p.postTagDo.As(alias).(*gen.DO)) + return p.updateTableName(alias) +} + +func (p *postTag) updateTableName(table string) *postTag { + p.ALL = field.NewAsterisk(table) + p.ID = field.NewInt32(table, "id") + p.CreateTime = field.NewTime(table, "create_time") + p.UpdateTime = field.NewTime(table, "update_time") + p.PostID = field.NewInt32(table, "post_id") + p.TagID = field.NewInt32(table, "tag_id") + + p.fillFieldMap() + + return p +} + +func (p *postTag) WithContext(ctx context.Context) *postTagDo { return p.postTagDo.WithContext(ctx) } + +func (p postTag) TableName() string { return p.postTagDo.TableName() } + +func (p postTag) Alias() string { return p.postTagDo.Alias() } + +func (p *postTag) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := p.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (p *postTag) fillFieldMap() { + p.fieldMap = make(map[string]field.Expr, 5) + p.fieldMap["id"] = p.ID + p.fieldMap["create_time"] = p.CreateTime + p.fieldMap["update_time"] = p.UpdateTime + p.fieldMap["post_id"] = p.PostID + p.fieldMap["tag_id"] = p.TagID +} + +func (p postTag) clone(db *gorm.DB) postTag { + p.postTagDo.ReplaceDB(db) + return p +} + +type postTagDo struct{ gen.DO } + +func (p postTagDo) Debug() *postTagDo { + return p.withDO(p.DO.Debug()) +} + +func (p postTagDo) WithContext(ctx context.Context) *postTagDo { + return p.withDO(p.DO.WithContext(ctx)) +} + +func (p postTagDo) ReadDB() *postTagDo { + return p.Clauses(dbresolver.Read) +} + +func (p postTagDo) WriteDB() *postTagDo { + return p.Clauses(dbresolver.Write) +} + +func (p postTagDo) Clauses(conds ...clause.Expression) *postTagDo { + return p.withDO(p.DO.Clauses(conds...)) +} + +func (p postTagDo) Returning(value interface{}, columns ...string) *postTagDo { + return p.withDO(p.DO.Returning(value, columns...)) +} + +func (p postTagDo) Not(conds ...gen.Condition) *postTagDo { + return p.withDO(p.DO.Not(conds...)) +} + +func (p postTagDo) Or(conds ...gen.Condition) *postTagDo { + return p.withDO(p.DO.Or(conds...)) +} + +func (p postTagDo) Select(conds ...field.Expr) *postTagDo { + return p.withDO(p.DO.Select(conds...)) +} + +func (p postTagDo) Where(conds ...gen.Condition) *postTagDo { + return p.withDO(p.DO.Where(conds...)) +} + +func (p postTagDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *postTagDo { + return p.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (p postTagDo) Order(conds ...field.Expr) *postTagDo { + return p.withDO(p.DO.Order(conds...)) +} + +func (p postTagDo) Distinct(cols ...field.Expr) *postTagDo { + return p.withDO(p.DO.Distinct(cols...)) +} + +func (p postTagDo) Omit(cols ...field.Expr) *postTagDo { + return p.withDO(p.DO.Omit(cols...)) +} + +func (p postTagDo) Join(table schema.Tabler, on ...field.Expr) *postTagDo { + return p.withDO(p.DO.Join(table, on...)) +} + +func (p postTagDo) LeftJoin(table schema.Tabler, on ...field.Expr) *postTagDo { + return p.withDO(p.DO.LeftJoin(table, on...)) +} + +func (p postTagDo) RightJoin(table schema.Tabler, on ...field.Expr) *postTagDo { + return p.withDO(p.DO.RightJoin(table, on...)) +} + +func (p postTagDo) Group(cols ...field.Expr) *postTagDo { + return p.withDO(p.DO.Group(cols...)) +} + +func (p postTagDo) Having(conds ...gen.Condition) *postTagDo { + return p.withDO(p.DO.Having(conds...)) +} + +func (p postTagDo) Limit(limit int) *postTagDo { + return p.withDO(p.DO.Limit(limit)) +} + +func (p postTagDo) Offset(offset int) *postTagDo { + return p.withDO(p.DO.Offset(offset)) +} + +func (p postTagDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *postTagDo { + return p.withDO(p.DO.Scopes(funcs...)) +} + +func (p postTagDo) Unscoped() *postTagDo { + return p.withDO(p.DO.Unscoped()) +} + +func (p postTagDo) Create(values ...*entity.PostTag) error { + if len(values) == 0 { + return nil + } + return p.DO.Create(values) +} + +func (p postTagDo) CreateInBatches(values []*entity.PostTag, batchSize int) error { + return p.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (p postTagDo) Save(values ...*entity.PostTag) error { + if len(values) == 0 { + return nil + } + return p.DO.Save(values) +} + +func (p postTagDo) First() (*entity.PostTag, error) { + if result, err := p.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.PostTag), nil + } +} + +func (p postTagDo) Take() (*entity.PostTag, error) { + if result, err := p.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.PostTag), nil + } +} + +func (p postTagDo) Last() (*entity.PostTag, error) { + if result, err := p.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.PostTag), nil + } +} + +func (p postTagDo) Find() ([]*entity.PostTag, error) { + result, err := p.DO.Find() + return result.([]*entity.PostTag), err +} + +func (p postTagDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.PostTag, err error) { + buf := make([]*entity.PostTag, 0, batchSize) + err = p.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (p postTagDo) FindInBatches(result *[]*entity.PostTag, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return p.DO.FindInBatches(result, batchSize, fc) +} + +func (p postTagDo) Attrs(attrs ...field.AssignExpr) *postTagDo { + return p.withDO(p.DO.Attrs(attrs...)) +} + +func (p postTagDo) Assign(attrs ...field.AssignExpr) *postTagDo { + return p.withDO(p.DO.Assign(attrs...)) +} + +func (p postTagDo) Joins(fields ...field.RelationField) *postTagDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Joins(_f)) + } + return &p +} + +func (p postTagDo) Preload(fields ...field.RelationField) *postTagDo { + for _, _f := range fields { + p = *p.withDO(p.DO.Preload(_f)) + } + return &p +} + +func (p postTagDo) FirstOrInit() (*entity.PostTag, error) { + if result, err := p.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.PostTag), nil + } +} + +func (p postTagDo) FirstOrCreate() (*entity.PostTag, error) { + if result, err := p.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.PostTag), nil + } +} + +func (p postTagDo) FindByPage(offset int, limit int) (result []*entity.PostTag, count int64, err error) { + result, err = p.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = p.Offset(-1).Limit(-1).Count() + return +} + +func (p postTagDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = p.Count() + if err != nil { + return + } + + err = p.Offset(offset).Limit(limit).Scan(result) + return +} + +func (p postTagDo) Scan(result interface{}) (err error) { + return p.DO.Scan(result) +} + +func (p postTagDo) Delete(models ...*entity.PostTag) (result gen.ResultInfo, err error) { + return p.DO.Delete(models) +} + +func (p *postTagDo) withDO(do gen.Dao) *postTagDo { + p.DO = *do.(*gen.DO) + return p +} diff --git a/dal/tag.gen.go b/dal/tag.gen.go new file mode 100644 index 00000000..f72b9cad --- /dev/null +++ b/dal/tag.gen.go @@ -0,0 +1,342 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newTag(db *gorm.DB) tag { + _tag := tag{} + + _tag.tagDo.UseDB(db) + _tag.tagDo.UseModel(&entity.Tag{}) + + tableName := _tag.tagDo.TableName() + _tag.ALL = field.NewAsterisk(tableName) + _tag.ID = field.NewInt32(tableName, "id") + _tag.CreateTime = field.NewTime(tableName, "create_time") + _tag.UpdateTime = field.NewTime(tableName, "update_time") + _tag.Name = field.NewString(tableName, "name") + _tag.Slug = field.NewString(tableName, "slug") + _tag.Thumbnail = field.NewString(tableName, "thumbnail") + _tag.Color = field.NewString(tableName, "color") + + _tag.fillFieldMap() + + return _tag +} + +type tag struct { + tagDo tagDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Name field.String + Slug field.String + Thumbnail field.String + Color field.String + + fieldMap map[string]field.Expr +} + +func (t tag) Table(newTableName string) *tag { + t.tagDo.UseTable(newTableName) + return t.updateTableName(newTableName) +} + +func (t tag) As(alias string) *tag { + t.tagDo.DO = *(t.tagDo.As(alias).(*gen.DO)) + return t.updateTableName(alias) +} + +func (t *tag) updateTableName(table string) *tag { + t.ALL = field.NewAsterisk(table) + t.ID = field.NewInt32(table, "id") + t.CreateTime = field.NewTime(table, "create_time") + t.UpdateTime = field.NewTime(table, "update_time") + t.Name = field.NewString(table, "name") + t.Slug = field.NewString(table, "slug") + t.Thumbnail = field.NewString(table, "thumbnail") + t.Color = field.NewString(table, "color") + + t.fillFieldMap() + + return t +} + +func (t *tag) WithContext(ctx context.Context) *tagDo { return t.tagDo.WithContext(ctx) } + +func (t tag) TableName() string { return t.tagDo.TableName() } + +func (t tag) Alias() string { return t.tagDo.Alias() } + +func (t *tag) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := t.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (t *tag) fillFieldMap() { + t.fieldMap = make(map[string]field.Expr, 7) + t.fieldMap["id"] = t.ID + t.fieldMap["create_time"] = t.CreateTime + t.fieldMap["update_time"] = t.UpdateTime + t.fieldMap["name"] = t.Name + t.fieldMap["slug"] = t.Slug + t.fieldMap["thumbnail"] = t.Thumbnail + t.fieldMap["color"] = t.Color +} + +func (t tag) clone(db *gorm.DB) tag { + t.tagDo.ReplaceDB(db) + return t +} + +type tagDo struct{ gen.DO } + +func (t tagDo) Debug() *tagDo { + return t.withDO(t.DO.Debug()) +} + +func (t tagDo) WithContext(ctx context.Context) *tagDo { + return t.withDO(t.DO.WithContext(ctx)) +} + +func (t tagDo) ReadDB() *tagDo { + return t.Clauses(dbresolver.Read) +} + +func (t tagDo) WriteDB() *tagDo { + return t.Clauses(dbresolver.Write) +} + +func (t tagDo) Clauses(conds ...clause.Expression) *tagDo { + return t.withDO(t.DO.Clauses(conds...)) +} + +func (t tagDo) Returning(value interface{}, columns ...string) *tagDo { + return t.withDO(t.DO.Returning(value, columns...)) +} + +func (t tagDo) Not(conds ...gen.Condition) *tagDo { + return t.withDO(t.DO.Not(conds...)) +} + +func (t tagDo) Or(conds ...gen.Condition) *tagDo { + return t.withDO(t.DO.Or(conds...)) +} + +func (t tagDo) Select(conds ...field.Expr) *tagDo { + return t.withDO(t.DO.Select(conds...)) +} + +func (t tagDo) Where(conds ...gen.Condition) *tagDo { + return t.withDO(t.DO.Where(conds...)) +} + +func (t tagDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *tagDo { + return t.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (t tagDo) Order(conds ...field.Expr) *tagDo { + return t.withDO(t.DO.Order(conds...)) +} + +func (t tagDo) Distinct(cols ...field.Expr) *tagDo { + return t.withDO(t.DO.Distinct(cols...)) +} + +func (t tagDo) Omit(cols ...field.Expr) *tagDo { + return t.withDO(t.DO.Omit(cols...)) +} + +func (t tagDo) Join(table schema.Tabler, on ...field.Expr) *tagDo { + return t.withDO(t.DO.Join(table, on...)) +} + +func (t tagDo) LeftJoin(table schema.Tabler, on ...field.Expr) *tagDo { + return t.withDO(t.DO.LeftJoin(table, on...)) +} + +func (t tagDo) RightJoin(table schema.Tabler, on ...field.Expr) *tagDo { + return t.withDO(t.DO.RightJoin(table, on...)) +} + +func (t tagDo) Group(cols ...field.Expr) *tagDo { + return t.withDO(t.DO.Group(cols...)) +} + +func (t tagDo) Having(conds ...gen.Condition) *tagDo { + return t.withDO(t.DO.Having(conds...)) +} + +func (t tagDo) Limit(limit int) *tagDo { + return t.withDO(t.DO.Limit(limit)) +} + +func (t tagDo) Offset(offset int) *tagDo { + return t.withDO(t.DO.Offset(offset)) +} + +func (t tagDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *tagDo { + return t.withDO(t.DO.Scopes(funcs...)) +} + +func (t tagDo) Unscoped() *tagDo { + return t.withDO(t.DO.Unscoped()) +} + +func (t tagDo) Create(values ...*entity.Tag) error { + if len(values) == 0 { + return nil + } + return t.DO.Create(values) +} + +func (t tagDo) CreateInBatches(values []*entity.Tag, batchSize int) error { + return t.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (t tagDo) Save(values ...*entity.Tag) error { + if len(values) == 0 { + return nil + } + return t.DO.Save(values) +} + +func (t tagDo) First() (*entity.Tag, error) { + if result, err := t.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.Tag), nil + } +} + +func (t tagDo) Take() (*entity.Tag, error) { + if result, err := t.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.Tag), nil + } +} + +func (t tagDo) Last() (*entity.Tag, error) { + if result, err := t.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.Tag), nil + } +} + +func (t tagDo) Find() ([]*entity.Tag, error) { + result, err := t.DO.Find() + return result.([]*entity.Tag), err +} + +func (t tagDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.Tag, err error) { + buf := make([]*entity.Tag, 0, batchSize) + err = t.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (t tagDo) FindInBatches(result *[]*entity.Tag, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return t.DO.FindInBatches(result, batchSize, fc) +} + +func (t tagDo) Attrs(attrs ...field.AssignExpr) *tagDo { + return t.withDO(t.DO.Attrs(attrs...)) +} + +func (t tagDo) Assign(attrs ...field.AssignExpr) *tagDo { + return t.withDO(t.DO.Assign(attrs...)) +} + +func (t tagDo) Joins(fields ...field.RelationField) *tagDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Joins(_f)) + } + return &t +} + +func (t tagDo) Preload(fields ...field.RelationField) *tagDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Preload(_f)) + } + return &t +} + +func (t tagDo) FirstOrInit() (*entity.Tag, error) { + if result, err := t.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.Tag), nil + } +} + +func (t tagDo) FirstOrCreate() (*entity.Tag, error) { + if result, err := t.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.Tag), nil + } +} + +func (t tagDo) FindByPage(offset int, limit int) (result []*entity.Tag, count int64, err error) { + result, err = t.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = t.Offset(-1).Limit(-1).Count() + return +} + +func (t tagDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = t.Count() + if err != nil { + return + } + + err = t.Offset(offset).Limit(limit).Scan(result) + return +} + +func (t tagDo) Scan(result interface{}) (err error) { + return t.DO.Scan(result) +} + +func (t tagDo) Delete(models ...*entity.Tag) (result gen.ResultInfo, err error) { + return t.DO.Delete(models) +} + +func (t *tagDo) withDO(do gen.Dao) *tagDo { + t.DO = *do.(*gen.DO) + return t +} diff --git a/dal/theme_setting.gen.go b/dal/theme_setting.gen.go new file mode 100644 index 00000000..f31663ba --- /dev/null +++ b/dal/theme_setting.gen.go @@ -0,0 +1,340 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newThemeSetting(db *gorm.DB) themeSetting { + _themeSetting := themeSetting{} + + _themeSetting.themeSettingDo.UseDB(db) + _themeSetting.themeSettingDo.UseModel(&entity.ThemeSetting{}) + + tableName := _themeSetting.themeSettingDo.TableName() + _themeSetting.ALL = field.NewAsterisk(tableName) + _themeSetting.ID = field.NewInt32(tableName, "id") + _themeSetting.CreateTime = field.NewTime(tableName, "create_time") + _themeSetting.UpdateTime = field.NewTime(tableName, "update_time") + _themeSetting.SettingKey = field.NewString(tableName, "setting_key") + _themeSetting.ThemeID = field.NewString(tableName, "theme_id") + _themeSetting.SettingValue = field.NewString(tableName, "setting_value") + + _themeSetting.fillFieldMap() + + return _themeSetting +} + +type themeSetting struct { + themeSettingDo themeSettingDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + SettingKey field.String + ThemeID field.String + SettingValue field.String + + fieldMap map[string]field.Expr +} + +func (t themeSetting) Table(newTableName string) *themeSetting { + t.themeSettingDo.UseTable(newTableName) + return t.updateTableName(newTableName) +} + +func (t themeSetting) As(alias string) *themeSetting { + t.themeSettingDo.DO = *(t.themeSettingDo.As(alias).(*gen.DO)) + return t.updateTableName(alias) +} + +func (t *themeSetting) updateTableName(table string) *themeSetting { + t.ALL = field.NewAsterisk(table) + t.ID = field.NewInt32(table, "id") + t.CreateTime = field.NewTime(table, "create_time") + t.UpdateTime = field.NewTime(table, "update_time") + t.SettingKey = field.NewString(table, "setting_key") + t.ThemeID = field.NewString(table, "theme_id") + t.SettingValue = field.NewString(table, "setting_value") + + t.fillFieldMap() + + return t +} + +func (t *themeSetting) WithContext(ctx context.Context) *themeSettingDo { + return t.themeSettingDo.WithContext(ctx) +} + +func (t themeSetting) TableName() string { return t.themeSettingDo.TableName() } + +func (t themeSetting) Alias() string { return t.themeSettingDo.Alias() } + +func (t *themeSetting) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := t.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (t *themeSetting) fillFieldMap() { + t.fieldMap = make(map[string]field.Expr, 6) + t.fieldMap["id"] = t.ID + t.fieldMap["create_time"] = t.CreateTime + t.fieldMap["update_time"] = t.UpdateTime + t.fieldMap["setting_key"] = t.SettingKey + t.fieldMap["theme_id"] = t.ThemeID + t.fieldMap["setting_value"] = t.SettingValue +} + +func (t themeSetting) clone(db *gorm.DB) themeSetting { + t.themeSettingDo.ReplaceDB(db) + return t +} + +type themeSettingDo struct{ gen.DO } + +func (t themeSettingDo) Debug() *themeSettingDo { + return t.withDO(t.DO.Debug()) +} + +func (t themeSettingDo) WithContext(ctx context.Context) *themeSettingDo { + return t.withDO(t.DO.WithContext(ctx)) +} + +func (t themeSettingDo) ReadDB() *themeSettingDo { + return t.Clauses(dbresolver.Read) +} + +func (t themeSettingDo) WriteDB() *themeSettingDo { + return t.Clauses(dbresolver.Write) +} + +func (t themeSettingDo) Clauses(conds ...clause.Expression) *themeSettingDo { + return t.withDO(t.DO.Clauses(conds...)) +} + +func (t themeSettingDo) Returning(value interface{}, columns ...string) *themeSettingDo { + return t.withDO(t.DO.Returning(value, columns...)) +} + +func (t themeSettingDo) Not(conds ...gen.Condition) *themeSettingDo { + return t.withDO(t.DO.Not(conds...)) +} + +func (t themeSettingDo) Or(conds ...gen.Condition) *themeSettingDo { + return t.withDO(t.DO.Or(conds...)) +} + +func (t themeSettingDo) Select(conds ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Select(conds...)) +} + +func (t themeSettingDo) Where(conds ...gen.Condition) *themeSettingDo { + return t.withDO(t.DO.Where(conds...)) +} + +func (t themeSettingDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *themeSettingDo { + return t.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (t themeSettingDo) Order(conds ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Order(conds...)) +} + +func (t themeSettingDo) Distinct(cols ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Distinct(cols...)) +} + +func (t themeSettingDo) Omit(cols ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Omit(cols...)) +} + +func (t themeSettingDo) Join(table schema.Tabler, on ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Join(table, on...)) +} + +func (t themeSettingDo) LeftJoin(table schema.Tabler, on ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.LeftJoin(table, on...)) +} + +func (t themeSettingDo) RightJoin(table schema.Tabler, on ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.RightJoin(table, on...)) +} + +func (t themeSettingDo) Group(cols ...field.Expr) *themeSettingDo { + return t.withDO(t.DO.Group(cols...)) +} + +func (t themeSettingDo) Having(conds ...gen.Condition) *themeSettingDo { + return t.withDO(t.DO.Having(conds...)) +} + +func (t themeSettingDo) Limit(limit int) *themeSettingDo { + return t.withDO(t.DO.Limit(limit)) +} + +func (t themeSettingDo) Offset(offset int) *themeSettingDo { + return t.withDO(t.DO.Offset(offset)) +} + +func (t themeSettingDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *themeSettingDo { + return t.withDO(t.DO.Scopes(funcs...)) +} + +func (t themeSettingDo) Unscoped() *themeSettingDo { + return t.withDO(t.DO.Unscoped()) +} + +func (t themeSettingDo) Create(values ...*entity.ThemeSetting) error { + if len(values) == 0 { + return nil + } + return t.DO.Create(values) +} + +func (t themeSettingDo) CreateInBatches(values []*entity.ThemeSetting, batchSize int) error { + return t.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (t themeSettingDo) Save(values ...*entity.ThemeSetting) error { + if len(values) == 0 { + return nil + } + return t.DO.Save(values) +} + +func (t themeSettingDo) First() (*entity.ThemeSetting, error) { + if result, err := t.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.ThemeSetting), nil + } +} + +func (t themeSettingDo) Take() (*entity.ThemeSetting, error) { + if result, err := t.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.ThemeSetting), nil + } +} + +func (t themeSettingDo) Last() (*entity.ThemeSetting, error) { + if result, err := t.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.ThemeSetting), nil + } +} + +func (t themeSettingDo) Find() ([]*entity.ThemeSetting, error) { + result, err := t.DO.Find() + return result.([]*entity.ThemeSetting), err +} + +func (t themeSettingDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.ThemeSetting, err error) { + buf := make([]*entity.ThemeSetting, 0, batchSize) + err = t.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (t themeSettingDo) FindInBatches(result *[]*entity.ThemeSetting, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return t.DO.FindInBatches(result, batchSize, fc) +} + +func (t themeSettingDo) Attrs(attrs ...field.AssignExpr) *themeSettingDo { + return t.withDO(t.DO.Attrs(attrs...)) +} + +func (t themeSettingDo) Assign(attrs ...field.AssignExpr) *themeSettingDo { + return t.withDO(t.DO.Assign(attrs...)) +} + +func (t themeSettingDo) Joins(fields ...field.RelationField) *themeSettingDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Joins(_f)) + } + return &t +} + +func (t themeSettingDo) Preload(fields ...field.RelationField) *themeSettingDo { + for _, _f := range fields { + t = *t.withDO(t.DO.Preload(_f)) + } + return &t +} + +func (t themeSettingDo) FirstOrInit() (*entity.ThemeSetting, error) { + if result, err := t.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.ThemeSetting), nil + } +} + +func (t themeSettingDo) FirstOrCreate() (*entity.ThemeSetting, error) { + if result, err := t.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.ThemeSetting), nil + } +} + +func (t themeSettingDo) FindByPage(offset int, limit int) (result []*entity.ThemeSetting, count int64, err error) { + result, err = t.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = t.Offset(-1).Limit(-1).Count() + return +} + +func (t themeSettingDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = t.Count() + if err != nil { + return + } + + err = t.Offset(offset).Limit(limit).Scan(result) + return +} + +func (t themeSettingDo) Scan(result interface{}) (err error) { + return t.DO.Scan(result) +} + +func (t themeSettingDo) Delete(models ...*entity.ThemeSetting) (result gen.ResultInfo, err error) { + return t.DO.Delete(models) +} + +func (t *themeSettingDo) withDO(do gen.Dao) *themeSettingDo { + t.DO = *do.(*gen.DO) + return t +} diff --git a/dal/user.gen.go b/dal/user.gen.go new file mode 100644 index 00000000..2e4cad35 --- /dev/null +++ b/dal/user.gen.go @@ -0,0 +1,362 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package dal + +import ( + "context" + + "gorm.io/gen" + "gorm.io/gen/field" + "gorm.io/gorm" + "gorm.io/gorm/clause" + "gorm.io/gorm/schema" + "gorm.io/plugin/dbresolver" + + "github.com/go-sonic/sonic/model/entity" +) + +func newUser(db *gorm.DB) user { + _user := user{} + + _user.userDo.UseDB(db) + _user.userDo.UseModel(&entity.User{}) + + tableName := _user.userDo.TableName() + _user.ALL = field.NewAsterisk(tableName) + _user.ID = field.NewInt32(tableName, "id") + _user.CreateTime = field.NewTime(tableName, "create_time") + _user.UpdateTime = field.NewTime(tableName, "update_time") + _user.Avatar = field.NewString(tableName, "avatar") + _user.Description = field.NewString(tableName, "description") + _user.Email = field.NewString(tableName, "email") + _user.ExpireTime = field.NewTime(tableName, "expire_time") + _user.MfaKey = field.NewString(tableName, "mfa_key") + _user.MfaType = field.NewField(tableName, "mfa_type") + _user.Nickname = field.NewString(tableName, "nickname") + _user.Password = field.NewString(tableName, "password") + _user.Username = field.NewString(tableName, "username") + + _user.fillFieldMap() + + return _user +} + +type user struct { + userDo userDo + + ALL field.Asterisk + ID field.Int32 + CreateTime field.Time + UpdateTime field.Time + Avatar field.String + Description field.String + Email field.String + ExpireTime field.Time + MfaKey field.String + MfaType field.Field + Nickname field.String + Password field.String + Username field.String + + fieldMap map[string]field.Expr +} + +func (u user) Table(newTableName string) *user { + u.userDo.UseTable(newTableName) + return u.updateTableName(newTableName) +} + +func (u user) As(alias string) *user { + u.userDo.DO = *(u.userDo.As(alias).(*gen.DO)) + return u.updateTableName(alias) +} + +func (u *user) updateTableName(table string) *user { + u.ALL = field.NewAsterisk(table) + u.ID = field.NewInt32(table, "id") + u.CreateTime = field.NewTime(table, "create_time") + u.UpdateTime = field.NewTime(table, "update_time") + u.Avatar = field.NewString(table, "avatar") + u.Description = field.NewString(table, "description") + u.Email = field.NewString(table, "email") + u.ExpireTime = field.NewTime(table, "expire_time") + u.MfaKey = field.NewString(table, "mfa_key") + u.MfaType = field.NewField(table, "mfa_type") + u.Nickname = field.NewString(table, "nickname") + u.Password = field.NewString(table, "password") + u.Username = field.NewString(table, "username") + + u.fillFieldMap() + + return u +} + +func (u *user) WithContext(ctx context.Context) *userDo { return u.userDo.WithContext(ctx) } + +func (u user) TableName() string { return u.userDo.TableName() } + +func (u user) Alias() string { return u.userDo.Alias() } + +func (u *user) GetFieldByName(fieldName string) (field.OrderExpr, bool) { + _f, ok := u.fieldMap[fieldName] + if !ok || _f == nil { + return nil, false + } + _oe, ok := _f.(field.OrderExpr) + return _oe, ok +} + +func (u *user) fillFieldMap() { + u.fieldMap = make(map[string]field.Expr, 12) + u.fieldMap["id"] = u.ID + u.fieldMap["create_time"] = u.CreateTime + u.fieldMap["update_time"] = u.UpdateTime + u.fieldMap["avatar"] = u.Avatar + u.fieldMap["description"] = u.Description + u.fieldMap["email"] = u.Email + u.fieldMap["expire_time"] = u.ExpireTime + u.fieldMap["mfa_key"] = u.MfaKey + u.fieldMap["mfa_type"] = u.MfaType + u.fieldMap["nickname"] = u.Nickname + u.fieldMap["password"] = u.Password + u.fieldMap["username"] = u.Username +} + +func (u user) clone(db *gorm.DB) user { + u.userDo.ReplaceDB(db) + return u +} + +type userDo struct{ gen.DO } + +func (u userDo) Debug() *userDo { + return u.withDO(u.DO.Debug()) +} + +func (u userDo) WithContext(ctx context.Context) *userDo { + return u.withDO(u.DO.WithContext(ctx)) +} + +func (u userDo) ReadDB() *userDo { + return u.Clauses(dbresolver.Read) +} + +func (u userDo) WriteDB() *userDo { + return u.Clauses(dbresolver.Write) +} + +func (u userDo) Clauses(conds ...clause.Expression) *userDo { + return u.withDO(u.DO.Clauses(conds...)) +} + +func (u userDo) Returning(value interface{}, columns ...string) *userDo { + return u.withDO(u.DO.Returning(value, columns...)) +} + +func (u userDo) Not(conds ...gen.Condition) *userDo { + return u.withDO(u.DO.Not(conds...)) +} + +func (u userDo) Or(conds ...gen.Condition) *userDo { + return u.withDO(u.DO.Or(conds...)) +} + +func (u userDo) Select(conds ...field.Expr) *userDo { + return u.withDO(u.DO.Select(conds...)) +} + +func (u userDo) Where(conds ...gen.Condition) *userDo { + return u.withDO(u.DO.Where(conds...)) +} + +func (u userDo) Exists(subquery interface{ UnderlyingDB() *gorm.DB }) *userDo { + return u.Where(field.CompareSubQuery(field.ExistsOp, nil, subquery.UnderlyingDB())) +} + +func (u userDo) Order(conds ...field.Expr) *userDo { + return u.withDO(u.DO.Order(conds...)) +} + +func (u userDo) Distinct(cols ...field.Expr) *userDo { + return u.withDO(u.DO.Distinct(cols...)) +} + +func (u userDo) Omit(cols ...field.Expr) *userDo { + return u.withDO(u.DO.Omit(cols...)) +} + +func (u userDo) Join(table schema.Tabler, on ...field.Expr) *userDo { + return u.withDO(u.DO.Join(table, on...)) +} + +func (u userDo) LeftJoin(table schema.Tabler, on ...field.Expr) *userDo { + return u.withDO(u.DO.LeftJoin(table, on...)) +} + +func (u userDo) RightJoin(table schema.Tabler, on ...field.Expr) *userDo { + return u.withDO(u.DO.RightJoin(table, on...)) +} + +func (u userDo) Group(cols ...field.Expr) *userDo { + return u.withDO(u.DO.Group(cols...)) +} + +func (u userDo) Having(conds ...gen.Condition) *userDo { + return u.withDO(u.DO.Having(conds...)) +} + +func (u userDo) Limit(limit int) *userDo { + return u.withDO(u.DO.Limit(limit)) +} + +func (u userDo) Offset(offset int) *userDo { + return u.withDO(u.DO.Offset(offset)) +} + +func (u userDo) Scopes(funcs ...func(gen.Dao) gen.Dao) *userDo { + return u.withDO(u.DO.Scopes(funcs...)) +} + +func (u userDo) Unscoped() *userDo { + return u.withDO(u.DO.Unscoped()) +} + +func (u userDo) Create(values ...*entity.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Create(values) +} + +func (u userDo) CreateInBatches(values []*entity.User, batchSize int) error { + return u.DO.CreateInBatches(values, batchSize) +} + +// Save : !!! underlying implementation is different with GORM +// The method is equivalent to executing the statement: db.Clauses(clause.OnConflict{UpdateAll: true}).Create(values) +func (u userDo) Save(values ...*entity.User) error { + if len(values) == 0 { + return nil + } + return u.DO.Save(values) +} + +func (u userDo) First() (*entity.User, error) { + if result, err := u.DO.First(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Take() (*entity.User, error) { + if result, err := u.DO.Take(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Last() (*entity.User, error) { + if result, err := u.DO.Last(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) Find() ([]*entity.User, error) { + result, err := u.DO.Find() + return result.([]*entity.User), err +} + +func (u userDo) FindInBatch(batchSize int, fc func(tx gen.Dao, batch int) error) (results []*entity.User, err error) { + buf := make([]*entity.User, 0, batchSize) + err = u.DO.FindInBatches(&buf, batchSize, func(tx gen.Dao, batch int) error { + defer func() { results = append(results, buf...) }() + return fc(tx, batch) + }) + return results, err +} + +func (u userDo) FindInBatches(result *[]*entity.User, batchSize int, fc func(tx gen.Dao, batch int) error) error { + return u.DO.FindInBatches(result, batchSize, fc) +} + +func (u userDo) Attrs(attrs ...field.AssignExpr) *userDo { + return u.withDO(u.DO.Attrs(attrs...)) +} + +func (u userDo) Assign(attrs ...field.AssignExpr) *userDo { + return u.withDO(u.DO.Assign(attrs...)) +} + +func (u userDo) Joins(fields ...field.RelationField) *userDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Joins(_f)) + } + return &u +} + +func (u userDo) Preload(fields ...field.RelationField) *userDo { + for _, _f := range fields { + u = *u.withDO(u.DO.Preload(_f)) + } + return &u +} + +func (u userDo) FirstOrInit() (*entity.User, error) { + if result, err := u.DO.FirstOrInit(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) FirstOrCreate() (*entity.User, error) { + if result, err := u.DO.FirstOrCreate(); err != nil { + return nil, err + } else { + return result.(*entity.User), nil + } +} + +func (u userDo) FindByPage(offset int, limit int) (result []*entity.User, count int64, err error) { + result, err = u.Offset(offset).Limit(limit).Find() + if err != nil { + return + } + + if size := len(result); 0 < limit && 0 < size && size < limit { + count = int64(size + offset) + return + } + + count, err = u.Offset(-1).Limit(-1).Count() + return +} + +func (u userDo) ScanByPage(result interface{}, offset int, limit int) (count int64, err error) { + count, err = u.Count() + if err != nil { + return + } + + err = u.Offset(offset).Limit(limit).Scan(result) + return +} + +func (u userDo) Scan(result interface{}) (err error) { + return u.DO.Scan(result) +} + +func (u userDo) Delete(models ...*entity.User) (result gen.ResultInfo, err error) { + return u.DO.Delete(models) +} + +func (u *userDo) withDO(do gen.Dao) *userDo { + u.DO = *do.(*gen.DO) + return u +} diff --git a/doc/README_ZH.md b/doc/README_ZH.md new file mode 100644 index 00000000..9ad627e5 --- /dev/null +++ b/doc/README_ZH.md @@ -0,0 +1,66 @@ +

+ +

+ +

Sonic [ˈsɒnɪk] ,Sonic 是一个用Golang开发的博客平台,高效快速.

+ +

+GitHub release +GitHub All Releases +Docker pulls +GitHub last commit +GitHub Workflow Status +
+Telegram 频道 +

+ + +## 📖 介绍 + +Sonic 意为声速的、声音的,正如它的名字一样, sonic 致力于成为最快速的开源博客平台。 + +感谢 [Halo](https://github.com/halo-dev/) 项目组,本项目的灵感来自Halo,前端项目Fork自[Console](https://github.com/halo-dev) + +## 🚀 Features: +- 支持多种类型的数据库:SQLite、MySQL(TODO: PostgreSQL) +- 体积小: 安装包仅仅只有10Mb +- 高性能: 文章详情页可以达到1000 QPS(压测环境是: Intel Xeon Platinum 8260 4C 8G ,SQLite3) +- 支持更换主题 +- 支持 Linux、Windows、Mac OS等主流操作系统,支持x86、x64、Arm、Arm64、MIPS等指令集架构 +- 支持对象存储(MINIO、Google Cloud、AWS、AliYun) + + +## 🧰 安装 + +### 下载对应平台的安装包 +> 根据你的操作系统和指令集下载对应的安装包 +```bash +wget https://github.com/go-sonic/sonic/releases/download/v1.0.0/sonic-linux-64.zip -O sonic.zip +``` +### 解压 +```bash +unzip sonic.zip +``` +### 运行 +> 可以通过 -config选项来指定配置文件的位置 +```bash +cd sonic +./sonic -config conf/config.yaml +``` + +**然后你就可以通过浏览器访问sonic了,默认的端口是8080** + +后台管理路径是 http://ip:port/admin + +## TODO +- [ ] i18n +- [ ] PostgreSQL +- [ ] 更好的错误处理 +- [ ] 插件系统(基于 Wasm) +- [ ] 使用新的web框架([Hertz](https://github.com/cloudwego/hertz)) + + +## 📄 License + +Source code in `sonic` is available under the [MIT License](/LICENSE.md). + diff --git a/event/event_bus.go b/event/event_bus.go new file mode 100644 index 00000000..eb84e261 --- /dev/null +++ b/event/event_bus.go @@ -0,0 +1,70 @@ +package event + +import ( + "context" + "reflect" + "sync" + + "go.uber.org/zap" + + "github.com/go-sonic/sonic/log" +) + +type Listener func(ctx context.Context, event Event) error + +type Bus interface { + Publish(ctx context.Context, event Event) + Subscribe(eventType string, listener Listener) + UnSubscribe(eventType string, listener Listener) +} + +type syncLocalBus struct { + listeners sync.Map + logger *zap.Logger +} + +func NewSyncEventBus(logger *zap.Logger) Bus { + return &syncLocalBus{ + logger: logger, + } +} + +func (e *syncLocalBus) Publish(ctx context.Context, event Event) { + defer func() { + if err := recover(); err != nil { + log.CtxError(ctx, "event panic", zap.String("event", event.EventType()), zap.Stack("stack"), zap.Any("err", err)) + } + }() + if listeners, ok := e.listeners.Load(event.EventType()); ok { + for _, listener := range listeners.([]Listener) { + err := listener(ctx, event) + if err != nil { + e.logger.Error("error in event listener", zap.Any("event", event.EventType()), zap.Error(err)) + } + } + } +} + +func (e *syncLocalBus) Subscribe(eventType string, listener Listener) { + if listeners, ok := e.listeners.Load(eventType); ok { + listeners = append(listeners.([]Listener), listener) + e.listeners.Store(eventType, listeners) + } else { + listeners := make([]Listener, 0) + listeners = append(listeners, listener) + e.listeners.Store(eventType, listeners) + } +} + +func (e *syncLocalBus) UnSubscribe(eventType string, listener Listener) { + if listeners, ok := e.listeners.Load(eventType); ok && len(listeners.([]Listener)) > 0 { + target := reflect.ValueOf(listener).Pointer() + var filtered []Listener + for _, i := range listeners.([]Listener) { + if reflect.ValueOf(i).Pointer() != target { + filtered = append(filtered, i) + } + } + e.listeners.Store(eventType, filtered) + } +} diff --git a/event/events.go b/event/events.go new file mode 100644 index 00000000..ef782fb4 --- /dev/null +++ b/event/events.go @@ -0,0 +1,96 @@ +package event + +import ( + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/entity" +) + +type Event interface { + EventType() string +} + +const ( + LogEventName = "LogEvent" + StartEventName = "StartEvent" + UserUpdateEventName = "UserUpdateEvent" + ThemeUpdateEventName = "ThemeUpdateEvent" + OptionUpdateEventName = "OptionUpdateEvent" + ThemeActivatedEventName = "ThemeActivatedEvent" + ThemeFileUpdatedEventName = "ThemeFileUpdatedEvent" + PostUpdateEventName = "PostUpdateEvent" + CommentNewEventName = "CommentNewEvent" + CommentReplyEventName = "CommentReplayEvent" +) + +type LogEvent struct { + LogKey string + LogType consts.LogType + Content string + IpAddress string +} + +func (*LogEvent) EventType() string { + return LogEventName +} + +type StartEvent struct{} + +func (*StartEvent) EventType() string { + return StartEventName +} + +type UserUpdateEvent struct { + UserID int32 +} + +func (*UserUpdateEvent) EventType() string { + return UserUpdateEventName +} + +type ThemeUpdateEvent struct{} + +func (*ThemeUpdateEvent) EventType() string { + return ThemeUpdateEventName +} + +type OptionUpdateEvent struct{} + +func (o *OptionUpdateEvent) EventType() string { + return OptionUpdateEventName +} + +type ThemeActivatedEvent struct{} + +func (t *ThemeActivatedEvent) EventType() string { + return ThemeActivatedEventName +} + +type ThemeFileUpdatedEvent struct{} + +func (t *ThemeFileUpdatedEvent) EventType() string { + return ThemeFileUpdatedEventName +} + +type PostUpdateEvent struct { + PostID int32 +} + +func (p *PostUpdateEvent) EventType() string { + return PostUpdateEventName +} + +type CommentNewEvent struct { + Comment *entity.Comment +} + +func (c *CommentNewEvent) EventType() string { + return CommentNewEventName +} + +type CommentReplyEvent struct { + Comment *entity.Comment +} + +func (c *CommentReplyEvent) EventType() string { + return CommentReplyEventName +} diff --git a/event/listener/comment.go b/event/listener/comment.go new file mode 100644 index 00000000..4b725e52 --- /dev/null +++ b/event/listener/comment.go @@ -0,0 +1,257 @@ +package listener + +import ( + "bytes" + "context" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type CommentListener struct { + OptionService service.OptionService + PostService service.PostService + PostAssembler assembler.PostAssembler + JournalService service.JournalService + SheetService service.SheetService + ThemeService service.ThemeService + EmailService service.EmailService + UserService service.UserService + BaseCommentService service.BaseCommentService + Template *template.Template +} + +func NewCommentListener( + optionService service.OptionService, + postService service.PostService, + journalService service.JournalService, + sheetService service.SheetService, + bus event.Bus, + postAssembler assembler.PostAssembler, + themeService service.ThemeService, + emailService service.EmailService, + userService service.UserService, + template *template.Template, + baseCommentService service.BaseCommentService, +) { + c := &CommentListener{ + OptionService: optionService, + PostService: postService, + PostAssembler: postAssembler, + JournalService: journalService, + SheetService: sheetService, + ThemeService: themeService, + EmailService: emailService, + UserService: userService, + Template: template, + BaseCommentService: baseCommentService, + } + bus.Subscribe(event.CommentNewEventName, c.HandleCommentNew) + bus.Subscribe(event.CommentReplyEventName, c.HandleCommentReply) +} +func (c *CommentListener) HandleCommentNew(ctx context.Context, ce event.Event) error { + newCommentNotice, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.CommentNewNotice, property.CommentNewNotice.DefaultValue) + if err != nil { + return err + } + if !newCommentNotice.(bool) { + return nil + } + commentEvent, ok := ce.(*event.CommentNewEvent) + if !ok { + return nil + } + enabledAbsolutePath, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.GlobalAbsolutePathEnabled, false) + if err != nil { + return err + } + blogBaseURL, err := c.OptionService.GetBlogBaseURL(ctx) + if err != nil { + return err + } + comment := commentEvent.Comment + data := make(map[string]interface{}) + var subject string + + if comment.Type == consts.CommentTypePost || comment.Type == consts.CommentTypeSheet { + post, err := c.PostService.GetByPostID(ctx, commentEvent.Comment.PostID) + if err != nil { + return nil + } + postDTO, err := c.PostAssembler.ConvertToMinimalDTO(ctx, post) + if err != nil { + return nil + } + data["pageFullPath"] = util.IfElse(enabledAbsolutePath.(bool), postDTO.FullPath, blogBaseURL+postDTO.FullPath).(string) + data["pageTitle"] = postDTO.Title + data["author"] = comment.Author + data["content"] = comment.Content + data["email"] = comment.Email + data["status"] = comment.Status + data["createTime"] = comment.CreateTime + data["authorUrl"] = comment.AuthorURL + if comment.Type == consts.CommentTypePost { + subject = "Your blog post 《" + postDTO.Title + "》 has a new comment" + } else { + subject = "Your blog page 《" + postDTO.Title + "》 has a new comment" + } + } else if comment.Type == consts.CommentTypeJournal { + journalPrefix, err := c.OptionService.GetJournalPrefix(ctx) + if err != nil { + return err + } + journals, err := c.JournalService.GetByJournalIDs(ctx, []int32{comment.PostID}) + if err != nil { + return err + } + if len(journals) == 0 { + return nil + } + journal := journals[comment.PostID] + + data["pageFullPath"] = blogBaseURL + "/" + journalPrefix + data["pageTitle"] = journal.CreateTime.Format("2006-01-02 03:04") + data["author"] = comment.Author + data["content"] = comment.Content + data["email"] = comment.Email + data["status"] = comment.Status + data["createTime"] = comment.CreateTime + data["authorUrl"] = comment.AuthorURL + subject = "Your blog journal has a new comment" + } + template := "common/mail_template/mail_notice" + if exist, err := c.ThemeService.TemplateExist(ctx, "mail_template/mail_notice.tmpl"); err != nil && exist { + t, err := c.ThemeService.Render(ctx, "mail_template/mail_notice") + if err == nil { + template = t + } + } + content := bytes.Buffer{} + err = c.Template.ExecuteTemplate(&content, template, data) + if err != nil { + return err + } + users, err := c.UserService.GetAllUser(ctx) + if err != nil { + return err + } + return c.EmailService.SendTemplateEmail(ctx, users[0].Email, subject, content.String()) +} +func (c *CommentListener) HandleCommentReply(ctx context.Context, ce event.Event) error { + commentReplyNotice, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.CommentReplyNotice, property.CommentNewNotice.DefaultValue) + if err != nil { + return err + } + if !commentReplyNotice.(bool) { + return nil + } + commentEvent, ok := ce.(*event.CommentReplyEvent) + if !ok { + return nil + } + + comment := commentEvent.Comment + if comment.Status != consts.CommentStatusPublished { + return nil + } + parentComment, err := c.BaseCommentService.GetByID(ctx, comment.ParentID) + if err != nil { + return err + } + if !parentComment.AllowNotification || parentComment.Status != consts.CommentStatusPublished { + return nil + } + + blogTitle, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.BlogTitle, "") + if err != nil { + return err + } + enabledAbsolutePath, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.GlobalAbsolutePathEnabled, false) + if err != nil { + return err + } + blogBaseURL, err := c.OptionService.GetBlogBaseURL(ctx) + if err != nil { + return err + } + + data := make(map[string]interface{}) + var subject string + if comment.Type == consts.CommentTypePost || comment.Type == consts.CommentTypeSheet { + post, err := c.PostService.GetByPostID(ctx, commentEvent.Comment.PostID) + if err != nil { + return nil + } + postDTO, err := c.PostAssembler.ConvertToMinimalDTO(ctx, post) + if err != nil { + return nil + } + data["pageFullPath"] = util.IfElse(enabledAbsolutePath.(bool), postDTO.FullPath, blogBaseURL+postDTO.FullPath).(string) + data["pageTitle"] = postDTO.Title + data["baseAuthor"] = parentComment.Author + data["baseContent"] = parentComment.Content + data["replyAuthor"] = comment.Author + data["replyContent"] = comment.Content + data["baseAuthorEmail"] = parentComment.Email + data["replyAuthorEmail"] = comment.Email + data["status"] = comment.Status + data["createTime"] = comment.CreateTime + data["authorUrl"] = comment.AuthorURL + if comment.Type == consts.CommentTypePost { + subject = "You have a new reply in the 《" + post.Title + "》 article you comment on " + blogTitle.(string) + } else { + subject = "You have a new reply in the 《" + post.Title + "》 page you comment on " + blogTitle.(string) + } + } else if comment.Type == consts.CommentTypeJournal { + blogBaseURL, err := c.OptionService.GetBlogBaseURL(ctx) + if err != nil { + return err + } + journalPrefix, err := c.OptionService.GetJournalPrefix(ctx) + if err != nil { + return err + } + journals, err := c.JournalService.GetByJournalIDs(ctx, []int32{comment.PostID}) + if err != nil { + return err + } + if len(journals) == 0 { + return nil + } + journal := journals[comment.PostID] + data["pageFullPath"] = blogBaseURL + "/" + journalPrefix + data["pageTitle"] = journal.CreateTime.Format("2006-01-02 03:04") + data["baseAuthor"] = parentComment.Author + data["baseContent"] = parentComment.Content + data["replyAuthor"] = comment.Author + data["replyContent"] = comment.Content + data["baseAuthorEmail"] = parentComment.Email + data["replyAuthorEmail"] = comment.Email + data["status"] = comment.Status + data["createTime"] = comment.CreateTime + data["authorUrl"] = comment.AuthorURL + subject = "You have a new reply in the journal page you comment on " + blogTitle.(string) + } + template := "common/mail_template/mail_reply" + if exist, err := c.ThemeService.TemplateExist(ctx, "mail_template/mail_reply.tmpl"); err != nil && exist { + t, err := c.ThemeService.Render(ctx, "mail_template/mail_reply") + if err == nil { + template = t + } + } + content := bytes.Buffer{} + err = c.Template.ExecuteTemplate(&content, template, data) + if err != nil { + return err + } + users, err := c.UserService.GetAllUser(ctx) + if err != nil { + return err + } + return c.EmailService.SendTemplateEmail(ctx, users[0].Email, subject, content.String()) +} diff --git a/event/listener/log.go b/event/listener/log.go new file mode 100644 index 00000000..74b35a6e --- /dev/null +++ b/event/listener/log.go @@ -0,0 +1,37 @@ +package listener + +import ( + "context" + + "gorm.io/gorm" + + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/model/entity" +) + +type LogEventListener struct { + db *gorm.DB +} + +func NewLogEventListener(db *gorm.DB, bus event.Bus) { + l := &LogEventListener{ + db: db, + } + bus.Subscribe(event.LogEventName, l.HandleEvent) +} + +func (l *LogEventListener) HandleEvent(ctx context.Context, logEvent event.Event) error { + log, ok := logEvent.(*event.LogEvent) + if !ok { + return nil + } + logDAL := dal.Use(dal.GetDBByCtx(ctx)).Log + logEntity := &entity.Log{ + Content: log.Content, + IPAddress: log.IpAddress, + LogKey: log.LogKey, + Type: log.LogType, + } + return logDAL.WithContext(ctx).Create(logEntity) +} diff --git a/event/listener/post_update.go b/event/listener/post_update.go new file mode 100644 index 00000000..374d2519 --- /dev/null +++ b/event/listener/post_update.go @@ -0,0 +1,61 @@ +package listener + +import ( + "context" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/service" +) + +type PostUpdateListener struct { + CategoryService service.CategoryService + PostCategoryService service.PostCategoryService + PostService service.PostService +} + +func NewPostUpdateListener(bus event.Bus, + categoryService service.CategoryService, + postCategoryService service.PostCategoryService, + postService service.PostService, +) { + p := &PostUpdateListener{ + PostCategoryService: postCategoryService, + PostService: postService, + CategoryService: categoryService, + } + + bus.Subscribe(event.PostUpdateEventName, p.HandlePostUpdateEvent) +} + +func (p *PostUpdateListener) HandlePostUpdateEvent(ctx context.Context, postUpdateEvent event.Event) error { + + postID := postUpdateEvent.(*event.PostUpdateEvent).PostID + + categories, err := p.PostCategoryService.ListCategoryByPostID(ctx, postID) + if err != nil { + return err + } + + postStatus := consts.PostStatusPublished + for _, category := range categories { + if category.Type == consts.CategoryTypeIntimate { + postStatus = consts.PostStatusIntimate + } + } + post, err := p.PostService.GetByPostID(ctx, postID) + if err != nil { + return err + } + if post.Status == consts.PostStatusRecycle || post.Status == consts.PostStatusDraft { + return nil + } + if post.Password != "" { + postStatus = consts.PostStatusIntimate + } + if post.Status == postStatus { + return nil + } + _, err = p.PostService.UpdateStatus(ctx, postID, postStatus) + return err +} diff --git a/event/listener/start.go b/event/listener/start.go new file mode 100644 index 00000000..f4e223f1 --- /dev/null +++ b/event/listener/start.go @@ -0,0 +1,91 @@ +package listener + +import ( + "context" + "time" + + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/log" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" +) + +type StartListener struct { + db *gorm.DB + optionService service.OptionService + bus event.Bus +} + +func NewStartListener(db *gorm.DB, optionService service.OptionService, bus event.Bus) { + s := StartListener{ + db: db, + optionService: optionService, + bus: bus, + } + bus.Subscribe(event.StartEventName, s.HandleEvent) +} + +func (s *StartListener) HandleEvent(ctx context.Context, startEvent event.Event) error { + if _, ok := startEvent.(*event.StartEvent); !ok { + return nil + } + consts.StartTime = time.Now() + + err := s.createOptions() + if err != nil { + log.Error("create options err", zap.Error(err)) + } + if dal.DBType == consts.DBTypeMySQL { + err = dal.DB.Session(&gorm.Session{Context: ctx}).Raw("SELECT VERSION()").Scan(&consts.DatabaseVersion).Error + } else if dal.DBType == consts.DBTypeSQLite { + err = dal.DB.Session(&gorm.Session{Context: ctx}).Raw("SELECT SQLITE_VERSION()").Scan(&consts.DatabaseVersion).Error + } + if err != nil { + return err + } + _ = s.printStartInfo(ctx) + return nil +} + +func (s *StartListener) createOptions() error { + ctx := context.Background() + ctx = dal.SetCtxDB(ctx, dal.GetDBByCtx(ctx).Session(&gorm.Session{ + Logger: dal.DB.Logger.LogMode(logger.Warn), + })) + + optionDAL := dal.Use(dal.GetDBByCtx(ctx)).Option + options, err := optionDAL.WithContext(ctx).Find() + if err != nil { + return err + } + toCreate := make([]*entity.Option, 0) +out: + for _, p := range property.AllProperty { + for _, o := range options { + if p.KeyValue == o.OptionKey { + continue out + } + } + toCreate = append(toCreate, p.ConvertToOption()) + } + return optionDAL.WithContext(ctx).Create(toCreate...) +} + +func (s *StartListener) printStartInfo(ctx context.Context) error { + blogURL, err := s.optionService.GetBlogBaseURL(ctx) + if err != nil { + return err + } + site := logger.BlueBold + "Sonic started at " + blogURL + logger.Reset + log.Info(site) + adminSite := logger.BlueBold + "Sonic admin started at " + blogURL + "/admin" + logger.Reset + log.Info(adminSite) + return nil +} diff --git a/event/listener/template_config.go b/event/listener/template_config.go new file mode 100644 index 00000000..361fa969 --- /dev/null +++ b/event/listener/template_config.go @@ -0,0 +1,207 @@ +package listener + +import ( + "context" + "path/filepath" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/handler" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" +) + +type TemplateConfigListener struct { + Template *template.Template + ThemeService service.ThemeService + OptionService service.ClientOptionService + UserService service.UserService + Logger *zap.Logger + Config *config.Config + Router *gin.Engine +} + +func NewTemplateConfigListener(bus event.Bus, + template *template.Template, + themeService service.ThemeService, + optionService service.ClientOptionService, + logger *zap.Logger, + userService service.UserService, + config *config.Config, server *handler.Server, +) { + t := &TemplateConfigListener{ + Template: template, + ThemeService: themeService, + OptionService: optionService, + Logger: logger, + UserService: userService, + Config: config, + Router: server.Router, + } + bus.Subscribe(event.ThemeUpdateEventName, t.HandleThemeUpdateEvent) + bus.Subscribe(event.UserUpdateEventName, t.HandleUserUpdateEvent) + bus.Subscribe(event.OptionUpdateEventName, t.HandleOptionUpdateEvent) + bus.Subscribe(event.StartEventName, t.HandleStartEvent) + bus.Subscribe(event.ThemeActivatedEventName, t.HandleThemeUpdateEvent) +} + +func (t *TemplateConfigListener) HandleThemeUpdateEvent(ctx context.Context, themeUpdateEvent event.Event) error { + return t.loadThemeConfig(ctx) +} + +func (t *TemplateConfigListener) HandleUserUpdateEvent(ctx context.Context, userUpdateEvent event.Event) error { + return t.loadUser(ctx) +} + +func (t *TemplateConfigListener) HandleOptionUpdateEvent(ctx context.Context, optionUpdateEvent event.Event) error { + return t.loadOption(ctx) +} + +func (t *TemplateConfigListener) HandleStartEvent(ctx context.Context, startEvent event.Event) error { + ctx = dal.SetCtxDB(ctx, dal.GetDBByCtx(ctx).Session(&gorm.Session{ + Logger: dal.DB.Logger.LogMode(logger.Warn), + })) + err := t.loadThemeConfig(ctx) + if err != nil { + return err + } + err = t.loadUser(ctx) + if err != nil { + return err + } + err = t.loadOption(ctx) + if err != nil { + return err + } + err = t.loadThemeTemplate(ctx) + if err != nil { + return err + } + err = t.registerTemplateStaticFileRoute(ctx) + return err +} + +func (t *TemplateConfigListener) HandleThemeFileUpdateEvent(ctx context.Context, themeFileUpdateEvent event.Event) error { + return t.loadThemeTemplate(ctx) +} + +func (t *TemplateConfigListener) loadThemeTemplate(ctx context.Context) error { + theme, err := t.ThemeService.GetActivateTheme(ctx) + if err != nil { + return err + } + err = t.Template.Load([]string{filepath.Join(t.Config.Sonic.TemplateDir, "common"), theme.ThemePath}) + return err +} + +func (t *TemplateConfigListener) loadThemeConfig(ctx context.Context) error { + theme, err := t.ThemeService.GetActivateTheme(ctx) + if err != nil { + return nil + } + isEnabledAbsolutePath, err := t.OptionService.IsEnabledAbsolutePath(ctx) + if err != nil { + return err + } + blogBaseURL, err := t.OptionService.GetBlogBaseURL(ctx) + if err != nil { + return err + } + themeBasePath := "" + if isEnabledAbsolutePath { + themeBasePath = blogBaseURL + "/themes/" + theme.FolderName + } else { + themeBasePath = "/themes/" + theme.FolderName + } + themeSetting, err := t.ThemeService.GetThemeSettingMap(ctx, theme.ID) + if err != nil { + return err + } + + t.Template.SetSharedVariable("theme_base", themeBasePath) + t.Template.SetSharedVariable("theme", theme) + t.Template.SetSharedVariable("settings", themeSetting) + t.Logger.Debug("load theme success", zap.String("theme", theme.Name)) + return nil +} + +func (t *TemplateConfigListener) loadUser(ctx context.Context) error { + users, err := t.UserService.GetAllUser(ctx) + if err != nil { + return err + } + if len(users) == 0 { + return nil + } + user := users[0] + user.Password = "" + user.MfaKey = "" + user.MfaType = consts.MFANone + t.Template.SetSharedVariable("user", user) + t.Logger.Debug("load user success", zap.Any("user", user)) + return nil +} + +func (t *TemplateConfigListener) loadOption(ctx context.Context) error { + options, err := t.OptionService.ListAllOption(ctx) + if err != nil { + return err + } + optionMap := make(map[string]interface{}) + for _, option := range options { + optionMap[option.Key] = option.Value + } + blogBaseURL := t.OptionService.GetOrByDefault(ctx, property.BlogUrl) + blogTitle := t.OptionService.GetOrByDefault(ctx, property.BlogTitle) + blogLogo := t.OptionService.GetOrByDefault(ctx, property.BlogLogo) + globalAbsolutePathEnabled := t.OptionService.GetOrByDefault(ctx, property.GlobalAbsolutePathEnabled) + seoKeywords := t.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + seoDescription := t.OptionService.GetOrByDefault(ctx, property.SeoDescription) + journalPrefix := t.OptionService.GetOrByDefault(ctx, property.JournalsPrefix) + archivePrefix := t.OptionService.GetOrByDefault(ctx, property.ArchivesPrefix) + categoryPrefix := t.OptionService.GetOrByDefault(ctx, property.CategoriesPrefix) + tagPrefix := t.OptionService.GetOrByDefault(ctx, property.TagsPrefix) + linkPrefix := t.OptionService.GetOrByDefault(ctx, property.LinksPrefix) + photoPrefix := t.OptionService.GetOrByDefault(ctx, property.PhotosPrefix) + urlContext := "/" + if globalAbsolutePathEnabled.(bool) { + urlContext = blogBaseURL.(string) + "/" + } + t.Template.SetSharedVariable("version", consts.SonicVersion) + t.Template.SetSharedVariable("options", optionMap) + t.Template.SetSharedVariable("context", urlContext) + t.Template.SetSharedVariable("globalAbsolutePathEnabled", globalAbsolutePathEnabled.(bool)) + t.Template.SetSharedVariable("blog_title", blogTitle) + t.Template.SetSharedVariable("blog_logo", blogLogo) + t.Template.SetSharedVariable("blog_url", blogBaseURL) + t.Template.SetSharedVariable("seo_keywords", seoKeywords) + t.Template.SetSharedVariable("seo_description", seoDescription) + t.Template.SetSharedVariable("rss_url", blogBaseURL.(string)+"/rss.xml") + t.Template.SetSharedVariable("atom_url", blogBaseURL.(string)+"/atom.xml") + t.Template.SetSharedVariable("sitemap_xml_url", blogBaseURL.(string)+"/sitemap.xml") + t.Template.SetSharedVariable("sitemap_html_url", blogBaseURL.(string)+"/sitemap.html") + t.Template.SetSharedVariable("links_url", urlContext+linkPrefix.(string)) + t.Template.SetSharedVariable("photos_url", urlContext+photoPrefix.(string)) + t.Template.SetSharedVariable("journals_url", urlContext+journalPrefix.(string)) + t.Template.SetSharedVariable("archives_url", urlContext+archivePrefix.(string)) + t.Template.SetSharedVariable("categories_url", urlContext+categoryPrefix.(string)) + t.Template.SetSharedVariable("tags_url", urlContext+tagPrefix.(string)) + return nil +} + +func (t *TemplateConfigListener) registerTemplateStaticFileRoute(ctx context.Context) error { + theme, err := t.ThemeService.GetActivateTheme(ctx) + if err != nil { + return nil + } + t.Router.StaticFS("/themes/"+theme.FolderName, gin.Dir(theme.ThemePath, false)) + return nil +} diff --git a/generate.sh b/generate.sh new file mode 100755 index 00000000..9ff6b81d --- /dev/null +++ b/generate.sh @@ -0,0 +1,7 @@ + +GENERATE_DIR="./cmd/generate" + +cd $GENERATE_DIR || exit + +echo "Start Generating" +go run . diff --git a/go.mod b/go.mod new file mode 100644 index 00000000..0658c754 --- /dev/null +++ b/go.mod @@ -0,0 +1,93 @@ +module github.com/go-sonic/sonic + +go 1.19 + +require ( + github.com/Masterminds/sprig/v3 v3.2.2 + github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible + github.com/disintegration/imaging v1.6.2 + github.com/fsnotify/fsnotify v1.6.0 + github.com/gin-contrib/cors v1.4.0 + github.com/gin-gonic/gin v1.8.1 + github.com/go-playground/locales v0.14.0 + github.com/go-playground/universal-translator v0.18.0 + github.com/go-playground/validator/v10 v10.11.1 + github.com/golang-jwt/jwt v3.2.2+incompatible + github.com/google/uuid v1.3.0 + github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible + github.com/minio/minio-go/v7 v7.0.42 + github.com/natefinch/lumberjack v2.0.0+incompatible + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pkg/errors v0.9.1 + github.com/pquerna/otp v1.3.0 + github.com/spf13/viper v1.13.0 + github.com/yeqown/go-qrcode v1.5.10 + go.uber.org/dig v1.15.0 + go.uber.org/fx v1.18.2 + go.uber.org/zap v1.23.0 + golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b + gopkg.in/yaml.v2 v2.4.0 + gorm.io/driver/mysql v1.4.3 + gorm.io/driver/sqlite v1.4.3 + gorm.io/gen v0.3.17 + gorm.io/gorm v1.24.0 + gorm.io/plugin/dbresolver v1.3.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fogleman/gg v1.3.0 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-sql-driver/mysql v1.6.0 // indirect + github.com/goccy/go-json v0.9.7 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huandu/xstrings v1.3.1 // indirect + github.com/imdario/mergo v0.3.11 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/klauspost/cpuid/v2 v2.1.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mattn/go-sqlite3 v1.14.15 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/sha256-simd v1.0.0 // indirect + github.com/mitchellh/copystructure v1.0.0 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.5 // indirect + github.com/rs/xid v1.4.0 // indirect + github.com/shopspring/decimal v1.2.0 // indirect + github.com/sirupsen/logrus v1.9.0 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.4.1 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + github.com/yeqown/reedsolomon v1.0.0 // indirect + go.uber.org/atomic v1.7.0 // indirect + go.uber.org/multierr v1.6.0 // indirect + golang.org/x/image v0.0.0-20211028202545-6944b10bf410 // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect + golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 // indirect + golang.org/x/text v0.3.7 // indirect + golang.org/x/time v0.0.0-20191024005414-555d28b269f0 // indirect + golang.org/x/tools v0.1.12 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.0.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/datatypes v1.0.7 // indirect + gorm.io/hints v1.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 00000000..c483ceb4 --- /dev/null +++ b/go.sum @@ -0,0 +1,817 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible h1:QoRMR0TCctLDqBCMyOu1eXdZyMw3F7uGA9qPn2J4+R8= +github.com/aliyun/aliyun-oss-go-sdk v2.2.5+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc h1:biVzkmvwrH8WK8raXaxBx6fRVTlJILwEwQGL1I/ByEI= +github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc/go.mod h1:paBWMcWSl3LHKBqUq+rly7CNSldXjb2rDl3JlRe0mD8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.12.0/go.mod h1:iiK0YP1ZeepvmBQk/QpLEhhTNJgfzrpArPY/aFvc9yU= +github.com/denisenkom/go-mssqldb v0.12.2 h1:1OcPn5GBIobjWNd+8yjfHNIaFX14B1pWI3F9HZy5KXw= +github.com/denisenkom/go-mssqldb v0.12.2/go.mod h1:lnIw1mZukFRZDJYQ0Pb833QS2IaC3l5HkEfra2LJ+sk= +github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c= +github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8= +github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= +github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/gin-contrib/cors v1.4.0 h1:oJ6gwtUl3lqV0WEIwM/LxPF1QZ5qe2lGWdY2+bz7y0g= +github.com/gin-contrib/cors v1.4.0/go.mod h1:bs9pNM0x/UsmHPBWT2xZz9ROh8xYjYkiURUfmBoMlcs= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.10.0/go.mod h1:74x4gJWsvQexRdW8Pn3dXSGrTK4nAUsbPlLADvpJkos= +github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= +github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/goccy/go-json v0.9.7 h1:IcB+Aqpx/iMHu5Yooh7jEzJk1JZ7Pjtmys2ukPr7EeM= +github.com/goccy/go-json v0.9.7/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.0.0-20170517235910-f1bb20e5a188/go.mod h1:vXjM/+wXQnTPR4KqTKDgJukSZ6amVRtWMPEjE6sQoK8= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/huandu/xstrings v1.3.1 h1:4jgBlKK6tLKFvO8u5pmYjG91cqytmDCDvGh7ECVFfFs= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11 h1:3tnifQM4i+fbajXKBHXWEH+KvNHqojZ778UH75j3bGA= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.11.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.10.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= +github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.15.0/go.mod h1:D/zyOyXiaM1TmVWnOM18p0xdDtdakRBa0RsVGI3U3bw= +github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.1/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible h1:jdpOPRN1zP63Td1hDQbZW73xKmzDvZHzVdNYxhnTMDA= +github.com/jordan-wright/email v4.0.1-0.20210109023952-943e75fe5223+incompatible/go.mod h1:1c7szIrayyPPB/987hsnvNzLushdWf4o/79s3P08L8A= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.4/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.1.0 h1:eyi1Ad2aNJMW95zcSbmGg7Cg6cq3ADwLpMAP96d8rF0= +github.com/klauspost/cpuid/v2 v2.1.0/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.12/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.42 h1:fP56plNR/Tkw/+Xczw9NL5TGxe5gJDvgd8LidNR3BEI= +github.com/minio/minio-go/v7 v7.0.42/go.mod h1:nCrRzjoSUQh8hgKKtu3Y708OLvRLtuASMg2/nvmbarw= +github.com/minio/sha256-simd v1.0.0 h1:v1ta+49hkWZyvaKwrQB8elexRqm6Y0aMLjCNsrYxo6g= +github.com/minio/sha256-simd v1.0.0/go.mod h1:OuYzVNI5vcoYIAmbIvHPl3N3jUzVedXbKy5RFepssQM= +github.com/mitchellh/copystructure v1.0.0 h1:Laisrj+bAB6b/yJwB5Bt3ITZhGJdqmxquMKeZ+mmkFQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0 h1:9D+8oIskB4VJBN5SFlmc27fSlIBZaov1Wpk/IfikLNY= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/natefinch/lumberjack v2.0.0+incompatible h1:4QJd3OLAMgj7ph+yZTuX13Ld4UpgHp07nNdFX7mqFfM= +github.com/natefinch/lumberjack v2.0.0+incompatible/go.mod h1:Wi9p2TTF5DG5oU+6YfsmYQpsTIOm0B1VNzQg9Mw6nPk= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo= +github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= +github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/otp v1.3.0 h1:oJV/SkzR33anKXwQU3Of42rL4wbrffP4uvUf1SvS5Xs= +github.com/pquerna/otp v1.3.0/go.mod h1:dkJfzwRKNiegxyNb54X/3fLwhCynbMspSyWKnvi1AEg= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/xid v1.4.0 h1:qd7wPTDkN6KQx2VmMBLrpHkiyQwgFXRnkOLacUiaSNY= +github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= +github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.13.0 h1:BWSJ/M+f+3nmdz9bxB+bWX28kkALN2ok11D0rSo8EJU= +github.com/spf13/viper v1.13.0/go.mod h1:Icm2xNL3/8uyh/wFuB1jI7TiTNKp8632Nwegu+zgdYw= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= +github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/yeqown/go-qrcode v1.5.10 h1:87GCtypY9oOadB7yGRW4qlgAoDOop8G4JEdqOQwu1WI= +github.com/yeqown/go-qrcode v1.5.10/go.mod h1:0FVyJ3MV9fF5lfAgTr0INcy+3rupmJhjp0mL3Z9eYXk= +github.com/yeqown/reedsolomon v1.0.0 h1:x1h/Ej/uJnNu8jaX7GLHBWmZKCAWjEJTetkqaabr4B0= +github.com/yeqown/reedsolomon v1.0.0/go.mod h1:P76zpcn2TCuL0ul1Fso373qHRc69LKwAw/Iy6g1WiiM= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/dig v1.15.0 h1:vq3YWr8zRj1eFGC7Gvf907hE0eRjPTZ1d3xHadD6liE= +go.uber.org/dig v1.15.0/go.mod h1:pKHs0wMynzL6brANhB2hLMro+zalv1osARTviTcqHLM= +go.uber.org/fx v1.18.2 h1:bUNI6oShr+OVFQeU8cDNbnN7VFsu+SsjHzUF51V/GAU= +go.uber.org/fx v1.18.2/go.mod h1:g0V1KMQ66zIRk8bLu3Ea5Jt2w/cHlOIp4wdRsgh0JaY= +go.uber.org/goleak v1.1.11 h1:wy28qYRKZgnJTxGxvye5/wgWr1EKjmUDGYox5mGlRlI= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.uber.org/zap v1.23.0 h1:OjGQ5KQDEUawVHxNwQgPpiypGHOxo2mNZsOqTak4fFY= +go.uber.org/zap v1.23.0/go.mod h1:D+nX8jyLsMHMYrln8A0rJjFt/T/9/bGgIhAqxv5URuY= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220214200702-86341886e292/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b h1:huxqepDufQpLLIRXiVkTvnxrzJlpwmIWAObmcCcUFr0= +golang.org/x/crypto v0.0.0-20221005025214-4161e89ecf1b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410 h1:hTftEOvwiOq2+O8k2D5/Q7COC7k5Qcrgc2TFURJYnvQ= +golang.org/x/image v0.0.0-20211028202545-6944b10bf410/go.mod h1:023OzeP/+EPmXeapQh35lcL3II3LrY8Ic+EFFKVhULM= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875 h1:AzgQNqF+FKwyQ5LbVrVqOcuuFB67N47F9+htZYH0wFM= +golang.org/x/sys v0.0.0-20221006211917-84dc82d7e875/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0 h1:/5xXl8Y5W96D+TtHSlonuFqGHIWVuyCkGJLwGh9JJFs= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0 h1:1Lc07Kr7qY4U2YPouBjpCLxpiyxIVoxqXgkXLknAOE8= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/datatypes v1.0.7 h1:8NhJN4+annFjwV1WufDhFiPjdUvV1lSGUdg1UCjQIWY= +gorm.io/datatypes v1.0.7/go.mod h1:l9qkCuy0CdzDEop9HKUdcnC9gHC2sRlaFtHkTzsZRqg= +gorm.io/driver/mysql v1.3.2/go.mod h1:ChK6AHbHgDCFZyJp0F+BmVGb06PSIoh9uVYKAlRbb2U= +gorm.io/driver/mysql v1.4.0/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/mysql v1.4.3 h1:/JhWJhO2v17d8hjApTltKNADm7K7YI2ogkR7avJUL3k= +gorm.io/driver/mysql v1.4.3/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/postgres v1.3.4/go.mod h1:y0vEuInFKJtijuSGu9e5bs5hzzSzPK+LancpKpvbRBw= +gorm.io/driver/postgres v1.4.1 h1:DutsKq2LK2Ag65q/+VygWth0/L4GAVOp+sCtg6WzZjs= +gorm.io/driver/postgres v1.4.1/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= +gorm.io/driver/sqlite v1.1.6/go.mod h1:W8LmC/6UvVbHKah0+QOC7Ja66EaZXHwUTjgXY8YNWX8= +gorm.io/driver/sqlite v1.3.1/go.mod h1:wJx0hJspfycZ6myN38x1O/AqLtNS6c5o9TndewFbELg= +gorm.io/driver/sqlite v1.4.1/go.mod h1:AKZZCAoFfOWHF7Nd685Iq8Uywc0i9sWJlzpoE/INzsw= +gorm.io/driver/sqlite v1.4.3 h1:HBBcZSDnWi5BW3B3rwvVTc510KGkBkexlOg0QrmLUuU= +gorm.io/driver/sqlite v1.4.3/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/driver/sqlserver v1.3.1/go.mod h1:w25Vrx2BG+CJNUu/xKbFhaKlGxT/nzRkhWCCoptX8tQ= +gorm.io/driver/sqlserver v1.4.0 h1:3fjbsNkr/YqocSBW5CP16Lq6+APjRrWMzu7NbkXr9QU= +gorm.io/driver/sqlserver v1.4.0/go.mod h1:P8BSbBwkdzXURYx3pWUSEAABRQU0vxbd6xk5+53pg7g= +gorm.io/gen v0.3.17 h1:vIgdvpBnXd3c1HOc7chuWlj0cM6w6ZimGZUDada6nwI= +gorm.io/gen v0.3.17/go.mod h1:uQsfditHYOjbibLcsXkFqSIfBmRFWwucMEa2WrcqX8k= +gorm.io/gorm v1.21.15/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.23.1/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.4/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.6/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.0 h1:j/CoiSm6xpRpmzbFJsQHYj+I8bGYWLXVHeYEyyKlF74= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/hints v1.1.0 h1:Lp4z3rxREufSdxn4qmkK3TLDltrM10FLTHiuqwDPvXw= +gorm.io/hints v1.1.0/go.mod h1:lKQ0JjySsPBj3uslFzY3JhYDtqEwzm+G1hv8rWujB6Y= +gorm.io/plugin/dbresolver v1.3.0 h1:uFDX3bIuH9Lhj5LY2oyqR/bU6pqWuDgas35NAPF4X3M= +gorm.io/plugin/dbresolver v1.3.0/go.mod h1:Pr7p5+JFlgDaiM6sOrli5olekJD16YRunMyA2S7ZfKk= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/handler/admin/admin.go b/handler/admin/admin.go new file mode 100644 index 00000000..573d1d60 --- /dev/null +++ b/handler/admin/admin.go @@ -0,0 +1,100 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type AdminHandler struct { + OptionService service.OptionService + AdminService service.AdminService + TwoFactorMFAService service.TwoFactorTOTPMFAService +} + +func NewAdminHandler(optionService service.OptionService, adminService service.AdminService, twoFactorMFA service.TwoFactorTOTPMFAService) *AdminHandler { + return &AdminHandler{ + OptionService: optionService, + AdminService: adminService, + TwoFactorMFAService: twoFactorMFA, + } +} + +func (a *AdminHandler) IsInstalled(ctx *gin.Context) (interface{}, error) { + return a.OptionService.GetOrByDefaultWithErr(ctx, property.IsInstalled, false) +} + +func (a *AdminHandler) AuthPreCheck(ctx *gin.Context) (interface{}, error) { + var loginParam param.LoginParam + err := ctx.ShouldBindJSON(&loginParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.BadParam.Wrapf(err, "") + } + + user, err := a.AdminService.Authenticate(ctx, loginParam) + if err != nil { + return nil, err + } + return &dto.LoginPreCheckDTO{NeedMFACode: a.TwoFactorMFAService.UseMFA(user.MfaType)}, nil +} + +func (a *AdminHandler) Auth(ctx *gin.Context) (interface{}, error) { + var loginParam param.LoginParam + err := ctx.ShouldBindJSON(&loginParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.BadParam.Wrapf(err, "").WithStatus(xerr.StatusBadRequest) + } + + return a.AdminService.Auth(ctx, loginParam) +} + +func (a *AdminHandler) LogOut(ctx *gin.Context) (interface{}, error) { + err := a.AdminService.ClearToken(ctx) + return nil, err +} + +func (a *AdminHandler) SendResetCode(ctx *gin.Context) (interface{}, error) { + var resetPasswordParam param.ResetPasswordParam + err := ctx.ShouldBindJSON(&resetPasswordParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.BadParam.Wrapf(err, "").WithStatus(xerr.StatusBadRequest) + } + return nil, a.AdminService.SendResetPasswordCode(ctx, resetPasswordParam) +} + +func (a *AdminHandler) RefreshToken(ctx *gin.Context) (interface{}, error) { + refreshToken := ctx.Param("refreshToken") + if refreshToken == "" { + return nil, xerr.BadParam.New("refreshToken参数为空").WithStatus(xerr.StatusBadRequest). + WithMsg("refreshToken 参数不能为空") + } + return a.AdminService.RefreshToken(ctx, refreshToken) +} + +func (a *AdminHandler) GetEnvironments(ctx *gin.Context) (interface{}, error) { + return a.AdminService.GetEnvironments(ctx), nil +} + +func (a *AdminHandler) GetLogFiles(ctx *gin.Context) (interface{}, error) { + lines, err := util.MustGetQueryInt64(ctx, "lines") + if err != nil { + return nil, err + } + return a.AdminService.GetLogFiles(ctx, lines) +} diff --git a/handler/admin/attachment.go b/handler/admin/attachment.go new file mode 100644 index 00000000..1a96f25f --- /dev/null +++ b/handler/admin/attachment.go @@ -0,0 +1,118 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type AttachmentHandler struct { + AttachmentService service.AttachmentService +} + +func NewAttachmentHandler(attachmentService service.AttachmentService) *AttachmentHandler { + return &AttachmentHandler{ + AttachmentService: attachmentService, + } +} + +func (a *AttachmentHandler) QueryAttachment(ctx *gin.Context) (interface{}, error) { + queryParam := ¶m.AttachmentQuery{} + err := ctx.ShouldBindWith(queryParam, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("param error ") + } + attachments, totalCount, err := a.AttachmentService.Page(ctx, queryParam) + if err != nil { + return nil, err + } + attachmentDTOs, err := a.AttachmentService.ConvertToDTOs(ctx, attachments) + if err != nil { + return nil, err + } + return dto.NewPage(attachmentDTOs, totalCount, queryParam.Page), nil +} + +func (a *AttachmentHandler) GetAttachmentByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + if id < 0 { + return nil, xerr.BadParam.New("id < 0").WithStatus(xerr.StatusBadRequest).WithMsg("param error") + } + return a.AttachmentService.GetAttachment(ctx, id) +} + +func (a *AttachmentHandler) UploadAttachment(ctx *gin.Context) (interface{}, error) { + fileHeader, err := ctx.FormFile("file") + if err != nil { + return nil, xerr.WithMsg(err, "上传文件错误").WithStatus(xerr.StatusBadRequest) + } + return a.AttachmentService.Upload(ctx, fileHeader) +} + +func (a *AttachmentHandler) UploadAttachments(ctx *gin.Context) (interface{}, error) { + form, _ := ctx.MultipartForm() + if len(form.File) == 0 { + return nil, xerr.BadParam.New("empty files").WithStatus(xerr.StatusBadRequest).WithMsg("empty files") + } + files := form.File["files"] + attachmentDTOs := make([]*dto.AttachmentDTO, 0) + for _, file := range files { + attachment, err := a.AttachmentService.Upload(ctx, file) + if err != nil { + return nil, err + } + attachmentDTOs = append(attachmentDTOs, attachment) + } + return attachmentDTOs, nil +} + +func (a *AttachmentHandler) UpdateAttachment(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + + updateParam := ¶m.AttachmentUpdate{} + err = ctx.ShouldBind(updateParam) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("param error ") + } + return a.AttachmentService.Update(ctx, id, updateParam) +} + +func (a *AttachmentHandler) DeleteAttachment(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return a.AttachmentService.Delete(ctx, id) +} + +func (a *AttachmentHandler) DeleteAttachmentInBatch(ctx *gin.Context) (interface{}, error) { + ids := make([]int32, 0) + err := ctx.ShouldBind(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + return a.AttachmentService.DeleteBatch(ctx, ids) +} + +func (a *AttachmentHandler) GetAllMediaType(ctx *gin.Context) (interface{}, error) { + return a.AttachmentService.GetAllMediaTypes(ctx) +} + +func (a *AttachmentHandler) GetAllTypes(ctx *gin.Context) (interface{}, error) { + attachmentTypes, err := a.AttachmentService.GetAllTypes(ctx) + if err != nil { + return nil, err + } + return attachmentTypes, nil +} diff --git a/handler/admin/backup.go b/handler/admin/backup.go new file mode 100644 index 00000000..77fdeb37 --- /dev/null +++ b/handler/admin/backup.go @@ -0,0 +1,215 @@ +package admin + +import ( + "net/http" + "path/filepath" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/log" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type BackupHandler struct { + BackupService service.BackupService +} + +func NewBackupHandler(backupService service.BackupService) *BackupHandler { + return &BackupHandler{ + BackupService: backupService, + } +} + +func (b *BackupHandler) GetWorkDirBackup(ctx *gin.Context) (interface{}, error) { + filename, err := util.ParamString(ctx, "filename") + if err != nil { + return nil, err + } + return b.BackupService.GetBackup(ctx, filepath.Join(config.BackupDir, filename), service.WholeSite) +} + +func (b *BackupHandler) GetDataBackup(ctx *gin.Context) (interface{}, error) { + filename, err := util.ParamString(ctx, "filename") + if err != nil { + return nil, err + } + return b.BackupService.GetBackup(ctx, filepath.Join(config.DataExportDir, filename), service.JsonData) +} + +func (b *BackupHandler) GetMarkDownBackup(ctx *gin.Context) (interface{}, error) { + filename, err := util.ParamString(ctx, "filename") + if err != nil { + return nil, err + } + return b.BackupService.GetBackup(ctx, filepath.Join(config.BackupMarkdownDir, filename), service.Markdown) +} + +func (b *BackupHandler) BackupWholeSite(ctx *gin.Context) (interface{}, error) { + return b.BackupService.BackupWholeSite(ctx) +} + +func (b *BackupHandler) ListBackups(ctx *gin.Context) (interface{}, error) { + return b.BackupService.ListFiles(ctx, config.BackupDir, service.WholeSite) +} + +func (b *BackupHandler) HandleWorkDir(ctx *gin.Context) { + path := ctx.Request.URL.Path + if path == "/api/admin/backups/work-dir/fetch" { + wrapHandler(b.GetWorkDirBackup)(ctx) + return + } + if path == "/api/admin/backups/work-dir" || path == "/api/admin/backups/work-dir/" { + wrapHandler(b.ListBackups)(ctx) + return + } + b.DownloadBackups(ctx) +} + +func (b *BackupHandler) DownloadBackups(ctx *gin.Context) { + filename := ctx.Param("path") + if filename == "" { + ctx.AbortWithStatusJSON(http.StatusBadRequest, &dto.BaseDTO{ + Status: http.StatusBadRequest, + Message: "Filename parameter does not exist", + }) + return + } + filePath, err := b.BackupService.GetBackupFilePath(ctx, config.BackupDir, filename) + if err != nil { + log.CtxErrorf(ctx, "err=%+v", err) + status := xerr.GetHttpStatus(err) + ctx.JSON(status, &dto.BaseDTO{Status: status, Message: xerr.GetMessage(err)}) + } + ctx.File(filePath) +} + +func (b *BackupHandler) DeleteBackups(ctx *gin.Context) (interface{}, error) { + filename, err := util.ParamString(ctx, "filename") + if err != nil { + return nil, err + } + return nil, b.BackupService.DeleteFile(ctx, config.BackupDir, filename) +} + +func (b *BackupHandler) ImportMarkdown(ctx *gin.Context) (interface{}, error) { + fileHeader, err := ctx.FormFile("file") + if err != nil { + return nil, xerr.WithMsg(err, "上传文件错误").WithStatus(xerr.StatusBadRequest) + } + return nil, b.BackupService.ImportMarkdown(ctx, fileHeader) +} + +func (b *BackupHandler) ExportData(ctx *gin.Context) (interface{}, error) { + return b.BackupService.ExportData(ctx) +} + +func (b *BackupHandler) HandleData(ctx *gin.Context) { + path := ctx.Request.URL.Path + if path == "/api/admin/backups/data/fetch" { + wrapHandler(b.GetDataBackup)(ctx) + return + } + if path == "/api/admin/backups/data" || path == "/api/admin/backups/data/" { + wrapHandler(b.ListExportData)(ctx) + return + } + b.DownloadData(ctx) +} + +func (b *BackupHandler) ListExportData(ctx *gin.Context) (interface{}, error) { + return b.BackupService.ListFiles(ctx, config.DataExportDir, service.JsonData) +} + +func (b *BackupHandler) DownloadData(ctx *gin.Context) { + filename := ctx.Param("path") + if filename == "" { + ctx.AbortWithStatusJSON(http.StatusBadRequest, &dto.BaseDTO{ + Status: http.StatusBadRequest, + Message: "Filename parameter does not exist", + }) + } + filePath, err := b.BackupService.GetBackupFilePath(ctx, config.DataExportDir, filename) + if err != nil { + log.CtxErrorf(ctx, "err=%+v", err) + status := xerr.GetHttpStatus(err) + ctx.JSON(status, &dto.BaseDTO{Status: status, Message: xerr.GetMessage(err)}) + } + ctx.File(filePath) +} + +func (b *BackupHandler) DeleteDataFile(ctx *gin.Context) (interface{}, error) { + filename, ok := ctx.GetQuery("filename") + if !ok || filename == "" { + return nil, xerr.BadParam.New("no filename param").WithStatus(xerr.StatusBadRequest).WithMsg("no filename param") + } + return nil, b.BackupService.DeleteFile(ctx, config.DataExportDir, filename) +} + +func (b *BackupHandler) ExportMarkdown(ctx *gin.Context) (interface{}, error) { + var exportMarkdownParam param.ExportMarkdown + err := ctx.ShouldBindJSON(&exportMarkdownParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + return b.BackupService.ExportMarkdown(ctx, exportMarkdownParam.NeedFrontMatter) +} + +func (b *BackupHandler) ListMarkdowns(ctx *gin.Context) (interface{}, error) { + return b.BackupService.ListFiles(ctx, config.BackupMarkdownDir, service.Markdown) +} + +func (b *BackupHandler) DeleteMarkdowns(ctx *gin.Context) (interface{}, error) { + filename, err := util.ParamString(ctx, "filename") + if err != nil { + return nil, err + } + return nil, b.BackupService.DeleteFile(ctx, config.BackupMarkdownDir, filename) +} + +func (b *BackupHandler) DownloadMarkdown(ctx *gin.Context) { + filename := ctx.Param("filename") + if filename == "" { + ctx.AbortWithStatusJSON(http.StatusBadRequest, &dto.BaseDTO{ + Status: http.StatusBadRequest, + Message: "Filename parameter does not exist", + }) + return + } + filePath, err := b.BackupService.GetBackupFilePath(ctx, config.BackupMarkdownDir, filename) + if err != nil { + log.CtxErrorf(ctx, "err=%+v", err) + status := xerr.GetHttpStatus(err) + ctx.JSON(status, &dto.BaseDTO{Status: status, Message: xerr.GetMessage(err)}) + } + ctx.File(filePath) +} + +type wrapperHandler func(ctx *gin.Context) (interface{}, error) + +func wrapHandler(handler wrapperHandler) gin.HandlerFunc { + return func(ctx *gin.Context) { + data, err := handler(ctx) + if err != nil { + log.CtxErrorf(ctx, "err=%+v", err) + status := xerr.GetHttpStatus(err) + ctx.JSON(status, &dto.BaseDTO{Status: status, Message: xerr.GetMessage(err)}) + return + } + + ctx.JSON(http.StatusOK, &dto.BaseDTO{ + Status: http.StatusOK, + Data: data, + Message: "OK", + }) + } +} diff --git a/handler/admin/category.go b/handler/admin/category.go new file mode 100644 index 00000000..61245b05 --- /dev/null +++ b/handler/admin/category.go @@ -0,0 +1,130 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type CategoryHandler struct { + CategoryService service.CategoryService +} + +func NewCategoryHandler(categoryService service.CategoryService) *CategoryHandler { + return &CategoryHandler{ + CategoryService: categoryService, + } +} + +func (c *CategoryHandler) GetCategoryByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "categoryID") + if err != nil { + return nil, err + } + category, err := c.CategoryService.GetByID(ctx, int32(id)) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTO(ctx, category) +} + +func (c *CategoryHandler) ListAllCategory(ctx *gin.Context) (interface{}, error) { + categoryQuery := struct { + *param.Sort + More *bool `json:"more" form:"more"` + }{} + + err := ctx.ShouldBindQuery(&categoryQuery) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if categoryQuery.Sort == nil || len(categoryQuery.Sort.Fields) == 0 { + categoryQuery.Sort = ¶m.Sort{Fields: []string{"priority,asc"}} + } + if categoryQuery.More != nil && *categoryQuery.More { + return c.CategoryService.ListCategoryWithPostCountDTO(ctx, categoryQuery.Sort) + } + categories, err := c.CategoryService.ListAll(ctx, categoryQuery.Sort) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTOs(ctx, categories) +} + +func (c *CategoryHandler) ListAsTree(ctx *gin.Context) (interface{}, error) { + var sort param.Sort + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, err + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "priority,asc") + } + return c.CategoryService.ListAsTree(ctx, &sort, false) +} + +func (c *CategoryHandler) CreateCategory(ctx *gin.Context) (interface{}, error) { + var categoryParam param.Category + err := ctx.ShouldBindJSON(&categoryParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + category, err := c.CategoryService.Create(ctx, &categoryParam) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTO(ctx, category) +} + +func (c *CategoryHandler) UpdateCategory(ctx *gin.Context) (interface{}, error) { + var categoryParam param.Category + err := ctx.ShouldBindJSON(&categoryParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + categoryID, err := util.ParamInt32(ctx, "categoryID") + if err != nil { + return nil, err + } + categoryParam.ID = categoryID + category, err := c.CategoryService.Update(ctx, &categoryParam) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTO(ctx, category) +} + +func (c *CategoryHandler) UpdateCategoryBatch(ctx *gin.Context) (interface{}, error) { + categoryParams := make([]*param.Category, 0) + err := ctx.ShouldBindJSON(&categoryParams) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + categories, err := c.CategoryService.UpdateBatch(ctx, categoryParams) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTOs(ctx, categories) +} + +func (c *CategoryHandler) DeleteCategory(ctx *gin.Context) (interface{}, error) { + categoryID, err := util.ParamInt32(ctx, "categoryID") + if err != nil { + return nil, err + } + return nil, c.CategoryService.Delete(ctx, categoryID) +} diff --git a/handler/admin/comment_journal.go b/handler/admin/comment_journal.go new file mode 100644 index 00000000..9374a7a6 --- /dev/null +++ b/handler/admin/comment_journal.go @@ -0,0 +1,229 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type JournalCommentHandler struct { + JournalCommentService service.JournalCommentService + OptionService service.OptionService + JournalService service.JournalService + JournalCommentAssembler assembler.JournalCommentAssembler +} + +func NewJournalCommentHandler(journalCommentService service.JournalCommentService, optionService service.OptionService, journalService service.JournalService, journalCommentAssembler assembler.JournalCommentAssembler) *JournalCommentHandler { + return &JournalCommentHandler{ + JournalCommentService: journalCommentService, + OptionService: optionService, + JournalService: journalService, + JournalCommentAssembler: journalCommentAssembler, + } +} + +func (j *JournalCommentHandler) ListJournalComment(ctx *gin.Context) (interface{}, error) { + var commentQuery param.CommentQuery + err := ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + comments, totalCount, err := j.JournalCommentService.Page(ctx, commentQuery, consts.CommentTypeJournal) + if err != nil { + return nil, err + } + commentDTOs, err := j.JournalCommentAssembler.ConvertToWithJournal(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentDTOs, totalCount, commentQuery.Page), nil +} + +func (j *JournalCommentHandler) ListJournalCommentLatest(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt32(ctx, "top") + if err != nil { + return nil, err + } + commentQuery := param.CommentQuery{ + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Page: param.Page{PageNum: 0, PageSize: int(top)}, + } + comments, _, err := j.JournalCommentService.Page(ctx, commentQuery, consts.CommentTypeSheet) + if err != nil { + return nil, err + } + return j.JournalCommentAssembler.ConvertToWithJournal(ctx, comments) +} + +func (j *JournalCommentHandler) ListJournalCommentAsTree(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + pageSize, err := j.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + page := param.Page{PageSize: pageSize.(int), PageNum: int(pageNum)} + + allComments, err := j.JournalCommentService.GetByContentID(ctx, journalID, ¶m.Sort{Fields: []string{"createTime,desc"}}) + if err != nil { + return nil, err + } + + commentVOs, totalCount, err := j.JournalCommentAssembler.PageConvertToVOs(ctx, allComments, page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, totalCount, page), nil +} + +func (j *JournalCommentHandler) ListJournalCommentWithParent(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + + pageSize, err := j.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + + page := param.Page{PageSize: pageSize.(int), PageNum: int(pageNum)} + + comments, totalCount, err := j.JournalCommentService.Page(ctx, param.CommentQuery{ + ContentID: &journalID, + Page: page, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + }, consts.CommentTypePost) + if err != nil { + return nil, err + } + + commentsWithParent, err := j.JournalCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentsWithParent, totalCount, page), nil +} + +func (j *JournalCommentHandler) CreateJournalComment(ctx *gin.Context) (interface{}, error) { + var commentParam *param.Comment + err := ctx.ShouldBindJSON(&commentParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if commentParam.AuthorURL != "" { + err = util.Validate.Var(commentParam.AuthorURL, "url") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("url is not available") + } + } + commentParam.CommentType = consts.CommentTypeJournal + comment, err := j.JournalCommentService.CreateBy(ctx, commentParam) + if err != nil { + return nil, err + } + return j.JournalCommentAssembler.ConvertToDTO(ctx, comment) +} + +func (j *JournalCommentHandler) UpdateJournalCommentStatus(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt64(ctx, "commentID") + if err != nil { + return nil, err + } + strStatus, err := util.ParamString(ctx, "status") + if err != nil { + return nil, err + } + status, err := consts.CommentStatusFromString(strStatus) + if err != nil { + return nil, err + } + return j.JournalCommentService.UpdateStatus(ctx, commentID, status) +} + +func (j *JournalCommentHandler) UpdateJournalComment(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt64(ctx, "commentID") + if err != nil { + return nil, err + } + var commentParam *param.Comment + err = ctx.ShouldBindJSON(&commentParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if commentParam.AuthorURL != "" { + err = util.Validate.Var(commentParam.AuthorURL, "url") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("url is not available") + } + } + comment, err := j.JournalCommentService.UpdateBy(ctx, commentID, commentParam) + if err != nil { + return nil, err + } + return j.JournalCommentAssembler.ConvertToDTO(ctx, comment) +} + +func (j *JournalCommentHandler) UpdateJournalStatusBatch(ctx *gin.Context) (interface{}, error) { + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + + ids := make([]int64, 0) + err = ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + comments, err := j.JournalCommentService.UpdateStatusBatch(ctx, ids, consts.CommentStatus(status)) + if err != nil { + return nil, err + } + return j.JournalCommentAssembler.ConvertToDTOList(ctx, comments) +} + +func (j *JournalCommentHandler) DeleteJournalComment(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt64(ctx, "commentID") + if err != nil { + return nil, err + } + return nil, j.JournalCommentService.Delete(ctx, commentID) +} + +func (j *JournalCommentHandler) DeleteJournalCommentBatch(ctx *gin.Context) (interface{}, error) { + ids := make([]int64, 0) + err := ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + return nil, j.JournalCommentService.DeleteBatch(ctx, ids) +} diff --git a/handler/admin/comment_post.go b/handler/admin/comment_post.go new file mode 100644 index 00000000..1d52be64 --- /dev/null +++ b/handler/admin/comment_post.go @@ -0,0 +1,236 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type PostCommentHandler struct { + PostCommentService service.PostCommentService + OptionService service.OptionService + PostService service.PostService + PostAssembler assembler.PostAssembler + PostCommentAssembler assembler.PostCommentAssembler +} + +func NewPostCommentHandler( + postCommentHandler service.PostCommentService, + optionService service.OptionService, + postService service.PostService, + postAssembler assembler.PostAssembler, + postCommentAssembler assembler.PostCommentAssembler, +) *PostCommentHandler { + return &PostCommentHandler{ + PostCommentService: postCommentHandler, + OptionService: optionService, + PostService: postService, + PostAssembler: postAssembler, + PostCommentAssembler: postCommentAssembler, + } +} + +func (p *PostCommentHandler) ListPostComment(ctx *gin.Context) (interface{}, error) { + var commentQuery param.CommentQuery + err := ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + comments, totalCount, err := p.PostCommentService.Page(ctx, commentQuery, consts.CommentTypePost) + if err != nil { + return nil, err + } + commentDTOs, err := p.PostCommentAssembler.ConvertToWithPost(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentDTOs, totalCount, commentQuery.Page), nil +} + +func (p *PostCommentHandler) ListPostCommentLatest(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt32(ctx, "top") + if err != nil { + return nil, err + } + commentQuery := param.CommentQuery{ + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Page: param.Page{PageNum: 0, PageSize: int(top)}, + } + comments, _, err := p.PostCommentService.Page(ctx, commentQuery, consts.CommentTypePost) + if err != nil { + return nil, err + } + return p.PostCommentAssembler.ConvertToWithPost(ctx, comments) +} + +func (p *PostCommentHandler) ListPostCommentAsTree(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + pageSize, err := p.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + page := param.Page{PageSize: pageSize.(int), PageNum: int(pageNum)} + allComments, err := p.PostCommentService.GetByContentID(ctx, postID, ¶m.Sort{Fields: []string{"createTime,desc"}}) + if err != nil { + return nil, err + } + commentVOs, totalCount, err := p.PostCommentAssembler.PageConvertToVOs(ctx, allComments, page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, totalCount, page), nil +} + +func (p *PostCommentHandler) ListPostCommentWithParent(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + + pageSize, err := p.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + + page := param.Page{PageNum: int(pageNum), PageSize: pageSize.(int)} + + comments, totalCount, err := p.PostCommentService.Page(ctx, param.CommentQuery{ + ContentID: &postID, + Page: page, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + }, consts.CommentTypePost) + if err != nil { + return nil, err + } + + commentsWithParent, err := p.PostCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentsWithParent, totalCount, page), nil +} + +func (p *PostCommentHandler) CreatePostComment(ctx *gin.Context) (interface{}, error) { + var commentParam *param.Comment + err := ctx.ShouldBindJSON(&commentParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if commentParam.AuthorURL != "" { + err = util.Validate.Var(commentParam.AuthorURL, "url") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("url is not available") + } + } + commentParam.CommentType = consts.CommentTypeSheet + comment, err := p.PostCommentService.CreateBy(ctx, commentParam) + if err != nil { + return nil, err + } + return p.PostCommentAssembler.ConvertToDTO(ctx, comment) +} + +func (p *PostCommentHandler) UpdatePostComment(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt64(ctx, "commentID") + if err != nil { + return nil, err + } + var commentParam *param.Comment + err = ctx.ShouldBindJSON(&commentParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if commentParam.AuthorURL != "" { + err = util.Validate.Var(commentParam.AuthorURL, "url") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("url is not available") + } + } + comment, err := p.PostCommentService.UpdateBy(ctx, commentID, commentParam) + if err != nil { + return nil, err + } + + return p.PostCommentAssembler.ConvertToDTO(ctx, comment) +} + +func (p *PostCommentHandler) UpdatePostCommentStatus(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt32(ctx, "commentID") + if err != nil { + return nil, err + } + strStatus, err := util.ParamString(ctx, "status") + if err != nil { + return nil, err + } + status, err := consts.CommentStatusFromString(strStatus) + if err != nil { + return nil, err + } + return p.PostCommentService.UpdateStatus(ctx, int64(commentID), status) +} + +func (p *PostCommentHandler) UpdatePostCommentStatusBatch(ctx *gin.Context) (interface{}, error) { + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + + ids := make([]int64, 0) + err = ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + comments, err := p.PostCommentService.UpdateStatusBatch(ctx, ids, consts.CommentStatus(status)) + if err != nil { + return nil, err + } + return p.PostCommentAssembler.ConvertToDTOList(ctx, comments) +} + +func (p *PostCommentHandler) DeletePostComment(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt64(ctx, "commentID") + if err != nil { + return nil, err + } + return nil, p.PostCommentService.Delete(ctx, commentID) +} + +func (p *PostCommentHandler) DeletePostCommentBatch(ctx *gin.Context) (interface{}, error) { + ids := make([]int64, 0) + err := ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + return nil, p.PostCommentService.DeleteBatch(ctx, ids) +} diff --git a/handler/admin/comment_sheet.go b/handler/admin/comment_sheet.go new file mode 100644 index 00000000..0b9ab5de --- /dev/null +++ b/handler/admin/comment_sheet.go @@ -0,0 +1,246 @@ +package admin + +import ( + "context" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/model/vo" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type SheetCommentHandler struct { + SheetCommentService service.SheetCommentService + BaseCommentService service.BaseCommentService + OptionService service.OptionService + SheetService service.SheetService + SheetAssembler assembler.SheetAssembler + SheetCommentAssembler assembler.SheetCommentAssembler +} + +func NewSheetCommentHandler( + sheetCommentService service.SheetCommentService, + baseCommentService service.BaseCommentService, + optionService service.OptionService, + sheetService service.SheetService, + sheetAssembler assembler.SheetAssembler, + sheetCommentAssembler assembler.SheetCommentAssembler, +) *SheetCommentHandler { + return &SheetCommentHandler{ + SheetCommentService: sheetCommentService, + BaseCommentService: baseCommentService, + OptionService: optionService, + SheetService: sheetService, + SheetAssembler: sheetAssembler, + SheetCommentAssembler: sheetCommentAssembler, + } +} + +func (s *SheetCommentHandler) ListSheetComment(ctx *gin.Context) (interface{}, error) { + var commentQuery param.CommentQuery + err := ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + comments, totalCount, err := s.SheetCommentService.Page(ctx, commentQuery, consts.CommentTypeSheet) + if err != nil { + return nil, err + } + commentDTOs, err := s.ConvertToWithSheet(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentDTOs, totalCount, commentQuery.Page), nil +} + +func (s *SheetCommentHandler) ListSheetCommentLatest(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt32(ctx, "top") + if err != nil { + return nil, err + } + commentQuery := param.CommentQuery{ + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Page: param.Page{PageNum: 0, PageSize: int(top)}, + } + comments, _, err := s.SheetCommentService.Page(ctx, commentQuery, consts.CommentTypeSheet) + if err != nil { + return nil, err + } + return s.ConvertToWithSheet(ctx, comments) +} + +func (s *SheetCommentHandler) ListSheetCommentAsTree(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + pageSize, err := s.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + page := param.Page{PageSize: pageSize.(int), PageNum: int(pageNum)} + + allComments, err := s.SheetCommentService.GetByContentID(ctx, postID, ¶m.Sort{Fields: []string{"createTime,desc"}}) + if err != nil { + return nil, err + } + commentVOs, totalCount, err := s.SheetCommentAssembler.PageConvertToVOs(ctx, allComments, page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, totalCount, page), nil +} + +func (s *SheetCommentHandler) ListSheetCommentWithParent(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + pageNum, err := util.MustGetQueryInt32(ctx, "page") + if err != nil { + return nil, err + } + + pageSize, err := s.OptionService.GetOrByDefaultWithErr(ctx, property.CommentPageSize, property.CommentPageSize.DefaultValue) + if err != nil { + return nil, err + } + page := param.Page{PageSize: pageSize.(int), PageNum: int(pageNum)} + + comments, totalCount, err := s.SheetCommentService.Page(ctx, param.CommentQuery{ + ContentID: &postID, + Page: page, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + }, consts.CommentTypePost) + if err != nil { + return nil, err + } + + commentsWithParent, err := s.SheetCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commentsWithParent, totalCount, page), nil +} + +func (s *SheetCommentHandler) CreateSheetComment(ctx *gin.Context) (interface{}, error) { + var commentParam *param.Comment + err := ctx.ShouldBindJSON(&commentParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if commentParam.AuthorURL != "" { + err = util.Validate.Var(commentParam.AuthorURL, "url") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("url is not available") + } + } + commentParam.CommentType = consts.CommentTypeSheet + comment, err := s.BaseCommentService.CreateBy(ctx, commentParam) + if err != nil { + return nil, err + } + return s.SheetCommentAssembler.ConvertToDTO(ctx, comment) +} + +func (s *SheetCommentHandler) UpdateSheetCommentStatus(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt32(ctx, "commentID") + if err != nil { + return nil, err + } + strStatus, err := util.ParamString(ctx, "status") + if err != nil { + return nil, err + } + status, err := consts.CommentStatusFromString(strStatus) + if err != nil { + return nil, err + } + return s.SheetCommentService.UpdateStatus(ctx, int64(commentID), status) +} + +func (s *SheetCommentHandler) UpdateSheetCommentStatusBatch(ctx *gin.Context) (interface{}, error) { + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + + ids := make([]int64, 0) + err = ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + comments, err := s.SheetCommentService.UpdateStatusBatch(ctx, ids, consts.CommentStatus(status)) + if err != nil { + return nil, err + } + return s.SheetCommentAssembler.ConvertToDTOList(ctx, comments) +} + +func (s *SheetCommentHandler) DeleteSheetComment(ctx *gin.Context) (interface{}, error) { + commentID, err := util.ParamInt32(ctx, "commentID") + if err != nil { + return nil, err + } + return nil, s.SheetCommentService.Delete(ctx, int64(commentID)) +} + +func (s *SheetCommentHandler) DeleteSheetCommentBatch(ctx *gin.Context) (interface{}, error) { + ids := make([]int64, 0) + err := ctx.ShouldBindJSON(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + return nil, s.SheetCommentService.DeleteBatch(ctx, ids) +} + +func (s *SheetCommentHandler) ConvertToWithSheet(ctx context.Context, comments []*entity.Comment) ([]*vo.SheetCommentWithSheet, error) { + postIDs := make([]int32, 0, len(comments)) + for _, comment := range comments { + postIDs = append(postIDs, comment.PostID) + } + posts, err := s.SheetService.GetByPostIDs(ctx, postIDs) + if err != nil { + return nil, err + } + result := make([]*vo.SheetCommentWithSheet, 0, len(comments)) + for _, comment := range comments { + commentDTO, err := s.SheetCommentAssembler.ConvertToDTO(ctx, comment) + if err != nil { + return nil, err + } + commentWithSheet := &vo.SheetCommentWithSheet{ + Comment: *commentDTO, + } + result = append(result, commentWithSheet) + post, ok := posts[comment.PostID] + if ok { + commentWithSheet.PostMinimal, err = s.SheetAssembler.ConvertToMinimalDTO(ctx, post) + if err != nil { + return nil, err + } + } + } + return result, nil +} diff --git a/handler/admin/email.go b/handler/admin/email.go new file mode 100644 index 00000000..1463ab51 --- /dev/null +++ b/handler/admin/email.go @@ -0,0 +1,26 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type EmailHandler struct { + EmailService service.EmailService +} + +func NewEmailHandler(emailService service.EmailService) *EmailHandler { + return &EmailHandler{ + EmailService: emailService, + } +} +func (e *EmailHandler) Test(ctx *gin.Context) (interface{}, error) { + p := ¶m.TestEmail{} + if err := ctx.ShouldBindJSON(p); err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("param error ") + } + return nil, e.EmailService.SendTextEmail(ctx, p.To, p.Subject, p.Content) +} diff --git a/handler/admin/init.go b/handler/admin/init.go new file mode 100644 index 00000000..f1a9d542 --- /dev/null +++ b/handler/admin/init.go @@ -0,0 +1,29 @@ +package admin + +import "github.com/go-sonic/sonic/injection" + +func init() { + injection.Provide( + NewAdminHandler, + NewAttachmentHandler, + NewCategoryHandler, + NewBackupHandler, + NewInstallHandler, + NewJournalHandler, + NewJournalCommentHandler, + NewLinkHandler, + NewLogHandler, + NewMenuHandler, + NewOptionHandler, + NewPhotoHandler, + NewPostHandler, + NewPostCommentHandler, + NewSheetHandler, + NewSheetCommentHandler, + NewStatisticHandler, + NewTagHandler, + NewThemeHandler, + NewUserHandler, + NewEmailHandler, + ) +} diff --git a/handler/admin/install.go b/handler/admin/install.go new file mode 100644 index 00000000..db18a64c --- /dev/null +++ b/handler/admin/install.go @@ -0,0 +1,37 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type InstallHandler struct { + InstallService service.InstallService +} + +func NewInstallHandler(installService service.InstallService) *InstallHandler { + return &InstallHandler{ + InstallService: installService, + } +} + +func (i *InstallHandler) InstallBlog(ctx *gin.Context) (interface{}, error) { + var installParam param.Install + err := ctx.ShouldBindJSON(&installParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + err = i.InstallService.InstallBlog(ctx, installParam) + if err != nil { + return nil, err + } + return "安装完成", nil +} diff --git a/handler/admin/journal.go b/handler/admin/journal.go new file mode 100644 index 00000000..6f4e97a9 --- /dev/null +++ b/handler/admin/journal.go @@ -0,0 +1,101 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type JournalHandler struct { + JournalService service.JournalService +} + +func NewJournalHandler(journalService service.JournalService) *JournalHandler { + return &JournalHandler{ + JournalService: journalService, + } +} + +func (j *JournalHandler) ListJournal(ctx *gin.Context) (interface{}, error) { + var journalQuery param.JournalQuery + err := ctx.ShouldBindWith(&journalQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + journalQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + journals, totalCount, err := j.JournalService.ListJournal(ctx, journalQuery) + if err != nil { + return nil, err + } + journalDTOs, err := j.JournalService.ConvertToWithCommentDTOList(ctx, journals) + if err != nil { + return nil, err + } + return dto.NewPage(journalDTOs, totalCount, journalQuery.Page), nil +} + +func (j *JournalHandler) ListLatestJournal(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt(ctx, "top") + if err != nil { + top = 10 + } + journalQuery := param.JournalQuery{ + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Page: param.Page{PageNum: 0, PageSize: top}, + } + journals, _, err := j.JournalService.ListJournal(ctx, journalQuery) + if err != nil { + return nil, err + } + return j.JournalService.ConvertToWithCommentDTOList(ctx, journals) +} + +func (j *JournalHandler) CreateJournal(ctx *gin.Context) (interface{}, error) { + var journalParam param.Journal + err := ctx.ShouldBindJSON(&journalParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + journal, err := j.JournalService.Create(ctx, &journalParam) + if err != nil { + return nil, err + } + return j.JournalService.ConvertToDTO(journal), nil +} + +func (j *JournalHandler) UpdateJournal(ctx *gin.Context) (interface{}, error) { + var journalParam param.Journal + err := ctx.ShouldBindJSON(&journalParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + return j.JournalService.Update(ctx, journalID, &journalParam) +} + +func (j *JournalHandler) DeleteJournal(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + return nil, j.JournalService.Delete(ctx, int32(journalID)) +} diff --git a/handler/admin/link.go b/handler/admin/link.go new file mode 100644 index 00000000..c0c4cbfe --- /dev/null +++ b/handler/admin/link.go @@ -0,0 +1,102 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type LinkHandler struct { + LinkService service.LinkService +} + +func NewLinkHandler(linkService service.LinkService) *LinkHandler { + return &LinkHandler{ + LinkService: linkService, + } +} + +func (l *LinkHandler) ListLinks(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindWith(&sort, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "team,desc", "priority,asc") + } else { + sort.Fields = append(sort.Fields, "priority,asc") + } + links, err := l.LinkService.List(ctx, &sort) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToDTOs(ctx, links), nil +} + +func (l *LinkHandler) GetLinkByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + link, err := l.LinkService.GetByID(ctx, id) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToDTO(ctx, link), nil +} + +func (l *LinkHandler) CreateLink(ctx *gin.Context) (interface{}, error) { + linkParam := ¶m.Link{} + err := ctx.ShouldBindJSON(linkParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + link, err := l.LinkService.Create(ctx, linkParam) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToDTO(ctx, link), nil +} + +func (l *LinkHandler) UpdateLink(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + linkParam := ¶m.Link{} + err = ctx.ShouldBindJSON(linkParam) + + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + link, err := l.LinkService.Update(ctx, id, linkParam) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToDTO(ctx, link), nil +} + +func (l *LinkHandler) DeleteLink(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return nil, l.LinkService.Delete(ctx, id) +} + +func (l *LinkHandler) ListLinkTeams(ctx *gin.Context) (interface{}, error) { + return l.LinkService.ListTeams(ctx) +} diff --git a/handler/admin/log.go b/handler/admin/log.go new file mode 100644 index 00000000..0f402ea0 --- /dev/null +++ b/handler/admin/log.go @@ -0,0 +1,67 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type LogHandler struct { + LogService service.LogService +} + +func NewLogHandler(logService service.LogService) *LogHandler { + return &LogHandler{ + LogService: logService, + } +} + +func (l *LogHandler) PageLatestLog(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt32(ctx, "top") + if err != nil { + top = 10 + } + logs, _, err := l.LogService.PageLog(ctx, param.Page{PageSize: int(top)}, ¶m.Sort{Fields: []string{"createTime,desc"}}) + if err != nil { + return nil, err + } + logDTOs := make([]*dto.Log, 0, len(logs)) + for _, log := range logs { + logDTOs = append(logDTOs, l.LogService.ConvertToDTO(log)) + } + return logDTOs, nil +} + +func (l *LogHandler) PageLog(ctx *gin.Context) (interface{}, error) { + type LogParam struct { + param.Page + *param.Sort + } + var logParam LogParam + err := ctx.ShouldBindQuery(&logParam) + if err != nil { + return nil, xerr.WithMsg(err, "parameter error").WithStatus(xerr.StatusBadRequest) + } + if logParam.Sort == nil { + logParam.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + logs, totalCount, err := l.LogService.PageLog(ctx, logParam.Page, logParam.Sort) + if err != nil { + return nil, err + } + logDTOs := make([]*dto.Log, 0, len(logs)) + for _, log := range logs { + logDTOs = append(logDTOs, l.LogService.ConvertToDTO(log)) + } + return dto.NewPage(logDTOs, totalCount, logParam.Page), nil +} + +func (l *LogHandler) ClearLog(ctx *gin.Context) (interface{}, error) { + return nil, l.LogService.Clear(ctx) +} diff --git a/handler/admin/menu.go b/handler/admin/menu.go new file mode 100644 index 00000000..43d1b5d8 --- /dev/null +++ b/handler/admin/menu.go @@ -0,0 +1,176 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type MenuHandler struct { + MenuService service.MenuService +} + +func NewMenuHandler(menuService service.MenuService) *MenuHandler { + return &MenuHandler{ + MenuService: menuService, + } +} + +func (m *MenuHandler) ListMenus(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "team,desc", "priority,asc") + } else { + sort.Fields = append(sort.Fields, "priority,asc") + } + menus, err := m.MenuService.List(ctx, &sort) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTOs(ctx, menus), nil +} + +func (m *MenuHandler) ListMenusAsTree(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "team,desc", "priority,asc") + } else { + sort.Fields = append(sort.Fields, "priority,asc") + } + menus, err := m.MenuService.ListAsTree(ctx, &sort) + if err != nil { + return nil, err + } + return menus, nil +} + +func (m *MenuHandler) ListMenusAsTreeByTeam(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "priority,asc") + } + menus, err := m.MenuService.ListAsTree(ctx, &sort) + if err != nil { + return nil, err + } + return menus, nil +} + +func (m *MenuHandler) GetMenuByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + menu, err := m.MenuService.GetByID(ctx, id) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTO(ctx, menu), nil +} + +func (m *MenuHandler) CreateMenu(ctx *gin.Context) (interface{}, error) { + menuParam := ¶m.Menu{} + err := ctx.ShouldBindJSON(menuParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + menu, err := m.MenuService.Create(ctx, menuParam) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTO(ctx, menu), nil +} + +func (m *MenuHandler) CreateMenuBatch(ctx *gin.Context) (interface{}, error) { + menuParams := make([]*param.Menu, 0) + err := ctx.ShouldBindJSON(&menuParams) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + menus, err := m.MenuService.CreateBatch(ctx, menuParams) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTOs(ctx, menus), nil +} + +func (m *MenuHandler) UpdateMenu(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + menuParam := ¶m.Menu{} + err = ctx.ShouldBindJSON(menuParam) + + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + menu, err := m.MenuService.Update(ctx, id, menuParam) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTO(ctx, menu), nil +} + +func (m *MenuHandler) UpdateMenuBatch(ctx *gin.Context) (interface{}, error) { + menuParams := make([]*param.Menu, 0) + err := ctx.ShouldBindJSON(&menuParams) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + menus, err := m.MenuService.UpdateBatch(ctx, menuParams) + if err != nil { + return nil, err + } + return m.MenuService.ConvertToDTOs(ctx, menus), nil +} + +func (m *MenuHandler) DeleteMenu(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return nil, m.MenuService.Delete(ctx, id) +} + +func (m *MenuHandler) DeleteMenuBatch(ctx *gin.Context) (interface{}, error) { + menuIDs := make([]int32, 0) + err := ctx.ShouldBind(&menuIDs) + if err != nil { + return nil, xerr.WithMsg(err, "menuIDs error").WithStatus(xerr.StatusBadRequest) + } + return nil, m.MenuService.DeleteBatch(ctx, menuIDs) +} + +func (m *MenuHandler) ListMenuTeams(ctx *gin.Context) (interface{}, error) { + return m.MenuService.ListTeams(ctx) +} diff --git a/handler/admin/option.go b/handler/admin/option.go new file mode 100644 index 00000000..068aca63 --- /dev/null +++ b/handler/admin/option.go @@ -0,0 +1,105 @@ +package admin + +import ( + "strconv" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type OptionHandler struct { + OptionService service.OptionService +} + +func NewOptionHandler(optionService service.OptionService) *OptionHandler { + return &OptionHandler{ + OptionService: optionService, + } +} + +func (o *OptionHandler) ListAllOptions(ctx *gin.Context) (interface{}, error) { + return o.OptionService.ListAllOption(ctx) +} + +func (o *OptionHandler) SaveOption(ctx *gin.Context) (interface{}, error) { + optionParams := make([]*param.Option, 0) + err := ctx.ShouldBindJSON(&optionParams) + if err != nil { + return nil, xerr.WithMsg(err, "param error").WithStatus(xerr.StatusBadRequest) + } + optionMap := make(map[string]string, 0) + for _, option := range optionParams { + optionMap[option.Key] = option.Value + } + return nil, o.OptionService.Save(ctx, optionMap) +} + +func (o *OptionHandler) ListAllOptionsAsMap(ctx *gin.Context) (interface{}, error) { + options, err := o.OptionService.ListAllOption(ctx) + if err != nil { + return nil, err + } + result := make(map[string]interface{}) + for _, option := range options { + result[option.Key] = option.Value + } + return result, nil +} + +func (o *OptionHandler) ListAllOptionsAsMapWithKey(ctx *gin.Context) (interface{}, error) { + keys := make([]string, 0) + err := ctx.ShouldBindJSON(&keys) + if err != nil { + return nil, xerr.WithMsg(err, "option key error").WithStatus(xerr.StatusBadRequest) + } + options, err := o.OptionService.ListAllOption(ctx) + if err != nil { + return nil, err + } + keyMap := make(map[string]struct{}) + for _, key := range keys { + keyMap[key] = struct{}{} + } + result := make(map[string]interface{}) + for _, option := range options { + if _, ok := keyMap[option.Key]; ok { + result[option.Key] = option.Value + } + } + return result, nil +} + +func (o *OptionHandler) SaveOptionWithMap(ctx *gin.Context) (interface{}, error) { + optionMap := make(map[string]interface{}, 0) + err := ctx.ShouldBind(&optionMap) + if err != nil { + return nil, xerr.WithMsg(err, "parameter error").WithStatus(xerr.StatusBadRequest) + } + temp := make(map[string]string) + for key, value := range optionMap { + var v string + switch value := value.(type) { + case int32: + v = strconv.Itoa(int(value)) + case int64: + v = strconv.FormatInt(value, 10) + case int: + v = strconv.Itoa(value) + case string: + v = value + case bool: + v = strconv.FormatBool(value) + case float64: + v = strconv.FormatFloat(value, 'f', -1, 64) + case float32: + v = strconv.FormatFloat(float64(value), 'f', -1, 32) + default: + return nil, xerr.BadParam.New("key=%v,value=%v", key, value).WithStatus(xerr.StatusBadRequest).WithMsg("Parameter type is incorrect") + } + temp[key] = v + } + return nil, o.OptionService.Save(ctx, temp) +} diff --git a/handler/admin/photo.go b/handler/admin/photo.go new file mode 100644 index 00000000..e1a663b1 --- /dev/null +++ b/handler/admin/photo.go @@ -0,0 +1,128 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type PhotoHandler struct { + PhotoService service.PhotoService +} + +func NewPhotoHandler(photoService service.PhotoService) *PhotoHandler { + return &PhotoHandler{ + PhotoService: photoService, + } +} + +func (p *PhotoHandler) ListPhoto(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "createTime,desc") + } + photos, err := p.PhotoService.List(ctx, &sort) + if err != nil { + return nil, err + } + return p.PhotoService.ConvertToDTOs(ctx, photos), nil +} + +func (p *PhotoHandler) PagePhotos(ctx *gin.Context) (interface{}, error) { + type Param struct { + param.Page + param.Sort + } + param := Param{} + err := ctx.ShouldBindQuery(¶m) + if err != nil { + return nil, xerr.WithMsg(err, "parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(param.Fields) == 0 { + param.Fields = append(param.Fields, "createTime,desc") + } + photos, totalCount, err := p.PhotoService.Page(ctx, param.Page, ¶m.Sort) + if err != nil { + return nil, err + } + return dto.NewPage(p.PhotoService.ConvertToDTOs(ctx, photos), totalCount, param.Page), nil +} + +func (p *PhotoHandler) GetPhotoByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + photo, err := p.PhotoService.GetByID(ctx, id) + if err != nil { + return nil, err + } + return p.PhotoService.ConvertToDTO(ctx, photo), nil +} + +func (p *PhotoHandler) CreatePhoto(ctx *gin.Context) (interface{}, error) { + photoParam := ¶m.Photo{} + err := ctx.ShouldBindJSON(photoParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + photo, err := p.PhotoService.Create(ctx, photoParam) + if err != nil { + return nil, err + } + return p.PhotoService.ConvertToDTO(ctx, photo), nil +} + +func (p *PhotoHandler) UpdatePhoto(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + photoParam := ¶m.Photo{} + err = ctx.ShouldBindJSON(photoParam) + + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + photo, err := p.PhotoService.Update(ctx, id, photoParam) + if err != nil { + return nil, err + } + return p.PhotoService.ConvertToDTO(ctx, photo), nil +} + +func (p *PhotoHandler) DeletePhoto(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return nil, p.PhotoService.Delete(ctx, id) +} + +func (p *PhotoHandler) ListPhotoTeams(ctx *gin.Context) (interface{}, error) { + return p.PhotoService.ListTeams(ctx) +} + +func (p *PhotoHandler) IncreasePhotoLike(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return nil, p.PhotoService.IncreaseLike(ctx, id) +} diff --git a/handler/admin/post.go b/handler/admin/post.go new file mode 100644 index 00000000..9a005596 --- /dev/null +++ b/handler/admin/post.go @@ -0,0 +1,277 @@ +package admin + +import ( + "strconv" + + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type PostHandler struct { + PostService service.PostService + PostAssembler assembler.PostAssembler +} + +func NewPostHandler(postService service.PostService, postAssembler assembler.PostAssembler) *PostHandler { + return &PostHandler{ + PostService: postService, + PostAssembler: postAssembler, + } +} + +func (p *PostHandler) ListPosts(ctx *gin.Context) (interface{}, error) { + postQuery := param.PostQuery{} + err := ctx.ShouldBindWith(&postQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if postQuery.Sort == nil { + postQuery.Sort = ¶m.Sort{Fields: []string{"topPriority,desc", "createTime,desc"}} + } + posts, totalCount, err := p.PostService.Page(ctx, postQuery) + if err != nil { + return nil, err + } + if postQuery.More == nil || *postQuery.More { + postVOs, err := p.PostAssembler.ConvertToListVO(ctx, posts) + return dto.NewPage(postVOs, totalCount, postQuery.Page), err + } + postDTOs := make([]*dto.Post, 0) + for _, post := range posts { + postDTO, err := p.PostAssembler.ConvertToSimpleDTO(ctx, post) + if err != nil { + return nil, err + } + postDTOs = append(postDTOs, postDTO) + } + return dto.NewPage(postDTOs, totalCount, postQuery.Page), nil +} + +func (p *PostHandler) ListLatestPosts(ctx *gin.Context) (interface{}, error) { + top, err := util.MustGetQueryInt32(ctx, "top") + if err != nil { + top = 10 + } + postQuery := param.PostQuery{ + Page: param.Page{ + PageSize: int(top), + PageNum: 0, + }, + Sort: ¶m.Sort{ + Fields: []string{"createTime,desc"}, + }, + Keyword: nil, + CategoryID: nil, + More: util.BoolPtr(false), + } + posts, _, err := p.PostService.Page(ctx, postQuery) + if err != nil { + return nil, err + } + postMinimals := make([]*dto.PostMinimal, 0, len(posts)) + + for _, post := range posts { + postMinimal, err := p.PostAssembler.ConvertToMinimalDTO(ctx, post) + if err != nil { + return nil, err + } + postMinimals = append(postMinimals, postMinimal) + } + return postMinimals, nil +} + +func (p *PostHandler) ListPostsByStatus(ctx *gin.Context) (interface{}, error) { + var postQuery param.PostQuery + err := ctx.ShouldBindWith(&postQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if postQuery.Sort == nil { + postQuery.Sort = ¶m.Sort{Fields: []string{"createTime,desc"}} + } + + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + postQuery.Statuses = make([]*consts.PostStatus, 0) + statusType := consts.PostStatus(status) + postQuery.Statuses = append(postQuery.Statuses, &statusType) + + posts, totalCount, err := p.PostService.Page(ctx, postQuery) + if err != nil { + return nil, err + } + if postQuery.More == nil { + *postQuery.More = false + } + if postQuery.More == nil { + postVOs, err := p.PostAssembler.ConvertToListVO(ctx, posts) + return dto.NewPage(postVOs, totalCount, postQuery.Page), err + } + + postDTOs := make([]*dto.Post, 0) + for _, post := range posts { + postDTO, err := p.PostAssembler.ConvertToSimpleDTO(ctx, post) + if err != nil { + return nil, err + } + postDTOs = append(postDTOs, postDTO) + } + + return dto.NewPage(postDTOs, totalCount, postQuery.Page), nil +} + +func (p *PostHandler) GetByPostID(ctx *gin.Context) (interface{}, error) { + postIDStr := ctx.Param("postID") + postID, err := strconv.ParseInt(postIDStr, 10, 32) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + post, err := p.PostService.GetByPostID(ctx, int32(postID)) + if err != nil { + return nil, err + } + postDetailVO, err := p.PostAssembler.ConvertToDetailVO(ctx, post) + if err != nil { + return nil, err + } + return postDetailVO, nil +} + +func (p *PostHandler) LikePost(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + return nil, p.PostService.IncreaseLike(ctx, int32(postID)) +} + +func (p *PostHandler) CreatePost(ctx *gin.Context) (interface{}, error) { + var postParam param.Post + err := ctx.ShouldBindJSON(&postParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + + post, err := p.PostService.Create(ctx, &postParam) + if err != nil { + return nil, err + } + return p.PostAssembler.ConvertToDetailVO(ctx, post) +} + +func (p *PostHandler) UpdatePost(ctx *gin.Context) (interface{}, error) { + var postParam param.Post + err := ctx.ShouldBindJSON(&postParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + + postIDStr := ctx.Param("postID") + postID, err := strconv.ParseInt(postIDStr, 10, 32) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + + postDetailVO, err := p.PostService.Update(ctx, int32(postID), &postParam) + if err != nil { + return nil, err + } + return postDetailVO, nil +} + +func (p *PostHandler) UpdatePostStatus(ctx *gin.Context) (interface{}, error) { + postIDStr := ctx.Param("postID") + postID, err := strconv.ParseInt(postIDStr, 10, 32) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if status < int32(consts.PostStatusPublished) || status > int32(consts.PostStatusIntimate) { + return nil, xerr.WithStatus(nil, xerr.StatusBadRequest).WithMsg("status error") + } + post, err := p.PostService.UpdateStatus(ctx, int32(postID), consts.PostStatus(status)) + if err != nil { + return nil, err + } + return p.PostAssembler.ConvertToMinimalDTO(ctx, post) +} + +func (p *PostHandler) UpdatePostStatusBatch(ctx *gin.Context) (interface{}, error) { + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + if status < int32(consts.PostStatusPublished) || status > int32(consts.PostStatusIntimate) { + return nil, xerr.WithStatus(nil, xerr.StatusBadRequest).WithMsg("status error") + } + ids := make([]int32, 0) + err = ctx.ShouldBind(&ids) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("post ids error") + } + + return p.PostService.UpdateStatusBatch(ctx, consts.PostStatus(status), ids) +} + +func (p *PostHandler) UpdatePostDraft(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + var postContentParam param.PostContent + err = ctx.ShouldBindJSON(&postContentParam) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("content param error") + } + post, err := p.PostService.UpdateDraftContent(ctx, int32(postID), postContentParam.Content) + if err != nil { + return nil, err + } + return p.PostAssembler.ConvertToDetailDTO(ctx, post) +} + +func (p *PostHandler) DeletePost(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + return nil, p.PostService.Delete(ctx, postID) +} + +func (p *PostHandler) DeletePostBatch(ctx *gin.Context) (interface{}, error) { + postIDs := make([]int32, 0) + err := ctx.ShouldBind(&postIDs) + if err != nil { + return nil, xerr.WithMsg(err, "postIDs error").WithStatus(xerr.StatusBadRequest) + } + return nil, p.PostService.DeleteBatch(ctx, postIDs) +} + +func (p *PostHandler) PreviewPost(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + return p.PostService.Preview(ctx, int32(postID)) +} diff --git a/handler/admin/sheet.go b/handler/admin/sheet.go new file mode 100644 index 00000000..6a3fe58a --- /dev/null +++ b/handler/admin/sheet.go @@ -0,0 +1,155 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type SheetHandler struct { + SheetService service.SheetService + PostService service.PostService + SheetAssembler assembler.SheetAssembler +} + +func NewSheetHandler(sheetService service.SheetService, postService service.PostService, sheetAssembler assembler.SheetAssembler) *SheetHandler { + return &SheetHandler{ + SheetService: sheetService, + PostService: postService, + SheetAssembler: sheetAssembler, + } +} + +func (s *SheetHandler) GetSheetByID(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + sheet, err := s.SheetService.GetByPostID(ctx, sheetID) + if err != nil { + return nil, err + } + return s.SheetAssembler.ConvertToDetailVO(ctx, sheet) +} + +func (s *SheetHandler) ListSheet(ctx *gin.Context) (interface{}, error) { + type SheetParam struct { + param.Page + Sort string `json:"sort"` + } + var sheetParam SheetParam + err := ctx.ShouldBind(&sheetParam) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + sheets, totalCount, err := s.SheetService.Page(ctx, sheetParam.Page, ¶m.Sort{Fields: []string{"createTime,desc"}}) + if err != nil { + return nil, err + } + sheetVOs, err := s.SheetAssembler.ConvertToListVO(ctx, sheets) + if err != nil { + return nil, err + } + return dto.NewPage(sheetVOs, totalCount, sheetParam.Page), nil +} + +func (s *SheetHandler) IndependentSheets(ctx *gin.Context) (interface{}, error) { + return s.SheetService.ListIndependentSheets(ctx) +} + +func (s *SheetHandler) CreateSheet(ctx *gin.Context) (interface{}, error) { + var sheetParam param.Sheet + err := ctx.ShouldBindJSON(&sheetParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + sheet, err := s.SheetService.Create(ctx, &sheetParam) + if err != nil { + return nil, err + } + sheetDetailVO, err := s.SheetAssembler.ConvertToDetailVO(ctx, sheet) + if err != nil { + return nil, err + } + return sheetDetailVO, nil +} + +func (s *SheetHandler) UpdateSheet(ctx *gin.Context) (interface{}, error) { + var sheetParam param.Sheet + err := ctx.ShouldBindJSON(&sheetParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + postDetailVO, err := s.SheetService.Update(ctx, sheetID, &sheetParam) + if err != nil { + return nil, err + } + return postDetailVO, nil +} + +func (s *SheetHandler) UpdateSheetStatus(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.MustGetQueryInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + status, err := util.ParamInt32(ctx, "status") + if err != nil { + return nil, err + } + if status < int32(consts.PostStatusPublished) || status > int32(consts.PostStatusIntimate) { + return nil, xerr.WithStatus(nil, xerr.StatusBadRequest).WithMsg("status error") + } + return s.SheetService.UpdateStatus(ctx, sheetID, consts.PostStatus(status)) +} + +func (s *SheetHandler) UpdateSheetDraft(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.MustGetQueryInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + var postContentParam param.PostContent + err = ctx.ShouldBindJSON(&postContentParam) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("content param error") + } + post, err := s.SheetService.UpdateDraftContent(ctx, int32(sheetID), postContentParam.Content) + if err != nil { + return nil, err + } + return s.SheetAssembler.ConvertToDetailDTO(ctx, post) +} + +func (s *SheetHandler) DeleteSheet(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.MustGetQueryInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + return nil, s.SheetService.Delete(ctx, int32(sheetID)) +} + +func (s *SheetHandler) PreviewSheet(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + return s.PostService.Preview(ctx, int32(sheetID)) +} diff --git a/handler/admin/statistic.go b/handler/admin/statistic.go new file mode 100644 index 00000000..faef02e7 --- /dev/null +++ b/handler/admin/statistic.go @@ -0,0 +1,25 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/service" +) + +type StatisticHandler struct { + StatisticService service.StatisticService +} + +func NewStatisticHandler(l service.StatisticService) *StatisticHandler { + return &StatisticHandler{ + StatisticService: l, + } +} + +func (s *StatisticHandler) Statistics(ctx *gin.Context) (interface{}, error) { + return s.StatisticService.Statistic(ctx) +} + +func (s *StatisticHandler) StatisticsWithUser(ctx *gin.Context) (interface{}, error) { + return s.StatisticService.StatisticWithUser(ctx) +} diff --git a/handler/admin/tag.go b/handler/admin/tag.go new file mode 100644 index 00000000..47408e4a --- /dev/null +++ b/handler/admin/tag.go @@ -0,0 +1,101 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type TagHandler struct { + PostTagService service.PostTagService + TagService service.TagService +} + +func NewTagHandler(postTagService service.PostTagService, tagService service.TagService) *TagHandler { + return &TagHandler{ + PostTagService: postTagService, + TagService: tagService, + } +} + +func (t *TagHandler) ListTags(ctx *gin.Context) (interface{}, error) { + sort := param.Sort{} + err := ctx.ShouldBindQuery(&sort) + if err != nil { + return nil, xerr.WithMsg(err, "sort parameter error").WithStatus(xerr.StatusBadRequest) + } + if len(sort.Fields) == 0 { + sort.Fields = append(sort.Fields, "createTime,desc") + } + more, _ := util.MustGetQueryBool(ctx, "more") + if more { + return t.PostTagService.ListAllTagWithPostCount(ctx, &sort) + } + tags, err := t.TagService.ListAll(ctx, &sort) + if err != nil { + return nil, err + } + return t.TagService.ConvertToDTOs(ctx, tags) +} + +func (t *TagHandler) GetTagByID(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + tag, err := t.TagService.GetByID(ctx, id) + if err != nil { + return nil, err + } + return t.TagService.ConvertToDTO(ctx, tag) +} + +func (t *TagHandler) CreateTag(ctx *gin.Context) (interface{}, error) { + tagParam := ¶m.Tag{} + err := ctx.ShouldBindJSON(tagParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + tag, err := t.TagService.Create(ctx, tagParam) + if err != nil { + return nil, err + } + return t.TagService.ConvertToDTO(ctx, tag) +} + +func (t *TagHandler) UpdateTag(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + tagParam := ¶m.Tag{} + err = ctx.ShouldBindJSON(tagParam) + + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + tag, err := t.TagService.Update(ctx, id, tagParam) + if err != nil { + return nil, err + } + return t.TagService.ConvertToDTO(ctx, tag) +} + +func (t *TagHandler) DeleteTag(ctx *gin.Context) (interface{}, error) { + id, err := util.ParamInt32(ctx, "id") + if err != nil { + return nil, err + } + return nil, t.TagService.Delete(ctx, id) +} diff --git a/handler/admin/theme.go b/handler/admin/theme.go new file mode 100644 index 00000000..50e2fb23 --- /dev/null +++ b/handler/admin/theme.go @@ -0,0 +1,305 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type ThemeHandler struct { + ThemeService service.ThemeService + OptionService service.OptionService +} + +func NewThemeHandler(l service.ThemeService, o service.OptionService) *ThemeHandler { + return &ThemeHandler{ + ThemeService: l, + OptionService: o, + } +} + +func (t *ThemeHandler) GetThemeByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeByID(ctx, themeID) +} + +func (t *ThemeHandler) ListAllThemes(ctx *gin.Context) (interface{}, error) { + return t.ThemeService.ListAllTheme(ctx) +} + +func (t *ThemeHandler) ListActivatedThemeFile(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.ListThemeFiles(ctx, activatedThemeID) +} + +func (t *ThemeHandler) ListThemeFileByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + return t.ThemeService.ListThemeFiles(ctx, themeID) +} + +func (t *ThemeHandler) GetThemeFileContent(ctx *gin.Context) (interface{}, error) { + path, err := util.MustGetQueryString(ctx, "path") + if err != nil { + return nil, err + } + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeFileContent(ctx, activatedThemeID, path) +} + +func (t *ThemeHandler) GetThemeFileContentByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + path, err := util.MustGetQueryString(ctx, "path") + if err != nil { + return nil, err + } + + return t.ThemeService.GetThemeFileContent(ctx, themeID, path) +} + +func (t *ThemeHandler) UpdateThemeFile(ctx *gin.Context) (interface{}, error) { + themeParam := ¶m.ThemeContent{} + err := ctx.ShouldBindJSON(themeParam) + if err != nil { + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + } + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return nil, t.ThemeService.UpdateThemeFile(ctx, activatedThemeID, themeParam.Path, themeParam.Content) +} + +func (t *ThemeHandler) UpdateThemeFileByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + themeParam := ¶m.ThemeContent{} + err = ctx.ShouldBindJSON(themeParam) + if err != nil { + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + } + return nil, t.ThemeService.UpdateThemeFile(ctx, themeID, themeParam.Path, themeParam.Content) +} + +func (t *ThemeHandler) ListCustomSheetTemplate(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.ListCustomTemplates(ctx, activatedThemeID, consts.ThemeCustomSheetPrefix) +} + +func (t *ThemeHandler) ListCustomPostTemplate(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.ListCustomTemplates(ctx, activatedThemeID, consts.ThemeCustomPostPrefix) +} + +func (t *ThemeHandler) ActivateTheme(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + return t.ThemeService.ActivateTheme(ctx, themeID) +} + +func (t *ThemeHandler) GetActivatedTheme(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeByID(ctx, activatedThemeID) +} + +func (t *ThemeHandler) GetActivatedThemeConfig(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeConfig(ctx, activatedThemeID) +} + +func (t *ThemeHandler) GetThemeConfigByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeConfig(ctx, themeID) +} + +func (t *ThemeHandler) GetThemeConfigByGroup(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + group, err := util.ParamString(ctx, "group") + if err != nil { + return nil, err + } + themeSettings, err := t.ThemeService.GetThemeConfig(ctx, themeID) + if err != nil { + return nil, err + } + for _, setting := range themeSettings { + if setting.Name == group { + return setting.Items, nil + } + } + return nil, nil +} + +func (t *ThemeHandler) GetThemeConfigGroupNames(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + themeSettings, err := t.ThemeService.GetThemeConfig(ctx, themeID) + if err != nil { + return nil, err + } + groupNames := make([]string, len(themeSettings)) + for index, setting := range themeSettings { + groupNames[index] = setting.Name + } + return groupNames, nil +} + +func (t *ThemeHandler) GetActivatedThemeSettingMap(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeSettingMap(ctx, activatedThemeID) +} + +func (t *ThemeHandler) GetThemeSettingMapByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeSettingMap(ctx, themeID) +} + +func (t *ThemeHandler) GetThemeSettingMapByGroupAndThemeID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + group, err := util.ParamString(ctx, "group") + if err != nil { + return nil, err + } + return t.ThemeService.GetThemeGroupSettingMap(ctx, themeID, group) +} + +func (t *ThemeHandler) SaveActivatedThemeSetting(ctx *gin.Context) (interface{}, error) { + activatedThemeID, err := t.OptionService.GetActivatedThemeId(ctx) + if err != nil { + return nil, err + } + settings := make(map[string]interface{}) + err = ctx.ShouldBindJSON(&settings) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + return nil, t.ThemeService.SaveThemeSettings(ctx, activatedThemeID, settings) +} + +func (t *ThemeHandler) SaveThemeSettingByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + settings := make(map[string]interface{}) + err = ctx.ShouldBindJSON(&settings) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest) + } + return nil, t.ThemeService.SaveThemeSettings(ctx, themeID, settings) +} + +func (t *ThemeHandler) DeleteThemeByID(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + isDeleteSetting, err := util.GetQueryBool(ctx, "deleteSettings", false) + if err != nil { + return nil, err + } + return nil, t.ThemeService.DeleteTheme(ctx, themeID, isDeleteSetting) +} + +func (t *ThemeHandler) UploadTheme(ctx *gin.Context) (interface{}, error) { + fileHeader, err := ctx.FormFile("file") + if err != nil { + return nil, xerr.WithMsg(err, "upload theme error").WithStatus(xerr.StatusBadRequest) + } + return t.ThemeService.UploadTheme(ctx, fileHeader) +} + +func (t *ThemeHandler) UpdateThemeByUpload(ctx *gin.Context) (interface{}, error) { + themeID, err := util.ParamString(ctx, "themeID") + if err != nil { + return nil, err + } + fileHeader, err := ctx.FormFile("file") + if err != nil { + return nil, xerr.WithMsg(err, "upload theme error").WithStatus(xerr.StatusBadRequest) + } + return t.ThemeService.UpdateThemeByUpload(ctx, themeID, fileHeader) +} + +func (t *ThemeHandler) FetchTheme(ctx *gin.Context) (interface{}, error) { + return nil, xerr.WithMsg(nil, "not support").WithStatus(xerr.StatusInternalServerError) +} + +func (t *ThemeHandler) UpdateThemeByFetching(ctx *gin.Context) (interface{}, error) { + return nil, xerr.WithMsg(nil, "not support").WithStatus(xerr.StatusInternalServerError) +} + +func (t *ThemeHandler) ReloadTheme(ctx *gin.Context) (interface{}, error) { + return nil, t.ThemeService.ReloadTheme(ctx) +} + +func (t *ThemeHandler) TemplateExist(ctx *gin.Context) (interface{}, error) { + template, err := util.MustGetQueryString(ctx, "template") + if err != nil { + return nil, err + } + return t.ThemeService.TemplateExist(ctx, template) +} diff --git a/handler/admin/user.go b/handler/admin/user.go new file mode 100644 index 00000000..1c602b72 --- /dev/null +++ b/handler/admin/user.go @@ -0,0 +1,126 @@ +package admin + +import ( + "github.com/gin-gonic/gin" + "github.com/go-playground/validator/v10" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/trans" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/vo" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/impl" + "github.com/go-sonic/sonic/util/xerr" +) + +type UserHandler struct { + UserService service.UserService + TwoFactorMFAService service.TwoFactorTOTPMFAService +} + +func NewUserHandler(userService service.UserService, twoFactorMFAService service.TwoFactorTOTPMFAService) *UserHandler { + return &UserHandler{ + UserService: userService, + TwoFactorMFAService: twoFactorMFAService, + } +} + +func (u *UserHandler) GetCurrentUserProfile(ctx *gin.Context) (interface{}, error) { + user, ok := impl.GetAuthorizedUser(ctx) + if !ok { + return nil, xerr.Forbidden.New("authorized user nil").WithStatus(xerr.StatusForbidden) + } + return u.UserService.ConvertToDTO(ctx, user), nil +} + +func (u *UserHandler) UpdateUserProfile(ctx *gin.Context) (interface{}, error) { + userParam := ¶m.User{} + err := ctx.ShouldBindJSON(userParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + user, err := u.UserService.Update(ctx, userParam) + if err != nil { + return nil, err + } + return u.UserService.ConvertToDTO(ctx, user), nil +} + +func (u *UserHandler) UpdatePassword(ctx *gin.Context) (interface{}, error) { + type Password struct { + OldPassword string `json:"oldPassword" form:"oldPassword" binding:"gte=1,lte=100"` + NewPassword string `json:"newPassword" form:"newPassword" binding:"gte=1,lte=100"` + } + passwordParam := &Password{} + err := ctx.ShouldBindJSON(passwordParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + return nil, u.UserService.UpdatePassword(ctx, passwordParam.OldPassword, passwordParam.NewPassword) +} + +func (u *UserHandler) GenerateMFAQRCode(ctx *gin.Context) (interface{}, error) { + type Param struct { + MFAType *consts.MFAType `json:"mfaType"` + } + param := &Param{} + err := ctx.ShouldBindJSON(param) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if param.MFAType == nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + user, ok := impl.GetAuthorizedUser(ctx) + if !ok || user == nil { + return nil, xerr.Forbidden.New("").WithMsg("unauthorized").WithStatus(xerr.StatusForbidden) + } + + mfaFactorAuthDTO := &vo.MFAFactorAuth{} + if *param.MFAType == consts.MFATFATotp { + key, url, err := u.TwoFactorMFAService.GenerateOTPKey(ctx, user.Nickname) + if err != nil { + return nil, err + } + mfaFactorAuthDTO.MFAType = consts.MFATFATotp + mfaFactorAuthDTO.OptAuthUrl = url + mfaFactorAuthDTO.MFAKey = key + qrCode, err := u.TwoFactorMFAService.GenerateMFAQRCode(ctx, url) + if err != nil { + return nil, err + } + mfaFactorAuthDTO.QRImage = qrCode + return mfaFactorAuthDTO, nil + } else { + return nil, xerr.WithMsg(nil, "Not supported authentication").WithStatus(xerr.StatusBadRequest) + } +} + +func (u *UserHandler) UpdateMFA(ctx *gin.Context) (interface{}, error) { + type Param struct { + MFAType *consts.MFAType `json:"mfaType" form:"mfaType"` + MFAKey string `json:"mfaKey" form:"mfaKey"` + AuthCode string `json:"authcode" form:"authcode" binding:"gte=6,lte=6"` + } + mfaParam := &Param{} + err := ctx.ShouldBindJSON(mfaParam) + if err != nil { + if e, ok := err.(validator.ValidationErrors); ok { + return nil, xerr.WithStatus(e, xerr.StatusBadRequest).WithMsg(trans.Translate(e)) + } + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + if mfaParam.MFAType == nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("parameter error") + } + return u.UserService.UpdateMFA(ctx, mfaParam.MFAKey, *mfaParam.MFAType, mfaParam.AuthCode), nil +} diff --git a/handler/binding/form_binding.go b/handler/binding/form_binding.go new file mode 100644 index 00000000..3e68d2ca --- /dev/null +++ b/handler/binding/form_binding.go @@ -0,0 +1,59 @@ +package binding + +import ( + "net/http" + + "github.com/gin-gonic/gin/binding" +) + +const defaultMemory = 32 << 20 + +// CustomFormBinding If the type implements the UnmarshalJSON interface, use JSON to bind +// For the purpose of support enum string to turn the enum type binding +var CustomFormBinding = customFormBinding{} +var CustomFormPostBinding = customFormPostBinding{} + +type ( + customFormBinding struct{} + customFormPostBinding struct{} +) + +func (customFormBinding) Name() string { + return "form" +} + +func (customFormBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := req.ParseMultipartForm(defaultMemory); err != nil { + if err != http.ErrNotMultipart { + return err + } + } + if err := mapForm(obj, req.Form); err != nil { + return err + } + return validate(obj) +} + +func (customFormPostBinding) Name() string { + return "form-urlencoded" +} + +func (customFormPostBinding) Bind(req *http.Request, obj interface{}) error { + if err := req.ParseForm(); err != nil { + return err + } + if err := mapForm(obj, req.PostForm); err != nil { + return err + } + return validate(obj) +} + +func validate(obj interface{}) error { + if binding.Validator == nil { + return nil + } + return binding.Validator.ValidateStruct(obj) +} diff --git a/handler/binding/form_mapping.go b/handler/binding/form_mapping.go new file mode 100644 index 00000000..625b2922 --- /dev/null +++ b/handler/binding/form_mapping.go @@ -0,0 +1,417 @@ +package binding + +import ( + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" + "time" + + "github.com/go-sonic/sonic/util" +) + +var errUnknownType = errors.New("unknown type") + +func mapForm(ptr interface{}, form map[string][]string) error { + return mapFormByTag(ptr, form, "form") +} + +var emptyField = reflect.StructField{} + +func mapFormByTag(ptr interface{}, form map[string][]string, tag string) error { + // Check if ptr is a map + ptrVal := reflect.ValueOf(ptr) + var pointed interface{} + if ptrVal.Kind() == reflect.Ptr { + ptrVal = ptrVal.Elem() + pointed = ptrVal.Interface() + } + if ptrVal.Kind() == reflect.Map && + ptrVal.Type().Key().Kind() == reflect.String { + if pointed != nil { + ptr = pointed + } + return setFormMap(ptr, form) + } + + return mappingByPtr(ptr, formSource(form), tag) +} + +// setter tries to set value on a walking by fields of a struct +type setter interface { + TrySet(value reflect.Value, field reflect.StructField, key string, opt setOptions) (isSetted bool, err error) +} + +type formSource map[string][]string + +var _ setter = formSource(nil) + +// TrySet tries to set a value by request's form source (like map[string][]string) +func (form formSource) TrySet(value reflect.Value, field reflect.StructField, tagValue string, opt setOptions) (isSetted bool, err error) { + return setByForm(value, field, form, tagValue, opt) +} + +func mappingByPtr(ptr interface{}, setter setter, tag string) error { + _, err := mapping(reflect.ValueOf(ptr), emptyField, setter, tag) + return err +} + +func mapping(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + if field.Tag.Get(tag) == "-" { // just ignoring this field + return false, nil + } + + vKind := value.Kind() + + if vKind == reflect.Ptr { + var isNew, isSetted bool + vPtr := value + if value.IsNil() { + isNew = true + vPtr = reflect.New(value.Type().Elem()) + } + if value.NumMethod() > 0 && value.CanInterface() { + if _, ok := value.Interface().(json.Unmarshaler); ok { + ok, err := tryToSetValue(vPtr, field, setter, tag) + if err != nil { + return false, err + } + if ok { + isSetted = true + } + if isNew && isSetted { + value.Set(vPtr) + } + return isSetted, nil + } + } + isSetted, err := mapping(vPtr.Elem(), field, setter, tag) + if err != nil { + return false, err + } + if isNew && isSetted { + value.Set(vPtr) + } + return isSetted, nil + } + + if vKind != reflect.Struct || !field.Anonymous { + ok, err := tryToSetValue(value, field, setter, tag) + if err != nil { + return false, err + } + if ok { + return true, nil + } + } + + if vKind == reflect.Struct { + tValue := value.Type() + + var isSetted bool + for i := 0; i < value.NumField(); i++ { + sf := tValue.Field(i) + if sf.PkgPath != "" && !sf.Anonymous { // unexported + continue + } + ok, err := mapping(value.Field(i), tValue.Field(i), setter, tag) + if err != nil { + return false, err + } + isSetted = isSetted || ok + } + return isSetted, nil + } + return false, nil +} + +type setOptions struct { + isDefaultExists bool + defaultValue string +} + +func tryToSetValue(value reflect.Value, field reflect.StructField, setter setter, tag string) (bool, error) { + var tagValue string + var setOpt setOptions + + tagValue = field.Tag.Get(tag) + tagValue, opts := head(tagValue, ",") + + if tagValue == "" { // default value is FieldName + tagValue = field.Name + } + if tagValue == "" { // when field is "emptyField" variable + return false, nil + } + + var opt string + for len(opts) > 0 { + opt, opts = head(opts, ",") + + if k, v := head(opt, "="); k == "default" { + setOpt.isDefaultExists = true + setOpt.defaultValue = v + } + } + + return setter.TrySet(value, field, tagValue, setOpt) +} + +func setByForm(value reflect.Value, field reflect.StructField, form map[string][]string, tagValue string, opt setOptions) (isSetted bool, err error) { + vs, ok := form[tagValue] + if !ok && !opt.isDefaultExists { + return false, nil + } + + switch value.Kind() { + case reflect.Slice: + if !ok { + vs = []string{opt.defaultValue} + } + return true, setSlice(vs, value, field) + case reflect.Array: + if !ok { + vs = []string{opt.defaultValue} + } + if len(vs) != value.Len() { + return false, fmt.Errorf("%q is not valid value for %s", vs, value.Type().String()) + } + return true, setArray(vs, value, field) + default: + var val string + if !ok { + val = opt.defaultValue + } + + if len(vs) > 0 { + val = vs[0] + } + return true, setWithProperType(val, value, field) + } +} + +func setWithProperType(val string, value reflect.Value, field reflect.StructField) error { + if value.NumMethod() > 0 && value.CanInterface() { + if u, ok := value.Interface().(json.Unmarshaler); ok { + err := u.UnmarshalJSON([]byte("\"" + val + "\"")) + if err != nil { + return err + } + return nil + } + } + switch value.Kind() { + case reflect.Int: + return setIntField(val, 0, value) + case reflect.Int8: + return setIntField(val, 8, value) + case reflect.Int16: + return setIntField(val, 16, value) + case reflect.Int32: + return setIntField(val, 32, value) + case reflect.Int64: + switch value.Interface().(type) { + case time.Duration: + return setTimeDuration(val, value) + } + return setIntField(val, 64, value) + case reflect.Uint: + return setUintField(val, 0, value) + case reflect.Uint8: + return setUintField(val, 8, value) + case reflect.Uint16: + return setUintField(val, 16, value) + case reflect.Uint32: + return setUintField(val, 32, value) + case reflect.Uint64: + return setUintField(val, 64, value) + case reflect.Bool: + return setBoolField(val, value) + case reflect.Float32: + return setFloatField(val, 32, value) + case reflect.Float64: + return setFloatField(val, 64, value) + case reflect.String: + value.SetString(val) + case reflect.Struct: + switch value.Interface().(type) { + case time.Time: + return setTimeField(val, field, value) + } + return json.Unmarshal(util.StringToBytes(val), value.Addr().Interface()) + case reflect.Map: + return json.Unmarshal(util.StringToBytes(val), value.Addr().Interface()) + default: + return errUnknownType + } + return nil +} + +func setIntField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0" + } + intVal, err := strconv.ParseInt(val, 10, bitSize) + if err == nil { + field.SetInt(intVal) + } + return err +} + +func setUintField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0" + } + uintVal, err := strconv.ParseUint(val, 10, bitSize) + if err == nil { + field.SetUint(uintVal) + } + return err +} + +func setBoolField(val string, field reflect.Value) error { + if val == "" { + val = "false" + } + boolVal, err := strconv.ParseBool(val) + if err == nil { + field.SetBool(boolVal) + } + return err +} + +func setFloatField(val string, bitSize int, field reflect.Value) error { + if val == "" { + val = "0.0" + } + floatVal, err := strconv.ParseFloat(val, bitSize) + if err == nil { + field.SetFloat(floatVal) + } + return err +} + +func setTimeField(val string, structField reflect.StructField, value reflect.Value) error { + timeFormat := structField.Tag.Get("time_format") + if timeFormat == "" { + timeFormat = time.RFC3339 + } + + switch tf := strings.ToLower(timeFormat); tf { + case "unix", "unixnano": + tv, err := strconv.ParseInt(val, 10, 64) + if err != nil { + return err + } + + d := time.Duration(1) + if tf == "unixnano" { + d = time.Second + } + + t := time.Unix(tv/int64(d), tv%int64(d)) + value.Set(reflect.ValueOf(t)) + return nil + } + + if val == "" { + value.Set(reflect.ValueOf(time.Time{})) + return nil + } + + l := time.Local + if isUTC, _ := strconv.ParseBool(structField.Tag.Get("time_utc")); isUTC { + l = time.UTC + } + + if locTag := structField.Tag.Get("time_location"); locTag != "" { + loc, err := time.LoadLocation(locTag) + if err != nil { + return err + } + l = loc + } + + t, err := time.ParseInLocation(timeFormat, val, l) + if err != nil { + return err + } + + value.Set(reflect.ValueOf(t)) + return nil +} + +func setArray(vals []string, value reflect.Value, field reflect.StructField) error { + for i, s := range vals { + iv := value.Index(i) + switch iv.Kind() { + case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer, reflect.Slice: + if iv.IsNil() { + if iv.Type().Kind() == reflect.Pointer { + zeroV := reflect.New(iv.Type().Elem()) + iv.Set(zeroV) + } + } + } + err := setWithProperType(s, iv, field) + if err != nil { + return err + } + } + return nil +} + +func setSlice(vals []string, value reflect.Value, field reflect.StructField) error { + slice := reflect.MakeSlice(value.Type(), len(vals), len(vals)) + err := setArray(vals, slice, field) + if err != nil { + return err + } + value.Set(slice) + return nil +} + +func setTimeDuration(val string, value reflect.Value) error { + d, err := time.ParseDuration(val) + if err != nil { + return err + } + value.Set(reflect.ValueOf(d)) + return nil +} + +func head(str, sep string) (head string, tail string) { + idx := strings.Index(str, sep) + if idx < 0 { + return str, "" + } + return str[:idx], str[idx+len(sep):] +} + +func setFormMap(ptr interface{}, form map[string][]string) error { + el := reflect.TypeOf(ptr).Elem() + + if el.Kind() == reflect.Slice { + ptrMap, ok := ptr.(map[string][]string) + if !ok { + return errors.New("cannot convert to map slices of strings") + } + for k, v := range form { + ptrMap[k] = v + } + + return nil + } + + ptrMap, ok := ptr.(map[string]string) + if !ok { + return errors.New("cannot convert to map of strings") + } + for k, v := range form { + ptrMap[k] = v[len(v)-1] // pick last + } + + return nil +} diff --git a/handler/content/api/archive.go b/handler/content/api/archive.go new file mode 100644 index 00000000..46f9f414 --- /dev/null +++ b/handler/content/api/archive.go @@ -0,0 +1,37 @@ +package api + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" +) + +type ArchiveHandler struct { + PostService service.PostService + PostAssembler assembler.PostAssembler +} + +func NewArchiveHandler(postService service.PostService, postAssemeber assembler.PostAssembler) *ArchiveHandler { + return &ArchiveHandler{ + PostService: postService, + PostAssembler: postAssemeber, + } +} + +func (a *ArchiveHandler) ListYearArchives(ctx *gin.Context) (interface{}, error) { + posts, err := a.PostService.GetByStatus(ctx, []consts.PostStatus{consts.PostStatusPublished}, consts.PostTypePost, nil) + if err != nil { + return nil, err + } + return a.PostAssembler.ConvertToArchiveYearVOs(ctx, posts) +} + +func (a *ArchiveHandler) ListMonthArchives(ctx *gin.Context) (interface{}, error) { + posts, err := a.PostService.GetByStatus(ctx, []consts.PostStatus{consts.PostStatusPublished}, consts.PostTypePost, nil) + if err != nil { + return nil, err + } + return a.PostAssembler.ConvertTOArchiveMonthVOs(ctx, posts) +} diff --git a/handler/content/api/category.go b/handler/content/api/category.go new file mode 100644 index 00000000..74ec9f1f --- /dev/null +++ b/handler/content/api/category.go @@ -0,0 +1,93 @@ +package api + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/content/authentication" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type CategoryHandler struct { + PostService service.PostService + CategoryService service.CategoryService + CategoryAuthentication authentication.CategoryAuthentication + PostAssembler assembler.PostAssembler +} + +func NewCategoryHandler(postService service.PostService, categoryService service.CategoryService, categoryAuthentication *authentication.CategoryAuthentication, postAssembler assembler.PostAssembler) *CategoryHandler { + return &CategoryHandler{ + PostService: postService, + CategoryService: categoryService, + CategoryAuthentication: *categoryAuthentication, + PostAssembler: postAssembler, + } +} + +func (c *CategoryHandler) ListCategories(ctx *gin.Context) (interface{}, error) { + categoryQuery := struct { + *param.Sort + More *bool `json:"more" form:"more"` + }{} + + err := ctx.ShouldBindQuery(&categoryQuery) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if categoryQuery.Sort == nil || len(categoryQuery.Sort.Fields) == 0 { + categoryQuery.Sort = ¶m.Sort{Fields: []string{"updateTime,desc"}} + } + if categoryQuery.More != nil && *categoryQuery.More { + return c.CategoryService.ListCategoryWithPostCountDTO(ctx, categoryQuery.Sort) + } + categories, err := c.CategoryService.ListAll(ctx, categoryQuery.Sort) + if err != nil { + return nil, err + } + return c.CategoryService.ConvertToCategoryDTOs(ctx, categories) +} + +func (c *CategoryHandler) ListPosts(ctx *gin.Context) (interface{}, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return nil, err + } + category, err := c.CategoryService.GetBySlug(ctx, slug) + if err != nil { + return nil, err + } + postQuery := param.PostQuery{} + err = ctx.ShouldBindWith(&postQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if postQuery.Sort == nil { + postQuery.Sort = ¶m.Sort{Fields: []string{"topPriority,desc", "updateTime,desc"}} + } + password, _ := util.MustGetQueryString(ctx, "password") + + if category.Type == consts.CategoryTypeIntimate { + token, _ := ctx.Cookie("authentication") + if authenticated, _ := c.CategoryAuthentication.IsAuthenticated(ctx, token, category.ID); !authenticated { + token, err := c.CategoryAuthentication.Authenticate(ctx, token, category.ID, password) + if err != nil { + return nil, err + } + ctx.SetCookie("authentication", token, 1800, "/", "", false, true) + } + } + postQuery.WithPassword = util.BoolPtr(false) + postQuery.Statuses = []*consts.PostStatus{consts.PostStatusPublished.Ptr(), consts.PostStatusIntimate.Ptr()} + posts, totalCount, err := c.PostService.Page(ctx, postQuery) + if err != nil { + return nil, err + } + postVOs, err := c.PostAssembler.ConvertToListVO(ctx, posts) + return dto.NewPage(postVOs, totalCount, postQuery.Page), err +} diff --git a/handler/content/api/init.go b/handler/content/api/init.go new file mode 100644 index 00000000..d0f4ec64 --- /dev/null +++ b/handler/content/api/init.go @@ -0,0 +1,15 @@ +package api + +import "github.com/go-sonic/sonic/injection" + +func init() { + injection.Provide( + NewArchiveHandler, + NewCategoryHandler, + NewJournalHandler, + NewLinkHandler, + NewPostHandler, + NewSheetHandler, + NewOptionHandler, + ) +} diff --git a/handler/content/api/journal.go b/handler/content/api/journal.go new file mode 100644 index 00000000..4c15079c --- /dev/null +++ b/handler/content/api/journal.go @@ -0,0 +1,232 @@ +package api + +import ( + "html/template" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type JournalHandler struct { + JournalService service.JournalService + JournalCommentService service.JournalCommentService + OptionService service.ClientOptionService + JournalCommentAssembler assembler.JournalCommentAssembler +} + +func NewJournalHandler( + journalService service.JournalService, + journalCommentService service.JournalCommentService, + optionService service.ClientOptionService, + journalCommentAssembler assembler.JournalCommentAssembler, +) *JournalHandler { + return &JournalHandler{ + JournalService: journalService, + JournalCommentService: journalCommentService, + OptionService: optionService, + JournalCommentAssembler: journalCommentAssembler, + } +} + +func (j *JournalHandler) ListJournal(ctx *gin.Context) (interface{}, error) { + var journalQuery param.JournalQuery + err := ctx.ShouldBindWith(&journalQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + journalQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + journalQuery.JournalType = consts.JournalTypePublic.Ptr() + journals, totalCount, err := j.JournalService.ListJournal(ctx, journalQuery) + if err != nil { + return nil, err + } + journalDTOs, err := j.JournalService.ConvertToWithCommentDTOList(ctx, journals) + if err != nil { + return nil, err + } + return dto.NewPage(journalDTOs, totalCount, journalQuery.Page), nil +} + +func (j *JournalHandler) GetJournal(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + journals, err := j.JournalService.GetByJournalIDs(ctx, []int32{journalID}) + if err != nil { + return nil, err + } + if len(journals) == 0 { + return nil, xerr.WithStatus(nil, xerr.StatusBadRequest) + } + journalDTOs, err := j.JournalService.ConvertToWithCommentDTOList(ctx, []*entity.Journal{journals[journalID]}) + if err != nil { + return nil, err + } + return journalDTOs[0], nil +} + +func (j *JournalHandler) ListTopComment(ctx *gin.Context) (interface{}, error) { + + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + pageSize := j.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &journalID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, totalCount, err := j.JournalCommentService.Page(ctx, commentQuery, consts.CommentTypeJournal) + if err != nil { + return nil, err + } + _ = j.JournalCommentAssembler.ClearSensitiveField(ctx, comments) + commenVOs, err := j.JournalCommentAssembler.ConvertToWithHasChildren(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commenVOs, totalCount, commentQuery.Page), nil +} + +func (j *JournalHandler) ListChildren(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + parentID, err := util.ParamInt64(ctx, "parentID") + if err != nil { + return nil, err + } + children, err := j.JournalCommentService.GetChildren(ctx, parentID, journalID) + if err != nil { + return nil, err + } + _ = j.JournalCommentAssembler.ClearSensitiveField(ctx, children) + return j.JournalCommentAssembler.ConvertToDTOList(ctx, children) +} + +func (j *JournalHandler) ListCommentTree(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + pageSize := j.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &journalID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + allComments, err := j.JournalCommentService.GetByContentID(ctx, journalID, commentQuery.Sort) + if err != nil { + return nil, err + } + _ = j.JournalCommentAssembler.ClearSensitiveField(ctx, allComments) + commentVOs, total, err := j.JournalCommentAssembler.PageConvertToVOs(ctx, allComments, commentQuery.Page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, total, commentQuery.Page), nil +} + +func (j *JournalHandler) ListComment(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + pageSize := j.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &journalID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, total, err := j.JournalCommentService.Page(ctx, commentQuery, consts.CommentTypeJournal) + if err != nil { + return nil, err + } + _ = j.JournalCommentAssembler.ClearSensitiveField(ctx, comments) + result, err := j.JournalCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(result, total, commentQuery.Page), nil +} + +func (j *JournalHandler) CreateComment(ctx *gin.Context) (interface{}, error) { + p := param.Comment{} + err := ctx.ShouldBindJSON(&p) + if err != nil { + return nil, err + } + p.Author = template.HTMLEscapeString(p.Author) + p.AuthorURL = template.HTMLEscapeString(p.AuthorURL) + p.Content = template.HTMLEscapeString(p.Content) + p.Email = template.HTMLEscapeString(p.Email) + p.CommentType = consts.CommentTypeJournal + result, err := j.JournalCommentService.CreateBy(ctx, &p) + if err != nil { + return nil, err + } + return j.JournalCommentAssembler.ConvertToDTO(ctx, result) +} + +func (j *JournalHandler) Like(ctx *gin.Context) (interface{}, error) { + journalID, err := util.ParamInt32(ctx, "journalID") + if err != nil { + return nil, err + } + err = j.JournalService.IncreaseLike(ctx, journalID) + if err != nil { + return nil, err + } + return nil, err +} diff --git a/handler/content/api/link.go b/handler/content/api/link.go new file mode 100644 index 00000000..a04ff7cd --- /dev/null +++ b/handler/content/api/link.go @@ -0,0 +1,58 @@ +package api + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/service" +) + +type LinkHandler struct { + LinkService service.LinkService +} + +func NewLinkHandler(linkService service.LinkService) *LinkHandler { + return &LinkHandler{ + LinkService: linkService, + } +} + +type linkParam struct { + *param.Sort +} + +func (l *LinkHandler) ListLinks(ctx *gin.Context) (interface{}, error) { + p := linkParam{} + if err := ctx.ShouldBindQuery(&p); err != nil { + return nil, err + } + + if p.Sort == nil || len(p.Sort.Fields) == 0 { + p.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + links, err := l.LinkService.List(ctx, p.Sort) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToDTOs(ctx, links), nil +} + +func (l *LinkHandler) LinkTeamVO(ctx *gin.Context) (interface{}, error) { + p := linkParam{} + if err := ctx.ShouldBindQuery(&p); err != nil { + return nil, err + } + + if p.Sort == nil || len(p.Sort.Fields) == 0 { + p.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + links, err := l.LinkService.List(ctx, p.Sort) + if err != nil { + return nil, err + } + return l.LinkService.ConvertToLinkTeamVO(ctx, links), nil +} diff --git a/handler/content/api/options.go b/handler/content/api/options.go new file mode 100644 index 00000000..e121c1ae --- /dev/null +++ b/handler/content/api/options.go @@ -0,0 +1,29 @@ +package api + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" +) + +type OptionHandler struct { + OptionService service.OptionService +} + +func NewOptionHandler( + optionService service.OptionService, +) *OptionHandler { + return &OptionHandler{ + OptionService: optionService, + } +} + +func (o *OptionHandler) Comment(ctx *gin.Context) (interface{}, error) { + result := make(map[string]interface{}) + + result[property.CommentGravatarSource.KeyValue] = o.OptionService.GetOrByDefault(ctx, property.CommentGravatarSource) + result[property.CommentGravatarDefault.KeyValue] = o.OptionService.GetOrByDefault(ctx, property.CommentGravatarDefault) + result[property.CommentContentPlaceholder.KeyValue] = o.OptionService.GetOrByDefault(ctx, property.CommentContentPlaceholder) + return result, nil +} diff --git a/handler/content/api/post.go b/handler/content/api/post.go new file mode 100644 index 00000000..f6cb5193 --- /dev/null +++ b/handler/content/api/post.go @@ -0,0 +1,190 @@ +package api + +import ( + "html/template" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type PostHandler struct { + OptionService service.OptionService + PostService service.PostService + PostCommentService service.PostCommentService + PostCommentAssembler assembler.PostCommentAssembler +} + +func NewPostHandler( + optionService service.OptionService, + postService service.PostService, + postCommentService service.PostCommentService, + postCommentAssembler assembler.PostCommentAssembler, +) *PostHandler { + return &PostHandler{ + OptionService: optionService, + PostService: postService, + PostCommentService: postCommentService, + PostCommentAssembler: postCommentAssembler, + } +} + +func (p *PostHandler) ListTopComment(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + pageSize := p.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &postID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, totalCount, err := p.PostCommentService.Page(ctx, commentQuery, consts.CommentTypePost) + if err != nil { + return nil, err + } + _ = p.PostCommentAssembler.ClearSensitiveField(ctx, comments) + commenVOs, err := p.PostCommentAssembler.ConvertToWithHasChildren(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commenVOs, totalCount, commentQuery.Page), nil +} + +func (p *PostHandler) ListChildren(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + parentID, err := util.ParamInt64(ctx, "parentID") + if err != nil { + return nil, err + } + children, err := p.PostCommentService.GetChildren(ctx, parentID, postID) + if err != nil { + return nil, err + } + _ = p.PostCommentAssembler.ClearSensitiveField(ctx, children) + return p.PostCommentAssembler.ConvertToDTOList(ctx, children) +} + +func (p *PostHandler) ListCommentTree(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + pageSize := p.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &postID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + allComments, err := p.PostCommentService.GetByContentID(ctx, postID, commentQuery.Sort) + if err != nil { + return nil, err + } + _ = p.PostCommentAssembler.ClearSensitiveField(ctx, allComments) + commentVOs, total, err := p.PostCommentAssembler.PageConvertToVOs(ctx, allComments, commentQuery.Page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, total, commentQuery.Page), nil +} + +func (p *PostHandler) ListComment(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + pageSize := p.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &postID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, total, err := p.PostCommentService.Page(ctx, commentQuery, consts.CommentTypePost) + if err != nil { + return nil, err + } + _ = p.PostCommentAssembler.ClearSensitiveField(ctx, comments) + result, err := p.PostCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(result, total, commentQuery.Page), nil +} + +func (p *PostHandler) CreateComment(ctx *gin.Context) (interface{}, error) { + comment := param.Comment{} + err := ctx.ShouldBindJSON(&comment) + if err != nil { + return nil, err + } + comment.Author = template.HTMLEscapeString(comment.Author) + comment.AuthorURL = template.HTMLEscapeString(comment.AuthorURL) + comment.Content = template.HTMLEscapeString(comment.Content) + comment.Email = template.HTMLEscapeString(comment.Email) + comment.CommentType = consts.CommentTypePost + result, err := p.PostCommentService.CreateBy(ctx, &comment) + if err != nil { + return nil, err + } + return p.PostCommentAssembler.ConvertToDTO(ctx, result) +} + +func (p *PostHandler) Like(ctx *gin.Context) (interface{}, error) { + postID, err := util.ParamInt32(ctx, "postID") + if err != nil { + return nil, err + } + err = p.PostService.IncreaseLike(ctx, postID) + if err != nil { + return nil, err + } + return nil, err +} diff --git a/handler/content/api/sheet.go b/handler/content/api/sheet.go new file mode 100644 index 00000000..627f27ba --- /dev/null +++ b/handler/content/api/sheet.go @@ -0,0 +1,178 @@ +package api + +import ( + "html/template" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type SheetHandler struct { + OptionService service.OptionService + SheetService service.SheetService + SheetCommentService service.SheetCommentService + SheetCommentAssembler assembler.SheetCommentAssembler +} + +func NewSheetHandler( + optionService service.OptionService, + sheetService service.SheetService, + sheetCommentService service.SheetCommentService, + sheetCommentAssembler assembler.SheetCommentAssembler, +) *SheetHandler { + return &SheetHandler{ + OptionService: optionService, + SheetService: sheetService, + SheetCommentService: sheetCommentService, + SheetCommentAssembler: sheetCommentAssembler, + } +} + +func (j *SheetHandler) ListTopComment(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + pageSize := j.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &sheetID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, totalCount, err := j.SheetCommentService.Page(ctx, commentQuery, consts.CommentTypeSheet) + if err != nil { + return nil, err + } + _ = j.SheetCommentAssembler.ClearSensitiveField(ctx, comments) + commenVOs, err := j.SheetCommentAssembler.ConvertToWithHasChildren(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(commenVOs, totalCount, commentQuery.Page), nil +} + +func (j *SheetHandler) ListChildren(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + parentID, err := util.ParamInt64(ctx, "parentID") + if err != nil { + return nil, err + } + children, err := j.SheetCommentService.GetChildren(ctx, parentID, sheetID) + if err != nil { + return nil, err + } + _ = j.SheetCommentAssembler.ClearSensitiveField(ctx, children) + return j.SheetCommentAssembler.ConvertToDTOList(ctx, children) +} + +func (p *SheetHandler) ListCommentTree(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + pageSize := p.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &sheetID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + allComments, err := p.SheetCommentService.GetByContentID(ctx, sheetID, commentQuery.Sort) + if err != nil { + return nil, err + } + _ = p.SheetCommentAssembler.ClearSensitiveField(ctx, allComments) + commentVOs, total, err := p.SheetCommentAssembler.PageConvertToVOs(ctx, allComments, commentQuery.Page) + if err != nil { + return nil, err + } + return dto.NewPage(commentVOs, total, commentQuery.Page), nil +} + +func (p *SheetHandler) ListComment(ctx *gin.Context) (interface{}, error) { + sheetID, err := util.ParamInt32(ctx, "sheetID") + if err != nil { + return nil, err + } + pageSize := p.OptionService.GetOrByDefault(ctx, property.CommentPageSize).(int) + + commentQuery := param.CommentQuery{} + err = ctx.ShouldBindWith(&commentQuery, binding.CustomFormBinding) + if err != nil { + return nil, xerr.WithStatus(err, xerr.StatusBadRequest).WithMsg("Parameter error") + } + if commentQuery.Sort != nil && len(commentQuery.Fields) > 0 { + commentQuery.Sort = ¶m.Sort{ + Fields: []string{"createTime,desc"}, + } + } + commentQuery.ContentID = &sheetID + commentQuery.Keyword = nil + commentQuery.CommentStatus = consts.CommentStatusPublished.Ptr() + commentQuery.PageSize = pageSize + commentQuery.ParentID = util.Int32Ptr(0) + + comments, total, err := p.SheetCommentService.Page(ctx, commentQuery, consts.CommentTypeSheet) + if err != nil { + return nil, err + } + _ = p.SheetCommentAssembler.ClearSensitiveField(ctx, comments) + result, err := p.SheetCommentAssembler.ConvertToWithParentVO(ctx, comments) + if err != nil { + return nil, err + } + return dto.NewPage(result, total, commentQuery.Page), nil +} + +func (p *SheetHandler) CreateComment(ctx *gin.Context) (interface{}, error) { + comment := param.Comment{} + err := ctx.ShouldBindJSON(&p) + if err != nil { + return nil, err + } + comment.Author = template.HTMLEscapeString(comment.Author) + comment.AuthorURL = template.HTMLEscapeString(comment.AuthorURL) + comment.Content = template.HTMLEscapeString(comment.Content) + comment.Email = template.HTMLEscapeString(comment.Email) + comment.CommentType = consts.CommentTypeSheet + result, err := p.SheetCommentService.CreateBy(ctx, &comment) + if err != nil { + return nil, err + } + return p.SheetCommentAssembler.ConvertToDTO(ctx, result) +} diff --git a/handler/content/archive.go b/handler/content/archive.go new file mode 100644 index 00000000..9fcb2a66 --- /dev/null +++ b/handler/content/archive.go @@ -0,0 +1,83 @@ +package content + +import ( + "strconv" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type ArchiveHandler struct { + OptionService service.OptionService + PostService service.PostService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService + PostAssembler assembler.PostAssembler + PostModel *model.PostModel +} + +func NewArchiveHandler( + optionService service.OptionService, + postService service.PostService, + categoryService service.CategoryService, + postCategoryService service.PostCategoryService, + postAssembler assembler.PostAssembler, + postModel *model.PostModel, +) *ArchiveHandler { + return &ArchiveHandler{ + OptionService: optionService, + PostService: postService, + PostCategoryService: postCategoryService, + CategoryService: categoryService, + PostAssembler: postAssembler, + PostModel: postModel, + } +} + +func (a *ArchiveHandler) Archives(ctx *gin.Context, model template.Model) (string, error) { + return a.PostModel.Archives(ctx, 0, model) +} +func (a *ArchiveHandler) ArchivesPage(ctx *gin.Context, model template.Model) (string, error) { + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + return a.PostModel.Archives(ctx, int(page-1), model) +} + +func (a *ArchiveHandler) ArchivesBySlug(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + + postPermalinkType, err := a.OptionService.GetPostPermalinkType(ctx) + if err != nil { + return "", err + } + var post *entity.Post + if postPermalinkType == consts.PostPermalinkTypeDefault { + post, err = a.PostService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + } else if postPermalinkType == consts.PostPermalinkTypeID { + postID, err := strconv.ParseInt(slug, 10, 32) + if err != nil { + return "", err + } + post, err = a.PostService.GetByPostID(ctx, int32(postID)) + if err != nil { + return "", err + } + } + token, _ := ctx.Cookie("authentication") + return a.PostModel.Content(ctx, post, token, model) +} diff --git a/handler/content/authentication/category_authentication.go b/handler/content/authentication/category_authentication.go new file mode 100644 index 00000000..43c5946e --- /dev/null +++ b/handler/content/authentication/category_authentication.go @@ -0,0 +1,146 @@ +package authentication + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/golang-jwt/jwt" + + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type CategoryAuthentication struct { + OptionService service.OptionService + CategoryService service.CategoryService +} + +func NewCategoryAuthentication( + optionService service.OptionService, + categoryService service.CategoryService, +) *CategoryAuthentication { + return &CategoryAuthentication{ + OptionService: optionService, + CategoryService: categoryService, + } +} + +// Authenticate implements ContentAuthentication +func (c *CategoryAuthentication) Authenticate(ctx context.Context, token string, id int32, password string) (string, error) { + category, err := c.CategoryService.GetByID(ctx, id) + if err != nil { + return "", err + } + if category.Password == "" && category.ParentID == 0 { + return "", nil + } + if category.Password == "" { + categories, err := c.CategoryService.ListAll(ctx, nil) + if err != nil { + return "", err + } + categoryMap := make(map[int32]*entity.Category) + for _, category := range categories { + categoryMap[category.ID] = category + } + parentID := category.ParentID + parentIDs := make([]int32, 0) + for { + parentCategory, ok := categoryMap[parentID] + if !ok || parentCategory == nil { + return "", nil + } + if parentCategory.Password == "" { + parentID = parentCategory.ParentID + parentIDs = append(parentIDs, parentID) + } else if parentCategory.Password == password { + return c.doAuthenticate(ctx, token, parentIDs...) + } else { + return "", xerr.WithMsg(nil, "密码不正确").WithStatus(http.StatusUnauthorized) + } + } + } else if category.Password == password { + return c.doAuthenticate(ctx, token, id) + } else { + return "", xerr.WithMsg(nil, "密码不正确").WithStatus(http.StatusUnauthorized) + } +} + +func (c *CategoryAuthentication) IsAuthenticated(ctx context.Context, tokenStr string, id int32) (bool, error) { + if tokenStr == "" { + return false, nil + } + secret, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.JWTSecret, "") + if err != nil { + return false, err + } + if secret.(string) == "" { + return false, xerr.WithMsg(nil, "jwt secret is nil").WithStatus(xerr.StatusInternalServerError) + } + token, err := jwt.ParseWithClaims(tokenStr, &customClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret.(string)), nil + }) + if err != nil { + return false, err + } + claims, ok := token.Claims.(*customClaims) + if !ok || !token.Valid || claims == nil { + return false, nil + } + + for _, categoryID := range claims.CategoryIDs { + if categoryID == id { + return true, nil + } + } + + return false, nil +} + +func (c *CategoryAuthentication) doAuthenticate(ctx context.Context, tokenStr string, id ...int32) (string, error) { + secret, err := c.OptionService.GetOrByDefaultWithErr(ctx, property.JWTSecret, "") + if err != nil { + return "", err + } + if secret.(string) == "" { + return "", xerr.WithMsg(nil, "jwt secret is nil").WithStatus(xerr.StatusInternalServerError) + } + var claims *customClaims + + if tokenStr != "" { + token, err := jwt.ParseWithClaims(tokenStr, &customClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret.(string)), nil + }) + if err == nil { + if c, ok := token.Claims.(*customClaims); ok && token.Valid { + claims = c + } + } + } + if claims == nil { + claims = &customClaims{ + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + IssuedAt: time.Now().Unix(), + }, + } + } + claims.CategoryIDs = append(claims.CategoryIDs, id...) + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString([]byte(secret.(string))) + if err != nil { + return "", xerr.WithStatus(err, xerr.StatusInternalServerError) + } + return ss, nil +} diff --git a/handler/content/authentication/content_authentication.go b/handler/content/authentication/content_authentication.go new file mode 100644 index 00000000..2d97142e --- /dev/null +++ b/handler/content/authentication/content_authentication.go @@ -0,0 +1,18 @@ +package authentication + +import ( + "context" + + "github.com/golang-jwt/jwt" +) + +type ContentAuthentication interface { + Authenticate(ctx context.Context, token string, id int32, password string) (string, error) + IsAuthenticated(ctx context.Context, token string, id int32) (bool, error) +} + +type customClaims struct { + CategoryIDs []int32 `json:"category_ids"` + PostIDs []int32 `json:"post_ids"` + jwt.StandardClaims +} diff --git a/handler/content/authentication/init.go b/handler/content/authentication/init.go new file mode 100644 index 00000000..2fb89cd3 --- /dev/null +++ b/handler/content/authentication/init.go @@ -0,0 +1,10 @@ +package authentication + +import "github.com/go-sonic/sonic/injection" + +func init() { + injection.Provide( + NewCategoryAuthentication, + NewPostAuthentication, + ) +} diff --git a/handler/content/authentication/post_authentication.go b/handler/content/authentication/post_authentication.go new file mode 100644 index 00000000..9728997d --- /dev/null +++ b/handler/content/authentication/post_authentication.go @@ -0,0 +1,190 @@ +package authentication + +import ( + "context" + "fmt" + "net/http" + "time" + + "github.com/golang-jwt/jwt" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type PostAuthentication struct { + OptionService service.OptionService + PostService service.PostService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService +} + +func NewPostAuthentication( + optionService service.OptionService, + postService service.PostService, + categoryService service.CategoryService, + postCategoryService service.PostCategoryService, +) *PostAuthentication { + return &PostAuthentication{ + CategoryService: categoryService, + OptionService: optionService, + PostService: postService, + PostCategoryService: postCategoryService, + } +} + +func (p *PostAuthentication) Authenticate(ctx context.Context, token string, id int32, password string) (string, error) { + post, err := p.PostService.GetByPostID(ctx, id) + if err != nil { + return "", err + } + if post.Password != "" { + if post.Password == password { + return p.doAuthenticate(ctx, token, id) + } else { + return "", xerr.WithMsg(nil, "密码不正确").WithStatus(http.StatusUnauthorized) + } + } + postCategories, err := p.PostCategoryService.ListCategoryByPostID(ctx, id) + if err != nil { + return "", err + } + + for _, category := range postCategories { + if category.Password == password { + return p.doAuthenticate(ctx, token, id) + } + } + + allCategories, err := p.CategoryService.ListAll(ctx, nil) + if err != nil { + return "", err + } + categoryMap := make(map[int32]*entity.Category) + for _, category := range allCategories { + categoryMap[category.ID] = category + } + + for _, postCategory := range postCategories { + parentID := postCategory.ParentID + for { + parentCategory, ok := categoryMap[parentID] + if !ok || parentCategory == nil { + break + } + if parentCategory.Password == "" { + parentID = parentCategory.ParentID + } else if parentCategory.Password == password { + return p.doAuthenticate(ctx, token, id) + } else { + break + } + } + } + return "", xerr.WithMsg(nil, "密码不正确").WithStatus(http.StatusUnauthorized) + +} + +func (p *PostAuthentication) IsAuthenticated(ctx context.Context, tokenStr string, id int32) (bool, error) { + if tokenStr == "" { + return false, nil + } + + secret, err := p.OptionService.GetOrByDefaultWithErr(ctx, property.JWTSecret, "") + if err != nil { + return false, err + } + if secret.(string) == "" { + return false, xerr.WithMsg(nil, "jwt secret is nil").WithStatus(xerr.StatusInternalServerError) + } + + post, err := p.PostService.GetByPostID(ctx, id) + if err != nil { + return false, err + } + + token, err := jwt.ParseWithClaims(tokenStr, &customClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret.(string)), nil + }) + if err != nil { + return false, err + } + claims, ok := token.Claims.(*customClaims) + if !ok || !token.Valid || claims == nil { + return false, nil + } + + for _, postID := range claims.PostIDs { + if postID == id { + return true, nil + } + } + if post.Password != "" { + return false, nil + } + categories, err := p.PostCategoryService.ListCategoryByPostID(ctx, id) + if err != nil { + return false, err + } + + if err != nil { + return false, err + } + for _, categoryID := range claims.CategoryIDs { + for _, category := range categories { + if category.Type == consts.CategoryTypeNormal { + continue + } + if category.ID == categoryID { + return true, nil + } + } + } + return false, nil +} + +func (p *PostAuthentication) doAuthenticate(ctx context.Context, tokenStr string, id int32) (string, error) { + secret, err := p.OptionService.GetOrByDefaultWithErr(ctx, property.JWTSecret, "") + if err != nil { + return "", err + } + if secret.(string) == "" { + return "", xerr.WithMsg(nil, "jwt secret is nil").WithStatus(xerr.StatusInternalServerError) + } + var claims *customClaims + if tokenStr != "" { + token, err := jwt.ParseWithClaims(tokenStr, &customClaims{}, func(token *jwt.Token) (interface{}, error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("Unexpected signing method: %v", token.Header["alg"]) + } + return []byte(secret.(string)), nil + }) + if err == nil { + if c, ok := token.Claims.(*customClaims); ok && token.Valid { + claims = c + } + } + } + if claims == nil { + claims = &customClaims{ + StandardClaims: jwt.StandardClaims{ + ExpiresAt: time.Now().Add(time.Minute * 1).Unix(), + IssuedAt: time.Now().Unix(), + }, + } + } + claims.PostIDs = append(claims.PostIDs, id) + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) + ss, err := token.SignedString([]byte(secret.(string))) + if err != nil { + return "", xerr.WithStatus(err, xerr.StatusInternalServerError) + } + return ss, nil +} diff --git a/handler/content/category.go b/handler/content/category.go new file mode 100644 index 00000000..54784ec7 --- /dev/null +++ b/handler/content/category.go @@ -0,0 +1,67 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type CategoryHandler struct { + OptionService service.OptionService + PostService service.PostService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService + PostAssembler assembler.PostAssembler + PostModel *model.PostModel + CategoryModel *model.CategoryModel +} + +func NewCategoryHandler( + optionService service.OptionService, + postService service.PostService, + categoryService service.CategoryService, + postCategoryService service.PostCategoryService, + postAssembler assembler.PostAssembler, + postModel *model.PostModel, + categoryModel *model.CategoryModel, +) *CategoryHandler { + return &CategoryHandler{ + OptionService: optionService, + PostService: postService, + PostCategoryService: postCategoryService, + CategoryService: categoryService, + PostAssembler: postAssembler, + PostModel: postModel, + CategoryModel: categoryModel, + } +} + +func (c *CategoryHandler) Categories(ctx *gin.Context, model template.Model) (string, error) { + return c.CategoryModel.ListCategories(ctx, model) +} +func (c *CategoryHandler) CategoryDetail(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + token, _ := ctx.Cookie("authentication") + return c.CategoryModel.CategoryDetail(ctx, model, slug, 0, token) +} + +func (c *CategoryHandler) CategoryDetailPage(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + token, _ := ctx.Cookie("authentication") + return c.CategoryModel.CategoryDetail(ctx, model, slug, int(page-1), token) +} diff --git a/handler/content/feed.go b/handler/content/feed.go new file mode 100644 index 00000000..8ae84cef --- /dev/null +++ b/handler/content/feed.go @@ -0,0 +1,187 @@ +package content + +import ( + "context" + "net/http" + "regexp" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/model/vo" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type FeedHandler struct { + OptionService service.OptionService + PostService service.PostService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService + PostAssembler assembler.PostAssembler +} + +func NewFeedHandler(optionService service.OptionService, postService service.PostService, categoryService service.CategoryService, postCategoryService service.PostCategoryService, postAssembler assembler.PostAssembler) *FeedHandler { + return &FeedHandler{ + OptionService: optionService, + PostService: postService, + CategoryService: categoryService, + PostCategoryService: postCategoryService, + PostAssembler: postAssembler, + } +} + +func (f *FeedHandler) Feed(ctx *gin.Context, model template.Model) (string, error) { + _, err := f.Atom(ctx, model) + if err != nil { + return "", err + } + ctx.Header("Content-Type", "application/xml; charset=utf-8") + return "common/web/rss", nil +} + +func (f *FeedHandler) CategoryFeed(ctx *gin.Context, model template.Model) (string, error) { + _, err := f.CategoryAtom(ctx, model) + if err != nil { + return "", err + } + ctx.Header("Content-Type", "application/xml; charset=utf-8") + return "common/web/rss", nil +} + +func (f *FeedHandler) Atom(ctx *gin.Context, model template.Model) (string, error) { + rssPageSize := f.OptionService.GetOrByDefault(ctx, property.RssPageSize).(int) + postQuery := param.PostQuery{ + Page: param.Page{PageNum: 0, PageSize: rssPageSize}, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + } + posts, _, err := f.PostService.Page(ctx, postQuery) + if err != nil { + return "", err + } + postDetailVOs, err := f.buildPost(ctx, posts) + if err != nil { + return "", err + } + lastModified := f.getLastModifiedTime(posts) + ctx.Header("Last-Modified", lastModified.Format(http.TimeFormat)) + ctx.Header("Content-Type", "application/xml; charset=utf-8") + model["lastModified"] = lastModified + model["posts"] = postDetailVOs + return "common/web/atom", nil +} + +func (f *FeedHandler) CategoryAtom(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + slug = strings.TrimSuffix(slug, ".xml") + category, err := f.CategoryService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + categoryDTO, err := f.CategoryService.ConvertToCategoryDTO(ctx, category) + if err != nil { + return "", err + } + + posts, err := f.PostCategoryService.ListByCategoryID(ctx, category.ID, consts.PostStatusPublished) + if err != nil { + return "", err + } + + postDetailVOs, err := f.buildPost(ctx, posts) + if err != nil { + return "", err + } + lastModified := f.getLastModifiedTime(posts) + + model["category"] = categoryDTO + model["posts"] = postDetailVOs + model["lastModified"] = lastModified + ctx.Header("Content-Type", "application/xml; charset=utf-8") + return "common/web/atom", nil +} + +func (f *FeedHandler) Robots(ctx *gin.Context, model template.Model) (string, error) { + ctx.Header("Content-Type", "text/plain;charset=utf-8") + return "common/web/robots", nil +} + +func (f *FeedHandler) SitemapXML(ctx *gin.Context, model template.Model) (string, error) { + posts, _, err := f.PostService.Page(ctx, param.PostQuery{ + Page: param.Page{PageNum: 0, PageSize: int(^uint(0) >> 1)}, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + }) + if err != nil { + return "", err + } + postDetailVOs, err := f.buildPost(ctx, posts) + if err != nil { + return "", err + } + model["posts"] = postDetailVOs + + ctx.Header("Content-Type", "application/xml; charset=utf-8") + return "common/web/sitemap_xml", nil +} + +func (f *FeedHandler) SitemapHTML(ctx *gin.Context, model template.Model) (string, error) { + posts, _, err := f.PostService.Page(ctx, param.PostQuery{ + Page: param.Page{PageNum: 0, PageSize: int(^uint(0) >> 1)}, + Sort: ¶m.Sort{Fields: []string{"createTime,desc"}}, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + }) + if err != nil { + return "", err + } + postDetailVOs, err := f.buildPost(ctx, posts) + if err != nil { + return "", err + } + model["posts"] = postDetailVOs + return "common/web/sitemap_html", nil +} + +func (f *FeedHandler) getLastModifiedTime(posts []*entity.Post) time.Time { + lastModifiedTime := time.Time{} + for _, post := range posts { + if post.EditTime != nil { + if post.EditTime.After(lastModifiedTime) { + lastModifiedTime = *post.EditTime + } + } else { + if post.CreateTime.After(lastModifiedTime) { + lastModifiedTime = post.CreateTime + } + } + } + if lastModifiedTime == (time.Time{}) { + lastModifiedTime = time.Now() + } + return lastModifiedTime +} + +var xmlInValidChar = regexp.MustCompile("[\x00-\x1F\x7F]") + +func (f *FeedHandler) buildPost(ctx context.Context, posts []*entity.Post) ([]*vo.PostDetailVO, error) { + postDetailVOs, err := f.PostAssembler.ConvertToDetailVOs(ctx, posts) + if err != nil { + return nil, err + } + for _, postDetailVO := range postDetailVOs { + postDetailVO.Content = xmlInValidChar.ReplaceAllString(postDetailVO.Content, "") + postDetailVO.Summary = xmlInValidChar.ReplaceAllString(postDetailVO.Summary, "") + } + return postDetailVOs, nil +} diff --git a/handler/content/index.go b/handler/content/index.go new file mode 100644 index 00000000..b45ace7c --- /dev/null +++ b/handler/content/index.go @@ -0,0 +1,31 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type IndexHandler struct { + PostModel *model.PostModel +} + +func NewIndexHandler(postModel *model.PostModel) *IndexHandler { + return &IndexHandler{ + PostModel: postModel, + } +} + +func (h *IndexHandler) Index(ctx *gin.Context, model template.Model) (string, error) { + return h.PostModel.List(ctx, 0, model) +} + +func (h *IndexHandler) IndexPage(ctx *gin.Context, model template.Model) (string, error) { + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + return h.PostModel.List(ctx, int(page)-1, model) +} diff --git a/handler/content/init.go b/handler/content/init.go new file mode 100644 index 00000000..621648db --- /dev/null +++ b/handler/content/init.go @@ -0,0 +1,18 @@ +package content + +import "github.com/go-sonic/sonic/injection" + +func init() { + injection.Provide( + NewIndexHandler, + NewFeedHandler, + NewArchiveHandler, + NewViewHandler, + NewCategoryHandler, + NewSheetHandler, + NewTagHandler, + NewLinkHandler, + NewPhotoHandler, + NewJournalHandler, + ) +} diff --git a/handler/content/journal.go b/handler/content/journal.go new file mode 100644 index 00000000..d82afe4c --- /dev/null +++ b/handler/content/journal.go @@ -0,0 +1,41 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type JournalHandler struct { + OptionService service.OptionService + JournalService service.JournalService + JournalModel *model.JournalModel +} + +func NewJournalHandler( + optionService service.OptionService, + journalService service.JournalService, + journalModel *model.JournalModel, + +) *JournalHandler { + return &JournalHandler{ + OptionService: optionService, + JournalService: journalService, + JournalModel: journalModel, + } +} + +func (p *JournalHandler) JournalsPage(ctx *gin.Context, model template.Model) (string, error) { + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + return p.JournalModel.Journals(ctx, model, int(page-1)) +} + +func (p *JournalHandler) Journals(ctx *gin.Context, model template.Model) (string, error) { + return p.JournalModel.Journals(ctx, model, 0) +} diff --git a/handler/content/link.go b/handler/content/link.go new file mode 100644 index 00000000..aa3e7d4e --- /dev/null +++ b/handler/content/link.go @@ -0,0 +1,24 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/template" +) + +type LinkHandler struct { + LinkModel *model.LinkModel +} + +func NewLinkHandler( + linkModel *model.LinkModel, +) *LinkHandler { + return &LinkHandler{ + LinkModel: linkModel, + } +} + +func (t *LinkHandler) Link(ctx *gin.Context, model template.Model) (string, error) { + return t.LinkModel.Links(ctx, model) +} diff --git a/handler/content/model/category.go b/handler/content/model/category.go new file mode 100644 index 00000000..50a78242 --- /dev/null +++ b/handler/content/model/category.go @@ -0,0 +1,122 @@ +package model + +import ( + "context" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/content/authentication" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" +) + +func NewCategoryModel(optionService service.OptionService, + postService service.PostService, + themeService service.ThemeService, + postCategoryService service.PostCategoryService, + categoryService service.CategoryService, + postTagService service.PostTagService, + tagService service.TagService, + postAssembler assembler.PostAssembler, + metaService service.MetaService, + categoryAuthentication *authentication.CategoryAuthentication, +) *CategoryModel { + return &CategoryModel{ + OptionService: optionService, + PostService: postService, + PostAssembler: postAssembler, + ThemeService: themeService, + PostCategoryService: postCategoryService, + CategoryService: categoryService, + PostTagService: postTagService, + TagService: tagService, + MetaService: metaService, + CategoryAuthentication: categoryAuthentication, + } +} + +type CategoryModel struct { + OptionService service.OptionService + PostService service.PostService + ThemeService service.ThemeService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService + PostTagService service.PostTagService + TagService service.TagService + MetaService service.MetaService + PostAssembler assembler.PostAssembler + CategoryAuthentication *authentication.CategoryAuthentication +} + +func (c *CategoryModel) ListCategories(ctx context.Context, model template.Model) (string, error) { + + seoKeyWords := c.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + seoDescription := c.OptionService.GetOrByDefault(ctx, property.SeoDescription) + + model["is_categories"] = true + model["meta_keywords"] = seoKeyWords + model["meta_description"] = seoDescription + return c.ThemeService.Render(ctx, "categories") +} + +func (c *CategoryModel) CategoryDetail(ctx context.Context, model template.Model, slug string, page int, token string) (string, error) { + category, err := c.CategoryService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + + if category.Type == consts.CategoryTypeIntimate { + if isAuthenticated, err := c.CategoryAuthentication.IsAuthenticated(ctx, token, category.ID); err != nil || !isAuthenticated { + model["slug"] = category.Slug + model["type"] = consts.EncryptTypeCategory.Name() + if exist, err := c.ThemeService.TemplateExist(ctx, "post_password.tmpl"); err == nil && exist { + return c.ThemeService.Render(ctx, "post_password") + } + return "common/template/post_password", nil + } + } + pageSize := c.OptionService.GetOrByDefault(ctx, property.ArchivePageSize).(int) + sort := c.OptionService.GetPostSort(ctx) + postQuery := param.PostQuery{ + Page: param.Page{ + PageNum: page, + PageSize: pageSize, + }, + Sort: &sort, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + CategoryID: &category.ID, + } + if category.Password != "" { + postQuery.Statuses = append(postQuery.Statuses, consts.PostStatusIntimate.Ptr()) + } + posts, totalPage, err := c.PostService.Page(ctx, postQuery) + if err != nil { + return "", err + } + postVOs, err := c.PostAssembler.ConvertToListVO(ctx, posts) + if err != nil { + return "", err + } + postPage := dto.NewPage(postVOs, totalPage, param.Page{ + PageNum: page, + PageSize: pageSize, + }) + categoryDTO, err := c.CategoryService.ConvertToCategoryDTO(ctx, category) + if err != nil { + return "", err + } + if categoryDTO.Description != "" { + model["meta_description"] = categoryDTO.Description + } else { + model["meta_description"] = c.OptionService.GetOrByDefault(ctx, property.SeoDescription) + } + model["is_category"] = true + model["posts"] = postPage + model["category"] = categoryDTO + model["meta_keywords"] = c.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + + return c.ThemeService.Render(ctx, "category") +} diff --git a/handler/content/model/init.go b/handler/content/model/init.go new file mode 100644 index 00000000..a60e0911 --- /dev/null +++ b/handler/content/model/init.go @@ -0,0 +1,15 @@ +package model + +import ( + "github.com/go-sonic/sonic/injection" +) + +func init() { + injection.Provide(NewPostModel) + injection.Provide(NewCategoryModel) + injection.Provide(NewSheetModel) + injection.Provide(NewTagModel) + injection.Provide(NewLinkModel) + injection.Provide(NewPhotoModel) + injection.Provide(NewJournalModel) +} diff --git a/handler/content/model/journal.go b/handler/content/model/journal.go new file mode 100644 index 00000000..d13684b9 --- /dev/null +++ b/handler/content/model/journal.go @@ -0,0 +1,54 @@ +package model + +import ( + "context" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" +) + +func NewJournalModel(optionService service.OptionService, + themeService service.ThemeService, + journalService service.JournalService, + JournalService service.JournalService, +) *JournalModel { + return &JournalModel{ + OptionService: optionService, + ThemeService: themeService, + JournalService: journalService, + } +} + +type JournalModel struct { + JournalService service.JournalService + OptionService service.OptionService + ThemeService service.ThemeService +} + +func (p *JournalModel) Journals(ctx context.Context, model template.Model, page int) (string, error) { + pageSize := p.OptionService.GetOrByDefault(ctx, property.JournalPageSize).(int) + journals, total, err := p.JournalService.Page(ctx, + param.Page{ + PageNum: page, + PageSize: pageSize, + }, + ¶m.Sort{ + Fields: []string{"createTime,desc"}, + }) + if err != nil { + return "", err + } + journalDTOs, err := p.JournalService.ConvertToWithCommentDTOList(ctx, journals) + if err != nil { + return "", err + } + journalPage := dto.NewPage(journalDTOs, total, param.Page{PageNum: page, PageSize: pageSize}) + model["is_journals"] = true + model["journals"] = journalPage + model["meta_keywords"] = p.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + model["meta_description"] = p.OptionService.GetOrByDefault(ctx, property.SeoDescription) + return p.ThemeService.Render(ctx, "journals") +} diff --git a/handler/content/model/link.go b/handler/content/model/link.go new file mode 100644 index 00000000..7d5f5b73 --- /dev/null +++ b/handler/content/model/link.go @@ -0,0 +1,34 @@ +package model + +import ( + "context" + + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" +) + +func NewLinkModel( + optionService service.OptionService, + themeService service.ThemeService, + linkService service.LinkService, +) *LinkModel { + return &LinkModel{ + OptionService: optionService, + ThemeService: themeService, + LinkService: linkService, + } +} + +type LinkModel struct { + LinkService service.LinkService + OptionService service.OptionService + ThemeService service.ThemeService +} + +func (l *LinkModel) Links(ctx context.Context, model template.Model) (string, error) { + model["is_links"] = true + model["meta_keywords"] = l.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + model["meta_description"] = l.OptionService.GetOrByDefault(ctx, property.SeoDescription) + return l.ThemeService.Render(ctx, "links") +} diff --git a/handler/content/model/photo.go b/handler/content/model/photo.go new file mode 100644 index 00000000..45faabcb --- /dev/null +++ b/handler/content/model/photo.go @@ -0,0 +1,51 @@ +package model + +import ( + "context" + + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" +) + +func NewPhotoModel(optionService service.OptionService, + themeService service.ThemeService, + photoService service.PhotoService, + PhotoService service.PhotoService, +) *PhotoModel { + return &PhotoModel{ + OptionService: optionService, + ThemeService: themeService, + PhotoService: photoService, + } +} + +type PhotoModel struct { + PhotoService service.PhotoService + OptionService service.OptionService + ThemeService service.ThemeService +} + +func (p *PhotoModel) Photos(ctx context.Context, model template.Model, page int) (string, error) { + pageSize := p.OptionService.GetOrByDefault(ctx, property.PhotoPageSize).(int) + photos, total, err := p.PhotoService.Page(ctx, + param.Page{ + PageNum: page, + PageSize: pageSize, + }, + ¶m.Sort{ + Fields: []string{"createTime,desc"}, + }) + if err != nil { + return "", err + } + photoDTOs := p.PhotoService.ConvertToDTOs(ctx, photos) + photoPage := dto.NewPage(photoDTOs, total, param.Page{PageNum: page, PageSize: pageSize}) + model["is_photos"] = true + model["photos"] = photoPage + model["meta_keywords"] = p.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + model["meta_description"] = p.OptionService.GetOrByDefault(ctx, property.SeoDescription) + return p.ThemeService.Render(ctx, "photos") +} diff --git a/handler/content/model/post.go b/handler/content/model/post.go new file mode 100644 index 00000000..8c6a6c76 --- /dev/null +++ b/handler/content/model/post.go @@ -0,0 +1,216 @@ +package model + +import ( + "context" + "strings" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/content/authentication" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util/xerr" +) + +func NewPostModel(optionService service.OptionService, + postService service.PostService, + themeService service.ThemeService, + postCategoryService service.PostCategoryService, + categoryService service.CategoryService, + postTagService service.PostTagService, + tagService service.TagService, + postAssembler assembler.PostAssembler, + metaService service.MetaService, + postAuthentication *authentication.PostAuthentication, +) *PostModel { + return &PostModel{ + OptionService: optionService, + PostService: postService, + PostAssembler: postAssembler, + ThemeService: themeService, + PostCategoryService: postCategoryService, + CategoryService: categoryService, + PostTagService: postTagService, + TagService: tagService, + MetaService: metaService, + PostAuthentication: postAuthentication, + } +} + +type PostModel struct { + OptionService service.OptionService + PostService service.PostService + ThemeService service.ThemeService + PostCategoryService service.PostCategoryService + CategoryService service.CategoryService + PostTagService service.PostTagService + TagService service.TagService + MetaService service.MetaService + PostAssembler assembler.PostAssembler + PostAuthentication *authentication.PostAuthentication +} + +func (p *PostModel) Content(ctx context.Context, post *entity.Post, token string, model template.Model) (string, error) { + if post == nil { + return "", xerr.WithStatus(nil, int(xerr.StatusBadRequest)).WithMsg("查询不到文章信息") + } + if post.Status == consts.PostStatusRecycle || post.Status == consts.PostStatusDraft { + return "", xerr.WithStatus(nil, xerr.StatusNotFound).WithMsg("查询不到文章信息") + } else if post.Status == consts.PostStatusIntimate { + if isAuthenticated, err := p.PostAuthentication.IsAuthenticated(ctx, token, post.ID); err != nil || !isAuthenticated { + model["slug"] = post.Slug + model["type"] = consts.EncryptTypePost.Name() + if exist, err := p.ThemeService.TemplateExist(ctx, "post_password.tmpl"); err == nil && exist { + return p.ThemeService.Render(ctx, "post_password") + } + return "common/template/post_password", nil + } + } + + postVO, err := p.PostAssembler.ConvertToDetailVO(ctx, post) + if err != nil { + return "", err + } + model["post"] = postVO + + prevPosts, err := p.PostService.GetPrevPosts(ctx, post, 1) + if err != nil { + return "", err + } + nextPosts, err := p.PostService.GetNextPosts(ctx, post, 1) + if err != nil { + return "", err + } + if len(prevPosts) > 0 { + prePost, err := p.PostAssembler.ConvertToDetailVO(ctx, prevPosts[0]) + if err != nil { + return "", err + } + model["prevPost"] = prePost + } + if len(nextPosts) > 0 { + nextPost, err := p.PostAssembler.ConvertToDetailVO(ctx, nextPosts[0]) + if err != nil { + return "", err + } + model["nextPost"] = nextPost + } + + categories, err := p.PostCategoryService.ListCategoryByPostID(ctx, post.ID) + if err != nil { + return "", err + } + model["categories"], _ = p.CategoryService.ConvertToCategoryDTOs(ctx, categories) + + tags, err := p.PostTagService.ListTagByPostID(ctx, post.ID) + if err != nil { + return "", err + } + model["tags"], _ = p.TagService.ConvertToDTOs(ctx, tags) + + metas, err := p.MetaService.GetPostMeta(ctx, post.ID) + if err != nil { + return "", err + } + model["metas"] = p.MetaService.ConvertToMetaDTOs(metas) + + if post.MetaDescription != "" { + model["meta_description"] = post.MetaDescription + } else { + model["meta_description"] = post.Summary + } + if post.MetaKeywords != "" { + model["meta_keywords"] = post.MetaKeywords + } else if len(tags) > 0 { + meta_keywords := strings.Builder{} + meta_keywords.Write([]byte(tags[0].Name)) + for _, tag := range tags[1:] { + meta_keywords.Write([]byte(",")) + meta_keywords.Write([]byte(tag.Name)) + } + model["meta_keywords"] = meta_keywords.String() + } + model["is_post"] = true + + p.PostService.IncreaseVisit(ctx, post.ID) + + model["target"] = postVO + model["type"] = "post" + return p.ThemeService.Render(ctx, "post") +} + +func (p *PostModel) List(ctx context.Context, page int, model template.Model) (string, error) { + pageSize := p.OptionService.GetIndexPageSize(ctx) + sort := p.OptionService.GetPostSort(ctx) + postQuery := param.PostQuery{ + Page: param.Page{ + PageNum: page, + PageSize: pageSize, + }, + Sort: &sort, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + } + posts, totalCount, err := p.PostService.Page(ctx, postQuery) + if err != nil { + return "", err + } + postVOs, err := p.PostAssembler.ConvertToListVO(ctx, posts) + if err != nil { + return "", err + } + postPage := dto.NewPage(postVOs, totalCount, param.Page{ + PageNum: page, + PageSize: pageSize, + }) + seoKeyWords := p.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + seoDescription := p.OptionService.GetOrByDefault(ctx, property.SeoDescription) + + model["is_index"] = true + model["posts"] = postPage + model["meta_keywords"] = seoKeyWords + model["meta_description"] = seoDescription + return p.ThemeService.Render(ctx, "index") +} + +func (p *PostModel) Archives(ctx context.Context, page int, model template.Model) (string, error) { + pageSize := p.OptionService.GetOrByDefault(ctx, property.ArchivePageSize).(int) + postQuery := param.PostQuery{ + Page: param.Page{ + PageNum: page, + PageSize: pageSize, + }, + Sort: ¶m.Sort{ + Fields: []string{"createTime,desc"}, + }, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + } + posts, totalPage, err := p.PostService.Page(ctx, postQuery) + if err != nil { + return "", err + } + postVOs, err := p.PostAssembler.ConvertToListVO(ctx, posts) + if err != nil { + return "", err + } + postPage := dto.NewPage(postVOs, totalPage, param.Page{ + PageNum: page, + PageSize: pageSize, + }) + archives, err := p.PostAssembler.ConvertToArchiveYearVOs(ctx, posts) + if err != nil { + return "", err + } + seoKeyWords := p.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + seoDescription := p.OptionService.GetOrByDefault(ctx, property.SeoDescription) + + model["is_archives"] = true + model["posts"] = postPage + model["archives"] = archives + model["meta_keywords"] = seoKeyWords + model["meta_description"] = seoDescription + return p.ThemeService.Render(ctx, "archives") +} diff --git a/handler/content/model/sheet.go b/handler/content/model/sheet.go new file mode 100644 index 00000000..35dbf7fe --- /dev/null +++ b/handler/content/model/sheet.go @@ -0,0 +1,102 @@ +package model + +import ( + "context" + "strings" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/entity" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util/xerr" +) + +func NewSheetModel(optionService service.OptionService, + themeService service.ThemeService, + postTagService service.PostTagService, + tagService service.TagService, + metaService service.MetaService, + sheetAssembler assembler.SheetAssembler, + sheetService service.SheetService, +) *SheetModel { + return &SheetModel{ + OptionService: optionService, + ThemeService: themeService, + PostTagService: postTagService, + TagService: tagService, + MetaService: metaService, + SheetAssembler: sheetAssembler, + SheetService: sheetService, + } +} + +type SheetModel struct { + SheetService service.SheetService + OptionService service.OptionService + ThemeService service.ThemeService + PostTagService service.PostTagService + TagService service.TagService + MetaService service.MetaService + SheetAssembler assembler.SheetAssembler +} + +func (s *SheetModel) Content(ctx context.Context, sheet *entity.Post, model template.Model) (string, error) { + if sheet == nil { + return "", xerr.WithStatus(nil, int(xerr.StatusBadRequest)).WithMsg("查询不到文章信息") + } + if sheet.Status == consts.PostStatusRecycle || sheet.Status == consts.PostStatusDraft { + return "", xerr.WithStatus(nil, xerr.StatusNotFound).WithMsg("查询不到文章信息") + } else if sheet.Status == consts.PostStatusIntimate { + // TODO + model["slug"] = sheet.Slug + model["type"] = consts.EncryptTypePost.Name() + if exist, err := s.ThemeService.TemplateExist(ctx, "post_password.tmpl"); err == nil && exist { + return s.ThemeService.Render(ctx, "post_password") + } + return "common/template/post_password", nil + } + + sheetVO, err := s.SheetAssembler.ConvertToDetailVO(ctx, sheet) + if err != nil { + return "", err + } + model["target"] = sheetVO + model["type"] = "sheet" + model["post"] = sheetVO + model["sheet"] = sheetVO + model["is_sheet"] = true + + metas, err := s.MetaService.GetPostMeta(ctx, sheet.ID) + if err != nil { + return "", err + } + model["metas"] = s.MetaService.ConvertToMetaDTOs(metas) + + tags, err := s.PostTagService.ListTagByPostID(ctx, sheet.ID) + if err != nil { + return "", err + } + model["tags"], _ = s.TagService.ConvertToDTOs(ctx, tags) + + if sheet.MetaDescription != "" { + model["meta_description"] = sheet.MetaDescription + } else { + model["meta_description"] = sheet.Summary + } + if sheet.MetaKeywords != "" { + model["meta_keywords"] = sheet.MetaKeywords + } else if len(tags) > 0 { + meta_keywords := strings.Builder{} + meta_keywords.Write([]byte(tags[0].Name)) + for _, tag := range tags[1:] { + meta_keywords.Write([]byte(",")) + meta_keywords.Write([]byte(tag.Name)) + } + model["meta_keywords"] = meta_keywords.String() + } + + s.SheetService.IncreaseVisit(ctx, sheet.ID) + + return s.ThemeService.Render(ctx, "sheet") +} diff --git a/handler/content/model/tag.go b/handler/content/model/tag.go new file mode 100644 index 00000000..be1fe721 --- /dev/null +++ b/handler/content/model/tag.go @@ -0,0 +1,85 @@ +package model + +import ( + "context" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/service/assembler" + "github.com/go-sonic/sonic/template" +) + +func NewTagModel(optionService service.OptionService, + themeService service.ThemeService, + tagService service.TagService, + TagService service.TagService, + postTagService service.PostTagService, + postAssembler assembler.PostAssembler, +) *TagModel { + return &TagModel{ + OptionService: optionService, + ThemeService: themeService, + TagService: tagService, + PostAssembler: postAssembler, + PostTagService: postTagService, + } +} + +type TagModel struct { + TagService service.TagService + OptionService service.OptionService + ThemeService service.ThemeService + PostTagService service.PostTagService + MetaService service.MetaService + PostAssembler assembler.PostAssembler +} + +func (t *TagModel) Tags(ctx context.Context, model template.Model) (string, error) { + model["is_tags"] = true + model["meta_keywords"] = t.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + model["meta_description"] = t.OptionService.GetOrByDefault(ctx, property.SeoDescription) + return t.ThemeService.Render(ctx, "tags") +} + +func (t *TagModel) TagPosts(ctx context.Context, model template.Model, slug string, page int) (string, error) { + tag, err := t.TagService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + tagDTO, err := t.TagService.ConvertToDTO(ctx, tag) + if err != nil { + return "", err + } + pageSize := t.OptionService.GetOrByDefault(ctx, property.ArchivePageSize).(int) + posts, totalPage, err := t.PostTagService.PagePost(ctx, param.PostQuery{ + Page: param.Page{ + PageNum: page, + PageSize: pageSize, + }, + Sort: ¶m.Sort{ + Fields: []string{"createTime,desc"}, + }, + Statuses: []*consts.PostStatus{consts.PostStatusPublished.Ptr()}, + TagID: &tag.ID, + }) + if err != nil { + return "", err + } + postVOs, err := t.PostAssembler.ConvertToListVO(ctx, posts) + if err != nil { + return "", err + } + postPage := dto.NewPage(postVOs, totalPage, param.Page{ + PageNum: page, + PageSize: pageSize, + }) + model["is_tag"] = true + model["posts"] = postPage + model["tag"] = tagDTO + model["meta_keywords"] = t.OptionService.GetOrByDefault(ctx, property.SeoKeywords) + model["meta_description"] = t.OptionService.GetOrByDefault(ctx, property.SeoDescription) + return t.ThemeService.Render(ctx, "tag") +} diff --git a/handler/content/photo.go b/handler/content/photo.go new file mode 100644 index 00000000..e1de1a84 --- /dev/null +++ b/handler/content/photo.go @@ -0,0 +1,41 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type PhotoHandler struct { + OptionService service.OptionService + PhotoService service.PhotoService + PhotoModel *model.PhotoModel +} + +func NewPhotoHandler( + optionService service.OptionService, + photoService service.PhotoService, + photoModel *model.PhotoModel, + +) *PhotoHandler { + return &PhotoHandler{ + OptionService: optionService, + PhotoService: photoService, + PhotoModel: photoModel, + } +} + +func (p *PhotoHandler) PhotosPage(ctx *gin.Context, model template.Model) (string, error) { + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + return p.PhotoModel.Photos(ctx, model, int(page-1)) +} + +func (p *PhotoHandler) Phtotos(ctx *gin.Context, model template.Model) (string, error) { + return p.PhotoModel.Photos(ctx, model, 0) +} diff --git a/handler/content/sheet.go b/handler/content/sheet.go new file mode 100644 index 00000000..3dc8d242 --- /dev/null +++ b/handler/content/sheet.go @@ -0,0 +1,40 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type SheetHandler struct { + OptionService service.OptionService + SheetService service.SheetService + SheetModel *model.SheetModel +} + +func NewSheetHandler( + optionService service.OptionService, + sheetService service.SheetService, + sheetModel *model.SheetModel, +) *SheetHandler { + return &SheetHandler{ + OptionService: optionService, + SheetService: sheetService, + SheetModel: sheetModel, + } +} + +func (s *SheetHandler) SheetBySlug(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + sheet, err := s.SheetService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + return s.SheetModel.Content(ctx, sheet, model) +} diff --git a/handler/content/tag.go b/handler/content/tag.go new file mode 100644 index 00000000..8c7677f7 --- /dev/null +++ b/handler/content/tag.go @@ -0,0 +1,55 @@ +package content + +import ( + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/handler/content/model" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" +) + +type TagHandler struct { + OptionService service.OptionService + TagService service.TagService + TagModel *model.TagModel + PostTagService service.PostTagService +} + +func NewTagHandler( + optionService service.OptionService, + tagService service.TagService, + tagModel *model.TagModel, + postTagService service.PostTagService, +) *TagHandler { + return &TagHandler{ + OptionService: optionService, + TagService: tagService, + TagModel: tagModel, + PostTagService: postTagService, + } +} + +func (t *TagHandler) Tags(ctx *gin.Context, model template.Model) (string, error) { + return t.TagModel.Tags(ctx, model) +} + +func (t *TagHandler) TagPost(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + return t.TagModel.TagPosts(ctx, model, slug, 0) +} + +func (t *TagHandler) TagPostPage(ctx *gin.Context, model template.Model) (string, error) { + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return "", err + } + page, err := util.ParamInt32(ctx, "page") + if err != nil { + return "", err + } + return t.TagModel.TagPosts(ctx, model, slug, int(page-1)) +} diff --git a/handler/content/view.go b/handler/content/view.go new file mode 100644 index 00000000..94f4b994 --- /dev/null +++ b/handler/content/view.go @@ -0,0 +1,162 @@ +package content + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/handler/binding" + "github.com/go-sonic/sonic/handler/content/authentication" + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util" + "github.com/go-sonic/sonic/util/xerr" +) + +type ViewHandler struct { + OptionService service.OptionService + UserService service.UserService + CategoryService service.CategoryService + PostService service.PostService + ThemeService service.ThemeService + CategoryAuthentication *authentication.CategoryAuthentication + PostAuthentication *authentication.PostAuthentication +} + +func NewViewHandler( + optionService service.OptionService, + userService service.UserService, + categoryService service.CategoryService, + postService service.PostService, + themeService service.ThemeService, + categoryAuthentication *authentication.CategoryAuthentication, + postAuthentication *authentication.PostAuthentication, +) *ViewHandler { + return &ViewHandler{ + OptionService: optionService, + UserService: userService, + CategoryService: categoryService, + PostService: postService, + ThemeService: themeService, + CategoryAuthentication: categoryAuthentication, + PostAuthentication: postAuthentication, + } +} + +func (v *ViewHandler) Admin(ctx *gin.Context) (interface{}, error) { + // TODO + return nil, nil +} + +func (v *ViewHandler) Version(ctx *gin.Context) (interface{}, error) { + return consts.SonicVersion, nil +} + +func (v *ViewHandler) Install(ctx *gin.Context) { + isInstall := v.OptionService.GetOrByDefault(ctx, property.IsInstalled).(bool) + if isInstall { + return + } + ctx.Redirect(http.StatusTemporaryRedirect, "admin/#install") +} + +func (v *ViewHandler) Logo(ctx *gin.Context) (interface{}, error) { + logo := v.OptionService.GetOrByDefault(ctx, property.BlogLogo).(string) + if logo != "" { + ctx.Redirect(http.StatusTemporaryRedirect, logo) + } + return nil, nil +} + +func (v *ViewHandler) Favicon(ctx *gin.Context) (interface{}, error) { + favicon := v.OptionService.GetOrByDefault(ctx, property.BlogFavicon).(string) + if favicon != "" { + ctx.Redirect(http.StatusTemporaryRedirect, favicon) + } + return nil, nil +} + +func (v *ViewHandler) Authenticate(ctx *gin.Context, model template.Model) (string, error) { + contentType, err := util.ParamString(ctx, "type") + if err != nil { + return v.authenticateErr(ctx, model, contentType, "", err) + } + slug, err := util.ParamString(ctx, "slug") + if err != nil { + return v.authenticateErr(ctx, model, contentType, slug, err) + } + + var authenticationParam param.Authentication + err = ctx.ShouldBindWith(&authenticationParam, binding.CustomFormBinding) + if err != nil { + return v.authenticateErr(ctx, model, "post", slug, err) + } + if authenticationParam.Password == "" { + return v.authenticateErr(ctx, model, "post", slug, xerr.WithMsg(nil, "密码为空")) + } + + token, _ := ctx.Cookie("authentication") + + switch contentType { + case consts.EncryptTypeCategory.Name(): + token, err = v.authenticateCategory(ctx, slug, authenticationParam.Password, token) + case consts.EncryptTypePost.Name(): + token, err = v.authenticatePost(ctx, slug, authenticationParam.Password, token) + default: + return v.authenticateErr(ctx, model, "post", slug, xerr.WithStatus(nil, xerr.StatusBadRequest)) + } + if err != nil { + return v.authenticateErr(ctx, model, contentType, slug, err) + } + ctx.SetCookie("authentication", token, 1800, "/", "", false, true) + return "", nil +} + +func (v *ViewHandler) authenticateCategory(ctx *gin.Context, slug, password, token string) (string, error) { + category, err := v.CategoryService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + categoryDTO, err := v.CategoryService.ConvertToCategoryDTO(ctx, category) + if err != nil { + return "", err + } + + token, err = v.CategoryAuthentication.Authenticate(ctx, token, category.ID, password) + if err != nil { + return "", err + } + + ctx.Redirect(http.StatusFound, categoryDTO.FullPath) + return token, nil +} +func (v *ViewHandler) authenticatePost(ctx *gin.Context, slug, password, token string) (string, error) { + post, err := v.PostService.GetBySlug(ctx, slug) + if err != nil { + return "", err + } + fullPath, err := v.PostService.BuildFullPath(ctx, post) + if err != nil { + return "", err + } + token, err = v.PostAuthentication.Authenticate(ctx, token, post.ID, password) + if err != nil { + return "", err + } + + ctx.Redirect(http.StatusFound, fullPath) + return token, nil +} + +func (v *ViewHandler) authenticateErr(ctx *gin.Context, model template.Model, aType string, slug string, err error) (string, error) { + model["type"] = aType + model["slug"] = slug + model["errorMsg"] = xerr.GetMessage(err) + if exist, err := v.ThemeService.TemplateExist(ctx, "post_password.tmpl"); err == nil && exist { + return v.ThemeService.Render(ctx, "post_password") + } + return "common/template/post_password", nil +} diff --git a/handler/middleware/auth.go b/handler/middleware/auth.go new file mode 100644 index 00000000..5b1b462a --- /dev/null +++ b/handler/middleware/auth.go @@ -0,0 +1,92 @@ +package middleware + +import ( + "net/http" + + "github.com/gin-gonic/gin" + + "github.com/go-sonic/sonic/cache" + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/model/property" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/util/xerr" +) + +type AuthMiddleware struct { + OptionService service.OptionService + OneTimeTokenService service.OneTimeTokenService + UserService service.UserService + Cache cache.Cache +} + +func NewAuthMiddleware(optionService service.OptionService, oneTimeTokenService service.OneTimeTokenService, cache cache.Cache, userService service.UserService) *AuthMiddleware { + authMiddleware := &AuthMiddleware{ + OptionService: optionService, + OneTimeTokenService: oneTimeTokenService, + Cache: cache, + UserService: userService, + } + return authMiddleware +} + +func (a *AuthMiddleware) GetWrapHandler() gin.HandlerFunc { + return func(ctx *gin.Context) { + isInstalled, err := a.OptionService.GetOrByDefaultWithErr(ctx, property.IsInstalled, false) + if err != nil { + abortWithStatusJSON(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return + } + if !isInstalled.(bool) { + abortWithStatusJSON(ctx, http.StatusBadRequest, "Blog is not initialized") + return + } + + oneTimeToken, ok := ctx.GetQuery(consts.OneTimeTokenQueryName) + if ok { + allowedURL, ok := a.OneTimeTokenService.Get(oneTimeToken) + if !ok { + abortWithStatusJSON(ctx, http.StatusBadRequest, "OneTimeToken is not exist or expired") + return + } + currentURL := ctx.Request.URL.Path + if currentURL != allowedURL { + abortWithStatusJSON(ctx, http.StatusBadRequest, "The one-time token does not correspond the request uri") + return + } + return + } + + token := ctx.GetHeader(consts.AdminTokenHeaderName) + if token == "" { + abortWithStatusJSON(ctx, http.StatusUnauthorized, "未登录,请登录后访问") + return + } + userID, ok := a.Cache.Get(cache.BuildTokenAccessKey(token)) + + if !ok || userID == nil { + abortWithStatusJSON(ctx, http.StatusUnauthorized, "Token 已过期或不存在") + return + } + + user, err := a.UserService.GetByID(ctx, userID.(int32)) + if xerr.GetType(err) == xerr.NoRecord { + _ = ctx.Error(err) + abortWithStatusJSON(ctx, http.StatusUnauthorized, "用户不存在") + return + } + if err != nil { + _ = ctx.Error(err) + abortWithStatusJSON(ctx, http.StatusInternalServerError, http.StatusText(http.StatusInternalServerError)) + return + } + ctx.Set(consts.AuthorizedUser, user) + } +} + +func abortWithStatusJSON(ctx *gin.Context, status int, message string) { + ctx.AbortWithStatusJSON(status, &dto.BaseDTO{ + Status: status, + Message: message, + }) +} diff --git a/handler/middleware/cache_control.go b/handler/middleware/cache_control.go new file mode 100644 index 00000000..1fc4ecf7 --- /dev/null +++ b/handler/middleware/cache_control.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "strconv" + "time" + + "github.com/gin-gonic/gin" +) + +type CacheControlMiddleware struct { + MaxAge time.Duration + Public bool +} + +type CacheControlOption func(*CacheControlMiddleware) + +func NewCacheControlMiddleware(opts ...CacheControlOption) *CacheControlMiddleware { + c := &CacheControlMiddleware{} + for _, opt := range opts { + opt(c) + } + return c +} + +func (c *CacheControlMiddleware) CacheControl() gin.HandlerFunc { + value := "" + if c.Public { + value = "public," + } + if c.MaxAge > 0 { + value = "max-age=" + strconv.FormatInt(int64(c.MaxAge.Seconds()), 10) + } + return func(ctx *gin.Context) { + ctx.Header("Cache-Control", value) + } +} + +func WithMaxAge(maxAge time.Duration) CacheControlOption { + return func(c *CacheControlMiddleware) { + c.MaxAge = maxAge + } +} + +func WithPublic(public bool) CacheControlOption { + return func(c *CacheControlMiddleware) { + c.Public = public + } +} diff --git a/handler/middleware/log.go b/handler/middleware/log.go new file mode 100644 index 00000000..7f8bcc31 --- /dev/null +++ b/handler/middleware/log.go @@ -0,0 +1,75 @@ +package middleware + +import ( + "strings" + "time" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +type GinLoggerMiddleware struct { + logger *zap.Logger +} + +func NewGinLoggerMiddleware(logger *zap.Logger) *GinLoggerMiddleware { + return &GinLoggerMiddleware{ + logger: logger, + } +} + +// GinLoggerConfig LoggerConfig defines the config for Logger middleware +type GinLoggerConfig struct { + // SkipPaths is an url path array which logs are not written. + // Optional. + SkipPaths []string +} + +// LoggerWithConfig instance a Logger middleware with config. +func (g *GinLoggerMiddleware) LoggerWithConfig(conf GinLoggerConfig) gin.HandlerFunc { + logger := g.logger.WithOptions(zap.WithCaller(false)) + notLogged := conf.SkipPaths + + var skip map[string]struct{} + + if length := len(notLogged); length > 0 { + skip = make(map[string]struct{}, length) + + for _, path := range notLogged { + skip[path] = struct{}{} + } + } + + return func(ctx *gin.Context) { + // Start timer + start := time.Now() + path := ctx.Request.URL.Path + raw := ctx.Request.URL.RawQuery + + // Process request + ctx.Next() + + if len(ctx.Errors) > 0 { + logger.Error(ctx.Errors.ByType(gin.ErrorTypePrivate).String()) + } + // Log only when path is not being skipped + if _, ok := skip[path]; !ok { + + if raw != "" { + path = path + "?" + raw + } + path = strings.Replace(path, "\n", "", -1) + path = strings.Replace(path, "\r", "", -1) + clientIP := strings.Replace(ctx.ClientIP(), "\n", "", -1) + clientIP = strings.Replace(clientIP, "\r", "", -1) + + logger.Info("[GIN]", + zap.Time("beginTime", start), + zap.Int("status", ctx.Writer.Status()), + zap.Duration("latency", time.Since(start)), + zap.String("clientIP", clientIP), + zap.String("method", ctx.Request.Method), + zap.String("path", path)) + } + } +} diff --git a/handler/middleware/recovery.go b/handler/middleware/recovery.go new file mode 100644 index 00000000..6fba9f26 --- /dev/null +++ b/handler/middleware/recovery.go @@ -0,0 +1,62 @@ +package middleware + +import ( + "net" + "net/http" + "os" + "strings" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" + + "github.com/go-sonic/sonic/model/dto" +) + +type RecoveryMiddleware struct { + logger *zap.Logger +} + +func NewRecoveryMiddleware(logger *zap.Logger) *RecoveryMiddleware { + return &RecoveryMiddleware{ + logger: logger, + } +} + +func (r *RecoveryMiddleware) RecoveryWithLogger() gin.HandlerFunc { + logger := r.logger.WithOptions(zap.AddCallerSkip(2)) + + return func(ctx *gin.Context) { + defer func() { + if err := recover(); err != nil { + // Check for a broken connection, as it is not really a + // condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + if brokenPipe { + logger.Error(ctx.Request.URL.Path, + zap.Any("error", err), + ) + } else { + logger.DPanic("[Recovery] panic recovered", zap.Any("error", err)) + } + + if brokenPipe { + // If the connection is dead, we can't write a status to it. + ctx.Error(err.(error)) // nolint: errcheck + ctx.Abort() + } else { + code := http.StatusInternalServerError + ctx.AbortWithStatusJSON(code, &dto.BaseDTO{Status: code, Message: http.StatusText(code)}) + } + } + }() + ctx.Next() + } +} diff --git a/handler/route.go b/handler/route.go new file mode 100644 index 00000000..b98d6a56 --- /dev/null +++ b/handler/route.go @@ -0,0 +1,417 @@ +package handler + +import ( + "context" + "path/filepath" + "time" + + "github.com/gin-contrib/cors" + "github.com/gin-gonic/gin" + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/consts" + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/handler/middleware" +) + +func (s *Server) RegisterRouters() { + router := s.Router + if config.IsDev() { + router.Use(cors.New(cors.Config{ + AllowAllOrigins: true, + AllowOrigins: []string{}, + AllowMethods: []string{"PUT", "PATCH", "GET", "DELETE", "POST", "OPTIONS"}, + AllowHeaders: []string{"Origin", "Admin-Authorization", "Content-Type"}, + AllowCredentials: true, + ExposeHeaders: []string{"Content-Length"}, + })) + } + + { + router.GET("/ping", func(ctx *gin.Context) { + _, _ = ctx.Writer.Write([]byte("pong")) + }) + { + staticRouter := router.Group("/") + staticRouter.StaticFS("admin", gin.Dir(s.Config.Sonic.AdminResourcesDir, false)) + staticRouter.StaticFS("/css", gin.Dir(filepath.Join(s.Config.Sonic.AdminResourcesDir, "css"), false)) + staticRouter.StaticFS("/js", gin.Dir(filepath.Join(s.Config.Sonic.AdminResourcesDir, "js"), false)) + staticRouter.StaticFS("/images", gin.Dir(filepath.Join(s.Config.Sonic.AdminResourcesDir, "images"), false)) + staticRouter.Use(middleware.NewCacheControlMiddleware(middleware.WithMaxAge(time.Hour*24*7)).CacheControl()). + StaticFS(consts.SonicUploadDir, gin.Dir(s.Config.Sonic.UploadDir, false)) + } + { + adminApiRouter := router.Group("/api/admin") + adminApiRouter.Use(s.LogMiddleware.LoggerWithConfig(middleware.GinLoggerConfig{}), s.RecoveryMiddleware.RecoveryWithLogger()) + adminApiRouter.GET("/is_installed", s.wrapHandler(s.AdminHandler.IsInstalled)) + adminApiRouter.POST("/login/precheck", s.wrapHandler(s.AdminHandler.AuthPreCheck)) + adminApiRouter.POST("/login", s.wrapHandler(s.AdminHandler.Auth)) + adminApiRouter.POST("/refresh/:refreshToken", s.wrapHandler(s.AdminHandler.RefreshToken)) + adminApiRouter.POST("/installations", s.wrapHandler(s.InstallHandler.InstallBlog)) + { + authRouter := adminApiRouter.Group("") + authRouter.Use(s.AuthMiddleware.GetWrapHandler()) + authRouter.POST("/logout", s.wrapHandler(s.AdminHandler.LogOut)) + authRouter.POST("/password/code", s.wrapHandler(s.AdminHandler.SendResetCode)) + authRouter.GET("/environments", s.wrapHandler(s.AdminHandler.GetEnvironments)) + authRouter.GET("/sonic/logfile", s.wrapHandler(s.AdminHandler.GetLogFiles)) + { + attachmentRouter := authRouter.Group("/attachments") + attachmentRouter.POST("/upload", s.wrapHandler(s.AttachmentHandler.UploadAttachment)) + attachmentRouter.POST("/uploads", s.wrapHandler(s.AttachmentHandler.UploadAttachments)) + attachmentRouter.DELETE("/:id", s.wrapHandler(s.AttachmentHandler.DeleteAttachment)) + attachmentRouter.DELETE("", s.wrapHandler(s.AttachmentHandler.DeleteAttachmentInBatch)) + attachmentRouter.GET("", s.wrapHandler(s.AttachmentHandler.QueryAttachment)) + attachmentRouter.GET("/:id", s.wrapHandler(s.AttachmentHandler.GetAttachmentByID)) + attachmentRouter.PUT("/:id", s.wrapHandler(s.AttachmentHandler.UpdateAttachment)) + attachmentRouter.GET("/media_types", s.wrapHandler(s.AttachmentHandler.GetAllMediaType)) + attachmentRouter.GET("types", s.wrapHandler(s.AttachmentHandler.GetAllTypes)) + } + { + backupRouter := authRouter.Group("/backups") + backupRouter.POST("/work-dir", s.wrapHandler(s.BackupHandler.BackupWholeSite)) + backupRouter.GET("/work-dir/*path", s.BackupHandler.HandleWorkDir) + backupRouter.DELETE("/work-dir", s.wrapHandler(s.BackupHandler.DeleteBackups)) + backupRouter.POST("/data", s.wrapHandler(s.BackupHandler.ExportData)) + backupRouter.DELETE("/data", s.wrapHandler(s.BackupHandler.DeleteDataFile)) + backupRouter.GET("/data/*path", s.BackupHandler.HandleData) + backupRouter.POST("/markdown/export", s.wrapHandler(s.BackupHandler.ExportMarkdown)) + backupRouter.POST("/mark-down/import", s.wrapHandler(s.BackupHandler.ImportMarkdown)) + backupRouter.GET("/markdown/fetch", s.wrapHandler(s.BackupHandler.GetMarkDownBackup)) + backupRouter.GET("/markdown/export", s.wrapHandler(s.BackupHandler.ListMarkdowns)) + backupRouter.DELETE("/markdown/export", s.wrapHandler(s.BackupHandler.DeleteMarkdowns)) + backupRouter.GET("/markdown/export/:filename", s.BackupHandler.DownloadMarkdown) + } + { + categoryRouter := authRouter.Group("/categories") + categoryRouter.PUT("/batch", s.wrapHandler(s.CategoryHandler.UpdateCategoryBatch)) + categoryRouter.GET("/:categoryID", s.wrapHandler(s.CategoryHandler.GetCategoryByID)) + categoryRouter.GET("", s.wrapHandler(s.CategoryHandler.ListAllCategory)) + categoryRouter.GET("/tree_view", s.wrapHandler(s.CategoryHandler.ListAsTree)) + categoryRouter.POST("", s.wrapHandler(s.CategoryHandler.CreateCategory)) + categoryRouter.PUT("/:categoryID", s.wrapHandler(s.CategoryHandler.UpdateCategory)) + categoryRouter.DELETE("/:categoryID", s.wrapHandler(s.CategoryHandler.DeleteCategory)) + } + { + postRouter := authRouter.Group("/posts") + postRouter.GET("", s.wrapHandler(s.PostHandler.ListPosts)) + postRouter.GET("/latest", s.wrapHandler(s.PostHandler.ListLatestPosts)) + postRouter.GET("/status/:status", s.wrapHandler(s.PostHandler.ListPostsByStatus)) + postRouter.GET("/:postID", s.wrapHandler(s.PostHandler.GetByPostID)) + postRouter.PUT("/:postID/likes", s.wrapHandler(s.PostHandler.LikePost)) + postRouter.POST("", s.wrapHandler(s.PostHandler.CreatePost)) + postRouter.PUT("/:postID", s.wrapHandler(s.PostHandler.UpdatePost)) + postRouter.PUT("/:postID/status/:status", s.wrapHandler(s.PostHandler.UpdatePostStatus)) + postRouter.PUT("/status/:status", s.wrapHandler(s.PostHandler.UpdatePostStatusBatch)) + postRouter.PUT("/:postID/status/draft/content", s.wrapHandler(s.PostHandler.UpdatePostDraft)) + postRouter.DELETE("/:postID", s.wrapHandler(s.PostHandler.DeletePost)) + postRouter.DELETE("", s.wrapHandler(s.PostHandler.DeletePostBatch)) + postRouter.GET("/preview/:postID", s.wrapHandler(s.PostHandler.PreviewPost)) + { + postCommentRouter := postRouter.Group("/comments") + postCommentRouter.GET("", s.wrapHandler(s.PostCommentHandler.ListPostComment)) + postCommentRouter.GET("/latest", s.wrapHandler(s.PostCommentHandler.ListPostCommentLatest)) + postCommentRouter.GET("/:postID/tree_view", s.wrapHandler(s.PostCommentHandler.ListPostCommentAsTree)) + postCommentRouter.GET("/:postID/list_view", s.wrapHandler(s.PostCommentHandler.ListPostCommentWithParent)) + postCommentRouter.POST("", s.wrapHandler(s.PostCommentHandler.CreatePostComment)) + postCommentRouter.PUT("/:commentID", s.wrapHandler(s.PostCommentHandler.UpdatePostComment)) + postCommentRouter.PUT("/:commentID/status/:status", s.wrapHandler(s.PostCommentHandler.UpdatePostCommentStatus)) + postCommentRouter.PUT("/status/:status", s.wrapHandler(s.PostCommentHandler.UpdatePostCommentStatusBatch)) + postCommentRouter.DELETE("/:commentID", s.wrapHandler(s.PostCommentHandler.DeletePostComment)) + postCommentRouter.DELETE("", s.wrapHandler(s.PostCommentHandler.DeletePostCommentBatch)) + } + } + { + optionRouter := authRouter.Group("/options") + optionRouter.GET("", s.wrapHandler(s.OptionHandler.ListAllOptions)) + optionRouter.GET("/map_view", s.wrapHandler(s.OptionHandler.ListAllOptionsAsMap)) + optionRouter.POST("/map_view/keys", s.wrapHandler(s.OptionHandler.ListAllOptionsAsMapWithKey)) + optionRouter.POST("/saving", s.wrapHandler(s.OptionHandler.SaveOption)) + optionRouter.POST("/map_view/saving", s.wrapHandler(s.OptionHandler.SaveOptionWithMap)) + } + { + logRouter := authRouter.Group("/logs") + logRouter.GET("/latest", s.wrapHandler(s.LogHandler.PageLatestLog)) + logRouter.GET("", s.wrapHandler(s.LogHandler.PageLog)) + logRouter.GET("/clear", s.wrapHandler(s.LogHandler.ClearLog)) + } + { + statisticRouter := authRouter.Group("/statistics") + statisticRouter.GET("", s.wrapHandler(s.StatisticHandler.Statistics)) + statisticRouter.GET("user", s.wrapHandler(s.StatisticHandler.StatisticsWithUser)) + } + { + sheetRouter := authRouter.Group("/sheets") + sheetRouter.GET("/:sheetID", s.wrapHandler(s.SheetHandler.GetSheetByID)) + sheetRouter.GET("", s.wrapHandler(s.SheetHandler.ListSheet)) + sheetRouter.POST("", s.wrapHandler(s.SheetHandler.CreateSheet)) + sheetRouter.PUT("/:sheetID", s.wrapHandler(s.SheetHandler.UpdateSheet)) + sheetRouter.PUT("/:sheetID/status", s.wrapHandler(s.SheetHandler.UpdateSheetStatus)) + sheetRouter.PUT("/:sheetID/status/draft/content", s.wrapHandler(s.SheetHandler.UpdateSheetDraft)) + sheetRouter.DELETE("/:sheetID", s.wrapHandler(s.SheetHandler.DeleteSheet)) + sheetRouter.GET("/preview/:sheetID", s.wrapHandler(s.SheetHandler.PreviewSheet)) + sheetRouter.GET("/independent", s.wrapHandler(s.SheetHandler.IndependentSheets)) + { + sheetCommentRouter := sheetRouter.Group("/comments") + sheetCommentRouter.GET("", s.wrapHandler(s.SheetCommentHandler.ListSheetComment)) + sheetCommentRouter.GET("/latest", s.wrapHandler(s.SheetCommentHandler.ListSheetCommentLatest)) + sheetCommentRouter.GET("/:sheetID/tree_view", s.wrapHandler(s.SheetCommentHandler.ListSheetCommentAsTree)) + sheetCommentRouter.GET("/:sheetID/list_view", s.wrapHandler(s.SheetCommentHandler.ListSheetCommentWithParent)) + sheetCommentRouter.POST("/", s.wrapHandler(s.SheetCommentHandler.CreateSheetComment)) + sheetCommentRouter.PUT("/:commentID/status/:status", s.wrapHandler(s.SheetCommentHandler.UpdateSheetCommentStatus)) + sheetCommentRouter.PUT("/status/:status", s.wrapHandler(s.SheetCommentHandler.UpdateSheetCommentStatusBatch)) + sheetCommentRouter.DELETE("/:commentID", s.wrapHandler(s.SheetCommentHandler.DeleteSheetComment)) + sheetCommentRouter.DELETE("", s.wrapHandler(s.SheetCommentHandler.DeleteSheetCommentBatch)) + } + } + { + journalRouter := authRouter.Group("/journals") + journalRouter.GET("", s.wrapHandler(s.JournalHandler.ListJournal)) + journalRouter.GET("/latest", s.wrapHandler(s.JournalHandler.ListLatestJournal)) + journalRouter.POST("", s.wrapHandler(s.JournalHandler.CreateJournal)) + journalRouter.PUT("/:journalID", s.wrapHandler(s.JournalHandler.UpdateJournal)) + journalRouter.DELETE("/:journalID", s.wrapHandler(s.JournalHandler.DeleteJournal)) + { + journalCommentRouter := journalRouter.Group("/comments") + journalCommentRouter.GET("", s.wrapHandler(s.JournalCommentHandler.ListJournalComment)) + journalCommentRouter.GET("/latest", s.wrapHandler(s.JournalCommentHandler.ListJournalCommentLatest)) + journalCommentRouter.GET("/:journalID/tree_view", s.wrapHandler(s.JournalCommentHandler.ListJournalCommentAsTree)) + journalCommentRouter.GET("/:journalID/list_view", s.wrapHandler(s.JournalCommentHandler.ListJournalCommentWithParent)) + journalCommentRouter.POST("/", s.wrapHandler(s.JournalCommentHandler.CreateJournalComment)) + journalCommentRouter.PUT("/:commentID/status/:status", s.wrapHandler(s.JournalCommentHandler.UpdateJournalCommentStatus)) + journalCommentRouter.PUT("/status/:status", s.wrapHandler(s.JournalCommentHandler.UpdateJournalStatusBatch)) + journalCommentRouter.PUT("/:commentID", s.wrapHandler(s.JournalCommentHandler.UpdateJournalComment)) + journalCommentRouter.DELETE("/:commentID", s.wrapHandler(s.JournalCommentHandler.DeleteJournalComment)) + journalCommentRouter.DELETE("", s.wrapHandler(s.JournalCommentHandler.DeleteJournalCommentBatch)) + } + } + + { + linkRouter := authRouter.Group("/links") + linkRouter.GET("", s.wrapHandler(s.LinkHandler.ListLinks)) + linkRouter.GET("/:id", s.wrapHandler(s.LinkHandler.GetLinkByID)) + linkRouter.POST("", s.wrapHandler(s.LinkHandler.CreateLink)) + linkRouter.PUT("/:id", s.wrapHandler(s.LinkHandler.UpdateLink)) + linkRouter.DELETE("/:id", s.wrapHandler(s.LinkHandler.DeleteLink)) + linkRouter.GET("/teams", s.wrapHandler(s.LinkHandler.ListLinkTeams)) + } + { + menuRouter := authRouter.Group("/menu") + menuRouter.GET("", s.wrapHandler(s.MenuHandler.ListMenus)) + menuRouter.GET("/tree_view", s.wrapHandler(s.MenuHandler.ListMenusAsTree)) + menuRouter.GET("/team/tree_view", s.wrapHandler(s.MenuHandler.ListMenusAsTreeByTeam)) + menuRouter.GET("/:id", s.wrapHandler(s.MenuHandler.GetMenuByID)) + menuRouter.POST("", s.wrapHandler(s.MenuHandler.CreateMenu)) + menuRouter.POST("/batch", s.wrapHandler(s.MenuHandler.CreateMenuBatch)) + menuRouter.PUT("/:id", s.wrapHandler(s.MenuHandler.UpdateMenu)) + menuRouter.PUT("/batch", s.wrapHandler(s.MenuHandler.UpdateMenuBatch)) + menuRouter.DELETE("/:id", s.wrapHandler(s.MenuHandler.DeleteMenu)) + menuRouter.DELETE("/batch", s.wrapHandler(s.MenuHandler.DeleteMenuBatch)) + menuRouter.GET("/teams", s.wrapHandler(s.MenuHandler.ListMenuTeams)) + } + { + tagRouter := authRouter.Group("/tags") + tagRouter.GET("", s.wrapHandler(s.TagHandler.ListTags)) + tagRouter.GET("/:id", s.wrapHandler(s.TagHandler.GetTagByID)) + tagRouter.POST("", s.wrapHandler(s.TagHandler.CreateTag)) + tagRouter.PUT("", s.wrapHandler(s.TagHandler.UpdateTag)) + tagRouter.DELETE("", s.wrapHandler(s.TagHandler.DeleteTag)) + } + { + photoRouter := authRouter.Group("/photos") + photoRouter.GET("/latest", s.wrapHandler(s.PhotoHandler.ListPhoto)) + photoRouter.GET("", s.wrapHandler(s.PhotoHandler.PagePhotos)) + photoRouter.GET("/:id", s.wrapHandler(s.PhotoHandler.GetPhotoByID)) + photoRouter.DELETE("/:id", s.wrapHandler(s.PhotoHandler.DeletePhoto)) + photoRouter.POST("", s.wrapHandler(s.PhotoHandler.CreatePhoto)) + photoRouter.PUT("/:id", s.wrapHandler(s.PhotoHandler.UpdatePhoto)) + photoRouter.POST("/:id/likes", s.wrapHandler(s.PhotoHandler.IncreasePhotoLike)) + photoRouter.GET("/teams", s.wrapHandler(s.PhotoHandler.ListPhotoTeams)) + } + { + userRouter := authRouter.Group("/users") + userRouter.GET("/profiles", s.wrapHandler(s.UserHandler.GetCurrentUserProfile)) + userRouter.PUT("/profiles", s.wrapHandler(s.UserHandler.UpdateUserProfile)) + userRouter.PUT("/profiles/password", s.wrapHandler(s.UserHandler.UpdatePassword)) + userRouter.PUT("/mfa/generate", s.wrapHandler(s.UserHandler.GenerateMFAQRCode)) + userRouter.PUT("/mfa/update", s.wrapHandler(s.UserHandler.UpdateMFA)) + } + { + themeRouter := authRouter.Group("themes") + themeRouter.GET("/activation", s.wrapHandler(s.ThemeHandler.GetActivatedTheme)) + themeRouter.GET("/:themeID", s.wrapHandler(s.ThemeHandler.GetThemeByID)) + themeRouter.GET("", s.wrapHandler(s.ThemeHandler.ListAllThemes)) + themeRouter.GET("/activation/files", s.wrapHandler(s.ThemeHandler.ListActivatedThemeFile)) + themeRouter.GET("/:themeID/files", s.wrapHandler(s.ThemeHandler.ListThemeFileByID)) + themeRouter.GET("files/content", s.wrapHandler(s.ThemeHandler.GetThemeFileContent)) + themeRouter.GET("/:themeID/files/content", s.wrapHandler(s.ThemeHandler.GetThemeFileContentByID)) + themeRouter.PUT("/files/content", s.wrapHandler(s.ThemeHandler.UpdateThemeFile)) + themeRouter.PUT("/:themeID/files/content", s.wrapHandler(s.ThemeHandler.UpdateThemeFileByID)) + themeRouter.GET("activation/template/custom/sheet", s.wrapHandler(s.ThemeHandler.ListCustomSheetTemplate)) + themeRouter.GET("activation/template/custom/post", s.wrapHandler(s.ThemeHandler.ListCustomPostTemplate)) + themeRouter.POST("/:themeID/activation", s.wrapHandler(s.ThemeHandler.ActivateTheme)) + themeRouter.GET("activation/configurations", s.wrapHandler(s.ThemeHandler.GetActivatedThemeConfig)) + themeRouter.GET("/:themeID/configurations", s.wrapHandler(s.ThemeHandler.GetThemeConfigByID)) + themeRouter.GET("/:themeID/configurations/groups/:group", s.wrapHandler(s.ThemeHandler.GetThemeConfigByGroup)) + themeRouter.GET("/:themeID/configurations/groups", s.wrapHandler(s.ThemeHandler.GetThemeConfigGroupNames)) + themeRouter.GET("activation/settings", s.wrapHandler(s.ThemeHandler.GetActivatedThemeSettingMap)) + themeRouter.GET("/:themeID/settings", s.wrapHandler(s.ThemeHandler.GetThemeSettingMapByID)) + themeRouter.GET("/:themeID/groups/:group/settings", s.wrapHandler(s.ThemeHandler.GetThemeSettingMapByGroupAndThemeID)) + themeRouter.POST("activation/settings", s.wrapHandler(s.ThemeHandler.SaveActivatedThemeSetting)) + themeRouter.POST("/:themeID/settings", s.wrapHandler(s.ThemeHandler.SaveThemeSettingByID)) + themeRouter.DELETE("/:themeID", s.wrapHandler(s.ThemeHandler.DeleteThemeByID)) + themeRouter.POST("upload", s.wrapHandler(s.ThemeHandler.UploadTheme)) + themeRouter.PUT("upload/:themeID", s.wrapHandler(s.ThemeHandler.UpdateThemeByUpload)) + themeRouter.POST("fetching", s.wrapHandler(s.ThemeHandler.FetchTheme)) + themeRouter.PUT("fetching/:themeID", s.wrapHandler(s.ThemeHandler.UpdateThemeByFetching)) + themeRouter.POST("reload", s.wrapHandler(s.ThemeHandler.ReloadTheme)) + themeRouter.GET("activation/template/exists", s.wrapHandler(s.ThemeHandler.TemplateExist)) + } + { + emailRouter := authRouter.Group("/mails") + emailRouter.POST("/test", s.wrapHandler(s.EmailHandler.Test)) + } + } + } + { + contentRouter := router.Group("") + contentRouter.Use(s.LogMiddleware.LoggerWithConfig(middleware.GinLoggerConfig{}), s.RecoveryMiddleware.RecoveryWithLogger()) + + contentRouter.POST("/content/:type/:slug/authentication", s.wrapHTMLHandler(s.ViewHandler.Authenticate)) + + contentRouter.GET("", s.wrapHTMLHandler(s.IndexHandler.Index)) + contentRouter.GET("/page/:page", s.wrapHTMLHandler(s.IndexHandler.IndexPage)) + contentRouter.GET("/robots.txt", s.wrapTextHandler(s.FeedHandler.Robots)) + contentRouter.GET("/atom", s.wrapTextHandler(s.FeedHandler.Atom)) + contentRouter.GET("/atom.xml", s.wrapTextHandler(s.FeedHandler.Atom)) + contentRouter.GET("/rss", s.wrapTextHandler(s.FeedHandler.Feed)) + contentRouter.GET("/rss.xml", s.wrapTextHandler(s.FeedHandler.Feed)) + contentRouter.GET("/feed", s.wrapTextHandler(s.FeedHandler.Feed)) + contentRouter.GET("/feed.xml", s.wrapTextHandler(s.FeedHandler.Feed)) + contentRouter.GET("/feed/categories/:slug", s.wrapTextHandler(s.FeedHandler.CategoryFeed)) + contentRouter.GET("/atom/categories/:slug", s.wrapTextHandler(s.FeedHandler.CategoryAtom)) + contentRouter.GET("/sitemap", s.wrapTextHandler(s.FeedHandler.SitemapXML)) + contentRouter.GET("/sitemap.html", s.wrapHTMLHandler(s.FeedHandler.SitemapHTML)) + + contentRouter.GET("/version", s.wrapHandler(s.ViewHandler.Version)) + contentRouter.GET("/install", s.ViewHandler.Install) + contentRouter.GET("/logo", s.wrapHandler(s.ViewHandler.Logo)) + contentRouter.GET("/favicon", s.wrapHandler(s.ViewHandler.Favicon)) + err := s.registerDynamicRouters(contentRouter) + if err != nil { + s.logger.DPanic("regiterDynamicRouters err", zap.Error(err)) + } + } + { + contentAPIRouter := router.Group("/api/content") + contentAPIRouter.Use(s.LogMiddleware.LoggerWithConfig(middleware.GinLoggerConfig{}), s.RecoveryMiddleware.RecoveryWithLogger()) + + contentAPIRouter.GET("/archives/years", s.wrapHandler(s.ContentAPIArchiveHandler.ListYearArchives)) + contentAPIRouter.GET("/archives/months", s.wrapHandler(s.ContentAPIArchiveHandler.ListMonthArchives)) + + contentAPIRouter.GET("/categories", s.wrapHandler(s.ContentAPICategoryHandler.ListCategories)) + contentAPIRouter.GET("/categories/:slug/posts", s.wrapHandler(s.ContentAPICategoryHandler.ListPosts)) + + contentAPIRouter.GET("/journals", s.wrapHandler(s.ContentAPIJournalHandler.ListJournal)) + contentAPIRouter.GET("/journals/:journalID", s.wrapHandler(s.ContentAPIJournalHandler.GetJournal)) + contentAPIRouter.GET("/journals/:journalID/comments/top_view", s.wrapHandler(s.ContentAPIJournalHandler.ListTopComment)) + contentAPIRouter.GET("/journals/:journalID/comments/:parentID/children", s.wrapHandler(s.ContentAPIJournalHandler.ListChildren)) + contentAPIRouter.GET("/journals/:journalID/comments/tree_view", s.wrapHandler(s.ContentAPIJournalHandler.ListCommentTree)) + contentAPIRouter.GET("/journals/:journalID/comments/list_view", s.wrapHandler(s.ContentAPIJournalHandler.ListComment)) + contentAPIRouter.POST("/journals/comments", s.wrapHandler(s.ContentAPIJournalHandler.CreateComment)) + + contentAPIRouter.GET("/posts/:postID/comments/top_view", s.wrapHandler(s.ContentAPIPostHandler.ListTopComment)) + contentAPIRouter.GET("/posts/:postID/comments/:parentID/children", s.wrapHandler(s.ContentAPIPostHandler.ListChildren)) + contentAPIRouter.GET("/posts/:postID/comments/tree_view", s.wrapHandler(s.ContentAPIPostHandler.ListCommentTree)) + contentAPIRouter.GET("/posts/:postID/comments/list_view", s.wrapHandler(s.ContentAPIPostHandler.ListComment)) + contentAPIRouter.POST("/posts/comments", s.wrapHandler(s.ContentAPIPostHandler.CreateComment)) + + contentAPIRouter.GET("/sheets/:sheetID/comments/top_view", s.wrapHandler(s.ContentAPISheetHandler.ListTopComment)) + contentAPIRouter.GET("/sheets/:sheetID/comments/:parentID/children", s.wrapHandler(s.ContentAPISheetHandler.ListChildren)) + contentAPIRouter.GET("/sheets/:sheetID/comments/tree_view", s.wrapHandler(s.ContentAPISheetHandler.ListCommentTree)) + contentAPIRouter.GET("/sheets/:sheetID/comments/list_view", s.wrapHandler(s.ContentAPISheetHandler.ListComment)) + contentAPIRouter.POST("/sheets/comments", s.wrapHandler(s.ContentAPISheetHandler.CreateComment)) + + contentAPIRouter.GET("/links", s.wrapHandler(s.ContentAPILinkHandler.ListLinks)) + contentAPIRouter.GET("/links/team_view", s.wrapHandler(s.ContentAPILinkHandler.LinkTeamVO)) + + contentAPIRouter.GET("/options/comment", s.wrapHandler(s.ContentAPIOptionHander.Comment)) + } + } + +} + +func (s *Server) registerDynamicRouters(contentRouter *gin.RouterGroup) error { + ctx := context.Background() + ctx = dal.SetCtxDB(ctx, dal.GetDBByCtx(ctx).Session(&gorm.Session{ + Logger: dal.DB.Logger.LogMode(logger.Warn), + })) + // theme, err := s.ThemeService.GetActivateTheme(ctx) + // if err != nil { + // return nil + // } + // s.Router.StaticFS("/themes/"+theme.FolderName, gin.Dir(theme.ThemePath, false)) + + archivePath, err := s.OptionService.GetArchivePrefix(ctx) + if err != nil { + return err + } + categoryPath, err := s.OptionService.GetCategoryPrefix(ctx) + if err != nil { + return err + } + sheetPermaLinkType, err := s.OptionService.GetSheetPermalinkType(ctx) + if err != nil { + return err + } + sheetPath, err := s.OptionService.GetSheetPrefix(ctx) + if err != nil { + return err + } + tagPath, err := s.OptionService.GetTagPrefix(ctx) + if err != nil { + return err + } + journalPath, err := s.OptionService.GetJournalPrefix(ctx) + if err != nil { + return err + } + + photoPath, err := s.OptionService.GetPhotoPrefix(ctx) + if err != nil { + return err + } + linkPath, err := s.OptionService.GetLinkPrefix(ctx) + if err != nil { + return err + } + contentRouter.GET(archivePath, s.wrapHTMLHandler(s.ArchiveHandler.Archives)) + contentRouter.GET(archivePath+"/page/:page", s.wrapHTMLHandler(s.ArchiveHandler.ArchivesPage)) + contentRouter.GET(archivePath+"/:slug", s.wrapHTMLHandler(s.ArchiveHandler.ArchivesBySlug)) + + contentRouter.GET(tagPath, s.wrapHTMLHandler(s.ContentTagHandler.Tags)) + contentRouter.GET(tagPath+"/:slug/page/:page", s.wrapHTMLHandler(s.ContentTagHandler.TagPostPage)) + contentRouter.GET(tagPath+"/:slug", s.wrapHTMLHandler(s.ContentTagHandler.TagPost)) + + contentRouter.GET(categoryPath, s.wrapHTMLHandler(s.ContentCategoryHandler.Categories)) + contentRouter.GET(categoryPath+"/:slug", s.wrapHTMLHandler(s.ContentCategoryHandler.CategoryDetail)) + contentRouter.GET(categoryPath+"/:slug/page/:page", s.wrapHTMLHandler(s.ContentCategoryHandler.CategoryDetailPage)) + + contentRouter.GET(linkPath, s.wrapHTMLHandler(s.ContentLinkHandler.Link)) + + contentRouter.GET(photoPath, s.wrapHTMLHandler(s.ContentPhotoHandler.Phtotos)) + contentRouter.GET(photoPath+"/page/:page", s.wrapHTMLHandler(s.ContentPhotoHandler.PhotosPage)) + + contentRouter.GET(journalPath, s.wrapHTMLHandler(s.ContentJournalHandler.Journals)) + contentRouter.GET(journalPath+"/page/:page", s.wrapHTMLHandler(s.ContentJournalHandler.JournalsPage)) + + if sheetPermaLinkType == consts.SheetPermaLinkTypeRoot { + contentRouter.GET("/:slug") + } else { + contentRouter.GET(sheetPath+"/:slug", s.wrapHTMLHandler(s.ContentSheetHandler.SheetBySlug)) + } + return nil +} diff --git a/handler/server.go b/handler/server.go new file mode 100644 index 00000000..d5ec59a7 --- /dev/null +++ b/handler/server.go @@ -0,0 +1,302 @@ +package handler + +import ( + "context" + "fmt" + "net/http" + "strconv" + + "github.com/gin-gonic/gin" + "go.uber.org/dig" + "go.uber.org/fx" + "go.uber.org/zap" + + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/handler/admin" + "github.com/go-sonic/sonic/handler/content" + "github.com/go-sonic/sonic/handler/content/api" + "github.com/go-sonic/sonic/handler/middleware" + "github.com/go-sonic/sonic/model/dto" + "github.com/go-sonic/sonic/service" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/util/xerr" +) + +type Server struct { + logger *zap.Logger + Config *config.Config + HttpServer *http.Server + Router *gin.Engine + Template *template.Template + AuthMiddleware *middleware.AuthMiddleware + LogMiddleware *middleware.GinLoggerMiddleware + RecoveryMiddleware *middleware.RecoveryMiddleware + OptionService service.OptionService + ThemeService service.ThemeService + SheetService service.SheetService + AdminHandler *admin.AdminHandler + AttachmentHandler *admin.AttachmentHandler + BackupHandler *admin.BackupHandler + CategoryHandler *admin.CategoryHandler + InstallHandler *admin.InstallHandler + JournalHandler *admin.JournalHandler + JournalCommentHandler *admin.JournalCommentHandler + LinkHandler *admin.LinkHandler + LogHandler *admin.LogHandler + MenuHandler *admin.MenuHandler + OptionHandler *admin.OptionHandler + PhotoHandler *admin.PhotoHandler + PostHandler *admin.PostHandler + PostCommentHandler *admin.PostCommentHandler + SheetHandler *admin.SheetHandler + SheetCommentHandler *admin.SheetCommentHandler + StatisticHandler *admin.StatisticHandler + TagHandler *admin.TagHandler + ThemeHandler *admin.ThemeHandler + UserHandler *admin.UserHandler + EmailHandler *admin.EmailHandler + IndexHandler *content.IndexHandler + FeedHandler *content.FeedHandler + ArchiveHandler *content.ArchiveHandler + ViewHandler *content.ViewHandler + ContentCategoryHandler *content.CategoryHandler + ContentSheetHandler *content.SheetHandler + ContentTagHandler *content.TagHandler + ContentLinkHandler *content.LinkHandler + ContentPhotoHandler *content.PhotoHandler + ContentJournalHandler *content.JournalHandler + ContentAPIArchiveHandler *api.ArchiveHandler + ContentAPICategoryHandler *api.CategoryHandler + ContentAPIJournalHandler *api.JournalHandler + ContentAPILinkHandler *api.LinkHandler + ContentAPIPostHandler *api.PostHandler + ContentAPISheetHandler *api.SheetHandler + ContentAPIOptionHander *api.OptionHandler +} + +type ServerParams struct { + dig.In + Config *config.Config + Logger *zap.Logger + Event event.Bus + Template *template.Template + AuthMiddleware *middleware.AuthMiddleware + LogMiddleware *middleware.GinLoggerMiddleware + RecoveryMiddleware *middleware.RecoveryMiddleware + OptionService service.OptionService + ThemeService service.ThemeService + SheetService service.SheetService + AdminHandler *admin.AdminHandler + AttachmentHandler *admin.AttachmentHandler + BackupHandler *admin.BackupHandler + CategoryHandler *admin.CategoryHandler + InstallHandler *admin.InstallHandler + JournalHandler *admin.JournalHandler + JournalCommentHandler *admin.JournalCommentHandler + LinkHandler *admin.LinkHandler + LogHandler *admin.LogHandler + MenuHandler *admin.MenuHandler + OptionHandler *admin.OptionHandler + PhotoHandler *admin.PhotoHandler + PostHandler *admin.PostHandler + PostCommentHandler *admin.PostCommentHandler + SheetHandler *admin.SheetHandler + SheetCommentHandler *admin.SheetCommentHandler + StatisticHandler *admin.StatisticHandler + TagHandler *admin.TagHandler + ThemeHandler *admin.ThemeHandler + UserHandler *admin.UserHandler + EmailHandler *admin.EmailHandler + IndexHandler *content.IndexHandler + FeedHandler *content.FeedHandler + ArchiveHandler *content.ArchiveHandler + ViewHandler *content.ViewHandler + ContentCategoryHandler *content.CategoryHandler + ContentSheetHandler *content.SheetHandler + ContentTagHandler *content.TagHandler + ContentLinkHandler *content.LinkHandler + ContentPhotoHandler *content.PhotoHandler + ContentJournalHandler *content.JournalHandler + ContentAPIArchiveHandler *api.ArchiveHandler + ContentAPICategoryHandler *api.CategoryHandler + ContentAPIJournalHandler *api.JournalHandler + ContentAPILinkHandler *api.LinkHandler + ContentAPIPostHandler *api.PostHandler + ContentAPISheetHandler *api.SheetHandler + ContentAPIOptionHander *api.OptionHandler +} + +func NewServer(param ServerParams, lifecycle fx.Lifecycle) *Server { + gin.SetMode(gin.ReleaseMode) + router := gin.New() + conf := param.Config + + httpServer := &http.Server{ + Addr: fmt.Sprintf("%s:%s", conf.Server.Host, conf.Server.Port), + Handler: router, + } + + s := &Server{ + logger: param.Logger, + Config: param.Config, + HttpServer: httpServer, + Router: router, + Template: param.Template, + AuthMiddleware: param.AuthMiddleware, + LogMiddleware: param.LogMiddleware, + RecoveryMiddleware: param.RecoveryMiddleware, + AdminHandler: param.AdminHandler, + AttachmentHandler: param.AttachmentHandler, + BackupHandler: param.BackupHandler, + CategoryHandler: param.CategoryHandler, + InstallHandler: param.InstallHandler, + JournalHandler: param.JournalHandler, + JournalCommentHandler: param.JournalCommentHandler, + LinkHandler: param.LinkHandler, + LogHandler: param.LogHandler, + MenuHandler: param.MenuHandler, + OptionHandler: param.OptionHandler, + PhotoHandler: param.PhotoHandler, + PostHandler: param.PostHandler, + PostCommentHandler: param.PostCommentHandler, + SheetHandler: param.SheetHandler, + SheetCommentHandler: param.SheetCommentHandler, + StatisticHandler: param.StatisticHandler, + TagHandler: param.TagHandler, + ThemeHandler: param.ThemeHandler, + UserHandler: param.UserHandler, + EmailHandler: param.EmailHandler, + OptionService: param.OptionService, + ThemeService: param.ThemeService, + SheetService: param.SheetService, + IndexHandler: param.IndexHandler, + FeedHandler: param.FeedHandler, + ArchiveHandler: param.ArchiveHandler, + ViewHandler: param.ViewHandler, + ContentCategoryHandler: param.ContentCategoryHandler, + ContentSheetHandler: param.ContentSheetHandler, + ContentTagHandler: param.ContentTagHandler, + ContentLinkHandler: param.ContentLinkHandler, + ContentPhotoHandler: param.ContentPhotoHandler, + ContentJournalHandler: param.ContentJournalHandler, + ContentAPIArchiveHandler: param.ContentAPIArchiveHandler, + ContentAPICategoryHandler: param.ContentAPICategoryHandler, + ContentAPIJournalHandler: param.ContentAPIJournalHandler, + ContentAPILinkHandler: param.ContentAPILinkHandler, + ContentAPIPostHandler: param.ContentAPIPostHandler, + ContentAPISheetHandler: param.ContentAPISheetHandler, + ContentAPIOptionHander: param.ContentAPIOptionHander, + } + lifecycle.Append(fx.Hook{ + OnStop: httpServer.Shutdown, + OnStart: s.Run, + }) + return s +} + +func (s *Server) Run(ctx context.Context) error { + if config.IsDev() { + gin.SetMode(gin.DebugMode) + } + go func() { + if err := s.HttpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + s.logger.Error("unexpected error from ListenAndServe", zap.Error(err)) + } + }() + return nil +} + +type wrapperHandler func(ctx *gin.Context) (interface{}, error) + +func (s *Server) wrapHandler(handler wrapperHandler) gin.HandlerFunc { + return func(ctx *gin.Context) { + data, err := handler(ctx) + if err != nil { + s.logger.Error("handler error", zap.Error(err)) + status := xerr.GetHttpStatus(err) + ctx.JSON(status, &dto.BaseDTO{Status: status, Message: xerr.GetMessage(err)}) + return + } + + ctx.JSON(http.StatusOK, &dto.BaseDTO{ + Status: http.StatusOK, + Data: data, + Message: "OK", + }) + } +} + +type wrapperHTMLHandler func(ctx *gin.Context, model template.Model) (templateName string, err error) + +var ( + htmlContentType = []string{"text/html; charset=utf-8"} + xmlContentType = []string{"application/xml; charset=utf-8"} +) + +func (s *Server) wrapHTMLHandler(handler wrapperHTMLHandler) gin.HandlerFunc { + return func(ctx *gin.Context) { + model := template.Model{} + templateName, err := handler(ctx, model) + if err != nil { + s.handleError(ctx, err) + return + } + if templateName == "" { + return + } + header := ctx.Writer.Header() + if val := header["Content-Type"]; len(val) == 0 { + header["Content-Type"] = htmlContentType + } + err = s.Template.ExecuteTemplate(ctx.Writer, templateName, model) + if err != nil { + s.logger.Error("render template err", zap.Error(err)) + } + } +} + +func (s *Server) wrapTextHandler(handler wrapperHTMLHandler) gin.HandlerFunc { + return func(ctx *gin.Context) { + model := template.Model{} + templateName, err := handler(ctx, model) + if err != nil { + s.handleError(ctx, err) + return + } + header := ctx.Writer.Header() + if val := header["Content-Type"]; len(val) == 0 { + header["Content-Type"] = xmlContentType + } + err = s.Template.ExecuteTextTemplate(ctx.Writer, templateName, model) + if err != nil { + s.logger.Error("render template err", zap.Error(err)) + } + } +} + +func (s *Server) handleError(ctx *gin.Context, err error) { + status := xerr.GetHttpStatus(err) + message := xerr.GetMessage(err) + model := template.Model{} + + templateName, _ := s.ThemeService.Render(ctx, strconv.Itoa(status)) + t := s.Template.HtmlTemplate.Lookup(templateName) + if t == nil { + templateName = "common/error/error" + } + + header := ctx.Writer.Header() + if val := header["Content-Type"]; len(val) == 0 { + header["Content-Type"] = htmlContentType + } + + model["status"] = status + model["message"] = message + model["err"] = err + + err = s.Template.ExecuteTemplate(ctx.Writer, templateName, model) + if err != nil { + s.logger.Error("render error template err", zap.Error(err)) + } +} diff --git a/handler/trans/trans.go b/handler/trans/trans.go new file mode 100644 index 00000000..47ebdfce --- /dev/null +++ b/handler/trans/trans.go @@ -0,0 +1,46 @@ +package trans + +import ( + "fmt" + "strings" + + "github.com/gin-gonic/gin/binding" + "github.com/go-playground/locales/en" + "github.com/go-playground/locales/zh" + ut "github.com/go-playground/universal-translator" + "github.com/go-playground/validator/v10" + chTranslations "github.com/go-playground/validator/v10/translations/zh" +) + +var trans ut.Translator + +func init() { + local := "zh" + if v, ok := binding.Validator.Engine().(*validator.Validate); ok { + zhT := zh.New() // chinese + enT := en.New() // english + uni := ut.New(enT, zhT, enT) + + var o bool + trans, o = uni.GetTranslator(local) + if !o { + panic(fmt.Sprintf("uni.GetTranslator(%s) failed", local)) + } + + err := chTranslations.RegisterDefaultTranslations(v, trans) + if err != nil { + panic(err) + } + + return + } +} + +func Translate(errs validator.ValidationErrors) string { + var errList []string + for _, e := range errs { + // can translate each error one at a time. + errList = append(errList, e.Translate(trans)) + } + return strings.Join(errList, "|") +} diff --git a/injection/fx.go b/injection/fx.go new file mode 100644 index 00000000..4df50a2e --- /dev/null +++ b/injection/fx.go @@ -0,0 +1,17 @@ +package injection + +import "go.uber.org/fx" + +var options []fx.Option + +func Provide(constructors ...interface{}) { + options = append(options, fx.Provide(constructors...)) +} + +func Invoke(funcs ...interface{}) { + options = append(options, fx.Invoke(funcs...)) +} + +func GetOptions() []fx.Option { + return options +} diff --git a/log/gorm_logger.go b/log/gorm_logger.go new file mode 100644 index 00000000..da3f08e2 --- /dev/null +++ b/log/gorm_logger.go @@ -0,0 +1,128 @@ +package log + +import ( + "context" + "errors" + "fmt" + "runtime" + "strings" + "time" + + "go.uber.org/zap" + "gorm.io/gorm" + "gorm.io/gorm/logger" + + "github.com/go-sonic/sonic/config" +) + +type gormLogger struct { + logger.Config + traceStr string + traceWarnStr string + traceErrStr string + zapLogger *zap.Logger +} + +func NewGormLogger(conf *config.Config, zapLogger *zap.Logger) logger.Interface { + logConfig := logger.Config{ + SlowThreshold: 200 * time.Millisecond, + LogLevel: GetGormLogLevel(conf.Log.Levels.Gorm), + IgnoreRecordNotFoundError: true, + Colorful: config.IsDev(), + } + gl := &gormLogger{ + Config: logConfig, + traceStr: "[%.3fms] [rows:%v] %s", + traceWarnStr: "%s [%.3fms] [rows:%v] %s", + traceErrStr: "%s [%.3fms] [rows:%v] %s", + zapLogger: zapLogger, + } + if logConfig.Colorful { + gl.traceStr = logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" + gl.traceWarnStr = "%s " + logger.Reset + logger.RedBold + "[%.3fms] " + logger.Yellow + "[rows:%v]" + logger.Magenta + " %s" + logger.Reset + gl.traceErrStr = logger.MagentaBold + "%s " + logger.Reset + logger.Yellow + "[%.3fms] " + logger.BlueBold + "[rows:%v]" + logger.Reset + " %s" + } + return gl +} + +func (l *gormLogger) LogMode(level logger.LogLevel) logger.Interface { + newLogger := *l + newLogger.LogLevel = level + return &newLogger +} + +const level = 2 + +func (l *gormLogger) Info(ctx context.Context, msg string, data ...interface{}) { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Infof(msg, data...) +} + +func (l *gormLogger) Warn(ctx context.Context, msg string, data ...interface{}) { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Warnf(msg, data...) +} + +func (l *gormLogger) Error(ctx context.Context, msg string, data ...interface{}) { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Errorf(msg, data...) +} + +func (l *gormLogger) Trace(ctx context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) { + if l.LogLevel <= logger.Silent { + return + } + + elapsed := time.Since(begin) + switch { + case err != nil && l.LogLevel >= logger.Error && (!errors.Is(err, gorm.ErrRecordNotFound) || !l.IgnoreRecordNotFoundError): + sql, rows := fc() + if rows == -1 { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Errorf(l.traceErrStr, err, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Errorf(l.traceErrStr, err, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case elapsed > l.SlowThreshold && l.SlowThreshold != 0 && l.LogLevel >= logger.Warn: + sql, rows := fc() + slowLog := fmt.Sprintf("SLOW SQL >= %v", l.SlowThreshold) + if rows == -1 { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Warnf(l.traceWarnStr, slowLog, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Warnf(l.traceWarnStr, slowLog, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + case l.LogLevel == logger.Info: + sql, rows := fc() + if rows == -1 { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Infof(l.traceStr, float64(elapsed.Nanoseconds())/1e6, "-", sql) + } else { + l.zapLogger.WithOptions(zap.AddCallerSkip(getCallerSkip()-level)).Sugar().Infof(l.traceStr, float64(elapsed.Nanoseconds())/1e6, rows, sql) + } + } +} + +func getCallerSkip() int { + for i := 3; i < 15; i++ { + pc := make([]uintptr, 1) + numFrames := runtime.Callers(i, pc) + if numFrames < 1 { + return i + } + frame, _ := runtime.CallersFrames(pc).Next() + if !strings.Contains(frame.Function, "gorm.io") && !strings.Contains(frame.Function, "github.com/go-sonic/sonic/dal") { + return i + } + } + return 0 +} + +func GetGormLogLevel(level string) logger.LogLevel { + switch level { + case "info": + return logger.Info + case "warn": + return logger.Warn + case "error": + return logger.Error + case "silent": + return logger.Silent + default: + panic("log level error") + } +} diff --git a/log/init.go b/log/init.go new file mode 100644 index 00000000..8edb6314 --- /dev/null +++ b/log/init.go @@ -0,0 +1,79 @@ +package log + +import ( + "os" + "path/filepath" + + "github.com/natefinch/lumberjack" + "go.uber.org/zap" + "go.uber.org/zap/zapcore" + + "github.com/go-sonic/sonic/config" +) + +func NewLogger(conf *config.Config) *zap.Logger { + _, err := os.Stat(conf.Sonic.LogDir) + if err != nil { + if os.IsNotExist(err) && !config.IsDev() { + err := os.MkdirAll(conf.Sonic.LogDir, os.ModePerm) + if err != nil { + panic("mkdir failed![%v]") + } + } + } + + var core zapcore.Core + + if config.IsDev() { + core = zapcore.NewCore(getDevEncoder(), os.Stdout, getLogLevel(conf.Log.Levels.App)) + } else { + core = zapcore.NewCore(getProdEncoder(), getWriter(conf), zap.DebugLevel) + } + + // 传入 zap.AddCaller() 显示打日志点的文件名和行数 + logger := zap.New(core, zap.AddCaller(), zap.AddStacktrace(zap.DPanicLevel)) + + exportUseLogger = logger.WithOptions(zap.AddCallerSkip(1)) + exportUseSugarLogger = exportUseLogger.Sugar() + return logger +} + +// getWriter 自定义Writer,分割日志 +func getWriter(conf *config.Config) zapcore.WriteSyncer { + rotatingLogger := &lumberjack.Logger{ + Filename: filepath.Join(conf.Sonic.LogDir, conf.Log.FileName), + MaxSize: conf.Log.MaxSize, + MaxAge: conf.Log.MaxAge, + Compress: conf.Log.Compress, + } + return zapcore.AddSync(rotatingLogger) +} + +// getProdEncoder 自定义日志编码器 +func getProdEncoder() zapcore.Encoder { + encoderConfig := zap.NewProductionEncoderConfig() + encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder + encoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder + return zapcore.NewConsoleEncoder(encoderConfig) +} + +func getDevEncoder() zapcore.Encoder { + encoderConfig := zap.NewDevelopmentEncoderConfig() + encoderConfig.EncodeLevel = zapcore.CapitalColorLevelEncoder + return zapcore.NewConsoleEncoder(encoderConfig) +} + +func getLogLevel(level string) zapcore.Level { + switch level { + case "debug": + return zapcore.DebugLevel + case "info": + return zapcore.InfoLevel + case "warn": + return zapcore.WarnLevel + case "error": + return zapcore.ErrorLevel + default: + panic("log level error") + } +} diff --git a/log/log.go b/log/log.go new file mode 100644 index 00000000..941c789a --- /dev/null +++ b/log/log.go @@ -0,0 +1,97 @@ +package log + +import ( + "context" + + "go.uber.org/zap" +) + +var ( + exportUseLogger *zap.Logger + exportUseSugarLogger *zap.SugaredLogger +) + +func Debugf(template string, args ...interface{}) { + exportUseSugarLogger.Debugf(template, args...) +} + +func Infof(template string, args ...interface{}) { + exportUseSugarLogger.Infof(template, args...) +} + +func Warnf(template string, args ...interface{}) { + exportUseSugarLogger.Warnf(template, args...) +} + +func Errorf(template string, args ...interface{}) { + exportUseSugarLogger.Errorf(template, args...) +} + +func Fatalf(template string, args ...interface{}) { + exportUseSugarLogger.Fatalf(template, args...) +} + +func Debug(msg string, fields ...zap.Field) { + exportUseLogger.Debug(msg, fields...) +} + +func Info(msg string, fields ...zap.Field) { + exportUseLogger.Info(msg, fields...) +} + +func Warn(msg string, fields ...zap.Field) { + exportUseLogger.Warn(msg, fields...) +} + +func Error(msg string, fields ...zap.Field) { + exportUseLogger.Error(msg, fields...) +} + +func Fatal(msg string, fields ...zap.Field) { + exportUseLogger.Fatal(msg, fields...) +} + +func CtxDebugf(ctx context.Context, template string, args ...interface{}) { + exportUseSugarLogger.Debugf(template, args...) +} + +func CtxInfof(ctx context.Context, template string, args ...interface{}) { + exportUseSugarLogger.Infof(template, args...) +} + +func CtxWarnf(ctx context.Context, template string, args ...interface{}) { + exportUseSugarLogger.Warnf(template, args...) +} + +func CtxErrorf(ctx context.Context, template string, args ...interface{}) { + exportUseSugarLogger.Errorf(template, args...) +} + +func CtxFatalf(ctx context.Context, template string, args ...interface{}) { + exportUseSugarLogger.Fatalf(template, args...) +} + +func CtxDebug(ctx context.Context, msg string, fields ...zap.Field) { + exportUseLogger.Debug(msg, fields...) +} + +func CtxInfo(ctx context.Context, msg string, fields ...zap.Field) { + exportUseLogger.Info(msg, fields...) +} + +func CtxWarn(ctx context.Context, msg string, fields ...zap.Field) { + exportUseLogger.Warn(msg, fields...) +} + +func CtxError(ctx context.Context, msg string, fields ...zap.Field) { + exportUseLogger.Error(msg, fields...) +} + +func CtxFatal(ctx context.Context, msg string, fields ...zap.Field) { + exportUseLogger.Fatal(msg, fields...) +} + +func Sync() { + exportUseLogger.Sync() + exportUseSugarLogger.Sync() +} diff --git a/main.go b/main.go new file mode 100644 index 00000000..3f802d2a --- /dev/null +++ b/main.go @@ -0,0 +1,77 @@ +package main + +import ( + "context" + + "go.uber.org/fx" + + "github.com/go-sonic/sonic/cache" + "github.com/go-sonic/sonic/config" + "github.com/go-sonic/sonic/dal" + "github.com/go-sonic/sonic/event" + "github.com/go-sonic/sonic/event/listener" + _ "github.com/go-sonic/sonic/event/listener" + "github.com/go-sonic/sonic/handler" + "github.com/go-sonic/sonic/handler/middleware" + "github.com/go-sonic/sonic/injection" + "github.com/go-sonic/sonic/log" + "github.com/go-sonic/sonic/template" + "github.com/go-sonic/sonic/template/extension" +) + +var eventBus event.Bus + +func main() { + app := InitApp() + + if err := app.Start(context.Background()); err != nil { + panic(err) + } + eventBus.Publish(context.Background(), &event.StartEvent{}) + <-app.Done() +} + +func InitApp() *fx.App { + options := injection.GetOptions() + options = append(options, + fx.NopLogger, + fx.Provide( + log.NewLogger, + log.NewGormLogger, + event.NewSyncEventBus, + dal.NewGormDB, + cache.NewCache, + config.NewConfig, + handler.NewServer, + template.NewTemplate, + middleware.NewAuthMiddleware, + middleware.NewGinLoggerMiddleware, + middleware.NewRecoveryMiddleware, + ), + fx.Populate(&dal.DB), + fx.Populate(&eventBus), + fx.Invoke( + listener.NewStartListener, + listener.NewTemplateConfigListener, + listener.NewLogEventListener, + listener.NewPostUpdateListener, + listener.NewCommentListener, + extension.RegisterCategoryFunc, + extension.RegisterCommentFunc, + extension.RegisterTagFunc, + extension.RegisterMenuFunc, + extension.RegisterPhotoFunc, + extension.RegisterLinkFunc, + extension.RegisterToolFunc, + extension.RegisterPaginationFunc, + extension.RegisterPostFunc, + func(s *handler.Server) { + s.RegisterRouters() + }, + ), + ) + app := fx.New( + options..., + ) + return app +} diff --git a/model/dto/attachment.go b/model/dto/attachment.go new file mode 100644 index 00000000..db1b39f7 --- /dev/null +++ b/model/dto/attachment.go @@ -0,0 +1,19 @@ +package dto + +import ( + "github.com/go-sonic/sonic/consts" +) + +type AttachmentDTO struct { + ID int32 `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + FileKey string `json:"fileKey"` + ThumbPath string `json:"thumbPath"` + MediaType string `json:"mediaType"` + Suffix string `json:"suffix"` + Width int32 `json:"width"` + Height int32 `json:"height"` + Size int64 `json:"size"` + AttachmentType consts.AttachmentType `json:"type"` +} diff --git a/model/dto/auth_token.go b/model/dto/auth_token.go new file mode 100644 index 00000000..60d2a6e3 --- /dev/null +++ b/model/dto/auth_token.go @@ -0,0 +1,7 @@ +package dto + +type AuthTokenDTO struct { + AccessToken string `json:"access_token"` + ExpiredIn int `json:"expired_in"` + RefreshToken string `json:"refresh_token"` +} diff --git a/model/dto/backup.go b/model/dto/backup.go new file mode 100644 index 00000000..e0a5befd --- /dev/null +++ b/model/dto/backup.go @@ -0,0 +1,8 @@ +package dto + +type BackupDTO struct { + DownloadLink string `json:"downloadLink"` + Filename string `json:"filename"` + UpdateTime int64 `json:"updateTime"` + FileSize int64 `json:"fileSize"` +} diff --git a/model/dto/base.go b/model/dto/base.go new file mode 100644 index 00000000..7b72b15a --- /dev/null +++ b/model/dto/base.go @@ -0,0 +1,55 @@ +package dto + +import ( + "math" + "reflect" + + "github.com/go-sonic/sonic/model/param" + "github.com/go-sonic/sonic/util" +) + +type BaseDTO struct { + Status int `json:"status"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type Page struct { + Content interface{} `json:"content"` + Pages int `json:"pages"` + Total int64 `json:"total"` + RPP int `json:"rpp"` + PageNum int `json:"pageNum"` + HasNext bool `json:"hasNext"` + HasPrevious bool `json:"hasPrevious"` + IsFirst bool `json:"isFirst"` + IsLast bool `json:"isLast"` + IsEmpty bool `json:"isEmpty"` + HasContent bool `json:"hasContent"` +} + +func NewPage(content interface{}, totalCount int64, page param.Page) *Page { + contentLen := 0 + r := reflect.ValueOf(content) + + if !r.IsNil() && r.Kind() != reflect.Slice { + panic("not slice") + } else { + contentLen = r.Len() + } + totalPage := util.IfElse(page.PageSize == 0, 1, int(math.Ceil(float64(totalCount)/float64(page.PageSize)))).(int) + dtoPage := &Page{ + Content: content, + Total: totalCount, + Pages: totalPage, + PageNum: page.PageNum, + RPP: page.PageNum, + HasNext: page.PageNum+1 < totalPage, + HasPrevious: page.PageNum > 0, + IsFirst: page.PageNum == 0, + IsLast: page.PageNum+1 == totalPage, + IsEmpty: contentLen == 0, + HasContent: contentLen > 0, + } + return dtoPage +} diff --git a/model/dto/category.go b/model/dto/category.go new file mode 100644 index 00000000..803b691a --- /dev/null +++ b/model/dto/category.go @@ -0,0 +1,20 @@ +package dto + +type CategoryDTO struct { + ID int32 `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Description string `json:"description"` + Thumbnail string `json:"thumbnail"` + ParentID int32 `json:"parentId"` + Password string `json:"password"` + CreateTime int64 `json:"createTime"` + FullPath string `json:"fullPath"` + Priority int32 `json:"priority"` + Type int32 `json:"type"` +} + +type CategoryWithPostCount struct { + *CategoryDTO + PostCount int32 `json:"postCount"` +} diff --git a/model/dto/comment.go b/model/dto/comment.go new file mode 100644 index 00000000..ba18c4cd --- /dev/null +++ b/model/dto/comment.go @@ -0,0 +1,20 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type Comment struct { + ID int64 `json:"id"` + Author string `json:"author"` + Email string `json:"email"` + IpAddress string `json:"ipAddress"` + AuthorURL string `json:"authorUrl"` + GravatarMD5 string `json:"gravatarMd5"` + Content string `json:"content"` + Status consts.CommentStatus `json:"status"` + UserAgent string `json:"userAgent"` + ParentID int64 `json:"parentId"` + IsAdmin bool `json:"isAdmin"` + AllowNotification bool `json:"allowNotification"` + CreateTime int64 `json:"createTime"` + Avatar string `json:"avatar"` +} diff --git a/model/dto/environment.go b/model/dto/environment.go new file mode 100644 index 00000000..097659d4 --- /dev/null +++ b/model/dto/environment.go @@ -0,0 +1,8 @@ +package dto + +type EnvironmentDTO struct { + Database string `json:"database"` + StartTime int64 `json:"startTime"` + Version string `json:"version"` + Mode string `json:"mode"` +} diff --git a/model/dto/independent_sheet.go b/model/dto/independent_sheet.go new file mode 100644 index 00000000..6a92e81e --- /dev/null +++ b/model/dto/independent_sheet.go @@ -0,0 +1,9 @@ +package dto + +type IndependentSheet struct { + ID int32 `json:"id"` + Title string `json:"title"` + FullPath string `json:"fullPath"` + RouteName string `json:"routeName"` + Available bool `json:"available"` +} diff --git a/model/dto/journal.go b/model/dto/journal.go new file mode 100644 index 00000000..92098eae --- /dev/null +++ b/model/dto/journal.go @@ -0,0 +1,17 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type Journal struct { + ID int32 `json:"id"` + SourceContent string `json:"sourceContent"` + Content string `json:"content"` + Likes int64 `json:"likes"` + CreateTime int64 `json:"createTime"` + JournalType consts.JournalType `json:"journalType"` +} + +type JournalWithComment struct { + Journal + CommentCount int64 `json:"commentCount"` +} diff --git a/model/dto/link.go b/model/dto/link.go new file mode 100644 index 00000000..c07e65a7 --- /dev/null +++ b/model/dto/link.go @@ -0,0 +1,11 @@ +package dto + +type Link struct { + ID int32 `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Logo string `json:"logo"` + Description string `json:"description"` + Team string `json:"team"` + Priority int32 `json:"priority"` +} diff --git a/model/dto/log.go b/model/dto/log.go new file mode 100644 index 00000000..dfd3fd26 --- /dev/null +++ b/model/dto/log.go @@ -0,0 +1,12 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type Log struct { + ID int64 `json:"id"` + LogKey string `json:"logKey"` + LogType consts.LogType `json:"type"` + Content string `json:"content"` + IPAddress string `json:"ipAddress"` + CreateTime int64 `json:"createTime"` +} diff --git a/model/dto/login_pre_check.go b/model/dto/login_pre_check.go new file mode 100644 index 00000000..13a67b95 --- /dev/null +++ b/model/dto/login_pre_check.go @@ -0,0 +1,5 @@ +package dto + +type LoginPreCheckDTO struct { + NeedMFACode bool `json:"needMFACode"` +} diff --git a/model/dto/menu.go b/model/dto/menu.go new file mode 100644 index 00000000..a1c4cdfd --- /dev/null +++ b/model/dto/menu.go @@ -0,0 +1,12 @@ +package dto + +type Menu struct { + ID int32 `json:"id"` + Name string `json:"name"` + URL string `json:"url"` + Priority int32 `json:"priority"` + Target string `json:"target"` + Icon string `json:"icon"` + ParentID int32 `json:"parentId"` + Team string `json:"team"` +} diff --git a/model/dto/meta.go b/model/dto/meta.go new file mode 100644 index 00000000..9ac4729f --- /dev/null +++ b/model/dto/meta.go @@ -0,0 +1,9 @@ +package dto + +type Meta struct { + ID int64 `json:"id"` + PostID int32 `json:"postId"` + Key string `json:"key"` + Value string `json:"value"` + CreateTime int64 `json:"createTime"` +} diff --git a/model/dto/option.go b/model/dto/option.go new file mode 100644 index 00000000..f9a10cb1 --- /dev/null +++ b/model/dto/option.go @@ -0,0 +1,6 @@ +package dto + +type Option struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} diff --git a/model/dto/photo.go b/model/dto/photo.go new file mode 100644 index 00000000..8dd63742 --- /dev/null +++ b/model/dto/photo.go @@ -0,0 +1,13 @@ +package dto + +type Photo struct { + ID int32 `json:"id"` + Name string `json:"name"` + Thumbnail string `json:"thumbnail"` + TakeTime int64 `json:"takeTime"` + URL string `json:"url"` + Team string `json:"team"` + Location string `json:"location"` + Description string `json:"description"` + Likes int64 `json:"likes"` +} diff --git a/model/dto/post.go b/model/dto/post.go new file mode 100644 index 00000000..a6e5c188 --- /dev/null +++ b/model/dto/post.go @@ -0,0 +1,38 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type Post struct { + PostMinimal + Summary string `json:"summary"` + Thumbnail string `json:"thumbnail"` + Visits int64 `json:"visits"` + DisallowComment bool `json:"disallowComment"` + Password string `json:"password"` + Template string `json:"template"` + TopPriority int32 `json:"topPriority"` + Likes int64 `json:"likes"` + WordCount int64 `json:"wordCount"` + Topped bool `json:"topped"` +} + +type PostMinimal struct { + ID int32 `json:"id"` + Title string `json:"title"` + Status consts.PostStatus `json:"status"` + Slug string `json:"slug"` + EditorType consts.EditorType `json:"editorType"` + CreateTime int64 `json:"createTime"` + EditTime int64 `json:"editTime"` + UpdateTime int64 `json:"updateTime"` + MetaKeywords string `json:"metaKeywords"` + MetaDescription string `json:"metaDescription"` + FullPath string `json:"fullPath"` +} + +type PostDetail struct { + Post + OriginalContent string `json:"originalContent"` + Content string `json:"content"` + CommentCount int64 `json:"commentCount"` +} diff --git a/model/dto/statistic.go b/model/dto/statistic.go new file mode 100644 index 00000000..667fbff5 --- /dev/null +++ b/model/dto/statistic.go @@ -0,0 +1,18 @@ +package dto + +type Statistic struct { + PostCount int64 `json:"postCount"` + CommentCount int64 `json:"commentCount"` + CategoryCount int64 `json:"categoryCount"` + TagCount int64 `json:"tagCount"` + JournalCount int64 `json:"journalCount"` + Birthday int64 `json:"birthday"` + EstablishDays int64 `json:"establishDays"` + LinkCount int64 `json:"linkCount"` + VisitCount int64 `json:"visitCount"` + LikeCount int64 `json:"likeCount"` +} +type StatisticWithUser struct { + Statistic + User User `json:"user"` +} diff --git a/model/dto/tag.go b/model/dto/tag.go new file mode 100644 index 00000000..236f17a6 --- /dev/null +++ b/model/dto/tag.go @@ -0,0 +1,16 @@ +package dto + +type Tag struct { + ID int32 `json:"id"` + Name string `json:"name"` + Slug string `json:"slug"` + Thumbnail string `json:"thumbnail"` + CreateTime int64 `json:"createTime"` + FullPath string `json:"fullPath"` + Color string `json:"color"` +} + +type TagWithPostCount struct { + *Tag + PostCount int64 `json:"postCount"` +} diff --git a/model/dto/theme.go b/model/dto/theme.go new file mode 100644 index 00000000..7329cb44 --- /dev/null +++ b/model/dto/theme.go @@ -0,0 +1,61 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type ThemeProperty struct { + ID string `json:"id"` + Name string `json:"name"` + Website string `json:"website"` + Branch string `json:"branch"` + Repo string `json:"repo"` + UpdateStrategy consts.ThemeUpdateStrategy `json:"updateStrategy"` + Description string `json:"description"` + Logo string `json:"logo"` + Version string `json:"version"` + Require string `json:"require"` + Author ThemeAuthor `json:"author"` + ThemePath string `json:"themePath"` + FolderName string `json:"folderName"` + HasOptions bool `json:"hasOptions"` + Activated bool `json:"activated"` + ScreenShots string `json:"screenshots"` + PostMetaField []string `json:"postMetaField"` + SheetMetaField []string `json:"sheetMetaField"` +} + +type ThemeAuthor struct { + Name string `json:"name"` + Website string `json:"website"` + Avatar string `json:"avatar"` +} + +type ThemeFile struct { + Name string `json:"name"` + Path string `json:"path"` + IsFile bool `json:"isFile"` + Editable bool `json:"editable"` + Node []*ThemeFile `json:"node"` +} + +type ThemeConfigGroup struct { + Name string `json:"name"` + Label string `json:"label"` + Items []*ThemeConfigItem `json:"items" yaml:"-"` + ItemMap map[string]*ThemeConfigItem `json:"-" yaml:"items"` +} + +type ThemeConfigItem struct { + Name string `json:"name"` + Label string `json:"label"` + InputType consts.ThemeConfigInputType `json:"type" yaml:"type"` + DataType consts.ThemeConfigDataType `json:"dataType" yaml:"data-type"` + DefaultValue interface{} `json:"defaultValue" yaml:"default"` + PlaceHolder string `json:"placeHolder"` + Description string `json:"description"` + Options []*ThemeConfigOption `json:"options"` +} + +type ThemeConfigOption struct { + Label string `json:"label"` + Value interface{} `json:"value"` +} diff --git a/model/dto/user.go b/model/dto/user.go new file mode 100644 index 00000000..d8c418ac --- /dev/null +++ b/model/dto/user.go @@ -0,0 +1,15 @@ +package dto + +import "github.com/go-sonic/sonic/consts" + +type User struct { + ID int32 `json:"id"` + Username string `json:"username"` + Nickname string `json:"nickname"` + Email string `json:"email"` + Avatar string `json:"avatar"` + Description string `json:"description"` + MFAType consts.MFAType `json:"mfaType"` + CreateTime int64 `json:"createTime"` + UpdateTime int64 `json:"updateTime"` +} diff --git a/model/entity/attachment.gen.go b/model/entity/attachment.gen.go new file mode 100644 index 00000000..0760b039 --- /dev/null +++ b/model/entity/attachment.gen.go @@ -0,0 +1,35 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameAttachment = "attachment" + +// Attachment mapped from table +type Attachment struct { + ID int32 `gorm:"column:id;type:int;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null;index:attachment_create_time,priority:1" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + FileKey string `gorm:"column:file_key;type:varchar(2047);not null" json:"file_key"` + Height int32 `gorm:"column:height;type:int;not null" json:"height"` + MediaType string `gorm:"column:media_type;type:varchar(127);not null;index:attachment_media_type,priority:1" json:"media_type"` + Name string `gorm:"column:name;type:varchar(255);not null" json:"name"` + Path string `gorm:"column:path;type:varchar(1023);not null" json:"path"` + Size int64 `gorm:"column:size;type:bigint;not null" json:"size"` + Suffix string `gorm:"column:suffix;type:varchar(50);not null" json:"suffix"` + ThumbPath string `gorm:"column:thumb_path;type:varchar(1023);not null" json:"thumb_path"` + Type consts.AttachmentType `gorm:"column:type;type:int;not null" json:"type"` + Width int32 `gorm:"column:width;type:int;not null" json:"width"` +} + +// TableName Attachment's table name +func (*Attachment) TableName() string { + return TableNameAttachment +} diff --git a/model/entity/category.gen.go b/model/entity/category.gen.go new file mode 100644 index 00000000..fe798764 --- /dev/null +++ b/model/entity/category.gen.go @@ -0,0 +1,33 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameCategory = "category" + +// Category mapped from table +type Category struct { + ID int32 `gorm:"column:id;type:int;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Description string `gorm:"column:description;type:varchar(100);not null" json:"description"` + Name string `gorm:"column:name;type:varchar(255);not null;index:category_name,priority:1" json:"name"` + ParentID int32 `gorm:"column:parent_id;type:int;not null;index:category_parent_id,priority:1" json:"parent_id"` + Password string `gorm:"column:password;type:varchar(255);not null" json:"password"` + Slug string `gorm:"column:slug;type:varchar(255);not null;uniqueIndex:uniq_category_slug,priority:1" json:"slug"` + Thumbnail string `gorm:"column:thumbnail;type:varchar(1023);not null" json:"thumbnail"` + Priority int32 `gorm:"column:priority;type:int;not null" json:"priority"` + Type consts.CategoryType `gorm:"column:type;type:int;not null" json:"type"` +} + +// TableName Category's table name +func (*Category) TableName() string { + return TableNameCategory +} diff --git a/model/entity/comment.gen.go b/model/entity/comment.gen.go new file mode 100644 index 00000000..033f3972 --- /dev/null +++ b/model/entity/comment.gen.go @@ -0,0 +1,39 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameComment = "comment" + +// Comment mapped from table +type Comment struct { + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + Type consts.CommentType `gorm:"column:type;type:int;not null;index:comment_type_status,priority:1" json:"type"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + AllowNotification bool `gorm:"column:allow_notification;type:tinyint(1);not null;default:1" json:"allow_notification"` + Author string `gorm:"column:author;type:varchar(50);not null" json:"author"` + AuthorURL string `gorm:"column:author_url;type:varchar(511);not null" json:"author_url"` + Content string `gorm:"column:content;type:varchar(1023);not null" json:"content"` + Email string `gorm:"column:email;type:varchar(255);not null" json:"email"` + GravatarMd5 string `gorm:"column:gravatar_md5;type:varchar(127);not null" json:"gravatar_md5"` + IPAddress string `gorm:"column:ip_address;type:varchar(127);not null" json:"ip_address"` + IsAdmin bool `gorm:"column:is_admin;type:tinyint(1);not null" json:"is_admin"` + ParentID int64 `gorm:"column:parent_id;type:bigint;not null;index:comment_parent_id,priority:1" json:"parent_id"` + PostID int32 `gorm:"column:post_id;type:int;not null;index:comment_post_id,priority:1" json:"post_id"` + Status consts.CommentStatus `gorm:"column:status;type:int;not null;index:comment_type_status,priority:2;default:1" json:"status"` + TopPriority int32 `gorm:"column:top_priority;type:int;not null" json:"top_priority"` + UserAgent string `gorm:"column:user_agent;type:varchar(511);not null" json:"user_agent"` +} + +// TableName Comment's table name +func (*Comment) TableName() string { + return TableNameComment +} diff --git a/model/entity/comment_black.gen.go b/model/entity/comment_black.gen.go new file mode 100644 index 00000000..51caa40d --- /dev/null +++ b/model/entity/comment_black.gen.go @@ -0,0 +1,25 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" +) + +const TableNameCommentBlack = "comment_black" + +// CommentBlack mapped from table +type CommentBlack struct { + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + BanTime time.Time `gorm:"column:ban_time;type:datetime;not null" json:"ban_time"` + IPAddress string `gorm:"column:ip_address;type:varchar(127);not null" json:"ip_address"` +} + +// TableName CommentBlack's table name +func (*CommentBlack) TableName() string { + return TableNameCommentBlack +} diff --git a/model/entity/flyway_schema_history.gen.go b/model/entity/flyway_schema_history.gen.go new file mode 100644 index 00000000..23b36312 --- /dev/null +++ b/model/entity/flyway_schema_history.gen.go @@ -0,0 +1,30 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" +) + +const TableNameFlywaySchemaHistory = "flyway_schema_history" + +// FlywaySchemaHistory mapped from table +type FlywaySchemaHistory struct { + InstalledRank int32 `gorm:"column:installed_rank;type:int;primaryKey" json:"installed_rank"` + Version *string `gorm:"column:version;type:varchar(50)" json:"version"` + Description string `gorm:"column:description;type:varchar(200);not null" json:"description"` + Type string `gorm:"column:type;type:varchar(20);not null" json:"type"` + Script string `gorm:"column:script;type:varchar(1000);not null" json:"script"` + Checksum *int32 `gorm:"column:checksum;type:int" json:"checksum"` + InstalledBy string `gorm:"column:installed_by;type:varchar(100);not null" json:"installed_by"` + InstalledOn time.Time `gorm:"column:installed_on;type:timestamp;not null;default:CURRENT_TIMESTAMP" json:"installed_on"` + ExecutionTime int32 `gorm:"column:execution_time;type:int;not null" json:"execution_time"` + Success bool `gorm:"column:success;type:tinyint(1);not null;index:flyway_schema_history_s_idx,priority:1" json:"success"` +} + +// TableName FlywaySchemaHistory's table name +func (*FlywaySchemaHistory) TableName() string { + return TableNameFlywaySchemaHistory +} diff --git a/model/entity/hooks.go b/model/entity/hooks.go new file mode 100644 index 00000000..530666bf --- /dev/null +++ b/model/entity/hooks.go @@ -0,0 +1,214 @@ +package entity + +import ( + "time" + + "gorm.io/gorm" +) + +// -----------------------Attachment---------------- + +func (m *Attachment) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Attachment) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ---------------------- Category ---------------- + +func (m *Category) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Category) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ------------------ Comment ----------- + +func (m *Comment) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Comment) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ---------------------- CommentBlack ------------------- + +func (m *CommentBlack) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *CommentBlack) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ---------------------- Journal ----------- + +func (m *Journal) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Journal) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// --------------------- Link ------------------------- + +func (m *Link) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Link) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ------------------- Log --------------------------- + +func (m *Log) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Log) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// -------------------- Menu ----------------- + +func (m *Menu) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Menu) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// --------------------------- Option ----------------------- + +func (m *Option) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Option) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// -------------------------- Photo --------------------- + +func (m *Photo) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Photo) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ----------------------- Post ------------------------- + +func (m *Post) BeforeCreate(tx *gorm.DB) (err error) { + if m.CreateTime == (time.Time{}) { + m.CreateTime = time.Now() + } + m.CreateTime = time.Now() + return nil +} + +func (m *Post) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ------------------------- PostCategory ---------------- + +func (m *PostCategory) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *PostCategory) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// -------------------- PostTag ---------------------- + +func (m *PostTag) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *PostTag) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ------------------------- Tag ------------------------ + +func (m *Tag) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Tag) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ------------------------- ThemeSetting -------------------------- + +func (m *ThemeSetting) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *ThemeSetting) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ----------------------- User --------------------- + +func (m *User) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *User) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} + +// ----------------------- Meta --------------------- + +func (m *Meta) BeforeCreate(tx *gorm.DB) (err error) { + m.CreateTime = time.Now() + return nil +} + +func (m *Meta) BeforeUpdate(tx *gorm.DB) (err error) { + tx.Statement.SetColumn("update_time", time.Now()) + return nil +} diff --git a/model/entity/journal.gen.go b/model/entity/journal.gen.go new file mode 100644 index 00000000..b8d489fb --- /dev/null +++ b/model/entity/journal.gen.go @@ -0,0 +1,29 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameJournal = "journal" + +// Journal mapped from table +type Journal struct { + ID int32 `gorm:"column:id;type:int;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Content string `gorm:"column:content;type:text;not null" json:"content"` + Likes int64 `gorm:"column:likes;type:bigint;not null" json:"likes"` + SourceContent string `gorm:"column:source_content;type:longtext;not null" json:"source_content"` + Type consts.JournalType `gorm:"column:type;type:int;not null" json:"type"` +} + +// TableName Journal's table name +func (*Journal) TableName() string { + return TableNameJournal +} diff --git a/model/entity/link.gen.go b/model/entity/link.gen.go new file mode 100644 index 00000000..154f0d92 --- /dev/null +++ b/model/entity/link.gen.go @@ -0,0 +1,29 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" +) + +const TableNameLink = "link" + +// Link mapped from table +type Link struct { + ID int32 `gorm:"column:id;type:int;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Description string `gorm:"column:description;type:varchar(255);not null" json:"description"` + Logo string `gorm:"column:logo;type:varchar(1023);not null" json:"logo"` + Name string `gorm:"column:name;type:varchar(255);not null;index:link_name,priority:1" json:"name"` + Priority int32 `gorm:"column:priority;type:int;not null" json:"priority"` + Team string `gorm:"column:team;type:varchar(255);not null" json:"team"` + URL string `gorm:"column:url;type:varchar(1023);not null" json:"url"` +} + +// TableName Link's table name +func (*Link) TableName() string { + return TableNameLink +} diff --git a/model/entity/log.gen.go b/model/entity/log.gen.go new file mode 100644 index 00000000..469f110a --- /dev/null +++ b/model/entity/log.gen.go @@ -0,0 +1,29 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameLog = "log" + +// Log mapped from table +type Log struct { + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null;index:log_create_time,priority:1" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Content string `gorm:"column:content;type:varchar(1023);not null" json:"content"` + IPAddress string `gorm:"column:ip_address;type:varchar(127);not null" json:"ip_address"` + LogKey string `gorm:"column:log_key;type:varchar(1023);not null" json:"log_key"` + Type consts.LogType `gorm:"column:type;type:int;not null" json:"type"` +} + +// TableName Log's table name +func (*Log) TableName() string { + return TableNameLog +} diff --git a/model/entity/menu.gen.go b/model/entity/menu.gen.go new file mode 100644 index 00000000..38884f77 --- /dev/null +++ b/model/entity/menu.gen.go @@ -0,0 +1,30 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" +) + +const TableNameMenu = "menu" + +// Menu mapped from table +type Menu struct { + ID int32 `gorm:"column:id;type:int;primaryKey;autoIncrement:true" json:"id"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + Icon string `gorm:"column:icon;type:varchar(50);not null" json:"icon"` + Name string `gorm:"column:name;type:varchar(50);not null;index:menu_name,priority:1" json:"name"` + ParentID int32 `gorm:"column:parent_id;type:int;not null;index:menu_parent_id,priority:1" json:"parent_id"` + Priority int32 `gorm:"column:priority;type:int;not null" json:"priority"` + Target string `gorm:"column:target;type:varchar(20);not null;default:_self" json:"target"` + Team string `gorm:"column:team;type:varchar(255);not null" json:"team"` + URL string `gorm:"column:url;type:varchar(1023);not null" json:"url"` +} + +// TableName Menu's table name +func (*Menu) TableName() string { + return TableNameMenu +} diff --git a/model/entity/meta.gen.go b/model/entity/meta.gen.go new file mode 100644 index 00000000..9480351a --- /dev/null +++ b/model/entity/meta.gen.go @@ -0,0 +1,29 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameMeta = "meta" + +// Meta mapped from table +type Meta struct { + ID int64 `gorm:"column:id;type:bigint;primaryKey;autoIncrement:true" json:"id"` + Type consts.MetaType `gorm:"column:type;type:int;not null" json:"type"` + CreateTime time.Time `gorm:"column:create_time;type:datetime;not null" json:"create_time"` + UpdateTime *time.Time `gorm:"column:update_time;type:datetime" json:"update_time"` + MetaKey string `gorm:"column:meta_key;type:varchar(255);not null" json:"meta_key"` + PostID int32 `gorm:"column:post_id;type:int;not null" json:"post_id"` + MetaValue string `gorm:"column:meta_value;type:varchar(1023);not null" json:"meta_value"` +} + +// TableName Meta's table name +func (*Meta) TableName() string { + return TableNameMeta +} diff --git a/model/entity/option.gen.go b/model/entity/option.gen.go new file mode 100644 index 00000000..c2113e31 --- /dev/null +++ b/model/entity/option.gen.go @@ -0,0 +1,28 @@ +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. +// Code generated by gorm.io/gen. DO NOT EDIT. + +package entity + +import ( + "time" + + "github.com/go-sonic/sonic/consts" +) + +const TableNameOption = "option" + +// Option mapped from table