collection.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. package collection
  2. import (
  3. "encoding/json"
  4. "errors"
  5. "fmt"
  6. "net/http"
  7. "github.com/gin-gonic/gin"
  8. "github.com/guregu/null"
  9. "github.com/metarare/metarare_api/common"
  10. "github.com/metarare/metarare_api/helpers"
  11. "github.com/metarare/metarare_api/helpers/gauth"
  12. "github.com/metarare/metarare_api/helpers/gerror"
  13. "github.com/metarare/metarare_api/models"
  14. "github.com/metarare/metarare_api/util"
  15. "github.com/metarare/metarare_api/view"
  16. "github.com/spf13/viper"
  17. "gorm.io/gorm"
  18. )
  19. type CollectionV1Router struct {
  20. group *gin.RouterGroup
  21. mDB *gorm.DB
  22. rDB *gorm.DB
  23. awsConf util.AWSConfs
  24. Env *viper.Viper
  25. }
  26. type RegisterCollection struct {
  27. Name string `json:"name" binding:"required"`
  28. Symbol string `json:"symbol" binding:"required"`
  29. Description string `json:"description" binding:"required"`
  30. Type string `json:"type" binding:"required"`
  31. }
  32. type ModifyStruct struct {
  33. CollectionID uint64 `json:"collection_id"`
  34. Description string `json:"description"`
  35. }
  36. type ReturnCollectionData struct {
  37. Collection models.Collection `json:"collection"`
  38. CollectionProfile models.CollectionProfile `json:"collection_profile"`
  39. TxHash string `json:"tx_hash"`
  40. }
  41. func NewCollectionV1Router(r common.Router, basePath string) CollectionV1Router {
  42. co := CollectionV1Router{
  43. group: r.Version.Group(basePath),
  44. mDB: r.Db.MasterDB,
  45. rDB: r.Db.ReadDB,
  46. awsConf: util.AWSConfs{
  47. Region: r.Env.GetString("storage.region"),
  48. BucketName: r.Env.GetString("storage.bucket_name"),
  49. Access_key_id: r.Env.GetString("storage.access_key_id"),
  50. Access_key: r.Env.GetString("storage.access_key"),
  51. },
  52. }
  53. co.group.POST("", co.registerCollection)
  54. co.group.GET(":collection_name", co.getCollectionInfo)
  55. co.group.POST("item/:collection_name", co.getItemsInCollection)
  56. co.group.POST("activity/:collection_name", co.getActivitiesInCollection)
  57. co.group.GET("duplicate/:name", co.duplicateName)
  58. co.group.PATCH("", co.updateCollection)
  59. return co
  60. }
  61. // registerCollection godoc
  62. // @Summary create collection
  63. // @Description 컬렉션 등록, json struct = {name: string, symbol: string, description: string, type: string}
  64. // @Schemes
  65. // @security ApiKeyAuth
  66. // @Tags collection
  67. // @Accept multipart/form-data
  68. // @Produce json
  69. // @Param thumbnailImage formData file true "thumbnail image"
  70. // @Param coverImage formData file true "cover image"
  71. // @Param json formData common.SwagStruct true "name, symbol, description"
  72. // @Success 200 {string} OK
  73. // @Router /collection [post]
  74. func (co CollectionV1Router) registerCollection(c *gin.Context) {
  75. userID, err := gauth.GetCurrentUserIDToInt64(c)
  76. if err != nil || userID == 0 {
  77. gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, err)
  78. return
  79. }
  80. form, err := c.MultipartForm()
  81. if err != nil {
  82. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MultipartError, nil, err)
  83. return
  84. }
  85. _request := form.Value["json"]
  86. if _request == nil {
  87. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  88. return
  89. }
  90. request := RegisterCollection{}
  91. if err := json.Unmarshal([]byte(_request[0]), &request); err != nil {
  92. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, nil)
  93. return
  94. }
  95. thumbnailImage := form.File["thumbnailImage"]
  96. if thumbnailImage == nil {
  97. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  98. return
  99. }
  100. coverImage := form.File["coverImage"]
  101. if coverImage == nil {
  102. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  103. return
  104. }
  105. wallet := models.UserWallet{}
  106. if err := co.rDB.Where("user_id = ?", userID).Find(&wallet).Error; err != nil {
  107. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
  108. return
  109. }
  110. /*
  111. FIXME ethclient 최소 네트워크 수수료 보유 여부를 확인하는 로직
  112. 현재 보유중인 이더와, 플랫폼 내에서 사용가능한 이더 가져오기.
  113. 사용가능한 이더 값으로 최소 가스비를 확인한다.
  114. */
  115. setting := models.Setting{}
  116. _err := co.rDB.Find(&setting).Error
  117. if errors.Is(_err, gorm.ErrRecordNotFound) {
  118. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, _err)
  119. return
  120. }
  121. _, availableEth := helpers.GetSelectedBalance(co.rDB, userID, "eth", wallet.Address)
  122. if setting.GasDeposit > availableEth {
  123. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("you don't have enough points"))
  124. return
  125. }
  126. tx := co.mDB.Begin()
  127. defer common.DBTransaction(tx)
  128. collection := models.Collection{
  129. ContractAddress: "temp",
  130. OwnerAddress: wallet.Address,
  131. Network: "eth",
  132. Symbol: request.Symbol,
  133. IsOfficial: 0,
  134. Status: "visible",
  135. Type: request.Type, //erc721, erc1155
  136. }
  137. if err := tx.Save(&collection).Error; err != nil {
  138. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
  139. tx.Rollback()
  140. return
  141. }
  142. var thumbnailURL string
  143. thumbnailPath := fmt.Sprintf("collection/thumbnail/%d", collection.ID)
  144. for _, f := range thumbnailImage {
  145. _thumbnailURL, err := util.UploadToS3(co.awsConf, thumbnailPath, f)
  146. if err != nil {
  147. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
  148. tx.Rollback()
  149. return
  150. }
  151. thumbnailURL = _thumbnailURL
  152. }
  153. var coverURL string
  154. coverPath := fmt.Sprintf("collection/cover/%d", collection.ID)
  155. for _, f := range coverImage {
  156. _coverURL, err := util.UploadToS3(co.awsConf, coverPath, f)
  157. if err != nil {
  158. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
  159. tx.Rollback()
  160. return
  161. }
  162. coverURL = _coverURL
  163. }
  164. profile := models.CollectionProfile{
  165. CollectionID: collection.ID,
  166. Name: request.Name,
  167. Description: request.Description,
  168. ThumbnailImage: thumbnailURL,
  169. CoverImage: null.StringFrom(coverURL),
  170. }
  171. if err := tx.Save(&profile).Error; err != nil {
  172. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
  173. tx.Rollback()
  174. return
  175. }
  176. res := ReturnCollectionData{}
  177. res.Collection = collection
  178. res.CollectionProfile = profile
  179. var _collectionAddress string
  180. if request.Type == "erc721" {
  181. body := helpers.EIP721{
  182. NFTBasic: &helpers.NFTBasic{
  183. Name: request.Name,
  184. Symbol: request.Symbol,
  185. OwnerAddress: wallet.Address,
  186. },
  187. BaseURI: "https://nftinfo.meta-rare.net/",
  188. }
  189. err, EIP721 := helpers.CreateCollection721(body, wallet.PrivateKey)
  190. if err != nil {
  191. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
  192. tx.Rollback()
  193. return
  194. }
  195. res.TxHash = EIP721.CollectionInfo.Tx.Hash().String()
  196. _collectionAddress = EIP721.CollectionInfo.CollectionAddress
  197. } else if request.Type == "erc1155" {
  198. body := helpers.EIP1155{
  199. NFTBasic: &helpers.NFTBasic{
  200. Name: request.Name,
  201. Symbol: request.Symbol,
  202. OwnerAddress: wallet.Address,
  203. },
  204. CustomURI: "https://nftinfo.meta-rare.net/",
  205. }
  206. err, EIP1155 := helpers.CreateCollection1155(body, wallet.PrivateKey)
  207. if err != nil {
  208. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, err)
  209. tx.Rollback()
  210. return
  211. }
  212. res.TxHash = EIP1155.CollectionInfo.Tx.Hash().String()
  213. _collectionAddress = EIP1155.CollectionInfo.CollectionAddress
  214. }
  215. if err := tx.Commit().Error; err != nil {
  216. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
  217. tx.Rollback()
  218. return
  219. }
  220. if err := co.mDB.Model(&models.Collection{}).Where("id = ?", collection.ID).Update("contract_address", _collectionAddress).Error; err != nil {
  221. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
  222. return
  223. }
  224. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, res, nil)
  225. }
  226. // getCollectionInfo godoc
  227. // @Summary get collection info
  228. // @Description 컬렉션 페이지에 있는 기본 정보 (탭 데이터는 따로 가져와야 한다.)
  229. // @Schemes
  230. // @Tags collection
  231. // @Accept json
  232. // @Produce json
  233. // @Param collection_name path string true "collection name"
  234. // @Success 200 {object} view.CollectionInfo
  235. // @Router /collection/{collection_name} [get]
  236. func (co CollectionV1Router) getCollectionInfo(c *gin.Context) {
  237. userID, _ := gauth.GetCurrentUserIDToInt64(c)
  238. _collectionName := c.Param("collection_name")
  239. if _collectionName == "" {
  240. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  241. return
  242. }
  243. _id, err := common.ConvertToCollectionId(co.rDB, _collectionName)
  244. if _id == 0 || err != nil {
  245. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  246. return
  247. }
  248. ids := []uint64{_id}
  249. response, err := view.GetCollectionInfomation(co.rDB, ids, userID)
  250. if err != nil {
  251. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
  252. return
  253. } else if len(response) == 0 {
  254. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("not found recored"))
  255. return
  256. }
  257. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response[0], nil)
  258. }
  259. // getItemsInCollection godoc
  260. // @Summary get items in collection
  261. // @Description 컬렉션 페이지 아이템 탭
  262. // @Schemes
  263. // @Tags collection
  264. // @Accept json
  265. // @Produce json
  266. // @Param collection_name path string true "collection name"
  267. // @Param common.Filter body common.Filter true "filter data"
  268. // @Success 200 {object} []common.ExpItem
  269. // @Router /collection/item/{collection_name} [post]
  270. func (co CollectionV1Router) getItemsInCollection(c *gin.Context) {
  271. userID, _ := gauth.GetCurrentUserIDToInt64(c)
  272. _collectionName := c.Param("collection_name")
  273. if _collectionName == "" {
  274. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  275. return
  276. }
  277. _id, err := common.ConvertToCollectionId(co.rDB, _collectionName)
  278. if _id == 0 || err != nil {
  279. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  280. return
  281. }
  282. _filter := common.Filter{}
  283. if err := c.ShouldBindJSON(&_filter); err != nil {
  284. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
  285. return
  286. }
  287. response := []common.ExpItem{}
  288. _err := view.GetExploreItemQuery(co.rDB, _filter, userID).Where("collection.id in (?)", _id).Find(&response).Error
  289. if errors.Is(_err, gorm.ErrRecordNotFound) {
  290. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, err)
  291. return
  292. }
  293. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
  294. }
  295. // getActivitiesInCollection godoc
  296. // @Summary get activities in collection
  297. // @Description 컬렉션 페이지 활동 탭
  298. // @Schemes
  299. // @Tags collection
  300. // @Accept json
  301. // @Produce json
  302. // @Param collection_name path string true "collection name"
  303. // @Success 200 {object} []common.ExpItem
  304. // @Router /collection/activity/{collection_name} [post]
  305. func (co CollectionV1Router) getActivitiesInCollection(c *gin.Context) {
  306. //ANCHOR 수정 필요 컬렉션 조인이 안되어있음
  307. _collectionName := c.Param("collection_name")
  308. if _collectionName == "" {
  309. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  310. return
  311. }
  312. _id, err := common.ConvertToCollectionId(co.rDB, _collectionName)
  313. if _id == 0 || err != nil {
  314. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  315. return
  316. }
  317. response := []common.ActivityItem{}
  318. _err := view.GetActivityItemQuery(co.rDB).Where("log_relation.collection_id = ?", _id).Find(&response).Error
  319. if errors.Is(_err, gorm.ErrRecordNotFound) {
  320. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.NotFoundRecord, nil, err)
  321. return
  322. }
  323. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, response, nil)
  324. }
  325. // duplicateName godoc
  326. // @Summary collection profile name duplicate check.
  327. // @Description 컬렉션 이름 중복체크
  328. // @Schemes
  329. // @Tags collection
  330. // @name get-string-by-int
  331. // @Accept json
  332. // @Produce json
  333. // @Param name path string true "check duplicate name"
  334. // @Success 200 {string} OK!
  335. // @Router /collection/duplicate/{name} [get]
  336. func (co CollectionV1Router) duplicateName(c *gin.Context) {
  337. name := c.Param("name")
  338. if name == "" {
  339. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  340. return
  341. }
  342. _bool, err := common.DuplicateValue(co.rDB, "collection", name)
  343. if err != nil {
  344. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, err)
  345. return
  346. } else if _bool {
  347. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.DuplicateValue, nil, errors.New("duplicate value"))
  348. return
  349. }
  350. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, nil, nil)
  351. }
  352. // updateCollection godoc
  353. // @Summary update collection
  354. // @Description 컬렉션 수정, json struct = {description: string, collection_id: uin64}
  355. // @Schemes
  356. // @security ApiKeyAuth
  357. // @Tags collection
  358. // @Accept multipart/form-data
  359. // @Produce json
  360. // @Param thumbnailImage formData file true "thumbnail image"
  361. // @Param coverImage formData file true "cover image"
  362. // @Param json formData common.SwagStruct true "description"
  363. // @Success 200 {string} OK
  364. // @Router /collection [patch]
  365. func (co CollectionV1Router) updateCollection(c *gin.Context) {
  366. userID, err := gauth.GetCurrentUserIDToInt64(c)
  367. if err != nil || userID == 0 {
  368. gerror.IntegratedResponseToRequest(c, http.StatusUnauthorized, gerror.Unauthorized, nil, err)
  369. return
  370. }
  371. form, err := c.MultipartForm()
  372. if err != nil {
  373. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MultipartError, nil, err)
  374. return
  375. }
  376. _request := form.Value["json"]
  377. if _request == nil {
  378. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("invalid parameter value"))
  379. return
  380. }
  381. request := ModifyStruct{}
  382. if err := json.Unmarshal([]byte(_request[0]), &request); err != nil {
  383. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.InvalidParameterValue, nil, nil)
  384. return
  385. }
  386. thumbnailImage := form.File["thumbnailImage"]
  387. coverImage := form.File["coverImage"]
  388. collection := models.Collection{}
  389. _err := co.rDB.Where("id = ?", request.CollectionID).Find(&collection).Error
  390. if errors.Is(_err, gorm.ErrRecordNotFound) {
  391. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err)
  392. return
  393. }
  394. _uw := models.UserWallet{}
  395. _err = co.rDB.Where("user_id = ?", userID).Find(&_uw).Error
  396. if errors.Is(_err, gorm.ErrRecordNotFound) {
  397. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err)
  398. return
  399. }
  400. if collection.OwnerAddress != _uw.Address {
  401. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, errors.New("permission not found"))
  402. return
  403. }
  404. collectionProfile := models.CollectionProfile{}
  405. _err = co.rDB.Where("collection_id = ?", collection.ID).Find(&collectionProfile).Error
  406. if errors.Is(_err, gorm.ErrRecordNotFound) {
  407. gerror.IntegratedResponseToRequest(c, http.StatusBadRequest, gerror.InvalidParameterValue, nil, _err)
  408. return
  409. }
  410. if thumbnailImage != nil {
  411. thumbnailPath := fmt.Sprintf("collection/thumbnail/%d", collection.ID)
  412. for _, f := range thumbnailImage {
  413. _thumbnailURL, err := util.UploadToS3(co.awsConf, thumbnailPath, f)
  414. if err != nil {
  415. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
  416. return
  417. }
  418. collectionProfile.ThumbnailImage = _thumbnailURL
  419. }
  420. }
  421. if coverImage != nil {
  422. coverPath := fmt.Sprintf("collection/cover/%d", collection.ID)
  423. for _, f := range coverImage {
  424. _coverURL, err := util.UploadToS3(co.awsConf, coverPath, f)
  425. if err != nil {
  426. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.Error3rdParty, nil, err)
  427. return
  428. }
  429. collectionProfile.CoverImage = null.StringFrom(_coverURL)
  430. }
  431. }
  432. if request.Description != "" {
  433. collectionProfile.Description = request.Description
  434. }
  435. if err := co.mDB.Save(&collectionProfile).Error; err != nil {
  436. gerror.IntegratedResponseToRequest(c, http.StatusInternalServerError, gerror.MysqlSaveError, nil, err)
  437. return
  438. }
  439. gerror.IntegratedResponseToRequest(c, http.StatusOK, gerror.OK, nil, nil)
  440. }