172 lines
4.5 KiB
Go
172 lines
4.5 KiB
Go
package controllers
|
|
|
|
import (
|
|
"strconv"
|
|
|
|
"gitea.tbdevent.eu/TBD/reforger_crawler_main/initializers"
|
|
"gitea.tbdevent.eu/TBD/reforger_crawler_main/models"
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
"gorm.io/gorm/clause"
|
|
)
|
|
|
|
// SetAddonDependencies replaces all outgoing dependency edges for an addon.
|
|
// An empty dependency list is valid and clears the edges.
|
|
func SetAddonDependencies(c *gin.Context) {
|
|
var report models.DependencyReport
|
|
if err := c.ShouldBindJSON(&report); err != nil {
|
|
c.JSON(400, gin.H{"error": "Invalid JSON"})
|
|
return
|
|
}
|
|
|
|
if report.AddonID == "" {
|
|
c.JSON(400, gin.H{"error": "addonId is required"})
|
|
return
|
|
}
|
|
|
|
seen := make(map[string]bool)
|
|
edges := make([]models.AddonDependency, 0, len(report.Dependencies))
|
|
for _, dep := range report.Dependencies {
|
|
if dep.ID == "" || dep.ID == report.AddonID || seen[dep.ID] {
|
|
continue
|
|
}
|
|
seen[dep.ID] = true
|
|
edges = append(edges, models.AddonDependency{
|
|
AddonID: report.AddonID,
|
|
DependencyID: dep.ID,
|
|
Version: dep.Version,
|
|
DependencyName: dep.Name,
|
|
})
|
|
}
|
|
|
|
err := initializers.DB.Transaction(func(tx *gorm.DB) error {
|
|
if err := tx.Where("addon_id = ?", report.AddonID).Delete(&models.AddonDependency{}).Error; err != nil {
|
|
return err
|
|
}
|
|
if len(edges) == 0 {
|
|
return nil
|
|
}
|
|
return tx.Clauses(clause.OnConflict{DoNothing: true}).CreateInBatches(&edges, 100).Error
|
|
})
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"status": "success", "stored": len(edges)})
|
|
}
|
|
|
|
// GetDependents returns the addons that depend on the given addon.
|
|
func GetDependents(c *gin.Context) {
|
|
addonID := c.Param("id")
|
|
|
|
var addon models.Addon
|
|
if err := initializers.DB.Where("id = ?", addonID).First(&addon).Error; err != nil {
|
|
c.JSON(404, gin.H{"error": "Addon not found"})
|
|
return
|
|
}
|
|
|
|
limit := parseLimit(c.Query("limit"))
|
|
offset := parseOffset(c.Query("offset"))
|
|
|
|
dependents, total, err := queryRelatedAddons(addonID, "ad.addon_id", "ad.dependency_id", limit, offset)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, models.DependentsResponse{
|
|
AddonID: addonID,
|
|
Dependents: dependents,
|
|
Total: total,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
})
|
|
}
|
|
|
|
// GetDependencies returns the addons that the given addon depends on.
|
|
func GetDependencies(c *gin.Context) {
|
|
addonID := c.Param("id")
|
|
|
|
var addon models.Addon
|
|
if err := initializers.DB.Where("id = ?", addonID).First(&addon).Error; err != nil {
|
|
c.JSON(404, gin.H{"error": "Addon not found"})
|
|
return
|
|
}
|
|
|
|
limit := parseLimit(c.Query("limit"))
|
|
offset := parseOffset(c.Query("offset"))
|
|
|
|
dependencies, total, err := queryRelatedAddons(addonID, "ad.dependency_id", "ad.addon_id", limit, offset)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, models.DependenciesResponse{
|
|
AddonID: addonID,
|
|
Dependencies: dependencies,
|
|
Total: total,
|
|
Limit: limit,
|
|
Offset: offset,
|
|
})
|
|
}
|
|
|
|
// queryRelatedAddons returns one page of addons linked to addonID through the
|
|
// addon_dependencies edge table, plus the total number of matches.
|
|
//
|
|
// joinCol is the edge column joined to addons.id; filterCol is the edge column
|
|
// matched against addonID. Both are caller-supplied constants, never user input.
|
|
//
|
|
// The addons.id tiebreaker in the ORDER BY is required: subscriber_count alone
|
|
// is not a unique ordering, so without it paging could repeat or skip rows.
|
|
func queryRelatedAddons(addonID, joinCol, filterCol string, limit, offset int) ([]models.AddonSearchResult, int, error) {
|
|
base := func() *gorm.DB {
|
|
return initializers.DB.Model(&models.Addon{}).
|
|
Joins("JOIN addon_dependencies ad ON "+joinCol+" = addons.id").
|
|
Where(filterCol+" = ?", addonID)
|
|
}
|
|
|
|
var total int64
|
|
if err := base().Count(&total).Error; err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
results := []models.AddonSearchResult{}
|
|
err := base().
|
|
Select("addons.id, addons.name, addons.type, addons.summary, addons.preview, addons.subscriber_count, addons.current_version_number, addons.author").
|
|
Order("addons.subscriber_count DESC, addons.id ASC").
|
|
Limit(limit).
|
|
Offset(offset).
|
|
Find(&results).Error
|
|
if err != nil {
|
|
return nil, 0, err
|
|
}
|
|
|
|
return results, int(total), nil
|
|
}
|
|
|
|
func parseLimit(raw string) int {
|
|
limit := 100
|
|
if raw != "" {
|
|
if val, err := strconv.Atoi(raw); err == nil && val > 0 {
|
|
limit = val
|
|
}
|
|
}
|
|
if limit > 500 {
|
|
limit = 500
|
|
}
|
|
return limit
|
|
}
|
|
|
|
func parseOffset(raw string) int {
|
|
if raw == "" {
|
|
return 0
|
|
}
|
|
val, err := strconv.Atoi(raw)
|
|
if err != nil || val < 0 {
|
|
return 0
|
|
}
|
|
return val
|
|
}
|