fix: add role limit

This commit is contained in:
fallen-angle
2022-04-27 22:08:02 +08:00
parent fc347a4140
commit 22cb5ec61f
19 changed files with 274 additions and 77 deletions

View File

@@ -1,11 +1,13 @@
package handler
import (
"github.com/gin-gonic/gin"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/article"
"nCovTrack-Backend/utils"
"strconv"
"github.com/gin-gonic/gin"
)
// SaveArticleHandler save an article
@@ -19,7 +21,13 @@ import (
// @Param Token header string true "token"
func SaveArticleHandler(c *gin.Context) {
jsonMap := bindJson(c)
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if jsonMap == nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return
}
colMap := models.MapJ2c[models.BackArticle](jsonMap, true)
@@ -30,7 +38,7 @@ func SaveArticleHandler(c *gin.Context) {
utils.Succ(c, jsonMap)
}
// GetAllArticlesHandler get all article
// ListPublishedArticlesHandler get all article
// @Tags Article
// @Accept json
// @Produce json
@@ -39,9 +47,28 @@ func SaveArticleHandler(c *gin.Context) {
// @Success 200 {object} utils.GinResponse{data=[]models.BackArticle}
// @Router /article/list [get]
// @Param Token header string false "token"
func GetAllArticlesHandler(c *gin.Context) {
func ListPublishedArticlesHandler(c *gin.Context) {
// TODO: admin need to show more articles
articles := article.ListAllArticles()
articles := article.ListPublishedArticles()
utils.Succ(c, articles)
}
func ListArticlesByUser(c *gin.Context) {
published := c.Param("published")
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
var articles *[]models.ListArtile
if published == "published" {
articles = article.ListPublishedArticlesByUser(claims.ID)
} else if published == "notpublished" {
articles = article.ListNotPublishedArticlesByUser(claims.ID)
} else {
UrlNotFound(c)
return
}
utils.Succ(c, articles)
}
@@ -56,6 +83,11 @@ func GetAllArticlesHandler(c *gin.Context) {
// @Param id path string true "id"
func DeleteArticleHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
claims := utils.ClaimsFromHeader(c)
if claims.Role == global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if err != nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return
@@ -84,7 +116,6 @@ func GetArticleHandler(c *gin.Context) {
return
}
res := article.GetArticleById(id)
//TODO: if not admin, will not show not published article
if res == nil {
DataNotFound(c, nil)
return
@@ -103,6 +134,11 @@ func GetArticleHandler(c *gin.Context) {
// @Param id path string true "id"
func PublishArticleHandler(c *gin.Context) {
id, err := strconv.Atoi(c.Param("id"))
claims := utils.ClaimsFromHeader(c)
if claims.Role == global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
if err != nil {
RequestErr(c, map[string]interface{}{"URI": c.Request.RequestURI})
return

View File

@@ -12,6 +12,8 @@ const (
BAD_REQUEST = "Bad Request"
DATA_NOT_FOUND = "Data not Found"
STATUS_DATA_NOT_FOUND = 210
FORBIDDENT = "FORBIDDENT"
PAGE_NOT_FOUND = "404 page not found"
)
func RequestError(c *gin.Context, code int, data interface{}) {
@@ -31,3 +33,10 @@ func ServerErr(c *gin.Context, msg interface{}) {
func DataNotFound(c *gin.Context, data interface{}) {
utils.Success(c, http.StatusOK, STATUS_DATA_NOT_FOUND, DATA_NOT_FOUND, data)
}
func Forbidden(c *gin.Context) {
utils.Err(c, http.StatusForbidden, http.StatusForbidden, FORBIDDENT)
}
func UrlNotFound(c *gin.Context) {
c.String(http.StatusNotFound, PAGE_NOT_FOUND)
}

View File

@@ -1,11 +1,13 @@
package handler
import (
"github.com/gin-gonic/gin"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/service/user"
"nCovTrack-Backend/utils"
"regexp"
"github.com/gin-gonic/gin"
)
//UserRegisterHandler user register
@@ -39,12 +41,16 @@ func UserRegisterHandler(c *gin.Context) {
// @Param Token header string true "token"
// @Param json body models.UserApprove true "json"
func UserApproveHandler(c *gin.Context) {
//TODO: auth user is admin or not
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
jsonMap := bindJsonStruct[models.UserApprove](c)
if jsonMap == nil {
return
}
if !user.ApproveRegister(jsonMap["email"].(string), jsonMap["pass"].(bool)) {
if !user.ApproveRegister(claims, jsonMap["email"].(string), jsonMap["pass"].(bool)) {
RequestErr(c, "approve failed")
return
}
@@ -79,10 +85,24 @@ func UserLoginHandler(c *gin.Context) {
// @Produce json
// @Summary list register infos, which is to be approved
// @Success 200 {object} utils.GinResponse{}
// @Router /user/registers [get]
// @Router /user/registers/{approved} [get]
// @Param Token header string true "token"
func ListRegisterUserHandler(c *gin.Context) {
registers := user.ListRegister()
approved := c.Param("approved")
claims := utils.ClaimsFromHeader(c)
if claims.Role != global.ROLE_ID_MAP["ADMIN"] {
Forbidden(c)
return
}
var registers *[]map[string]interface{}
if approved == "notapproved" {
registers = user.ListRegister(claims)
} else if approved == "approved" {
registers = user.ListApprovedRegister(claims)
} else {
UrlNotFound(c)
return
}
utils.Succ(c, registers)
}

View File

@@ -1,14 +1,12 @@
package initialize
import (
"nCovTrack-Backend/service/statistics"
"github.com/robfig/cron/v3"
)
func initCron() {
c := cron.New()
//c.AddFunc("@every 10s", func() { global.Redis.Set("OK", time.Now().String(), time.Duration(10*time.Hour)) })
c.AddFunc("@every 10m", statistics.CacheNCov)
//c.AddFunc("@every 10m", statistics.CacheNCov)
c.Start()
}

View File

@@ -1,14 +1,16 @@
package middleware
import (
"fmt"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
"encoding/json"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/utils"
"net/http"
"strconv"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
const UNAUTH_MSG = "unauthorized"
@@ -24,10 +26,15 @@ func Auth() gin.HandlerFunc {
}
// Write the field of token to request header
claims := utils.ParseClaims(oldToken[0])
c.Request.Header.Set("role", fmt.Sprint(claims["role"]))
c.Request.Header.Set("email", claims["email"].(string))
c.Request.Header.Set("id", fmt.Sprint(claims["id"]))
c.Request.Header.Set("role", claims["role"].(string))
tokenClaims := models.TokenClaims{
ID: int(claims["id"].(float64)),
Username: claims["username"].(string),
Email: claims["email"].(string),
Role: int(claims["role"].(float64)),
Region: claims["region"].(string),
}
claimsByte, _ := json.Marshal(tokenClaims)
c.Request.Header.Add("claims", string(claimsByte))
// renew token, and judge the token's iat is expired or not
renewToken := utils.RenewToken(oldToken[0])

View File

@@ -13,7 +13,7 @@ func Cors() gin.HandlerFunc {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Headers", "Content-Type,AccessToken,X-CSRF-Token, Authorization, Token,X-Token,X-User-Id")
c.Header("Access-Control-Allow-Methods", "POST, GET, OPTIONS,DELETE,PUT")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Expose-Headers", "Content-Length, Access-Control-Allow-Origin, X-Token, Access-Control-Allow-Headers, Content-Type")
c.Header("Access-Control-Allow-Credentials", "true")
if method == "OPTIONS" {

View File

@@ -8,9 +8,9 @@ import (
type BackArticle struct {
ID int `gorm:"primaryKey;column:id" json:"-"` // 文章id
CreateTime time.Time `gorm:"column:create_time" json:"createTime"` // 文章新建时间
CreateUser string `gorm:"column:create_user" json:"createUser"` // 文章创建者id
CreateUser int `gorm:"column:create_user" json:"createUser"` // 文章创建者id
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"` // 文章最后更新时间
ModifyUser string `gorm:"column:modify_user" json:"modifyUser"` // 文章最后更新者id
ModifyUser int `gorm:"column:modify_user" json:"modifyUser"` // 文章最后更新者id
Title string `gorm:"column:title" json:"title"` // 文章标题
Tags string `gorm:"column:tags" json:"tags"` // 文章Tag
Resume string `gorm:"column:resume" json:"resume"` // 文章简述
@@ -20,6 +20,17 @@ type BackArticle struct {
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
}
type ListArtile struct {
ID int `json:"-"`
Username string `json:"username"`
CreateTime time.Time `json:"createTime"`
ModifyTime time.Time `json:"modifyTime"`
Title string `json:"title"`
Tags string `json:"tags"`
Resume string `json:"resume"`
Cover string `json:"cover"`
}
func init() {
initJcMap[BackArticle]()
}

View File

@@ -41,3 +41,23 @@ type HotelContactRequest struct {
InData FakerDate `json:"in_data"`
OutData FakerDate `json:"out_data"`
}
type RailwayContactRequest struct {
Name string `json:"name"`
Age int `json:"age,string"`
Sex int `json:"sex,string"`
Phone int `json:"phone"`
Address string `json:"address"`
Train string `json:"train"`
Launch FakerDate `json:"launch"`
Identification string `json:"identification"`
}
type PatientRequest struct {
Name string `json:"name"`
Age int `json:"age,string"`
Sex int `json:"sex,string"`
Phone string `json:"phone"`
Address string `json:"address"`
Identification string `json:"identification"`
}

View File

@@ -3,7 +3,7 @@ package models
import "time"
type BackUser struct {
ID int `gorm:"primaryKey;column:id" json:"-"` // 用户ID
ID int `gorm:"primaryKey;column:id" json:"id"` // 用户ID
Username string `gorm:"column:username" json:"username"` // 用户真实姓名
Password string `gorm:"column:password" json:"password"` // 用户密码
Role int `gorm:"column:role" json:"role"` // 用户角色
@@ -14,6 +14,7 @@ type BackUser struct {
Approver int `gorm:"column:approver" json:"approver"` // 注册审核人ID
ModifyTime time.Time `gorm:"column:modify_time" json:"modifyTime"`
IsDelete int8 `gorm:"column:is_delete" json:"isDelete"` // 删除标志
Region string `gorm:"column:region" json:"region"` // 用户所属地域
}
type UserLogin struct {
@@ -27,6 +28,8 @@ type UserRegister struct {
Email string `json:"email"`
Phone string `json:"phone"`
Aptitude string `json:"aptitude"`
Region string `json:"region"`
Role int `json:"role"`
}
type UserChangePwd struct {
@@ -40,6 +43,14 @@ type UserApprove struct {
Pass bool `json:"pass"`
}
type TokenClaims struct {
ID int `json:"id"`
Username string `json:"username"`
Email string `json:"email"`
Role int `json:"role"`
Region string `json:"region"`
}
func init() {
initJcMap[BackUser]()
}

View File

@@ -93,6 +93,24 @@ func Upsert[T any](colMap map[string]interface{}) (ok bool, rowsAffected int64)
return true, tx.RowsAffected
}
func Update[T any](queryMap []map[string]interface{}, updateMap map[string]interface{}) (ok bool, rowsAffected int64) {
tx := global.Db.Model(new(T))
for _, e := range queryMap {
e[IS_DELETE] = 0
tx = tx.Or(e)
}
return UpdateByOrm(tx, updateMap)
}
func UpdateByOrm(tx *gorm.DB, updateMap map[string]interface{}) (ok bool, rowsAffected int64) {
tx.Updates(updateMap)
if tx.Error != nil {
fmt.Println(tx.Error)
return false, 0
}
return true, tx.RowsAffected
}
// DeleteById will delete by id, not delete the record from database, only set the field "is_delete" as 1
func DeleteById[T any](id int) (ok bool, rowsAffected int64) {
tx := global.Db.Model(new(T)).Where("id = ?", id).Update("is_delete", 1)

View File

@@ -10,6 +10,7 @@ func articlePrivateRouter(router *gin.RouterGroup) {
{
articleRouter.DELETE("/:id", handler.DeleteArticleHandler)
articleRouter.POST("/:id/publish", handler.PublishArticleHandler)
articleRouter.GET("/list/:published", handler.ListArticlesByUser)
}
}
@@ -17,7 +18,7 @@ func articlePublicRouter(router *gin.RouterGroup) {
articleRouter := router.Group("/article")
{
articleRouter.POST("", handler.SaveArticleHandler)
articleRouter.GET("/list", handler.GetAllArticlesHandler)
articleRouter.GET("/list", handler.ListPublishedArticlesHandler)
articleRouter.GET("/:id", handler.GetArticleHandler)
}
}

View File

@@ -19,6 +19,6 @@ func userPrivateRouter(router *gin.RouterGroup) {
userRouter := router.Group("/user")
{
userRouter.POST("/approve", handler.UserApproveHandler)
userRouter.GET("/registers", handler.ListRegisterUserHandler)
userRouter.GET("/registers/:approved", handler.ListRegisterUserHandler)
}
}

View File

@@ -1,26 +1,40 @@
package article
import (
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"strconv"
)
//ListPublishedArticles list the articles published, use to show the articles to all people
func ListPublishedArticles() *[]map[string]interface{} {
article := models.ListField[models.BackArticle]([]map[string]interface{}{{"is_publish": 0}}, true, "content")
if *article == nil {
article = &[]map[string]interface{}{}
}
return article
func ListPublishedArticles() *[]models.ListArtile {
return listArticles(1, 0)
}
//ListAllArticles list all articles, will show the articles not published of the user
// TODO: need only show the user's not published article
func ListAllArticles() *[]map[string]interface{} {
article := models.ListField[models.BackArticle]([]map[string]interface{}{{}}, true, "content")
if *article == nil {
article = &[]map[string]interface{}{}
func ListPublishedArticlesByUser(id int) *[]models.ListArtile {
return listArticles(1, id)
}
return article
//ListAllArticles list all articles(without not published)
// TODO: need only show the user's not published article
func ListNotPublishedArticlesByUser(id int) *[]models.ListArtile {
return listArticles(0, id)
}
func listArticles(isPublish int, createUser int) *[]models.ListArtile {
queryStr := "back_article.is_delete = 0 AND is_publish = " + strconv.Itoa(isPublish)
if createUser != 0 {
queryStr += " AND create_user = " + strconv.Itoa(createUser)
}
var res []models.ListArtile
global.Db.Table("back_article").
Select("back_user.username, back_article.*").
Joins("join back_user on back_article.create_user=back_user.id").
Where(queryStr).Find(&res)
if res == nil {
res = []models.ListArtile{}
}
return &res
}
//SaveArticle save the articles

View File

@@ -2,7 +2,6 @@ package investigate
import (
"encoding/json"
"fmt"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"nCovTrack-Backend/utils"
@@ -16,12 +15,32 @@ func fakerGetRequest(uri string) string {
return string(dataStr)
}
func QueryHotelContacts() {
func QueryHotelContacts() []models.HotelContactRequest {
dataStr := fakerGetRequest("query/contacts/hotel/320581199103182689")
var data []models.HotelContactRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
fmt.Println(data)
return data
}
func QueryRailwayContacts() []models.RailwayContactRequest {
dataStr := fakerGetRequest("query/contacts/railway/320581199103182689")
var data []models.RailwayContactRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
return data
}
func QueryPatients() []models.PatientRequest {
dataStr := fakerGetRequest("query/contacts/railway/320581199103182689")
var data []models.PatientRequest
err := json.Unmarshal([]byte(dataStr), &data)
if err != nil {
panic(err)
}
return data
}

View File

@@ -48,7 +48,10 @@ func cacheNCovStatistics() {
var nCovRes map[string]string
json.Unmarshal([]byte(resp), &nCovRes)
var nCovResData map[string]interface{}
json.Unmarshal([]byte(nCovRes["data"]), &nCovResData)
err := json.Unmarshal([]byte(nCovRes["data"]), &nCovResData)
if err != nil {
panic(err)
}
if !needToRecache(nCovResData) {
return
}

View File

@@ -36,6 +36,7 @@ func GetAllCityData(sort string) []interface{} {
}
func GetCountryData(child bool) []interface{} {
checkCache()
if child {
return getEntireRedisList(rds_COUNTRY_LEVEL_CHILD_KEY)
}
@@ -43,6 +44,7 @@ func GetCountryData(child bool) []interface{} {
}
func GetChinaNCovStatistic() models.ChinaData {
checkCache()
data := models.ChinaData{}
json.Unmarshal([]byte(global.Redis.Get(rds_CHINA_ADD_KEY).Val()), &data.ChinaAdd)
json.Unmarshal([]byte(global.Redis.Get(rds_CHINA_TOTAL_KEY).Val()), &data.ChinaTotal)

View File

@@ -1,7 +1,6 @@
package user
import (
"encoding/json"
"fmt"
"github.com/golang-jwt/jwt/v4"
"github.com/google/uuid"
@@ -18,6 +17,7 @@ const (
// Login if login success, will return token
func Login(user map[string]interface{}) (token string) {
// TODO: need to detect is passed or not
account := user["account"].(string)
var queryMap []map[string]interface{}
if strings.Contains(account, "@") {
@@ -29,64 +29,73 @@ func Login(user map[string]interface{}) (token string) {
if userInfo == nil {
return ""
}
if userInfo["approver"].(int) <= 0 {
return ""
}
if !utils.PasswordCompare(user["password"].(string), userInfo["password"].(string)) {
return ""
}
claims := jwt.MapClaims{
"id": userInfo["id"],
"username": userInfo["username"],
"role": userInfo["role"],
"email": userInfo["email"],
"region": userInfo["region"],
"role": userInfo["role"],
}
return utils.GenerateToken(claims)
}
// Register user register, user can use account after approved
func Register(user map[string]interface{}) {
func Register(user map[string]interface{}) bool {
user["password"] = utils.PasswordEncrypt(user["password"].(string))
userStr, _ := json.Marshal(user)
// insert into redis, wait for approve
cmd := global.Redis.HMSet(global.REGISTER_REDIS_KEY, map[string]interface{}{user["email"].(string): userStr})
if cmd.Err() != nil {
panic(cmd.Err())
user["approver"] = 0
colMap := models.MapJ2c[models.BackUser](user, false)
ok, rowsAffected := models.Upsert[models.BackUser](colMap)
if !ok || rowsAffected == 0 {
return false
}
return true
}
// ListRegister list the registers in the redis to be approved
func ListRegister() *[]map[string]interface{} {
applyStrMap := global.Redis.HGetAll(global.REGISTER_REDIS_KEY).Val()
var applies []map[string]interface{}
for _, v := range applyStrMap {
var apply map[string]interface{}
_ = json.Unmarshal([]byte(v), &apply)
applies = append(applies, apply)
func ListRegister(claims models.TokenClaims) *[]map[string]interface{} {
registers := []map[string]interface{}{}
tx := global.Db.Model(new(models.BackUser)).Omit("password")
if claims.Region == "" {
// do nothing
} else if !strings.Contains(claims.Region, " ") {
tx.Where("approver = 0 AND is_delete = 0 AND region LIKE ? AND role = ?", claims.Region+" %", global.ROLE_ID_MAP["ADMIN"])
registers = *models.ListByOrm(tx)
} else {
tx.Where("approver = 0 AND is_delete = 0 AND region = ? AND role in ?", claims.Region, []int{global.ROLE_ID_MAP["WORKER"], global.ROLE_ID_MAP["VOLUNTEER"]})
registers = *models.ListByOrm(tx)
}
if applies == nil {
applies = []map[string]interface{}{}
return &registers
}
return &applies
// ListApprovedRegister list registers approved by the admin
func ListApprovedRegister(claims models.TokenClaims) *[]map[string]interface{} {
approvedRegisters := []map[string]interface{}{}
tx := global.Db.Model(new(models.BackUser)).Omit("password").Where("approver in ? and is_delete = 0", []int{claims.ID, -claims.ID})
approvedRegisters = *models.ListByOrm(tx)
return &approvedRegisters
}
// ApproveRegister approve a register
func ApproveRegister(email string, pass bool) bool {
if !pass {
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
return rowsAffected != 0
func ApproveRegister(claims models.TokenClaims, email string, pass bool) bool {
queryMap := []map[string]interface{}{{"email": email}}
var approver int
if pass {
approver = claims.ID
} else {
approver = -claims.ID
}
// if pass, will get the register info from redis, and the insert into mysql, this mean user is register success
applyStr := global.Redis.HGet(global.REGISTER_REDIS_KEY, email).Val()
rowsAffected := global.Redis.HDel(global.REGISTER_REDIS_KEY, email).Val()
if rowsAffected == 0 {
updateMap := map[string]interface{}{"approver": approver}
ok, rowsAffected := models.Update[models.BackUser](queryMap, updateMap)
if !ok || rowsAffected == 0 {
return false
}
var apply map[string]interface{}
_ = json.Unmarshal([]byte(applyStr), &apply)
if !NoDuplicatePhoneOrEmail(apply["phone"].(string), apply["email"].(string)) {
return false
}
colMap := models.MapJ2c[models.BackUser](apply, true)
ok, rowsAffected := models.Upsert[models.BackUser](colMap)
return ok && rowsAffected != 0
return true
}
// ChangePassword user change password, or user forgot password

10
utils/json.go Normal file
View File

@@ -0,0 +1,10 @@
package utils
import (
"encoding/json"
)
func Strcut2Map[S any, T *map[string]interface{} | *[]map[string]interface{}](source S, target T) {
jsonByte, _ := json.Marshal(source)
json.Unmarshal(jsonByte, target)
}

View File

@@ -1,10 +1,13 @@
package utils
import (
"encoding/json"
"fmt"
"nCovTrack-Backend/global"
"nCovTrack-Backend/models"
"time"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v4"
)
@@ -81,3 +84,9 @@ func ParseClaims(tokenStr string) jwt.MapClaims {
}
return token.Claims.(jwt.MapClaims)
}
func ClaimsFromHeader(c *gin.Context) models.TokenClaims {
var claims models.TokenClaims
json.Unmarshal([]byte(c.Request.Header.Get("claims")), &claims)
return claims
}