From 8837b186f10b672e581833dac21dae7b2e865bf9 Mon Sep 17 00:00:00 2001 From: goozp Date: Thu, 10 Sep 2020 01:24:11 +0800 Subject: [PATCH] refactor media and user; replace gin default logger and recovery; optimizate a lot of code structure --- README.md | 2 +- README_EN.md | 2 +- docs/CHANGELOG.md | 4 +- go.mod | 15 +- go.sum | 51 ++-- init/scheme/db_puti.sql | 2 +- internal/backend/handler/article/create.go | 10 +- internal/backend/handler/article/list.go | 4 +- internal/backend/handler/article/update.go | 4 - internal/backend/handler/auth/info.go | 8 +- internal/backend/handler/auth/login.go | 30 +-- internal/backend/handler/media/delete.go | 7 +- internal/backend/handler/media/detail.go | 4 +- internal/backend/handler/media/list.go | 18 +- internal/backend/handler/media/update.go | 35 +-- internal/backend/handler/media/upload.go | 136 +---------- internal/backend/handler/option/update.go | 4 - internal/backend/handler/page/create.go | 4 - internal/backend/handler/page/list.go | 4 +- internal/backend/handler/page/update.go | 4 - internal/backend/handler/subject/create.go | 5 - internal/backend/handler/subject/delete.go | 5 - internal/backend/handler/subject/update.go | 5 - internal/backend/handler/taxonomy/create.go | 5 - internal/backend/handler/taxonomy/delete.go | 5 - internal/backend/handler/taxonomy/update.go | 5 - internal/backend/handler/user/avator.go | 38 +-- internal/backend/handler/user/create.go | 66 +----- internal/backend/handler/user/delete.go | 9 +- internal/backend/handler/user/get.go | 2 +- internal/backend/handler/user/list.go | 21 +- internal/backend/handler/user/update.go | 84 +------ internal/backend/service/auth.go | 33 +++ internal/backend/service/media.go | 197 +++++++++++++--- internal/backend/service/post.go | 24 +- internal/backend/service/statistics.go | 12 +- internal/backend/service/subejct.go | 11 +- internal/backend/service/system.go | 33 +-- internal/backend/service/taxonomy.go | 4 +- internal/backend/service/user.go | 219 +++++++++++++----- internal/dao/dao.go | 20 ++ internal/dao/media.go | 79 +++++++ internal/dao/user.go | 173 ++++++++++++++ internal/frontend/handler/article.go | 5 +- internal/frontend/handler/base.go | 6 +- internal/frontend/handler/subject.go | 5 +- internal/frontend/service/archive.go | 8 +- internal/frontend/service/article.go | 18 +- internal/frontend/service/page.go | 2 +- internal/frontend/service/subject.go | 13 +- internal/model/media.go | 74 +++--- internal/model/model.go | 10 +- internal/model/option.go | 2 +- internal/model/post.go | 51 ++-- internal/model/show.go | 15 +- internal/model/subject.go | 25 +- internal/model/subjectRelationship.go | 4 +- internal/model/taxonomy.go | 12 +- internal/model/taxonomyRelationship.go | 4 +- internal/model/user.go | 103 +++----- internal/pkg/config/config.go | 5 +- internal/pkg/config/path.go | 8 + internal/pkg/config/section.go | 3 +- internal/pkg/counter/ticker.go | 2 +- internal/pkg/db/db.go | 49 ++-- internal/pkg/logger/logger.go | 93 +++++--- internal/pkg/option/option.go | 14 +- internal/pkg/token/token.go | 8 +- .../middleware/api}/auth.go | 2 +- .../middleware/api}/header.go | 4 +- .../middleware/api}/requestid.go | 2 +- internal/routers/middleware/logger.go | 41 ++++ internal/routers/middleware/recovery.go | 71 ++++++ .../middleware/view}/renderer.go | 4 +- internal/routers/router.go | 31 ++- internal/utils/convert.go | 51 ++++ internal/utils/file.go | 17 ++ internal/utils/pagination.go | 2 +- .../utils/{idGenerator.go => requestId.go} | 6 - internal/utils/time.go | 41 ++-- main.go | 69 +++--- 81 files changed, 1309 insertions(+), 974 deletions(-) create mode 100644 internal/dao/dao.go create mode 100644 internal/dao/media.go create mode 100644 internal/dao/user.go rename internal/{backend/middleware => routers/middleware/api}/auth.go (96%) rename internal/{backend/middleware => routers/middleware/api}/header.go (97%) rename internal/{backend/middleware => routers/middleware/api}/requestid.go (96%) create mode 100644 internal/routers/middleware/logger.go create mode 100644 internal/routers/middleware/recovery.go rename internal/{frontend/middleware => routers/middleware/view}/renderer.go (96%) create mode 100644 internal/utils/convert.go rename internal/utils/{idGenerator.go => requestId.go} (72%) diff --git a/README.md b/README.md index 384b351..557a5a9 100644 --- a/README.md +++ b/README.md @@ -182,7 +182,7 @@ Detailed changes for each release are documented in the [changelog file]((https: | 依赖 | 关于 | | :----- | :----- | | [gin-gonic/gin](https://github.com/gin-gonic/gin) | HTTP web framework written in Go | -| [jinzhu/gorm](https://github.com/jinzhu/gorm) | The ORM library for Golang| +| [go-gorm/gorm](https://github.com/go-gorm/gorm) | The ORM library for Golang| | [patrickmn/go-cache](https://github.com/patrickmn/go-cache) | An in-memory key:value store/cache| | [spf13/viper](https://github.com/spf13/viper) | Complete configuration solution| | [go.uber.org/zap](https://go.uber.org/zap) | Fast, structured, leveled logging| diff --git a/README_EN.md b/README_EN.md index bdbebd9..ab58f6e 100644 --- a/README_EN.md +++ b/README_EN.md @@ -180,7 +180,7 @@ Thanks for these great open source libraries: | Dependency | About | | :----- | :----- | | [gin-gonic/gin](https://github.com/gin-gonic/gin) | HTTP web framework written in Go | -| [jinzhu/gorm](https://github.com/jinzhu/gorm) | The ORM library for Golang| +| [go-gorm/gorm](https://github.com/go-gorm/gorm) | The ORM library for Golang| | [vuejs/vue](https://github.com/vuejs/vue) | JavaScript framework for building UI on the web | | [ElemeFE/element](https://github.com/ElemeFE/element) | A Vue.js 2.0 UI Toolkit for Web | | [PanJiaChen/vue-element-admin](https://github.com/PanJiaChen/vue-element-admin) | A front-end management background integration solution | diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index b21e95d..70a0b05 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -1,11 +1,13 @@ # Changelog -## v0.2.3 +## v0.3.0 ### 功能和优化 - 优化 Lin 主题 Latex 公式样式在移动端的展示 - 移除 vendor 目录和依赖,使用 goproxy - 支持 Github Actions +- 大量基础组件优化和重构 +- 升级 GROM 到 v2 ### Bug 修复 diff --git a/go.mod b/go.mod index f29dd1a..9b7fda1 100644 --- a/go.mod +++ b/go.mod @@ -8,18 +8,17 @@ require ( github.com/fsnotify/fsnotify v1.4.9 github.com/gin-gonic/gin v1.6.3 github.com/go-ole/go-ole v1.2.1 // indirect - github.com/go-sql-driver/mysql v1.5.0 - github.com/google/uuid v1.0.0 - github.com/jinzhu/gorm v1.9.16 + github.com/google/uuid v1.1.2 github.com/patrickmn/go-cache v2.1.0+incompatible - github.com/shirou/gopsutil v2.18.12+incompatible - github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 // indirect + github.com/shirou/gopsutil v2.20.8+incompatible github.com/spf13/afero v1.2.1 // indirect github.com/spf13/jwalterweatherman v1.1.0 // indirect github.com/spf13/pflag v1.0.5 github.com/spf13/viper v1.7.1 - github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf - go.uber.org/zap v1.15.0 - golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd + go.uber.org/zap v1.16.0 + golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a + golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f // indirect gopkg.in/natefinch/lumberjack.v2 v2.0.0 + gorm.io/driver/mysql v1.0.1 + gorm.io/gorm v1.20.0 ) diff --git a/go.sum b/go.sum index 275cae0..e7975b7 100644 --- a/go.sum +++ b/go.sum @@ -15,12 +15,10 @@ github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f h1:5ZfJxyXo8KyX8DgGXC5B7ILL8y51fci/qYz2B4j8iLY= github.com/StackExchange/wmi v0.0.0-20180725035823-b12b22c5341f/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= 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= @@ -38,13 +36,9 @@ github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfc github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd h1:83Wprp6ROGeiHFAP8WJdI2RoxALQYgdllERc3N5N2DM= -github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= -github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= @@ -74,8 +68,6 @@ github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LB github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= -github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= @@ -95,8 +87,8 @@ github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXi github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0 h1:b4Gk+7WdP/d3HZH8EJsZpvV7EtDOgaZLtnaNGIu1adA= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= @@ -126,12 +118,10 @@ github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= -github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= -github.com/jinzhu/now v1.0.1 h1:HjfetcXq097iXP0uoPCdnM4Efp5/9MsM0/M+XOTeR3M= -github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.1 h1:g39TucaRWyV3dwDO++eEc6qf8TVIQ/Da48WmqjZ3i7E= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= @@ -151,16 +141,12 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= -github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= -github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4= github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= -github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= @@ -203,10 +189,8 @@ github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6So github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= -github.com/shirou/gopsutil v2.18.12+incompatible h1:1eaJvGomDnH74/5cF4CTmTbLHAriGFsTZppLXDX93OM= -github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4 h1:udFKJ0aHUL60LboW/A+DfgoHVedieIzIXE8uylPue0U= -github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= +github.com/shirou/gopsutil v2.20.8+incompatible h1:8c7Atn0FAUZJo+f4wYbN0iVpdWniCQk7IYwGtgdh1mY= +github.com/shirou/gopsutil v2.20.8+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= @@ -240,8 +224,6 @@ github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJy github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s= github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf h1:Z2X3Os7oRzpdJ75iPqWZc0HeJWFYNCvKsfpQwFpRNTA= -github.com/teris-io/shortid v0.0.0-20171029131806-771a37caa5cf/go.mod h1:M8agBzgqHIhgj7wEn9/0hJUZcrvt9VY+Ln+S1I5Mha0= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= @@ -262,16 +244,15 @@ go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee h1:0mgffUl7nfd+FpvXMVz4IDEa go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= go.uber.org/zap v1.10.0 h1:ORx85nbTijNz8ljznvCMR1ZBIPKFn3jQrag10X2AsuM= go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.15.0 h1:ZZCA22JRF2gQE5FoNmhmrf7jeJJ2uhqDUNRYKm8dvmM= -go.uber.org/zap v1.15.0/go.mod h1:Mb2vm2krFEG5DV0W9qcHBYFtp/Wku1cvYaqPsS/WYfc= +go.uber.org/zap v1.16.0 h1:uFRZXykJGK9lLY4HtgSw44DnIcAM+kRBP7x5m+NpAOM= +go.uber.org/zap v1.16.0/go.mod h1:MA8QOfq0BHJwdXa996Y4dYkAqRKB8/1K1QMMZVaNZjQ= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/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-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd h1:GGJVjV8waZKRHrgwvtH66z9ZGVurTD1MT0n1Bb+q4aM= -golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= 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= @@ -291,7 +272,6 @@ golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 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-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -306,8 +286,6 @@ golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= 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= @@ -332,8 +310,8 @@ golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200116001909-b77594299b42 h1:vEOn+mP2zCOVzKckCZy6YsCtDblrpj/w7B9nxGNELpg= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd h1:xhmwyvizuTgC2qz7ZlMluP20uW+C3Rm0FD/WLDX8884= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f h1:Fqb3ao1hUmOR3GkUOg/Y+BadLwykBIzs5q8Ez2SbHyc= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -401,6 +379,11 @@ 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.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gorm.io/driver/mysql v1.0.1 h1:omJoilUzyrAp0xNoio88lGJCroGdIOen9hq2A/+3ifw= +gorm.io/driver/mysql v1.0.1/go.mod h1:KtqSthtg55lFp3S5kUXqlGaelnWpKitn4k1xZTnoiPw= +gorm.io/gorm v1.9.19/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.0 h1:qfIlyaZvrF7kMWY3jBdEBXkXJ2M5MFYMTppjILxS3fQ= +gorm.io/gorm v1.20.0/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/init/scheme/db_puti.sql b/init/scheme/db_puti.sql index 9c32abf..137db8e 100644 --- a/init/scheme/db_puti.sql +++ b/init/scheme/db_puti.sql @@ -347,7 +347,7 @@ CREATE TABLE `pt_user` ( `email` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '邮箱', `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '头像', `page_url` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT '' COMMENT '主页链接', - `status` int(11) NOT NULL DEFAULT 0 COMMENT '状态.1激活2冻结', + `status` int(11) NOT NULL DEFAULT 1 COMMENT '状态.1激活2冻结', `role` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'subscriber' COMMENT '用户角色', `created_time` datetime(0) NOT NULL COMMENT '注册时间(UTC)', `updated_time` datetime(0) NOT NULL COMMENT '更新时间(UTC)', diff --git a/internal/backend/handler/article/create.go b/internal/backend/handler/article/create.go index 4c652b9..22c9ab4 100644 --- a/internal/backend/handler/article/create.go +++ b/internal/backend/handler/article/create.go @@ -1,23 +1,21 @@ package article import ( + "database/sql" "fmt" "strconv" "strings" "time" - "github.com/go-sql-driver/mysql" Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" "github.com/puti-projects/puti/internal/pkg/token" "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // CreateRequest struct of article create params @@ -44,8 +42,6 @@ type CreateResponse struct { // Create create a new article (published or draft) func Create(c *gin.Context) { - logger.Info("article create function called", zap.String("X-request-Id", utils.GetReqID(c))) - // get token and parse t := c.Query("token") userContext, err := token.ParseToken(t) @@ -99,7 +95,7 @@ func handleCreate(r *CreateRequest, userID uint64) (rsp *CreateResponse, err err ViewCount: 0, } if r.PostedTime == "" && r.Status == model.PostStatusPublish { - article.PostDate = mysql.NullTime{Time: time.Now(), Valid: true} + article.PostDate = &sql.NullTime{Time: time.Now(), Valid: true} } else { article.PostDate = utils.StringToNullTime("2006-01-02 15:04:05", r.PostedTime) } @@ -108,7 +104,7 @@ func handleCreate(r *CreateRequest, userID uint64) (rsp *CreateResponse, err err } // set GUID - article.GUID = fmt.Sprintf("/article/%s.html", strconv.FormatUint(article.ID, 10)) + article.GUID = fmt.Sprintf("/article/%s.html", strconv.FormatUint(uint64(article.ID), 10)) if err := tx.Model(&model.PostModel{}).Save(article).Error; err != nil { tx.Rollback() return rsp, err diff --git a/internal/backend/handler/article/list.go b/internal/backend/handler/article/list.go index 217f068..adfabb1 100644 --- a/internal/backend/handler/article/list.go +++ b/internal/backend/handler/article/list.go @@ -22,7 +22,7 @@ type ListRequest struct { // ListResponse is the article list response struct type ListResponse struct { - TotalCount uint64 `json:"totalCount"` + TotalCount int64 `json:"totalCount"` TotalPage uint64 `json:"totalPage"` ArticleList []*service.PostInfo `json:"articleList"` } @@ -45,7 +45,7 @@ func List(c *gin.Context) { return } - number := uint64(r.Number) + number := int64(r.Number) totalPage := math.Ceil(float64(count / number)) Response.SendResponse(c, nil, ListResponse{ diff --git a/internal/backend/handler/article/update.go b/internal/backend/handler/article/update.go index 7c5b601..1e6f160 100644 --- a/internal/backend/handler/article/update.go +++ b/internal/backend/handler/article/update.go @@ -7,11 +7,9 @@ import ( "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // UpdateRequest struct for update article @@ -34,8 +32,6 @@ type UpdateRequest struct { // Update update article // Delete and restore article are also in this function and it depends on the 'status' func Update(c *gin.Context) { - logger.Info("article update function called", zap.String("X-request-Id", utils.GetReqID(c))) - // Get article id ID, _ := strconv.Atoi(c.Param("id")) diff --git a/internal/backend/handler/auth/info.go b/internal/backend/handler/auth/info.go index ded654c..c40654b 100644 --- a/internal/backend/handler/auth/info.go +++ b/internal/backend/handler/auth/info.go @@ -4,19 +4,15 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/token" "github.com/gin-gonic/gin" ) -// Info gets an user by the user identifier. +// Info get an user by the user identifier. func Info(c *gin.Context) { t := c.Query("token") - userContext, err := token.ParseToken(t) - - // Get the user by the `username` from the database. - user, err := service.GetUser(userContext.Username) + user, err := service.GetUserByToken(t) if err != nil { Response.SendResponse(c, errno.ErrUserNotFound, nil) return diff --git a/internal/backend/handler/auth/login.go b/internal/backend/handler/auth/login.go index 334ae8d..9453c7b 100644 --- a/internal/backend/handler/auth/login.go +++ b/internal/backend/handler/auth/login.go @@ -3,44 +3,24 @@ package auth import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" - "github.com/puti-projects/puti/internal/model" - "github.com/puti-projects/puti/internal/pkg/auth" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/token" "github.com/gin-gonic/gin" ) -// LoginRequest is the login request params struct -type LoginRequest struct { - Username string `json:"username" binding:"required"` - Password string `json:"password" binding:"required"` -} - -// Login is the Login function +// Login is the Login handler func Login(c *gin.Context) { - var u LoginRequest + var u service.LoginRequest if err := c.Bind(&u); err != nil { Response.SendResponse(c, errno.ErrBind, nil) return } - d, err := model.GetUser(u.Username) - if err != nil { - Response.SendResponse(c, errno.ErrUserNotFound, nil) - return - } - - if err := auth.Compare(d.Password, u.Password); err != nil { - Response.SendResponse(c, errno.ErrPasswordIncorrect, nil) - return - } - - t, err := token.Sign(c, token.Context{ID: d.ID, Username: d.Username}, "") + token, err := service.LoginAuth(c, u.Username, u.Password) if err != nil { - Response.SendResponse(c, errno.ErrToken, nil) + Response.SendResponse(c, err, nil) return } - Response.SendResponse(c, nil, service.Token{Username: u.Username, Token: t}) + Response.SendResponse(c, nil, token) } diff --git a/internal/backend/handler/media/delete.go b/internal/backend/handler/media/delete.go index a82bf34..21c0dfd 100644 --- a/internal/backend/handler/media/delete.go +++ b/internal/backend/handler/media/delete.go @@ -4,8 +4,7 @@ import ( "strconv" Response "github.com/puti-projects/puti/internal/backend/handler" - "github.com/puti-projects/puti/internal/model" - "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/backend/service" "github.com/gin-gonic/gin" ) @@ -14,8 +13,8 @@ import ( func Delete(c *gin.Context) { mediaID, _ := strconv.Atoi(c.Param("id")) - if err := model.DeleteMedia(uint64(mediaID)); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.DeleteMedia(uint64(mediaID)); err != nil { + Response.SendResponse(c, err, nil) return } diff --git a/internal/backend/handler/media/detail.go b/internal/backend/handler/media/detail.go index 87571bd..a4e90c9 100644 --- a/internal/backend/handler/media/detail.go +++ b/internal/backend/handler/media/detail.go @@ -10,13 +10,13 @@ import ( "github.com/gin-gonic/gin" ) -// Detail get media info +// Detail get media info detail handler func Detail(c *gin.Context) { ID := c.Param("id") // Get media info by id mediaID, _ := strconv.Atoi(ID) - media, err := service.GetMedia(uint64(mediaID)) + media, err := service.GetMediaDetail(uint64(mediaID)) if err != nil { Response.SendResponse(c, errno.ErrMediaNotFound, nil) return diff --git a/internal/backend/handler/media/list.go b/internal/backend/handler/media/list.go index 6fe9ff9..c2b494c 100644 --- a/internal/backend/handler/media/list.go +++ b/internal/backend/handler/media/list.go @@ -8,21 +8,9 @@ import ( "github.com/gin-gonic/gin" ) -// ListRequest is the media list request struct -type ListRequest struct { - Limit int `form:"limit"` - Page int `form:"page"` -} - -// ListResponse returns total number of media and current page of media -type ListResponse struct { - TotalCount uint64 `json:"totalCount"` - MediaList []*service.MediaInfo `json:"mediaList"` -} - -// List returns current page media list and the total number of media +// List media list handler func List(c *gin.Context) { - var r ListRequest + var r service.MediaListRequest if err := c.ShouldBind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) return @@ -34,7 +22,7 @@ func List(c *gin.Context) { return } - Response.SendResponse(c, nil, ListResponse{ + Response.SendResponse(c, nil, service.MediaListResponse{ TotalCount: count, MediaList: infos, }) diff --git a/internal/backend/handler/media/update.go b/internal/backend/handler/media/update.go index b50f198..4586fb6 100644 --- a/internal/backend/handler/media/update.go +++ b/internal/backend/handler/media/update.go @@ -5,32 +5,17 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" - "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) -// UpdateRequest is the update media request params struct -type UpdateRequest struct { - ID uint64 `json:"id"` - Title string `json:"title"` - Slug string `json:"slug"` - Description string `json:"description"` -} - -// Update update media info +// Update update media info handler func Update(c *gin.Context) { - logger.Info("update function called", zap.String("X-request-Id", utils.GetReqID(c))) - // Get user id userID, _ := strconv.Atoi(c.Param("id")) - var r UpdateRequest - + var r service.MediaUpdateRequest if err := c.ShouldBind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) return @@ -41,20 +26,8 @@ func Update(c *gin.Context) { return } - r.ID = uint64(userID) - - media := &model.MediaModel{ - Model: model.Model{ID: r.ID}, - - Title: r.Title, - Slug: r.Slug, - Description: r.Description, - } - - // Update changed fields. - if err := service.UpdateMedia(media); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) - return + if err := service.UpdateMedia(&r, userID); err != nil { + Response.SendResponse(c, err, nil) } Response.SendResponse(c, nil, nil) diff --git a/internal/backend/handler/media/upload.go b/internal/backend/handler/media/upload.go index 362e9aa..71fe268 100644 --- a/internal/backend/handler/media/upload.go +++ b/internal/backend/handler/media/upload.go @@ -1,149 +1,27 @@ package media import ( - "bytes" - "crypto/md5" - "encoding/hex" - "fmt" - "os" - "strconv" - "strings" - "time" - Response "github.com/puti-projects/puti/internal/backend/handler" - "github.com/puti-projects/puti/internal/model" - "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" + "github.com/puti-projects/puti/internal/backend/service" "github.com/gin-gonic/gin" ) -// UploadResponse is the upload media request's response struct -type UploadResponse struct { - ID uint64 `json:"id"` - URL string `json:"url"` -} - -// savePathURI defines the media file save path uri -const savePathURI string = "/uploads/" - -// Upload the function handle the file upload +// Upload file upload handler func Upload(c *gin.Context) { // get userId and file userID := c.PostForm("userId") usage := c.DefaultPostForm("usage", "common") file, _ := c.FormFile("file") - // General the save path by upload time - savePath, err := getSavePath(usage) - if err != nil { - Response.SendResponse(c, errno.ErrUploadFile, nil) - return - } - - // set variables - fileExt := utils.GetFileExt(file) - fileNameWithoutExt := strings.TrimSuffix(file.Filename, fileExt) - unixTime := time.Now().Unix() - - // set buf string - buf := bytes.NewBufferString(fileNameWithoutExt) - buf.Write([]byte(strconv.FormatInt(unixTime, 10))) // add a time string - // md5 encode - h := md5.New() - h.Write([]byte(buf.String())) // encode the buf.String() - newFileName := hex.EncodeToString(h.Sum(nil)) - - // final save path with file name - pathName := savePath + newFileName + fileExt - dst := "." + pathName - // Upload the file to specific dst. - if err := c.SaveUploadedFile(file, dst); err != nil { - Response.SendResponse(c, errno.ErrUploadFile, nil) - return - } - - uID, err := strconv.Atoi(userID) + ID, GUID, err := service.UploadMedia(c, userID, usage, file) if err != nil { - Response.SendResponse(c, errno.ErrUploadFile, nil) - return - } - - media := &model.MediaModel{ - UserID: uint64(uID), - Title: file.Filename, - Slug: fileNameWithoutExt, - GUID: pathName, - MimeType: utils.GetFileMimeTypeByExt(fileExt), - Usage: usage, - } - - // save file info - if err := media.Create(); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) - return + Response.SendResponse(c, err, nil) } - rsp := UploadResponse{ - ID: media.ID, - URL: media.GUID, + rsp := service.MediaUploadResponse{ + ID: ID, + URL: GUID, } - Response.SendResponse(c, nil, rsp) } - -// getSavePath general the hole uri by upload time -func getSavePath(usage string) (string, error) { - if usage == "cover" { - coverPath := fmt.Sprintf(".%s%s", savePathURI, "cover") - coverPathExist, err := utils.PathExists(coverPath) - if err != nil { - return "", err - } - if !coverPathExist { - err := os.Mkdir(coverPath, os.ModePerm) - if err != nil { - return "", err - } - } - - var savePath string - savePath = fmt.Sprintf("%s%s/", savePathURI, "cover") - return savePath, nil - } - - now := time.Now() - - // handel year path - year := utils.GetFormatTime(&now, "2006") - yearPath := fmt.Sprintf(".%s%s", savePathURI, year) - yearExist, err := utils.PathExists(yearPath) - if err != nil { - return "", err - } - if !yearExist { - err := os.Mkdir(yearPath, os.ModePerm) - if err != nil { - return "", err - } - } - - // handle month path - month := utils.GetFormatTime(&now, "01") - monthPath := fmt.Sprintf(".%s%s/%s", savePathURI, year, month) - monthExist, err := utils.PathExists(monthPath) - if err != nil { - return "", err - } - if !monthExist { - err := os.Mkdir(monthPath, os.ModePerm) - if err != nil { - return "", err - } - } - - var savePath string - savePath = fmt.Sprintf("%s%s/%s/", savePathURI, year, month) - - return savePath, nil -} diff --git a/internal/backend/handler/option/update.go b/internal/backend/handler/option/update.go index 17440d6..8c376d0 100644 --- a/internal/backend/handler/option/update.go +++ b/internal/backend/handler/option/update.go @@ -6,12 +6,10 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" optionCache "github.com/puti-projects/puti/internal/pkg/option" "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // UpdateRequest update request include params which are dynamitly @@ -21,8 +19,6 @@ type UpdateRequest struct { // Update update options by setting type func Update(c *gin.Context) { - logger.Info("option update function called", zap.String("X-request-Id", utils.GetReqID(c))) - // Get setting type settingType := c.Query("settingType") diff --git a/internal/backend/handler/page/create.go b/internal/backend/handler/page/create.go index 0f90178..f850d40 100644 --- a/internal/backend/handler/page/create.go +++ b/internal/backend/handler/page/create.go @@ -7,12 +7,10 @@ import ( "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" "github.com/puti-projects/puti/internal/pkg/token" "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // CreateRequest struct of page create params @@ -38,8 +36,6 @@ type CreateResponse struct { // Create add new page func Create(c *gin.Context) { - logger.Info("Page create function called.", zap.String("X-request-Id", utils.GetReqID(c))) - // get token and parse t := c.Query("token") userContext, err := token.ParseToken(t) diff --git a/internal/backend/handler/page/list.go b/internal/backend/handler/page/list.go index db5e9b5..acc1ba9 100644 --- a/internal/backend/handler/page/list.go +++ b/internal/backend/handler/page/list.go @@ -22,7 +22,7 @@ type ListRequest struct { // ListResponse is the page list response struct type ListResponse struct { - TotalCount uint64 `json:"totalCount"` + TotalCount int64 `json:"totalCount"` TotalPage uint64 `json:"totalPage"` PageList []*service.PostInfo `json:"pageList"` } @@ -45,7 +45,7 @@ func List(c *gin.Context) { return } - number := uint64(r.Number) + number := int64(r.Number) totalPage := math.Ceil(float64(count / number)) Response.SendResponse(c, nil, ListResponse{ diff --git a/internal/backend/handler/page/update.go b/internal/backend/handler/page/update.go index 4c36ef3..5b416c8 100644 --- a/internal/backend/handler/page/update.go +++ b/internal/backend/handler/page/update.go @@ -7,11 +7,9 @@ import ( "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // UpdateRequest struct of page update params @@ -33,8 +31,6 @@ type UpdateRequest struct { // Update update page info // Delete and restore info are also in this function and it depends on the 'status' func Update(c *gin.Context) { - logger.Info("Page update function called.", zap.String("X-request-Id", utils.GetReqID(c))) - // Get page id ID, _ := strconv.Atoi(c.Param("id")) diff --git a/internal/backend/handler/subject/create.go b/internal/backend/handler/subject/create.go index eb3b93d..7f8b416 100644 --- a/internal/backend/handler/subject/create.go +++ b/internal/backend/handler/subject/create.go @@ -4,11 +4,8 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // CreateRequest struct bind to create subject @@ -22,8 +19,6 @@ type CreateRequest struct { // Create create a new subject func Create(c *gin.Context) { - logger.Info("Subject Create function called.", zap.String("X-request-Id", utils.GetReqID(c))) - var r CreateRequest if err := c.Bind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) diff --git a/internal/backend/handler/subject/delete.go b/internal/backend/handler/subject/delete.go index a95fdb2..6a98038 100644 --- a/internal/backend/handler/subject/delete.go +++ b/internal/backend/handler/subject/delete.go @@ -6,17 +6,12 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "github.com/puti-projects/puti/internal/pkg/logger" - "go.uber.org/zap" ) // Delete delete the taxonomy directly without soft delete func Delete(c *gin.Context) { - logger.Info("Delete subject function called.", zap.String("X-request-Id", utils.GetReqID(c))) - ID, _ := strconv.Atoi(c.Param("id")) subjectID := uint64(ID) diff --git a/internal/backend/handler/subject/update.go b/internal/backend/handler/subject/update.go index cb12258..76b309a 100644 --- a/internal/backend/handler/subject/update.go +++ b/internal/backend/handler/subject/update.go @@ -5,11 +5,8 @@ import ( "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // UpdateRequest struct bind to update subject @@ -24,8 +21,6 @@ type UpdateRequest struct { // Update update the subject by ID func Update(c *gin.Context) { - logger.Info("Subject update function called.", zap.String("X-request-Id", utils.GetReqID(c))) - var r UpdateRequest if err := c.Bind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) diff --git a/internal/backend/handler/taxonomy/create.go b/internal/backend/handler/taxonomy/create.go index 92506fd..8884de3 100644 --- a/internal/backend/handler/taxonomy/create.go +++ b/internal/backend/handler/taxonomy/create.go @@ -6,11 +6,8 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "go.uber.org/zap" ) // CreateRequest struct to crate taxonomy include category and tag @@ -24,8 +21,6 @@ type CreateRequest struct { // Create create txonomy func Create(c *gin.Context) { - logger.Info("Category Create function called.", zap.String("X-request-Id", utils.GetReqID(c))) - var r CreateRequest if err := c.Bind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) diff --git a/internal/backend/handler/taxonomy/delete.go b/internal/backend/handler/taxonomy/delete.go index 8528249..d6be3a2 100644 --- a/internal/backend/handler/taxonomy/delete.go +++ b/internal/backend/handler/taxonomy/delete.go @@ -6,17 +6,12 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "github.com/puti-projects/puti/internal/pkg/logger" - "go.uber.org/zap" ) // Delete delete the taxonomy directly without soft delete func Delete(c *gin.Context) { - logger.Info("Delete taxonomy function called.", zap.String("X-request-Id", utils.GetReqID(c))) - ID, _ := strconv.Atoi(c.Param("id")) taxonomyType := c.Query("taxonomy") // TODO diff --git a/internal/backend/handler/taxonomy/update.go b/internal/backend/handler/taxonomy/update.go index 1673863..fa3f9d4 100644 --- a/internal/backend/handler/taxonomy/update.go +++ b/internal/backend/handler/taxonomy/update.go @@ -7,11 +7,8 @@ import ( "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "github.com/puti-projects/puti/internal/pkg/logger" - "go.uber.org/zap" ) // UpdateRequest param struct to update taxonomy include category and tag @@ -26,8 +23,6 @@ type UpdateRequest struct { // Update update taxonomy include category or tag func Update(c *gin.Context) { - logger.Info("Update function called.", zap.String("X-request-Id", utils.GetReqID(c))) - // Get term id termID, _ := strconv.Atoi(c.Param("id")) diff --git a/internal/backend/handler/user/avator.go b/internal/backend/handler/user/avator.go index 892c346..a1e5e1d 100644 --- a/internal/backend/handler/user/avator.go +++ b/internal/backend/handler/user/avator.go @@ -1,51 +1,19 @@ package user import ( - "strconv" - Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" - "github.com/puti-projects/puti/internal/model" - "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" ) -// savePath defines the use avatar save path -const savePath string = "/uploads/users/" - -// Avatar saves the upload avatar for user +// Avatar upload user avatar handler func Avatar(c *gin.Context) { userID := c.PostForm("userId") file, _ := c.FormFile("img") - fileExt := utils.GetFileExt(file) - newFileName := "user_" + userID + fileExt - - // Upload the file to specific dst. - pathName := savePath + newFileName - dst := "." + pathName - if err := c.SaveUploadedFile(file, dst); err != nil { - Response.SendResponse(c, errno.ErrSaveAvatar, nil) - return - } - - // update user info for avatar - ID, err := strconv.Atoi(userID) - if err != nil { - Response.SendResponse(c, errno.ErrSaveAvatar, nil) - return - } - user := &model.UserModel{ - Model: model.Model{ID: uint64(ID)}, - - Avatar: pathName, - } - - // Update changed fields. - if err := service.UpdateUserAvatar(user); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.UpdateUserAvatar(c, userID, file); err != nil { + Response.SendResponse(c, err, nil) return } diff --git a/internal/backend/handler/user/create.go b/internal/backend/handler/user/create.go index a18ebc4..da4bb8d 100644 --- a/internal/backend/handler/user/create.go +++ b/internal/backend/handler/user/create.go @@ -2,85 +2,41 @@ package user import ( Response "github.com/puti-projects/puti/internal/backend/handler" - "github.com/puti-projects/puti/internal/model" + "github.com/puti-projects/puti/internal/backend/service" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "github.com/puti-projects/puti/internal/pkg/logger" - "go.uber.org/zap" ) -// CreateRequest is the create user request params struct -type CreateRequest struct { - Account string `form:"account"` - Nickname string `form:"nickname"` - Email string `form:"email"` - Role string `form:"role"` - Password string `form:"password"` - PasswordAgain string `form:"passwordAgain"` - Website string `form:"website"` -} - -// CreateResponse is the create user request's response struct -type CreateResponse struct { - Account string - Nickname string -} - -// Create creates a user +// Create user create handler func Create(c *gin.Context) { - logger.Info("User Create function called.", zap.String("X-request-Id", utils.GetReqID(c))) - - var r CreateRequest + var r service.UserCreateRequest if err := c.Bind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) return } // check params - if err := r.checkParam(); err != nil { + if err := checkParam(&r); err != nil { Response.SendResponse(c, err, nil) return } - if "" == r.Nickname { - r.Nickname = r.Account - } - - // TODO - u := model.UserModel{ - Username: r.Account, - Password: r.Password, - Nickname: r.Nickname, - Email: r.Email, - PageURL: r.Website, - Status: 1, - Roles: r.Role, - } - - // encrypt password - if err := u.Encrypt(); err != nil { - Response.SendResponse(c, errno.ErrEncrypt, nil) - return - } - - // Insert the user to the database. TODO 字段提示 - if err := u.Create(); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) - return + username, nickname, err := service.CreateUser(&r) + if err != nil { + Response.SendResponse(c, err, nil) } - rsp := CreateResponse{ - Account: r.Account, - Nickname: r.Nickname, + rsp := &service.UserCreateResponse{ + Account: username, + Nickname: nickname, } // Show the user information. Response.SendResponse(c, nil, rsp) } -func (r *CreateRequest) checkParam() error { +func checkParam(r *service.UserCreateRequest) error { if r.Account == "" { return errno.New(errno.ErrValidation, nil).Add("account is empty.") } diff --git a/internal/backend/handler/user/delete.go b/internal/backend/handler/user/delete.go index ccf92cf..7e37422 100644 --- a/internal/backend/handler/user/delete.go +++ b/internal/backend/handler/user/delete.go @@ -4,18 +4,17 @@ import ( "strconv" Response "github.com/puti-projects/puti/internal/backend/handler" - "github.com/puti-projects/puti/internal/model" - "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/backend/service" "github.com/gin-gonic/gin" ) -// Delete deletes the user by id +// Delete user delete handler func Delete(c *gin.Context) { userID, _ := strconv.Atoi(c.Param("id")) - if err := model.DeleteUser(uint64(userID)); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.DeleteUser(userID); err != nil { + Response.SendResponse(c, err, nil) return } diff --git a/internal/backend/handler/user/get.go b/internal/backend/handler/user/get.go index 89437d0..65130bc 100644 --- a/internal/backend/handler/user/get.go +++ b/internal/backend/handler/user/get.go @@ -11,7 +11,7 @@ import ( // Get gets an user by the user identifier. func Get(c *gin.Context) { username := c.Param("username") - // Get the user by the `username` from the database. + user, err := service.GetUser(username) if err != nil { Response.SendResponse(c, errno.ErrUserNotFound, nil) diff --git a/internal/backend/handler/user/list.go b/internal/backend/handler/user/list.go index a3f2344..f598d3b 100644 --- a/internal/backend/handler/user/list.go +++ b/internal/backend/handler/user/list.go @@ -8,24 +8,9 @@ import ( "github.com/gin-gonic/gin" ) -// ListRequest is the user list request struct -type ListRequest struct { - Username string `form:"username"` - Number int `form:"number"` - Page int `form:"page"` - Status int `form:"status"` - Role string `form:"role"` -} - -// ListResponse is the use list response struct -type ListResponse struct { - TotalCount uint64 `json:"totalCount"` - UserList []*service.UserInfo `json:"userList"` -} - -// List list the users in the database. +// List user list handler func List(c *gin.Context) { - var r ListRequest + var r service.UserListRequest if err := c.Bind(&r); err != nil { Response.SendResponse(c, errno.ErrBind, nil) return @@ -37,7 +22,7 @@ func List(c *gin.Context) { return } - Response.SendResponse(c, nil, ListResponse{ + Response.SendResponse(c, nil, service.UserListResponse{ TotalCount: count, UserList: infos, }) diff --git a/internal/backend/handler/user/update.go b/internal/backend/handler/user/update.go index 4f31b83..204c895 100644 --- a/internal/backend/handler/user/update.go +++ b/internal/backend/handler/user/update.go @@ -5,112 +5,46 @@ import ( Response "github.com/puti-projects/puti/internal/backend/handler" "github.com/puti-projects/puti/internal/backend/service" - "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/errno" - "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" "github.com/gin-gonic/gin/binding" - "go.uber.org/zap" ) -// UpdateRequest is the update user request params struct -type UpdateRequest struct { - ID uint64 `json:"id"` - Nickname string `json:"nickname"` - Email string `json:"email" binding:"required"` - Role string `json:"role" binding:"required"` - Website string `json:"website"` -} - -// UpdateStatusRequest only use for update user status -type UpdateStatusRequest struct { - ID uint64 `json:"id"` - Status int `json:"status" binding:"required"` -} - -// UpdatePasswordRequest user for reset user's password during the profile page -type UpdatePasswordRequest struct { - ID uint64 `json:"id"` - Password string `json:"password" binding:"required"` - PasswordAgain string `json:"passwordAgain" binding:"required"` -} - // Update user func Update(c *gin.Context) { - logger.Info("Update function called.", zap.String("X-request-Id", utils.GetReqID(c))) - // Get user id userID, _ := strconv.Atoi(c.Param("id")) - var r UpdateRequest - var s UpdateStatusRequest - var p UpdatePasswordRequest + var r service.UserUpdateRequest + var s service.UserUpdateStatusRequest + var p service.UserUpdatePasswordRequest if err := c.ShouldBindBodyWith(&r, binding.JSON); err == nil { - // We update the record based on the user id. - r.ID = uint64(userID) - - user := &model.UserModel{ - Model: model.Model{ID: r.ID}, - - Nickname: r.Nickname, - Email: r.Email, - PageURL: r.Website, - Roles: r.Role, - } - - // Update changed fields. - if err := service.UpdateUser(user); err != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.UpdateUser(&r, userID); err != nil { + Response.SendResponse(c, err, nil) return } Response.SendResponse(c, nil, nil) return } else if errStatus := c.ShouldBindBodyWith(&s, binding.JSON); errStatus == nil { - // We update the record based on the user id. - s.ID = uint64(userID) - - user := &model.UserModel{ - Model: model.Model{ID: s.ID}, - - Status: s.Status, - } - - // Update changed fields. - if errStatus := service.UpdateUserStatus(user); errStatus != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.UpdateUserStatus(&s, userID); err != nil { + Response.SendResponse(c, err, nil) return } Response.SendResponse(c, nil, nil) return } else if errPassword := c.ShouldBindBodyWith(&p, binding.JSON); errPassword == nil { - p.ID = uint64(userID) - // check two input password if p.Password != p.PasswordAgain { Response.SendResponse(c, errno.ErrValidation, nil) return } - user := &model.UserModel{ - Model: model.Model{ID: p.ID}, - - Password: p.Password, - } - - // encrypt password - if err := user.Encrypt(); err != nil { - Response.SendResponse(c, errno.ErrEncrypt, nil) - return - } - - // Update changed fields. - if errPassword := service.UpdateUserPassword(user); errPassword != nil { - Response.SendResponse(c, errno.ErrDatabase, nil) + if err := service.UpdateUserPassword(&p, userID); err != nil { + Response.SendResponse(c, err, nil) return } diff --git a/internal/backend/service/auth.go b/internal/backend/service/auth.go index 51570e8..557d690 100644 --- a/internal/backend/service/auth.go +++ b/internal/backend/service/auth.go @@ -1,7 +1,40 @@ package service +import ( + "github.com/gin-gonic/gin" + "github.com/puti-projects/puti/internal/dao" + "github.com/puti-projects/puti/internal/pkg/auth" + "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/pkg/token" +) + +// LoginRequest is the login request params struct +type LoginRequest struct { + Username string `json:"username" binding:"required"` + Password string `json:"password" binding:"required"` +} + // Token represents a JSON web token. type Token struct { Username string `json:"username"` Token string `json:"token"` } + +// LoginAuth user login authentication +func LoginAuth(c *gin.Context, username string, password string) (*Token, error) { + u, err := dao.Engine.GetUser(username) + if err != nil { + return nil, errno.New(errno.ErrUserNotFound, err) + } + + if err := auth.Compare(u.Password, password); err != nil { + return nil, errno.New(errno.ErrPasswordIncorrect, err) + } + + t, err := token.Sign(c, token.Context{ID: u.ID, Username: u.Username}, "") + if err != nil { + return nil, errno.New(errno.ErrToken, err) + } + + return &Token{Username: u.Username, Token: t}, nil +} diff --git a/internal/backend/service/media.go b/internal/backend/service/media.go index d89a48b..5a1efc3 100644 --- a/internal/backend/service/media.go +++ b/internal/backend/service/media.go @@ -1,12 +1,50 @@ package service import ( + "bytes" + "crypto/md5" + "encoding/hex" + "fmt" + "mime/multipart" + "strconv" + "strings" "sync" + "time" + "github.com/gin-gonic/gin" + "github.com/puti-projects/puti/internal/dao" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/config" + "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/utils" ) +// MediaUploadResponse is the upload media request's response struct +type MediaUploadResponse struct { + ID uint64 `json:"id"` + URL string `json:"url"` +} + +// MediaUpdateRequest is the update media request params struct +type MediaUpdateRequest struct { + ID uint64 `json:"id"` + Title string `json:"title"` + Slug string `json:"slug"` + Description string `json:"description"` +} + +// MediaListRequest is the media list request struct +type MediaListRequest struct { + Limit int `form:"limit"` + Page int `form:"page"` +} + +// MediaListResponse returns total number of media and current page of media +type MediaListResponse struct { + TotalCount int64 `json:"totalCount"` + MediaList []*MediaInfo `json:"mediaList"` +} + // MediaInfo is the media struct for media list type MediaInfo struct { ID uint64 `json:"id"` @@ -29,32 +67,135 @@ type MediaList struct { IDMap map[uint64]*MediaInfo } +// UploadMedia upload media and save record +func UploadMedia(c *gin.Context, userID, usage string, file *multipart.FileHeader) (ID uint64, GUID string, err error) { + fileNameWithoutExt, fileExt, pathName, dst, err := getFileSavePath(usage, file) + if err != nil { + return + } + + // Upload the file to specific dst. + if err = c.SaveUploadedFile(file, dst); err != nil { + err = errno.New(errno.ErrUploadFile, err) + return + } + + uID, err := strconv.Atoi(userID) + if err != nil { + return + } + + ID, GUID, err = dao.Engine.CreateMedia(uID, file.Filename, fileNameWithoutExt, fileExt, pathName, usage) + return +} + +// getFileSavePath general the hole uri for the file +func getFileSavePath(usage string, file *multipart.FileHeader) ( + fileNameWithoutExt string, + fileExt string, + pathName string, + dst string, + err error, +) { + // General the save path by upload time + savePath, err := getSavePath(usage) + if err != nil { + return + } + + // set variables + fileExt = utils.GetFileExt(file) + fileNameWithoutExt = strings.TrimSuffix(file.Filename, fileExt) + unixTime := time.Now().Unix() + + // set buf string + buf := bytes.NewBufferString(fileNameWithoutExt) + buf.Write([]byte(strconv.FormatInt(unixTime, 10))) // add a time string + // md5 encode + h := md5.New() + h.Write([]byte(buf.String())) // encode the buf.String() + newFileName := hex.EncodeToString(h.Sum(nil)) + + // final save path with file name + pathName = savePath + newFileName + fileExt + dst = "." + pathName + + return fileNameWithoutExt, fileExt, pathName, dst, nil +} + +// getSavePath general the hole uri by upload time +func getSavePath(usage string) (string, error) { + savePathURI := config.UploadPath + + // for cover picture + if usage == "cover" { + // check cover path exist + coverPath := fmt.Sprintf(".%s%s", savePathURI, "cover") + if err := utils.CheckPathAndCreate(coverPath); err != nil { + return "", err + } + + savePath := fmt.Sprintf("%s%s/", savePathURI, "cover") + return savePath, nil + } + + // for common picture + now := time.Now() + // handel year path + year := utils.GetFormatTime(&now, "2006") + yearPath := fmt.Sprintf(".%s%s", savePathURI, year) + if err := utils.CheckPathAndCreate(yearPath); err != nil { + return "", err + } + + // handle month path + month := utils.GetFormatTime(&now, "01") + monthPath := fmt.Sprintf(".%s%s/%s", savePathURI, year, month) + if err := utils.CheckPathAndCreate(monthPath); err != nil { + return "", err + } + + savePath := fmt.Sprintf("%s%s/%s/", savePathURI, year, month) + return savePath, nil +} + +// GetMediaByID get media by ID +func GetMediaByID(ID uint64) (*model.Media, error) { + media, err := dao.Engine.GetMediaByID(ID) + if err != nil { + return nil, err + } + + return media, nil +} + // GetMedia return media info if database select success -func GetMedia(id uint64) (*MediaDetail, error) { - m, err := model.GetMediaByID(id) +func GetMediaDetail(ID uint64) (*MediaDetail, error) { + media, err := dao.Engine.GetMediaByID(ID) if err != nil { return nil, err } mediaInfo := &MediaDetail{ MediaInfo: MediaInfo{ - ID: m.ID, - Title: m.Title, - GUID: m.GUID, - Type: m.Type, - UploadTime: m.CreatedAt.In(config.TimeLoc()).Format("2006-01-02 15:04:05"), + ID: media.ID, + Title: media.Title, + GUID: media.GUID, + Type: media.Type, + UploadTime: utils.GetFormatTime(&media.CreatedAt, "2006-01-02 15:04:05"), }, - Slug: m.Slug, - Description: m.Description, + Slug: media.Slug, + Description: media.Description, } return mediaInfo, nil } -// ListMedia returns media list and media count -func ListMedia(limit, page int) ([]*MediaInfo, uint64, error) { +// ListMedia returns current page media list and the total number of media +func ListMedia(limit, page int) ([]*MediaInfo, int64, error) { infos := make([]*MediaInfo, 0) - medias, count, err := model.ListMedia(limit, page) + + medias, count, err := dao.Engine.ListMedia(limit, page) if err != nil { return nil, count, err } @@ -70,13 +211,12 @@ func ListMedia(limit, page int) ([]*MediaInfo, uint64, error) { IDMap: make(map[uint64]*MediaInfo, len(medias)), } - errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range medias { wg.Add(1) - go func(u *model.MediaModel) { + go func(u *model.Media) { defer wg.Done() mediaList.Lock.Lock() @@ -96,11 +236,7 @@ func ListMedia(limit, page int) ([]*MediaInfo, uint64, error) { close(finished) }() - select { - case <-finished: - case err := <-errChan: - return nil, count, err - } + <-finished for _, id := range ids { infos = append(infos, mediaList.IDMap[id]) @@ -110,18 +246,19 @@ func ListMedia(limit, page int) ([]*MediaInfo, uint64, error) { } // UpdateMedia update media info -func UpdateMedia(media *model.MediaModel) (err error) { - // Get old media info - oldMedia, err := model.GetMediaByID(media.ID) - if err != nil { - return err +func UpdateMedia(r *MediaUpdateRequest, userID int) (err error) { + if err := dao.Engine.UpdateMedia(uint64(userID), r.Title, r.Slug, r.Description); err != nil { + return errno.New(errno.ErrDatabase, err) } - // Set new status values - oldMedia.Title = media.Title - oldMedia.Slug = media.Slug - oldMedia.Description = media.Description + return nil +} + +// DeleteMedia delete media +func DeleteMedia(userID uint64) error { + if err := dao.Engine.DeleteMediaByID(userID); err != nil { + return errno.New(errno.ErrDatabase, err) + } - err = oldMedia.Update() - return err + return nil } diff --git a/internal/backend/service/post.go b/internal/backend/service/post.go index 657e011..7eb9399 100644 --- a/internal/backend/service/post.go +++ b/internal/backend/service/post.go @@ -1,18 +1,19 @@ package service import ( + "database/sql" + "errors" "fmt" "strconv" "strings" "sync" "time" - "github.com/go-sql-driver/mysql" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/utils" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // PostInfo is post info for post list @@ -65,7 +66,7 @@ type PageDetail struct { } // ListPost post list -func ListPost(postType, title string, page, number int, sort, status string) ([]*PostInfo, uint64, error) { +func ListPost(postType, title string, page, number int, sort, status string) ([]*PostInfo, int64, error) { infos := make([]*PostInfo, 0) posts, count, err := model.ListPost(postType, title, page, number, sort, status) if err != nil { @@ -83,7 +84,6 @@ func ListPost(postType, title string, page, number int, sort, status string) ([] IDMap: make(map[uint64]*PostInfo, len(posts)), } - errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel @@ -99,7 +99,7 @@ func ListPost(postType, title string, page, number int, sort, status string) ([] UserID: u.UserID, Title: u.Title, Status: u.Status, - PostDate: utils.GetFormatNullTime(&u.PostDate, "2006-01-02 15:04"), + PostDate: utils.GetFormatNullTime(u.PostDate, "2006-01-02 15:04"), CommentCount: u.CommentCount, ViewCount: u.ViewCount, } @@ -111,11 +111,7 @@ func ListPost(postType, title string, page, number int, sort, status string) ([] close(finished) }() - select { - case <-finished: - case err := <-errChan: - return nil, count, err - } + <-finished for _, id := range ids { infos = append(infos, postList.IDMap[id]) @@ -150,7 +146,7 @@ func GetArticleDetail(articleID string) (*ArticleDetail, error) { IfTop: a.IfTop, GUID: a.GUID, CoverPicture: a.CoverPicture, - PostDate: utils.GetFormatNullTime(&a.PostDate, "2006-01-02 15:04:05"), + PostDate: utils.GetFormatNullTime(a.PostDate, "2006-01-02 15:04:05"), MetaData: make(map[string]interface{}), Category: make([]uint64, 0), Tag: make([]uint64, 0), @@ -208,7 +204,7 @@ func GetPageDetail(pageID string) (*PageDetail, error) { CommentStatus: p.CommentStatus, GUID: p.GUID, CoverPicture: p.CoverPicture, - PostDate: utils.GetFormatNullTime(&p.PostDate, "2006-01-02 15:04:05"), + PostDate: utils.GetFormatNullTime(p.PostDate, "2006-01-02 15:04:05"), MetaData: make(map[string]interface{}), } @@ -247,7 +243,7 @@ func UpdateArticle(article *model.PostModel, description string, category []uint oldArticle.CoverPicture = article.CoverPicture oldArticle.PostDate = article.PostDate if oldArticle.PostDate.Valid == false && article.Status == model.PostStatusPublish { - oldArticle.PostDate = mysql.NullTime{Time: time.Now(), Valid: true} + oldArticle.PostDate = &sql.NullTime{Time: time.Now(), Valid: true} } if err = tx.Model(&model.PostModel{}).Save(oldArticle).Error; err != nil { tx.Rollback() @@ -315,7 +311,7 @@ func UpdateArticle(article *model.PostModel, description string, category []uint // get old subject articleSubject, err := model.GetArticleSubject(article.ID) - if err != nil && !gorm.IsRecordNotFoundError(err) { + if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) { tx.Rollback() return err } diff --git a/internal/backend/service/statistics.go b/internal/backend/service/statistics.go index bff0727..072c85a 100644 --- a/internal/backend/service/statistics.go +++ b/internal/backend/service/statistics.go @@ -7,16 +7,16 @@ import ( // DashboardData some statistics index type DashboardData struct { - TotalViews uint64 - TotalComments uint64 - TotalArticles uint64 - TotalMedia uint64 + TotalViews int64 + TotalComments int64 + TotalArticles int64 + TotalMedia int64 } // GetDashboardStatisticsData get dashboard statistics data // TODO store in cache first func GetDashboardStatisticsData() (*DashboardData, error) { - var totalViews, totalComments, totalArticles, totalMedia uint64 + var totalViews, totalComments, totalArticles, totalMedia int64 postModel := &model.PostModel{} totalViewsRow := db.DBEngine.Table(postModel.TableName()).Where("`status` != ? AND `deleted_time` is null", "deleted"). Select("sum(`view_count`) as total_views"). @@ -30,7 +30,7 @@ func GetDashboardStatisticsData() (*DashboardData, error) { return nil, err } - mediaModel := &model.MediaModel{} + mediaModel := &model.Media{} if err := db.DBEngine.Table(mediaModel.TableName()).Where("`deleted_time` is null").Count(&totalMedia).Error; err != nil { return nil, err } diff --git a/internal/backend/service/subejct.go b/internal/backend/service/subejct.go index 39d871d..89d34cf 100644 --- a/internal/backend/service/subejct.go +++ b/internal/backend/service/subejct.go @@ -1,15 +1,16 @@ package service import ( + "database/sql" + "errors" "strings" "time" - "github.com/go-sql-driver/mysql" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/utils" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // SubjectTreeNode tree struct of subject list @@ -86,8 +87,8 @@ func GetSubjectInfo(subjectID uint64) (*SubjectDetail, error) { var coverImageStatus string var coverImageURL string if s.CoverImage != 0 { - m, err := model.GetMediaByID(s.CoverImage) - if gorm.IsRecordNotFoundError(err) { + m, err := GetMediaByID(s.CoverImage) + if errors.Is(err, gorm.ErrRecordNotFound) { coverImageStatus = "关联封面图不存在,可能已被删除。" } coverImageURL = m.GUID @@ -235,7 +236,7 @@ func UpdateSubjectInfoByArticleChange(tx *gorm.DB, subjectIDGroup []uint64, coun updateColumns["count"] = gorm.Expr("count + ?", countDiff) } if updateLastUpdated { - updateColumns["last_updated"] = mysql.NullTime{Time: time.Now(), Valid: true} + updateColumns["last_updated"] = sql.NullTime{Time: time.Now(), Valid: true} } // exec diff --git a/internal/backend/service/system.go b/internal/backend/service/system.go index c2cca8b..fd181a8 100644 --- a/internal/backend/service/system.go +++ b/internal/backend/service/system.go @@ -2,14 +2,15 @@ package service import ( "fmt" + "strconv" + "github.com/puti-projects/puti/internal/pkg/constvar" "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/shirou/gopsutil/cpu" - "github.com/shirou/gopsutil/load" - "strconv" + "github.com/shirou/gopsutil/cpu" "github.com/shirou/gopsutil/disk" "github.com/shirou/gopsutil/host" + "github.com/shirou/gopsutil/load" "github.com/shirou/gopsutil/mem" ) @@ -25,10 +26,10 @@ const ( ) const ( - statusERROR = "ERROR" + statusERROR = "ERROR" statusCRITICAL = "CRITICAL" - statusWARNING = "WARNING" - statusNORMAL = "NORMAL" + statusWARNING = "WARNING" + statusNORMAL = "NORMAL" ) // SystemInfo system information @@ -105,8 +106,8 @@ func (c *CPUHealth) GetHealthStatusByCores() string { warningLevel = float64(c.CPUCores) - 0.5 break default: - criticalLevel = float64(c.CPUCores-1) - warningLevel = float64(c.CPUCores-2) + criticalLevel = float64(c.CPUCores - 1) + warningLevel = float64(c.CPUCores - 2) break } @@ -133,8 +134,8 @@ func DiskCheck() *DiskHealth { } diskHealth = &DiskHealth{ - Used: transSize(u.Used), - Total: transSize(u.Total), + Used: transSize(u.Used), + Total: transSize(u.Total), HealthPercent: HealthPercent{UsedPercent: int(u.UsedPercent)}, } diskHealth.HealthStatus = diskHealth.GetHealthStatusByPercent() @@ -158,8 +159,8 @@ func RAMCheck() *RAMHealth { } ramHealth = &RAMHealth{ - Used: transSize(u.Used), - Total: transSize(u.Total), + Used: transSize(u.Used), + Total: transSize(u.Total), HealthPercent: HealthPercent{UsedPercent: int(u.UsedPercent)}, } ramHealth.HealthStatus = ramHealth.GetHealthStatusByPercent() @@ -217,12 +218,12 @@ func SystemInfoCheck() *SystemInfo { // transSize tool function for change size func transSize(size uint64) string { var transfer string - if size >= 1 * GB { - sizeFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%.2d", size / GB, size % GB), 64) - sizeGB := strconv.FormatFloat(sizeFloat,'f',2,64) + if size >= 1*GB { + sizeFloat, _ := strconv.ParseFloat(fmt.Sprintf("%d.%.2d", size/GB, size%GB), 64) + sizeGB := strconv.FormatFloat(sizeFloat, 'f', 2, 64) transfer = fmt.Sprintf("%s GB", sizeGB) } else { - transfer = fmt.Sprintf("%d MB", size / MB) + transfer = fmt.Sprintf("%d MB", size/MB) } return transfer diff --git a/internal/backend/service/taxonomy.go b/internal/backend/service/taxonomy.go index 9686404..6699087 100644 --- a/internal/backend/service/taxonomy.go +++ b/internal/backend/service/taxonomy.go @@ -6,7 +6,7 @@ import ( "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // TermInfo terms info @@ -163,7 +163,7 @@ func UpdateTaxonomy(term *model.TermModel, termTaxonomy *model.TermTaxonomyModel } // updateTaxonomyChildLevel update category's children level -func updateTaxonomyChildLevel(tx *gorm.DB, termID, parentLevel uint64, taxonomyType string) error { +func updateTaxonomyChildLevel(tx *gorm.DB, termID uint64, parentLevel uint64, taxonomyType string) error { TermTaxonomy := []model.TermTaxonomyModel{} tt := tx.Where("parent_term_id = ? AND taxonomy = ?", termID, taxonomyType).Find(&TermTaxonomy) if tt.Error != nil { diff --git a/internal/backend/service/user.go b/internal/backend/service/user.go index e7480a4..7109146 100644 --- a/internal/backend/service/user.go +++ b/internal/backend/service/user.go @@ -1,13 +1,34 @@ package service import ( - "strings" + "mime/multipart" + "strconv" "sync" + "github.com/gin-gonic/gin" + "github.com/puti-projects/puti/internal/dao" "github.com/puti-projects/puti/internal/model" + "github.com/puti-projects/puti/internal/pkg/config" + "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/pkg/token" "github.com/puti-projects/puti/internal/utils" ) +// ListRequest is the user list request struct +type UserListRequest struct { + Username string `form:"username"` + Number int `form:"number"` + Page int `form:"page"` + Status int `form:"status"` + Role string `form:"role"` +} + +// ListResponse is the use list response struct +type UserListResponse struct { + TotalCount int64 `json:"totalCount"` + UserList []*UserInfo `json:"userList"` +} + // UserInfo is the user struct for user list type UserInfo struct { ID uint64 `json:"id"` @@ -22,19 +43,29 @@ type UserInfo struct { DeletedTime string `json:"deleted_time"` } -// UserList user list -type UserList struct { - Lock *sync.Mutex - IDMap map[uint64]*UserInfo +// CreateRequest is the create user request params struct +type UserCreateRequest struct { + Account string `form:"account"` + Nickname string `form:"nickname"` + Email string `form:"email"` + Role string `form:"role"` + Password string `form:"password"` + PasswordAgain string `form:"passwordAgain"` + Website string `form:"website"` } -// GetUser gets userInfo by username(account) +// CreateResponse is the create user request's response struct +type UserCreateResponse struct { + Account string + Nickname string +} + +// GetUser get userInfo by username(account) func GetUser(username string) (*UserInfo, error) { - u, err := model.GetUser(username) + u, err := dao.Engine.GetUser(username) if err != nil { return nil, err } - userInfo := &UserInfo{ ID: u.ID, Accout: u.Username, @@ -45,20 +76,42 @@ func GetUser(username string) (*UserInfo, error) { Status: u.Status, Website: u.PageURL, RegisteredTime: utils.GetFormatTime(&u.CreatedAt, "2006-01-02 15:04:05"), - DeletedTime: utils.GetFormatTime(u.DeletedAt, "2006-01-02 15:04:05"), + DeletedTime: utils.GetFormatDeletedAtTime(&u.DeletedAt, "2006-01-02 15:04:05"), } return userInfo, nil } +// GetUserByToken get userInfo by token(JWT) +func GetUserByToken(t string) (*UserInfo, error) { + userContext, err := token.ParseToken(t) + if err != nil { + return nil, err + } + + userInfo, err := GetUser(userContext.Username) + if err != nil { + return nil, err + } + + return userInfo, nil +} + +// UserList user list handle struct +type UserList struct { + Lock *sync.Mutex + IDMap map[uint64]*UserInfo +} + // ListUser show the user list in page -func ListUser(username, role string, number, page, status int) ([]*UserInfo, uint64, error) { - infos := make([]*UserInfo, 0) - users, count, err := model.ListUser(username, role, number, page, status) +func ListUser(username, role string, number, page, status int) ([]*UserInfo, int64, error) { + // get user list + users, count, err := dao.Engine.ListUser(username, role, number, page, status) if err != nil { return nil, count, err } + infos := make([]*UserInfo, 0) ids := []uint64{} for _, user := range users { ids = append(ids, user.ID) @@ -70,13 +123,12 @@ func ListUser(username, role string, number, page, status int) ([]*UserInfo, uin IDMap: make(map[uint64]*UserInfo, len(users)), } - errChan := make(chan error, 1) finished := make(chan bool, 1) // Improve query efficiency in parallel for _, u := range users { wg.Add(1) - go func(u *model.UserModel) { + go func(u *model.User) { defer wg.Done() userList.Lock.Lock() @@ -89,20 +141,18 @@ func ListUser(username, role string, number, page, status int) ([]*UserInfo, uin Status: u.Status, Roles: u.Roles, RegisteredTime: utils.GetFormatTime(&u.CreatedAt, "2006-01-02 15:04:05"), - DeletedTime: utils.GetFormatTime(u.DeletedAt, "2006-01-02 15:04:05")} + DeletedTime: utils.GetFormatDeletedAtTime(&u.DeletedAt, "2006-01-02 15:04:05")} }(u) } go func() { + // wait for finish wg.Wait() + // close finished channel when finished close(finished) }() - select { - case <-finished: - case err := <-errChan: - return nil, count, err - } + <-finished for _, id := range ids { infos = append(infos, userList.IDMap[id]) @@ -111,70 +161,113 @@ func ListUser(username, role string, number, page, status int) ([]*UserInfo, uin return infos, count, nil } -// UpdateUser updates userinfo by id -func UpdateUser(user *model.UserModel) (err error) { - // Get old user info - oldUser, err := model.GetUserByID(user.ID) - if err != nil { - return err +// CreateUser create a new user +func CreateUser(u *UserCreateRequest) (string, string, error) { + if "" == u.Nickname { + u.Nickname = u.Account } - // Set new values - user.Nickname = strings.TrimSpace(user.Nickname) - if user.Nickname == "" { - user.Nickname = oldUser.Username // Set nickname to account if nickname is empty + user := &model.User{ + Username: u.Account, + Password: u.Password, + Nickname: u.Nickname, + Email: u.Email, + PageURL: u.Website, + Status: 1, + Roles: u.Role, } - oldUser.Nickname = user.Nickname - oldUser.Email = strings.TrimSpace(user.Email) - oldUser.PageURL = strings.TrimSpace(user.PageURL) - oldUser.Roles = user.Roles + if err := dao.Engine.CreateUser(user); err != nil { + return "", "", err + } + + return user.Username, user.Nickname, nil +} - err = oldUser.Update() - return err +// UpdateRequest is the update user request params struct +type UserUpdateRequest struct { + ID uint64 `json:"id"` + Nickname string `json:"nickname"` + Email string `json:"email" binding:"required"` + Role string `json:"role" binding:"required"` + Website string `json:"website"` } -// UpdateUserStatus updates user status by id -func UpdateUserStatus(user *model.UserModel) (err error) { - // Get old user info - oldUser, err := model.GetUserByID(user.ID) +// UpdateStatusRequest only use for update user status +type UserUpdateStatusRequest struct { + ID uint64 `json:"id"` + Status int `json:"status" binding:"required"` +} + +// UpdatePasswordRequest user for reset user's password during the profile page +type UserUpdatePasswordRequest struct { + ID uint64 `json:"id"` + Password string `json:"password" binding:"required"` + PasswordAgain string `json:"passwordAgain" binding:"required"` +} + +// UpdateUser update user info by id +func UpdateUser(u *UserUpdateRequest, userID int) error { + err := dao.Engine.UpdateUser(uint64(userID), u.Nickname, u.Email, u.Website, u.Role) if err != nil { - return err + return errno.New(errno.ErrDatabase, err) } - // Set new status values - oldUser.Status = user.Status + return nil +} + +// UpdateUserStatus update user status +func UpdateUserStatus(u *UserUpdateStatusRequest, userID int) error { + err := dao.Engine.UpdateUserStatus(uint64(userID), u.Status) + if err != nil { + return errno.New(errno.ErrDatabase, err) + } - err = oldUser.Update() - return err + return nil } // UpdateUserPassword just reset user's password -func UpdateUserPassword(user *model.UserModel) (err error) { - // Get old user info - oldUser, err := model.GetUserByID(user.ID) +func UpdateUserPassword(u *UserUpdatePasswordRequest, userID int) error { + err := dao.Engine.UpdateUserPassword(uint64(userID), u.Password) if err != nil { - return err + return errno.New(errno.ErrDatabase, err) } - // Set new password - oldUser.Password = user.Password - - err = oldUser.Update() - return err + return nil } -// UpdateUserAvatar save the new avatar url -func UpdateUserAvatar(user *model.UserModel) (err error) { - // Get old user info - oldUser, err := model.GetUserByID(user.ID) +// UpdateUserAvatar update user avatar +func UpdateUserAvatar(c *gin.Context, userID string, file *multipart.FileHeader) error { + fileExt := utils.GetFileExt(file) + newFileName := "user_" + userID + fileExt + + // Upload the file to specific dst. + pathName := config.UploadUserAvatarPath + newFileName + dst := "." + pathName + if err := c.SaveUploadedFile(file, dst); err != nil { + return errno.New(errno.ErrSaveAvatar, err) + } + + // update user info for avatar + ID, err := strconv.Atoi(userID) if err != nil { - return err + return errno.New(errno.ErrSaveAvatar, err) } - // Set new password - oldUser.Avatar = user.Avatar + err = dao.Engine.UpdateUserAvatar(uint64(ID), pathName) + if err != nil { + return errno.New(errno.ErrDatabase, err) + } + + return nil +} + +// DeleteUser delete user by ID +func DeleteUser(userID int) error { + err := dao.Engine.DeleteUser(uint64(userID)) + if err != nil { + return errno.New(errno.ErrDatabase, err) + } - err = oldUser.Update() - return err + return nil } diff --git a/internal/dao/dao.go b/internal/dao/dao.go new file mode 100644 index 0000000..61b6041 --- /dev/null +++ b/internal/dao/dao.go @@ -0,0 +1,20 @@ +package dao + +import ( + "github.com/puti-projects/puti/internal/pkg/db" + + "gorm.io/gorm" +) + +type Dao struct { + db *gorm.DB +} + +var Engine *Dao + +// NewDaoEngine create a dao instance +// Note: after db.DBEngine inited +func NewDaoEngine() { + Engine = new(Dao) + Engine.db = db.DBEngine +} diff --git a/internal/dao/media.go b/internal/dao/media.go new file mode 100644 index 0000000..30c6d5f --- /dev/null +++ b/internal/dao/media.go @@ -0,0 +1,79 @@ +package dao + +import ( + "github.com/puti-projects/puti/internal/model" + "github.com/puti-projects/puti/internal/pkg/errno" + "github.com/puti-projects/puti/internal/utils" +) + +// CreateMedia crate new media record after uploaded +func (d *Dao) CreateMedia(uID int, filename, fileNameWithoutExt, fileExt, pathName, usage string) (uint64, string, error) { + media := &model.Media{ + UserID: uint64(uID), + Title: filename, + Slug: fileNameWithoutExt, + GUID: pathName, + MimeType: utils.GetFileMimeTypeByExt(fileExt), + Usage: usage, + } + + if err := media.Create(d.db); err != nil { + return 0, "", errno.New(errno.ErrDatabase, err) + } + return media.ID, media.GUID, nil +} + +// UpdateMedia update media info +func (d *Dao) UpdateMedia(ID uint64, title, slug, description string) error { + // Get old media info + oldMedia := &model.Media{ + Model: model.Model{ID: ID}, + } + err := oldMedia.GetByID(d.db) + if err != nil { + return err + } + + // Set new status values + oldMedia.Title = title + oldMedia.Slug = slug + oldMedia.Description = description + + err = oldMedia.Update(d.db) + return err +} + +// ListMedia get media list +func (d *Dao) ListMedia(limit, page int) ([]*model.Media, int64, error) { + where := "deleted_time is null" + + media := &model.Media{} + count, err := media.Count(d.db, where, nil) + if err != nil { + return nil, count, err + } + + offset := (page - 1) * limit + medias, err := media.List(d.db, where, nil, offset, limit) + if err != nil { + return nil, count, err + } + + return medias, count, nil +} + +// GetMediaByID get media by ID +func (d *Dao) GetMediaByID(ID uint64) (*model.Media, error) { + media := &model.Media{ + Model: model.Model{ID: ID}, + } + err := media.GetByID(d.db) + return media, err +} + +// DeleteMediaByID delete media by ID +func (d *Dao) DeleteMediaByID(ID uint64) error { + media := &model.Media{} + media.ID = ID + return media.Delete(d.db) +} diff --git a/internal/dao/user.go b/internal/dao/user.go new file mode 100644 index 0000000..6f5f557 --- /dev/null +++ b/internal/dao/user.go @@ -0,0 +1,173 @@ +package dao + +import ( + "strings" + + "github.com/puti-projects/puti/internal/model" + "github.com/puti-projects/puti/internal/pkg/constvar" + "github.com/puti-projects/puti/internal/pkg/errno" +) + +// GetUser get user by username +func (d *Dao) GetUser(username string) (*model.User, error) { + user := &model.User{ + Username: username, + } + err := user.Get(d.db) + return user, err +} + +// GetUser get user by ID +func (d *Dao) GetUserByID(userID uint64) (*model.User, error) { + user := &model.User{ + Model: model.Model{ID: userID}, + } + err := user.GetByID(d.db) + return user, err +} + +// CreateUser create a new user +func (d *Dao) CreateUser(user *model.User) error { + if err := user.Encrypt(); err != nil { + return errno.New(errno.ErrEncrypt, err) + } + + // Insert the user to the database. + if err := user.Create(d.db); err != nil { + return errno.New(errno.ErrDatabase, err) + } + + return nil +} + +// UpdateUser update user infomation +func (d *Dao) UpdateUser(userID uint64, nickname, email, website, role string) error { + oldUser, err := d.GetUserByID(userID) + if err != nil { + return err + } + + // Set new values + nickname = strings.TrimSpace(nickname) + if nickname == "" { + // Set nickname to account if nickname is empty + // username can not be change + nickname = oldUser.Username + } + oldUser.Nickname = nickname + oldUser.Email = strings.TrimSpace(email) + oldUser.PageURL = strings.TrimSpace(website) + oldUser.Roles = role + + err = oldUser.Update(d.db) + if err != nil { + return err + } + + return nil +} + +// UpdateUserStatus update user status +func (d *Dao) UpdateUserStatus(userID uint64, status int) error { + oldUser, err := d.GetUserByID(userID) + if err != nil { + return err + } + + // Set new status values + oldUser.Status = status + + err = oldUser.Update(d.db) + if err != nil { + return err + } + + return nil +} + +// UpdateUserPassword reset user password by user ID +func (d *Dao) UpdateUserPassword(userID uint64, password string) error { + oldUser, err := d.GetUserByID(userID) + if err != nil { + return err + } + + // Set new password + oldUser.Password = password + + // encrypt password + if err := oldUser.Encrypt(); err != nil { + return errno.New(errno.ErrEncrypt, err) + } + + err = oldUser.Update(d.db) + if err != nil { + return err + } + + return nil +} + +// UpdateUserAvatar update user avatar path +func (d *Dao) UpdateUserAvatar(userID uint64, pathName string) error { + oldUser, err := d.GetUserByID(userID) + if err != nil { + return err + } + + oldUser.Avatar = pathName + + err = oldUser.Update(d.db) + if err != nil { + return err + } + + return nil +} + +// ListUser list usser with pagination +func (d *Dao) ListUser(username, role string, number, page, status int) ([]*model.User, int64, error) { + if number == 0 { + number = constvar.DefaultLimit + } + + where := "`deleted_time` is null" + whereArgs := []interface{}{} + if username != "" { + where += " AND `nickname` LIKE ?" + whereArgs = append(whereArgs, "%"+username+"%") + } + + if role != "" { + where += " AND `role` = ?" + whereArgs = append(whereArgs, role) + } + + if status != 0 { + where += " AND `status` = ?" + whereArgs = append(whereArgs, status) + } + + user := &model.User{} + count, err := user.Count(d.db, where, whereArgs) + if err != nil { + return nil, count, err + } + + offset := (page - 1) * number + users, err := user.List(d.db, where, whereArgs, offset, number) + if err != nil { + return nil, count, err + } + + return users, count, nil +} + +// DeleteUser delete user by ID +func (d *Dao) DeleteUser(userID uint64) error { + user := &model.User{ + Model: model.Model{ID: userID}, + } + err := user.Delete(d.db) + return err +} diff --git a/internal/frontend/handler/article.go b/internal/frontend/handler/article.go index 7525613..96cae75 100644 --- a/internal/frontend/handler/article.go +++ b/internal/frontend/handler/article.go @@ -1,6 +1,7 @@ package handler import ( + "errors" "net/http" "strconv" "strings" @@ -9,7 +10,7 @@ import ( "github.com/puti-projects/puti/internal/pkg/counter" "github.com/gin-gonic/gin" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // ShowArticleList article list handle @@ -103,7 +104,7 @@ func ShowArticleDetail(c *gin.Context) { articleDetail, err := service.GetArticleDetailByID(aID) if err != nil { - if gorm.IsRecordNotFoundError(err) { + if errors.Is(err, gorm.ErrRecordNotFound) { ShowNotFound(c) return } diff --git a/internal/frontend/handler/base.go b/internal/frontend/handler/base.go index f25d310..161fe51 100644 --- a/internal/frontend/handler/base.go +++ b/internal/frontend/handler/base.go @@ -1,16 +1,16 @@ package handler import ( - "github.com/puti-projects/puti/internal/frontend/middleware" + "github.com/puti-projects/puti/internal/routers/middleware/view" "github.com/gin-gonic/gin" ) // getDataModel consume renderer data -func getRenderData(c *gin.Context) middleware.RenderData { +func getRenderData(c *gin.Context) view.RenderData { renderData, _ := c.Get("renderData") - return *(renderData.(*middleware.RenderData)) + return *(renderData.(*view.RenderData)) } // getTheme return current theme name diff --git a/internal/frontend/handler/subject.go b/internal/frontend/handler/subject.go index 523ae1b..e75a83f 100644 --- a/internal/frontend/handler/subject.go +++ b/internal/frontend/handler/subject.go @@ -1,13 +1,14 @@ package handler import ( + "errors" "fmt" "net/http" "github.com/puti-projects/puti/internal/frontend/service" "github.com/gin-gonic/gin" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // ShowTopSubjects subject list (parent id is 0) @@ -39,7 +40,7 @@ func ShowSubjects(c *gin.Context) { // get subject info subjectInfo, err := service.GetSubjectInfoBySlug(subjectSlug) if err != nil { - if gorm.IsRecordNotFoundError(err) { + if errors.Is(err, gorm.ErrRecordNotFound) { ShowNotFound(c) return } diff --git a/internal/frontend/service/archive.go b/internal/frontend/service/archive.go index 7b64784..5c96502 100644 --- a/internal/frontend/service/archive.go +++ b/internal/frontend/service/archive.go @@ -37,8 +37,8 @@ func GetArchive() (map[string]map[string][]*model.ShowArchive, []string, map[str sortYear := []string{} sortMonth := map[string][]string{} for _, v := range archives { - postedYear := utils.GetFormatNullTime(&v.PostDate, "2006") - postedMonth := utils.GetFormatNullTime(&v.PostDate, "01") + postedYear := utils.GetFormatNullTime(v.PostDate, "2006") + postedMonth := utils.GetFormatNullTime(v.PostDate, "01") _, existYear := dataMap[postedYear] if !existYear { @@ -61,8 +61,8 @@ func GetArchive() (map[string]map[string][]*model.ShowArchive, []string, map[str GUID: v.GUID, CommentCount: v.CommentCount, ViewCount: v.ViewCount, - PostedTime: utils.GetFormatNullTime(&v.PostDate, "2006-01-02 15:04"), - PostedDay: utils.GetFormatNullTime(&v.PostDate, "02"), + PostedTime: utils.GetFormatNullTime(v.PostDate, "2006-01-02 15:04"), + PostedDay: utils.GetFormatNullTime(v.PostDate, "02"), } dataMap[postedYear][postedMonth] = append(dataMap[postedYear][postedMonth], article) diff --git a/internal/frontend/service/article.go b/internal/frontend/service/article.go index 9ec9b52..463da2d 100644 --- a/internal/frontend/service/article.go +++ b/internal/frontend/service/article.go @@ -1,6 +1,7 @@ package service import ( + "database/sql" "fmt" "html/template" "regexp" @@ -8,7 +9,6 @@ import ( "strings" "sync" - "github.com/go-sql-driver/mysql" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/config" "github.com/puti-projects/puti/internal/pkg/db" @@ -28,7 +28,7 @@ func GetArticleListByTaxonomy(currentPage int, taxonomyType, taxonomySlug, keywo // get articles data pageSize, _ := strconv.Atoi(optionCache.Options.Get("posts_per_page")) offset := (currentPage - 1) * pageSize - count := 0 + var count int64 = 0 // get term id var termTaxonomyID uint64 @@ -62,7 +62,7 @@ func GetArticleListByTaxonomy(currentPage int, taxonomyType, taxonomySlug, keywo } // get pagination - pagination = utils.GetPagination(count, currentPage, pageSize, 0) + pagination = utils.GetPagination(int(count), currentPage, pageSize, 0) // handle articles data articleResult = make([]*model.ShowArticle, 0) @@ -107,7 +107,7 @@ func GetArticleListByTaxonomy(currentPage int, taxonomyType, taxonomySlug, keywo CoverPicture: a.CoverPicture, CommentCount: a.CommentCount, ViewCount: a.ViewCount, - PostedTime: utils.GetFormatNullTime(&a.PostDate, "2006-01-02 15:04"), + PostedTime: utils.GetFormatNullTime(a.PostDate, "2006-01-02 15:04"), Tags: articleTag, Categories: articleCategory, } @@ -137,7 +137,7 @@ func GetArticleList(currentPage int, keyword string) (articleResult []*model.Sho // get articles data pageSize, _ := strconv.Atoi(optionCache.Options.Get("posts_per_page")) offset := (currentPage - 1) * pageSize - count := 0 + var count int64 = 0 where := "`post_type` = ? AND `parent_id` = ? AND `status` = ?" whereArgs := []interface{}{model.PostTypeArticle, 0, model.PostStatusPublish} @@ -159,7 +159,7 @@ func GetArticleList(currentPage int, keyword string) (articleResult []*model.Sho } // get pagination - pagination = utils.GetPagination(count, currentPage, pageSize, 0) + pagination = utils.GetPagination(int(count), currentPage, pageSize, 0) // handle articles data articleResult = make([]*model.ShowArticle, 0) @@ -204,7 +204,7 @@ func GetArticleList(currentPage int, keyword string) (articleResult []*model.Sho CoverPicture: a.CoverPicture, CommentCount: a.CommentCount, ViewCount: a.ViewCount, - PostedTime: utils.GetFormatNullTime(&a.PostDate, "2006-01-02 15:04"), + PostedTime: utils.GetFormatNullTime(a.PostDate, "2006-01-02 15:04"), Tags: articleTag, Categories: articleCategory, } @@ -318,7 +318,7 @@ func GetArticleDetailByID(articleID uint64) (*model.ShowArticleDetail, error) { GUID: siteURL + a.GUID, CommentCount: a.CommentCount, ViewCount: a.ViewCount, - PostedTime: utils.GetFormatNullTime(&a.PostDate, "2006-01-02 15:04"), + PostedTime: utils.GetFormatNullTime(a.PostDate, "2006-01-02 15:04"), MetaData: make(map[string]interface{}), Categories: articleCategory, Tags: articleTag, @@ -392,7 +392,7 @@ func GetSubjectArticleList(subjectID uint64) ([]*map[string]interface{}, error) for rows.Next() { var id, commentCount, viewCount uint64 var title, guid string - var postedTime *mysql.NullTime + var postedTime *sql.NullTime rows.Scan(&id, &title, &guid, &commentCount, &viewCount, &postedTime) item := &map[string]interface{}{ diff --git a/internal/frontend/service/page.go b/internal/frontend/service/page.go index 7c343fc..5d01cb0 100644 --- a/internal/frontend/service/page.go +++ b/internal/frontend/service/page.go @@ -40,7 +40,7 @@ func GetPageDetailByID(pageID uint64) (*model.ShowPageDetail, error) { GUID: siteURL + p.GUID, CommentCount: p.CommentCount, ViewCount: p.ViewCount, - PostedTime: utils.GetFormatNullTime(&p.PostDate, "2006-01-02 15:04"), + PostedTime: utils.GetFormatNullTime(p.PostDate, "2006-01-02 15:04"), MetaData: make(map[string]interface{}), } diff --git a/internal/frontend/service/subject.go b/internal/frontend/service/subject.go index 9a54d03..0bf45ea 100644 --- a/internal/frontend/service/subject.go +++ b/internal/frontend/service/subject.go @@ -1,10 +1,10 @@ package service import ( + "database/sql" "fmt" "sync" - "github.com/go-sql-driver/mysql" "github.com/puti-projects/puti/internal/model" "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/utils" @@ -36,7 +36,7 @@ type ChildrenSubejctsResult struct { Description string CoverImageURL string Count uint64 - LastUpdated mysql.NullTime + LastUpdated sql.NullTime } // GetChildrenSubejcts get subject's all children @@ -75,7 +75,6 @@ func GetChildrenSubejcts(parentID uint64) (subjectResult []*model.ShowSubjectLis IDMap: make(map[uint64]*model.ShowSubjectList, len(subjects)), } - errChan := make(chan error, 1) finished := make(chan bool, 1) for _, s := range subjects { @@ -104,11 +103,7 @@ func GetChildrenSubejcts(parentID uint64) (subjectResult []*model.ShowSubjectLis close(finished) }() - select { - case <-finished: - case err := <-errChan: - return nil, err - } + <-finished for _, id := range ids { subjectResult = append(subjectResult, subjectList.IDMap[id]) @@ -118,7 +113,7 @@ func GetChildrenSubejcts(parentID uint64) (subjectResult []*model.ShowSubjectLis } // getDifferDayBetweenLastUpdatedTimeAndNow calculate updated how many days ago -func getDifferDayBetweenLastUpdatedTimeAndNow(lastUpdatedTime *mysql.NullTime) string { +func getDifferDayBetweenLastUpdatedTimeAndNow(lastUpdatedTime *sql.NullTime) string { if lastUpdatedTime.Valid { day := utils.SubNullTimeUnitlNowAsDay(lastUpdatedTime.Time) if day < 1 { diff --git a/internal/model/media.go b/internal/model/media.go index cb0d036..75cf04c 100644 --- a/internal/model/media.go +++ b/internal/model/media.go @@ -1,14 +1,13 @@ package model import ( - "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/utils" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) // MediaModel the definition of media model -type MediaModel struct { +type Media struct { Model UserID uint64 `gorm:"column:upload_user_id;not null"` @@ -16,10 +15,10 @@ type MediaModel struct { Slug string `gorm:"column:slug;not null"` Description string `gorm:"column:description;not null"` GUID string `gorm:"column:guid;not null"` - Type string `gorm:"column:type;not null"` + Type string `gorm:"column:type;not null;default:picture"` MimeType string `gorm:"column:mime_type;not null"` Usage string `gorm:"column:usage;not null"` - Status uint64 `gorm:"column:status;default:1;not null"` + Status uint64 `gorm:"column:status;not null;default:1"` } // ResourceTypePicture resource type of picture @@ -35,58 +34,55 @@ const UsageDefault = "common" const UsageCover = "cover" // TableName is the resource table name in db -func (c *MediaModel) TableName() string { +func (m *Media) TableName() string { return "pt_resource" } // BeforeCreate set values before create // Set file type by mime-type -func (c *MediaModel) BeforeCreate(scope *gorm.Scope) error { - return scope.SetColumn("Type", utils.GetFileType(c.MimeType)) +func (m *Media) BeforeCreate(tx *gorm.DB) (err error) { + m.Type = utils.GetFileType(m.MimeType) + return } // Create save the new media file info -func (c *MediaModel) Create() error { - return db.DBEngine.Create(&c).Error +func (m *Media) Create(db *gorm.DB) error { + return db.Create(m).Error } // Update update media info -func (c *MediaModel) Update() (err error) { - if err = db.DBEngine.Model(&MediaModel{}).Save(c).Error; err != nil { - return err - } - - return nil +func (m *Media) Update(db *gorm.DB) (err error) { + return db.Save(m).Error } -// DeleteMedia deletes the media info by id (not file) -func DeleteMedia(id uint64) error { - media := MediaModel{} - media.ID = id - return db.DBEngine.Delete(&media).Error +// GetByID get media info by ID +func (m *Media) GetByID(db *gorm.DB) error { + return db.Where("`status` = 1 AND `deleted_time` is null AND `id` = ?", m.ID).First(m).Error } -// ListMedia returns the media list in condition -func ListMedia(limit, page int) ([]*MediaModel, uint64, error) { - medias := make([]*MediaModel, 0) - var count uint64 +// Delete delete the media info by id (not file right now) +func (m *Media) Delete(db *gorm.DB) error { + return db.Delete(m).Error +} - where := "deleted_time is null" - if err := db.DBEngine.Model(&MediaModel{}).Where(where).Count(&count).Error; err != nil { - return medias, count, err +// Count count media in condition +func (m *Media) Count(db *gorm.DB, where string, whereArgs []interface{}) (count int64, err error) { + if whereArgs != nil { + err = db.Model(m).Where(where, whereArgs...).Count(&count).Error + return } + err = db.Model(m).Where(where).Count(&count).Error + return +} - offset := (page - 1) * limit - if err := db.DBEngine.Where(where).Offset(offset).Limit(limit).Order("created_time DESC").Find(&medias).Error; err != nil { - return medias, count, err +// List get media list +func (m *Media) List(db *gorm.DB, where string, whereArgs []interface{}, offset, limit int) (medias []*Media, err error) { + medias = make([]*Media, 0) + if whereArgs != nil { + err = db.Where(where, whereArgs...).Offset(offset).Limit(limit).Order("created_time DESC").Find(&medias).Error + return } - return medias, count, nil -} - -// GetMediaByID get media info by id -func GetMediaByID(id uint64) (*MediaModel, error) { - m := &MediaModel{} - d := db.DBEngine.Where("status = 1 AND deleted_time is null AND id = ?", id).First(&m) - return m, d.Error + err = db.Where(where).Offset(offset).Limit(limit).Order("created_time DESC").Find(&medias).Error + return medias, err } diff --git a/internal/model/model.go b/internal/model/model.go index e80e93c..f7427ab 100644 --- a/internal/model/model.go +++ b/internal/model/model.go @@ -2,12 +2,14 @@ package model import ( "time" + + "gorm.io/gorm" ) // Model base model type Model struct { - ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id"` - CreatedAt time.Time `gorm:"column:created_time;not null"` - UpdatedAt time.Time `gorm:"column:updated_time;not null"` - DeletedAt *time.Time `gorm:"column:deleted_time;DEFAULT null" sql:"index"` + ID uint64 `gorm:"primaryKey;autoIncrement;column:id"` + CreatedAt time.Time `gorm:"column:created_time;not null"` + UpdatedAt time.Time `gorm:"column:updated_time;not null"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_time;DEFAULT null" sql:"index"` } diff --git a/internal/model/option.go b/internal/model/option.go index 2ff0608..a5c81c8 100644 --- a/internal/model/option.go +++ b/internal/model/option.go @@ -4,7 +4,7 @@ import "github.com/puti-projects/puti/internal/pkg/db" // OptionModel site options type OptionModel struct { - ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id"` + ID uint64 `gorm:"primaryKey;autoIncrement;column:id"` OptionName string `gorm:"column:option_name;not null"` OptionValue string `gorm:"column:option_value;not null"` Autoload uint64 `gorm:"column:autoload;not null"` diff --git a/internal/model/post.go b/internal/model/post.go index 8de36af..c7bbd1d 100644 --- a/internal/model/post.go +++ b/internal/model/post.go @@ -1,34 +1,37 @@ package model import ( - "github.com/go-sql-driver/mysql" + "database/sql" + "errors" + "github.com/puti-projects/puti/internal/pkg/db" + "gorm.io/gorm" ) // PostModel is the struct model for post type PostModel struct { Model - UserID uint64 `gorm:"column:user_id;not null"` - PostType string `gorm:"column:post_type;not null"` - Title string `gorm:"column:title;not null"` - ContentMarkdown string `gorm:"column:content_markdown;not null"` - ContentHTML string `gorm:"column:content_html;not null"` - Slug string `gorm:"column:slug;not null"` - ParentID uint64 `gorm:"column:parent_id;not null"` // set to 0 now, use for draft history feature in the future - Status string `gorm:"column:status;not null"` - CommentStatus uint64 `gorm:"column:comment_status;not null"` - IfTop uint64 `gorm:"column:if_top;not null"` - GUID string `gorm:"column:guid;not null"` - CoverPicture string `gorm:"column:cover_picture;not null"` - CommentCount uint64 `gorm:"column:comment_count;not null"` - ViewCount uint64 `gorm:"column:view_count;not null"` - PostDate mysql.NullTime `gorm:"column:posted_time;not null"` + UserID uint64 `gorm:"column:user_id;not null"` + PostType string `gorm:"column:post_type;not null"` + Title string `gorm:"column:title;not null"` + ContentMarkdown string `gorm:"column:content_markdown;not null"` + ContentHTML string `gorm:"column:content_html;not null"` + Slug string `gorm:"column:slug;not null"` + ParentID uint64 `gorm:"column:parent_id;not null"` // set to 0 now, use for draft history feature in the future + Status string `gorm:"column:status;not null"` + CommentStatus uint64 `gorm:"column:comment_status;not null"` + IfTop uint64 `gorm:"column:if_top;not null"` + GUID string `gorm:"column:guid;not null"` + CoverPicture string `gorm:"column:cover_picture;not null"` + CommentCount uint64 `gorm:"column:comment_count;not null"` + ViewCount uint64 `gorm:"column:view_count;not null"` + PostDate *sql.NullTime `gorm:"column:posted_time;not null"` } // PostMetaModel meta data for post type PostMetaModel struct { - ID uint64 `gorm:"primary_key;AUTO_INCREMENT;column:id"` + ID uint64 `gorm:"primaryKey;autoIncrement;column:id"` PostID uint64 `gorm:"column:post_id;not null"` MetaKey string `gorm:"column:meta_key;not null"` MetaValue string `gorm:"column:meta_value;not null"` @@ -79,9 +82,9 @@ func GetOnePostMetaData(postID uint64, metaKey string) (*PostMetaModel, error) { } // ListPost returns the posts list in condition -func ListPost(postType, title string, page, number int, sort, status string) ([]*PostModel, uint64, error) { +func ListPost(postType, title string, page, number int, sort, status string) ([]*PostModel, int64, error) { posts := make([]*PostModel, 0) - var count uint64 + var count int64 where := "post_type = ? AND parent_id = ?" whereArgs := []interface{}{postType, 0} @@ -115,17 +118,19 @@ func ListPost(postType, title string, page, number int, sort, status string) ([] } // PageCheckSlugExist check the slug if already exist +// ErrRecordNotFound => False +// exist => True func PageCheckSlugExist(pageID uint64, Slug string) bool { post := &PostModel{} - var ifNotFound bool + var err error if pageID > 0 { - ifNotFound = db.DBEngine.Where("id != ? AND slug = ?", pageID, Slug).First(&post).RecordNotFound() + err = db.DBEngine.Where("id != ? AND slug = ?", pageID, Slug).First(&post).Error } else { - ifNotFound = db.DBEngine.Where("slug = ?", Slug).First(&post).RecordNotFound() + err = db.DBEngine.Where("slug = ?", Slug).First(&post).Error } - if ifNotFound { + if errors.Is(err, gorm.ErrRecordNotFound) { return false } diff --git a/internal/model/show.go b/internal/model/show.go index e793095..54408e1 100644 --- a/internal/model/show.go +++ b/internal/model/show.go @@ -1,9 +1,8 @@ package model import ( + "database/sql" "html/template" - - "github.com/go-sql-driver/mysql" ) // ShowArticle output article model for list @@ -36,12 +35,12 @@ type ShowCategory struct { // ShowWidgetLatestArticles latest article list for widget // Use {{ formatNullTime .PostedTime "2006-01-02 15:04" }} to decode the time type ShowWidgetLatestArticles struct { - ID string `json:"id"` - Title string `json:"title"` - GUID string `json:"GUID"` - CommentCount string `json:"comment_count"` - ViewCount string `json:"view_count"` - PostedTime *mysql.NullTime `json:"posted_time"` + ID string `json:"id"` + Title string `json:"title"` + GUID string `json:"GUID"` + CommentCount string `json:"comment_count"` + ViewCount string `json:"view_count"` + PostedTime *sql.NullTime `json:"posted_time"` } // ShowWidgetCategoryTreeNode category tree node for widget diff --git a/internal/model/subject.go b/internal/model/subject.go index 42534e3..8c9a2df 100644 --- a/internal/model/subject.go +++ b/internal/model/subject.go @@ -1,7 +1,8 @@ package model import ( - "github.com/go-sql-driver/mysql" + "database/sql" + "github.com/puti-projects/puti/internal/pkg/db" ) @@ -9,14 +10,14 @@ import ( type SubjectModel struct { Model - ParentID uint64 `gorm:"column:parent_id;not null"` - Name string `gorm:"column:name;not null"` - Slug string `gorm:"column:slug;not null"` - Description string `gorm:"column:description;not null"` - CoverImage uint64 `gorm:"column:cover_image;not null"` - IsEnd uint64 `gorm:"column:is_end;not null"` - Count uint64 `gorm:"column:count;not null"` - LastUpdated mysql.NullTime `gorm:"column:last_updated"` + ParentID uint64 `gorm:"column:parent_id;not null"` + Name string `gorm:"column:name;not null"` + Slug string `gorm:"column:slug;not null"` + Description string `gorm:"column:description;not null"` + CoverImage uint64 `gorm:"column:cover_image;not null"` + IsEnd uint64 `gorm:"column:is_end;not null"` + Count uint64 `gorm:"column:count;not null"` + LastUpdated sql.NullTime `gorm:"column:last_updated"` } // TableName is the resource table name in db @@ -55,7 +56,7 @@ func GetAllSubjects() ([]*SubjectModel, error) { // SubjectCheckNameExistWhileCreate check the subject name if is already exist func SubjectCheckNameExistWhileCreate(name string) bool { - count := 0 + var count int64 = 0 subjectModel := &SubjectModel{} db.DBEngine.Table(subjectModel.TableName()). Where("`name` = ?", name). @@ -70,7 +71,7 @@ func SubjectCheckNameExistWhileCreate(name string) bool { // SubjectCheckNameExistWhileUpdate check the subject name if is already exist without itself func SubjectCheckNameExistWhileUpdate(subjectID uint64, name string) bool { - count := 0 + var count int64 = 0 subjectModel := &SubjectModel{} db.DBEngine.Table(subjectModel.TableName()). Where("`id` != ? AND `name` = ?", subjectID, name). @@ -84,7 +85,7 @@ func SubjectCheckNameExistWhileUpdate(subjectID uint64, name string) bool { } // GetSubjectChildrenNumber calcuelate the total number of subject's children -func GetSubjectChildrenNumber(subjectID uint64) (count int) { +func GetSubjectChildrenNumber(subjectID uint64) (count int64) { db.DBEngine.Model(&SubjectModel{}).Where("`parent_id` = ?", subjectID).Count(&count) return count } diff --git a/internal/model/subjectRelationship.go b/internal/model/subjectRelationship.go index ae4bae4..760b2a4 100644 --- a/internal/model/subjectRelationship.go +++ b/internal/model/subjectRelationship.go @@ -4,8 +4,8 @@ import "github.com/puti-projects/puti/internal/pkg/db" // SubjectRelationshipsModel `pt_subject_relationships` 's struct type SubjectRelationshipsModel struct { - ObjectID uint64 `gorm:"column:object_id;not null;primary_key"` - SubjectID uint64 `gorm:"column:subject_id;not null;primary_key"` + ObjectID uint64 `gorm:"column:object_id;not null;primaryKey"` + SubjectID uint64 `gorm:"column:subject_id;not null;primaryKey"` OrderNum string `gorm:"column:order_num;not null"` } diff --git a/internal/model/taxonomy.go b/internal/model/taxonomy.go index 13a12fd..6a1fc4a 100644 --- a/internal/model/taxonomy.go +++ b/internal/model/taxonomy.go @@ -4,8 +4,8 @@ import "github.com/puti-projects/puti/internal/pkg/db" // TermTaxonomyModel `pt_term_taxonomy`'s struct using GORM Has-One model type TermTaxonomyModel struct { - ID uint64 `gorm:"column:term_taxonomy_id;not null;primary_key"` - Term TermModel `gorm:"foreignkey:TermID;association_foreignkey:ID"` + ID uint64 `gorm:"column:term_taxonomy_id;not null;primaryKey"` + Term TermModel `gorm:"foreignKey:TermID;references:ID"` TermID uint64 `gorm:"column:term_id;not null"` ParentTermID uint64 `gorm:"column:parent_term_id;not null"` Level uint64 `gorm:"column:level;not null"` @@ -72,13 +72,13 @@ func GetTermsInfo(termID uint64) (*TermTaxonomyModel, error) { return nil, model.Error } - result := db.DBEngine.Model(&termTaxonomy).Related(&termTaxonomy.Term, "TermID") - return termTaxonomy, result.Error + err := db.DBEngine.Model(&termTaxonomy).Association("Term").Find(&termTaxonomy.Term) + return termTaxonomy, err } // TaxonomyCheckNameExist check the taxonomy name if is already exist func TaxonomyCheckNameExist(name, taxonomy string) bool { - count := 0 + var count int64 = 0 db.DBEngine.Table("pt_term t"). Select("t.term_id, t.name"). Joins("inner join pt_term_taxonomy tt on tt.term_id = t.term_id"). @@ -125,7 +125,7 @@ func GetTermTaxonomy(termID uint64, taxonomyType string) (*TermTaxonomyModel, er } // GetTermChildrenNumber calcuelate the total number of term's children -func GetTermChildrenNumber(termID uint64, taxonomyType string) (count int) { +func GetTermChildrenNumber(termID uint64, taxonomyType string) (count int64) { if taxonomyType != "category" { count = 0 return count diff --git a/internal/model/taxonomyRelationship.go b/internal/model/taxonomyRelationship.go index 8def13a..fa84a6d 100644 --- a/internal/model/taxonomyRelationship.go +++ b/internal/model/taxonomyRelationship.go @@ -4,8 +4,8 @@ import "github.com/puti-projects/puti/internal/pkg/db" // TermRelationshipsModel `pt_term_relationships` 's struct taxomony raltionships with object type TermRelationshipsModel struct { - ObjectID uint64 `gorm:"column:object_id;not null;primary_key"` - TermTaxonomyID uint64 `gorm:"column:term_taxonomy_id;not null;primary_key"` + ObjectID uint64 `gorm:"column:object_id;not null;primaryKey"` + TermTaxonomyID uint64 `gorm:"column:term_taxonomy_id;not null;primaryKey"` TermOrder string `gorm:"column:term_order;not null"` } diff --git a/internal/model/user.go b/internal/model/user.go index ce07f99..1310d39 100644 --- a/internal/model/user.go +++ b/internal/model/user.go @@ -2,12 +2,12 @@ package model import ( "github.com/puti-projects/puti/internal/pkg/auth" - "github.com/puti-projects/puti/internal/pkg/constvar" - "github.com/puti-projects/puti/internal/pkg/db" + + "gorm.io/gorm" ) -// UserModel user model -type UserModel struct { +// User user model +type User struct { Model Username string `gorm:"column:account;unique;not null" validate:"min=1,max=60"` @@ -16,86 +16,59 @@ type UserModel struct { Email string `gorm:"column:email;unique"` Avatar string `gorm:"column:avatar"` PageURL string `gorm:"column:page_url"` - Status int `gorm:"column:status" sql:"index"` - Roles string `gorm:"column:role"` + Status int `gorm:"column:status;default:1" sql:"index"` + Roles string `gorm:"column:role;default:subscriber"` } // TableName is the user table name in db -func (c *UserModel) TableName() string { +func (u *User) TableName() string { return "pt_user" } // Create creates a new user account -func (c *UserModel) Create() error { - return db.DBEngine.Create(&c).Error +func (u *User) Create(db *gorm.DB) error { + return db.Create(u).Error } // Encrypt the user password. -func (c *UserModel) Encrypt() (err error) { - c.Password, err = auth.Encrypt(c.Password) - return +func (u *User) Encrypt() (err error) { + u.Password, err = auth.Encrypt(u.Password) + return err } -// GetUser gets an user by the user identifier. -func GetUser(username string) (*UserModel, error) { - u := &UserModel{} - d := db.DBEngine.Where("status = 1 AND deleted_time is null AND account = ?", username).First(&u) - return u, d.Error +// Get get an user by the user identifier +func (u *User) Get(db *gorm.DB) error { + if u.Username != "" { + db = db.Where("status = 1 AND deleted_time is null AND account = ?", u.Username) + } + return db.First(u).Error } -// GetUserByID gets an user by ID -func GetUserByID(id uint64) (*UserModel, error) { - u := &UserModel{} - d := db.DBEngine.Model(&UserModel{}).Where("`id` = ?", id).Find(&u) - return u, d.Error +// GetByID get user by ID +func (u *User) GetByID(db *gorm.DB) error { + return db.Where("`id` = ?", u.ID).First(u).Error } -// DeleteUser deletes the user by id -func DeleteUser(id uint64) error { - user := UserModel{} - user.ID = id - return db.DBEngine.Delete(&user).Error +// Delete delete a user by id +func (u *User) Delete(db *gorm.DB) error { + return db.Delete(u).Error } -// Update updates an user account information. -func (c *UserModel) Update() (err error) { - return db.DBEngine.Model(&UserModel{}).Save(c).Error +// Update updates an user account information +func (u *User) Update(db *gorm.DB) error { + return db.Save(u).Error } -// ListUser List all users -func ListUser(username, role string, number, page, status int) ([]*UserModel, uint64, error) { - if number == 0 { - number = constvar.DefaultLimit - } - - users := make([]*UserModel, 0) - var count uint64 - - where := "`deleted_time` is null" - whereArgs := []interface{}{} - if username != "" { - where += " AND `nickname` LIKE ?" - whereArgs = append(whereArgs, "%"+username+"%") - } - - if role != "" { - where += " AND `role` = ?" - whereArgs = append(whereArgs, role) - } - - if status != 0 { - where += " AND `status` = ?" - whereArgs = append(whereArgs, status) - } - - if err := db.DBEngine.Model(&UserModel{}).Where(where, whereArgs...).Count(&count).Error; err != nil { - return users, count, err - } - - offset := (page - 1) * number - if err := db.DBEngine.Where(where, whereArgs...).Offset(offset).Limit(number).Order("id desc").Find(&users).Error; err != nil { - return users, count, err - } +// Count count user +func (u *User) Count(db *gorm.DB, where string, whereArgs []interface{}) (int64, error) { + var count int64 + err := db.Model(u).Where(where, whereArgs...).Count(&count).Error + return count, err +} - return users, count, nil +// List get user list +func (u *User) List(db *gorm.DB, where string, whereArgs []interface{}, offset, number int) ([]*User, error) { + users := make([]*User, 0) + err := db.Where(where, whereArgs...).Offset(offset).Limit(number).Order("id desc").Find(&users).Error + return users, err } diff --git a/internal/pkg/config/config.go b/internal/pkg/config/config.go index c98ae1b..a7dd895 100644 --- a/internal/pkg/config/config.go +++ b/internal/pkg/config/config.go @@ -6,6 +6,7 @@ import ( "github.com/fsnotify/fsnotify" "github.com/spf13/viper" + "go.uber.org/zap" ) // Config instance struct of config @@ -106,10 +107,12 @@ func (c *Config) reloadAllSection() error { for k, v := range sections { err := c.readConfigSections(k, v) if err != nil { + zap.L().Error(fmt.Sprintf("reload all config sections failed, err:%s", err)) return err } } + zap.L().Info("already reload all config sections") return nil } @@ -118,8 +121,8 @@ func (c *Config) watchConfigChange() { go func() { c.vp.WatchConfig() c.vp.OnConfigChange(func(e fsnotify.Event) { + zap.L().Info("config file changed", zap.String("config-path", e.Name)) _ = c.reloadAllSection() - fmt.Printf("config file changed: %s\n", e.Name) }) }() } diff --git a/internal/pkg/config/path.go b/internal/pkg/config/path.go index db830f4..3a40d9d 100644 --- a/internal/pkg/config/path.go +++ b/internal/pkg/config/path.go @@ -25,6 +25,14 @@ const ( StaticPathTheme = "theme" ) +// upload path config +const ( + // UploadPath defines the media file save path uri + UploadPath = "/uploads/" + // savePath defines the use avatar saving path + UploadUserAvatarPath = "/uploads/users/" +) + // StaticPath change path to static path base on the StaticPathRoot func StaticPath(Path string) string { return filepath.ToSlash(filepath.Join(StaticPathRoot, Path)) diff --git a/internal/pkg/config/section.go b/internal/pkg/config/section.go index 1369363..9236854 100644 --- a/internal/pkg/config/section.go +++ b/internal/pkg/config/section.go @@ -17,7 +17,8 @@ type SafetyConfig struct { } type LogConfig struct { - LoggerFile string `mapstructure:"logger_file"` + LoggerFileInfo string `mapstructure:"logger_file_info"` + LoggerFileError string `mapstructure:"logger_file_error"` LoggerMaxSize int `mapstructure:"logger_max_size"` LoggerMaxBackups int `mapstructure:"logger_max_backups"` LoggerMaxAge int `mapstructure:"logger_max_age"` diff --git a/internal/pkg/counter/ticker.go b/internal/pkg/counter/ticker.go index 1cf11cb..3ce7d3b 100644 --- a/internal/pkg/counter/ticker.go +++ b/internal/pkg/counter/ticker.go @@ -7,7 +7,7 @@ import ( "github.com/puti-projects/puti/internal/pkg/db" "github.com/puti-projects/puti/internal/pkg/logger" - "github.com/jinzhu/gorm" + "gorm.io/gorm" ) const ( diff --git a/internal/pkg/db/db.go b/internal/pkg/db/db.go index 8fe65e4..5315b27 100644 --- a/internal/pkg/db/db.go +++ b/internal/pkg/db/db.go @@ -2,29 +2,31 @@ package db import ( "fmt" + "time" - "github.com/jinzhu/gorm" "github.com/puti-projects/puti/internal/pkg/config" + + "gorm.io/driver/mysql" + "gorm.io/gorm" ) var ( DBEngine *gorm.DB ) +// InitDB init db connection pool func InitDB() error { var err error - DBEngine, err = openDB( - config.Db.Username, - config.Db.Password, - config.Db.Addr, - config.Db.Name, - ) + DBEngine, err = openDB(config.Db.Username, config.Db.Password, config.Db.Addr, config.Db.Name) if err != nil { return err } - // set for db connection - setupDB(DBEngine) + // set up config of db connection pool + err = setupDB(DBEngine) + if err != nil { + return err + } return nil } @@ -32,14 +34,16 @@ func InitDB() error { // openDB creates the DB connection // It sets the location to UTC time func openDB(username, password, addr, name string) (*gorm.DB, error) { - config := fmt.Sprintf("%s:%s@tcp(%s)/%s?charset=utf8mb4,utf8&parseTime=%t&loc=%s", + dsn := fmt.Sprintf( + "%s:%s@tcp(%s)/%s?charset=utf8mb4&parseTime=%t&loc=%s", username, password, addr, name, true, - "UTC") - db, err := gorm.Open("mysql", config) + "UTC", + ) + db, err := gorm.Open(mysql.Open(dsn), &gorm.Config{}) if err != nil { return nil, err } @@ -48,15 +52,18 @@ func openDB(username, password, addr, name string) (*gorm.DB, error) { } // setupDB sets the DB settings -func setupDB(db *gorm.DB) { - // gorm log mode - if config.Server.Runmode == "debug" { - db.LogMode(true) - } else { - db.LogMode(false) +func setupDB(db *gorm.DB) error { + sqlDB, err := db.DB() + if err != nil { + return err } - // connection pool setting - db.DB().SetMaxOpenConns(config.Db.MaxOpenConns) // 用于设置最大打开的连接数,默认值为0表示不限制.设置最大的连接数,可以避免并发太高导致连接mysql出现too many connections的错误。 - db.DB().SetMaxIdleConns(config.Db.MaxIdleConns) // 用于设置闲置的连接数.设置闲置的连接数则当开启的一个连接使用完成后可以放在池里等候下一次使用。 + // 设置空闲连接池中连接的最大数量 + sqlDB.SetMaxIdleConns(config.Db.MaxIdleConns) + // 设置打开数据库连接的最大数量 + sqlDB.SetMaxOpenConns(config.Db.MaxOpenConns) + // 设置连接可复用的最大时间 + sqlDB.SetConnMaxLifetime(time.Hour) + + return nil } diff --git a/internal/pkg/logger/logger.go b/internal/pkg/logger/logger.go index c198027..8f09334 100644 --- a/internal/pkg/logger/logger.go +++ b/internal/pkg/logger/logger.go @@ -16,48 +16,43 @@ import ( "os" "github.com/puti-projects/puti/internal/pkg/config" + "go.uber.org/zap" "go.uber.org/zap/zapcore" - lumberjack "gopkg.in/natefinch/lumberjack.v2" + "gopkg.in/natefinch/lumberjack.v2" ) var logger *zap.Logger // InitLogger init zap logger func InitLogger(runmode string) { - w := zapcore.AddSync(&lumberjack.Logger{ - Filename: config.Log.LoggerFile, - MaxSize: config.Log.LoggerMaxSize, // megabytes - MaxBackups: config.Log.LoggerMaxBackups, - MaxAge: config.Log.LoggerMaxAge, // days - }) - if runmode == "release" { - InitProductionLogger(w) + logger = InitProductionLogger() } else { - InitDevelopmentLogger(w) + logger = InitDevelopmentLogger() } + defer logger.Sync() + + zap.ReplaceGlobals(logger) } // InitProductionLogger init the logger for production environment -func InitProductionLogger(w zapcore.WriteSyncer) { +func InitProductionLogger() *zap.Logger { highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }) - jsonEncoder := zapcore.NewJSONEncoder(newPutiEncoderConfig()) - core := zapcore.NewCore( - jsonEncoder, - w, + zapcore.NewJSONEncoder(newPutiEncoderConfig()), + getWriteSyncer("error"), highPriority, ) - logger = zap.New(core) - defer logger.Sync() + + return zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) } // InitDevelopmentLogger init the logger for development environment -func InitDevelopmentLogger(w zapcore.WriteSyncer) { +func InitDevelopmentLogger() *zap.Logger { highPriority := zap.LevelEnablerFunc(func(lvl zapcore.Level) bool { return lvl >= zapcore.ErrorLevel }) @@ -65,21 +60,49 @@ func InitDevelopmentLogger(w zapcore.WriteSyncer) { return lvl < zapcore.ErrorLevel }) - consoleDebugging := zapcore.Lock(os.Stdout) - consoleErrors := zapcore.Lock(os.Stderr) - - jsonEncoder := zapcore.NewJSONEncoder(newPutiEncoderConfig()) - consoleEncoder := zapcore.NewConsoleEncoder(newPutiEncoderConfig()) - core := zapcore.NewTee( - // lumberjack writer - zapcore.NewCore(jsonEncoder, w, zap.InfoLevel), - // console - zapcore.NewCore(consoleEncoder, consoleDebugging, lowPriority), - zapcore.NewCore(consoleEncoder, consoleErrors, highPriority), + // normal json encoder + zapcore.NewCore( + zapcore.NewJSONEncoder(newPutiEncoderConfig()), + getWriteSyncer("info"), + lowPriority, + ), + // error json encoder + zapcore.NewCore( + zapcore.NewJSONEncoder(newPutiEncoderConfig()), + getWriteSyncer("error"), + highPriority, + ), + // normal console + zapcore.NewCore( + zapcore.NewConsoleEncoder(newPutiEncoderConfig()), + zapcore.Lock(os.Stdout), + lowPriority, + ), + // error console + zapcore.NewCore( + zapcore.NewConsoleEncoder(newPutiEncoderConfig()), + zapcore.Lock(os.Stderr), + highPriority, + ), ) - logger = zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) - defer logger.Sync() + return zap.New(core, zap.AddCaller(), zap.AddCallerSkip(1)) +} + +func getWriteSyncer(writemode string) zapcore.WriteSyncer { + sl := &lumberjack.Logger{ + MaxSize: config.Log.LoggerMaxSize, // megabytes + MaxBackups: config.Log.LoggerMaxBackups, + MaxAge: config.Log.LoggerMaxAge, // days + } + + if writemode == "error" { + sl.Filename = config.Log.LoggerFileError + return zapcore.AddSync(sl) + } + + sl.Filename = config.Log.LoggerFileInfo + return zapcore.AddSync(sl) } func newPutiEncoderConfig() zapcore.EncoderConfig { @@ -89,11 +112,12 @@ func newPutiEncoderConfig() zapcore.EncoderConfig { LevelKey: "level", NameKey: "name", CallerKey: "caller", + FunctionKey: zapcore.OmitKey, MessageKey: "message", - StacktraceKey: "stack-trace", + StacktraceKey: "stacktrace", LineEnding: zapcore.DefaultLineEnding, - EncodeLevel: zapcore.CapitalLevelEncoder, - EncodeTime: zapcore.ISO8601TimeEncoder, // TODO 时区 + EncodeLevel: zapcore.LowercaseLevelEncoder, + EncodeTime: zapcore.TimeEncoderOfLayout("2006-01-02 15:04:05"), EncodeDuration: zapcore.StringDurationEncoder, EncodeCaller: zapcore.ShortCallerEncoder, } @@ -130,6 +154,7 @@ func Errorf(template string, args ...interface{}) { } // DPanic logs a message at DPanicLevel. The message includes any fields passed at the log site. +// DPanic means "development panic" func DPanic(msg string, args ...zapcore.Field) { logger.DPanic(msg, args...) } diff --git a/internal/pkg/option/option.go b/internal/pkg/option/option.go index 80a10db..b170f15 100644 --- a/internal/pkg/option/option.go +++ b/internal/pkg/option/option.go @@ -14,22 +14,22 @@ var DefaultExpiration = 2 * time.Hour // DefaultPurgesExpiration default purges expired items of 3 hours for puti var DefaultPurgesExpiration = 3 * time.Hour +type optionCache struct { + gocacheBody *gocache.Cache +} + // Options cache var Options = &optionCache{ gocacheBody: gocache.New(DefaultExpiration, DefaultPurgesExpiration), } -type optionCache struct { - gocacheBody *gocache.Cache -} - // LoadOptions load default options -func LoadOptions() { +func LoadOptions() error { if err := getAutoLoadOptions(); err != nil { - logger.Errorf("load options failed, %s", err) + return err } - logger.Info("options has been deployed successfully") + return nil } // getAutoLoadOptions get options need to load diff --git a/internal/pkg/token/token.go b/internal/pkg/token/token.go index fecdc29..eaee6b5 100644 --- a/internal/pkg/token/token.go +++ b/internal/pkg/token/token.go @@ -7,7 +7,7 @@ import ( jwt "github.com/dgrijalva/jwt-go" "github.com/gin-gonic/gin" - "github.com/spf13/viper" + "github.com/puti-projects/puti/internal/pkg/config" ) var ( @@ -62,7 +62,7 @@ func Parse(tokenString string, secret string) (*Context, error) { // ParseToken directly parse the token func ParseToken(t string) (*Context, error) { // Load the jwt secret from config - secret := viper.GetString("jwt_secret") + secret := config.Safety.JwtSecret if len(t) == 0 { return &Context{}, ErrMissingToken @@ -77,7 +77,7 @@ func ParseRequest(c *gin.Context) (*Context, error) { header := c.Request.Header.Get("Authorization") // Load the jwt secret from config - secret := viper.GetString("jwt_secret") + secret := config.Safety.JwtSecret if len(header) == 0 { return &Context{}, ErrMissingHeader @@ -93,7 +93,7 @@ func ParseRequest(c *gin.Context) (*Context, error) { func Sign(ctx *gin.Context, c Context, secret string) (tokenString string, err error) { // Load the jwt secret from the Gin config if the secret isn't specified. if secret == "" { - secret = viper.GetString("jwt_secret") + secret = config.Safety.JwtSecret } // The token content. token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ diff --git a/internal/backend/middleware/auth.go b/internal/routers/middleware/api/auth.go similarity index 96% rename from internal/backend/middleware/auth.go rename to internal/routers/middleware/api/auth.go index 6bed4a9..2d221f1 100644 --- a/internal/backend/middleware/auth.go +++ b/internal/routers/middleware/api/auth.go @@ -1,4 +1,4 @@ -package middleware +package api import ( "github.com/puti-projects/puti/internal/backend/handler" diff --git a/internal/backend/middleware/header.go b/internal/routers/middleware/api/header.go similarity index 97% rename from internal/backend/middleware/header.go rename to internal/routers/middleware/api/header.go index 6f3640f..58b423b 100644 --- a/internal/backend/middleware/header.go +++ b/internal/routers/middleware/api/header.go @@ -1,4 +1,6 @@ -package middleware +package api + +// header handlerFunc import ( "net/http" diff --git a/internal/backend/middleware/requestid.go b/internal/routers/middleware/api/requestid.go similarity index 96% rename from internal/backend/middleware/requestid.go rename to internal/routers/middleware/api/requestid.go index b4cd7a9..21a5483 100644 --- a/internal/backend/middleware/requestid.go +++ b/internal/routers/middleware/api/requestid.go @@ -1,4 +1,4 @@ -package middleware +package api import ( "github.com/gin-gonic/gin" diff --git a/internal/routers/middleware/logger.go b/internal/routers/middleware/logger.go new file mode 100644 index 0000000..23fefc4 --- /dev/null +++ b/internal/routers/middleware/logger.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "time" + + "github.com/puti-projects/puti/internal/pkg/logger" + "github.com/puti-projects/puti/internal/utils" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// AccessLogger replace gin default access logger +// Note: using zap logger +// In puti, runmode in release will not generate log infomation +// In production, it is better to let web server (nginx) to do this job +func AccessLogger() gin.HandlerFunc { + return func(c *gin.Context) { + // Start timer + start := time.Now() + path := c.Request.URL.Path + query := c.Request.URL.RawQuery + + // Process requests + c.Next() + + // log part + cost := time.Since(start) + logger.Info(path, + zap.Int("status", c.Writer.Status()), + zap.String("method", c.Request.Method), + zap.String("path", path), + zap.String("query", query), + zap.String("ip", c.ClientIP()), + zap.String("user-agent", c.Request.UserAgent()), + zap.String("errors", c.Errors.ByType(gin.ErrorTypePrivate).String()), + zap.Duration("cost", cost), + zap.String("request-ID", utils.GetReqID(c)), + ) + } +} diff --git a/internal/routers/middleware/recovery.go b/internal/routers/middleware/recovery.go new file mode 100644 index 0000000..b16e24f --- /dev/null +++ b/internal/routers/middleware/recovery.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "net" + "net/http" + "net/http/httputil" + "os" + "runtime/debug" + "strings" + + "github.com/puti-projects/puti/internal/pkg/logger" + + "github.com/gin-gonic/gin" + "go.uber.org/zap" +) + +// Recovery replace gin default Recovery function using zap logger +func Recovery() gin.HandlerFunc { + return func(c *gin.Context) { + defer func() { + if err := recover(); err != nil { + // Check for a broken connection, as it is not really a + // condition that warrants a panic stack trace. + var brokenPipe bool + if ne, ok := err.(*net.OpError); ok { + if se, ok := ne.Err.(*os.SyscallError); ok { + if strings.Contains(strings.ToLower(se.Error()), "broken pipe") || strings.Contains(strings.ToLower(se.Error()), "connection reset by peer") { + brokenPipe = true + } + } + } + + httpRequest, _ := httputil.DumpRequest(c.Request, false) + headers := strings.Split(string(httpRequest), "\r\n") + for idx, header := range headers { + current := strings.Split(header, ":") + if current[0] == "Authorization" { + headers[idx] = current[0] + ": *" + } + } + + if brokenPipe { + logger.Error("", + zap.Any("error", err), + zap.String("request", string(httpRequest)), + ) + } else if gin.IsDebugging() { + logger.Error("", + zap.Any("error", err), + zap.Any("header", strings.Join(headers, "\r\n")), + zap.String("stack", string(debug.Stack())), + ) + } else { + logger.Error("recovery from panic", + zap.Any("error", err), + zap.String("stack", string(debug.Stack())), + ) + } + + // If the connection is dead, we can't write a status to it. + if brokenPipe { + c.Error(err.(error)) // nolint: errcheck + c.Abort() + } else { + c.AbortWithStatus(http.StatusInternalServerError) + } + } + }() + c.Next() + } +} diff --git a/internal/frontend/middleware/renderer.go b/internal/routers/middleware/view/renderer.go similarity index 96% rename from internal/frontend/middleware/renderer.go rename to internal/routers/middleware/view/renderer.go index db92fab..6760923 100644 --- a/internal/frontend/middleware/renderer.go +++ b/internal/routers/middleware/view/renderer.go @@ -1,4 +1,4 @@ -package middleware +package view import ( "html/template" @@ -11,7 +11,7 @@ import ( // RenderData renderer data type RenderData map[string]interface{} -// Renderer set rederer data +// Renderer gin handlerfunc for set rederer data func Renderer(c *gin.Context) { // init the data renderData := &RenderData{} diff --git a/internal/routers/router.go b/internal/routers/router.go index f76a581..bcceb93 100644 --- a/internal/routers/router.go +++ b/internal/routers/router.go @@ -1,6 +1,7 @@ package routers import ( + "database/sql" "html/template" "net/http" "path/filepath" @@ -15,32 +16,40 @@ import ( "github.com/puti-projects/puti/internal/backend/handler/subject" "github.com/puti-projects/puti/internal/backend/handler/taxonomy" "github.com/puti-projects/puti/internal/backend/handler/user" - apiMiddleware "github.com/puti-projects/puti/internal/backend/middleware" webHandler "github.com/puti-projects/puti/internal/frontend/handler" - webMiddleware "github.com/puti-projects/puti/internal/frontend/middleware" + "github.com/puti-projects/puti/internal/pkg/config" "github.com/puti-projects/puti/internal/pkg/logger" optionCache "github.com/puti-projects/puti/internal/pkg/option" "github.com/puti-projects/puti/internal/pkg/theme" - "github.com/spf13/viper" + "github.com/puti-projects/puti/internal/routers/middleware" + apiMiddleware "github.com/puti-projects/puti/internal/routers/middleware/api" + webMiddleware "github.com/puti-projects/puti/internal/routers/middleware/view" + "github.com/puti-projects/puti/internal/utils" "github.com/gin-gonic/gin" - "github.com/go-sql-driver/mysql" - "github.com/puti-projects/puti/internal/pkg/config" - "github.com/puti-projects/puti/internal/utils" ) -func NewRouter() *gin.Engine { +func NewRouter(runmode string) *gin.Engine { + // Set gin mode before initialize the gin router + if "debug" == runmode { + gin.SetMode(gin.DebugMode) + } else if "test" == runmode { + gin.SetMode(gin.TestMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + // create the gin engine g := gin.New() g = setFuncMap(g) - // r.Use(gin.Logger()) // default logger middleware - g.Use(gin.Recovery()) // default recovery middleware + g.Use(middleware.AccessLogger()) + g.Use(middleware.Recovery()) currentTheme := optionCache.Options.Get("current_theme") - if viper.GetString("runmode") == gin.DebugMode { + if runmode == gin.DebugMode { g.Use(apiMiddleware.Options) } @@ -57,7 +66,7 @@ func setFuncMap(g *gin.Engine) *gin.Engine { "minus": func(a, b int) int { return a - b }, - "formatNullTime": func(time *mysql.NullTime, format string) string { + "formatNullTime": func(time *sql.NullTime, format string) string { return utils.GetFormatNullTime(time, format) }, }) diff --git a/internal/utils/convert.go b/internal/utils/convert.go new file mode 100644 index 0000000..34a20e9 --- /dev/null +++ b/internal/utils/convert.go @@ -0,0 +1,51 @@ +package utils + +// String convert util +// Example: +// intVar, err := utils.StrTo("123").Int() +// intVar := utils.StrTo("123").MustInt() + +import "strconv" + +type StrTo string + +// String convert back to string +func (s StrTo) String() string { + return string(s) +} + +// Int convert string to int +func (s StrTo) Int() (int, error) { + v, err := strconv.Atoi(s.String()) + return v, err +} + +// MustInt convert string to int without error +func (s StrTo) MustInt() int { + v, _ := s.Int() + return v +} + +// Int convert string to uint32 +func (s StrTo) UInt32() (uint32, error) { + v, err := strconv.Atoi(s.String()) + return uint32(v), err +} + +// MustUInt32 convert string to uint32 without error +func (s StrTo) MustUInt32() uint32 { + v, _ := s.UInt32() + return v +} + +// Int convert string to uint32 +func (s StrTo) UInt64() (uint64, error) { + v, err := strconv.Atoi(s.String()) + return uint64(v), err +} + +// MustUInt32 convert string to uint32 without error +func (s StrTo) MustUInt64() uint64 { + v, _ := s.UInt64() + return v +} diff --git a/internal/utils/file.go b/internal/utils/file.go index 24ca121..b23a9bc 100644 --- a/internal/utils/file.go +++ b/internal/utils/file.go @@ -66,3 +66,20 @@ func PathExists(path string) (bool, error) { } return false, err } + +// CheckPathAndCreate check path if exist, fi not exist, make the path dir +func CheckPathAndCreate(path string) error { + coverPathExist, err := PathExists(path) + if err != nil { + return err + } + + if !coverPathExist { + err := os.Mkdir(path, os.ModePerm) + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/utils/pagination.go b/internal/utils/pagination.go index 6fef89e..37d0d5e 100644 --- a/internal/utils/pagination.go +++ b/internal/utils/pagination.go @@ -18,7 +18,7 @@ type Page struct { PageNums []int `json:"pageNums"` } -// Pagination include pagination and page url +// Pagination include page and page url type Pagination struct { Page *Page PageURL string `json:"pageURL"` diff --git a/internal/utils/idGenerator.go b/internal/utils/requestId.go similarity index 72% rename from internal/utils/idGenerator.go rename to internal/utils/requestId.go index 7d1b94a..7f7ff7c 100644 --- a/internal/utils/idGenerator.go +++ b/internal/utils/requestId.go @@ -2,14 +2,8 @@ package utils import ( "github.com/gin-gonic/gin" - "github.com/teris-io/shortid" ) -// GenShortID ... -func GenShortID() (string, error) { - return shortid.Generate() -} - // GetReqID get X-Request-Id header and return the value which is a uuid func GetReqID(c *gin.Context) string { v, ok := c.Get("X-Request-Id") diff --git a/internal/utils/time.go b/internal/utils/time.go index 58a9a1f..bbea72c 100644 --- a/internal/utils/time.go +++ b/internal/utils/time.go @@ -1,32 +1,43 @@ package utils import ( + "database/sql" "time" "github.com/puti-projects/puti/internal/pkg/config" - "github.com/go-sql-driver/mysql" + "gorm.io/gorm" ) // GetFormatTime get format time include nil value func GetFormatTime(t *time.Time, layout string) string { - if t != nil { - formatTime := t.In(config.TimeLoc()).Format(layout) - return formatTime + if t == nil { + return "" + } + + formatedTime := t.In(config.TimeLoc()).Format(layout) + return formatedTime +} + +// GetFormatDeletedAtTime get format time from grom(v2) DeletedAt +func GetFormatDeletedAtTime(d *gorm.DeletedAt, layout string) string { + t, _ := d.Value() + if tt, ok := t.(time.Time); ok { + return GetFormatTime(&tt, layout) } return "" } -// GetFormatNullTime get format time which could be null +// GetFormatNullTime get format time which could be NULL // it returns empty string if the time is NULL -func GetFormatNullTime(t *mysql.NullTime, layout string) string { - if t.Valid { - formatTime := t.Time.In(config.TimeLoc()).Format(layout) - return formatTime +func GetFormatNullTime(t *sql.NullTime, layout string) string { + if !t.Valid { + return "" } - return "" + formatedTime := t.Time.In(config.TimeLoc()).Format(layout) + return formatedTime } // StringToTime changesfer a time string to time.Time @@ -37,18 +48,16 @@ func StringToTime(layout string, timeString string) time.Time { } // StringToNullTime changesfer a time string to mysql.NullTime -func StringToNullTime(layout string, timeString string) mysql.NullTime { - var nullTime mysql.NullTime +func StringToNullTime(layout string, timeString string) *sql.NullTime { + var nullTime sql.NullTime if timeString == "" { nullTime.Valid = false } else { nullTime.Valid = true } - t, _ := time.ParseInLocation(layout, timeString, config.TimeLoc()) - nullTime.Time = t - - return nullTime + nullTime.Time = StringToTime(layout, timeString) + return &nullTime } // SubNullTimeUnitlNowAsDay calculate the diff day until now diff --git a/main.go b/main.go index 53dc2df..271eddc 100644 --- a/main.go +++ b/main.go @@ -8,6 +8,7 @@ import ( "os" "time" + "github.com/puti-projects/puti/internal/dao" "github.com/puti-projects/puti/internal/pkg/config" "github.com/puti-projects/puti/internal/pkg/counter" "github.com/puti-projects/puti/internal/pkg/db" @@ -54,34 +55,30 @@ func init() { logger.InitLogger(config.Server.Runmode) logger.Info("logger construction succeeded") + // init db + err = db.InitDB() + if err != nil { + logger.Panicf("database connection failed. error(%v)", err) + } + // if init db without problem, set up dao engine + dao.NewDaoEngine() + // load theme path theme.LoadInstalled() - - // Set gin mode. - if "debug" == config.Server.Runmode { - gin.SetMode(gin.DebugMode) - } else if "test" == config.Server.Runmode { - gin.SetMode(gin.TestMode) - } else { - gin.SetMode(gin.ReleaseMode) - } } func main() { - // init db - if err := db.InitDB(); err != nil { - logger.Errorf("sql.Open() error(%v)", err) - panic(fmt.Sprintf("database connection failed. err: %v", err)) + // load default options (need db connection) + if err := option.LoadOptions(); err != nil { + logger.Errorf("load options failed, %s", err) } - defer db.DBEngine.Close() - - // load default options - option.LoadOptions() + logger.Info("options has been deployed successfully") // routers - router := routers.NewRouter() + router := routers.NewRouter(config.Server.Runmode) // Ping the server to make sure the router is working. + // should before http server set up go func() { if err := pingServer(); err != nil { logger.Fatal("The router has no response, or it might took too long to start up. Error Detail:" + err.Error()) @@ -92,25 +89,29 @@ func main() { // init ticker counter.InitCountTicker() - // If open https, start listening https request + // listen and serve http + httpServe(router) +} + +// httpServe set up http server +// If https open, should only listen https port +func httpServe(router *gin.Engine) { + // if open https if true == config.Server.HttpsOpen { - cert := config.Server.TlsCert - key := config.Server.TlsKey - if cert != "" && key != "" { - go func() { - logger.Info("start to listening the incoming https requests", zap.String("port", config.Server.HttpsPort)) - logger.Info( - http.ListenAndServeTLS( - "0.0.0.0:"+config.Server.HttpsPort, - cert, - key, - router, - ).Error()) - }() - } else { - logger.Errorf("cert and key can not be empty, failed to listen https port") + if config.Server.TlsCert == "" || config.Server.TlsKey == "" { + logger.Errorf("https opened but cert and key can not be empty, failed to listen https port") } + + logger.Info("start to listening the incoming https requests", zap.String("port", config.Server.HttpsPort)) + logger.Info( + http.ListenAndServeTLS( + "0.0.0.0:"+config.Server.HttpsPort, + config.Server.TlsCert, + config.Server.TlsKey, + router, + ).Error()) } + logger.Info("start to listening the incoming http requests", zap.String("port", config.Server.HttpPort)) logger.Info(http.ListenAndServe( "0.0.0.0:"+config.Server.HttpPort,