chore: initial commit
This commit is contained in:
52
.air.toml
Normal file
52
.air.toml
Normal file
@@ -0,0 +1,52 @@
|
||||
root = "."
|
||||
testdata_dir = "testdata"
|
||||
tmp_dir = "tmp"
|
||||
|
||||
[build]
|
||||
args_bin = []
|
||||
bin = "./tmp/main"
|
||||
cmd = "go build -o ./tmp/main ./cmd"
|
||||
delay = 1000
|
||||
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
|
||||
exclude_file = []
|
||||
exclude_regex = ["_test.go"]
|
||||
exclude_unchanged = false
|
||||
follow_symlink = false
|
||||
full_bin = ""
|
||||
include_dir = []
|
||||
include_ext = ["go", "tpl", "tmpl", "html"]
|
||||
include_file = []
|
||||
kill_delay = "0s"
|
||||
log = "build-errors.log"
|
||||
poll = false
|
||||
poll_interval = 0
|
||||
post_cmd = []
|
||||
pre_cmd = []
|
||||
rerun = false
|
||||
rerun_delay = 500
|
||||
send_interrupt = false
|
||||
stop_on_error = true
|
||||
|
||||
[color]
|
||||
app = ""
|
||||
build = "yellow"
|
||||
main = "magenta"
|
||||
runner = "green"
|
||||
watcher = "cyan"
|
||||
|
||||
[log]
|
||||
main_only = false
|
||||
silent = false
|
||||
time = false
|
||||
|
||||
[misc]
|
||||
clean_on_exit = false
|
||||
|
||||
[proxy]
|
||||
app_port = 0
|
||||
enabled = false
|
||||
proxy_port = 0
|
||||
|
||||
[screen]
|
||||
clear_on_rebuild = false
|
||||
keep_scroll = true
|
||||
31
.gitignore
vendored
Normal file
31
.gitignore
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
### Go template
|
||||
# If you prefer the allow list template instead of the deny list, see community template:
|
||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||
#
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
go.work.sum
|
||||
|
||||
# env file
|
||||
.env
|
||||
|
||||
tmp/
|
||||
.idea/
|
||||
uploads/
|
||||
logs/
|
||||
66
cmd/main.go
Normal file
66
cmd/main.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"server/internal/config"
|
||||
"server/internal/db"
|
||||
"server/internal/handler"
|
||||
"server/internal/middleware"
|
||||
"server/internal/pkg/cache"
|
||||
"server/internal/pkg/logger"
|
||||
"server/internal/router"
|
||||
"server/internal/service"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
func NewHttpServer(lc fx.Lifecycle, r *chi.Mux, cfg *config.Config) *http.Server {
|
||||
srv := &http.Server{
|
||||
Addr: fmt.Sprintf("%s:%s", cfg.Server.Host, cfg.Server.Port),
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
go func() {
|
||||
err := srv.ListenAndServe()
|
||||
if err != nil {
|
||||
slog.Error("server error", "error", err)
|
||||
}
|
||||
}()
|
||||
slog.Info(fmt.Sprintf("starting HTTP server http://%s", srv.Addr))
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
return srv.Shutdown(ctx)
|
||||
},
|
||||
})
|
||||
|
||||
return srv
|
||||
}
|
||||
|
||||
func main() {
|
||||
app := fx.New(
|
||||
fx.Provide(
|
||||
config.NewConfig,
|
||||
logger.New,
|
||||
cache.NewCaches,
|
||||
middleware.NewJWTMiddleware,
|
||||
middleware.NewAuthMiddleware,
|
||||
middleware.NewLoggerMiddleware,
|
||||
router.NewRouter,
|
||||
db.NewDB,
|
||||
db.NewQueries,
|
||||
),
|
||||
handler.Module,
|
||||
service.Module,
|
||||
fx.Invoke(func(*slog.Logger) {}),
|
||||
fx.Invoke(NewHttpServer),
|
||||
)
|
||||
|
||||
app.Run()
|
||||
}
|
||||
33
example.yaml
Normal file
33
example.yaml
Normal file
@@ -0,0 +1,33 @@
|
||||
server:
|
||||
port: 8080
|
||||
host: 127.0.0.1
|
||||
|
||||
database:
|
||||
host: db_host
|
||||
port: db_port
|
||||
user: postgres
|
||||
password: db_password
|
||||
dbname: db_name
|
||||
sslMode: disable
|
||||
|
||||
jwt:
|
||||
secret:
|
||||
signing_method: HS256
|
||||
expire: 30m
|
||||
refresh_expire: 720h
|
||||
issuer:
|
||||
audience:
|
||||
token_header: Authorization
|
||||
token_prefix: "Bearer "
|
||||
|
||||
file:
|
||||
base_url: http://127.0.0.1:8080/static/ #静态文件目录 用于本地访问开放静态资源
|
||||
|
||||
log:
|
||||
level: debug # debug / info / warn / error
|
||||
filename: logs/app.log
|
||||
max_size: 100 # 单个文件最大 MB
|
||||
max_backups: 30 # 最多保留旧文件个数
|
||||
max_age: 30 # 最多保留天数
|
||||
compress: true # 是否压缩旧日志
|
||||
console: true # 是否同时输出到控制台
|
||||
42
go.mod
Normal file
42
go.mod
Normal file
@@ -0,0 +1,42 @@
|
||||
module server
|
||||
|
||||
go 1.24.2
|
||||
|
||||
require (
|
||||
github.com/go-chi/chi/v5 v5.2.3
|
||||
github.com/go-playground/locales v0.14.1
|
||||
github.com/go-playground/universal-translator v0.18.1
|
||||
github.com/go-playground/validator/v10 v10.30.1
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||
github.com/jackc/pgx/v5 v5.7.6
|
||||
github.com/knadh/koanf v1.5.0
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0
|
||||
github.com/maypok86/otter/v2 v2.3.0
|
||||
go.uber.org/fx v1.24.0
|
||||
golang.org/x/crypto v0.46.0
|
||||
golang.org/x/sync v0.19.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/stretchr/testify v1.11.1 // indirect
|
||||
go.uber.org/dig v1.19.0 // indirect
|
||||
go.uber.org/multierr v1.10.0 // indirect
|
||||
go.uber.org/zap v1.26.0 // indirect
|
||||
golang.org/x/sys v0.39.0 // indirect
|
||||
golang.org/x/text v0.32.0 // indirect
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
443
go.sum
Normal file
443
go.sum
Normal file
@@ -0,0 +1,443 @@
|
||||
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=
|
||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||
github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
|
||||
github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
|
||||
github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/aws/aws-sdk-go-v2 v1.9.2/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4=
|
||||
github.com/aws/aws-sdk-go-v2/config v1.8.3/go.mod h1:4AEiLtAb8kLs7vgw2ZV3p2VZ1+hBavOc84hqxVNpCyw=
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.4.3/go.mod h1:FNNC6nQZQUuyhq5aE5c7ata8o9e4ECGmS4lAXC7o1mQ=
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.6.0/go.mod h1:gqlclDEZp4aqJOancXK6TN24aKhT0W0Ae9MHk3wzTMM=
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.2.4/go.mod h1:ZcBrrI3zBKlhGFNYWvju0I3TR93I7YIgAfy82Fh4lcQ=
|
||||
github.com/aws/aws-sdk-go-v2/service/appconfig v1.4.2/go.mod h1:FZ3HkCe+b10uFZZkFdvf98LHW21k49W8o8J366lqVKY=
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.3.2/go.mod h1:72HRZDLMtmVQiLG2tLfQcaWLCssELvGl+Zf2WVxMmR8=
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.4.2/go.mod h1:NBvT9R1MEF+Ud6ApJKM0G+IkPchKS7p7c2YPKwHmBOk=
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.7.2/go.mod h1:8EzeIqfWt2wWT4rJVu3f21TfrhJ8AEMzVybRNSb/b4g=
|
||||
github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E=
|
||||
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
|
||||
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
|
||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
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-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||
github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
|
||||
github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||
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/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.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||
github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU=
|
||||
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
|
||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw=
|
||||
github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
|
||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
|
||||
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
|
||||
github.com/go-ldap/ldap v3.0.2+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc=
|
||||
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
|
||||
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
|
||||
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
|
||||
github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
|
||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||
github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
|
||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
|
||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
|
||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
|
||||
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.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/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
|
||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/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.5.0/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.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||
github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
|
||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||
github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ=
|
||||
github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms=
|
||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
|
||||
github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI=
|
||||
github.com/hashicorp/go-hclog v0.8.0/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ=
|
||||
github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ=
|
||||
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
|
||||
github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
|
||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||
github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA=
|
||||
github.com/hashicorp/go-plugin v1.0.1/go.mod h1:++UyYGoz3o5w9ZzAdZxtQKrWWP+iqPBn3cQptSMzBuY=
|
||||
github.com/hashicorp/go-retryablehttp v0.5.4/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
|
||||
github.com/hashicorp/go-rootcerts v1.0.1/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
|
||||
github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
|
||||
github.com/hashicorp/go-sockaddr v1.0.2/go.mod h1:rB4wwRAUzs07qva3c5SdrY/NEtAUjGlgmH/UkBUC97A=
|
||||
github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
|
||||
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
|
||||
github.com/hashicorp/go-version v1.1.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||
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/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=
|
||||
github.com/hashicorp/mdns v1.0.4/go.mod h1:mtBihi+LeNXGtG8L9dX59gAEa12BDtBQSp4v/YAJqrc=
|
||||
github.com/hashicorp/memberlist v0.3.0/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE=
|
||||
github.com/hashicorp/serf v0.9.6/go.mod h1:TXZNMjZQijwlDvp+r0b63xZ45H7JmCmgg4gpTwn9UV4=
|
||||
github.com/hashicorp/vault/api v1.0.4/go.mod h1:gDcqh3WGcR1cpF5AJz/B1UFheUEneMoIospckxBxk6Q=
|
||||
github.com/hashicorp/vault/sdk v0.1.13/go.mod h1:B+hVj7TpuQY1Y/GPbCpffmgd+tSEwvhkWnjtSYCaS2M=
|
||||
github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM=
|
||||
github.com/hjson/hjson-go/v4 v4.0.0 h1:wlm6IYYqHjOdXH1gHev4VoXCaW20HdQAGCxdOEEg2cs=
|
||||
github.com/hjson/hjson-go/v4 v4.0.0/go.mod h1:KaYt3bTw3zhBjYqnXkYywcYctk0A2nxeEFTse3rH13E=
|
||||
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/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.6 h1:rWQc5FwZSPX58r1OQmkuaNicxdmExaEz5A2DO2hUuTk=
|
||||
github.com/jackc/pgx/v5 v5.7.6/go.mod h1:aruU7o91Tc2q2cFp5h4uP3f6ztExVpyVv88Xl/8Vl8M=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
|
||||
github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
|
||||
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
|
||||
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
|
||||
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
|
||||
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
|
||||
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
|
||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
|
||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||
github.com/knadh/koanf v1.5.0 h1:q2TSd/3Pyc/5yP9ldIrSdIz26MCcyNQzW0pEAugLPNs=
|
||||
github.com/knadh/koanf v1.5.0/go.mod h1:Hgyjp4y8v44hpZtPzs7JZfRAW5AhN7KfZcwv1RYggDs=
|
||||
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.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
|
||||
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
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.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0 h1:P64+dmq21hhWdtvZfEAofnvJULaRR1Yib0+PnU669bE=
|
||||
github.com/matoous/go-nanoid/v2 v2.1.0/go.mod h1:KlbGNQ+FhrUNIHUxZdL63t7tl4LaPkZNpUULS8H4uVM=
|
||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||
github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
|
||||
github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE=
|
||||
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
|
||||
github.com/maypok86/otter/v2 v2.3.0 h1:8H8AVVFUSzJwIegKwv1uF5aGitTY+AIrtktg7OcLs8w=
|
||||
github.com/maypok86/otter/v2 v2.3.0/go.mod h1:XgIdlpmL6jYz882/CAx1E4C1ukfgDKSaw4mWq59+7l8=
|
||||
github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso=
|
||||
github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI=
|
||||
github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
|
||||
github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
|
||||
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
|
||||
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
|
||||
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
|
||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||
github.com/mitchellh/go-testing-interface v0.0.0-20171004221916-a61a99592b77/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
|
||||
github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo=
|
||||
github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||
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/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
|
||||
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
|
||||
github.com/mitchellh/reflectwalk v1.0.2/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/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
|
||||
github.com/npillmayer/nestext v0.1.3/go.mod h1:h2lrijH8jpicr25dFY+oAJLyzlya6jhnuG+zWp9L0Uk=
|
||||
github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA=
|
||||
github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
|
||||
github.com/pelletier/go-toml v1.7.0 h1:7utD74fnzVc/cpcyy8sjrlFr5vYpypUixARcHIMIGuI=
|
||||
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
|
||||
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
|
||||
github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA=
|
||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||
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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI=
|
||||
github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s=
|
||||
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
|
||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
|
||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc=
|
||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||
github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA=
|
||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
|
||||
github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc=
|
||||
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
|
||||
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
|
||||
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
|
||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
||||
github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
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/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.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=
|
||||
go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
|
||||
go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
|
||||
go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY=
|
||||
go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
|
||||
go.uber.org/dig v1.19.0 h1:BACLhebsYdpQ7IROQ1AGPjrXcP5dF80U3gKoFzbaq/4=
|
||||
go.uber.org/dig v1.19.0/go.mod h1:Us0rSJiThwCv2GteUN0Q7OKvU7n5J4dxZ9JKUXozFdE=
|
||||
go.uber.org/fx v1.24.0 h1:wE8mruvpg2kiiL1Vqd0CC+tr0/24XIB10Iwp2lLWzkg=
|
||||
go.uber.org/fx v1.24.0/go.mod h1:AmDeGyS+ZARGKM4tlH4FY2Jr63VjbEDJHtqXTGP5hbo=
|
||||
go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
|
||||
go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
|
||||
go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
|
||||
go.uber.org/multierr v1.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
|
||||
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
|
||||
go.uber.org/zap v1.26.0 h1:sI7k6L95XOKS281NhVKOFCUNIvv9e0w4BF8N3u+tCRo=
|
||||
go.uber.org/zap v1.26.0/go.mod h1:dtElttAiwGvoJ/vj4IwHBS/gXsEu/pZ50mUIRWuG0so=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
|
||||
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
|
||||
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
|
||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||
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-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
|
||||
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/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.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
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-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20190923162816-aa69164e4478/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-20200625001655-4c5254603344/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-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
|
||||
golang.org/x/net v0.0.0-20210410081132-afb366fc7cd1/go.mod h1:9tjilg8BloeKEkVJvy7fQ90B1CfIiPueXVOjqfkSzI8=
|
||||
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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||
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-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-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
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-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190129075346-302c3dd5f1cc/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-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-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/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-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
|
||||
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
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.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
|
||||
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
|
||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/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-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
|
||||
golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/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-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
|
||||
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
|
||||
golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
|
||||
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/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/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
|
||||
google.golang.org/genproto v0.0.0-20190404172233-64821d5d2107/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
|
||||
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
|
||||
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
|
||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||
google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
|
||||
google.golang.org/grpc v1.14.0/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||
google.golang.org/grpc v1.22.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
|
||||
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.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
|
||||
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
|
||||
google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
|
||||
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.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.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||
gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/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/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
|
||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
|
||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.5/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/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=
|
||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||
sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc=
|
||||
106
internal/config/config.go
Normal file
106
internal/config/config.go
Normal file
@@ -0,0 +1,106 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/knadh/koanf"
|
||||
"github.com/knadh/koanf/parsers/yaml"
|
||||
"github.com/knadh/koanf/providers/file"
|
||||
)
|
||||
|
||||
const (
|
||||
Development = "development"
|
||||
Production = "production"
|
||||
)
|
||||
|
||||
var K = koanf.New(".")
|
||||
|
||||
type Server struct {
|
||||
Port string `koanf:"port"`
|
||||
Host string `koanf:"host"`
|
||||
}
|
||||
|
||||
type Database struct {
|
||||
Host string `koanf:"host"`
|
||||
Port int `koanf:"port"`
|
||||
User string `koanf:"user"`
|
||||
Password string `koanf:"password"`
|
||||
DBName string `koanf:"dbname"`
|
||||
SSLMode string `koanf:"sslMode"`
|
||||
}
|
||||
|
||||
type JWTConfig struct {
|
||||
Secret string `koanf:"secret"`
|
||||
SigningMethod string `koanf:"signing_method"`
|
||||
Expire time.Duration `koanf:"expire"`
|
||||
RefreshExpire time.Duration `koanf:"refresh_expire"`
|
||||
Issuer string `koanf:"issuer"`
|
||||
Audience string `koanf:"audience"`
|
||||
TokenHeader string `koanf:"token_header"`
|
||||
TokenPrefix string `koanf:"token_prefix"`
|
||||
}
|
||||
|
||||
type LogConfig struct {
|
||||
Level string `koanf:"level"`
|
||||
Filename string `koanf:"filename"`
|
||||
MaxSize int `koanf:"max_size"`
|
||||
MaxBackups int `koanf:"max_backups"`
|
||||
MaxAge int `koanf:"max_age"`
|
||||
Compress bool `koanf:"compress"`
|
||||
Console bool `koanf:"console"`
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
Server Server `koanf:"server"`
|
||||
Database Database `koanf:"database"`
|
||||
JWTConfig JWTConfig `koanf:"jwt"`
|
||||
Log LogConfig `koanf:"log"`
|
||||
}
|
||||
|
||||
// GetEnv 获取环境变量,确保环境变量始终有效
|
||||
func GetEnv() string {
|
||||
env := os.Getenv("APP_ENV")
|
||||
if env == "" {
|
||||
env = Development
|
||||
}
|
||||
|
||||
switch env {
|
||||
case Development, Production:
|
||||
return env
|
||||
default:
|
||||
return Development
|
||||
}
|
||||
}
|
||||
|
||||
func IsDev() bool {
|
||||
return GetEnv() == Development
|
||||
}
|
||||
|
||||
func NewConfig() (*Config, error) {
|
||||
env := GetEnv()
|
||||
filepath := fmt.Sprintf("%s.yaml", env)
|
||||
|
||||
if err := K.Load(file.Provider(filepath), yaml.Parser()); err != nil {
|
||||
slog.Error("error loading config", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var cfg Config
|
||||
if err := K.Unmarshal("", &cfg); err != nil {
|
||||
slog.Error("error unmarshaling config", "error", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
func GetString(key string) string {
|
||||
return K.String(key)
|
||||
}
|
||||
|
||||
func GetInt(key string) int {
|
||||
return K.Int(key)
|
||||
}
|
||||
48
internal/db/db.go
Normal file
48
internal/db/db.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"server/internal/config"
|
||||
"server/internal/db/sqlc"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
func NewDB(lc fx.Lifecycle, cfg *config.Config) (*pgxpool.Pool, error) {
|
||||
ctx := context.Background()
|
||||
dbConfig := cfg.Database
|
||||
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Password, dbConfig.DBName, dbConfig.SSLMode)
|
||||
|
||||
// 创建连接池
|
||||
pool, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 注册fx生命周期钩子
|
||||
lc.Append(fx.Hook{
|
||||
OnStart: func(ctx context.Context) error {
|
||||
return nil
|
||||
},
|
||||
OnStop: func(ctx context.Context) error {
|
||||
pool.Close()
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
// 测试连接
|
||||
if err = pool.Ping(ctx); err != nil {
|
||||
pool.Close()
|
||||
return nil, fmt.Errorf("failed to ping database: %w", err)
|
||||
}
|
||||
|
||||
return pool, nil
|
||||
}
|
||||
|
||||
func NewQueries(pool *pgxpool.Pool) *db.Queries {
|
||||
return db.New(pool)
|
||||
}
|
||||
32
internal/db/migrations/000001_init.down.sql
Normal file
32
internal/db/migrations/000001_init.down.sql
Normal file
@@ -0,0 +1,32 @@
|
||||
-- migrate/down.sql
|
||||
-- 此文件用于撤销 up.sql 文件的所有操作
|
||||
-- 注意:执行此脚本将会永久删除相关表和数据!
|
||||
|
||||
-- 移除表上的触发器
|
||||
-- 必须在删除表和函数之前移除依赖它们的触发器
|
||||
DROP TRIGGER IF EXISTS update_sys_users_updated_at ON sys_users;
|
||||
DROP TRIGGER IF EXISTS update_sys_roles_updated_at ON sys_roles;
|
||||
DROP TRIGGER IF EXISTS update_sys_menus_updated_at ON sys_menus;
|
||||
DROP TRIGGER IF EXISTS update_sys_permissions_updated_at ON sys_permissions;
|
||||
DROP TRIGGER IF EXISTS update_sys_apis_updated_at ON sys_apis;
|
||||
DROP TRIGGER IF EXISTS update_files_updated_at ON files;
|
||||
|
||||
-- 删除索引
|
||||
DROP INDEX IF EXISTS idx_sys_users_account;
|
||||
|
||||
-- 移除表
|
||||
-- 按照与创建相反的顺序删除,先删除依赖其他表的表
|
||||
DROP TABLE IF EXISTS sys_api_permission;
|
||||
DROP TABLE IF EXISTS sys_apis;
|
||||
DROP TABLE IF EXISTS sys_menu_permission;
|
||||
DROP TABLE IF EXISTS sys_role_permission;
|
||||
DROP TABLE IF EXISTS sys_user_role;
|
||||
DROP TABLE IF EXISTS sys_permissions;
|
||||
DROP TABLE IF EXISTS sys_menus;
|
||||
DROP TABLE IF EXISTS sys_roles;
|
||||
DROP TABLE IF EXISTS sys_users;
|
||||
DROP TABLE IF EXISTS files;
|
||||
|
||||
-- 移除触发器函数
|
||||
-- 在所有使用此函数的触发器都被移除后,可以安全地删除此函数
|
||||
DROP FUNCTION IF EXISTS update_updated_at_column;
|
||||
221
internal/db/migrations/000001_init.up.sql
Normal file
221
internal/db/migrations/000001_init.up.sql
Normal file
@@ -0,0 +1,221 @@
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
-- function
|
||||
CREATE
|
||||
OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
NEW.updated_at
|
||||
:= NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
language 'plpgsql';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_users
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
account TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_id INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sys_users_account ON sys_users (account);
|
||||
|
||||
CREATE TRIGGER update_sys_users_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
|
||||
COMMENT ON COLUMN sys_users.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_users.account IS '用户账号';
|
||||
COMMENT ON COLUMN sys_users.username IS '用户名称';
|
||||
COMMENT ON COLUMN sys_users.password_hash IS 'hash密码';
|
||||
COMMENT ON COLUMN sys_users.avatar_id IS '头像文件id';
|
||||
COMMENT ON COLUMN sys_users.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_users.updated_at IS '更新时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_roles
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_roles_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_roles
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_roles.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_roles.name IS '角色名称';
|
||||
COMMENT ON COLUMN sys_roles.code IS '角色编码,唯一';
|
||||
COMMENT ON COLUMN sys_roles.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_roles.updated_at IS '更新时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_permissions
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
code TEXT UNIQUE,
|
||||
type SMALLINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CONSTRAINT unique_type_code UNIQUE (type, code)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_permissions_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_permissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_permissions.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_permissions.code IS '权限字符,唯一';
|
||||
COMMENT ON COLUMN sys_permissions.code IS '权限类型,0: 菜单 1: api';
|
||||
COMMENT ON COLUMN sys_permissions.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_permissions.updated_at IS '更新时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_menus
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
component TEXT,
|
||||
type SMALLINT NOT NULL,
|
||||
hidden BOOLEAN DEFAULT FALSE,
|
||||
sort INTEGER DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
parent_id INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CHECK (type IN (0, 1, 2)),
|
||||
CHECK (status IN (0, 1))
|
||||
);
|
||||
|
||||
|
||||
CREATE TRIGGER update_sys_menus_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_menus
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_menus.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_menus.name IS '菜单名称';
|
||||
COMMENT ON COLUMN sys_menus.path IS '菜单路径';
|
||||
COMMENT ON COLUMN sys_menus.component IS '组件路径';
|
||||
COMMENT ON COLUMN sys_menus.type IS '菜单类型 0: 目录 1: 菜单 2: button';
|
||||
COMMENT ON COLUMN sys_menus.sort IS '菜单排序';
|
||||
COMMENT ON COLUMN sys_menus.hidden IS '是否隐藏菜单';
|
||||
COMMENT ON COLUMN sys_menus.status IS '菜单状态 1: 启用 0: 禁用';
|
||||
COMMENT ON COLUMN sys_menus.parent_id IS '父级id';
|
||||
COMMENT ON COLUMN sys_menus.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_menus.updated_at IS '更新时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_apis
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
group_name TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
sort INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
UNIQUE (method, path)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_apis_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_apis
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_apis.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_apis.group_name IS '分组名称';
|
||||
COMMENT ON COLUMN sys_apis.name IS '接口名称';
|
||||
COMMENT ON COLUMN sys_apis.method IS 'api方法';
|
||||
COMMENT ON COLUMN sys_apis.path IS 'api路径';
|
||||
COMMENT ON COLUMN sys_apis.sort IS '排序';
|
||||
COMMENT ON COLUMN sys_apis.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_apis.updated_at IS '更新时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE files
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
file_name TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
folder_name TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_files_updated_at
|
||||
BEFORE UPDATE
|
||||
ON files
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN files.id IS '主键ID';
|
||||
COMMENT ON COLUMN files.file_name IS '文件名 xxx.jpg';
|
||||
COMMENT ON COLUMN files.file_path IS '文件路径 带path /xxx/xxx.jpg';
|
||||
COMMENT ON COLUMN files.original_name IS '原始文件名';
|
||||
COMMENT ON COLUMN files.folder_name IS '文件夹名称';
|
||||
COMMENT ON COLUMN files.mime_type IS 'mime_type';
|
||||
COMMENT ON COLUMN files.file_size IS '文件大小';
|
||||
COMMENT ON COLUMN files.created_at IS '上传时间';
|
||||
COMMENT ON COLUMN files.updated_at IS '修改时间';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_user_role
|
||||
(
|
||||
user_id INTEGER NOT NULL,
|
||||
role_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_user_role.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys_user_role.role_id IS '角色ID';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_role_permission
|
||||
(
|
||||
role_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (role_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_role_permission.role_id IS '角色id';
|
||||
COMMENT ON COLUMN sys_role_permission.permission_id IS '权限id';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_menu_permission
|
||||
(
|
||||
menu_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (menu_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_menu_permission.menu_id IS '菜单id';
|
||||
COMMENT ON COLUMN sys_menu_permission.permission_id IS '权限id';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
CREATE TABLE sys_api_permission
|
||||
(
|
||||
api_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (api_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_api_permission.api_id IS 'api ID';
|
||||
COMMENT ON COLUMN sys_api_permission.permission_id IS '权限id';
|
||||
--------------------------------------------------------------------------------------------------------------
|
||||
0
internal/db/migrations/000002_seed.down.sql
Normal file
0
internal/db/migrations/000002_seed.down.sql
Normal file
83
internal/db/migrations/000002_seed.up.sql
Normal file
83
internal/db/migrations/000002_seed.up.sql
Normal file
@@ -0,0 +1,83 @@
|
||||
-- 创建用户
|
||||
INSERT INTO sys_users (account, username, password_hash, avatar_id, created_at)
|
||||
VALUES ('admin', '管理员', '$2a$10$d/xYJVUFTmpSVUaV3uawZ.7hj6FanbooMJ9YjsFOmQKNopWc6oR9i', NULL,
|
||||
'2025-10-18 03:05:54.692714');
|
||||
|
||||
-- 批量插入菜单及关联权限
|
||||
WITH input(name, path, component, type, sort, status, code)
|
||||
AS (VALUES ('首页', '/', '/system/home/index', 1, 0, 1, 'home'),
|
||||
('用户管理', '/user', '/system/user/index', 1, 1, 1, 'user'),
|
||||
('角色管理', '/role', '/system/role/index', 1, 2, 1, 'role'),
|
||||
('菜单管理', '/menu', '/system/menu/index', 1, 3, 1, 'menu'),
|
||||
('接口管理', '/sys_api', '/system/api/index', 1, 4, 1, 'api'),
|
||||
('文件管理', '/file', '/system/file/index', 1, 5, 1, 'file')),
|
||||
-- 插入菜单
|
||||
inserted_menus AS (
|
||||
INSERT INTO sys_menus (name, path, component, type, sort, status)
|
||||
SELECT name, path, component, type, sort, status FROM input RETURNING id),
|
||||
menu_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn FROM inserted_menus),
|
||||
-- 插入权限
|
||||
inserted_permissions AS (
|
||||
INSERT INTO sys_permissions (type, code)
|
||||
SELECT 0, code FROM input RETURNING id),
|
||||
permission_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn FROM inserted_permissions)
|
||||
-- 关联权限
|
||||
INSERT
|
||||
INTO sys_menu_permission(menu_id, permission_id)
|
||||
SELECT m.id, p.id
|
||||
FROM menu_rows m
|
||||
JOIN permission_rows p ON m.rn = p.rn;
|
||||
|
||||
-- 批量插入接口及关联权限
|
||||
WITH inserted_apis AS (
|
||||
-- 插入菜单
|
||||
INSERT INTO sys_apis (group_name, name, method, path)
|
||||
VALUES ('用户管理', '用户列表', 'GET', '/admin/user'),
|
||||
('用户管理', '用户信息', 'GET', '/admin/user/info'),
|
||||
('用户管理', '查询用户', 'GET', '/admin/user/{id}'),
|
||||
('用户管理', '用户角色', 'GET', '/admin/user/{id}/roles'),
|
||||
('用户管理', '创建用户', 'POST', '/admin/user'),
|
||||
('用户管理', '修改用户', 'PATCH', '/admin/user/{id}'),
|
||||
('用户管理', '删除用户', 'DELETE', '/admin/user/{id}'),
|
||||
('用户管理', '分配用户角色', 'PUT', '/admin/user/{id}/roles'),
|
||||
('用户管理', '修改密码', 'PATCH', '/admin/user/{id}/password'),
|
||||
--
|
||||
('角色管理', '角色列表', 'GET', '/admin/role'),
|
||||
('角色管理', '创建角色', 'POST', '/admin/role'),
|
||||
('角色管理', '所有角色', 'GET', '/admin/role/all'),
|
||||
('角色管理', '单个角色', 'GET', '/admin/role/{id}'),
|
||||
('角色管理', '角色菜单', 'GET', '/admin/role/{id}/menus'),
|
||||
('角色管理', '角色接口', 'GET', '/admin/role/{id}/apis'),
|
||||
('角色管理', '修改角色', 'PATCH', '/admin/role/{id}'),
|
||||
('角色管理', '删除角色', 'DELETE', '/admin/role/{id}'),
|
||||
('角色管理', '分配角色菜单', 'PUT', '/admin/role/{id}/menus'),
|
||||
('角色管理', '分配角色接口', 'PUT', '/admin/role/{id}/apis'),
|
||||
--
|
||||
('接口管理', '接口列表', 'GET', '/admin/api'),
|
||||
('接口管理', '所有接口', 'GET', '/admin/api/all'),
|
||||
('接口管理', '接口分组', 'GET', '/admin/api/group_names'),
|
||||
('接口管理', '创建接口', 'POST', '/admin/api'),
|
||||
('接口管理', '修改接口', 'PATCH', '/admin/api/{id}'),
|
||||
('接口管理', '删除接口', 'DELETE', '/admin/api/{id}'),
|
||||
--
|
||||
('菜单管理', '菜单列表', 'GET', '/admin/menu'),
|
||||
('菜单管理', '创建菜单', 'POST', '/admin/menu'),
|
||||
('菜单管理', '所有菜单', 'GET', '/admin/menu/all'),
|
||||
('菜单管理', '修改菜单', 'PATCH', '/admin/menu/{id}'),
|
||||
('菜单管理', '删除菜单', 'DELETE', '/admin/menu/{id}'),
|
||||
--
|
||||
('文件管理', '文件列表', 'GET', '/admin/file'),
|
||||
('文件管理', '上传文件', 'POST', '/admin/file') RETURNING id),
|
||||
api_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn
|
||||
FROM inserted_apis),
|
||||
-- 创建权限
|
||||
inserted_permissions AS (
|
||||
INSERT INTO sys_permissions (type)
|
||||
SELECT 1 FROM api_rows RETURNING id),
|
||||
permission_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn
|
||||
FROM inserted_permissions)
|
||||
INSERT
|
||||
INTO sys_api_permission(api_id, permission_id)
|
||||
SELECT a.id, b.id
|
||||
FROM api_rows a
|
||||
INNER JOIN permission_rows b ON a.rn = b.rn;
|
||||
19
internal/db/migrations/000003_add_posts.down.sql
Normal file
19
internal/db/migrations/000003_add_posts.down.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
DELETE FROM sys_api_permission
|
||||
WHERE api_id IN (SELECT id FROM sys_apis WHERE group_name = '文章管理');
|
||||
|
||||
DELETE FROM sys_permissions
|
||||
WHERE id IN (
|
||||
SELECT permission_id FROM sys_api_permission
|
||||
WHERE api_id IN (SELECT id FROM sys_apis WHERE group_name = '文章管理')
|
||||
);
|
||||
|
||||
DELETE FROM sys_apis WHERE group_name = '文章管理';
|
||||
|
||||
DELETE FROM sys_menu_permission
|
||||
WHERE menu_id IN (SELECT id FROM sys_menus WHERE path = '/post');
|
||||
|
||||
DELETE FROM sys_permissions WHERE code = 'post' AND type = 1;
|
||||
|
||||
DELETE FROM sys_menus WHERE path = '/post';
|
||||
|
||||
DROP TABLE IF EXISTS posts;
|
||||
82
internal/db/migrations/000003_add_posts.up.sql
Normal file
82
internal/db/migrations/000003_add_posts.up.sql
Normal file
@@ -0,0 +1,82 @@
|
||||
CREATE TABLE posts
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
title TEXT NOT NULL,
|
||||
cover_id INTEGER,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
view INTEGER DEFAULT 0,
|
||||
sort INTEGER DEFAULT 0,
|
||||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CHECK (status IN (0, 1, 2))
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_posts_updated_at
|
||||
BEFORE UPDATE
|
||||
ON posts
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
|
||||
COMMENT ON COLUMN posts.id IS '主键ID';
|
||||
COMMENT ON COLUMN posts.title IS '文章标题';
|
||||
COMMENT ON COLUMN posts.cover_id IS '文章封面图片id';
|
||||
COMMENT ON COLUMN posts.slug IS 'URL友好地址';
|
||||
COMMENT ON COLUMN posts.content IS '文章正文';
|
||||
COMMENT ON COLUMN posts.summary IS '摘要';
|
||||
COMMENT ON COLUMN posts.status IS '状态 0:草稿 1:已发布 2:已下线';
|
||||
COMMENT ON COLUMN posts.view IS '阅读量';
|
||||
COMMENT ON COLUMN posts.sort IS '文章排序';
|
||||
COMMENT ON COLUMN posts.published_at IS '发布时间';
|
||||
COMMENT ON COLUMN posts.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN posts.updated_at IS '修改时间';
|
||||
|
||||
-- 批量插入菜单及关联权限
|
||||
WITH input(name, path, component, type, sort, status, code)
|
||||
AS (VALUES ('文章管理', '/post', '/blog/post/index', 1, 6, 1, 'post'),
|
||||
('文章编辑', '/post/edit', '/blog/post-edit/index', 1, 7, 1, 'post:edit')
|
||||
),
|
||||
-- 插入菜单
|
||||
inserted_menus AS (
|
||||
INSERT INTO sys_menus (name, path, component, type, sort, status)
|
||||
SELECT name, path, component, type, sort, status FROM input RETURNING id),
|
||||
menu_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn FROM inserted_menus),
|
||||
-- 插入权限
|
||||
inserted_permissions AS (
|
||||
INSERT INTO sys_permissions (type, code)
|
||||
SELECT 0, code FROM input RETURNING id),
|
||||
permission_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn FROM inserted_permissions)
|
||||
-- 关联权限
|
||||
INSERT
|
||||
INTO sys_menu_permission(menu_id, permission_id)
|
||||
SELECT m.id, p.id
|
||||
FROM menu_rows m
|
||||
JOIN permission_rows p ON m.rn = p.rn;
|
||||
|
||||
-- 批量插入接口及关联权限
|
||||
WITH inserted_apis AS (
|
||||
-- 插入菜单
|
||||
INSERT INTO sys_apis (group_name, name, method, path)
|
||||
VALUES ('文章管理', '文章列表', 'GET', '/admin/post'),
|
||||
('文章管理', '创建文章', 'POST', '/admin/post'),
|
||||
('文章管理', '修改文章', 'PATCH', '/admin/post/{id}'),
|
||||
('文章管理', '删除文章', 'DELETE', '/admin/post/{id}')
|
||||
RETURNING id),
|
||||
api_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn
|
||||
FROM inserted_apis),
|
||||
-- 创建权限
|
||||
inserted_permissions AS (
|
||||
INSERT INTO sys_permissions (type)
|
||||
SELECT 1 FROM api_rows RETURNING id),
|
||||
permission_rows AS (SELECT id, ROW_NUMBER() OVER () AS rn
|
||||
FROM inserted_permissions)
|
||||
INSERT
|
||||
INTO sys_api_permission(api_id, permission_id)
|
||||
SELECT a.id, b.id
|
||||
FROM api_rows a
|
||||
INNER JOIN permission_rows b ON a.rn = b.rn;
|
||||
2
internal/db/migrations/000004_add_categories.down.sql
Normal file
2
internal/db/migrations/000004_add_categories.down.sql
Normal file
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS categories;
|
||||
DROP TABLE IF EXISTS post_category;
|
||||
30
internal/db/migrations/000004_add_categories.up.sql
Normal file
30
internal/db/migrations/000004_add_categories.up.sql
Normal file
@@ -0,0 +1,30 @@
|
||||
-- 分类表
|
||||
CREATE TABLE categories
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_categories_updated_at
|
||||
BEFORE UPDATE
|
||||
ON categories
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN categories.id IS '分类ID';
|
||||
COMMENT ON COLUMN categories.name IS '分类名称';
|
||||
COMMENT ON COLUMN categories.code IS '分类编码';
|
||||
|
||||
-- 关联表
|
||||
CREATE TABLE post_category
|
||||
(
|
||||
post_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
PRIMARY KEY (post_id, category_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_category.post_id IS '文章ID';
|
||||
COMMENT ON COLUMN post_category.category_id IS '分类ID';
|
||||
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS post_stats;
|
||||
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE post_stats
|
||||
(
|
||||
post_id INTEGER PRIMARY KEY,
|
||||
view INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_stats.post_id IS '文章id';
|
||||
COMMENT ON COLUMN post_stats.view IS '文章阅读量';
|
||||
|
||||
-- 删除view字段
|
||||
ALTER TABLE posts
|
||||
DROP COLUMN IF EXISTS view;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sys_menus
|
||||
DROP COLUMN IF EXISTS icon;
|
||||
@@ -0,0 +1,2 @@
|
||||
ALTER TABLE sys_menus
|
||||
ADD COLUMN IF NOT EXISTS icon INTEGER;
|
||||
1
internal/db/migrations/000007_add_post_viewers.down.sql
Normal file
1
internal/db/migrations/000007_add_post_viewers.down.sql
Normal file
@@ -0,0 +1 @@
|
||||
DROP TABLE IF EXISTS post_viewers;
|
||||
12
internal/db/migrations/000007_add_post_viewers.up.sql
Normal file
12
internal/db/migrations/000007_add_post_viewers.up.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE post_viewers
|
||||
(
|
||||
post_id INTEGER NOT NULL,
|
||||
ip INET NOT NULL,
|
||||
last_viewed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
PRIMARY KEY(post_id, ip)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_viewers.post_id IS '文章id';
|
||||
COMMENT ON COLUMN post_viewers.ip IS '访问ip';
|
||||
COMMENT ON COLUMN post_viewers.last_viewed_at IS '最后访问时间';
|
||||
42
internal/db/query/category.sql
Normal file
42
internal/db/query/category.sql
Normal file
@@ -0,0 +1,42 @@
|
||||
-- name: CreateCategory :exec
|
||||
INSERT INTO categories(name, code)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: ListCategories :many
|
||||
SELECT *
|
||||
FROM categories
|
||||
ORDER BY id LIMIT $1
|
||||
OFFSET $2;
|
||||
|
||||
-- name: CountCategories :one
|
||||
SELECT COUNT(*)
|
||||
FROM categories;
|
||||
|
||||
-- name: ListAllCategories :many
|
||||
SELECT *
|
||||
FROM categories;
|
||||
|
||||
-- name: GetCategoryById :one
|
||||
SELECT *
|
||||
FROM categories
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: UpdateCategory :execrows
|
||||
UPDATE categories
|
||||
SET name = coalesce(sqlc.narg('name'), name),
|
||||
code = coalesce(sqlc.narg('code'), code)
|
||||
WHERE id = sqlc.arg('id');
|
||||
|
||||
-- name: DeleteCategory :execrows
|
||||
DELETE
|
||||
FROM categories
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreatePostCategory :exec
|
||||
INSERT INTO post_category(post_id, category_id)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: DeletePostCategory :exec
|
||||
DELETE
|
||||
FROM post_category
|
||||
WHERE post_id = $1;
|
||||
13
internal/db/query/file.sql
Normal file
13
internal/db/query/file.sql
Normal file
@@ -0,0 +1,13 @@
|
||||
-- name: CreateFile :one
|
||||
INSERT INTO files(file_name, file_path, original_name, folder_name, mime_type, file_size)
|
||||
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id,file_path,file_name;
|
||||
|
||||
-- name: GetFiles :many
|
||||
SELECT *
|
||||
FROM files
|
||||
ORDER BY id LIMIT $1
|
||||
OFFSET $2;
|
||||
|
||||
-- name: CountFiles :one
|
||||
SELECT COUNT(*)
|
||||
FROM files;
|
||||
145
internal/db/query/post.sql
Normal file
145
internal/db/query/post.sql
Normal file
@@ -0,0 +1,145 @@
|
||||
-- name: CreatePost :one
|
||||
INSERT INTO posts(title, cover_id, slug, content, summary, status, sort, published_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id;
|
||||
|
||||
-- name: ListPosts :many
|
||||
WITH paginated_posts AS (
|
||||
-- 第一步:先精准查出当前页需要的文章 ID
|
||||
SELECT id,
|
||||
title,
|
||||
cover_id,
|
||||
slug,
|
||||
summary,
|
||||
status,
|
||||
sort,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM posts
|
||||
ORDER BY sort DESC, published_at DESC, id DESC
|
||||
LIMIT $1 OFFSET $2)
|
||||
-- 第二步:用这极少量的记录去进行 JOIN
|
||||
SELECT p.*,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM paginated_posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
ORDER BY p.sort DESC, p.published_at DESC, p.id DESC;
|
||||
|
||||
-- name: CountPosts :one
|
||||
SELECT COUNT(*)
|
||||
FROM posts;
|
||||
|
||||
-- name: GetPostById :one
|
||||
SELECT p.*,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id
|
||||
FROM posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
WHERE p.id = $1
|
||||
LIMIT 1;
|
||||
|
||||
-- name: UpdatePost :execrows
|
||||
UPDATE posts
|
||||
SET title = coalesce(sqlc.narg('title'), title),
|
||||
cover_id = coalesce(sqlc.narg('cover_id'), cover_id),
|
||||
slug = coalesce(sqlc.narg('slug'), slug),
|
||||
content = coalesce(sqlc.narg('content'), content),
|
||||
summary = coalesce(sqlc.narg('summary'), summary),
|
||||
status = coalesce(sqlc.narg('status'), status),
|
||||
sort = coalesce(sqlc.narg('sort'), sort),
|
||||
published_at = coalesce(sqlc.narg('published_at'), published_at)
|
||||
WHERE id = sqlc.arg('id');
|
||||
|
||||
-- name: DeletePost :execrows
|
||||
DELETE
|
||||
FROM posts
|
||||
WHERE id = $1;
|
||||
|
||||
-- web -------------------------------------------------------
|
||||
|
||||
-- name: GetPublicPostBySlug :one
|
||||
SELECT p.*,
|
||||
f.file_path AS cover,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
WHERE p.slug = $1
|
||||
AND p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
LIMIT 1;
|
||||
|
||||
-- name: ListPublishedPosts :many
|
||||
WITH paginated_posts AS (
|
||||
-- 第一步:先精准查出当前页需要的文章 ID
|
||||
SELECT id,
|
||||
title,
|
||||
cover_id,
|
||||
slug,
|
||||
summary,
|
||||
sort,
|
||||
published_at
|
||||
FROM posts
|
||||
WHERE status = 1
|
||||
AND published_at < NOW()
|
||||
ORDER BY sort DESC, published_at DESC, id DESC
|
||||
LIMIT $1 OFFSET $2)
|
||||
SELECT p.*,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM paginated_posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
ORDER BY p.sort DESC, p.published_at DESC, p.id DESC;
|
||||
|
||||
-- name: CountPublishedPosts :one
|
||||
SELECT COUNT(*)
|
||||
FROM posts
|
||||
WHERE status = 1
|
||||
AND published_at < NOW();
|
||||
|
||||
-- name: ListArchives :many
|
||||
SELECT
|
||||
p.id,
|
||||
p.slug,
|
||||
p.title,
|
||||
p.published_at,
|
||||
c."name" AS category_name
|
||||
FROM
|
||||
posts p
|
||||
LEFT JOIN post_category pc ON p.id = pc.post_id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
WHERE
|
||||
p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
ORDER BY
|
||||
p.published_at DESC, p.id DESC;
|
||||
|
||||
-- name: ListCategoryStats :many
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
COUNT(p.id) AS post_count
|
||||
FROM
|
||||
categories c
|
||||
LEFT JOIN post_category pc ON c.id = pc.category_id
|
||||
LEFT JOIN posts p ON p.id = pc.post_id
|
||||
AND p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
GROUP BY
|
||||
c.id,
|
||||
c.name;
|
||||
29
internal/db/query/post_stats.sql
Normal file
29
internal/db/query/post_stats.sql
Normal file
@@ -0,0 +1,29 @@
|
||||
-- name: IncrementPostStatsView :exec
|
||||
WITH visitor AS (
|
||||
INSERT INTO post_viewers (
|
||||
post_id,
|
||||
ip,
|
||||
last_viewed_at
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT (post_id, ip)
|
||||
DO UPDATE
|
||||
SET last_viewed_at = NOW()
|
||||
WHERE post_viewers.last_viewed_at < NOW() - INTERVAL '30 minutes'
|
||||
RETURNING post_id
|
||||
)
|
||||
INSERT INTO post_stats (
|
||||
post_id,
|
||||
view
|
||||
)
|
||||
SELECT
|
||||
post_id,
|
||||
1
|
||||
FROM visitor
|
||||
ON CONFLICT (post_id)
|
||||
DO UPDATE
|
||||
SET view = post_stats.view + 1;
|
||||
58
internal/db/query/sys_api.sql
Normal file
58
internal/db/query/sys_api.sql
Normal file
@@ -0,0 +1,58 @@
|
||||
-- name: CreateSysApi :one
|
||||
INSERT INTO sys_apis (name, group_name, method, path, sort)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id;
|
||||
|
||||
-- name: GetSysApis :many
|
||||
SELECT *
|
||||
FROM sys_apis
|
||||
ORDER BY id
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: CountSysApis :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_apis;
|
||||
|
||||
-- name: GetSysApiByID :one
|
||||
SELECT *
|
||||
FROM sys_apis
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: UpdateSysApi :execrows
|
||||
UPDATE sys_apis
|
||||
SET name = $2,
|
||||
method = $3,
|
||||
path = $4,
|
||||
sort = $5,
|
||||
group_name = $6
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: DeleteSysApi :execrows
|
||||
DELETE
|
||||
FROM sys_apis
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateSysApiPermission :exec
|
||||
INSERT INTO sys_api_permission (api_id, permission_id)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: DeleteSysApiPermission :exec
|
||||
DELETE
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = $1;
|
||||
|
||||
-- name: DeleteSysPermissionBySysApiID :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id IN (SELECT permission_id
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = $1);
|
||||
|
||||
-- name: GetSysApiGroupNames :many
|
||||
SELECT DISTINCT group_name
|
||||
FROM sys_apis;
|
||||
|
||||
-- name: GetAllSysApis :many
|
||||
SELECT *
|
||||
FROM sys_apis
|
||||
ORDER BY sort ASC;
|
||||
71
internal/db/query/sys_menu.sql
Normal file
71
internal/db/query/sys_menu.sql
Normal file
@@ -0,0 +1,71 @@
|
||||
-- name: CreateSysMenu :one
|
||||
INSERT INTO sys_menus (name, path, component, type, hidden, sort, status, parent_id, icon)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id;
|
||||
|
||||
-- name: ListSysMenus :many
|
||||
SELECT m.*, p.code AS permission_code
|
||||
FROM sys_menus m
|
||||
LEFT JOIN sys_menu_permission mp ON m.id = mp.menu_id
|
||||
LEFT JOIN sys_permissions p ON mp.permission_id = p.id
|
||||
ORDER BY m.id
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: CountSysMenus :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_menus;
|
||||
|
||||
-- name: GetAllSysMenus :many
|
||||
SELECT m.*,
|
||||
p.code AS permission_code
|
||||
FROM sys_menus m
|
||||
LEFT JOIN sys_menu_permission mp ON m.id = mp.menu_id
|
||||
LEFT JOIN sys_permissions p ON p.id = mp.permission_id
|
||||
ORDER BY m.sort ASC,
|
||||
m.id ASC;
|
||||
|
||||
-- name: GetSysMenuByID :one
|
||||
SELECT *
|
||||
FROM sys_menus
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: UpdateSysMenu :execrows
|
||||
UPDATE sys_menus
|
||||
SET name = coalesce(sqlc.narg('name'), name),
|
||||
path = coalesce(sqlc.narg('path'), path),
|
||||
component = coalesce(sqlc.narg('component'), component),
|
||||
type = coalesce(sqlc.narg('type'), type),
|
||||
hidden = coalesce(sqlc.narg('hidden'), hidden),
|
||||
sort = coalesce(sqlc.narg('sort'), sort),
|
||||
status = coalesce(sqlc.narg('status'), status),
|
||||
icon = coalesce(sqlc.narg('icon'), status),
|
||||
parent_id = CASE WHEN @update_parent_id::boolean THEN @parent_id ELSE parent_id END
|
||||
WHERE id = sqlc.arg('id');
|
||||
|
||||
-- name: DeleteSysMenu :execrows
|
||||
DELETE
|
||||
FROM sys_menus
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateSysMenuPermission :exec
|
||||
INSERT INTO sys_menu_permission (menu_id, permission_id)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: DeleteSysMenuPermission :exec
|
||||
DELETE
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = $1;
|
||||
|
||||
-- name: DeleteSysPermissionByMenuID :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id IN (SELECT permission_id
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = $1);
|
||||
|
||||
-- name: UpdateSysMenuPermissionCode :exec
|
||||
UPDATE sys_permissions p
|
||||
SET code = coalesce(sqlc.narg('code'), code)
|
||||
FROM sys_menu_permission mp
|
||||
WHERE p.id = mp.permission_id
|
||||
AND mp.menu_id = $1;
|
||||
14
internal/db/query/sys_permission.sql
Normal file
14
internal/db/query/sys_permission.sql
Normal file
@@ -0,0 +1,14 @@
|
||||
-- name: CreateSysPermission :one
|
||||
INSERT INTO sys_permissions(type,code)
|
||||
VALUES ($1,$2) RETURNING id;
|
||||
|
||||
-- name: UpdateSysPermission :exec
|
||||
UPDATE sys_permissions
|
||||
SET code = $2
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: DeleteSysPermission :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id = $1;
|
||||
|
||||
76
internal/db/query/sys_role.sql
Normal file
76
internal/db/query/sys_role.sql
Normal file
@@ -0,0 +1,76 @@
|
||||
-- name: CreateSysRole :exec
|
||||
INSERT INTO sys_roles (name, code)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: ListSysRoles :many
|
||||
SELECT *
|
||||
FROM sys_roles
|
||||
ORDER BY id
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: CountSysRoles :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_roles;
|
||||
|
||||
-- name: GetSysRoleByID :one
|
||||
SELECT *
|
||||
FROM sys_roles
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: ExistsSysRoleCode :one
|
||||
SELECT EXISTS(SELECT 1
|
||||
FROM sys_roles
|
||||
WHERE code = sqlc.arg(code)
|
||||
AND (
|
||||
sqlc.narg(id)::integer IS NULL
|
||||
OR id <> sqlc.narg(id)::integer
|
||||
));
|
||||
|
||||
-- name: UpdateSysRole :execrows
|
||||
UPDATE sys_roles
|
||||
SET name = coalesce(sqlc.narg('name'), name)
|
||||
WHERE id = sqlc.arg('id');
|
||||
|
||||
-- name: DeleteSysRole :execrows
|
||||
DELETE
|
||||
FROM sys_roles
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: CreateSysRolePermission :copyfrom
|
||||
INSERT INTO sys_role_permission (role_id, permission_id)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: GetSysPermissionIdsByMenuIDs :many
|
||||
SELECT permission_id
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = ANY ($1::int[]);
|
||||
|
||||
-- name: GetSysPermissionIdsByApiIDs :many
|
||||
SELECT permission_id
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = ANY ($1::int[]);
|
||||
|
||||
-- name: GetAllSysRoles :many
|
||||
SELECT *
|
||||
from sys_roles;
|
||||
|
||||
-- name: GetSysRoleMenus :many
|
||||
SELECT m.id, m.name
|
||||
FROM sys_role_permission rp
|
||||
JOIN sys_menu_permission mp ON mp.permission_id = rp.permission_id
|
||||
JOIN sys_menus m ON m.id = mp.menu_id
|
||||
WHERE rp.role_id = $1;
|
||||
|
||||
-- name: GetSysRoleApis :many
|
||||
SELECT a.id, a."name"
|
||||
FROM sys_role_permission rp
|
||||
JOIN sys_api_permission ap ON ap.permission_id = rp.permission_id
|
||||
JOIN sys_apis a ON a.id = ap.api_id
|
||||
WHERE rp.role_id = $1;
|
||||
|
||||
-- name: DeleteSysRolePermission :exec
|
||||
DELETE
|
||||
FROM sys_role_permission rp USING sys_permissions p
|
||||
WHERE rp.permission_id = p.id
|
||||
AND p."type" = $2
|
||||
AND rp.role_id = $1;
|
||||
107
internal/db/query/sys_user.sql
Normal file
107
internal/db/query/sys_user.sql
Normal file
@@ -0,0 +1,107 @@
|
||||
-- name: CreateSysUser :exec
|
||||
INSERT INTO sys_users (account, username, password_hash, avatar_id)
|
||||
VALUES ($1, $2, $3, $4);
|
||||
|
||||
-- name: GetSysUserByID :one
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
WHERE u.id = $1;
|
||||
|
||||
-- name: GetSysUserByAccount :one
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.password_hash,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
WHERE u.account = $1;
|
||||
|
||||
-- name: ListSysUsers :many
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
ORDER BY u.id
|
||||
LIMIT $1 OFFSET $2;
|
||||
|
||||
-- name: CountSysUsers :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_users;
|
||||
|
||||
-- name: UpdateSysUser :execrows
|
||||
UPDATE sys_users
|
||||
SET username = coalesce(sqlc.narg('username'), username),
|
||||
avatar_id = CASE WHEN @update_avatar_id::boolean THEN @avatar_id ELSE avatar_id END
|
||||
WHERE id = sqlc.arg('id');
|
||||
|
||||
-- name: UpdateSysUserPassword :execrows
|
||||
UPDATE sys_users
|
||||
SET password_hash = $2
|
||||
WHERE id = $1;
|
||||
|
||||
-- name: DeleteSysUser :execrows
|
||||
DELETE
|
||||
FROM sys_users
|
||||
WHERE id = $1
|
||||
AND id <> 1;
|
||||
|
||||
-- name: ClearSysUserRoles :exec
|
||||
DELETE
|
||||
FROM sys_user_role
|
||||
WHERE user_id = $1;
|
||||
|
||||
-- name: CreateSysUserRole :exec
|
||||
INSERT INTO sys_user_role (user_id, role_id)
|
||||
VALUES ($1, $2);
|
||||
|
||||
-- name: GetSysUserRoles :many
|
||||
SELECT r.*
|
||||
FROM sys_roles r
|
||||
JOIN sys_user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = $1;
|
||||
|
||||
-- name: GetSysAdminMenus :many
|
||||
SELECT *
|
||||
FROM sys_menus
|
||||
ORDER BY sort, id;
|
||||
|
||||
-- name: GetSysUserMenus :many
|
||||
SELECT DISTINCT m.*
|
||||
FROM sys_users u
|
||||
JOIN sys_user_role ur ON ur.user_id = u.id
|
||||
JOIN sys_roles r ON r.id = ur.role_id
|
||||
JOIN sys_role_permission rp ON rp.role_id = r.id
|
||||
JOIN sys_permissions p ON p.id = rp.permission_id
|
||||
JOIN sys_menu_permission mp ON mp.permission_id = p.id
|
||||
JOIN sys_menus m ON m.id = mp.menu_id
|
||||
WHERE u.id = $1
|
||||
ORDER BY m.sort, m.id;
|
||||
|
||||
-- name: GetSysUserApis :many
|
||||
SELECT DISTINCT a.id,
|
||||
a.PATH,
|
||||
a.METHOD
|
||||
FROM sys_users u
|
||||
JOIN sys_user_role ur ON u.id = ur.user_id
|
||||
JOIN sys_role_permission rp ON rp.role_id = ur.role_id
|
||||
JOIN sys_permissions p ON p.id = rp.permission_id
|
||||
AND p.TYPE = 1
|
||||
JOIN sys_api_permission ap ON ap.permission_id = p.id
|
||||
JOIN sys_apis a ON a.id = ap.api_id
|
||||
WHERE u.id = $1;
|
||||
19
internal/db/schema/categories.sql
Normal file
19
internal/db/schema/categories.sql
Normal file
@@ -0,0 +1,19 @@
|
||||
-- 分类表
|
||||
CREATE TABLE categories
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT UNIQUE NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_categories_updated_at
|
||||
BEFORE UPDATE
|
||||
ON categories
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN categories.id IS '分类ID';
|
||||
COMMENT ON COLUMN categories.name IS '分类名称';
|
||||
COMMENT ON COLUMN categories.code IS '分类编码';
|
||||
28
internal/db/schema/files.sql
Normal file
28
internal/db/schema/files.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE files
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
file_name TEXT NOT NULL,
|
||||
file_path TEXT NOT NULL,
|
||||
original_name TEXT NOT NULL,
|
||||
folder_name TEXT NOT NULL,
|
||||
mime_type TEXT NOT NULL,
|
||||
file_size BIGINT NOT NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_files_updated_at
|
||||
BEFORE UPDATE
|
||||
ON files
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN files.id IS '主键ID';
|
||||
COMMENT ON COLUMN files.file_name IS '文件名 xxx.jpg';
|
||||
COMMENT ON COLUMN files.file_path IS '文件路径 带path /xxx/xxx.jpg';
|
||||
COMMENT ON COLUMN files.original_name IS '原始文件名';
|
||||
COMMENT ON COLUMN files.folder_name IS '文件夹名称';
|
||||
COMMENT ON COLUMN files.mime_type IS 'mime_type';
|
||||
COMMENT ON COLUMN files.file_size IS '文件大小';
|
||||
COMMENT ON COLUMN files.created_at IS '上传时间';
|
||||
COMMENT ON COLUMN files.updated_at IS '修改时间';
|
||||
11
internal/db/schema/func.sql
Normal file
11
internal/db/schema/func.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
CREATE
|
||||
OR REPLACE FUNCTION update_updated_at_column()
|
||||
RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
NEW.updated_at
|
||||
:= NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$
|
||||
language 'plpgsql';
|
||||
9
internal/db/schema/post_category.sql
Normal file
9
internal/db/schema/post_category.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE post_category
|
||||
(
|
||||
post_id INT NOT NULL,
|
||||
category_id INT NOT NULL,
|
||||
PRIMARY KEY (post_id, category_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_category.post_id IS '文章ID';
|
||||
COMMENT ON COLUMN post_category.category_id IS '分类ID';
|
||||
8
internal/db/schema/post_stats.sql
Normal file
8
internal/db/schema/post_stats.sql
Normal file
@@ -0,0 +1,8 @@
|
||||
CREATE TABLE post_stats
|
||||
(
|
||||
post_id INTEGER PRIMARY KEY,
|
||||
view INTEGER NOT NULL DEFAULT 0
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_stats.post_id IS '文章id';
|
||||
COMMENT ON COLUMN post_stats.view IS '文章阅读量';
|
||||
12
internal/db/schema/post_viewers.sql
Normal file
12
internal/db/schema/post_viewers.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
CREATE TABLE post_viewers
|
||||
(
|
||||
post_id INTEGER NOT NULL,
|
||||
ip INET NOT NULL,
|
||||
last_viewed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
|
||||
PRIMARY KEY(post_id, ip)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN post_viewers.post_id IS '文章id';
|
||||
COMMENT ON COLUMN post_viewers.ip IS '访问ip';
|
||||
COMMENT ON COLUMN post_viewers.last_viewed_at IS '最后访问时间';
|
||||
35
internal/db/schema/posts.sql
Normal file
35
internal/db/schema/posts.sql
Normal file
@@ -0,0 +1,35 @@
|
||||
CREATE TABLE posts
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
title TEXT NOT NULL,
|
||||
cover_id INTEGER,
|
||||
slug TEXT UNIQUE NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
summary TEXT NOT NULL,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
sort INTEGER DEFAULT 0,
|
||||
published_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CHECK (status IN (0, 1, 2))
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_posts_updated_at
|
||||
BEFORE UPDATE
|
||||
ON posts
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
|
||||
COMMENT ON COLUMN posts.id IS '主键ID';
|
||||
COMMENT ON COLUMN posts.title IS '文章标题';
|
||||
COMMENT ON COLUMN posts.cover_id IS '文章封面图片id';
|
||||
COMMENT ON COLUMN posts.slug IS 'URL友好地址';
|
||||
COMMENT ON COLUMN posts.content IS '文章正文';
|
||||
COMMENT ON COLUMN posts.summary IS '摘要';
|
||||
COMMENT ON COLUMN posts.status IS '状态 0:草稿 1:已发布 2:已下线';
|
||||
COMMENT ON COLUMN posts.sort IS '文章排序';
|
||||
COMMENT ON COLUMN posts.published_at IS '发布时间';
|
||||
COMMENT ON COLUMN posts.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN posts.updated_at IS '修改时间';
|
||||
9
internal/db/schema/sys_api_permission.sql
Normal file
9
internal/db/schema/sys_api_permission.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sys_api_permission
|
||||
(
|
||||
api_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (api_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_api_permission.api_id IS 'api ID';
|
||||
COMMENT ON COLUMN sys_api_permission.permission_id IS '权限id';
|
||||
28
internal/db/schema/sys_apis.sql
Normal file
28
internal/db/schema/sys_apis.sql
Normal file
@@ -0,0 +1,28 @@
|
||||
CREATE TABLE sys_apis
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
group_name TEXT NOT NULL,
|
||||
name TEXT NOT NULL,
|
||||
method TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
sort INTEGER DEFAULT 0,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
UNIQUE (method, path)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_apis_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_apis
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_apis.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_apis.group_name IS '分组名称';
|
||||
COMMENT ON COLUMN sys_apis.name IS '接口名称';
|
||||
COMMENT ON COLUMN sys_apis.method IS 'api方法';
|
||||
COMMENT ON COLUMN sys_apis.path IS 'api路径';
|
||||
COMMENT ON COLUMN sys_apis.sort IS '排序';
|
||||
COMMENT ON COLUMN sys_apis.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_apis.updated_at IS '更新时间';
|
||||
9
internal/db/schema/sys_menu_permission.sql
Normal file
9
internal/db/schema/sys_menu_permission.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sys_menu_permission
|
||||
(
|
||||
menu_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (menu_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_menu_permission.menu_id IS '菜单id';
|
||||
COMMENT ON COLUMN sys_menu_permission.permission_id IS '权限id';
|
||||
38
internal/db/schema/sys_menus.sql
Normal file
38
internal/db/schema/sys_menus.sql
Normal file
@@ -0,0 +1,38 @@
|
||||
CREATE TABLE sys_menus
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
path TEXT NOT NULL,
|
||||
component TEXT,
|
||||
type SMALLINT NOT NULL,
|
||||
hidden BOOLEAN DEFAULT FALSE,
|
||||
sort INTEGER DEFAULT 0,
|
||||
status SMALLINT NOT NULL DEFAULT 1,
|
||||
parent_id INTEGER,
|
||||
icon INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CHECK (type IN (0, 1, 2)),
|
||||
CHECK (status IN (0, 1))
|
||||
);
|
||||
|
||||
|
||||
CREATE TRIGGER update_sys_menus_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_menus
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_menus.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_menus.name IS '菜单名称';
|
||||
COMMENT ON COLUMN sys_menus.path IS '菜单路径';
|
||||
COMMENT ON COLUMN sys_menus.component IS '组件路径';
|
||||
COMMENT ON COLUMN sys_menus.type IS '菜单类型 0: 目录 1: 菜单 2: button';
|
||||
COMMENT ON COLUMN sys_menus.sort IS '菜单排序';
|
||||
COMMENT ON COLUMN sys_menus.hidden IS '是否隐藏菜单';
|
||||
COMMENT ON COLUMN sys_menus.status IS '菜单状态 1: 启用 0: 禁用';
|
||||
COMMENT ON COLUMN sys_menus.parent_id IS '父级id';
|
||||
COMMENT ON COLUMN sys_menus.icon IS '菜单icon';
|
||||
COMMENT ON COLUMN sys_menus.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_menus.updated_at IS '更新时间';
|
||||
22
internal/db/schema/sys_permissions.sql
Normal file
22
internal/db/schema/sys_permissions.sql
Normal file
@@ -0,0 +1,22 @@
|
||||
CREATE TABLE sys_permissions
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
code TEXT UNIQUE,
|
||||
type SMALLINT NOT NULL,
|
||||
created_at TIMESTAMPTZtz NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ,
|
||||
|
||||
CONSTRAINT unique_type_code UNIQUE (type, code)
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_permissions_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_permissions
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_permissions.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_permissions.code IS '权限字符,唯一';
|
||||
COMMENT ON COLUMN sys_permissions.code IS '权限类型,0: 菜单 1: api';
|
||||
COMMENT ON COLUMN sys_permissions.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_permissions.updated_at IS '更新时间';
|
||||
9
internal/db/schema/sys_role_permission.sql
Normal file
9
internal/db/schema/sys_role_permission.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sys_role_permission
|
||||
(
|
||||
role_id INTEGER NOT NULL,
|
||||
permission_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (role_id, permission_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_role_permission.role_id IS '角色id';
|
||||
COMMENT ON COLUMN sys_role_permission.permission_id IS '权限id';
|
||||
20
internal/db/schema/sys_roles.sql
Normal file
20
internal/db/schema/sys_roles.sql
Normal file
@@ -0,0 +1,20 @@
|
||||
CREATE TABLE sys_roles
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
name TEXT NOT NULL,
|
||||
code TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE TRIGGER update_sys_roles_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_roles
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
COMMENT ON COLUMN sys_roles.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_roles.name IS '角色名称';
|
||||
COMMENT ON COLUMN sys_roles.code IS '角色编码,唯一';
|
||||
COMMENT ON COLUMN sys_roles.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_roles.updated_at IS '更新时间';
|
||||
9
internal/db/schema/sys_user_role.sql
Normal file
9
internal/db/schema/sys_user_role.sql
Normal file
@@ -0,0 +1,9 @@
|
||||
CREATE TABLE sys_user_role
|
||||
(
|
||||
user_id INTEGER NOT NULL,
|
||||
role_id INTEGER NOT NULL,
|
||||
PRIMARY KEY (user_id, role_id)
|
||||
);
|
||||
|
||||
COMMENT ON COLUMN sys_user_role.user_id IS '用户ID';
|
||||
COMMENT ON COLUMN sys_user_role.role_id IS '角色ID';
|
||||
27
internal/db/schema/sys_users.sql
Normal file
27
internal/db/schema/sys_users.sql
Normal file
@@ -0,0 +1,27 @@
|
||||
CREATE TABLE sys_users
|
||||
(
|
||||
id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY,
|
||||
account TEXT NOT NULL UNIQUE,
|
||||
username TEXT NOT NULL,
|
||||
password_hash TEXT NOT NULL,
|
||||
avatar_id INTEGER,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ
|
||||
);
|
||||
|
||||
CREATE INDEX idx_sys_users_account ON sys_users (account);
|
||||
|
||||
CREATE TRIGGER update_sys_users_updated_at
|
||||
BEFORE UPDATE
|
||||
ON sys_users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_updated_at_column();
|
||||
|
||||
|
||||
COMMENT ON COLUMN sys_users.id IS '主键ID';
|
||||
COMMENT ON COLUMN sys_users.account IS '用户账号';
|
||||
COMMENT ON COLUMN sys_users.username IS '用户名称';
|
||||
COMMENT ON COLUMN sys_users.password_hash IS 'hash密码';
|
||||
COMMENT ON COLUMN sys_users.avatar_id IS '头像文件id';
|
||||
COMMENT ON COLUMN sys_users.created_at IS '创建时间';
|
||||
COMMENT ON COLUMN sys_users.updated_at IS '更新时间';
|
||||
186
internal/db/sqlc/category.sql.go
Normal file
186
internal/db/sqlc/category.sql.go
Normal file
@@ -0,0 +1,186 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: category.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const countCategories = `-- name: CountCategories :one
|
||||
SELECT COUNT(*)
|
||||
FROM categories
|
||||
`
|
||||
|
||||
func (q *Queries) CountCategories(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countCategories)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createCategory = `-- name: CreateCategory :exec
|
||||
INSERT INTO categories(name, code)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreateCategoryParams struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateCategory(ctx context.Context, arg CreateCategoryParams) error {
|
||||
_, err := q.db.Exec(ctx, createCategory, arg.Name, arg.Code)
|
||||
return err
|
||||
}
|
||||
|
||||
const createPostCategory = `-- name: CreatePostCategory :exec
|
||||
INSERT INTO post_category(post_id, category_id)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreatePostCategoryParams struct {
|
||||
PostID int32 `json:"post_id"`
|
||||
CategoryID int32 `json:"category_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreatePostCategory(ctx context.Context, arg CreatePostCategoryParams) error {
|
||||
_, err := q.db.Exec(ctx, createPostCategory, arg.PostID, arg.CategoryID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteCategory = `-- name: DeleteCategory :execrows
|
||||
DELETE
|
||||
FROM categories
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteCategory(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deleteCategory, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const deletePostCategory = `-- name: DeletePostCategory :exec
|
||||
DELETE
|
||||
FROM post_category
|
||||
WHERE post_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeletePostCategory(ctx context.Context, postID int32) error {
|
||||
_, err := q.db.Exec(ctx, deletePostCategory, postID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getCategoryById = `-- name: GetCategoryById :one
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
FROM categories
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetCategoryById(ctx context.Context, id int32) (Category, error) {
|
||||
row := q.db.QueryRow(ctx, getCategoryById, id)
|
||||
var i Category
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listAllCategories = `-- name: ListAllCategories :many
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
FROM categories
|
||||
`
|
||||
|
||||
func (q *Queries) ListAllCategories(ctx context.Context) ([]Category, error) {
|
||||
rows, err := q.db.Query(ctx, listAllCategories)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Category{}
|
||||
for rows.Next() {
|
||||
var i Category
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listCategories = `-- name: ListCategories :many
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
FROM categories
|
||||
ORDER BY id LIMIT $1
|
||||
OFFSET $2
|
||||
`
|
||||
|
||||
type ListCategoriesParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListCategories(ctx context.Context, arg ListCategoriesParams) ([]Category, error) {
|
||||
rows, err := q.db.Query(ctx, listCategories, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []Category{}
|
||||
for rows.Next() {
|
||||
var i Category
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateCategory = `-- name: UpdateCategory :execrows
|
||||
UPDATE categories
|
||||
SET name = coalesce($1, name),
|
||||
code = coalesce($2, code)
|
||||
WHERE id = $3
|
||||
`
|
||||
|
||||
type UpdateCategoryParams struct {
|
||||
Name *string `json:"name"`
|
||||
Code *string `json:"code"`
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateCategory(ctx context.Context, arg UpdateCategoryParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateCategory, arg.Name, arg.Code, arg.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
43
internal/db/sqlc/copyfrom.go
Normal file
43
internal/db/sqlc/copyfrom.go
Normal file
@@ -0,0 +1,43 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: copyfrom.go
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
// iteratorForCreateSysRolePermission implements pgx.CopyFromSource.
|
||||
type iteratorForCreateSysRolePermission struct {
|
||||
rows []CreateSysRolePermissionParams
|
||||
skippedFirstNextCall bool
|
||||
}
|
||||
|
||||
func (r *iteratorForCreateSysRolePermission) Next() bool {
|
||||
if len(r.rows) == 0 {
|
||||
return false
|
||||
}
|
||||
if !r.skippedFirstNextCall {
|
||||
r.skippedFirstNextCall = true
|
||||
return true
|
||||
}
|
||||
r.rows = r.rows[1:]
|
||||
return len(r.rows) > 0
|
||||
}
|
||||
|
||||
func (r iteratorForCreateSysRolePermission) Values() ([]interface{}, error) {
|
||||
return []interface{}{
|
||||
r.rows[0].RoleID,
|
||||
r.rows[0].PermissionID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r iteratorForCreateSysRolePermission) Err() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysRolePermission(ctx context.Context, arg []CreateSysRolePermissionParams) (int64, error) {
|
||||
return q.db.CopyFrom(ctx, []string{"sys_role_permission"}, []string{"role_id", "permission_id"}, &iteratorForCreateSysRolePermission{rows: arg})
|
||||
}
|
||||
33
internal/db/sqlc/db.go
Normal file
33
internal/db/sqlc/db.go
Normal file
@@ -0,0 +1,33 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
type DBTX interface {
|
||||
Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
|
||||
Query(context.Context, string, ...interface{}) (pgx.Rows, error)
|
||||
QueryRow(context.Context, string, ...interface{}) pgx.Row
|
||||
CopyFrom(ctx context.Context, tableName pgx.Identifier, columnNames []string, rowSrc pgx.CopyFromSource) (int64, error)
|
||||
}
|
||||
|
||||
func New(db DBTX) *Queries {
|
||||
return &Queries{db: db}
|
||||
}
|
||||
|
||||
type Queries struct {
|
||||
db DBTX
|
||||
}
|
||||
|
||||
func (q *Queries) WithTx(tx pgx.Tx) *Queries {
|
||||
return &Queries{
|
||||
db: tx,
|
||||
}
|
||||
}
|
||||
98
internal/db/sqlc/file.sql.go
Normal file
98
internal/db/sqlc/file.sql.go
Normal file
@@ -0,0 +1,98 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: file.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const countFiles = `-- name: CountFiles :one
|
||||
SELECT COUNT(*)
|
||||
FROM files
|
||||
`
|
||||
|
||||
func (q *Queries) CountFiles(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countFiles)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createFile = `-- name: CreateFile :one
|
||||
INSERT INTO files(file_name, file_path, original_name, folder_name, mime_type, file_size)
|
||||
VALUES ($1, $2, $3, $4, $5, $6) RETURNING id,file_path,file_name
|
||||
`
|
||||
|
||||
type CreateFileParams struct {
|
||||
FileName string `json:"file_name"`
|
||||
FilePath string `json:"file_path"`
|
||||
OriginalName string `json:"original_name"`
|
||||
FolderName string `json:"folder_name"`
|
||||
MimeType string `json:"mime_type"`
|
||||
FileSize int64 `json:"file_size"`
|
||||
}
|
||||
|
||||
type CreateFileRow struct {
|
||||
ID int32 `json:"id"`
|
||||
FilePath string `json:"file_path"`
|
||||
FileName string `json:"file_name"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateFile(ctx context.Context, arg CreateFileParams) (CreateFileRow, error) {
|
||||
row := q.db.QueryRow(ctx, createFile,
|
||||
arg.FileName,
|
||||
arg.FilePath,
|
||||
arg.OriginalName,
|
||||
arg.FolderName,
|
||||
arg.MimeType,
|
||||
arg.FileSize,
|
||||
)
|
||||
var i CreateFileRow
|
||||
err := row.Scan(&i.ID, &i.FilePath, &i.FileName)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getFiles = `-- name: GetFiles :many
|
||||
SELECT id, file_name, file_path, original_name, folder_name, mime_type, file_size, created_at, updated_at
|
||||
FROM files
|
||||
ORDER BY id LIMIT $1
|
||||
OFFSET $2
|
||||
`
|
||||
|
||||
type GetFilesParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetFiles(ctx context.Context, arg GetFilesParams) ([]File, error) {
|
||||
rows, err := q.db.Query(ctx, getFiles, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []File{}
|
||||
for rows.Next() {
|
||||
var i File
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.FileName,
|
||||
&i.FilePath,
|
||||
&i.OriginalName,
|
||||
&i.FolderName,
|
||||
&i.MimeType,
|
||||
&i.FileSize,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
206
internal/db/sqlc/models.go
Normal file
206
internal/db/sqlc/models.go
Normal file
@@ -0,0 +1,206 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"net/netip"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Category struct {
|
||||
// 分类ID
|
||||
ID int32 `json:"id"`
|
||||
// 分类名称
|
||||
Name string `json:"name"`
|
||||
// 分类编码
|
||||
Code string `json:"code"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type File struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 文件名 xxx.jpg
|
||||
FileName string `json:"file_name"`
|
||||
// 文件路径 带path /xxx/xxx.jpg
|
||||
FilePath string `json:"file_path"`
|
||||
// 原始文件名
|
||||
OriginalName string `json:"original_name"`
|
||||
// 文件夹名称
|
||||
FolderName string `json:"folder_name"`
|
||||
// mime_type
|
||||
MimeType string `json:"mime_type"`
|
||||
// 文件大小
|
||||
FileSize int64 `json:"file_size"`
|
||||
// 上传时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 修改时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 文章标题
|
||||
Title string `json:"title"`
|
||||
// 文章封面图片id
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
// URL友好地址
|
||||
Slug string `json:"slug"`
|
||||
// 文章正文
|
||||
Content string `json:"content"`
|
||||
// 摘要
|
||||
Summary string `json:"summary"`
|
||||
// 状态 0:草稿 1:已发布 2:已下线
|
||||
Status int16 `json:"status"`
|
||||
// 文章排序
|
||||
Sort *int32 `json:"sort"`
|
||||
// 发布时间
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
// 创建时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 修改时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type PostCategory struct {
|
||||
// 文章ID
|
||||
PostID int32 `json:"post_id"`
|
||||
// 分类ID
|
||||
CategoryID int32 `json:"category_id"`
|
||||
}
|
||||
|
||||
type PostStat struct {
|
||||
// 文章id
|
||||
PostID int32 `json:"post_id"`
|
||||
// 文章阅读量
|
||||
View int32 `json:"view"`
|
||||
}
|
||||
|
||||
type PostViewer struct {
|
||||
// 文章id
|
||||
PostID int32 `json:"post_id"`
|
||||
// 访问ip
|
||||
Ip netip.Addr `json:"ip"`
|
||||
// 最后访问时间
|
||||
LastViewedAt time.Time `json:"last_viewed_at"`
|
||||
}
|
||||
|
||||
type SysApi struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 分组名称
|
||||
GroupName string `json:"group_name"`
|
||||
// 接口名称
|
||||
Name string `json:"name"`
|
||||
// api方法
|
||||
Method string `json:"method"`
|
||||
// api路径
|
||||
Path string `json:"path"`
|
||||
// 排序
|
||||
Sort *int32 `json:"sort"`
|
||||
// 创建时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 更新时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SysApiPermission struct {
|
||||
// api ID
|
||||
ApiID int32 `json:"api_id"`
|
||||
// 权限id
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
type SysMenu struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 菜单名称
|
||||
Name string `json:"name"`
|
||||
// 菜单路径
|
||||
Path string `json:"path"`
|
||||
// 组件路径
|
||||
Component *string `json:"component"`
|
||||
// 菜单类型 0: 目录 1: 菜单 2: button
|
||||
Type int16 `json:"type"`
|
||||
// 是否隐藏菜单
|
||||
Hidden *bool `json:"hidden"`
|
||||
// 菜单排序
|
||||
Sort *int32 `json:"sort"`
|
||||
// 菜单状态 1: 启用 0: 禁用
|
||||
Status int16 `json:"status"`
|
||||
// 父级id
|
||||
ParentID *int32 `json:"parent_id"`
|
||||
// 菜单icon
|
||||
Icon *int32 `json:"icon"`
|
||||
// 创建时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 更新时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SysMenuPermission struct {
|
||||
// 菜单id
|
||||
MenuID int32 `json:"menu_id"`
|
||||
// 权限id
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
type SysPermission struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 权限类型,0: 菜单 1: api
|
||||
Code *string `json:"code"`
|
||||
Type int16 `json:"type"`
|
||||
// 创建时间
|
||||
CreatedAt interface{} `json:"created_at"`
|
||||
// 更新时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SysRole struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 角色名称
|
||||
Name string `json:"name"`
|
||||
// 角色编码,唯一
|
||||
Code string `json:"code"`
|
||||
// 创建时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 更新时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SysRolePermission struct {
|
||||
// 角色id
|
||||
RoleID int32 `json:"role_id"`
|
||||
// 权限id
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
type SysUser struct {
|
||||
// 主键ID
|
||||
ID int32 `json:"id"`
|
||||
// 用户账号
|
||||
Account string `json:"account"`
|
||||
// 用户名称
|
||||
Username string `json:"username"`
|
||||
// hash密码
|
||||
PasswordHash string `json:"password_hash"`
|
||||
// 头像文件id
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
// 创建时间
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
// 更新时间
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type SysUserRole struct {
|
||||
// 用户ID
|
||||
UserID int32 `json:"user_id"`
|
||||
// 角色ID
|
||||
RoleID int32 `json:"role_id"`
|
||||
}
|
||||
491
internal/db/sqlc/post.sql.go
Normal file
491
internal/db/sqlc/post.sql.go
Normal file
@@ -0,0 +1,491 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: post.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const countPosts = `-- name: CountPosts :one
|
||||
SELECT COUNT(*)
|
||||
FROM posts
|
||||
`
|
||||
|
||||
func (q *Queries) CountPosts(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countPosts)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const countPublishedPosts = `-- name: CountPublishedPosts :one
|
||||
SELECT COUNT(*)
|
||||
FROM posts
|
||||
WHERE status = 1
|
||||
AND published_at < NOW()
|
||||
`
|
||||
|
||||
func (q *Queries) CountPublishedPosts(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countPublishedPosts)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createPost = `-- name: CreatePost :one
|
||||
INSERT INTO posts(title, cover_id, slug, content, summary, status, sort, published_at)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type CreatePostParams struct {
|
||||
Title string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug string `json:"slug"`
|
||||
Content string `json:"content"`
|
||||
Summary string `json:"summary"`
|
||||
Status int16 `json:"status"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreatePost(ctx context.Context, arg CreatePostParams) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, createPost,
|
||||
arg.Title,
|
||||
arg.CoverID,
|
||||
arg.Slug,
|
||||
arg.Content,
|
||||
arg.Summary,
|
||||
arg.Status,
|
||||
arg.Sort,
|
||||
arg.PublishedAt,
|
||||
)
|
||||
var id int32
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const deletePost = `-- name: DeletePost :execrows
|
||||
DELETE
|
||||
FROM posts
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeletePost(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deletePost, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const getPostById = `-- name: GetPostById :one
|
||||
SELECT p.id, p.title, p.cover_id, p.slug, p.content, p.summary, p.status, p.sort, p.published_at, p.created_at, p.updated_at,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id
|
||||
FROM posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
WHERE p.id = $1
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetPostByIdRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug string `json:"slug"`
|
||||
Content string `json:"content"`
|
||||
Summary string `json:"summary"`
|
||||
Status int16 `json:"status"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Cover *string `json:"cover"`
|
||||
CategoryName *string `json:"category_name"`
|
||||
CategoryID *int32 `json:"category_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetPostById(ctx context.Context, id int32) (GetPostByIdRow, error) {
|
||||
row := q.db.QueryRow(ctx, getPostById, id)
|
||||
var i GetPostByIdRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.CoverID,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.Summary,
|
||||
&i.Status,
|
||||
&i.Sort,
|
||||
&i.PublishedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Cover,
|
||||
&i.CategoryName,
|
||||
&i.CategoryID,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getPublicPostBySlug = `-- name: GetPublicPostBySlug :one
|
||||
|
||||
SELECT p.id, p.title, p.cover_id, p.slug, p.content, p.summary, p.status, p.sort, p.published_at, p.created_at, p.updated_at,
|
||||
f.file_path AS cover,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
WHERE p.slug = $1
|
||||
AND p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
LIMIT 1
|
||||
`
|
||||
|
||||
type GetPublicPostBySlugRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug string `json:"slug"`
|
||||
Content string `json:"content"`
|
||||
Summary string `json:"summary"`
|
||||
Status int16 `json:"status"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Cover *string `json:"cover"`
|
||||
View int32 `json:"view"`
|
||||
}
|
||||
|
||||
// web -------------------------------------------------------
|
||||
func (q *Queries) GetPublicPostBySlug(ctx context.Context, slug string) (GetPublicPostBySlugRow, error) {
|
||||
row := q.db.QueryRow(ctx, getPublicPostBySlug, slug)
|
||||
var i GetPublicPostBySlugRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.CoverID,
|
||||
&i.Slug,
|
||||
&i.Content,
|
||||
&i.Summary,
|
||||
&i.Status,
|
||||
&i.Sort,
|
||||
&i.PublishedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Cover,
|
||||
&i.View,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listArchives = `-- name: ListArchives :many
|
||||
SELECT
|
||||
p.id,
|
||||
p.slug,
|
||||
p.title,
|
||||
p.published_at,
|
||||
c."name" AS category_name
|
||||
FROM
|
||||
posts p
|
||||
LEFT JOIN post_category pc ON p.id = pc.post_id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
WHERE
|
||||
p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
ORDER BY
|
||||
p.published_at DESC, p.id DESC
|
||||
`
|
||||
|
||||
type ListArchivesRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
CategoryName *string `json:"category_name"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListArchives(ctx context.Context) ([]ListArchivesRow, error) {
|
||||
rows, err := q.db.Query(ctx, listArchives)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListArchivesRow{}
|
||||
for rows.Next() {
|
||||
var i ListArchivesRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Slug,
|
||||
&i.Title,
|
||||
&i.PublishedAt,
|
||||
&i.CategoryName,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listCategoryStats = `-- name: ListCategoryStats :many
|
||||
SELECT
|
||||
c.id,
|
||||
c.name,
|
||||
COUNT(p.id) AS post_count
|
||||
FROM
|
||||
categories c
|
||||
LEFT JOIN post_category pc ON c.id = pc.category_id
|
||||
LEFT JOIN posts p ON p.id = pc.post_id
|
||||
AND p.status = 1
|
||||
AND p.published_at < NOW()
|
||||
GROUP BY
|
||||
c.id,
|
||||
c.name
|
||||
`
|
||||
|
||||
type ListCategoryStatsRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PostCount int64 `json:"post_count"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListCategoryStats(ctx context.Context) ([]ListCategoryStatsRow, error) {
|
||||
rows, err := q.db.Query(ctx, listCategoryStats)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListCategoryStatsRow{}
|
||||
for rows.Next() {
|
||||
var i ListCategoryStatsRow
|
||||
if err := rows.Scan(&i.ID, &i.Name, &i.PostCount); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listPosts = `-- name: ListPosts :many
|
||||
WITH paginated_posts AS (
|
||||
-- 第一步:先精准查出当前页需要的文章 ID
|
||||
SELECT id,
|
||||
title,
|
||||
cover_id,
|
||||
slug,
|
||||
summary,
|
||||
status,
|
||||
sort,
|
||||
published_at,
|
||||
created_at,
|
||||
updated_at
|
||||
FROM posts
|
||||
ORDER BY sort DESC, published_at DESC, id DESC
|
||||
LIMIT $1 OFFSET $2)
|
||||
SELECT p.id, p.title, p.cover_id, p.slug, p.summary, p.status, p.sort, p.published_at, p.created_at, p.updated_at,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM paginated_posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
ORDER BY p.sort DESC, p.published_at DESC, p.id DESC
|
||||
`
|
||||
|
||||
type ListPostsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type ListPostsRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug string `json:"slug"`
|
||||
Summary string `json:"summary"`
|
||||
Status int16 `json:"status"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
Cover *string `json:"cover"`
|
||||
CategoryName *string `json:"category_name"`
|
||||
CategoryID *int32 `json:"category_id"`
|
||||
View int32 `json:"view"`
|
||||
}
|
||||
|
||||
// 第二步:用这极少量的记录去进行 JOIN
|
||||
func (q *Queries) ListPosts(ctx context.Context, arg ListPostsParams) ([]ListPostsRow, error) {
|
||||
rows, err := q.db.Query(ctx, listPosts, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListPostsRow{}
|
||||
for rows.Next() {
|
||||
var i ListPostsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.CoverID,
|
||||
&i.Slug,
|
||||
&i.Summary,
|
||||
&i.Status,
|
||||
&i.Sort,
|
||||
&i.PublishedAt,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.Cover,
|
||||
&i.CategoryName,
|
||||
&i.CategoryID,
|
||||
&i.View,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listPublishedPosts = `-- name: ListPublishedPosts :many
|
||||
WITH paginated_posts AS (
|
||||
-- 第一步:先精准查出当前页需要的文章 ID
|
||||
SELECT id,
|
||||
title,
|
||||
cover_id,
|
||||
slug,
|
||||
summary,
|
||||
sort,
|
||||
published_at
|
||||
FROM posts
|
||||
WHERE status = 1
|
||||
AND published_at < NOW()
|
||||
ORDER BY sort DESC, published_at DESC, id DESC
|
||||
LIMIT $1 OFFSET $2)
|
||||
SELECT p.id, p.title, p.cover_id, p.slug, p.summary, p.sort, p.published_at,
|
||||
f.file_path AS cover,
|
||||
c.name AS category_name,
|
||||
c.id AS category_id,
|
||||
COALESCE(ps.view, 0) AS view
|
||||
FROM paginated_posts p
|
||||
LEFT JOIN files f ON f.id = p.cover_id
|
||||
LEFT JOIN post_category pc ON pc.post_id = p.id
|
||||
LEFT JOIN categories c ON c.id = pc.category_id
|
||||
LEFT JOIN post_stats ps ON ps.post_id = p.id
|
||||
ORDER BY p.sort DESC, p.published_at DESC, p.id DESC
|
||||
`
|
||||
|
||||
type ListPublishedPostsParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type ListPublishedPostsRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Title string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug string `json:"slug"`
|
||||
Summary string `json:"summary"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
Cover *string `json:"cover"`
|
||||
CategoryName *string `json:"category_name"`
|
||||
CategoryID *int32 `json:"category_id"`
|
||||
View int32 `json:"view"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListPublishedPosts(ctx context.Context, arg ListPublishedPostsParams) ([]ListPublishedPostsRow, error) {
|
||||
rows, err := q.db.Query(ctx, listPublishedPosts, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListPublishedPostsRow{}
|
||||
for rows.Next() {
|
||||
var i ListPublishedPostsRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Title,
|
||||
&i.CoverID,
|
||||
&i.Slug,
|
||||
&i.Summary,
|
||||
&i.Sort,
|
||||
&i.PublishedAt,
|
||||
&i.Cover,
|
||||
&i.CategoryName,
|
||||
&i.CategoryID,
|
||||
&i.View,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updatePost = `-- name: UpdatePost :execrows
|
||||
UPDATE posts
|
||||
SET title = coalesce($1, title),
|
||||
cover_id = coalesce($2, cover_id),
|
||||
slug = coalesce($3, slug),
|
||||
content = coalesce($4, content),
|
||||
summary = coalesce($5, summary),
|
||||
status = coalesce($6, status),
|
||||
sort = coalesce($7, sort),
|
||||
published_at = coalesce($8, published_at)
|
||||
WHERE id = $9
|
||||
`
|
||||
|
||||
type UpdatePostParams struct {
|
||||
Title *string `json:"title"`
|
||||
CoverID *int32 `json:"cover_id"`
|
||||
Slug *string `json:"slug"`
|
||||
Content *string `json:"content"`
|
||||
Summary *string `json:"summary"`
|
||||
Status *int16 `json:"status"`
|
||||
Sort *int32 `json:"sort"`
|
||||
PublishedAt *time.Time `json:"published_at"`
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdatePost(ctx context.Context, arg UpdatePostParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updatePost,
|
||||
arg.Title,
|
||||
arg.CoverID,
|
||||
arg.Slug,
|
||||
arg.Content,
|
||||
arg.Summary,
|
||||
arg.Status,
|
||||
arg.Sort,
|
||||
arg.PublishedAt,
|
||||
arg.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
52
internal/db/sqlc/post_stats.sql.go
Normal file
52
internal/db/sqlc/post_stats.sql.go
Normal file
@@ -0,0 +1,52 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: post_stats.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
)
|
||||
|
||||
const incrementPostStatsView = `-- name: IncrementPostStatsView :exec
|
||||
WITH visitor AS (
|
||||
INSERT INTO post_viewers (
|
||||
post_id,
|
||||
ip,
|
||||
last_viewed_at
|
||||
)
|
||||
VALUES (
|
||||
$1,
|
||||
$2,
|
||||
NOW()
|
||||
)
|
||||
ON CONFLICT (post_id, ip)
|
||||
DO UPDATE
|
||||
SET last_viewed_at = NOW()
|
||||
WHERE post_viewers.last_viewed_at < NOW() - INTERVAL '30 minutes'
|
||||
RETURNING post_id
|
||||
)
|
||||
INSERT INTO post_stats (
|
||||
post_id,
|
||||
view
|
||||
)
|
||||
SELECT
|
||||
post_id,
|
||||
1
|
||||
FROM visitor
|
||||
ON CONFLICT (post_id)
|
||||
DO UPDATE
|
||||
SET view = post_stats.view + 1
|
||||
`
|
||||
|
||||
type IncrementPostStatsViewParams struct {
|
||||
PostID int32 `json:"post_id"`
|
||||
Ip netip.Addr `json:"ip"`
|
||||
}
|
||||
|
||||
func (q *Queries) IncrementPostStatsView(ctx context.Context, arg IncrementPostStatsViewParams) error {
|
||||
_, err := q.db.Exec(ctx, incrementPostStatsView, arg.PostID, arg.Ip)
|
||||
return err
|
||||
}
|
||||
259
internal/db/sqlc/sys_api.sql.go
Normal file
259
internal/db/sqlc/sys_api.sql.go
Normal file
@@ -0,0 +1,259 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: sys_api.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const countSysApis = `-- name: CountSysApis :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_apis
|
||||
`
|
||||
|
||||
func (q *Queries) CountSysApis(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countSysApis)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createSysApi = `-- name: CreateSysApi :one
|
||||
INSERT INTO sys_apis (name, group_name, method, path, sort)
|
||||
VALUES ($1, $2, $3, $4, $5)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type CreateSysApiParams struct {
|
||||
Name string `json:"name"`
|
||||
GroupName string `json:"group_name"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Sort *int32 `json:"sort"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysApi(ctx context.Context, arg CreateSysApiParams) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, createSysApi,
|
||||
arg.Name,
|
||||
arg.GroupName,
|
||||
arg.Method,
|
||||
arg.Path,
|
||||
arg.Sort,
|
||||
)
|
||||
var id int32
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const createSysApiPermission = `-- name: CreateSysApiPermission :exec
|
||||
INSERT INTO sys_api_permission (api_id, permission_id)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreateSysApiPermissionParams struct {
|
||||
ApiID int32 `json:"api_id"`
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysApiPermission(ctx context.Context, arg CreateSysApiPermissionParams) error {
|
||||
_, err := q.db.Exec(ctx, createSysApiPermission, arg.ApiID, arg.PermissionID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSysApi = `-- name: DeleteSysApi :execrows
|
||||
DELETE
|
||||
FROM sys_apis
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysApi(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deleteSysApi, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const deleteSysApiPermission = `-- name: DeleteSysApiPermission :exec
|
||||
DELETE
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysApiPermission(ctx context.Context, apiID int32) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysApiPermission, apiID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSysPermissionBySysApiID = `-- name: DeleteSysPermissionBySysApiID :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id IN (SELECT permission_id
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = $1)
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysPermissionBySysApiID(ctx context.Context, apiID int32) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysPermissionBySysApiID, apiID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllSysApis = `-- name: GetAllSysApis :many
|
||||
SELECT id, group_name, name, method, path, sort, created_at, updated_at
|
||||
FROM sys_apis
|
||||
ORDER BY sort ASC
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllSysApis(ctx context.Context) ([]SysApi, error) {
|
||||
rows, err := q.db.Query(ctx, getAllSysApis)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysApi{}
|
||||
for rows.Next() {
|
||||
var i SysApi
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GroupName,
|
||||
&i.Name,
|
||||
&i.Method,
|
||||
&i.Path,
|
||||
&i.Sort,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysApiByID = `-- name: GetSysApiByID :one
|
||||
SELECT id, group_name, name, method, path, sort, created_at, updated_at
|
||||
FROM sys_apis
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysApiByID(ctx context.Context, id int32) (SysApi, error) {
|
||||
row := q.db.QueryRow(ctx, getSysApiByID, id)
|
||||
var i SysApi
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.GroupName,
|
||||
&i.Name,
|
||||
&i.Method,
|
||||
&i.Path,
|
||||
&i.Sort,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSysApiGroupNames = `-- name: GetSysApiGroupNames :many
|
||||
SELECT DISTINCT group_name
|
||||
FROM sys_apis
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysApiGroupNames(ctx context.Context) ([]string, error) {
|
||||
rows, err := q.db.Query(ctx, getSysApiGroupNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []string{}
|
||||
for rows.Next() {
|
||||
var group_name string
|
||||
if err := rows.Scan(&group_name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, group_name)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysApis = `-- name: GetSysApis :many
|
||||
SELECT id, group_name, name, method, path, sort, created_at, updated_at
|
||||
FROM sys_apis
|
||||
ORDER BY id
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type GetSysApisParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysApis(ctx context.Context, arg GetSysApisParams) ([]SysApi, error) {
|
||||
rows, err := q.db.Query(ctx, getSysApis, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysApi{}
|
||||
for rows.Next() {
|
||||
var i SysApi
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.GroupName,
|
||||
&i.Name,
|
||||
&i.Method,
|
||||
&i.Path,
|
||||
&i.Sort,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateSysApi = `-- name: UpdateSysApi :execrows
|
||||
UPDATE sys_apis
|
||||
SET name = $2,
|
||||
method = $3,
|
||||
path = $4,
|
||||
sort = $5,
|
||||
group_name = $6
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateSysApiParams struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Method string `json:"method"`
|
||||
Path string `json:"path"`
|
||||
Sort *int32 `json:"sort"`
|
||||
GroupName string `json:"group_name"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysApi(ctx context.Context, arg UpdateSysApiParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateSysApi,
|
||||
arg.ID,
|
||||
arg.Name,
|
||||
arg.Method,
|
||||
arg.Path,
|
||||
arg.Sort,
|
||||
arg.GroupName,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
327
internal/db/sqlc/sys_menu.sql.go
Normal file
327
internal/db/sqlc/sys_menu.sql.go
Normal file
@@ -0,0 +1,327 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: sys_menu.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const countSysMenus = `-- name: CountSysMenus :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_menus
|
||||
`
|
||||
|
||||
func (q *Queries) CountSysMenus(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countSysMenus)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createSysMenu = `-- name: CreateSysMenu :one
|
||||
INSERT INTO sys_menus (name, path, component, type, hidden, sort, status, parent_id, icon)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)
|
||||
RETURNING id
|
||||
`
|
||||
|
||||
type CreateSysMenuParams struct {
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Component *string `json:"component"`
|
||||
Type int16 `json:"type"`
|
||||
Hidden *bool `json:"hidden"`
|
||||
Sort *int32 `json:"sort"`
|
||||
Status int16 `json:"status"`
|
||||
ParentID *int32 `json:"parent_id"`
|
||||
Icon *int32 `json:"icon"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysMenu(ctx context.Context, arg CreateSysMenuParams) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, createSysMenu,
|
||||
arg.Name,
|
||||
arg.Path,
|
||||
arg.Component,
|
||||
arg.Type,
|
||||
arg.Hidden,
|
||||
arg.Sort,
|
||||
arg.Status,
|
||||
arg.ParentID,
|
||||
arg.Icon,
|
||||
)
|
||||
var id int32
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const createSysMenuPermission = `-- name: CreateSysMenuPermission :exec
|
||||
INSERT INTO sys_menu_permission (menu_id, permission_id)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreateSysMenuPermissionParams struct {
|
||||
MenuID int32 `json:"menu_id"`
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysMenuPermission(ctx context.Context, arg CreateSysMenuPermissionParams) error {
|
||||
_, err := q.db.Exec(ctx, createSysMenuPermission, arg.MenuID, arg.PermissionID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSysMenu = `-- name: DeleteSysMenu :execrows
|
||||
DELETE
|
||||
FROM sys_menus
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysMenu(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deleteSysMenu, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const deleteSysMenuPermission = `-- name: DeleteSysMenuPermission :exec
|
||||
DELETE
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysMenuPermission(ctx context.Context, menuID int32) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysMenuPermission, menuID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSysPermissionByMenuID = `-- name: DeleteSysPermissionByMenuID :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id IN (SELECT permission_id
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = $1)
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysPermissionByMenuID(ctx context.Context, menuID int32) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysPermissionByMenuID, menuID)
|
||||
return err
|
||||
}
|
||||
|
||||
const getAllSysMenus = `-- name: GetAllSysMenus :many
|
||||
SELECT m.id, m.name, m.path, m.component, m.type, m.hidden, m.sort, m.status, m.parent_id, m.icon, m.created_at, m.updated_at,
|
||||
p.code AS permission_code
|
||||
FROM sys_menus m
|
||||
LEFT JOIN sys_menu_permission mp ON m.id = mp.menu_id
|
||||
LEFT JOIN sys_permissions p ON p.id = mp.permission_id
|
||||
ORDER BY m.sort ASC,
|
||||
m.id ASC
|
||||
`
|
||||
|
||||
type GetAllSysMenusRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Component *string `json:"component"`
|
||||
Type int16 `json:"type"`
|
||||
Hidden *bool `json:"hidden"`
|
||||
Sort *int32 `json:"sort"`
|
||||
Status int16 `json:"status"`
|
||||
ParentID *int32 `json:"parent_id"`
|
||||
Icon *int32 `json:"icon"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
PermissionCode *string `json:"permission_code"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetAllSysMenus(ctx context.Context) ([]GetAllSysMenusRow, error) {
|
||||
rows, err := q.db.Query(ctx, getAllSysMenus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetAllSysMenusRow{}
|
||||
for rows.Next() {
|
||||
var i GetAllSysMenusRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Path,
|
||||
&i.Component,
|
||||
&i.Type,
|
||||
&i.Hidden,
|
||||
&i.Sort,
|
||||
&i.Status,
|
||||
&i.ParentID,
|
||||
&i.Icon,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PermissionCode,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysMenuByID = `-- name: GetSysMenuByID :one
|
||||
SELECT id, name, path, component, type, hidden, sort, status, parent_id, icon, created_at, updated_at
|
||||
FROM sys_menus
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysMenuByID(ctx context.Context, id int32) (SysMenu, error) {
|
||||
row := q.db.QueryRow(ctx, getSysMenuByID, id)
|
||||
var i SysMenu
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Path,
|
||||
&i.Component,
|
||||
&i.Type,
|
||||
&i.Hidden,
|
||||
&i.Sort,
|
||||
&i.Status,
|
||||
&i.ParentID,
|
||||
&i.Icon,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const listSysMenus = `-- name: ListSysMenus :many
|
||||
SELECT m.id, m.name, m.path, m.component, m.type, m.hidden, m.sort, m.status, m.parent_id, m.icon, m.created_at, m.updated_at, p.code AS permission_code
|
||||
FROM sys_menus m
|
||||
LEFT JOIN sys_menu_permission mp ON m.id = mp.menu_id
|
||||
LEFT JOIN sys_permissions p ON mp.permission_id = p.id
|
||||
ORDER BY m.id
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListSysMenusParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type ListSysMenusRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Path string `json:"path"`
|
||||
Component *string `json:"component"`
|
||||
Type int16 `json:"type"`
|
||||
Hidden *bool `json:"hidden"`
|
||||
Sort *int32 `json:"sort"`
|
||||
Status int16 `json:"status"`
|
||||
ParentID *int32 `json:"parent_id"`
|
||||
Icon *int32 `json:"icon"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
PermissionCode *string `json:"permission_code"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSysMenus(ctx context.Context, arg ListSysMenusParams) ([]ListSysMenusRow, error) {
|
||||
rows, err := q.db.Query(ctx, listSysMenus, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListSysMenusRow{}
|
||||
for rows.Next() {
|
||||
var i ListSysMenusRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Path,
|
||||
&i.Component,
|
||||
&i.Type,
|
||||
&i.Hidden,
|
||||
&i.Sort,
|
||||
&i.Status,
|
||||
&i.ParentID,
|
||||
&i.Icon,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
&i.PermissionCode,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateSysMenu = `-- name: UpdateSysMenu :execrows
|
||||
UPDATE sys_menus
|
||||
SET name = coalesce($1, name),
|
||||
path = coalesce($2, path),
|
||||
component = coalesce($3, component),
|
||||
type = coalesce($4, type),
|
||||
hidden = coalesce($5, hidden),
|
||||
sort = coalesce($6, sort),
|
||||
status = coalesce($7, status),
|
||||
icon = coalesce($8, status),
|
||||
parent_id = CASE WHEN $9::boolean THEN $10 ELSE parent_id END
|
||||
WHERE id = $11
|
||||
`
|
||||
|
||||
type UpdateSysMenuParams struct {
|
||||
Name *string `json:"name"`
|
||||
Path *string `json:"path"`
|
||||
Component *string `json:"component"`
|
||||
Type *int16 `json:"type"`
|
||||
Hidden *bool `json:"hidden"`
|
||||
Sort *int32 `json:"sort"`
|
||||
Status *int16 `json:"status"`
|
||||
Icon *int32 `json:"icon"`
|
||||
UpdateParentID bool `json:"update_parent_id"`
|
||||
ParentID *int32 `json:"parent_id"`
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysMenu(ctx context.Context, arg UpdateSysMenuParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateSysMenu,
|
||||
arg.Name,
|
||||
arg.Path,
|
||||
arg.Component,
|
||||
arg.Type,
|
||||
arg.Hidden,
|
||||
arg.Sort,
|
||||
arg.Status,
|
||||
arg.Icon,
|
||||
arg.UpdateParentID,
|
||||
arg.ParentID,
|
||||
arg.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const updateSysMenuPermissionCode = `-- name: UpdateSysMenuPermissionCode :exec
|
||||
UPDATE sys_permissions p
|
||||
SET code = coalesce($2, code)
|
||||
FROM sys_menu_permission mp
|
||||
WHERE p.id = mp.permission_id
|
||||
AND mp.menu_id = $1
|
||||
`
|
||||
|
||||
type UpdateSysMenuPermissionCodeParams struct {
|
||||
MenuID int32 `json:"menu_id"`
|
||||
Code *string `json:"code"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysMenuPermissionCode(ctx context.Context, arg UpdateSysMenuPermissionCodeParams) error {
|
||||
_, err := q.db.Exec(ctx, updateSysMenuPermissionCode, arg.MenuID, arg.Code)
|
||||
return err
|
||||
}
|
||||
54
internal/db/sqlc/sys_permission.sql.go
Normal file
54
internal/db/sqlc/sys_permission.sql.go
Normal file
@@ -0,0 +1,54 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: sys_permission.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const createSysPermission = `-- name: CreateSysPermission :one
|
||||
INSERT INTO sys_permissions(type,code)
|
||||
VALUES ($1,$2) RETURNING id
|
||||
`
|
||||
|
||||
type CreateSysPermissionParams struct {
|
||||
Type int16 `json:"type"`
|
||||
Code *string `json:"code"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysPermission(ctx context.Context, arg CreateSysPermissionParams) (int32, error) {
|
||||
row := q.db.QueryRow(ctx, createSysPermission, arg.Type, arg.Code)
|
||||
var id int32
|
||||
err := row.Scan(&id)
|
||||
return id, err
|
||||
}
|
||||
|
||||
const deleteSysPermission = `-- name: DeleteSysPermission :exec
|
||||
DELETE
|
||||
FROM sys_permissions
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysPermission(ctx context.Context, id int32) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysPermission, id)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateSysPermission = `-- name: UpdateSysPermission :exec
|
||||
UPDATE sys_permissions
|
||||
SET code = $2
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateSysPermissionParams struct {
|
||||
ID int32 `json:"id"`
|
||||
Code *string `json:"code"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysPermission(ctx context.Context, arg UpdateSysPermissionParams) error {
|
||||
_, err := q.db.Exec(ctx, updateSysPermission, arg.ID, arg.Code)
|
||||
return err
|
||||
}
|
||||
321
internal/db/sqlc/sys_role.sql.go
Normal file
321
internal/db/sqlc/sys_role.sql.go
Normal file
@@ -0,0 +1,321 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: sys_role.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
const countSysRoles = `-- name: CountSysRoles :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_roles
|
||||
`
|
||||
|
||||
func (q *Queries) CountSysRoles(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countSysRoles)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createSysRole = `-- name: CreateSysRole :exec
|
||||
INSERT INTO sys_roles (name, code)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreateSysRoleParams struct {
|
||||
Name string `json:"name"`
|
||||
Code string `json:"code"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysRole(ctx context.Context, arg CreateSysRoleParams) error {
|
||||
_, err := q.db.Exec(ctx, createSysRole, arg.Name, arg.Code)
|
||||
return err
|
||||
}
|
||||
|
||||
type CreateSysRolePermissionParams struct {
|
||||
RoleID int32 `json:"role_id"`
|
||||
PermissionID int32 `json:"permission_id"`
|
||||
}
|
||||
|
||||
const deleteSysRole = `-- name: DeleteSysRole :execrows
|
||||
DELETE
|
||||
FROM sys_roles
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysRole(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deleteSysRole, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const deleteSysRolePermission = `-- name: DeleteSysRolePermission :exec
|
||||
DELETE
|
||||
FROM sys_role_permission rp USING sys_permissions p
|
||||
WHERE rp.permission_id = p.id
|
||||
AND p."type" = $2
|
||||
AND rp.role_id = $1
|
||||
`
|
||||
|
||||
type DeleteSysRolePermissionParams struct {
|
||||
RoleID int32 `json:"role_id"`
|
||||
Type int16 `json:"type"`
|
||||
}
|
||||
|
||||
func (q *Queries) DeleteSysRolePermission(ctx context.Context, arg DeleteSysRolePermissionParams) error {
|
||||
_, err := q.db.Exec(ctx, deleteSysRolePermission, arg.RoleID, arg.Type)
|
||||
return err
|
||||
}
|
||||
|
||||
const existsSysRoleCode = `-- name: ExistsSysRoleCode :one
|
||||
SELECT EXISTS(SELECT 1
|
||||
FROM sys_roles
|
||||
WHERE code = $1
|
||||
AND (
|
||||
$2::integer IS NULL
|
||||
OR id <> $2::integer
|
||||
))
|
||||
`
|
||||
|
||||
type ExistsSysRoleCodeParams struct {
|
||||
Code string `json:"code"`
|
||||
ID *int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) ExistsSysRoleCode(ctx context.Context, arg ExistsSysRoleCodeParams) (bool, error) {
|
||||
row := q.db.QueryRow(ctx, existsSysRoleCode, arg.Code, arg.ID)
|
||||
var exists bool
|
||||
err := row.Scan(&exists)
|
||||
return exists, err
|
||||
}
|
||||
|
||||
const getAllSysRoles = `-- name: GetAllSysRoles :many
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
from sys_roles
|
||||
`
|
||||
|
||||
func (q *Queries) GetAllSysRoles(ctx context.Context) ([]SysRole, error) {
|
||||
rows, err := q.db.Query(ctx, getAllSysRoles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysRole{}
|
||||
for rows.Next() {
|
||||
var i SysRole
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysPermissionIdsByApiIDs = `-- name: GetSysPermissionIdsByApiIDs :many
|
||||
SELECT permission_id
|
||||
FROM sys_api_permission
|
||||
WHERE api_id = ANY ($1::int[])
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysPermissionIdsByApiIDs(ctx context.Context, dollar_1 []int32) ([]int32, error) {
|
||||
rows, err := q.db.Query(ctx, getSysPermissionIdsByApiIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []int32{}
|
||||
for rows.Next() {
|
||||
var permission_id int32
|
||||
if err := rows.Scan(&permission_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, permission_id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysPermissionIdsByMenuIDs = `-- name: GetSysPermissionIdsByMenuIDs :many
|
||||
SELECT permission_id
|
||||
FROM sys_menu_permission
|
||||
WHERE menu_id = ANY ($1::int[])
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysPermissionIdsByMenuIDs(ctx context.Context, dollar_1 []int32) ([]int32, error) {
|
||||
rows, err := q.db.Query(ctx, getSysPermissionIdsByMenuIDs, dollar_1)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []int32{}
|
||||
for rows.Next() {
|
||||
var permission_id int32
|
||||
if err := rows.Scan(&permission_id); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, permission_id)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysRoleApis = `-- name: GetSysRoleApis :many
|
||||
SELECT a.id, a."name"
|
||||
FROM sys_role_permission rp
|
||||
JOIN sys_api_permission ap ON ap.permission_id = rp.permission_id
|
||||
JOIN sys_apis a ON a.id = ap.api_id
|
||||
WHERE rp.role_id = $1
|
||||
`
|
||||
|
||||
type GetSysRoleApisRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysRoleApis(ctx context.Context, roleID int32) ([]GetSysRoleApisRow, error) {
|
||||
rows, err := q.db.Query(ctx, getSysRoleApis, roleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetSysRoleApisRow{}
|
||||
for rows.Next() {
|
||||
var i GetSysRoleApisRow
|
||||
if err := rows.Scan(&i.ID, &i.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysRoleByID = `-- name: GetSysRoleByID :one
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
FROM sys_roles
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysRoleByID(ctx context.Context, id int32) (SysRole, error) {
|
||||
row := q.db.QueryRow(ctx, getSysRoleByID, id)
|
||||
var i SysRole
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSysRoleMenus = `-- name: GetSysRoleMenus :many
|
||||
SELECT m.id, m.name
|
||||
FROM sys_role_permission rp
|
||||
JOIN sys_menu_permission mp ON mp.permission_id = rp.permission_id
|
||||
JOIN sys_menus m ON m.id = mp.menu_id
|
||||
WHERE rp.role_id = $1
|
||||
`
|
||||
|
||||
type GetSysRoleMenusRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysRoleMenus(ctx context.Context, roleID int32) ([]GetSysRoleMenusRow, error) {
|
||||
rows, err := q.db.Query(ctx, getSysRoleMenus, roleID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetSysRoleMenusRow{}
|
||||
for rows.Next() {
|
||||
var i GetSysRoleMenusRow
|
||||
if err := rows.Scan(&i.ID, &i.Name); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listSysRoles = `-- name: ListSysRoles :many
|
||||
SELECT id, name, code, created_at, updated_at
|
||||
FROM sys_roles
|
||||
ORDER BY id
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListSysRolesParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSysRoles(ctx context.Context, arg ListSysRolesParams) ([]SysRole, error) {
|
||||
rows, err := q.db.Query(ctx, listSysRoles, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysRole{}
|
||||
for rows.Next() {
|
||||
var i SysRole
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateSysRole = `-- name: UpdateSysRole :execrows
|
||||
UPDATE sys_roles
|
||||
SET name = coalesce($1, name)
|
||||
WHERE id = $2
|
||||
`
|
||||
|
||||
type UpdateSysRoleParams struct {
|
||||
Name *string `json:"name"`
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysRole(ctx context.Context, arg UpdateSysRoleParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateSysRole, arg.Name, arg.ID)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
426
internal/db/sqlc/sys_user.sql.go
Normal file
426
internal/db/sqlc/sys_user.sql.go
Normal file
@@ -0,0 +1,426 @@
|
||||
// Code generated by sqlc. DO NOT EDIT.
|
||||
// versions:
|
||||
// sqlc v1.31.1
|
||||
// source: sys_user.sql
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
)
|
||||
|
||||
const clearSysUserRoles = `-- name: ClearSysUserRoles :exec
|
||||
DELETE
|
||||
FROM sys_user_role
|
||||
WHERE user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) ClearSysUserRoles(ctx context.Context, userID int32) error {
|
||||
_, err := q.db.Exec(ctx, clearSysUserRoles, userID)
|
||||
return err
|
||||
}
|
||||
|
||||
const countSysUsers = `-- name: CountSysUsers :one
|
||||
SELECT COUNT(*)
|
||||
FROM sys_users
|
||||
`
|
||||
|
||||
func (q *Queries) CountSysUsers(ctx context.Context) (int64, error) {
|
||||
row := q.db.QueryRow(ctx, countSysUsers)
|
||||
var count int64
|
||||
err := row.Scan(&count)
|
||||
return count, err
|
||||
}
|
||||
|
||||
const createSysUser = `-- name: CreateSysUser :exec
|
||||
INSERT INTO sys_users (account, username, password_hash, avatar_id)
|
||||
VALUES ($1, $2, $3, $4)
|
||||
`
|
||||
|
||||
type CreateSysUserParams struct {
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysUser(ctx context.Context, arg CreateSysUserParams) error {
|
||||
_, err := q.db.Exec(ctx, createSysUser,
|
||||
arg.Account,
|
||||
arg.Username,
|
||||
arg.PasswordHash,
|
||||
arg.AvatarID,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
const createSysUserRole = `-- name: CreateSysUserRole :exec
|
||||
INSERT INTO sys_user_role (user_id, role_id)
|
||||
VALUES ($1, $2)
|
||||
`
|
||||
|
||||
type CreateSysUserRoleParams struct {
|
||||
UserID int32 `json:"user_id"`
|
||||
RoleID int32 `json:"role_id"`
|
||||
}
|
||||
|
||||
func (q *Queries) CreateSysUserRole(ctx context.Context, arg CreateSysUserRoleParams) error {
|
||||
_, err := q.db.Exec(ctx, createSysUserRole, arg.UserID, arg.RoleID)
|
||||
return err
|
||||
}
|
||||
|
||||
const deleteSysUser = `-- name: DeleteSysUser :execrows
|
||||
DELETE
|
||||
FROM sys_users
|
||||
WHERE id = $1
|
||||
AND id <> 1
|
||||
`
|
||||
|
||||
func (q *Queries) DeleteSysUser(ctx context.Context, id int32) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, deleteSysUser, id)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const getSysAdminMenus = `-- name: GetSysAdminMenus :many
|
||||
SELECT id, name, path, component, type, hidden, sort, status, parent_id, icon, created_at, updated_at
|
||||
FROM sys_menus
|
||||
ORDER BY sort, id
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysAdminMenus(ctx context.Context) ([]SysMenu, error) {
|
||||
rows, err := q.db.Query(ctx, getSysAdminMenus)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysMenu{}
|
||||
for rows.Next() {
|
||||
var i SysMenu
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Path,
|
||||
&i.Component,
|
||||
&i.Type,
|
||||
&i.Hidden,
|
||||
&i.Sort,
|
||||
&i.Status,
|
||||
&i.ParentID,
|
||||
&i.Icon,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysUserApis = `-- name: GetSysUserApis :many
|
||||
SELECT DISTINCT a.id,
|
||||
a.PATH,
|
||||
a.METHOD
|
||||
FROM sys_users u
|
||||
JOIN sys_user_role ur ON u.id = ur.user_id
|
||||
JOIN sys_role_permission rp ON rp.role_id = ur.role_id
|
||||
JOIN sys_permissions p ON p.id = rp.permission_id
|
||||
AND p.TYPE = 1
|
||||
JOIN sys_api_permission ap ON ap.permission_id = p.id
|
||||
JOIN sys_apis a ON a.id = ap.api_id
|
||||
WHERE u.id = $1
|
||||
`
|
||||
|
||||
type GetSysUserApisRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Path string `json:"path"`
|
||||
Method string `json:"method"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysUserApis(ctx context.Context, id int32) ([]GetSysUserApisRow, error) {
|
||||
rows, err := q.db.Query(ctx, getSysUserApis, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []GetSysUserApisRow{}
|
||||
for rows.Next() {
|
||||
var i GetSysUserApisRow
|
||||
if err := rows.Scan(&i.ID, &i.Path, &i.Method); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysUserByAccount = `-- name: GetSysUserByAccount :one
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.password_hash,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
WHERE u.account = $1
|
||||
`
|
||||
|
||||
type GetSysUserByAccountRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
AvatarUrl *string `json:"avatar_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysUserByAccount(ctx context.Context, account string) (GetSysUserByAccountRow, error) {
|
||||
row := q.db.QueryRow(ctx, getSysUserByAccount, account)
|
||||
var i GetSysUserByAccountRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Account,
|
||||
&i.Username,
|
||||
&i.PasswordHash,
|
||||
&i.AvatarID,
|
||||
&i.AvatarUrl,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSysUserByID = `-- name: GetSysUserByID :one
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
WHERE u.id = $1
|
||||
`
|
||||
|
||||
type GetSysUserByIDRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
AvatarUrl *string `json:"avatar_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) GetSysUserByID(ctx context.Context, id int32) (GetSysUserByIDRow, error) {
|
||||
row := q.db.QueryRow(ctx, getSysUserByID, id)
|
||||
var i GetSysUserByIDRow
|
||||
err := row.Scan(
|
||||
&i.ID,
|
||||
&i.Account,
|
||||
&i.Username,
|
||||
&i.AvatarID,
|
||||
&i.AvatarUrl,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
)
|
||||
return i, err
|
||||
}
|
||||
|
||||
const getSysUserMenus = `-- name: GetSysUserMenus :many
|
||||
SELECT DISTINCT m.id, m.name, m.path, m.component, m.type, m.hidden, m.sort, m.status, m.parent_id, m.icon, m.created_at, m.updated_at
|
||||
FROM sys_users u
|
||||
JOIN sys_user_role ur ON ur.user_id = u.id
|
||||
JOIN sys_roles r ON r.id = ur.role_id
|
||||
JOIN sys_role_permission rp ON rp.role_id = r.id
|
||||
JOIN sys_permissions p ON p.id = rp.permission_id
|
||||
JOIN sys_menu_permission mp ON mp.permission_id = p.id
|
||||
JOIN sys_menus m ON m.id = mp.menu_id
|
||||
WHERE u.id = $1
|
||||
ORDER BY m.sort, m.id
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysUserMenus(ctx context.Context, id int32) ([]SysMenu, error) {
|
||||
rows, err := q.db.Query(ctx, getSysUserMenus, id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysMenu{}
|
||||
for rows.Next() {
|
||||
var i SysMenu
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Path,
|
||||
&i.Component,
|
||||
&i.Type,
|
||||
&i.Hidden,
|
||||
&i.Sort,
|
||||
&i.Status,
|
||||
&i.ParentID,
|
||||
&i.Icon,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const getSysUserRoles = `-- name: GetSysUserRoles :many
|
||||
SELECT r.id, r.name, r.code, r.created_at, r.updated_at
|
||||
FROM sys_roles r
|
||||
JOIN sys_user_role ur ON r.id = ur.role_id
|
||||
WHERE ur.user_id = $1
|
||||
`
|
||||
|
||||
func (q *Queries) GetSysUserRoles(ctx context.Context, userID int32) ([]SysRole, error) {
|
||||
rows, err := q.db.Query(ctx, getSysUserRoles, userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []SysRole{}
|
||||
for rows.Next() {
|
||||
var i SysRole
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Name,
|
||||
&i.Code,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const listSysUsers = `-- name: ListSysUsers :many
|
||||
SELECT u.id,
|
||||
u.account,
|
||||
u.username,
|
||||
u.avatar_id,
|
||||
f.file_path AS avatar_url,
|
||||
u.created_at,
|
||||
u.updated_at
|
||||
FROM sys_users u
|
||||
LEFT JOIN files f ON u.avatar_id = f.id
|
||||
ORDER BY u.id
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
type ListSysUsersParams struct {
|
||||
Limit int32 `json:"limit"`
|
||||
Offset int32 `json:"offset"`
|
||||
}
|
||||
|
||||
type ListSysUsersRow struct {
|
||||
ID int32 `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
AvatarUrl *string `json:"avatar_url"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt *time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (q *Queries) ListSysUsers(ctx context.Context, arg ListSysUsersParams) ([]ListSysUsersRow, error) {
|
||||
rows, err := q.db.Query(ctx, listSysUsers, arg.Limit, arg.Offset)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
items := []ListSysUsersRow{}
|
||||
for rows.Next() {
|
||||
var i ListSysUsersRow
|
||||
if err := rows.Scan(
|
||||
&i.ID,
|
||||
&i.Account,
|
||||
&i.Username,
|
||||
&i.AvatarID,
|
||||
&i.AvatarUrl,
|
||||
&i.CreatedAt,
|
||||
&i.UpdatedAt,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items = append(items, i)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
|
||||
const updateSysUser = `-- name: UpdateSysUser :execrows
|
||||
UPDATE sys_users
|
||||
SET username = coalesce($1, username),
|
||||
avatar_id = CASE WHEN $2::boolean THEN $3 ELSE avatar_id END
|
||||
WHERE id = $4
|
||||
`
|
||||
|
||||
type UpdateSysUserParams struct {
|
||||
Username *string `json:"username"`
|
||||
UpdateAvatarID bool `json:"update_avatar_id"`
|
||||
AvatarID *int32 `json:"avatar_id"`
|
||||
ID int32 `json:"id"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysUser(ctx context.Context, arg UpdateSysUserParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateSysUser,
|
||||
arg.Username,
|
||||
arg.UpdateAvatarID,
|
||||
arg.AvatarID,
|
||||
arg.ID,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
|
||||
const updateSysUserPassword = `-- name: UpdateSysUserPassword :execrows
|
||||
UPDATE sys_users
|
||||
SET password_hash = $2
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
type UpdateSysUserPasswordParams struct {
|
||||
ID int32 `json:"id"`
|
||||
PasswordHash string `json:"password_hash"`
|
||||
}
|
||||
|
||||
func (q *Queries) UpdateSysUserPassword(ctx context.Context, arg UpdateSysUserPasswordParams) (int64, error) {
|
||||
result, err := q.db.Exec(ctx, updateSysUserPassword, arg.ID, arg.PasswordHash)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected(), nil
|
||||
}
|
||||
96
internal/handler/category.go
Normal file
96
internal/handler/category.go
Normal file
@@ -0,0 +1,96 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type CategoryHandler struct {
|
||||
categoryService *service.CategoryService
|
||||
}
|
||||
|
||||
func NewCategoryHandler(categoryService *service.CategoryService) *CategoryHandler {
|
||||
return &CategoryHandler{categoryService: categoryService}
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
list, total, err := h.categoryService.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) ListAll(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.categoryService.ListAll(r.Context())
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreateCategoryRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.categoryService.Create(r.Context(), req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req request.UpdateCategoryRequest
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.categoryService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *CategoryHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.categoryService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
20
internal/handler/module.go
Normal file
20
internal/handler/module.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var Module = fx.Module("handlers",
|
||||
fx.Provide(
|
||||
NewSysUserHandler,
|
||||
NewSysRoleHandler,
|
||||
NewSysMenuHandler,
|
||||
NewSysApiHandler,
|
||||
NewSysFileHandler,
|
||||
NewSysPostHandler,
|
||||
NewCategoryHandler,
|
||||
|
||||
NewPostHandler,
|
||||
NewWebHandler,
|
||||
),
|
||||
)
|
||||
83
internal/handler/post.go
Normal file
83
internal/handler/post.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/netip"
|
||||
"server/internal/model/common"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
"server/internal/utils"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
type PostHandler struct {
|
||||
postService *service.PostService
|
||||
}
|
||||
|
||||
func NewPostHandler(postService *service.PostService) *PostHandler {
|
||||
return &PostHandler{postService: postService}
|
||||
}
|
||||
|
||||
func (h *PostHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.postService.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *PostHandler) GetPost(w http.ResponseWriter, r *http.Request) {
|
||||
slug := chi.URLParam(r, "slug")
|
||||
|
||||
if slug == "" {
|
||||
httputil.Fail(w, errs.ErrSlugRequired)
|
||||
return
|
||||
}
|
||||
|
||||
ip, err := netip.ParseAddr(utils.ClientIP(r))
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := h.postService.GetPost(r.Context(), slug, ip)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, post)
|
||||
}
|
||||
|
||||
func (h *PostHandler) ListCategoryStats(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.postService.ListCategoryStats(r.Context())
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *PostHandler) ListArchives(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.postService.ListArchives(r.Context())
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
112
internal/handler/sys_api.go
Normal file
112
internal/handler/sys_api.go
Normal file
@@ -0,0 +1,112 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type SysApiHandler struct {
|
||||
apiService *service.SysApiService
|
||||
}
|
||||
|
||||
func NewSysApiHandler(apiService *service.SysApiService) *SysApiHandler {
|
||||
return &SysApiHandler{apiService: apiService}
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.apiService.ListPage(r.Context(), pagination)
|
||||
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) GetAllSysApis(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.apiService.GetAllSysApis(r.Context())
|
||||
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) GetApiGroupNames(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.apiService.GetApiGroupNames(r.Context())
|
||||
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreateSysApiRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.apiService.Create(r.Context(), req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.UpdateSysApiRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.apiService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysApiHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.apiService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
58
internal/handler/sys_file.go
Normal file
58
internal/handler/sys_file.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type SysFileHandler struct {
|
||||
service *service.SysFileService
|
||||
}
|
||||
|
||||
func NewSysFileHandler(service *service.SysFileService) *SysFileHandler {
|
||||
return &SysFileHandler{
|
||||
service: service,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SysFileHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.service.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysFileHandler) Upload(w http.ResponseWriter, r *http.Request) {
|
||||
folder := r.FormValue("folder")
|
||||
if folder == "" {
|
||||
folder = "/"
|
||||
}
|
||||
|
||||
_, header, err := r.FormFile("file")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
file, err := h.service.Upload(r.Context(), folder, header)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, file)
|
||||
}
|
||||
100
internal/handler/sys_menu.go
Normal file
100
internal/handler/sys_menu.go
Normal file
@@ -0,0 +1,100 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type SysMenuHandler struct {
|
||||
menuService *service.SysMenuService
|
||||
}
|
||||
|
||||
func NewSysMenuHandler(menuService *service.SysMenuService) *SysMenuHandler {
|
||||
return &SysMenuHandler{menuService: menuService}
|
||||
}
|
||||
|
||||
func (h *SysMenuHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.menuService.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysMenuHandler) GetMenus(w http.ResponseWriter, r *http.Request) {
|
||||
list, err := h.menuService.GetMenus(r.Context())
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysMenuHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreateSysMenuRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.menuService.Create(r.Context(), req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysMenuHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.UpdateSysMenuRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.menuService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysMenuHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.menuService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
|
||||
}
|
||||
108
internal/handler/sys_post.go
Normal file
108
internal/handler/sys_post.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type SysPostHandler struct {
|
||||
sysPostService *service.SysPostService
|
||||
}
|
||||
|
||||
func NewSysPostHandler(postService *service.SysPostService) *SysPostHandler {
|
||||
return &SysPostHandler{sysPostService: postService}
|
||||
}
|
||||
|
||||
func (h *SysPostHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.sysPostService.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysPostHandler) GetPostById(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
post, err := h.sysPostService.FindByID(r.Context(), id)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, post)
|
||||
}
|
||||
|
||||
func (h *SysPostHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreatePostRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
postID, err := h.sysPostService.Create(r.Context(), req)
|
||||
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, map[string]int32{
|
||||
"post_id": postID,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *SysPostHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
var req request.UpdatePostRequest
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.sysPostService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysPostHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.sysPostService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
177
internal/handler/sys_role.go
Normal file
177
internal/handler/sys_role.go
Normal file
@@ -0,0 +1,177 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
)
|
||||
|
||||
type SysRoleHandler struct {
|
||||
roleService *service.SysRoleService
|
||||
}
|
||||
|
||||
func NewSysRoleHandler(roleService *service.SysRoleService) *SysRoleHandler {
|
||||
return &SysRoleHandler{roleService: roleService}
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.roleService.ListPage(r.Context(), pagination)
|
||||
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) GetRoleMenus(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.roleService.GetRoleMenus(r.Context(), id)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) GetRoleApis(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.roleService.GetRoleApis(r.Context(), id)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) GetRoles(w http.ResponseWriter, r *http.Request) {
|
||||
roles, err := h.roleService.GetRoles(r.Context())
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, roles)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreateSysRoleRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.roleService.Create(r.Context(), req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.UpdateSysRoleRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.roleService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) SetRoleMenus(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.SetSysRoleMenusRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.roleService.SetRoleMenus(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) SetRoleApis(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.SetSysRoleApisRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.roleService.SetRoleApis(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
|
||||
}
|
||||
|
||||
func (h *SysRoleHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.roleService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
239
internal/handler/sys_user.go
Normal file
239
internal/handler/sys_user.go
Normal file
@@ -0,0 +1,239 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/middleware"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/service"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SysUserHandler struct {
|
||||
userService *service.SysUserService
|
||||
}
|
||||
|
||||
func NewSysUserHandler(userService *service.SysUserService) *SysUserHandler {
|
||||
return &SysUserHandler{
|
||||
userService: userService,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) Login(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.LoginRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, errs.ErrInvalidCredentials)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.userService.Login(r.Context(), req)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: res.RefreshToken,
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
Expires: res.RefreshTokenExp,
|
||||
})
|
||||
|
||||
httputil.Ok(w, map[string]any{
|
||||
"access_token": res.AccessToken,
|
||||
"access_token_exp": res.AccessTokenExp,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) Logout(w http.ResponseWriter, r *http.Request) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: "refresh_token",
|
||||
Value: "",
|
||||
Path: "/",
|
||||
HttpOnly: true,
|
||||
Secure: true,
|
||||
SameSite: http.SameSiteNoneMode,
|
||||
MaxAge: -1,
|
||||
Expires: time.Unix(0, 0),
|
||||
})
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) RefreshToken(w http.ResponseWriter, r *http.Request) {
|
||||
cookie, err := r.Cookie("refresh_token")
|
||||
if err != nil {
|
||||
httputil.Fail(w, errs.ErrInvalidRefreshToken)
|
||||
return
|
||||
}
|
||||
|
||||
res, err := h.userService.RefreshToken(r.Context(), cookie.Value)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, map[string]any{
|
||||
"access_token": res.AccessToken,
|
||||
"access_token_exp": res.AccessTokenExp,
|
||||
})
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) ListPage(w http.ResponseWriter, r *http.Request) {
|
||||
pagination := httputil.Pagination(r)
|
||||
|
||||
list, total, err := h.userService.ListPage(r.Context(), pagination)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
resp := common.PageResponse{
|
||||
Page: pagination.Page,
|
||||
PageSize: pagination.PageSize,
|
||||
List: list,
|
||||
Total: total,
|
||||
}
|
||||
|
||||
httputil.OkWithPage(w, &resp)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) GetUserInfo(w http.ResponseWriter, r *http.Request) {
|
||||
claims, ok := middleware.GetClaims(r.Context())
|
||||
|
||||
if !ok || claims.UserID == 0 {
|
||||
httputil.Fail(w, errs.ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
isAdmin, _ := r.Context().Value(middleware.IsAdminKey).(bool)
|
||||
uid := claims.UserID
|
||||
|
||||
user, err := h.userService.GetUserInfo(r.Context(), uid, isAdmin)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, user)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) GetRoles(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
list, err := h.userService.GetRoles(r.Context(), id)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w, list)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) Create(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.CreateSysUserRequest
|
||||
|
||||
if err := httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := h.userService.Create(r.Context(), req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) Update(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.UpdateSysUserRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.userService.Update(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) SetRoles(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.SetSysUserRolesRequest
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.userService.SetRoles(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) UpdatePassword(w http.ResponseWriter, r *http.Request) {
|
||||
var req request.UpdateSysUserPassword
|
||||
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = httputil.BindJson(r, &req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.userService.UpdatePassword(r.Context(), id, req); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
|
||||
func (h *SysUserHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := httputil.URLParamInt32(r, "id")
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = h.userService.Delete(r.Context(), id); err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
httputil.Ok(w)
|
||||
}
|
||||
30
internal/handler/web.go
Normal file
30
internal/handler/web.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"server/internal/pkg/httputil"
|
||||
"server/internal/utils"
|
||||
)
|
||||
|
||||
type WebHandler struct {
|
||||
}
|
||||
|
||||
func NewWebHandler() *WebHandler {
|
||||
return &WebHandler{}
|
||||
}
|
||||
|
||||
func (h *WebHandler) GetClientInfo(w http.ResponseWriter, r *http.Request) {
|
||||
ip := utils.ClientIP(r)
|
||||
userAgent := r.UserAgent()
|
||||
|
||||
httputil.Ok(w, map[string]any{
|
||||
"ip": ip,
|
||||
"userAgent": userAgent,
|
||||
"method": r.Method,
|
||||
"host": r.Host,
|
||||
"path": r.URL.Path,
|
||||
"origin": r.Header.Get("Origin"),
|
||||
"accept": r.Header.Get("Accept"),
|
||||
"acceptLanguage": r.Header.Get("Accept-Language"),
|
||||
})
|
||||
}
|
||||
101
internal/middleware/auth.go
Normal file
101
internal/middleware/auth.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/pkg/cache"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/httputil"
|
||||
"strings"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
const (
|
||||
IsAdminKey contextKey = "is_admin"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
queries *db.Queries
|
||||
cache *cache.Caches
|
||||
}
|
||||
|
||||
func NewAuthMiddleware(queries *db.Queries, cache *cache.Caches) *AuthMiddleware {
|
||||
return &AuthMiddleware{
|
||||
cache: cache,
|
||||
queries: queries,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *AuthMiddleware) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
ctx := r.Context()
|
||||
|
||||
claims, ok := GetClaims(ctx)
|
||||
if !ok || claims.UserID == 0 {
|
||||
httputil.Fail(w, errs.ErrUnauthorized)
|
||||
return
|
||||
}
|
||||
|
||||
// 判断是否有管理员权限 目前只判断uid是否为1
|
||||
isAdmin := userIsAdmin(claims.UserID)
|
||||
|
||||
if isAdmin {
|
||||
ctx = context.WithValue(ctx, IsAdminKey, isAdmin)
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
return
|
||||
}
|
||||
|
||||
hasPermission, err := userHasApiPermission(ctx, r, m.queries, claims.UserID, m.cache)
|
||||
if err != nil {
|
||||
httputil.Fail(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
if !hasPermission {
|
||||
httputil.Fail(w, errs.ErrPermissionDenied)
|
||||
return
|
||||
}
|
||||
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func userIsAdmin(uid int32) bool {
|
||||
if uid == 1 {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func userHasApiPermission(ctx context.Context, r *http.Request, queries *db.Queries, uid int32, cache *cache.Caches) (bool, error) {
|
||||
var (
|
||||
apis []db.GetSysUserApisRow
|
||||
err error
|
||||
)
|
||||
|
||||
apis, ok := cache.SysUserApisCache.GetIfPresent(uid)
|
||||
|
||||
if !ok {
|
||||
apis, err = queries.GetSysUserApis(ctx, uid)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
cache.SysUserApisCache.Set(uid, apis)
|
||||
}
|
||||
|
||||
requestPath := chi.RouteContext(r.Context()).RoutePattern()
|
||||
requestPath = strings.TrimPrefix(requestPath, "/api")
|
||||
requestMethod := r.Method
|
||||
|
||||
for _, api := range apis {
|
||||
if api.Path == requestPath && api.Method == requestMethod {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
184
internal/middleware/jwt.go
Normal file
184
internal/middleware/jwt.go
Normal file
@@ -0,0 +1,184 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"server/internal/config"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/httputil"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/golang-jwt/jwt/v5"
|
||||
)
|
||||
|
||||
type JWTMiddleware struct {
|
||||
cfg *config.JWTConfig
|
||||
}
|
||||
|
||||
type contextKey string
|
||||
|
||||
const UserContextKey contextKey = "user"
|
||||
|
||||
const RefreshTokenType = "refresh"
|
||||
|
||||
type Claims struct {
|
||||
UserID int32 `json:"user_id"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
type RefreshClaims struct {
|
||||
UserID int32 `json:"user_id"`
|
||||
Type string `json:"type"`
|
||||
jwt.RegisteredClaims
|
||||
}
|
||||
|
||||
func NewJWTMiddleware(cfg *config.Config) *JWTMiddleware {
|
||||
jwtCfg := cfg.JWTConfig
|
||||
|
||||
return &JWTMiddleware{cfg: &jwtCfg}
|
||||
}
|
||||
|
||||
func GetClaims(ctx context.Context) (*Claims, bool) {
|
||||
claims, ok := ctx.Value(UserContextKey).(*Claims)
|
||||
return claims, ok
|
||||
}
|
||||
|
||||
// ParseToken 解析accessToken
|
||||
func (m *JWTMiddleware) ParseToken(tokenStr string) (*Claims, error) {
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tokenStr,
|
||||
&Claims{},
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.cfg.Secret), nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil || !token.Valid {
|
||||
return nil, errs.ErrInvalidToken
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*Claims)
|
||||
if !ok {
|
||||
return nil, errs.ErrInvalidTokenClaims
|
||||
}
|
||||
|
||||
if claims.UserID == 0 {
|
||||
return nil, errs.ErrInvalidTokenClaims
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
// ParseRefreshToken 解析RefreshToken
|
||||
func (m *JWTMiddleware) ParseRefreshToken(tokenStr string) (*RefreshClaims, error) {
|
||||
token, err := jwt.ParseWithClaims(
|
||||
tokenStr,
|
||||
&RefreshClaims{},
|
||||
func(token *jwt.Token) (interface{}, error) {
|
||||
return []byte(m.cfg.Secret), nil
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
// Token 已过期
|
||||
if errors.Is(err, jwt.ErrTokenExpired) {
|
||||
return nil, errs.ErrExpiredRefreshToken
|
||||
}
|
||||
|
||||
// 签名错误、格式错误、非法 Token
|
||||
return nil, errs.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
if !token.Valid {
|
||||
return nil, errs.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(*RefreshClaims)
|
||||
if !ok {
|
||||
return nil, errs.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
if claims.UserID == 0 {
|
||||
return nil, errs.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
if claims.Type != RefreshTokenType {
|
||||
return nil, errs.ErrInvalidRefreshToken
|
||||
}
|
||||
|
||||
return claims, nil
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
|
||||
if auth == "" {
|
||||
httputil.Fail(w, errs.ErrUnauthenticated)
|
||||
return
|
||||
}
|
||||
|
||||
parts := strings.SplitN(auth, " ", 2)
|
||||
if len(parts) != 2 || parts[0] != "Bearer" {
|
||||
httputil.Fail(w, errs.ErrUnauthenticated)
|
||||
return
|
||||
}
|
||||
|
||||
tokenStr := parts[1]
|
||||
|
||||
claims, err := m.ParseToken(tokenStr)
|
||||
if err != nil {
|
||||
httputil.Fail(w, errs.ErrInvalidToken)
|
||||
return
|
||||
}
|
||||
|
||||
ctx := context.WithValue(r.Context(), UserContextKey, claims)
|
||||
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) GenerateAccessToken(userID int32) (string, time.Time, error) {
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(m.cfg.Expire)
|
||||
|
||||
accessClaims := Claims{
|
||||
UserID: userID,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
},
|
||||
}
|
||||
|
||||
accessToken := jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims)
|
||||
token, err := accessToken.SignedString([]byte(m.cfg.Secret))
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
|
||||
func (m *JWTMiddleware) GenerateRefreshToken(userID int32) (string, time.Time, error) {
|
||||
now := time.Now()
|
||||
expiresAt := now.Add(m.cfg.RefreshExpire)
|
||||
|
||||
refreshClaims := RefreshClaims{
|
||||
UserID: userID,
|
||||
Type: RefreshTokenType,
|
||||
RegisteredClaims: jwt.RegisteredClaims{
|
||||
ExpiresAt: jwt.NewNumericDate(expiresAt),
|
||||
IssuedAt: jwt.NewNumericDate(now),
|
||||
},
|
||||
}
|
||||
|
||||
refreshToken := jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims)
|
||||
token, err := refreshToken.SignedString([]byte(m.cfg.Secret))
|
||||
if err != nil {
|
||||
return "", time.Time{}, err
|
||||
}
|
||||
|
||||
return token, expiresAt, nil
|
||||
}
|
||||
103
internal/middleware/logger.go
Normal file
103
internal/middleware/logger.go
Normal file
@@ -0,0 +1,103 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/utils"
|
||||
"time"
|
||||
|
||||
gonanoid "github.com/matoous/go-nanoid/v2"
|
||||
)
|
||||
|
||||
type LoggerMiddleware struct {
|
||||
}
|
||||
|
||||
type responseWriter struct {
|
||||
http.ResponseWriter
|
||||
statusCode int
|
||||
bytes int
|
||||
errorMsg string
|
||||
}
|
||||
|
||||
func (rw *responseWriter) WriteHeader(code int) {
|
||||
rw.statusCode = code
|
||||
rw.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func generateRequestID() string {
|
||||
id, _ := gonanoid.New(16) // 16字符
|
||||
return id
|
||||
}
|
||||
|
||||
// extractErrorMessage 从响应体中提取错误信息
|
||||
func (rw *responseWriter) extractErrorMessage(body []byte) string {
|
||||
var resp common.Response
|
||||
if err := json.Unmarshal(body, &resp); err == nil && resp.Message != "" {
|
||||
return resp.Message
|
||||
}
|
||||
return string(body)
|
||||
}
|
||||
|
||||
func (rw *responseWriter) Write(b []byte) (int, error) {
|
||||
n, err := rw.ResponseWriter.Write(b)
|
||||
rw.bytes += n
|
||||
|
||||
// 只在错误状态码且未记录错误时处理
|
||||
if rw.statusCode >= 400 && rw.errorMsg == "" && n > 0 {
|
||||
rw.errorMsg = rw.extractErrorMessage(b[:n])
|
||||
}
|
||||
|
||||
return n, err
|
||||
}
|
||||
|
||||
func NewLoggerMiddleware() *LoggerMiddleware {
|
||||
return &LoggerMiddleware{}
|
||||
}
|
||||
|
||||
func (m *LoggerMiddleware) Middleware(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
|
||||
reqID := generateRequestID()
|
||||
|
||||
wrapped := &responseWriter{
|
||||
ResponseWriter: w,
|
||||
statusCode: http.StatusOK,
|
||||
}
|
||||
|
||||
next.ServeHTTP(wrapped, r)
|
||||
|
||||
duration := time.Since(start)
|
||||
|
||||
ip := utils.ClientIP(r)
|
||||
|
||||
fullPath := r.URL.Path
|
||||
if r.URL.RawQuery != "" {
|
||||
fullPath = fullPath + "?" + r.URL.RawQuery
|
||||
}
|
||||
|
||||
fields := []any{
|
||||
"request_id", reqID,
|
||||
"method", r.Method,
|
||||
"path", fullPath,
|
||||
"status", wrapped.statusCode,
|
||||
"duration", duration,
|
||||
"client_ip", ip,
|
||||
}
|
||||
|
||||
if wrapped.errorMsg != "" {
|
||||
fields = append(fields, "error", wrapped.errorMsg)
|
||||
}
|
||||
|
||||
switch {
|
||||
case wrapped.statusCode >= 500:
|
||||
slog.Error("request", fields...)
|
||||
case wrapped.statusCode >= 400:
|
||||
slog.Warn("request", fields...)
|
||||
default:
|
||||
slog.Info("request", fields...)
|
||||
}
|
||||
})
|
||||
}
|
||||
6
internal/model/common/request.go
Normal file
6
internal/model/common/request.go
Normal file
@@ -0,0 +1,6 @@
|
||||
package common
|
||||
|
||||
type Pagination struct {
|
||||
Page int32 `form:"page,default=1" validate:"gte=1"`
|
||||
PageSize int32 `form:"page_size,default=10" validate:"gte=1,lte=100"`
|
||||
}
|
||||
16
internal/model/common/response.go
Normal file
16
internal/model/common/response.go
Normal file
@@ -0,0 +1,16 @@
|
||||
package common
|
||||
|
||||
// Response 通用响应结构体
|
||||
type Response struct {
|
||||
Message string `json:"msg,omitempty"` // 消息说明
|
||||
Data interface{} `json:"data,omitempty"` // 业务数据
|
||||
}
|
||||
|
||||
// PageResponse 分页查询通用相应结构体
|
||||
type PageResponse struct {
|
||||
Message string `json:"msg,omitempty"` // 消息说明
|
||||
Page int32 `json:"page"` // 当前页码
|
||||
PageSize int32 `json:"page_size"` // 每页数量
|
||||
List interface{} `json:"list,omitempty"` // 业务数据
|
||||
Total int64 `json:"total"` // 总记录数
|
||||
}
|
||||
33
internal/model/enum/enum.go
Normal file
33
internal/model/enum/enum.go
Normal file
@@ -0,0 +1,33 @@
|
||||
package enum
|
||||
|
||||
// Status 通用状态枚举
|
||||
type Status int16
|
||||
|
||||
const (
|
||||
StatusDisabled Status = iota // 0
|
||||
StatusEnabled // 1
|
||||
)
|
||||
|
||||
// MenuType 菜单类型枚举
|
||||
type MenuType int16
|
||||
|
||||
const (
|
||||
MenuTypeDirectory MenuType = iota // 0
|
||||
MenuTypeMenu // 1
|
||||
MenuTypeButton // 2
|
||||
)
|
||||
|
||||
type PermissionType int16
|
||||
|
||||
const (
|
||||
PermissionTypeMenu PermissionType = iota // 0
|
||||
PermissionTypeApi // 1
|
||||
)
|
||||
|
||||
type PostStatus int16
|
||||
|
||||
const (
|
||||
PostStatusDraft PostStatus = iota // 0: 草稿
|
||||
PostStatusPublished // 1: 已发布
|
||||
PostStatusOffline // 2: 已下线
|
||||
)
|
||||
11
internal/model/request/category.go
Normal file
11
internal/model/request/category.go
Normal file
@@ -0,0 +1,11 @@
|
||||
package request
|
||||
|
||||
type CreateCategoryRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1"`
|
||||
Code string `json:"code" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type UpdateCategoryRequest struct {
|
||||
Name *string `json:"name" validate:"omitempty,min=1"`
|
||||
Code *string `json:"code" validate:"omitempty,min=1"`
|
||||
}
|
||||
30
internal/model/request/post.go
Normal file
30
internal/model/request/post.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type CreatePostRequest struct {
|
||||
Title string `json:"title" validate:"required,min=1"`
|
||||
CoverID *int32 `json:"cover_id" validate:"required,min=1"`
|
||||
Slug string `json:"slug" validate:"required,min=0"`
|
||||
Content string `json:"content" validate:"required,min=0"`
|
||||
Summary string `json:"summary" validate:"required,min=0"`
|
||||
Status *int16 `json:"status" validate:"required,oneof=0 1 2"`
|
||||
Sort *int32 `json:"sort" validate:"required,min=0"`
|
||||
PublishedAt time.Time `json:"published_at" validate:"required"`
|
||||
CategoryID *int32 `json:"category_id" validate:"required,min=1"`
|
||||
}
|
||||
|
||||
type UpdatePostRequest struct {
|
||||
Title *string `json:"title" validate:"omitempty,min=1"`
|
||||
CoverID *int32 `json:"cover_id" validate:"omitempty,min=1"`
|
||||
Slug *string `json:"slug" validate:"omitempty,min=1"`
|
||||
Content *string `json:"content" validate:"required,min=0"`
|
||||
Summary *string `json:"summary" validate:"omitempty,min=1"`
|
||||
Status *int16 `json:"status" validate:"omitempty,oneof=0 1 2"`
|
||||
View *int32 `json:"view" validate:"omitempty,min=1"`
|
||||
Sort *int32 `json:"sort" validate:"omitempty,min=0"`
|
||||
PublishedAt *time.Time `json:"published_at" validate:"omitempty"`
|
||||
CategoryID *int32 `json:"category_id" validate:"required,min=1"`
|
||||
}
|
||||
17
internal/model/request/sys_api.go
Normal file
17
internal/model/request/sys_api.go
Normal file
@@ -0,0 +1,17 @@
|
||||
package request
|
||||
|
||||
type CreateSysApiRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
GroupName string `json:"group_name" validate:"required,min=1,max=100"`
|
||||
Path string `json:"path" validate:"required,min=1,max=100"`
|
||||
Method string `json:"method" validate:"required,oneof=GET POST PUT PATCH DELETE"`
|
||||
Sort *int32 `json:"sort" validate:"required,min=0"`
|
||||
}
|
||||
|
||||
type UpdateSysApiRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
GroupName string `json:"group_name" validate:"required,min=1,max=100"`
|
||||
Path string `json:"path" validate:"required,min=1,max=100"`
|
||||
Method string `json:"method" validate:"required,oneof=GET POST PUT PATCH DELETE"`
|
||||
Sort *int32 `json:"sort" validate:"required,min=0"`
|
||||
}
|
||||
31
internal/model/request/sys_menu.go
Normal file
31
internal/model/request/sys_menu.go
Normal file
@@ -0,0 +1,31 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"server/internal/pkg/validator"
|
||||
)
|
||||
|
||||
type CreateSysMenuRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Path string `json:"path" validate:"required,min=1,max=100"`
|
||||
Type *int16 `json:"type" validate:"required,oneof=0 1 2"`
|
||||
Component *string `json:"component" validate:"omitempty"`
|
||||
Hidden *bool `json:"hidden" validate:"omitempty"`
|
||||
Sort *int32 `json:"sort" validate:"required,min=0"`
|
||||
Status *int16 `json:"status" validate:"required,oneof=0 1"`
|
||||
ParentID *int32 `json:"parent_id" validate:"omitempty,gt=0"`
|
||||
Icon *int32 `json:"icon" validate:"omitempty"`
|
||||
PermissionCode string `json:"permission_code" validate:"required,min=1,max=100"`
|
||||
}
|
||||
|
||||
type UpdateSysMenuRequest struct {
|
||||
Name *string `json:"name" validate:"omitempty,min=1,max=100"`
|
||||
Path *string `json:"path" validate:"omitempty,min=1,max=100"`
|
||||
Type *int16 `json:"type" validate:"omitempty,oneof=0 1 2"`
|
||||
Component *string `json:"component" validate:"omitempty"`
|
||||
Hidden *bool `json:"hidden" validate:"omitempty"`
|
||||
Sort *int32 `json:"sort" validate:"omitempty,min=0"`
|
||||
Status *int16 `json:"status" validate:"omitempty,oneof=0 1"`
|
||||
ParentID validator.NullInt32 `json:"parent_id" validate:"omitempty"`
|
||||
Icon *int32 `json:"icon" validate:"omitempty"`
|
||||
PermissionCode *string `json:"permission_code" validate:"omitempty,min=1,max=100"`
|
||||
}
|
||||
1
internal/model/request/sys_permission.go
Normal file
1
internal/model/request/sys_permission.go
Normal file
@@ -0,0 +1 @@
|
||||
package request
|
||||
18
internal/model/request/sys_role.go
Normal file
18
internal/model/request/sys_role.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package request
|
||||
|
||||
type CreateSysRoleRequest struct {
|
||||
Name string `json:"name" validate:"required,min=1,max=100"`
|
||||
Code string `json:"code" validate:"required,max=100"`
|
||||
}
|
||||
|
||||
type UpdateSysRoleRequest struct {
|
||||
Name *string `json:"name" validate:"min=1,max=100"`
|
||||
}
|
||||
|
||||
type SetSysRoleMenusRequest struct {
|
||||
MenuIDs []int32 `json:"menu_ids" validate:"required,dive,gt=0"`
|
||||
}
|
||||
|
||||
type SetSysRoleApisRequest struct {
|
||||
ApiIDs []int32 `json:"api_ids" validate:"required,dive,gt=0"`
|
||||
}
|
||||
30
internal/model/request/sys_user.go
Normal file
30
internal/model/request/sys_user.go
Normal file
@@ -0,0 +1,30 @@
|
||||
package request
|
||||
|
||||
import (
|
||||
"server/internal/pkg/validator"
|
||||
)
|
||||
|
||||
type CreateSysUserRequest struct {
|
||||
Username string `json:"username" validate:"required"`
|
||||
Account string `json:"account" validate:"required,min=5,max=100"`
|
||||
Password string `json:"password" validate:"required,min=6,max=255"`
|
||||
AvatarID *int32 `json:"avatar_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type UpdateSysUserRequest struct {
|
||||
Username *string `json:"username" validate:"omitempty,min=0,max=50"`
|
||||
AvatarID validator.NullInt32 `json:"avatar_id" validate:"omitempty"`
|
||||
}
|
||||
|
||||
type UpdateSysUserPassword struct {
|
||||
Password string `json:"password" validate:"required,min=6,max=255"`
|
||||
}
|
||||
|
||||
type SetSysUserRolesRequest struct {
|
||||
RoleIDs []int32 `json:"role_ids" validate:"required,dive,gt=0"`
|
||||
}
|
||||
|
||||
type LoginRequest struct {
|
||||
Account string `json:"account" validate:"required,min=5,max=100"`
|
||||
Password string `json:"password" validate:"required,min=6,max=255"`
|
||||
}
|
||||
25
internal/model/response/post.go
Normal file
25
internal/model/response/post.go
Normal file
@@ -0,0 +1,25 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type ArchivePost struct {
|
||||
ID int32 `json:"id"`
|
||||
Slug string `json:"slug"`
|
||||
Title string `json:"title"`
|
||||
PublishedAt time.Time `json:"published_at"`
|
||||
PublishedAtDisplay string `json:"published_at_display"`
|
||||
CategoryName string `json:"category_name"`
|
||||
}
|
||||
|
||||
type ArchiveMonth struct {
|
||||
Month int `json:"month"`
|
||||
Archive []ArchivePost `json:"list"`
|
||||
}
|
||||
|
||||
type ArchiveYear struct {
|
||||
Year int `json:"year"`
|
||||
Total int `json:"total"`
|
||||
ArchiveMonth []ArchiveMonth `json:"list"`
|
||||
}
|
||||
15
internal/model/response/sys_file.go
Normal file
15
internal/model/response/sys_file.go
Normal file
@@ -0,0 +1,15 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/pkg/httputil"
|
||||
)
|
||||
|
||||
func ToFiles(files []db.File) []db.File {
|
||||
result := make([]db.File, len(files))
|
||||
for i := range files {
|
||||
result[i] = files[i]
|
||||
result[i].FilePath = httputil.BuildFileUrl(&files[i].FilePath)
|
||||
}
|
||||
return result
|
||||
}
|
||||
45
internal/model/response/sys_user.go
Normal file
45
internal/model/response/sys_user.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package response
|
||||
|
||||
import (
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/pkg/httputil"
|
||||
"time"
|
||||
)
|
||||
|
||||
type SysUserRolesResponse struct {
|
||||
ID uint `json:"id"`
|
||||
Code string `json:"code"`
|
||||
Description string `json:"description"`
|
||||
}
|
||||
|
||||
type SysUserInfo struct {
|
||||
ID int32 `json:"id"`
|
||||
Account string `json:"account"`
|
||||
Username string `json:"username"`
|
||||
AvatarUrl string `json:"avatar_url"`
|
||||
Roles []string `json:"roles"`
|
||||
Menus []db.SysMenu `json:"menus"`
|
||||
}
|
||||
|
||||
type LoginResponse struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
AccessTokenExp time.Time `json:"access_token_exp"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
RefreshTokenExp time.Time `json:"refresh_token_exp"`
|
||||
}
|
||||
|
||||
func NewSysUserInfo(user db.GetSysUserByIDRow, roles []db.SysRole, menus []db.SysMenu) *SysUserInfo {
|
||||
roleCodes := make([]string, len(roles))
|
||||
for i, role := range roles {
|
||||
roleCodes[i] = role.Code
|
||||
}
|
||||
|
||||
return &SysUserInfo{
|
||||
ID: user.ID,
|
||||
Account: user.Account,
|
||||
Username: user.Username,
|
||||
AvatarUrl: httputil.BuildFileUrl(user.AvatarUrl),
|
||||
Roles: roleCodes,
|
||||
Menus: menus,
|
||||
}
|
||||
}
|
||||
31
internal/pkg/cache/cache.go
vendored
Normal file
31
internal/pkg/cache/cache.go
vendored
Normal file
@@ -0,0 +1,31 @@
|
||||
package cache
|
||||
|
||||
import (
|
||||
"server/internal/db/sqlc"
|
||||
|
||||
"github.com/maypok86/otter/v2"
|
||||
)
|
||||
|
||||
type Caches struct {
|
||||
SysUserApisCache *otter.Cache[int32, []db.GetSysUserApisRow]
|
||||
}
|
||||
|
||||
func NewCaches() *Caches {
|
||||
sysUserApisCache := otter.Must(&otter.Options[int32, []db.GetSysUserApisRow]{
|
||||
MaximumSize: 1_000,
|
||||
})
|
||||
|
||||
return &Caches{
|
||||
SysUserApisCache: sysUserApisCache,
|
||||
}
|
||||
}
|
||||
|
||||
// ClearSysUserCache 清理单个用户缓存
|
||||
func (c *Caches) ClearSysUserCache(userID int32) {
|
||||
c.SysUserApisCache.Invalidate(userID)
|
||||
}
|
||||
|
||||
// ClearAllSysUserCache 清理所有用户缓存
|
||||
func (c *Caches) ClearAllSysUserCache() {
|
||||
c.SysUserApisCache.InvalidateAll()
|
||||
}
|
||||
49
internal/pkg/dberr/postgres.go
Normal file
49
internal/pkg/dberr/postgres.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package dberr
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgconn"
|
||||
)
|
||||
|
||||
const (
|
||||
SysUserAccountKey = "sys_users_account_key"
|
||||
SysRoleCodeKey = "sys_roles_code_key"
|
||||
PostSlugKey = "posts_slug_key"
|
||||
SysPermissionsCodeKey = "sys_permissions_code_key"
|
||||
SysApisMethodPathKey = "sys_apis_method_path_key"
|
||||
CategoryCodeKey = "categories_code_key"
|
||||
)
|
||||
|
||||
func MapRowsAffected(rows int64, err error, notFoundErr error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if rows == 0 {
|
||||
return notFoundErr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func MapNoRows(err error, notFoundErr error) error {
|
||||
if errors.Is(err, pgx.ErrNoRows) {
|
||||
return notFoundErr
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func MapUniqueViolation(err error, constraint string, uniqueErr error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var pgErr *pgconn.PgError
|
||||
if errors.As(err, &pgErr) &&
|
||||
pgErr.Code == "23505" &&
|
||||
pgErr.ConstraintName == constraint {
|
||||
return uniqueErr
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
48
internal/pkg/errs/errs.go
Normal file
48
internal/pkg/errs/errs.go
Normal file
@@ -0,0 +1,48 @@
|
||||
package errs
|
||||
|
||||
import "net/http"
|
||||
|
||||
type AppError struct {
|
||||
HTTPCode int
|
||||
Msg string
|
||||
}
|
||||
|
||||
func (e *AppError) Error() string {
|
||||
return e.Msg
|
||||
}
|
||||
|
||||
func New(httpCode int, msg string) *AppError {
|
||||
return &AppError{
|
||||
HTTPCode: httpCode,
|
||||
Msg: msg,
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidCredentials = New(http.StatusUnauthorized, "用户名或密码错误")
|
||||
ErrUnauthenticated = New(http.StatusUnauthorized, "用户未登录或登录已失效")
|
||||
ErrUnauthorized = New(http.StatusUnauthorized, "认证失败:无法获取当前用户信息")
|
||||
ErrInvalidToken = New(http.StatusUnauthorized, "登录凭证无效")
|
||||
ErrInvalidTokenClaims = New(http.StatusUnauthorized, "登录凭证解析失败")
|
||||
ErrInvalidRefreshToken = New(http.StatusBadRequest, "invalid_grant")
|
||||
ErrExpiredRefreshToken = New(http.StatusBadRequest, "invalid_grant")
|
||||
ErrUserNotFound = New(http.StatusNotFound, "用户数据不存在")
|
||||
ErrCategoryNotFound = New(http.StatusNotFound, "分类数据不存在")
|
||||
ErrSysApiNotFound = New(http.StatusNotFound, "接口数据不存在")
|
||||
ErrPostNotFound = New(http.StatusNotFound, "文章数据不存在")
|
||||
ErrSysMenuNotFound = New(http.StatusNotFound, "菜单数据不存在")
|
||||
ErrSysRoleNotFound = New(http.StatusNotFound, "角色数据不存在")
|
||||
ErrCannotDeleteSuperAdmin = New(http.StatusForbidden, "超级管理员账号无法被删除")
|
||||
ErrSlugRequired = New(http.StatusBadRequest, "slug不能为空")
|
||||
ErrIDRequired = New(http.StatusBadRequest, "id不能为空")
|
||||
ErrPermissionDenied = New(http.StatusForbidden, "没有权限访问该资源")
|
||||
ErrInvalidID = New(http.StatusBadRequest, "id非法请检查传入的id")
|
||||
ErrEmptyBody = New(http.StatusBadRequest, "请求内容(body)不能为空")
|
||||
ErrInvalidJSON = New(http.StatusBadRequest, "请求数据格式错误")
|
||||
ErrAccountAlreadyExists = New(http.StatusBadRequest, "账号已存在")
|
||||
ErrCodeAlreadyExists = New(http.StatusBadRequest, "角色编码已存在")
|
||||
ErrSlugAlreadyExists = New(http.StatusBadRequest, "slug已存在")
|
||||
ErrPermissionCodeAlreadyExists = New(http.StatusBadRequest, "权限编码已存在")
|
||||
ErrCategoryCodeAlreadyExists = New(http.StatusBadRequest, "分类编码已存在")
|
||||
ErrSysApiMethodPathAlreadyExists = New(http.StatusBadRequest, "接口方法(method)路径(path)已存在")
|
||||
)
|
||||
90
internal/pkg/httputil/request.go
Normal file
90
internal/pkg/httputil/request.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"server/internal/config"
|
||||
"server/internal/model/common"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/validator"
|
||||
"strconv"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// Pagination 分页请求参数
|
||||
func Pagination(r *http.Request) *common.Pagination {
|
||||
q := r.URL.Query()
|
||||
|
||||
// page 默认 1
|
||||
page := int32(1)
|
||||
if p := q.Get("page"); p != "" {
|
||||
if v, err := strconv.Atoi(p); err == nil && v >= 1 {
|
||||
page = int32(v)
|
||||
}
|
||||
}
|
||||
|
||||
// pageSize 默认 10
|
||||
pageSize := int32(10)
|
||||
if ps := q.Get("page_size"); ps != "" {
|
||||
if v, err := strconv.Atoi(ps); err == nil && v >= 1 && v <= 100 {
|
||||
pageSize = int32(v)
|
||||
}
|
||||
}
|
||||
|
||||
return &common.Pagination{
|
||||
Page: page,
|
||||
PageSize: pageSize,
|
||||
}
|
||||
}
|
||||
|
||||
func URLParamInt32(r *http.Request, key string) (int32, error) {
|
||||
v := chi.URLParam(r, key)
|
||||
|
||||
if v == "" {
|
||||
return 0, errs.ErrIDRequired
|
||||
}
|
||||
|
||||
n, err := strconv.ParseInt(v, 10, 32)
|
||||
if err != nil {
|
||||
return 0, errs.ErrInvalidID
|
||||
}
|
||||
|
||||
return int32(n), nil
|
||||
}
|
||||
|
||||
func BindJson(r *http.Request, dest any) error {
|
||||
if err := json.NewDecoder(r.Body).Decode(dest); err != nil {
|
||||
if errors.Is(err, io.EOF) {
|
||||
return errs.ErrEmptyBody
|
||||
}
|
||||
return errs.ErrInvalidJSON
|
||||
}
|
||||
|
||||
if err := validator.Struct(dest); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func BuildFileUrl(path *string) string {
|
||||
if path == nil || *path == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
baseURL := config.GetString("file.base_url")
|
||||
if baseURL == "" {
|
||||
return ""
|
||||
}
|
||||
|
||||
result, err := url.JoinPath(baseURL, *path)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
65
internal/pkg/httputil/response.go
Normal file
65
internal/pkg/httputil/response.go
Normal file
@@ -0,0 +1,65 @@
|
||||
package httputil
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"server/internal/model/common"
|
||||
"server/internal/pkg/errs"
|
||||
validatorI18 "server/internal/pkg/validator"
|
||||
"strings"
|
||||
|
||||
"github.com/go-playground/validator/v10"
|
||||
)
|
||||
|
||||
func respondWithJSON(w http.ResponseWriter, code int, data any) {
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
w.WriteHeader(code)
|
||||
if err := json.NewEncoder(w).Encode(data); err != nil {
|
||||
slog.Error("respondWithJSON: failed to encode response", "error", err)
|
||||
}
|
||||
}
|
||||
|
||||
func Ok(w http.ResponseWriter, data ...any) {
|
||||
resp := common.Response{
|
||||
Message: "ok",
|
||||
}
|
||||
|
||||
if len(data) > 0 && data[0] != nil {
|
||||
resp.Data = data[0]
|
||||
}
|
||||
|
||||
respondWithJSON(w, http.StatusOK, resp)
|
||||
}
|
||||
|
||||
func OkWithPage(w http.ResponseWriter, pageResp *common.PageResponse) {
|
||||
pageResp.Message = "ok"
|
||||
respondWithJSON(w, http.StatusOK, pageResp)
|
||||
}
|
||||
|
||||
// Fail 响应失败
|
||||
func Fail(w http.ResponseWriter, err error) {
|
||||
var msg string
|
||||
// 默认code 500
|
||||
httpStatusCode := http.StatusInternalServerError
|
||||
|
||||
var validationErrs validator.ValidationErrors
|
||||
var appErr *errs.AppError
|
||||
if errors.As(err, &validationErrs) {
|
||||
httpStatusCode = http.StatusBadRequest
|
||||
msgs := validatorI18.Translate(err)
|
||||
msg = strings.Join(msgs, "; ")
|
||||
} else if errors.As(err, &appErr) {
|
||||
httpStatusCode = appErr.HTTPCode
|
||||
msg = appErr.Msg
|
||||
} else {
|
||||
msg = err.Error()
|
||||
}
|
||||
|
||||
resp := common.Response{
|
||||
Message: msg,
|
||||
}
|
||||
|
||||
respondWithJSON(w, httpStatusCode, resp)
|
||||
}
|
||||
76
internal/pkg/logger/logger.go
Normal file
76
internal/pkg/logger/logger.go
Normal file
@@ -0,0 +1,76 @@
|
||||
package logger
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log/slog"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"server/internal/config"
|
||||
|
||||
"gopkg.in/natefinch/lumberjack.v2"
|
||||
)
|
||||
|
||||
// New 根据配置创建 slog.Logger,并将其设置为默认 logger。
|
||||
// 日志会写入文件(自动按大小/时间滚动),并可选同时输出到控制台。
|
||||
func New(cfg *config.Config) *slog.Logger {
|
||||
logCfg := cfg.Log
|
||||
|
||||
// 文件滚动写入
|
||||
fileWriter := &lumberjack.Logger{
|
||||
Filename: defaultStr(logCfg.Filename, "logs/app.log"),
|
||||
MaxSize: defaultInt(logCfg.MaxSize, 100), // MB
|
||||
MaxBackups: defaultInt(logCfg.MaxBackups, 30),
|
||||
MaxAge: defaultInt(logCfg.MaxAge, 30), // days
|
||||
Compress: logCfg.Compress,
|
||||
}
|
||||
|
||||
var writer io.Writer = fileWriter
|
||||
if logCfg.Console {
|
||||
writer = io.MultiWriter(os.Stdout, fileWriter)
|
||||
}
|
||||
|
||||
opts := &slog.HandlerOptions{
|
||||
Level: parseLevel(logCfg.Level),
|
||||
AddSource: false,
|
||||
}
|
||||
|
||||
// 生产环境用 JSON,便于日志采集/检索;开发环境用文本更易读
|
||||
var handler slog.Handler
|
||||
if config.IsDev() {
|
||||
handler = slog.NewTextHandler(writer, opts)
|
||||
} else {
|
||||
handler = slog.NewJSONHandler(writer, opts)
|
||||
}
|
||||
|
||||
l := slog.New(handler)
|
||||
slog.SetDefault(l) // 让全局 slog.Info/Error 也走同一套配置
|
||||
return l
|
||||
}
|
||||
|
||||
func parseLevel(level string) slog.Level {
|
||||
switch strings.ToLower(level) {
|
||||
case "debug":
|
||||
return slog.LevelDebug
|
||||
case "warn", "warning":
|
||||
return slog.LevelWarn
|
||||
case "error":
|
||||
return slog.LevelError
|
||||
default:
|
||||
return slog.LevelInfo
|
||||
}
|
||||
}
|
||||
|
||||
func defaultStr(v, def string) string {
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
func defaultInt(v, def int) int {
|
||||
if v <= 0 {
|
||||
return def
|
||||
}
|
||||
return v
|
||||
}
|
||||
28
internal/pkg/validator/null_int32.go
Normal file
28
internal/pkg/validator/null_int32.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package validator
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
// NullInt32 Valid(false) + Set(true) = null | Valid(false) + Set(false) = nil
|
||||
type NullInt32 struct {
|
||||
Value int32
|
||||
Valid bool
|
||||
Set bool
|
||||
}
|
||||
|
||||
func (i *NullInt32) UnmarshalJSON(data []byte) error {
|
||||
i.Set = true
|
||||
|
||||
if string(data) == "null" {
|
||||
i.Valid = false
|
||||
return nil
|
||||
}
|
||||
|
||||
var temp int32
|
||||
if err := json.Unmarshal(data, &temp); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.Value = temp
|
||||
i.Valid = true
|
||||
return nil
|
||||
}
|
||||
66
internal/pkg/validator/validator.go
Normal file
66
internal/pkg/validator/validator.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package validator
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
|
||||
"github.com/go-playground/locales/zh"
|
||||
ut "github.com/go-playground/universal-translator"
|
||||
"github.com/go-playground/validator/v10"
|
||||
zhtranslations "github.com/go-playground/validator/v10/translations/zh"
|
||||
)
|
||||
|
||||
var (
|
||||
trans ut.Translator
|
||||
validate *validator.Validate
|
||||
)
|
||||
|
||||
func InitValidatorZh() {
|
||||
langZh := zh.New()
|
||||
uni := ut.New(langZh, langZh)
|
||||
var found bool
|
||||
trans, found = uni.GetTranslator("zh")
|
||||
|
||||
if !found {
|
||||
panic("translator 'zh' not found")
|
||||
}
|
||||
|
||||
err := zhtranslations.RegisterDefaultTranslations(validate, trans)
|
||||
if err != nil {
|
||||
panic("failed to register zh translations: " + err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func init() {
|
||||
validate = validator.New()
|
||||
|
||||
// 注册自定义标签名称
|
||||
validate.RegisterTagNameFunc(func(field reflect.StructField) string {
|
||||
name := field.Tag.Get("json")
|
||||
if name == "-" {
|
||||
return ""
|
||||
}
|
||||
return name
|
||||
})
|
||||
|
||||
// 初始化中文翻译器
|
||||
InitValidatorZh()
|
||||
}
|
||||
|
||||
func Translate(err error) []string {
|
||||
var validateErrs validator.ValidationErrors
|
||||
if errors.As(err, &validateErrs) {
|
||||
messages := make([]string, 0, len(validateErrs))
|
||||
for _, e := range validateErrs {
|
||||
messages = append(messages, e.Translate(trans))
|
||||
}
|
||||
return messages
|
||||
}
|
||||
|
||||
// 非校验错误 直接返回
|
||||
return []string{err.Error()}
|
||||
}
|
||||
|
||||
func Struct(s any) error {
|
||||
return validate.Struct(s)
|
||||
}
|
||||
18
internal/router/client.go
Normal file
18
internal/router/client.go
Normal file
@@ -0,0 +1,18 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"server/internal/handler"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func SetupPostRoutes(r chi.Router, h *handler.PostHandler) {
|
||||
r.Get("/post", h.ListPage)
|
||||
r.Get("/post/{slug}", h.GetPost)
|
||||
r.Get("/category/stats", h.ListCategoryStats)
|
||||
r.Get("/post/archive", h.ListArchives)
|
||||
}
|
||||
|
||||
func SetupWebRoutes(r chi.Router, h *handler.WebHandler) {
|
||||
|
||||
}
|
||||
80
internal/router/router.go
Normal file
80
internal/router/router.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"server/internal/config"
|
||||
"server/internal/handler"
|
||||
"server/internal/middleware"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
type Handlers struct {
|
||||
fx.In
|
||||
// auth
|
||||
JwtMiddleware *middleware.JWTMiddleware
|
||||
AuthMiddleware *middleware.AuthMiddleware
|
||||
LoggerMiddleware *middleware.LoggerMiddleware
|
||||
// system
|
||||
SysUserHandler *handler.SysUserHandler
|
||||
SysRoleHandler *handler.SysRoleHandler
|
||||
SysMenuHandler *handler.SysMenuHandler
|
||||
SysApiHandler *handler.SysApiHandler
|
||||
SysFileHandler *handler.SysFileHandler
|
||||
SysPostHandler *handler.SysPostHandler
|
||||
CategoryHandler *handler.CategoryHandler
|
||||
// web
|
||||
PostHandler *handler.PostHandler
|
||||
WebHandler *handler.WebHandler
|
||||
}
|
||||
|
||||
func setupStaticFiles(r chi.Router) {
|
||||
rootDir, _ := os.Getwd()
|
||||
|
||||
uploadsDir := filepath.Join(rootDir, "uploads")
|
||||
|
||||
r.Handle("/static/*", http.StripPrefix("/static/", http.FileServer(http.Dir(uploadsDir))))
|
||||
}
|
||||
|
||||
func NewRouter(m Handlers) *chi.Mux {
|
||||
r := chi.NewRouter()
|
||||
|
||||
r.Use(m.LoggerMiddleware.Middleware)
|
||||
|
||||
if config.IsDev() {
|
||||
// 开放静态目录
|
||||
setupStaticFiles(r)
|
||||
}
|
||||
|
||||
r.Route("/api/admin", func(r chi.Router) {
|
||||
r.Post("/auth/login", m.SysUserHandler.Login)
|
||||
r.Post("/auth/refresh-token", m.SysUserHandler.RefreshToken)
|
||||
r.Post("/auth/logout", m.SysUserHandler.Logout)
|
||||
|
||||
r.Group(func(r chi.Router) {
|
||||
r.Use(m.JwtMiddleware.Middleware)
|
||||
r.Use(m.AuthMiddleware.Middleware)
|
||||
|
||||
SetupSysUserRoutes(r, m.SysUserHandler)
|
||||
SetupSysRoleRoutes(r, m.SysRoleHandler)
|
||||
SetupSysMenuRoutes(r, m.SysMenuHandler)
|
||||
SetupSysApiRoutes(r, m.SysApiHandler)
|
||||
SetupSysFileRoutes(r, m.SysFileHandler)
|
||||
SetupCategoryRoutes(r, m.CategoryHandler)
|
||||
SetupSysPostRoutes(r, m.SysPostHandler)
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
// web api
|
||||
r.Route("/api", func(r chi.Router) {
|
||||
r.Group(func(r chi.Router) {
|
||||
SetupPostRoutes(r, m.PostHandler)
|
||||
SetupWebRoutes(r, m.WebHandler)
|
||||
})
|
||||
})
|
||||
return r
|
||||
}
|
||||
68
internal/router/system.go
Normal file
68
internal/router/system.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"server/internal/handler"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
func SetupSysUserRoutes(r chi.Router, h *handler.SysUserHandler) {
|
||||
r.Get("/user", h.ListPage)
|
||||
r.Get("/user/info", h.GetUserInfo)
|
||||
r.Get("/user/{id}/roles", h.GetRoles)
|
||||
r.Post("/user", h.Create)
|
||||
r.Patch("/user/{id}", h.Update)
|
||||
r.Put("/user/{id}/roles", h.SetRoles)
|
||||
r.Patch("/user/{id}/password", h.UpdatePassword)
|
||||
r.Delete("/user/{id}", h.Delete)
|
||||
}
|
||||
|
||||
func SetupSysRoleRoutes(r chi.Router, h *handler.SysRoleHandler) {
|
||||
r.Get("/role", h.ListPage)
|
||||
r.Get("/role/{id}/menus", h.GetRoleMenus)
|
||||
r.Get("/role/{id}/apis", h.GetRoleApis)
|
||||
r.Get("/role/all", h.GetRoles)
|
||||
r.Post("/role", h.Create)
|
||||
r.Patch("/role/{id}", h.Update)
|
||||
r.Put("/role/{id}/menus", h.SetRoleMenus)
|
||||
r.Put("/role/{id}/apis", h.SetRoleApis)
|
||||
r.Delete("/role/{id}", h.Delete)
|
||||
}
|
||||
|
||||
func SetupSysMenuRoutes(r chi.Router, h *handler.SysMenuHandler) {
|
||||
r.Get("/menu", h.ListPage)
|
||||
r.Get("/menu/all", h.GetMenus)
|
||||
r.Post("/menu", h.Create)
|
||||
r.Patch("/menu/{id}", h.Update)
|
||||
r.Delete("/menu/{id}", h.Delete)
|
||||
}
|
||||
|
||||
func SetupSysApiRoutes(r chi.Router, h *handler.SysApiHandler) {
|
||||
r.Get("/api", h.ListPage)
|
||||
r.Get("/api/all", h.GetAllSysApis)
|
||||
r.Get("/api/group-names", h.GetApiGroupNames)
|
||||
r.Post("/api", h.Create)
|
||||
r.Patch("/api/{id}", h.Update)
|
||||
r.Delete("/api/{id}", h.Delete)
|
||||
}
|
||||
|
||||
func SetupSysFileRoutes(r chi.Router, h *handler.SysFileHandler) {
|
||||
r.Get("/file", h.ListPage)
|
||||
r.Post("/file", h.Upload)
|
||||
}
|
||||
|
||||
func SetupSysPostRoutes(r chi.Router, h *handler.SysPostHandler) {
|
||||
r.Get("/post", h.ListPage)
|
||||
r.Get("/post/{id}", h.GetPostById)
|
||||
r.Post("/post", h.Create)
|
||||
r.Patch("/post/{id}", h.Update)
|
||||
r.Delete("/post/{id}", h.Delete)
|
||||
}
|
||||
|
||||
func SetupCategoryRoutes(r chi.Router, h *handler.CategoryHandler) {
|
||||
r.Get("/category", h.ListPage)
|
||||
r.Get("/category/all", h.ListAll)
|
||||
r.Post("/category", h.Create)
|
||||
r.Patch("/category/{id}", h.Update)
|
||||
r.Delete("/category/{id}", h.Delete)
|
||||
}
|
||||
74
internal/service/category.go
Normal file
74
internal/service/category.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/dberr"
|
||||
"server/internal/pkg/errs"
|
||||
)
|
||||
|
||||
type CategoryService struct {
|
||||
queries *db.Queries
|
||||
}
|
||||
|
||||
func NewCategoryService(queries *db.Queries) *CategoryService {
|
||||
return &CategoryService{
|
||||
queries: queries,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *CategoryService) ListPage(ctx context.Context, p *common.Pagination) ([]db.Category, int64, error) {
|
||||
params := db.ListCategoriesParams{
|
||||
Limit: p.PageSize,
|
||||
Offset: (p.Page - 1) * p.PageSize,
|
||||
}
|
||||
|
||||
total, err := s.queries.CountCategories(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
list, err := s.queries.ListCategories(ctx, params)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (s *CategoryService) ListAll(ctx context.Context) ([]db.Category, error) {
|
||||
return s.queries.ListAllCategories(ctx)
|
||||
}
|
||||
|
||||
func (s *CategoryService) Create(ctx context.Context, req request.CreateCategoryRequest) error {
|
||||
params := db.CreateCategoryParams{
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
}
|
||||
|
||||
err := s.queries.CreateCategory(ctx, params)
|
||||
return dberr.MapUniqueViolation(err, dberr.CategoryCodeKey, errs.ErrCategoryCodeAlreadyExists)
|
||||
}
|
||||
|
||||
func (s *CategoryService) Update(ctx context.Context, id int32, req request.UpdateCategoryRequest) error {
|
||||
params := db.UpdateCategoryParams{
|
||||
ID: id,
|
||||
Name: req.Name,
|
||||
Code: req.Code,
|
||||
}
|
||||
rows, err := s.queries.UpdateCategory(ctx, params)
|
||||
// 先判断数据条目是否存在
|
||||
if err = dberr.MapRowsAffected(rows, err, errs.ErrCategoryNotFound); err != nil {
|
||||
// 再判断code是否重复
|
||||
return dberr.MapUniqueViolation(err, dberr.CategoryCodeKey, errs.ErrCategoryCodeAlreadyExists)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *CategoryService) Delete(ctx context.Context, id int32) error {
|
||||
rows, err := s.queries.DeleteCategory(ctx, id)
|
||||
return dberr.MapRowsAffected(rows, err, errs.ErrCategoryNotFound)
|
||||
}
|
||||
19
internal/service/module.go
Normal file
19
internal/service/module.go
Normal file
@@ -0,0 +1,19 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"go.uber.org/fx"
|
||||
)
|
||||
|
||||
var Module = fx.Module("services",
|
||||
fx.Provide(
|
||||
NewSysUserService,
|
||||
NewSysRoleService,
|
||||
NewSysMenuService,
|
||||
NewSysApiService,
|
||||
NewSysFileService,
|
||||
NewSysPostService,
|
||||
|
||||
NewPostService,
|
||||
NewCategoryService,
|
||||
),
|
||||
)
|
||||
113
internal/service/post.go
Normal file
113
internal/service/post.go
Normal file
@@ -0,0 +1,113 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/netip"
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/response"
|
||||
"server/internal/pkg/dberr"
|
||||
"server/internal/pkg/errs"
|
||||
"server/internal/pkg/httputil"
|
||||
)
|
||||
|
||||
type PostService struct {
|
||||
queries *db.Queries
|
||||
}
|
||||
|
||||
func NewPostService(queries *db.Queries) *PostService {
|
||||
return &PostService{
|
||||
queries: queries,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *PostService) ListPage(ctx context.Context, p *common.Pagination) ([]db.ListPublishedPostsRow, int64, error) {
|
||||
params := db.ListPublishedPostsParams{
|
||||
Limit: p.PageSize,
|
||||
Offset: (p.Page - 1) * p.PageSize,
|
||||
}
|
||||
|
||||
total, err := s.queries.CountPublishedPosts(ctx)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
list, err := s.queries.ListPublishedPosts(ctx, params)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
for i := range list {
|
||||
url := httputil.BuildFileUrl(list[i].Cover)
|
||||
list[i].Cover = &url
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (s *PostService) GetPost(ctx context.Context, slug string, ip netip.Addr) (*db.GetPublicPostBySlugRow, error) {
|
||||
post, err := s.queries.GetPublicPostBySlug(ctx, slug)
|
||||
if err != nil {
|
||||
return nil, dberr.MapNoRows(err, errs.ErrPostNotFound)
|
||||
}
|
||||
|
||||
_ = s.queries.IncrementPostStatsView(ctx, db.IncrementPostStatsViewParams{
|
||||
PostID: post.ID,
|
||||
Ip: ip,
|
||||
})
|
||||
|
||||
return &post, nil
|
||||
}
|
||||
|
||||
func (s *PostService) ListCategoryStats(ctx context.Context) ([]db.ListCategoryStatsRow, error) {
|
||||
return s.queries.ListCategoryStats(ctx)
|
||||
}
|
||||
|
||||
func (s *PostService) ListArchives(ctx context.Context) ([]response.ArchiveYear, error) {
|
||||
list, err := s.queries.ListArchives(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
archive := make([]response.ArchiveYear, 0)
|
||||
|
||||
for _, item := range list {
|
||||
y := item.PublishedAt.Year()
|
||||
m := int(item.PublishedAt.Month())
|
||||
|
||||
if len(archive) == 0 || archive[len(archive)-1].Year != y {
|
||||
archive = append(archive, response.ArchiveYear{
|
||||
Year: y,
|
||||
Total: 0,
|
||||
ArchiveMonth: make([]response.ArchiveMonth, 0),
|
||||
})
|
||||
}
|
||||
|
||||
// 获取索引
|
||||
lastYearIndex := len(archive) - 1
|
||||
|
||||
months := archive[lastYearIndex].ArchiveMonth
|
||||
if len(months) == 0 || months[len(months)-1].Month != m {
|
||||
archive[lastYearIndex].ArchiveMonth = append(archive[lastYearIndex].ArchiveMonth, response.ArchiveMonth{
|
||||
Month: m,
|
||||
Archive: make([]response.ArchivePost, 0),
|
||||
})
|
||||
}
|
||||
|
||||
lastMonthIndex := len(archive[lastYearIndex].ArchiveMonth) - 1
|
||||
|
||||
archive[lastYearIndex].ArchiveMonth[lastMonthIndex].Archive =
|
||||
append(archive[lastYearIndex].ArchiveMonth[lastMonthIndex].Archive, response.ArchivePost{
|
||||
ID: item.ID,
|
||||
Slug: item.Slug,
|
||||
Title: item.Title,
|
||||
PublishedAt: item.PublishedAt,
|
||||
PublishedAtDisplay: item.PublishedAt.Format("01-02"),
|
||||
CategoryName: *item.CategoryName,
|
||||
})
|
||||
|
||||
archive[lastYearIndex].Total++
|
||||
}
|
||||
|
||||
return archive, nil
|
||||
}
|
||||
158
internal/service/sys_api.go
Normal file
158
internal/service/sys_api.go
Normal file
@@ -0,0 +1,158 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
db "server/internal/db/sqlc"
|
||||
"server/internal/model/common"
|
||||
"server/internal/model/enum"
|
||||
"server/internal/model/request"
|
||||
"server/internal/pkg/cache"
|
||||
"server/internal/pkg/dberr"
|
||||
"server/internal/pkg/errs"
|
||||
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
type SysApiService struct {
|
||||
queries *db.Queries
|
||||
pool *pgxpool.Pool
|
||||
cache *cache.Caches
|
||||
}
|
||||
|
||||
func NewSysApiService(queries *db.Queries, pool *pgxpool.Pool, cache *cache.Caches) *SysApiService {
|
||||
return &SysApiService{queries: queries, pool: pool, cache: cache}
|
||||
}
|
||||
|
||||
func (s *SysApiService) ListPage(ctx context.Context, p *common.Pagination) ([]db.SysApi, int64, error) {
|
||||
params := db.GetSysApisParams{
|
||||
Limit: p.PageSize,
|
||||
Offset: (p.Page - 1) * p.PageSize,
|
||||
}
|
||||
|
||||
total, err := s.queries.CountSysApis(ctx)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
list, err := s.queries.GetSysApis(ctx, params)
|
||||
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
return list, total, nil
|
||||
}
|
||||
|
||||
func (s *SysApiService) GetAllSysApis(ctx context.Context) ([]db.SysApi, error) {
|
||||
return s.queries.GetAllSysApis(ctx)
|
||||
}
|
||||
|
||||
func (s *SysApiService) GetApiGroupNames(ctx context.Context) ([]string, error) {
|
||||
return s.queries.GetSysApiGroupNames(ctx)
|
||||
}
|
||||
|
||||
func (s *SysApiService) Create(ctx context.Context, req request.CreateSysApiRequest) error {
|
||||
tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer func(ctx context.Context) {
|
||||
_ = tx.Rollback(ctx)
|
||||
}(ctx)
|
||||
|
||||
q := db.New(tx)
|
||||
|
||||
api := db.CreateSysApiParams{
|
||||
Name: req.Name,
|
||||
GroupName: req.GroupName,
|
||||
Method: req.Method,
|
||||
Path: req.Path,
|
||||
Sort: req.Sort,
|
||||
}
|
||||
|
||||
// 创建权限
|
||||
permissionId, err := q.CreateSysPermission(ctx, db.CreateSysPermissionParams{
|
||||
Type: int16(enum.PermissionTypeApi),
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建api
|
||||
apiId, err := q.CreateSysApi(ctx, api)
|
||||
if err != nil {
|
||||
return dberr.MapUniqueViolation(err, dberr.SysApisMethodPathKey, errs.ErrSysApiMethodPathAlreadyExists)
|
||||
}
|
||||
|
||||
// 关联权限
|
||||
if err = q.CreateSysApiPermission(ctx, db.CreateSysApiPermissionParams{
|
||||
ApiID: apiId,
|
||||
PermissionID: permissionId,
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = tx.Commit(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
s.cache.ClearAllSysUserCache()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SysApiService) Update(ctx context.Context, id int32, req request.UpdateSysApiRequest) error {
|
||||
api := db.UpdateSysApiParams{
|
||||
ID: id,
|
||||
Name: req.Name,
|
||||
GroupName: req.GroupName,
|
||||
Method: req.Method,
|
||||
Path: req.Path,
|
||||
Sort: req.Sort,
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
s.cache.ClearAllSysUserCache()
|
||||
|
||||
rows, err := s.queries.UpdateSysApi(ctx, api)
|
||||
if err = dberr.MapRowsAffected(rows, err, errs.ErrSysApiNotFound); err != nil {
|
||||
return dberr.MapUniqueViolation(err, dberr.SysApisMethodPathKey, errs.ErrSysApiMethodPathAlreadyExists)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SysApiService) Delete(ctx context.Context, id int32) error {
|
||||
tx, err := s.pool.BeginTx(ctx, pgx.TxOptions{})
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
q := db.New(tx)
|
||||
|
||||
rows, err := q.DeleteSysApi(ctx, id)
|
||||
if err = dberr.MapRowsAffected(rows, err, errs.ErrSysApiNotFound); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = q.DeleteSysPermissionBySysApiID(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err = q.DeleteSysApiPermission(ctx, id); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 清理缓存
|
||||
s.cache.ClearAllSysUserCache()
|
||||
|
||||
return tx.Commit(ctx)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user