Skip to content

Commit

Permalink
fix giftcode concurrency issue and add realname info api
Browse files Browse the repository at this point in the history
  • Loading branch information
HUAHUAI23 committed Sep 23, 2024
1 parent 932cd71 commit b1d6d8d
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 5 deletions.
29 changes: 29 additions & 0 deletions controllers/pkg/database/cockroach/accountv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ package cockroach
import (
"errors"
"fmt"
"gorm.io/gorm/clause"
"log"
"os"
"path/filepath"
Expand Down Expand Up @@ -1069,6 +1070,17 @@ func (c *Cockroach) GetGiftCodeWithCode(code string) (*types.GiftCode, error) {

func (c *Cockroach) UseGiftCode(giftCode *types.GiftCode, userID string) error {
return c.DB.Transaction(func(tx *gorm.DB) error {
var lockedGiftCode types.GiftCode
// Lock the gift code record for update
if err := tx.Clauses(clause.Locking{Strength: "UPDATE", Options: "NOWAIT"}).
Where(&types.GiftCode{ID: giftCode.ID}).First(&lockedGiftCode).Error; err != nil {
return fmt.Errorf("failed to lock gift code: %w", err)
}

if lockedGiftCode.Used {
return fmt.Errorf("gift code has already been used")
}

ops := &types.UserQueryOpts{ID: userID}
// Update the user's balance
if err := c.updateBalance(tx, ops, giftCode.CreditAmount, false, true); err != nil {
Expand Down Expand Up @@ -1103,3 +1115,20 @@ func (c *Cockroach) UseGiftCode(giftCode *types.GiftCode, userID string) error {
return nil
})
}

func (c *Cockroach) GetUserRealNameInfoByUserID(userID string) (*types.UserRealNameInfo, error) {
// get user info
ops := &types.UserQueryOpts{ID: userID}
user, err := c.GetUserCr(ops)

if err != nil {
return nil, fmt.Errorf("failed to get user: %v", err)
}

// get user realname info
var userRealNameInfo types.UserRealNameInfo
if err := c.DB.Where(&types.UserRealNameInfo{UserUID: user.UserUID}).First(&userRealNameInfo).Error; err != nil {
return nil, fmt.Errorf("failed to get user real name info: %w", err)
}
return &userRealNameInfo, nil
}
21 changes: 19 additions & 2 deletions controllers/pkg/types/global.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
package types

import (
"time"

"encoding/json"
"github.com/google/uuid"
"time"
)

type Account struct {
Expand Down Expand Up @@ -320,3 +320,20 @@ type AccountTransaction struct {
func (AccountTransaction) TableName() string {
return "AccountTransaction"
}

type UserRealNameInfo struct {
ID uuid.UUID `gorm:"column:id;type:uuid;default:gen_random_uuid();primary_key"`
UserUID uuid.UUID `gorm:"column:userUid;type:uuid;unique"`
RealName *string `gorm:"column:realName;type:text"`
IDCard *string `gorm:"column:idCard;type:text"`
Phone *string `gorm:"column:phone;type:text"`
IsVerified bool `gorm:"column:isVerified;type:boolean;default:false"`
IDVerifyFailedTimes int `gorm:"column:idVerifyFailedTimes;type:integer;default:0"`
CreatedAt time.Time `gorm:"column:createdAt;type:timestamp(3) with time zone;default:current_timestamp()"`
UpdatedAt time.Time `gorm:"column:updatedAt;type:timestamp(3) with time zone;autoUpdateTime"`
AdditionalInfo json.RawMessage `gorm:"column:additionalInfo;type:jsonb"`
}

func (UserRealNameInfo) TableName() string {
return "UserRealNameInfo"
}
52 changes: 52 additions & 0 deletions service/account/api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -993,6 +993,58 @@ func UserUsage(c *gin.Context) {
})
}

// GetUserRealNameInfo
// @Summary Get user real name information
// @Description Retrieve the real name information for a user
// @Tags GetUserRealNameInfo
// @Accept json
// @Produce json
// @Param request body helper.GetRealNameInfoReq true "Get real name info request"
// @Success 200 {object} helper.GetRealNameInfoResp "Successfully retrieved user real name info"
// @Failure 400 {object} helper.ErrorMessage "Failed to parse get real name info request"
// @Failure 401 {object} helper.ErrorMessage "Authentication error"
// @Failure 500 {object} helper.ErrorMessage "Failed to get user real name info or info not found/verified"
// @Router /account/v1alpha1/real-name-info [post]
func GetUserRealNameInfo(c *gin.Context) {
// Parse the get real name info request
req, err := helper.ParseGetRealNameInfoReq(c)
if err != nil {
c.JSON(http.StatusBadRequest, helper.ErrorMessage{Error: fmt.Sprintf("failed to parse get real name info request: %v", err)})
return
}

if err := authenticateRequest(c, req); err != nil {
c.JSON(http.StatusUnauthorized, helper.ErrorMessage{Error: fmt.Sprintf("authenticate error : %v", err)})
return
}

userRealNameInfo, err := dao.DBClient.GetUserRealNameInfo(req)

if err != nil {
c.JSON(http.StatusInternalServerError, helper.ErrorMessage{Error: fmt.Sprintf("failed to get user real name info: %v", err)})
return
}

if userRealNameInfo == nil {
c.JSON(http.StatusInternalServerError, helper.ErrorMessage{Error: "user real name info not found"})
return
}

if !userRealNameInfo.IsVerified {
c.JSON(http.StatusInternalServerError, helper.ErrorMessage{Error: "user real name info is not verified"})
return
}

// Return success response
c.JSON(http.StatusOK, helper.GetRealNameInfoResp{
Data: helper.GetRealNameInfoRespData{
UserID: req.Auth.UserID,
IsRealName: userRealNameInfo.IsVerified,
},
Message: "successfully get user real name info",
})
}

func CheckAuthAndCalibrate(auth *helper.Auth) (err error) {
if auth == nil {
return helper.ErrNullAuth
Expand Down
12 changes: 12 additions & 0 deletions service/account/dao/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ type Interface interface {
GetRegions() ([]types.Region, error)
GetLocalRegion() types.Region
UseGiftCode(req *helper.UseGiftCodeReq) (*types.GiftCode, error)
GetUserRealNameInfo(req *helper.GetRealNameInfoReq) (*types.UserRealNameInfo, error)
}

type Account struct {
Expand Down Expand Up @@ -1434,3 +1435,14 @@ func (m *Account) UseGiftCode(req *helper.UseGiftCodeReq) (*types.GiftCode, erro

return giftCode, nil
}

func (m *Account) GetUserRealNameInfo(req *helper.GetRealNameInfoReq) (*types.UserRealNameInfo, error) {
// get user info
userRealNameInfo, err := m.ck.GetUserRealNameInfoByUserID(req.UserID)

if err != nil {
return nil, fmt.Errorf("failed to get user real name info: %v", err)
}

return userRealNameInfo, nil
}
26 changes: 24 additions & 2 deletions service/account/dao/interface_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -608,10 +608,10 @@ func TestAccount_UseGiftCode(t *testing.T) {
}

giftcode, err := db.UseGiftCode(&helper.UseGiftCodeReq{
Code: "DfxAffaeEf",
Code: "d-intl-TXERVADC3ASAGQJSx",
AuthBase: helper.AuthBase{
Auth: &helper.Auth{
Owner: "E1xAJ0fy4k",
UserID: "E1xAJ0fy4k",
},
},
})
Expand All @@ -623,6 +623,28 @@ func TestAccount_UseGiftCode(t *testing.T) {
t.Logf("giftcode = %+v", giftcode)
}

func TestAccount_GetUserRealNameInfo(t *testing.T) {
db, err := newAccountForTest("", os.Getenv("GLOBAL_COCKROACH_URI"), os.Getenv("LOCAL_COCKROACH_URI"))
if err != nil {
t.Fatalf("NewAccountInterface() error = %v", err)
return
}

userRealNameInfo, err := db.GetUserRealNameInfo(&helper.GetRealNameInfoReq{
AuthBase: helper.AuthBase{
Auth: &helper.Auth{
UserID: "E1xAJ0fy4k",
},
},
})

if err != nil {
t.Fatalf("GetUserRealNameInfo() error = %v", err)
return
}
t.Logf("userRealNameInfo = %+v", userRealNameInfo)
}

func init() {
// set env
os.Setenv("MONGO_URI", "")
Expand Down
1 change: 1 addition & 0 deletions service/account/helper/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ const (
GetInvoicePayment = "/invoice/get-payment"
UseGiftCode = "/gift-code/use"
UserUsage = "/user-usage"
GetUserRealNameInfo = "/real-name-info"
)

// env
Expand Down
26 changes: 26 additions & 0 deletions service/account/helper/request.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,13 @@ type UseGiftCodeReq struct {
AuthBase `json:",inline" bson:",inline"`
}

type GetRealNameInfoReq struct {
// @Summary Authentication information
// @Description Authentication information
// @JSONSchema required
AuthBase `json:",inline" bson:",inline"`
}

type UserUsageReq struct {
// @Summary Start and end time for the request
// @Description Start and end time for the request
Expand Down Expand Up @@ -551,6 +558,25 @@ func ParseUseGiftCodeReq(c *gin.Context) (*UseGiftCodeReq, error) {
return useGiftCode, nil
}

type GetRealNameInfoRespData struct {
UserID string `json:"userID" bson:"userID" example:"user-123"`
IsRealName bool `json:"isRealName" bson:"isRealName" example:"true"`
}

type GetRealNameInfoResp struct {
Data GetRealNameInfoRespData `json:"data,omitempty" bson:"data,omitempty"`
Message string `json:"message,omitempty" bson:"message" example:"Successfully retrieved real name information"`
}

func ParseGetRealNameInfoReq(c *gin.Context) (*GetRealNameInfoReq, error) {
getRealNameInfoReq := &GetRealNameInfoReq{}
if err := c.ShouldBindJSON(getRealNameInfoReq); err != nil {
return nil, fmt.Errorf("bind json error: %v", err)
}

return getRealNameInfoReq, nil
}

func ParseUserUsageReq(c *gin.Context) (*UserUsageReq, error) {
userUsage := &UserUsageReq{}
if err := c.ShouldBindJSON(userUsage); err != nil {
Expand Down
3 changes: 2 additions & 1 deletion service/account/router/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ func RegisterPayRouter() {
POST(helper.SetStatusInvoice, api.SetStatusInvoice).
POST(helper.GetInvoicePayment, api.GetInvoicePayment).
POST(helper.UseGiftCode, api.UseGiftCode).
POST(helper.UserUsage, api.UserUsage)
POST(helper.UserUsage, api.UserUsage).
POST(helper.GetUserRealNameInfo, api.GetUserRealNameInfo)
docs.SwaggerInfo.Host = env.GetEnvWithDefault("SWAGGER_HOST", "localhost:2333")
router.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))

Expand Down

0 comments on commit b1d6d8d

Please sign in to comment.