package versions_handler import ( "errors" "gorm.io/gorm" "log" "net/http" "strconv" "autolibrary.top/webserver/backend/database/versions_database" "autolibrary.top/webserver/backend/models/versions_model" "autolibrary.top/webserver/utils" "github.com/gin-gonic/gin" ) /* These handlers are used to manage download versions. */ // GetLatestDownload returns the newest published stable release, or null. func GetLatestDownload(c *gin.Context) { var version versions_model.DownloadVersion err := versions_database.DB.Where("type = ? AND release_date <= ?", "stable", utils.NowInShanghai()).Order("release_date DESC, id DESC").First(&version).Error if errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "msg": "No stable release available", "data": nil}) return } if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"code": http.StatusInternalServerError, "msg": "Unable to load latest release", "data": nil}) return } c.JSON(http.StatusOK, gin.H{"code": http.StatusOK, "msg": "Latest stable release", "data": version}) } // GET /download/versions?page={number}&per_page={number<=50} func GetDownloadVersions(c *gin.Context) { // Parse page and perPage parameters pageStr := c.Query("page") perPageStr := c.Query("per_page") // Default values for page and perPage if pageStr == "" { pageStr = "1" } if perPageStr == "" { perPageStr = "9" } page, err := strconv.Atoi(pageStr) if err != nil { log.Printf("[E] GET > try to get downloads, but got invalid parameter: 'page=%s'", pageStr) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "GET > try to get downloads, but got invalid parameter: 'page'", "data": nil, }) return } perPage, err := strconv.Atoi(perPageStr) if err != nil { log.Printf("[E] GET > try to get downloads (page=%d), but got invalid parameter: 'per_page=%s'", page, perPageStr) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "GET > try to get downloads, but got invalid parameter: 'per_page'", "data": nil, }) return } // constrain page and perPage parameters if page <= 0 { page = 1 } if perPage <= 0 || perPage > 50 { perPage = 9 } // Calculate offset offset := (page - 1) * perPage var downloads []versions_model.DownloadVersion result := versions_database.DB.Order("id DESC").Limit(perPage).Offset(offset).Find(&downloads) if result.Error != nil { log.Printf("[E] GET > occur an error when get downloads (page=%d, per_page=%d): %v", page, perPage, result.Error) c.JSON(http.StatusInternalServerError, gin.H{ "code": http.StatusInternalServerError, "msg": "GET > occur an error when get downloads", "data": nil, }) return } var total int64 result = versions_database.DB.Model(&versions_model.DownloadVersion{}).Count(&total) if result.Error != nil { log.Printf("[E] GET > occur an error when get downloads (page=%d, per_page=%d): %v", page, perPage, result.Error) c.JSON(http.StatusInternalServerError, gin.H{ "code": http.StatusInternalServerError, "msg": "GET > occur an error when get downloads", "data": nil, }) return } totalPages := int(total) / perPage if int(total)%perPage != 0 { totalPages++ } pageMeta := gin.H{ "page": page, "per_page": perPage, "total": total, "total_pages": totalPages, "has_next": page < totalPages, } log.Printf("[I] GET > get downloads (page=%d, per_page=%d) successful", page, perPage) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "GET > get downloads successful", "data": gin.H{ "downloads": downloads, "meta": pageMeta, }, }) } // GET /download/versions/:id func GetDownloadVersionByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { log.Printf("[E] GET > try to get download, but got invalid id: '%s'", c.Param("id")) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "GET > try to get download, but got invalid id", "data": nil, }) return } var download versions_model.DownloadVersion result := versions_database.DB.First(&download, id) if result.Error != nil { log.Printf("[E] GET > download (id=%d) can not found: %v", id, result.Error) c.JSON(http.StatusNotFound, gin.H{ "code": http.StatusNotFound, "msg": "GET > download can not found", "data": nil, }) return } log.Printf("[I] GET > get download (id=%d) successful", id) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "GET > get download successful", "data": download, }) } // POST /download/versions func PostDownloadVersion(c *gin.Context) { var download versions_model.DownloadVersion if err := c.ShouldBindJSON(&download); err != nil { log.Printf("[E] POST > try to create download, but got invalid request data: %v", err) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "POST > try to create download, but got invalid request data", "data": nil, }) return } download.CreatedAt = utils.NowInShanghai() result := versions_database.DB.Create(&download) if result.Error != nil { log.Printf("[E] POST > occur an error when create download: %v", result.Error) c.JSON(http.StatusInternalServerError, gin.H{ "code": http.StatusInternalServerError, "msg": "POST > occur an error when create download", "data": nil, }) return } id := strconv.FormatUint(uint64(download.ID), 10) log.Printf("[I] POST > create download (id=%s) successful", id) c.JSON(http.StatusCreated, gin.H{ "code": http.StatusCreated, "msg": "POST > create download successful", "data": download, }) } // PUT /download/versions/:id func PutDownloadVersionByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { log.Printf("[E] PUT > try to update download, but got invalid id: '%s'", c.Param("id")) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "PUT > try to update download, but got invalid id", "data": nil, }) return } var updateDownload versions_model.DownloadVersion if err = c.ShouldBindJSON(&updateDownload); err != nil { log.Printf("[E] PUT > try to update download (id=%d), but got invalid request data: %v", id, err) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "PUT > try to update download, but got invalid request data", "data": nil, }) return } var existingDownload versions_model.DownloadVersion result := versions_database.DB.First(&existingDownload, id) if result.Error != nil { log.Printf("[E] PUT > download (id=%d) can not found: %v", id, result.Error) c.JSON(http.StatusNotFound, gin.H{ "code": http.StatusNotFound, "msg": "PUT > download can not found", "data": nil, }) return } result = versions_database.DB.Model(&versions_model.DownloadVersion{}).Where("id = ?", id).Updates(updateDownload) if result.Error != nil { log.Printf("[E] PUT > occur an error when update download (id=%d): %v", id, result.Error) c.JSON(http.StatusInternalServerError, gin.H{ "code": http.StatusInternalServerError, "msg": "PUT > occur an error when update download", "data": nil, }) return } // post check after update var updatedDownload versions_model.DownloadVersion if result = versions_database.DB.First(&updatedDownload, id); result.Error == nil { log.Printf("[I] PUT > update download (id=%d) successful", id) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "PUT > update download successful", "data": updatedDownload, }) } else { log.Printf("[W] PUT > update successful but failed to retrieve updated download (id=%d): %v", id, result.Error) c.JSON(http.StatusAccepted, gin.H{ "code": http.StatusAccepted, "msg": "PUT > update successful but failed to retrieve updated download", "data": nil, }) } } // DELETE /download/versions/:id func DeleteDownloadVersionByID(c *gin.Context) { id, err := strconv.Atoi(c.Param("id")) if err != nil { log.Printf("[E] DELETE > try to delete download, but got invalid id: %s", c.Param("id")) c.JSON(http.StatusBadRequest, gin.H{ "code": http.StatusBadRequest, "msg": "DELETE > try to delete download, but got invalid id", "data": nil, }) return } result := versions_database.DB.Delete(&versions_model.DownloadVersion{}, id) if result.Error != nil { log.Printf("[E] DELETE > occur an error when delete download (id=%d): %v", id, result.Error) c.JSON(http.StatusInternalServerError, gin.H{ "code": http.StatusInternalServerError, "msg": "DELETE > occur an error when delete download", "data": nil, }) return } if result.RowsAffected == 0 { log.Printf("[E] DELETE > try to delete download (id=%d), but RowsAffected is 0", id) c.JSON(http.StatusNotFound, gin.H{ "code": http.StatusNotFound, "msg": "DELETE > delete download has no effect", "data": nil, }) return } log.Printf("[I] DELETE > delete download (id=%d) successful", id) c.JSON(http.StatusOK, gin.H{ "code": http.StatusOK, "msg": "DELETE > delete download successful", "data": nil, }) }