327 lines
10 KiB
Go
327 lines
10 KiB
Go
package controllers
|
|
|
|
import (
|
|
"fmt"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"gitea.tbdevent.eu/TBD/reforger_crawler_main/initializers"
|
|
"gitea.tbdevent.eu/TBD/reforger_crawler_main/models"
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// convertToSimplifiedAddon converts a full Addon to SimplifiedAddon
|
|
func convertToSimplifiedAddon(addon models.Addon) models.SimplifiedAddon {
|
|
var simplifiedFiles []models.SimplifiedAddonFile
|
|
for _, file := range addon.AddonFiles {
|
|
simplifiedFiles = append(simplifiedFiles, models.SimplifiedAddonFile{
|
|
Path: file.Path,
|
|
Hash: file.Hash,
|
|
Version: file.Version,
|
|
AddonID: file.AddonID,
|
|
})
|
|
}
|
|
|
|
return models.SimplifiedAddon{
|
|
ID: addon.ID,
|
|
Name: addon.Name,
|
|
Type: addon.Type,
|
|
Summary: addon.Summary,
|
|
SubscriberCount: addon.SubscriberCount,
|
|
CurrentVersionNumber: addon.CurrentVersionNumber,
|
|
Preview: addon.Preview,
|
|
Author: addon.Author,
|
|
AddonFiles: simplifiedFiles,
|
|
}
|
|
}
|
|
|
|
// convertToSimplifiedAddonWithoutFiles converts a full Addon to SimplifiedAddon without files
|
|
func convertToSimplifiedAddonWithoutFiles(addon models.Addon) models.SimplifiedAddon {
|
|
return models.SimplifiedAddon{
|
|
ID: addon.ID,
|
|
Name: addon.Name,
|
|
Type: addon.Type,
|
|
Summary: addon.Summary,
|
|
SubscriberCount: addon.SubscriberCount,
|
|
CurrentVersionNumber: addon.CurrentVersionNumber,
|
|
Preview: addon.Preview,
|
|
Author: addon.Author,
|
|
AddonFiles: []models.SimplifiedAddonFile{}, // Empty slice
|
|
}
|
|
}
|
|
|
|
func GetDuplicates(c *gin.Context) {
|
|
id := c.Param("id")
|
|
|
|
// First check if addon exists (lightweight query)
|
|
var exists bool
|
|
if err := initializers.DB.Model(&models.Addon{}).Select("1").Where("id = ?", id).Limit(1).Find(&exists).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Addon not found"})
|
|
return
|
|
}
|
|
|
|
// Get only the hashes from source addon files (don't load full addon yet)
|
|
var sourceHashes []string
|
|
if err := initializers.DB.Model(&models.AddonFile{}).
|
|
Where("addon_id = ? AND hash != ''", id).
|
|
Pluck("hash", &sourceHashes).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch source addon files"})
|
|
return
|
|
}
|
|
|
|
// Now load the full source addon for the response
|
|
var sourceAddon models.Addon
|
|
if err := initializers.DB.Preload("AddonFiles").First(&sourceAddon, "id = ?", id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Addon not found"})
|
|
return
|
|
}
|
|
|
|
simplifiedSourceAddon := convertToSimplifiedAddon(sourceAddon)
|
|
|
|
if len(sourceHashes) == 0 {
|
|
c.JSON(http.StatusOK, models.DuplicatesResponse{
|
|
SourceAddon: simplifiedSourceAddon,
|
|
DuplicateAddons: make(map[string]models.AddonWithDuplicates),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Single query with JOIN to get all data
|
|
type DuplicateRow struct {
|
|
Hash string
|
|
Path string
|
|
Version string
|
|
AddonID string
|
|
AddonName string
|
|
AddonType string
|
|
Summary string
|
|
Preview string
|
|
Author string
|
|
SubscriberCount int
|
|
CurrentVersionNumber string
|
|
}
|
|
|
|
var duplicates []DuplicateRow
|
|
err := initializers.DB.Table("addon_files af").
|
|
Select(`af.hash, af.path, af.version, af.addon_id,
|
|
a.name as addon_name, a.type as addon_type, a.summary,
|
|
a.preview, a.author, a.subscriber_count, a.current_version_number`).
|
|
Joins("INNER JOIN addons a ON a.id = af.addon_id").
|
|
Joins("LEFT JOIN whitelisted_hashes wh ON wh.hash = af.hash").
|
|
Where(`af.hash IN ?
|
|
AND af.addon_id != ?
|
|
AND af.deleted_at IS NULL
|
|
AND wh.hash IS NULL`,
|
|
sourceHashes, id).
|
|
Scan(&duplicates).Error
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch duplicates"})
|
|
return
|
|
}
|
|
|
|
// Build response from single result set
|
|
response := models.DuplicatesResponse{
|
|
SourceAddon: simplifiedSourceAddon,
|
|
DuplicateAddons: make(map[string]models.AddonWithDuplicates),
|
|
}
|
|
|
|
for _, dup := range duplicates {
|
|
if _, exists := response.DuplicateAddons[dup.AddonID]; !exists {
|
|
response.DuplicateAddons[dup.AddonID] = models.AddonWithDuplicates{
|
|
Addon: models.SimplifiedAddon{
|
|
ID: dup.AddonID,
|
|
Name: dup.AddonName,
|
|
Type: dup.AddonType,
|
|
Summary: dup.Summary,
|
|
Preview: dup.Preview,
|
|
Author: dup.Author,
|
|
SubscriberCount: dup.SubscriberCount,
|
|
CurrentVersionNumber: dup.CurrentVersionNumber,
|
|
AddonFiles: []models.SimplifiedAddonFile{},
|
|
},
|
|
Duplicates: []models.DuplicateFileInfo{},
|
|
}
|
|
}
|
|
|
|
entry := response.DuplicateAddons[dup.AddonID]
|
|
entry.Duplicates = append(entry.Duplicates, models.DuplicateFileInfo{
|
|
Path: dup.Path,
|
|
Hash: dup.Hash,
|
|
Version: dup.Version,
|
|
AddonID: dup.AddonID,
|
|
})
|
|
response.DuplicateAddons[dup.AddonID] = entry
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|
|
|
|
/*func GetDuplicates(c *gin.Context) {
|
|
id := c.Param("id")
|
|
fmt.Println("Fetching addon with ID:", id)
|
|
|
|
// Fetch the source addon with its files
|
|
var sourceAddon models.Addon
|
|
if err := initializers.DB.Preload("AddonFiles").First(&sourceAddon, "id = ?", id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Addon not found"})
|
|
return
|
|
}
|
|
|
|
// Convert to simplified addon
|
|
simplifiedSourceAddon := convertToSimplifiedAddon(sourceAddon)
|
|
|
|
// If the addon has no files, return early
|
|
if len(sourceAddon.AddonFiles) == 0 {
|
|
c.JSON(http.StatusOK, models.DuplicatesResponse{
|
|
SourceAddon: simplifiedSourceAddon,
|
|
DuplicateAddons: make(map[string]models.AddonWithDuplicates),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Get all whitelisted hashes
|
|
var whitelistedHashes []string
|
|
if err := initializers.DB.Model(&models.WhitelistedHash{}).Pluck("hash", &whitelistedHashes).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch whitelisted hashes"})
|
|
return
|
|
}
|
|
|
|
// Create a map for faster lookup
|
|
whitelistMap := make(map[string]bool)
|
|
for _, hash := range whitelistedHashes {
|
|
whitelistMap[hash] = true
|
|
}
|
|
|
|
// Extract all hashes from source addon files, excluding whitelisted ones
|
|
var sourceHashes []string
|
|
for _, file := range sourceAddon.AddonFiles {
|
|
if file.Hash != "" && !whitelistMap[file.Hash] {
|
|
sourceHashes = append(sourceHashes, file.Hash)
|
|
}
|
|
}
|
|
|
|
if len(sourceHashes) == 0 {
|
|
c.JSON(http.StatusOK, models.DuplicatesResponse{
|
|
SourceAddon: simplifiedSourceAddon,
|
|
DuplicateAddons: make(map[string]models.AddonWithDuplicates),
|
|
})
|
|
return
|
|
}
|
|
|
|
// Find all addon files that have matching hashes but belong to different addons
|
|
var duplicateFiles []models.AddonFile
|
|
if err := initializers.DB.Where("hash IN ? AND addon_id != ?", sourceHashes, id).Find(&duplicateFiles).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch duplicate files"})
|
|
return
|
|
}
|
|
|
|
// Group duplicate files by addon ID
|
|
duplicatesByAddonID := make(map[string][]models.AddonFile)
|
|
for _, file := range duplicateFiles {
|
|
duplicatesByAddonID[file.AddonID] = append(duplicatesByAddonID[file.AddonID], file)
|
|
}
|
|
|
|
// Fetch addon information for each addon that has duplicates
|
|
var addonIDs []string
|
|
for addonID := range duplicatesByAddonID {
|
|
addonIDs = append(addonIDs, addonID)
|
|
}
|
|
|
|
var duplicateAddons []models.Addon
|
|
if len(addonIDs) > 0 {
|
|
if err := initializers.DB.Where("id IN ?", addonIDs).Find(&duplicateAddons).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch duplicate addons"})
|
|
return
|
|
}
|
|
}
|
|
|
|
// Build the response
|
|
response := models.DuplicatesResponse{
|
|
SourceAddon: simplifiedSourceAddon,
|
|
DuplicateAddons: make(map[string]models.AddonWithDuplicates),
|
|
}
|
|
|
|
// Create a map for easy addon lookup
|
|
addonMap := make(map[string]models.Addon)
|
|
for _, addon := range duplicateAddons {
|
|
addonMap[addon.ID] = addon
|
|
}
|
|
|
|
// Build the duplicate addons response
|
|
for addonID, files := range duplicatesByAddonID {
|
|
var duplicateFileInfos []models.DuplicateFileInfo
|
|
for _, file := range files {
|
|
duplicateFileInfos = append(duplicateFileInfos, models.DuplicateFileInfo{
|
|
Path: file.Path,
|
|
Hash: file.Hash,
|
|
Version: file.Version,
|
|
AddonID: file.AddonID,
|
|
})
|
|
}
|
|
|
|
if addon, exists := addonMap[addonID]; exists {
|
|
response.DuplicateAddons[addonID] = models.AddonWithDuplicates{
|
|
Addon: convertToSimplifiedAddonWithoutFiles(addon),
|
|
Duplicates: duplicateFileInfos,
|
|
}
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}*/
|
|
|
|
func GetPossibleAddons(c *gin.Context) {
|
|
query := c.Query("q")
|
|
if query == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Query parameter 'q' is required"})
|
|
return
|
|
}
|
|
|
|
// Set default limit
|
|
limit := 20
|
|
if limitParam := c.Query("limit"); limitParam != "" {
|
|
if parsedLimit, err := strconv.Atoi(limitParam); err == nil && parsedLimit > 0 && parsedLimit <= 100 {
|
|
limit = parsedLimit
|
|
}
|
|
}
|
|
|
|
fmt.Printf("Searching for addons with query: %s (limit: %d)\n", query, limit)
|
|
|
|
var addons []models.Addon
|
|
|
|
// Search by ID (exact match) or by name (case-insensitive partial match)
|
|
// Using OR condition to search both ID and name fields
|
|
dbQuery := initializers.DB.Where("id = ? OR LOWER(name) LIKE LOWER(?)", query, "%"+query+"%").
|
|
Order("subscriber_count DESC"). // Order by popularity
|
|
Limit(limit)
|
|
|
|
if err := dbQuery.Find(&addons).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to search addons"})
|
|
return
|
|
}
|
|
|
|
// Convert to search results
|
|
var results []models.AddonSearchResult
|
|
for _, addon := range addons {
|
|
results = append(results, models.AddonSearchResult{
|
|
ID: addon.ID,
|
|
Name: addon.Name,
|
|
Type: addon.Type,
|
|
Summary: addon.Summary,
|
|
Preview: addon.Preview,
|
|
SubscriberCount: addon.SubscriberCount,
|
|
CurrentVersionNumber: addon.CurrentVersionNumber,
|
|
Author: addon.Author,
|
|
})
|
|
}
|
|
|
|
response := models.SearchResponse{
|
|
Query: query,
|
|
Results: results,
|
|
Total: len(results),
|
|
}
|
|
|
|
c.JSON(http.StatusOK, response)
|
|
}
|