package collection import ( "encoding/json" "errors" "fmt" "net/http" "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" "github.com/spf13/viper" "gorm.io/gorm" ) type CollectionV1Router struct { group *gin.RouterGroup mDB *gorm.DB rDB *gorm.DB awsConf util.AWSConfs Env *viper.Viper } type RegisterCollection struct { Name string `json:"name" binding:"required"` Symbol string `json:"symbol" binding:"required"` Description string `json:"description" binding:"required"` Type string `json:"type" binding:"required"` } type ModifyStruct struct { CollectionID uint64 `json:"collection_id"` Description string `json:"description"` } type ReturnCollectionData struct { Collection models.Collection `json:"collection"` CollectionProfile models.CollectionProfile `json:"collection_profile"` TxHash string `json:"tx_hash"` } func NewCollectionV1Router(r common.Router, basePath string) CollectionV1Router { co := CollectionV1Router{ 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"), }, } co.group.POST("", co.registerCollection) co.group.GET(":collection_name", co.getCollectionInfo) co.group.POST("item/:collection_name", co.getItemsInCollection) co.group.POST("activity/:collection_name", co.getActivitiesInCollection) co.group.GET("duplicate/:name", co.duplicateName) co.group.PATCH("", co.updateCollection) return co } // registerCollection godoc // @Summary create collection // @Description 컬렉션 등록, json struct = {name: string, symbol: string, description: string, type: string} // @Schemes // @security ApiKeyAuth // @Tags collection // @Accept multipart/form-data // @Produce json // @Param thumbnailImage formData file true "thumbnail image" // @Param coverImage formData file true "cover image" // @Param json formData common.SwagStruct true "name, symbol, description" // @Success 200 {string} OK // @Router /collection [post] func (co CollectionV1Router) registerCollection(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 := RegisterCollection{} if err := json.Unmarshal([]byte(_request[0]), &request); err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, nil) return } thumbnailImage := form.File["thumbnailImage"] if thumbnailImage == nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } coverImage := form.File["coverImage"] if coverImage == nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } wallet := models.UserWallet{} if err := co.rDB.Where("user_id = ?", userID).Find(&wallet).Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err) return } /* FIXME ethclient 최소 네트워크 수수료 보유 여부를 확인하는 로직 현재 보유중인 이더와, 플랫폼 내에서 사용가능한 이더 가져오기. 사용가능한 이더 값으로 최소 가스비를 확인한다. */ setting := models.Setting{} _err := co.rDB.Find(&setting).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, _err) return } _, availableEth := helpers.GetSelectedBalance(co.rDB, userID, "eth", wallet.Address) if setting.GasDeposit > availableEth { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("you don't have enough points")) return } tx := co.mDB.Begin() defer common.DBTransaction(tx) collection := models.Collection{ ContractAddress: "temp", OwnerAddress: wallet.Address, Network: "eth", Symbol: request.Symbol, IsOfficial: 0, Status: "visible", Type: request.Type, //erc721, erc1155 } if err := tx.Save(&collection).Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err) tx.Rollback() return } var thumbnailURL string thumbnailPath := fmt.Sprintf("collection/thumbnail/%d", collection.ID) for _, f := range thumbnailImage { _thumbnailURL, err := util.UploadToS3(co.awsConf, thumbnailPath, f) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err) tx.Rollback() return } thumbnailURL = _thumbnailURL } var coverURL string coverPath := fmt.Sprintf("collection/cover/%d", collection.ID) for _, f := range coverImage { _coverURL, err := util.UploadToS3(co.awsConf, coverPath, f) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err) tx.Rollback() return } coverURL = _coverURL } profile := models.CollectionProfile{ CollectionID: collection.ID, Name: request.Name, Description: request.Description, ThumbnailImage: thumbnailURL, CoverImage: null.StringFrom(coverURL), } if err := tx.Save(&profile).Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err) tx.Rollback() return } res := ReturnCollectionData{} res.Collection = collection res.CollectionProfile = profile var _collectionAddress string if request.Type == "erc721" { body := helpers.EIP721{ NFTBasic: &helpers.NFTBasic{ Name: request.Name, Symbol: request.Symbol, OwnerAddress: wallet.Address, }, BaseURI: "https://nftinfo.meta-rare.net/", } err, EIP721 := helpers.CreateCollection721(body, wallet.PrivateKey) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err) tx.Rollback() return } res.TxHash = EIP721.CollectionInfo.Tx.Hash().String() _collectionAddress = EIP721.CollectionInfo.CollectionAddress } else if request.Type == "erc1155" { body := helpers.EIP1155{ NFTBasic: &helpers.NFTBasic{ Name: request.Name, Symbol: request.Symbol, OwnerAddress: wallet.Address, }, CustomURI: "https://nftinfo.meta-rare.net/", } err, EIP1155 := helpers.CreateCollection1155(body, wallet.PrivateKey) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err) tx.Rollback() return } res.TxHash = EIP1155.CollectionInfo.Tx.Hash().String() _collectionAddress = EIP1155.CollectionInfo.CollectionAddress } if err := tx.Commit().Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err) tx.Rollback() return } if err := co.mDB.Model(&models.Collection{}).Where("id = ?", collection.ID).Update("contract_address", _collectionAddress).Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, res, nil) } // getCollectionInfo godoc // @Summary get collection info // @Description 컬렉션 페이지에 있는 기본 정보 (탭 데이터는 따로 가져와야 한다.) // @Schemes // @Tags collection // @Accept json // @Produce json // @Param collection_name path string true "collection name" // @Success 200 {object} view.CollectionInfo // @Router /collection/{collection_name} [get] func (co CollectionV1Router) getCollectionInfo(c *gin.Context) { userID, _ := gauth.GetCurrentUserIDToInt64(c) _collectionName := c.Param("collection_name") if _collectionName == "" { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } _id, err := common.ConvertToCollectionId(co.rDB, _collectionName) if _id == 0 || err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } ids := []uint64{_id} response, err := view.GetCollectionInfomation(co.rDB, ids, userID) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err) return } else if len(response) == 0 { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("not found recored")) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response[0], nil) } // getItemsInCollection godoc // @Summary get items in collection // @Description 컬렉션 페이지 아이템 탭 // @Schemes // @Tags collection // @Accept json // @Produce json // @Param collection_name path string true "collection name" // @Param common.Filter body common.Filter true "filter data" // @Success 200 {object} []common.ExpItem // @Router /collection/item/{collection_name} [post] func (co CollectionV1Router) getItemsInCollection(c *gin.Context) { userID, _ := gauth.GetCurrentUserIDToInt64(c) _collectionName := c.Param("collection_name") if _collectionName == "" { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } _id, err := common.ConvertToCollectionId(co.rDB, _collectionName) if _id == 0 || err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } _filter := common.Filter{} if err := c.ShouldBindJSON(&_filter); err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err) return } response := []common.ExpItem{} _err := view.GetExploreItemQuery(co.rDB, _filter, userID).Where("collection.id in (?)", _id).Find(&response).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, err) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil) } // getActivitiesInCollection godoc // @Summary get activities in collection // @Description 컬렉션 페이지 활동 탭 // @Schemes // @Tags collection // @Accept json // @Produce json // @Param collection_name path string true "collection name" // @Success 200 {object} []common.ExpItem // @Router /collection/activity/{collection_name} [post] func (co CollectionV1Router) getActivitiesInCollection(c *gin.Context) { //ANCHOR 수정 필요 컬렉션 조인이 안되어있음 _collectionName := c.Param("collection_name") if _collectionName == "" { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } _id, err := common.ConvertToCollectionId(co.rDB, _collectionName) if _id == 0 || err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } response := []common.ActivityItem{} _err := view.GetActivityItemQuery(co.rDB).Where("log_relation.collection_id = ?", _id).Find(&response).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, err) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil) } // duplicateName godoc // @Summary collection profile name duplicate check. // @Description 컬렉션 이름 중복체크 // @Schemes // @Tags collection // @name get-string-by-int // @Accept json // @Produce json // @Param name path string true "check duplicate name" // @Success 200 {string} OK! // @Router /collection/duplicate/{name} [get] func (co CollectionV1Router) duplicateName(c *gin.Context) { name := c.Param("name") if name == "" { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value")) return } _bool, err := common.DuplicateValue(co.rDB, "collection", name) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err) return } else if _bool { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.DuplicateValue, nil, errors.New("duplicate value")) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, nil, nil) } // updateCollection godoc // @Summary update collection // @Description 컬렉션 수정, json struct = {description: string, collection_id: uin64} // @Schemes // @security ApiKeyAuth // @Tags collection // @Accept multipart/form-data // @Produce json // @Param thumbnailImage formData file true "thumbnail image" // @Param coverImage formData file true "cover image" // @Param json formData common.SwagStruct true "description" // @Success 200 {string} OK // @Router /collection [patch] func (co CollectionV1Router) updateCollection(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 := ModifyStruct{} if err := json.Unmarshal([]byte(_request[0]), &request); err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, nil) return } thumbnailImage := form.File["thumbnailImage"] coverImage := form.File["coverImage"] collection := models.Collection{} _err := co.rDB.Where("id = ?", request.CollectionID).Find(&collection).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err) return } _uw := models.UserWallet{} _err = co.rDB.Where("user_id = ?", userID).Find(&_uw).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err) return } if collection.OwnerAddress != _uw.Address { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("permission not found")) return } collectionProfile := models.CollectionProfile{} _err = co.rDB.Where("collection_id = ?", collection.ID).Find(&collectionProfile).Error if errors.Is(_err, gorm.ErrRecordNotFound) { gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err) return } if thumbnailImage != nil { thumbnailPath := fmt.Sprintf("collection/thumbnail/%d", collection.ID) for _, f := range thumbnailImage { _thumbnailURL, err := util.UploadToS3(co.awsConf, thumbnailPath, f) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err) return } collectionProfile.ThumbnailImage = _thumbnailURL } } if coverImage != nil { coverPath := fmt.Sprintf("collection/cover/%d", collection.ID) for _, f := range coverImage { _coverURL, err := util.UploadToS3(co.awsConf, coverPath, f) if err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err) return } collectionProfile.CoverImage = null.StringFrom(_coverURL) } } if request.Description != "" { collectionProfile.Description = request.Description } if err := co.mDB.Save(&collectionProfile).Error; err != nil { gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err) return } gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, nil, nil) }