From 2bbd2995404fdae2fb94df1efd4e0d030b47f72c Mon Sep 17 00:00:00 2001 From: daz-3ux Date: Sat, 7 Oct 2023 21:44:07 +0800 Subject: [PATCH] FEAT: complete business logic Signed-off-by: daz-3ux --- README.md | 2 +- docs/devel/zh-CN/conversions/auth.md | 5 +- docs/devel/zh-CN/conversions/useCasbin.md | 69 ++++++++++++ go.mod | 17 ++- go.sum | 59 ++++++++-- internal/dazBlog/biz/README.md | 2 + internal/dazBlog/biz/post/post.go | 101 +++++++++++++++++- internal/dazBlog/biz/user/user.go | 87 ++++++++++++++- internal/dazBlog/controller/v1/post/create.go | 7 +- internal/dazBlog/controller/v1/post/delete.go | 26 +++++ .../controller/v1/post/delete_collection.go | 26 +++++ internal/dazBlog/controller/v1/post/get.go | 27 +++++ internal/dazBlog/controller/v1/post/list.go | 35 ++++++ internal/dazBlog/controller/v1/post/update.go | 42 ++++++++ internal/dazBlog/controller/v1/user/create.go | 1 + internal/dazBlog/controller/v1/user/delete.go | 26 +++++ internal/dazBlog/controller/v1/user/get.go | 27 +++++ internal/dazBlog/controller/v1/user/list.go | 34 ++++++ internal/dazBlog/controller/v1/user/update.go | 40 +++++++ internal/dazBlog/router.go | 22 ++++ internal/dazBlog/store/helper.go | 17 +++ internal/dazBlog/store/post.go | 48 +++++++++ internal/dazBlog/store/user.go | 32 +++++- internal/pkg/errno/post.go | 9 ++ internal/pkg/errno/user.go | 1 + internal/pkg/model/post.go | 12 ++- internal/pkg/model/user.go | 1 + pkg/api/dazBlog/v1/post.go | 48 ++++++++- pkg/api/dazBlog/v1/user.go | 59 ++++++++-- pkg/auth/authz.go | 30 ++++++ pkg/util/id/id.go | 29 +++++ 31 files changed, 906 insertions(+), 35 deletions(-) create mode 100644 docs/devel/zh-CN/conversions/useCasbin.md create mode 100644 internal/dazBlog/controller/v1/post/delete.go create mode 100644 internal/dazBlog/controller/v1/post/delete_collection.go create mode 100644 internal/dazBlog/controller/v1/post/get.go create mode 100644 internal/dazBlog/controller/v1/post/list.go create mode 100644 internal/dazBlog/controller/v1/post/update.go create mode 100644 internal/dazBlog/controller/v1/user/delete.go create mode 100644 internal/dazBlog/controller/v1/user/get.go create mode 100644 internal/dazBlog/controller/v1/user/list.go create mode 100644 internal/dazBlog/controller/v1/user/update.go create mode 100644 internal/dazBlog/store/helper.go create mode 100644 internal/pkg/errno/post.go create mode 100644 pkg/auth/authz.go create mode 100644 pkg/util/id/id.go diff --git a/README.md b/README.md index 63e13dc..da3c9b3 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,5 @@ ## 版本信息 [打印详细版本信息](./pkg/version/README.md) -## 认证系统 +## 认证授权系统 [dBlog的认证与授权](./docs/devel/zh-CN/conversions/auth.md) \ No newline at end of file diff --git a/docs/devel/zh-CN/conversions/auth.md b/docs/devel/zh-CN/conversions/auth.md index b6a2df5..238274f 100644 --- a/docs/devel/zh-CN/conversions/auth.md +++ b/docs/devel/zh-CN/conversions/auth.md @@ -57,4 +57,7 @@ eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjE1MjgwMTY5MjIsImlkIjowLCJuYmYiOjE - POST 用于创建 - PUT 用于更新,是一个幂等操作 -## 授权 - Authorization - Authz \ No newline at end of file +## 授权 - Authorization - Authz +- 使用 RBAC (Role-Based Access Control) 模型进行授权 + - 基于 [casbin](./useCasbin.md) 进行开发 + - \ No newline at end of file diff --git a/docs/devel/zh-CN/conversions/useCasbin.md b/docs/devel/zh-CN/conversions/useCasbin.md new file mode 100644 index 0000000..837aa37 --- /dev/null +++ b/docs/devel/zh-CN/conversions/useCasbin.md @@ -0,0 +1,69 @@ +# cabin + +## 元模型 +- PERM 元模型 +- policy, effect, request, matcher + - sub: subject, 访问实体 + - obj: object, 被访问实体 + - act: action, 访问行为 + - eft: effect, 访问结果,一般为空,默认指定为 allow 或 deny + +### Policy +- 策略 +- p = {sub, obj, act, eft} +- 一般存储与数据库中,因为会有很多 +```text +[policy_definition] +p = sub, obj, act, eft +``` + + +### Matchers +- 匹配规则 +```text +m = r.sub == p.sub && r.obj == p.obj && r.act == p.act +``` +- r 是请求, p 是策略 +- 会把 r 和 p 按照上述描述进行匹配 + - 从而返回匹配结果(eft),如果不定义,则返回allow,否则返回定义值 + + +### Effect +- 影响 +- 决定我们受否放行 +- cabin 支持的 policy effect + +| Policy effect | 意义 | +|-------------------------------------------------------------|------------------------| +| some(where (p.eft == allow)) | allow-override | +| !some(p.eft == deny) | deny-override | +| some(where (p.eft == allow) && !some(where (p.eft == deny)) | allow-and-deny | +| priority(p.eft)\|\|deny | priority | +| subjectPriority(p.eft) | priority based on role | + +### Resource +- 请求 +- r = {sub, obj, act} + + +## 角色域 +- role_definition + - g = _, _ 表示以角色为基础 + - g = _, _, _ 表示以域为基础(多商户模式) + + +## dBlog 对 casbin 的使用 +- 使用 RBAC 模型 + - Role Based Access Control +- 对资源操作进行授权 + - `用户`只可以访问自己账户下的 用户/博客 等资源 + - `管理员`可以访问所有资源 + - 也就是对 API 路径进行授权 +- 授权策略: + +| A | B | C | D | +|---|------|----------------|--------------------------| +| p | root | /v1/users* | (GET)(POST)(PUT)(DELETE) | +| p | bob | /v1/users/belm | (GET)(POST)(PUT)(DELETE) | + +- 因为要对每一个 HTTP 进行授权, 所以将授权功能封装为中间件 \ No newline at end of file diff --git a/go.mod b/go.mod index 7d3290c..70ad1cd 100644 --- a/go.mod +++ b/go.mod @@ -4,22 +4,25 @@ go 1.21.0 require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 + github.com/casbin/casbin/v2 v2.77.2 github.com/gin-gonic/gin v1.9.1 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/uuid v1.1.2 github.com/gosuri/uitable v0.0.4 + github.com/jasonsoft/go-short-id v0.0.0-20180410073244-6ed30cc4305d github.com/jinzhu/copier v0.4.0 github.com/spf13/cobra v1.7.0 github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.16.0 go.uber.org/automaxprocs v1.5.3 go.uber.org/zap v1.25.0 - golang.org/x/crypto v0.9.0 + golang.org/x/crypto v0.12.0 gorm.io/driver/mysql v1.5.1 gorm.io/gorm v1.25.4 ) require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect github.com/bytedance/sonic v1.9.1 // indirect github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect github.com/fatih/color v1.13.0 // indirect @@ -29,6 +32,7 @@ require ( github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/go-redis/redis v6.15.9+incompatible // indirect github.com/go-sql-driver/mysql v1.7.0 // indirect github.com/goccy/go-json v0.10.2 // indirect github.com/hashicorp/hcl v1.0.0 // indirect @@ -45,19 +49,24 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.28.0 // indirect github.com/pelletier/go-toml/v2 v2.0.8 // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/spf13/afero v1.9.5 // indirect github.com/spf13/cast v1.5.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/subosito/gotenv v1.4.2 // indirect + github.com/tidwall/gjson v1.14.4 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.11 // indirect go.uber.org/multierr v1.10.0 // indirect golang.org/x/arch v0.3.0 // indirect - golang.org/x/net v0.10.0 // indirect - golang.org/x/sys v0.8.0 // indirect - golang.org/x/text v0.9.0 // indirect + golang.org/x/net v0.14.0 // indirect + golang.org/x/sys v0.11.0 // indirect + golang.org/x/text v0.12.0 // indirect google.golang.org/protobuf v1.30.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect diff --git a/go.sum b/go.sum index 06fdedb..d41605b 100644 --- a/go.sum +++ b/go.sum @@ -38,6 +38,8 @@ cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3f dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 h1:DklsrG3dyBCFEj5IhUbnKptjxatkF07cF2ak3yi77so= github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2/go.mod h1:WaHUgvxTVq04UNunO+XhnAqY/wQc+bxr74GqbsZ/Jqw= github.com/benbjohnson/clock v1.3.0 h1:ip6w0uFQkncKQ979AypyG0ER7mqUSBdKLOgAle/AT8A= @@ -45,6 +47,8 @@ github.com/benbjohnson/clock v1.3.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZx github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/casbin/casbin/v2 v2.77.2 h1:yQinn/w9x8AswiwqwtrXz93VU48R1aYTXdHEx4RI3jM= +github.com/casbin/casbin/v2 v2.77.2/go.mod h1:mzGx0hYW9/ksOSpw3wNjk3NRAroq5VMFYUQ6G43iGPk= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= @@ -70,6 +74,8 @@ github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.4 h1:g2rn0vABPOOXmZUj+vbmUp0lPoXEMuhTpIluN0XL9UY= github.com/frankban/quicktest v1.14.4/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= @@ -89,8 +95,11 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= github.com/go-sql-driver/mysql v1.7.0 h1:ueSltNNllEqE3qcWBTD0iQd3IpL/6U+mJxLkazJ7YPc= github.com/go-sql-driver/mysql v1.7.0/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -105,6 +114,7 @@ github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFU github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -161,10 +171,13 @@ github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ 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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jasonsoft/go-short-id v0.0.0-20180410073244-6ed30cc4305d h1:wBbuyDOwfblqMAmbUWUuSufOOCGABj+7lU+fcuIR1/0= +github.com/jasonsoft/go-short-id v0.0.0-20180410073244-6ed30cc4305d/go.mod h1:si4BT002DRDG5t2w3jE9k65Yv+FP1XxcQe9I77QmaOY= github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -207,6 +220,17 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.28.0 h1:i2rg/p9n/UqIDAMFUJ6qIUUMcsqOuUHgbpbu235Vr1c= +github.com/onsi/gomega v1.28.0/go.mod h1:A1H2JE76sI14WIP57LMKj7FVfCHx3g3BcZVjJG8bjX8= github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= @@ -250,6 +274,12 @@ github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gt github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= +github.com/tidwall/gjson v1.14.4 h1:uo0p8EbA09J7RQaflQ1aBRffTR7xedD2bcIVSYxLnkM= +github.com/tidwall/gjson v1.14.4/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= @@ -282,8 +312,8 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= -golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= +golang.org/x/crypto v0.12.0 h1:tFM/ta59kqch6LlvYnPa0yx5a83cL2nHflFhYKvv9Yk= +golang.org/x/crypto v0.12.0/go.mod h1:NF0Gs7EO5K4qLn+Ylc+fih8BSTeIjAP05siRnAh98yw= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -319,6 +349,7 @@ golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/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-20180906233101-161cd47e91fd/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= @@ -339,6 +370,7 @@ golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/ golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= @@ -349,8 +381,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= -golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.14.0 h1:BONx9s002vGdD9umnlX1Po8vOZmrgH34qlHcD1MfK14= +golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -371,6 +403,7 @@ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJ 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/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -379,7 +412,10 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -401,6 +437,7 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -411,8 +448,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= -golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= +golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -422,8 +459,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= -golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.12.0 h1:k+n5B8goJNdU7hSvEtMUz3d1Q6D/XW4COJSJR6fN0mc= +golang.org/x/text v0.12.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -471,6 +508,7 @@ golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82u golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= @@ -573,9 +611,14 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/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= diff --git a/internal/dazBlog/biz/README.md b/internal/dazBlog/biz/README.md index 89363e0..c75ee45 100644 --- a/internal/dazBlog/biz/README.md +++ b/internal/dazBlog/biz/README.md @@ -1,9 +1,11 @@ ## biz layer - 业务逻辑层 +- business logic layer ## 开发流程 - [biz.go](./biz.go) 中存放需要在 biz 层实现的模块 - [user.go](./user/user.go) 是 user 模块在 biz 层实现具体方法 + - [post.go](./post/post.go) 是 post 模块在 biz 层实现具体方法 - 在构造 `model.UserM` 时, 使用 [copier](https://github.com/jinzhu/copier) 简化代码量 - 将 `CreateUserRequest` 结构体的定义文件 `user.go` 放置在 `pkg/api/dazBlog/v1` 目录下 - CreateUserRequest 对用户暴露, 作为 `POST /v1/users` 接口的请求 Body,将其放置在 pkg diff --git a/internal/dazBlog/biz/post/post.go b/internal/dazBlog/biz/post/post.go index e33dabe..988162d 100644 --- a/internal/dazBlog/biz/post/post.go +++ b/internal/dazBlog/biz/post/post.go @@ -7,17 +7,25 @@ package post import ( "context" + "errors" "github.com/Daz-3ux/dBlog/internal/dazBlog/store" "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/log" "github.com/Daz-3ux/dBlog/internal/pkg/model" v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" "github.com/jinzhu/copier" + "gorm.io/gorm" "regexp" ) -// PostBiz defines the methods implemented in the post module at the biz layer +// PostBiz defines the methods implemented in the post-module at the biz layer type PostBiz interface { - Create(ctx context.Context, r *v1.CreatePostRequest) error + Create(ctx context.Context, username string, r *v1.CreatePostRequest) (*v1.CreatePostResponse, error) + Update(ctx context.Context, username, postID string, r *v1.UpdatePostRequest) error + Delete(ctx context.Context, username, postID string) error + DeleteCollection(ctx context.Context, username string, postIDs []string) error + Get(ctx context.Context, username, postID string) (*v1.GetPostResponse, error) + List(ctx context.Context, username string, offset, limit int) (*v1.ListPostsResponse, error) } // postBiz implements the PostBiz interface @@ -32,16 +40,101 @@ func NewPostBiz(ds store.IStore) PostBiz { return &postBiz{ds: ds} } -func (p *postBiz) Create(ctx context.Context, r *v1.CreatePostRequest) error { +// Create is the implementation of the Create method defined in the PostBiz interface +func (p *postBiz) Create(ctx context.Context, username string, r *v1.CreatePostRequest) (*v1.CreatePostResponse, + error) { var postM model.PostM _ = copier.Copy(&postM, r) + postM.Username = username if err := p.ds.Posts().Create(ctx, &postM); err != nil { if match, _ := regexp.MatchString("Duplicate entry '.*' for key 'title'", err.Error()); match { - return errno.ErrUserAlreadyExist + return nil, errno.ErrTitleAlreadyExist } + return nil, err + } + + return &v1.CreatePostResponse{PostID: postM.PostID}, nil +} + +// Update is the implementation of the Update method defined in the PostBiz interface +func (p *postBiz) Update(ctx context.Context, username, postID string, r *v1.UpdatePostRequest) error { + postM, err := p.ds.Posts().Get(ctx, username, postID) + if err != nil { + return err + } + + if r.Title != nil { + postM.Title = *r.Title + } + if r.Content != nil { + postM.Content = *r.Content + } + + if err := p.ds.Posts().Update(ctx, postM); err != nil { + return err + } + + return nil +} + +// Delete is the implementation of the Delete method defined in the PostBiz interface +func (p *postBiz) Delete(ctx context.Context, username, postID string) error { + if err := p.ds.Posts().Delete(ctx, username, []string{postID}); err != nil { + return err + } + + return nil +} + +// DeleteCollection is the implementation of the DeleteCollection method defined in the PostBiz interface +func (p *postBiz) DeleteCollection(ctx context.Context, username string, postIDs []string) error { + if err := p.ds.Posts().Delete(ctx, username, postIDs); err != nil { return err } return nil } + +// Get is the implementation of the Get method defined in the PostBiz interface +func (p *postBiz) Get(ctx context.Context, username, postID string) (*v1.GetPostResponse, error) { + postM, err := p.ds.Posts().Get(ctx, username, postID) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errno.ErrPostNotFound + } + + return nil, err + } + + var resp v1.GetPostResponse + _ = copier.Copy(&resp, postM) + + resp.CreatedAt = postM.CreatedAt.Format("2006-01-02 15:04:05") + resp.UpdatedAt = postM.UpdatedAt.Format("2006-01-02 15:04:05") + + return &resp, nil +} + +// List is the implementation of the List method defined in the PostBiz interface +func (p *postBiz) List(ctx context.Context, username string, offset, limit int) (*v1.ListPostsResponse, error) { + count, list, err := p.ds.Posts().List(ctx, username, offset, limit) + if err != nil { + log.C(ctx).Errorw("Failed to list posts form storage", "err", err) + return nil, err + } + + posts := make([]*v1.PostInfo, 0, len(list)) + for _, post := range list { + posts = append(posts, &v1.PostInfo{ + Username: post.Username, + PostID: post.PostID, + Title: post.Title, + Content: post.Content, + CreatedAt: post.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: post.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + return &v1.ListPostsResponse{TotalCount: count, Posts: posts}, nil +} diff --git a/internal/dazBlog/biz/user/user.go b/internal/dazBlog/biz/user/user.go index 5e00f79..bdc6682 100644 --- a/internal/dazBlog/biz/user/user.go +++ b/internal/dazBlog/biz/user/user.go @@ -7,13 +7,16 @@ package user import ( "context" + "errors" "github.com/Daz-3ux/dBlog/internal/dazBlog/store" "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/log" "github.com/Daz-3ux/dBlog/internal/pkg/model" v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" "github.com/Daz-3ux/dBlog/pkg/auth" - token "github.com/Daz-3ux/dBlog/pkg/token" + "github.com/Daz-3ux/dBlog/pkg/token" "github.com/jinzhu/copier" + "gorm.io/gorm" "regexp" ) @@ -21,6 +24,10 @@ import ( // implement the specific implementations of the REST resources for the user type UserBiz interface { Create(ctx context.Context, r *v1.CreateUserRequest) error + Get(ctx context.Context, id string) (*v1.GetUserResponse, error) + List(ctx context.Context, offset, limit int) (*v1.ListUserResponse, error) + Update(ctx context.Context, username string, r *v1.UpdateUserRequest) error + Delete(ctx context.Context, username string) error ChangePassword(ctx context.Context, username string, r *v1.ChangePasswordRequest) error Login(ctx context.Context, r *v1.LoginRequest) (*v1.LoginResponse, error) } @@ -54,6 +61,84 @@ func (b *userBiz) Create(ctx context.Context, r *v1.CreateUserRequest) error { return nil } +// Get is the implementation of the `Get` method in the UserBiz interface +func (b *userBiz) Get(ctx context.Context, id string) (*v1.GetUserResponse, error) { + user, err := b.ds.Users().Get(ctx, id) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, errno.ErrUserNotFound + } + + return nil, err + } + + var resp v1.GetUserResponse + _ = copier.Copy(&resp, user) + + resp.CreatedAt = user.CreatedAt.Format("2006-01-02 15:04:05") + resp.UpdatedAt = user.UpdatedAt.Format("2006-01-02 15:04:05") + + return &resp, nil +} + +// List is the implementation of the `List` method of the UserBiz interface +func (b *userBiz) List(ctx context.Context, offset, limit int) (*v1.ListUserResponse, error) { + count, list, err := b.ds.Users().List(ctx, offset, limit) + if err != nil { + log.C(ctx).Errorw("failed to list users from storage", "err", err) + return nil, err + } + + users := make([]*v1.UserInfo, 0, len(list)) + for _, user := range list { + users = append(users, &v1.UserInfo{ + Username: user.Username, + Nickname: user.Nickname, + Email: user.Email, + Phone: user.Phone, + PostCount: user.PostCount, + CreatedAt: user.CreatedAt.Format("2006-01-02 15:04:05"), + UpdatedAt: user.UpdatedAt.Format("2006-01-02 15:04:05"), + }) + } + + log.C(ctx).Debugw("Get users from storage", "count", count) + + return &v1.ListUserResponse{TotalCount: count, Users: users}, nil +} + +// Update is the implementation of the `Update` method of the UserBiz interface +func (b *userBiz) Update(ctx context.Context, username string, user *v1.UpdateUserRequest) error { + userM, err := b.ds.Users().Get(ctx, username) + if err != nil { + return err + } + + if user.Email != "" { + userM.Email = user.Email + } + if user.Nickname != "" { + userM.Nickname = user.Nickname + } + if user.Phone != "" { + userM.Phone = user.Phone + } + + if err := b.ds.Users().Update(ctx, userM); err != nil { + return err + } + + return nil +} + +func (b *userBiz) Delete(ctx context.Context, username string) error { + if err := b.ds.Users().Delete(ctx, username); err != nil { + return err + } + + return nil +} + // ChangePassword is the implementation of the `ChangePassword` method of the UserBiz interface func (b *userBiz) ChangePassword(ctx context.Context, username string, r *v1.ChangePasswordRequest) error { userM, err := b.ds.Users().Get(ctx, username) diff --git a/internal/dazBlog/controller/v1/post/create.go b/internal/dazBlog/controller/v1/post/create.go index c8d349e..488e71e 100644 --- a/internal/dazBlog/controller/v1/post/create.go +++ b/internal/dazBlog/controller/v1/post/create.go @@ -8,12 +8,14 @@ package post import ( "github.com/Daz-3ux/dBlog/internal/pkg/core" "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/known" "github.com/Daz-3ux/dBlog/internal/pkg/log" v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" "github.com/asaskevich/govalidator" "github.com/gin-gonic/gin" ) +// Create a new post func (ctrl *PostController) Create(c *gin.Context) { log.C(c).Infow("Create post function called") @@ -28,10 +30,11 @@ func (ctrl *PostController) Create(c *gin.Context) { return } - if err := ctrl.b.Posts().Create(c, &r); err != nil { + resp, err := ctrl.b.Posts().Create(c, c.GetString(known.XUsernameKey), &r) + if err != nil { core.WriteResponse(c, err, nil) return } - core.WriteResponse(c, nil, nil) + core.WriteResponse(c, nil, resp) } diff --git a/internal/dazBlog/controller/v1/post/delete.go b/internal/dazBlog/controller/v1/post/delete.go new file mode 100644 index 0000000..ee0a43f --- /dev/null +++ b/internal/dazBlog/controller/v1/post/delete.go @@ -0,0 +1,26 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package post + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + "github.com/gin-gonic/gin" +) + +func (ctrl *PostController) Delete(c *gin.Context) { + log.C(c).Infow("Delete post function called") + + postID := c.Param("PostId") + if err := ctrl.b.Posts().Delete(c, c.GetString(known.XUsernameKey), postID); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, nil) +} diff --git a/internal/dazBlog/controller/v1/post/delete_collection.go b/internal/dazBlog/controller/v1/post/delete_collection.go new file mode 100644 index 0000000..d4114c1 --- /dev/null +++ b/internal/dazBlog/controller/v1/post/delete_collection.go @@ -0,0 +1,26 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package post + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + "github.com/gin-gonic/gin" +) + +func (ctrl *PostController) DeleteCollection(c *gin.Context) { + log.C(c).Infow("Batch delete post function called") + + postIDs := c.QueryArray("postID") + if err := ctrl.b.Posts().DeleteCollection(c, c.GetString(known.XUsernameKey), postIDs); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, nil) +} diff --git a/internal/dazBlog/controller/v1/post/get.go b/internal/dazBlog/controller/v1/post/get.go new file mode 100644 index 0000000..5201f3c --- /dev/null +++ b/internal/dazBlog/controller/v1/post/get.go @@ -0,0 +1,27 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package post + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + "github.com/gin-gonic/gin" +) + +func (ctrl *PostController) Get(c *gin.Context) { + log.C(c).Infow("Get post function called") + + postID := c.Param("postID") + post, err := ctrl.b.Posts().Get(c, c.GetString(known.XUsernameKey), postID) + if err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, post) +} diff --git a/internal/dazBlog/controller/v1/post/list.go b/internal/dazBlog/controller/v1/post/list.go new file mode 100644 index 0000000..06dffad --- /dev/null +++ b/internal/dazBlog/controller/v1/post/list.go @@ -0,0 +1,35 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package post + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" + "github.com/gin-gonic/gin" +) + +func (ctrl *PostController) List(c *gin.Context) { + log.C(c).Infow("List post function called") + + var r v1.ListPostsRequest + if err := c.ShouldBindJSON(&r); err != nil { + core.WriteResponse(c, errno.ErrBind, nil) + + return + } + + resp, err := ctrl.b.Posts().List(c, c.GetString(known.XUsernameKey), r.Offset, r.Limit) + if err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, resp) +} diff --git a/internal/dazBlog/controller/v1/post/update.go b/internal/dazBlog/controller/v1/post/update.go new file mode 100644 index 0000000..a9b8596 --- /dev/null +++ b/internal/dazBlog/controller/v1/post/update.go @@ -0,0 +1,42 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package post + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/known" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" + "github.com/asaskevich/govalidator" + "github.com/gin-gonic/gin" +) + +func (ctrl *PostController) Update(c *gin.Context) { + log.C(c).Infow("Update post function called") + + var r v1.UpdatePostRequest + if err := c.ShouldBindJSON(&r); err != nil { + core.WriteResponse(c, errno.ErrBind, nil) + + return + } + + if _, err := govalidator.ValidateStruct(r); err != nil { + core.WriteResponse(c, errno.ErrInvalidParameter.SetMessage(err.Error()), nil) + + return + } + + postID := c.Param("postID") + if err := ctrl.b.Posts().Update(c, c.GetString(known.XUsernameKey), postID, &r); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, nil) +} diff --git a/internal/dazBlog/controller/v1/user/create.go b/internal/dazBlog/controller/v1/user/create.go index 2946488..f6e5b99 100644 --- a/internal/dazBlog/controller/v1/user/create.go +++ b/internal/dazBlog/controller/v1/user/create.go @@ -14,6 +14,7 @@ import ( "github.com/gin-gonic/gin" ) +// Create a new user account. func (ctrl *UserController) Create(c *gin.Context) { log.C(c).Infow("Create user function called") diff --git a/internal/dazBlog/controller/v1/user/delete.go b/internal/dazBlog/controller/v1/user/delete.go new file mode 100644 index 0000000..7572b5f --- /dev/null +++ b/internal/dazBlog/controller/v1/user/delete.go @@ -0,0 +1,26 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package user + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + "github.com/gin-gonic/gin" +) + +func (ctrl *UserController) Delete(c *gin.Context) { + log.C(c).Infow("Delete user function called") + + username := c.Param("name") + + if err := ctrl.b.Users().Delete(c, username); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, nil) +} diff --git a/internal/dazBlog/controller/v1/user/get.go b/internal/dazBlog/controller/v1/user/get.go new file mode 100644 index 0000000..8fccd04 --- /dev/null +++ b/internal/dazBlog/controller/v1/user/get.go @@ -0,0 +1,27 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package user + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + "github.com/gin-gonic/gin" +) + +// Get user's information +func (ctrl *UserController) Get(c *gin.Context) { + log.C(c).Infow("Get user function called") + + id := c.Param("id") + user, err := ctrl.b.Users().Get(c, id) + if err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, user) +} diff --git a/internal/dazBlog/controller/v1/user/list.go b/internal/dazBlog/controller/v1/user/list.go new file mode 100644 index 0000000..fd4a0fc --- /dev/null +++ b/internal/dazBlog/controller/v1/user/list.go @@ -0,0 +1,34 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package user + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" + "github.com/gin-gonic/gin" +) + +// List return users list, only root user can call this function +func (ctrl *UserController) List(c *gin.Context) { + log.C(c).Infow("List user function called") + + var r v1.ListUserRequest + if err := c.ShouldBindJSON(&r); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + resp, err := ctrl.b.Users().List(c, r.Offset, r.Limit) + if err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, resp) +} diff --git a/internal/dazBlog/controller/v1/user/update.go b/internal/dazBlog/controller/v1/user/update.go new file mode 100644 index 0000000..53934e7 --- /dev/null +++ b/internal/dazBlog/controller/v1/user/update.go @@ -0,0 +1,40 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package user + +import ( + "github.com/Daz-3ux/dBlog/internal/pkg/core" + "github.com/Daz-3ux/dBlog/internal/pkg/errno" + "github.com/Daz-3ux/dBlog/internal/pkg/log" + v1 "github.com/Daz-3ux/dBlog/pkg/api/dazBlog/v1" + "github.com/asaskevich/govalidator" + "github.com/gin-gonic/gin" +) + +func (ctrl *UserController) Update(c *gin.Context) { + log.C(c).Infow("Update user function called") + + var r v1.UpdateUserRequest + if err := c.ShouldBindJSON(&r); err != nil { + core.WriteResponse(c, errno.ErrBind, nil) + + return + } + + if _, err := govalidator.ValidateStruct(r); err != nil { + core.WriteResponse(c, errno.ErrInvalidParameter.SetMessage(err.Error()), nil) + + return + } + + if err := ctrl.b.Users().Update(c, c.Param("name"), &r); err != nil { + core.WriteResponse(c, err, nil) + + return + } + + core.WriteResponse(c, nil, nil) +} diff --git a/internal/dazBlog/router.go b/internal/dazBlog/router.go index a2a1a28..8d70628 100644 --- a/internal/dazBlog/router.go +++ b/internal/dazBlog/router.go @@ -40,13 +40,35 @@ func installRouters(g *gin.Engine) error { // create user's route group userv1 := v1.Group("/users") { + // create user userv1.POST("", uc.Create) + // change user password userv1.PUT(":name/change-password", uc.ChangePassword) + // get user info + userv1.GET(":name", uc.Get) + // update user info + userv1.PUT(":name", uc.Update) + // list all users, only root user can access + userv1.GET("", uc.List) + userv1.DELETE(":name", uc.Delete) + // middleware userv1.Use(mw.Authn()) } + postv1 := v1.Group("/posts") { + // create post postv1.POST("", pc.Create) + // get post + postv1.GET(":postID", pc.Get) + // update post + postv1.PUT(":postID", pc.Update) + // delete post + postv1.DELETE(":postID", pc.Delete) + // batch delete posts + postv1.DELETE("", pc.DeleteCollection) + // list posts + postv1.GET("", pc.List) } } diff --git a/internal/dazBlog/store/helper.go b/internal/dazBlog/store/helper.go new file mode 100644 index 0000000..a52c55d --- /dev/null +++ b/internal/dazBlog/store/helper.go @@ -0,0 +1,17 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package store + +const defaultLimitNumber = 20 + +// defaultLimit sets the default number of records to be returned +func defaultLimit(limit int) int { + if limit == 0 { + limit = defaultLimitNumber + } + + return limit +} diff --git a/internal/dazBlog/store/post.go b/internal/dazBlog/store/post.go index 4e4ab67..523bf47 100644 --- a/internal/dazBlog/store/post.go +++ b/internal/dazBlog/store/post.go @@ -7,6 +7,7 @@ package store import ( "context" + "errors" "github.com/Daz-3ux/dBlog/internal/pkg/model" "gorm.io/gorm" ) @@ -14,6 +15,10 @@ import ( // PostStore defines the methods that need to be implemented by the post model in the store layer type PostStore interface { Create(ctx context.Context, user *model.PostM) error + Get(ctx context.Context, username string, postID string) (*model.PostM, error) + Update(ctx context.Context, user *model.PostM) error + List(ctx context.Context, username string, offset, limit int) (int64, []*model.PostM, error) + Delete(ctx context.Context, username string, postIDs []string) error } // posts is the implementation of PostStore interface @@ -28,6 +33,49 @@ func newPosts(db *gorm.DB) *posts { return &posts{db} } +// Create a new post func (p *posts) Create(ctx context.Context, post *model.PostM) error { return p.db.Create(&post).Error } + +// Get a post by username and postID +func (p *posts) Get(ctx context.Context, username string, postID string) (*model.PostM, error) { + var post model.PostM + if err := p.db.Where("username = ? AND postID = ?", username, postID).First(&post).Error; err != nil { + return nil, err + } + return &post, nil +} + +// Update a post +func (p *posts) Update(ctx context.Context, post *model.PostM) error { + return p.db.Save(post).Error +} + +func (p *posts) List(ctx context.Context, username string, offset, limit int) (count int64, ret []*model.PostM, err error) { + err = p.db.Where("username = ?", username). + // set the offset and limit + Offset(offset). + Limit(defaultLimit(limit)). + // descending order the results by id + Order("id desc"). + // store the result ti ret + Find(&ret). + // reset the offset and limit + Offset(-1). + Limit(-1). + // calculate the total number of results and store to count + Count(&count). + Error + + return +} + +func (p *posts) Delete(ctx context.Context, username string, postIDs []string) error { + err := p.db.Where("username = ? AND postID in (?)", username, postIDs).Delete(&model.PostM{}).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + return nil +} diff --git a/internal/dazBlog/store/user.go b/internal/dazBlog/store/user.go index d7ed752..e2f8abb 100644 --- a/internal/dazBlog/store/user.go +++ b/internal/dazBlog/store/user.go @@ -7,6 +7,7 @@ package store import ( "context" + "errors" "github.com/Daz-3ux/dBlog/internal/pkg/model" "gorm.io/gorm" ) @@ -14,8 +15,10 @@ import ( // UserStore defines the methods that need to be implemented by the user model in the store layer type UserStore interface { Create(ctx context.Context, user *model.UserM) error - Get(ctx context.Context, username string) (*model.UserM, error) + Get(ctx context.Context, id string) (*model.UserM, error) Update(ctx context.Context, user *model.UserM) error + List(ctx context.Context, offset, limit int) (int64, []*model.UserM, error) + Delete(ctx context.Context, username string) error } // users is the implementation of UserStore interface @@ -36,9 +39,9 @@ func (u *users) Create(ctx context.Context, user *model.UserM) error { } // Get returns the user record with the specified username -func (u *users) Get(ctx context.Context, username string) (*model.UserM, error) { +func (u *users) Get(ctx context.Context, id string) (*model.UserM, error) { var user model.UserM - if err := u.db.Where("username = ?", username).First(&user).Error; err != nil { + if err := u.db.Where("id = ?", id).First(&user).Error; err != nil { return nil, err } return &user, nil @@ -48,3 +51,26 @@ func (u *users) Get(ctx context.Context, username string) (*model.UserM, error) func (u *users) Update(ctx context.Context, user *model.UserM) error { return u.db.Save(&user).Error } + +func (u *users) List(ctx context.Context, offset, limit int) (count int64, ret []*model.UserM, err error) { + err = u.db.Offset(offset).Limit(defaultLimit(limit)). + // descending order the results by id + Order("id DESC"). + // find the results + Find(&ret). + Offset(-1). + Limit(-1). + Count(&count). + Error + + return +} + +func (u *users) Delete(ctx context.Context, username string) error { + err := u.db.Where("username = ?", username).Delete(&model.UserM{}).Error + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { + return err + } + + return nil +} diff --git a/internal/pkg/errno/post.go b/internal/pkg/errno/post.go new file mode 100644 index 0000000..c18d55b --- /dev/null +++ b/internal/pkg/errno/post.go @@ -0,0 +1,9 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package errno + +// ErrPostNotFound is the error when the post is not found +var ErrPostNotFound = &Errno{HTTP: 404, Code: "ResourceNotFound.PostNotFound", Message: "post not found"} diff --git a/internal/pkg/errno/user.go b/internal/pkg/errno/user.go index a66ab63..c843dc8 100644 --- a/internal/pkg/errno/user.go +++ b/internal/pkg/errno/user.go @@ -8,6 +8,7 @@ package errno var ( // ErrUserAlreadyExist represents user already exists ErrUserAlreadyExist = &Errno{HTTP: 400, Code: "FailedOperation.UserAlreadyExist", Message: "User already exists."} + ErrTitleAlreadyExist = &Errno{HTTP: 400, Code: "FailedOperation.TitleAlreadyExist", Message: "Title already exists."} ErrUserNotFound = &Errno{HTTP: 404, Code: "ResourceNotFound.UserNotFound", Message: "User not found."} ErrPasswordIncorrect = &Errno{HTTP: 401, Code: "InvalidParameter.PasswordIncorrect", Message: "Password incorrect."} ) diff --git a/internal/pkg/model/post.go b/internal/pkg/model/post.go index 4e94781..d23e9c4 100644 --- a/internal/pkg/model/post.go +++ b/internal/pkg/model/post.go @@ -5,7 +5,11 @@ package model -import "time" +import ( + "github.com/Daz-3ux/dBlog/pkg/util/id" + "gorm.io/gorm" + "time" +) type PostM struct { ID int64 `gorm:"column:id;primary_key"` // unique id for the post, server as the primary key @@ -21,3 +25,9 @@ type PostM struct { func (p *PostM) TableName() string { return "posts" } + +func (p *PostM) BeforeCreate(tx *gorm.DB) error { + p.PostID = "post-" + id.GenShortID() + + return nil +} diff --git a/internal/pkg/model/user.go b/internal/pkg/model/user.go index 6624bd3..65b0d8d 100644 --- a/internal/pkg/model/user.go +++ b/internal/pkg/model/user.go @@ -13,6 +13,7 @@ import ( type UserM struct { ID int64 `gorm:"column:id;primary_key"` // unique id for the user, server as the primary key + PostCount int64 `gorm:"column:postCount"` // number of posts the user has Username string `gorm:"column:username"` // username of the user Password string `gorm:"column:password"` // password of the user Nickname string `gorm:"column:nickname"` // nickname of the user diff --git a/pkg/api/dazBlog/v1/post.go b/pkg/api/dazBlog/v1/post.go index 7d536e0..47ed3cc 100644 --- a/pkg/api/dazBlog/v1/post.go +++ b/pkg/api/dazBlog/v1/post.go @@ -5,8 +5,50 @@ package v1 +// CreatePostRequest specifies the request parameters for +// `POST /v1/posts` type CreatePostRequest struct { - Username string `json:"username" valid:"alphanum,required,stringlength(1|255)"` - Title string `json:"title" valid:"required,stringlength(1|255)"` - Content string `json:"content" valid:"required"` + Title string `json:"title" valid:"required,stringlength(1|255)"` + Content string `json:"content" valid:"required"` +} + +// CreatePostResponse specifies the response parameters for +// `POST /v1/posts` +type CreatePostResponse struct { + PostID string `json:"post_id"` +} + +// GetPostResponse specifies the request parameters for +// `GET /v1/posts/{post_id}` +type GetPostResponse PostInfo + +// PostInfo is the post's detail info +type PostInfo struct { + Username string `json:"username,omitempty"` + PostID string `json:"post_id,omitempty"` + Title string `json:"title"` + Content string `json:"content"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// UpdatePostRequest specifies the request parameters for +// `PUT /v1/posts` +type UpdatePostRequest struct { + Title *string `json:"title" valid:"stringlength(1|255)"` + Content *string `json:"content" valid:"stringlength(1|65535)"` +} + +// ListPostsRequest specifies the request parameters for +// `GET /v1/posts` +type ListPostsRequest struct { + Offset int `form:"offset"` + Limit int `form:"limit"` +} + +// ListPostsResponse specifies the response parameters for +// `GET /v1/posts` +type ListPostsResponse struct { + TotalCount int64 `json:"totalCount"` + Posts []*PostInfo `json:"posts"` } diff --git a/pkg/api/dazBlog/v1/user.go b/pkg/api/dazBlog/v1/user.go index d14383e..516f1dd 100644 --- a/pkg/api/dazBlog/v1/user.go +++ b/pkg/api/dazBlog/v1/user.go @@ -5,24 +5,69 @@ package v1 -type LoginRequest struct { +// CreateUserRequest specifies the request parameters for +// `POST /v1/users` +type CreateUserRequest struct { Username string `json:"username" valid:"alphanum,required,stringlength(1|255)"` Password string `json:"password" valid:"required,stringlength(6|18)"` + Nickname string `json:"nickname" valid:"required,stringlength(1|255)"` + Email string `json:"email" valid:"required,email"` + Phone string `json:"phone" valid:"required,numeric,stringlength(11|11)"` } -type LoginResponse struct { - Token string `json:"token"` +// GetUserResponse specifies the response parameters for +// `GET /v1/users/{username}` +type GetUserResponse UserInfo + +// UserInfo is the user's all information +type UserInfo struct { + Username string `json:"username"` + Nickname string `json:"nickname"` + Email string `json:"email"` + Phone string `json:"phone"` + PostCount int64 `json:"postCount"` + CreatedAt string `json:"createdAt"` + UpdatedAt string `json:"updatedAt"` +} + +// ListUserRequest specifies the request parameters for +// `GET /v1/users` +type ListUserRequest struct { + Offset int `form:"offset" valid:"numeric"` + Limit int `form:"limit" valid:"numeric"` +} + +// ListUserResponse specifies the response parameters for +// `GET /v1/users` +type ListUserResponse struct { + TotalCount int64 `json:"totalCount" valid:"numeric"` + Users []*UserInfo `json:"users"` } +// UpdateUserRequest specifies the request parameters for +// `PUT /v1/users/{username}` +type UpdateUserRequest struct { + Nickname string `json:"nickname" valid:"required,stringlength(1|255)"` + Email string `json:"email" valid:"email"` + Phone string `json:"phone" valid:"numeric,stringlength(11|11)"` +} + +// ChangePasswordRequest specifies the request parameters for +// `POST /v1/users/{username}/change-password` type ChangePasswordRequest struct { OldPassword string `json:"oldPassword" valid:"required,stringlength(6|18)"` NewPassword string `json:"newPassword" valid:"required,stringlength(6|18)"` } -type CreateUserRequest struct { +// LoginRequest specifies the request parameters for +// `POST /login` +type LoginRequest struct { Username string `json:"username" valid:"alphanum,required,stringlength(1|255)"` Password string `json:"password" valid:"required,stringlength(6|18)"` - Nickname string `json:"nickname" valid:"required,stringlength(1|255)"` - Email string `json:"email" valid:"required,email"` - Phone string `json:"phone" valid:"required,numeric,stringlength(11|11)"` +} + +// LoginResponse specifies the response parameters for +// `POST /login` +type LoginResponse struct { + Token string `json:"token"` } diff --git a/pkg/auth/authz.go b/pkg/auth/authz.go new file mode 100644 index 0000000..42407f0 --- /dev/null +++ b/pkg/auth/authz.go @@ -0,0 +1,30 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package auth + +import ( + "github.com/casbin/casbin/v2" +) + +const ( + // casbin access control model + aclModel = `[request_definition] +r = sub, obj, act + +[policy_definition] +p = sub, obj, act + +[policy_effect] +e = some(where (p.eft == allow)) + +[matchers] +m = r.sub == p.sub && keyMatch(r.obj, p.obj) && regexMatch(r.act, p.act)` +) + +// Authz defines a adapter for casbin +type Authz struct { + *casbin.SyncedEnforcer +} diff --git a/pkg/util/id/id.go b/pkg/util/id/id.go new file mode 100644 index 0000000..debac6d --- /dev/null +++ b/pkg/util/id/id.go @@ -0,0 +1,29 @@ +// Copyright 2023 daz-3ux(杨鹏达) . All rights reserved. +// Use of this source code is governed by a MIT style +// license that can be found in the LICENSE file. The original repo for +// this file is https://github.com/Daz-3ux/dBlog. + +package id + +import ( + shortid "github.com/jasonsoft/go-short-id" +) + +func GenShortID() string { + opt := shortid.Options{ + Number: 4, + StartWithYear: true, + EndWithHost: false, + } + + return toLower(shortid.Generate(opt)) +} + +func toLower(ss string) string { + var lower string + for _, s := range ss { + lower += string(s | ' ') + } + + return lower +}