123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610 |
- package token
- import (
- "encoding/hex"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "github.com/guregu/null"
- "github.com/metarare/metarare_api/common"
- "github.com/metarare/metarare_api/helpers"
- "github.com/metarare/metarare_api/helpers/gauth"
- "github.com/metarare/metarare_api/helpers/gerror"
- "github.com/metarare/metarare_api/models"
- "github.com/metarare/metarare_api/util"
- "github.com/metarare/metarare_api/view"
- "gorm.io/gorm"
- )
- type TokenV1Router struct {
- group *gin.RouterGroup
- mDB *gorm.DB
- rDB *gorm.DB
- awsConf util.AWSConfs
- assetsAwsConf util.AWSConfs
- }
- type TokenBaseInfo struct {
- Commission float64 `json:"commission"`
- Collection []CollectionData `json:"collection"`
- }
- type CollectionData struct {
- CollectionID uint64 `json:"collection_id"`
- ThumbnailImage string `json:"thumbnail_image"`
- Name string `json:"name"`
- IsOfficial bool `json:"is_official"`
- }
- type RegisterTokenData struct {
- CollectionID uint64 `json:"collection_id" binding:"required"`
- Name string `json:"name" binding:"required"`
- Description string `json:"description" binding:"required"`
- Type string `json:"type" binding:"required"`
- TotalCount int `json:"total_count" binding:"required"`
- Royalties uint32 `json:"royalties" binding:"required"`
- Sale RegisterSaleData `json:"sale" binding:"required"`
- Traits []Trait `json:"traits"`
- }
- type Trait struct {
- Key string `json:"key"`
- Value string `json:"value"`
- }
- type RegisterSaleData struct {
- SaleType string `json:"sale_type" binding:"required"`
- Price float64 `json:"price" binding:"required"`
- StartPrice float64 `json:"start_price" binding:"required"`
- StartAt time.Time `json:"start_at" binding:"required"`
- EndAt time.Time `json:"end_at" binding:"required"`
- Currency string `json:"currency" binding:"required"`
- }
- type TargetID struct {
- ID uint64 `json:"id"`
- }
- type Like struct {
- TokenID uint64 `json:"token_id" binding:"required"`
- IsLike bool `json:"is_like"`
- }
- func NewTokenV1Router(r common.Router, basePath string) TokenV1Router {
- t := TokenV1Router{
- group: r.Version.Group(basePath),
- mDB: r.Db.MasterDB,
- rDB: r.Db.ReadDB,
- awsConf: util.AWSConfs{
- Region: r.Env.GetString("storage.region"),
- BucketName: r.Env.GetString("storage.bucket_name"),
- Access_key_id: r.Env.GetString("storage.access_key_id"),
- Access_key: r.Env.GetString("storage.access_key"),
- },
- assetsAwsConf: util.AWSConfs{
- Region: r.Env.GetString("storage.region"),
- BucketName: r.Env.GetString("storage.assets_buket_name"),
- Access_key_id: r.Env.GetString("storage.access_key_id"),
- Access_key: r.Env.GetString("storage.access_key"),
- },
- }
- t.group.GET("baseinfo", t.getTokenBaseInfo)
- t.group.PATCH("like", t.updateLikeCount)
- t.group.POST("", t.registerToken)
- t.group.GET("owner", t.getOwnerList)
- t.group.GET("log/:token_id", t.getTokenLog)
- t.group.GET("resaleinfo/:token_id", t.getTokenResaleInfo)
- t.group.GET(":token_id", t.getTokenDetailInfo)
- return t
- }
- // getTokenBaseInfo godoc
- // @Summary get token base info
- // @Description 아이템 생성 시 내가 만든 컬렉션 리스트와, 사이트 커미션 수수료 정보 가져오기
- // @Schemes
- // @security ApiKeyAuth
- // @Tags token
- // @Accept json
- // @Produce json
- // @Param type query string true "erc721, erc1155"
- // @Success 200 {object} TokenBaseInfo
- // @Router /token/baseinfo [get]
- func (t TokenV1Router) getTokenBaseInfo(c *gin.Context) {
- //NOTE x 다이나믹으로 해당 컬렉션의 타입을 받아 분기시켜서 보내준다.
- userID, err := gauth.GetCurrentUserIDToInt64(c)
- if err != nil || userID == 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, err)
- return
- }
- condition := c.Query("type")
- if condition != "erc721" && condition != "erc1155" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- wallet := models.UserWallet{}
- if err := t.rDB.Where("user_id = ?", userID).Find(&wallet).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- return
- }
- response := TokenBaseInfo{}
- if err := t.rDB.Select("commission").Table("setting").Find(&response.Commission).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- return
- }
- if err := t.rDB.Select("collection.id AS collection_id, collection.is_official, collection_profile.thumbnail_image, collection_profile.name").Table("collection").
- Joins("INNER JOIN collection_profile ON collection_profile.collection_id = collection.id").
- Where("(collection.is_official = 1 AND collection.type = ?) OR (collection.type = ? AND collection.owner_address = ?)", condition, condition, wallet.Address).Find(&response.Collection).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- return
- }
- // registerToken godoc
- // @Summary create NFT
- // @Description NFT 생성과 동시에 판매 등록
- // @Schemes
- // @security ApiKeyAuth
- // @Tags token
- // @Accept multipart/form-data
- // @Produce json
- // @Param contentImage formData file true "content image"
- // @Param json formData common.SwagStruct true "object"
- // @Success 200 {string} OK
- // @Router /token [post]
- func (t TokenV1Router) registerToken(c *gin.Context) {
- userID, err := gauth.GetCurrentUserIDToInt64(c)
- if err != nil || userID == 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, err)
- return
- }
- form, err := c.MultipartForm()
- if err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MultipartError, nil, err)
- return
- }
- _request := form.Value["json"]
- if _request == nil {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- request := RegisterTokenData{}
- if err := json.Unmarshal([]byte(_request[0]), &request); err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, nil)
- return
- }
- //validate check
- if request.CollectionID == 0 || request.Name == "" || request.Type == "" || request.TotalCount > 20 ||
- request.Royalties > 50 || request.Sale.SaleType == "" || request.Sale.Currency == "" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- contentImage := form.File["contentImage"]
- if contentImage == nil {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- wallet := models.UserWallet{}
- if err := t.rDB.Where("user_id = ?", userID).Find(&wallet).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidConnection, nil, err)
- return
- }
- collection := models.Collection{}
- _err := t.rDB.Where("id = ?", request.CollectionID).Find(&collection).Error
- if errors.Is(_err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err)
- return
- }
- setting := models.Setting{}
- _err = t.rDB.Find(&setting).Error
- if errors.Is(_err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err)
- return
- }
- tx := t.mDB.Begin()
- defer common.DBTransaction(tx)
- var contentImageURL string
- var assetsImageURL string
- ContentImagePath := fmt.Sprintf("token/content")
- for _, f := range contentImage {
- contentImageURL, err = util.UploadToS3(t.awsConf, ContentImagePath, f)
- if err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
- tx.Rollback()
- return
- }
- // NOTE x assets image data
- imageURL, err := util.UploadToS3(t.assetsAwsConf, "", f)
- if err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
- tx.Rollback()
- return
- }
- str := strings.Split(imageURL, "/")
- assetsImageURL = "https://assets.meta-rare.net/" + str[len(str)-1]
- }
- response := models.Sale{}
- tokenUID := common.GenerateTokenUID(tx, "token_")
- for idx := 1; idx <= int(request.TotalCount); idx++ {
- token := models.Token{
- CollectionID: request.CollectionID,
- Name: request.Name,
- Description: request.Description,
- Royalties: request.Royalties,
- CreatorAddress: wallet.Address,
- OwnerAddress: wallet.Address,
- ContentURL: contentImageURL,
- AssetsImageURL: assetsImageURL,
- Type: request.Type,
- UID: tokenUID,
- }
- if request.Type == "erc1155" {
- token.TotalCount = null.IntFrom(int64(request.TotalCount))
- token.Index = null.IntFrom(int64(idx))
- }
- if err := tx.Save(&token).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- sale := models.Sale{
- TokenID: token.ID,
- SaleType: request.Sale.SaleType,
- UID: common.GenerateSaleUID(tx, "sale_"),
- Currency: request.Sale.Currency,
- Status: "ongoing",
- TreasuryTax: setting.Commission,
- }
- if request.Sale.SaleType == "fixed" {
- sale.Price = null.FloatFrom(request.Sale.Price)
- } else if request.Sale.SaleType == "time" {
- sale.StartPrice = null.FloatFrom(request.Sale.StartPrice)
- sale.StartAt = null.TimeFrom(request.Sale.StartAt)
- sale.EndAt = null.TimeFrom(request.Sale.EndAt)
- }
- if request.Sale.SaleType == "fixed" {
- _v := common.MakeHashData{
- OwnerAddress: wallet.Address,
- CollectionAddress: collection.ContractAddress,
- TokenType: request.Type,
- TokenID: int64(token.ID),
- Currency: request.Sale.Currency,
- Price: request.Sale.Price,
- Index: int64(idx),
- TotalIndex: int64(request.TotalCount),
- CreationTax: int64(request.Royalties * 100),
- PrevTreasuryTax: int64(setting.Commission * 100),
- TreasuryAddress: setting.TreasuryAddress,
- }
- _vv, err := common.MakeSining(tx, _v)
- if err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- _signature, _hashData, err := helpers.Signing(_vv, wallet.PrivateKey)
- if err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- sale.HashData = hex.EncodeToString(_hashData)
- sale.Signature = hex.EncodeToString(_signature)
- }
- if err := tx.Save(&sale).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- for _, item := range request.Traits {
- tr := models.Traits{
- TokenID: token.ID,
- Key: item.Key,
- Value: item.Value,
- }
- if err := tx.Save(&tr).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- }
- if idx == 1 {
- response = sale
- }
- if request.Type == "erc721" {
- break
- }
- }
- if err := tx.Commit().Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- return
- }
- // updateLikeCount godoc
- // @Summary update token like count
- // @Description 토큰 좋아요 기능
- // @Schemes
- // @security ApiKeyAuth
- // @Tags token
- // @Accept json
- // @Produce json
- // @Param Like body Like true "selected target id"
- // @Success 200 {string} OK
- // @Router /token/like [patch]
- func (t TokenV1Router) updateLikeCount(c *gin.Context) {
- userID, err := gauth.GetCurrentUserIDToInt64(c)
- if err != nil || userID == 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, err)
- return
- }
- request := Like{}
- if err := c.ShouldBindJSON(&request); err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- token := models.Token{}
- _err := t.rDB.Where("id = ?", request.TokenID).Find(&token).Error
- if errors.Is(_err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.NotFoundRecord, nil, err)
- return
- }
- userLike := models.UserLike{}
- if err := t.rDB.Where("user_id = ? AND token_id = ?", userID, token.ID).Find(&userLike).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- return
- }
- if (userLike.IsLike == 1 && request.IsLike) || (userLike.IsLike == 0 && !request.IsLike) {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- return
- }
- tx := t.mDB.Begin()
- defer common.DBTransaction(tx)
- if userLike.ID == 0 {
- userLike.UserID = userID
- userLike.TokenID = token.ID
- userLike.IsLike = 1
- if err := tx.Save(&userLike).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- } else {
- if err := tx.Model(&userLike).Update("is_like", request.IsLike).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
- tx.Rollback()
- return
- }
- }
- if request.IsLike {
- token.LikeCount += 1
- } else {
- token.LikeCount -= 1
- }
- if err := tx.Save(&token).Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- if err := tx.Commit().Error; err != nil {
- gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
- tx.Rollback()
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, nil, nil)
- return
- }
- // getTokenLog godoc
- // @Summary get token log
- // @Description NFT 판매 페이지 기록 탭
- // @Schemes
- // @Tags token
- // @Accept json
- // @Produce json
- // @Success 200 {object} common.ActivityItem
- // @Param token_id path string true "token id"
- // @Router /token/log/{token_id} [get]
- func (t TokenV1Router) getTokenLog(c *gin.Context) {
- tokenID := c.Param("token_id")
- if tokenID == "" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- condition := [...]string{"purchase", "create", "sell"}
- response := []common.ActivityItem{}
- err := view.GetActivityItemQuery(t.rDB).Where("log_relation.token_id = ? AND log.type in (?)", tokenID, condition).Find(&response).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- return
- }
- // getOwnerList godoc
- // @Summary get owner list
- // @Description NFT 1155타입의 소유자 탭
- // @Schemes
- // @Tags token
- // @Accept json
- // @Produce json
- // @Success 200 {object} view.TokenOwnerList
- // @Param token_uid query string true "type"
- // @Router /token/owner [get]
- func (t TokenV1Router) getOwnerList(c *gin.Context) {
- tokenUID := c.Query("token_uid")
- if tokenUID == "" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- response := []view.TokenOwnerList{}
- err := view.GetOwnerList(t.rDB).Where("token.uid = ?", tokenUID).Order("token.id desc").Find(&response).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- return
- }
- // getTokenResaleInfo godoc
- // @Summary get resale data
- // @Description 토큰 재판매 시 설정해야하는 기본 데이터
- // @Schemes
- // @security ApiKeyAuth
- // @Tags token
- // @Accept json
- // @Produce json
- // @Success 200 {object} view.TokenResaleData
- // @Param token_id path string true "token id"
- // @Router /token/resaleinfo/{token_id} [get]
- func (t TokenV1Router) getTokenResaleInfo(c *gin.Context) {
- userID, _err := gauth.GetCurrentUserIDToInt64(c)
- if _err != nil || userID == 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, _err)
- return
- }
- tokenID := c.Param("token_id")
- if tokenID == "" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- //NOTE x 토큰의 오너인지, 세일이 없는 아이인지 확인하기
- _t := models.Token{}
- err := t.rDB.Where("id = ?", tokenID).Find(&_t).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- _uw := models.UserWallet{}
- err = t.rDB.Where("user_id = ?", userID).Find(&_uw).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- if _t.OwnerAddress != _uw.Address {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- _s := []models.Sale{}
- t.rDB.Where("token_id = ? AND status = 'ongoing'", tokenID).Find(&_s)
- if len(_s) > 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- response := view.TokenResaleData{}
- err = view.GetResaleInfo(t.rDB).Where("token.id = ?", tokenID).Find(&response.Token).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- _setting := models.Setting{}
- err = t.rDB.Find(&_setting).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusNotFound, gerror.NotFoundRecord, nil, err)
- return
- }
- response.Token.Commission = _setting.Commission
- t.rDB.Where("token_id = ?", tokenID).Find(&response.Traits)
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- return
- }
- // getTokenDetailInfo godoc
- // @Summary get token detail
- // @Description 토큰 상세페이지
- // @Schemes
- // @Tags token
- // @Accept json
- // @Produce json
- // @Success 200 {object} view.TokenDetailData
- // @Param token_id path string true "token id"
- // @Router /token/{token_id} [get]
- func (t TokenV1Router) getTokenDetailInfo(c *gin.Context) {
- userID, _ := gauth.GetCurrentUserIDToInt64(c)
- tokenID := c.Param("token_id")
- if tokenID == "" {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
- return
- }
- _s := []models.Sale{}
- err := t.rDB.Where("token_id = ? AND status = 'ongoing'", tokenID).Find(&_s).Error
- if len(_s) > 0 {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
- return
- }
- response := view.TokenDetailData{}
- err = view.GetTokenDetail(t.rDB, userID).Where("token.id = ?", tokenID).Find(&response.Token).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.NotFoundRecord, nil, err)
- return
- }
- err = t.rDB.Where("token_id = ?", tokenID).Find(&response.Traits).Error
- if errors.Is(err, gorm.ErrRecordNotFound) {
- gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.NotFoundRecord, nil, err)
- return
- }
- gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
- }
|