mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
439 lines
14 KiB
Go
439 lines
14 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/trackeep/backend/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type MarketplaceHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
func NewMarketplaceHandler(db *gorm.DB) *MarketplaceHandler {
|
|
return &MarketplaceHandler{db: db}
|
|
}
|
|
|
|
// GetMarketplaceItems returns all marketplace items with filtering
|
|
func (h *MarketplaceHandler) GetMarketplaceItems(c *gin.Context) {
|
|
var items []models.MarketplaceItem
|
|
query := h.db.Preload("Seller").Preload("Tags")
|
|
|
|
// Filter by category
|
|
if category := c.Query("category"); category != "" {
|
|
query = query.Where("category = ?", category)
|
|
}
|
|
|
|
// Filter by content type
|
|
if contentType := c.Query("content_type"); contentType != "" {
|
|
query = query.Where("content_type = ?", contentType)
|
|
}
|
|
|
|
// Filter by price range
|
|
if minPrice := c.Query("min_price"); minPrice != "" {
|
|
if price, err := strconv.ParseFloat(minPrice, 64); err == nil {
|
|
query = query.Where("price >= ?", price)
|
|
}
|
|
}
|
|
if maxPrice := c.Query("max_price"); maxPrice != "" {
|
|
if price, err := strconv.ParseFloat(maxPrice, 64); err == nil {
|
|
query = query.Where("price <= ?", price)
|
|
}
|
|
}
|
|
|
|
// Filter by free items
|
|
if isFree := c.Query("is_free"); isFree == "true" {
|
|
query = query.Where("is_free = ?", true)
|
|
}
|
|
|
|
// Filter by featured items
|
|
if featured := c.Query("featured"); featured == "true" {
|
|
query = query.Where("is_featured = ?", true)
|
|
}
|
|
|
|
// Filter by status (only show published items for public)
|
|
query = query.Where("status = ? AND is_approved = ?", "published", true)
|
|
|
|
// Search by title or description
|
|
if search := c.Query("search"); search != "" {
|
|
// Escape special SQL characters to prevent SQL injection
|
|
escapedSearch := strings.ReplaceAll(search, "%", "\\%")
|
|
escapedSearch = strings.ReplaceAll(escapedSearch, "_", "\\_")
|
|
query = query.Where("title ILIKE ? OR description ILIKE ?", "%"+escapedSearch+"%", "%"+escapedSearch+"%")
|
|
}
|
|
|
|
// Sort by
|
|
sortBy := c.DefaultQuery("sort", "created_at")
|
|
switch sortBy {
|
|
case "rating":
|
|
query = query.Order("rating DESC, review_count DESC")
|
|
case "downloads":
|
|
query = query.Order("download_count DESC")
|
|
case "price_low":
|
|
query = query.Order("price ASC")
|
|
case "price_high":
|
|
query = query.Order("price DESC")
|
|
case "views":
|
|
query = query.Order("view_count DESC")
|
|
case "created_at":
|
|
query = query.Order("created_at DESC")
|
|
default:
|
|
query = query.Order("created_at DESC")
|
|
}
|
|
|
|
// Pagination
|
|
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
|
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20"))
|
|
offset := (page - 1) * limit
|
|
|
|
var total int64
|
|
query.Model(&models.MarketplaceItem{}).Count(&total)
|
|
|
|
if err := query.Offset(offset).Limit(limit).Find(&items).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch marketplace items"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"items": items,
|
|
"pagination": gin.H{
|
|
"page": page,
|
|
"limit": limit,
|
|
"total": total,
|
|
"pages": (total + int64(limit) - 1) / int64(limit),
|
|
},
|
|
})
|
|
}
|
|
|
|
// GetMarketplaceItem returns a specific marketplace item
|
|
func (h *MarketplaceHandler) GetMarketplaceItem(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var item models.MarketplaceItem
|
|
|
|
if err := h.db.Preload("Seller").Preload("Tags").Preload("Reviews").Preload("Reviews.Reviewer").First(&item, id).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Marketplace item not found"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch marketplace item"})
|
|
return
|
|
}
|
|
|
|
// Increment view count
|
|
h.db.Model(&item).UpdateColumn("view_count", gorm.Expr("view_count + 1"))
|
|
h.db.Model(&item).Update("last_viewed_at", time.Now())
|
|
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
// CreateMarketplaceItem creates a new marketplace item
|
|
func (h *MarketplaceHandler) CreateMarketplaceItem(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var item models.MarketplaceItem
|
|
|
|
if err := c.ShouldBindJSON(&item); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
item.SellerID = userID
|
|
item.Status = "draft" // Items start as draft and need approval
|
|
|
|
if err := h.db.Create(&item).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create marketplace item"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, item)
|
|
}
|
|
|
|
// UpdateMarketplaceItem updates an existing marketplace item
|
|
func (h *MarketplaceHandler) UpdateMarketplaceItem(c *gin.Context) {
|
|
id := c.Param("id")
|
|
userID := c.GetUint("user_id")
|
|
var item models.MarketplaceItem
|
|
|
|
if err := h.db.First(&item, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Marketplace item not found"})
|
|
return
|
|
}
|
|
|
|
// Check if user is the seller
|
|
if item.SellerID != userID {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "You can only update your own items"})
|
|
return
|
|
}
|
|
|
|
var updateData models.MarketplaceItem
|
|
if err := c.ShouldBindJSON(&updateData); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update allowed fields
|
|
item.Title = updateData.Title
|
|
item.Description = updateData.Description
|
|
item.Category = updateData.Category
|
|
item.ContentType = updateData.ContentType
|
|
item.ContentURL = updateData.ContentURL
|
|
item.PreviewURL = updateData.PreviewURL
|
|
item.Thumbnail = updateData.Thumbnail
|
|
item.Price = updateData.Price
|
|
item.Currency = updateData.Currency
|
|
item.IsFree = updateData.IsFree
|
|
item.Subscription = updateData.Subscription
|
|
item.SubscriptionPrice = updateData.SubscriptionPrice
|
|
item.License = updateData.License
|
|
item.Version = updateData.Version
|
|
item.LastUpdated = &time.Time{}
|
|
*item.LastUpdated = time.Now()
|
|
|
|
if err := h.db.Save(&item).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update marketplace item"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, item)
|
|
}
|
|
|
|
// DeleteMarketplaceItem deletes a marketplace item
|
|
func (h *MarketplaceHandler) DeleteMarketplaceItem(c *gin.Context) {
|
|
id := c.Param("id")
|
|
userID := c.GetUint("user_id")
|
|
var item models.MarketplaceItem
|
|
|
|
if err := h.db.First(&item, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Marketplace item not found"})
|
|
return
|
|
}
|
|
|
|
// Check if user is the seller
|
|
if item.SellerID != userID {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "You can only delete your own items"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&item).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete marketplace item"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Marketplace item deleted successfully"})
|
|
}
|
|
|
|
// GetMyMarketplaceItems returns current user's marketplace items
|
|
func (h *MarketplaceHandler) GetMyMarketplaceItems(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var items []models.MarketplaceItem
|
|
|
|
if err := h.db.Preload("Tags").Where("seller_id = ?", userID).Find(&items).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch your marketplace items"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, items)
|
|
}
|
|
|
|
// CreateMarketplaceReview creates a new review for a marketplace item
|
|
func (h *MarketplaceHandler) CreateMarketplaceReview(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
itemID := c.Param("id")
|
|
|
|
var review models.MarketplaceReview
|
|
if err := c.ShouldBindJSON(&review); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Check if item exists
|
|
var item models.MarketplaceItem
|
|
if err := h.db.First(&item, itemID).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Marketplace item not found"})
|
|
return
|
|
}
|
|
|
|
// Check if user already reviewed this item
|
|
var existingReview models.MarketplaceReview
|
|
if err := h.db.Where("item_id = ? AND reviewer_id = ?", itemID, userID).First(&existingReview).Error; err == nil {
|
|
c.JSON(http.StatusConflict, gin.H{"error": "You have already reviewed this item"})
|
|
return
|
|
}
|
|
|
|
review.ItemID = item.ID
|
|
review.ReviewerID = userID
|
|
|
|
// Start transaction
|
|
tx := h.db.Begin()
|
|
|
|
// Create review
|
|
if err := tx.Create(&review).Error; err != nil {
|
|
tx.Rollback()
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create review"})
|
|
return
|
|
}
|
|
|
|
// Update item rating
|
|
var avgRating float64
|
|
var reviewCount int64
|
|
tx.Model(&models.MarketplaceReview{}).Where("item_id = ? AND status = ?", itemID, "published").Select("AVG(rating)").Scan(&avgRating)
|
|
tx.Model(&models.MarketplaceReview{}).Where("item_id = ? AND status = ?", itemID, "published").Count(&reviewCount)
|
|
|
|
tx.Model(&item).Updates(map[string]interface{}{
|
|
"rating": avgRating,
|
|
"review_count": reviewCount,
|
|
})
|
|
|
|
if err := tx.Commit().Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create review"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, review)
|
|
}
|
|
|
|
// GetMarketplaceReviews returns reviews for a marketplace item
|
|
func (h *MarketplaceHandler) GetMarketplaceReviews(c *gin.Context) {
|
|
itemID := c.Param("id")
|
|
var reviews []models.MarketplaceReview
|
|
|
|
if err := h.db.Preload("Reviewer").Where("item_id = ? AND status = ?", itemID, "published").Find(&reviews).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch reviews"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, reviews)
|
|
}
|
|
|
|
// CreateContentShare creates a new content share link
|
|
func (h *MarketplaceHandler) CreateContentShare(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var share models.ContentShare
|
|
|
|
if err := c.ShouldBindJSON(&share); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
share.OwnerID = userID
|
|
share.ShareURL = "/shared/" + share.ShareToken
|
|
|
|
if err := h.db.Create(&share).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create content share"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, share)
|
|
}
|
|
|
|
// GetContentShare returns a shared content by token
|
|
func (h *MarketplaceHandler) GetContentShare(c *gin.Context) {
|
|
token := c.Param("token")
|
|
var share models.ContentShare
|
|
|
|
if err := h.db.Preload("Owner").Where("share_token = ? AND is_active = ?", token, true).First(&share).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Shared content not found"})
|
|
return
|
|
}
|
|
|
|
// Check if share has expired
|
|
if share.ExpiresAt != nil && share.ExpiresAt.Before(time.Now()) {
|
|
c.JSON(http.StatusGone, gin.H{"error": "Shared content has expired"})
|
|
return
|
|
}
|
|
|
|
// Increment view count
|
|
h.db.Model(&share).UpdateColumn("view_count", gorm.Expr("view_count + 1"))
|
|
h.db.Model(&share).Update("last_accessed_at", time.Now())
|
|
|
|
// Get the actual content based on content type
|
|
var content interface{}
|
|
switch share.ContentType {
|
|
case "bookmark":
|
|
var bookmark models.Bookmark
|
|
if err := h.db.Where("id = ? AND user_id = ?", share.ContentID, share.OwnerID).First(&bookmark).Error; err == nil {
|
|
content = bookmark
|
|
}
|
|
case "note":
|
|
var note models.Note
|
|
if err := h.db.Where("id = ? AND user_id = ?", share.ContentID, share.OwnerID).First(¬e).Error; err == nil {
|
|
content = note
|
|
}
|
|
case "file":
|
|
var file models.File
|
|
if err := h.db.Where("id = ? AND user_id = ?", share.ContentID, share.OwnerID).First(&file).Error; err == nil {
|
|
content = file
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"share": share,
|
|
"content": content,
|
|
})
|
|
}
|
|
|
|
// GetMyContentShares returns current user's content shares
|
|
func (h *MarketplaceHandler) GetMyContentShares(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
var shares []models.ContentShare
|
|
|
|
if err := h.db.Where("owner_id = ?", userID).Find(&shares).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch your content shares"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, shares)
|
|
}
|
|
|
|
// DeleteContentShare deletes a content share
|
|
func (h *MarketplaceHandler) DeleteContentShare(c *gin.Context) {
|
|
id := c.Param("id")
|
|
userID := c.GetUint("user_id")
|
|
var share models.ContentShare
|
|
|
|
if err := h.db.First(&share, id).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Content share not found"})
|
|
return
|
|
}
|
|
|
|
// Check if user is the owner
|
|
if share.OwnerID != userID {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "You can only delete your own shares"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&share).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete content share"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Content share deleted successfully"})
|
|
}
|
|
|
|
// GetMarketplaceStats returns marketplace statistics
|
|
func (h *MarketplaceHandler) GetMarketplaceStats(c *gin.Context) {
|
|
var stats struct {
|
|
TotalItems int64 `json:"total_items"`
|
|
TotalSellers int64 `json:"total_sellers"`
|
|
TotalBuyers int64 `json:"total_buyers"`
|
|
TotalRevenue float64 `json:"total_revenue"`
|
|
AverageRating float64 `json:"average_rating"`
|
|
TotalReviews int64 `json:"total_reviews"`
|
|
TotalDownloads int64 `json:"total_downloads"`
|
|
}
|
|
|
|
h.db.Model(&models.MarketplaceItem{}).Where("status = ? AND is_approved = ?", "published", true).Count(&stats.TotalItems)
|
|
h.db.Model(&models.MarketplaceItem{}).Select("COUNT(DISTINCT seller_id)").Row().Scan(&stats.TotalSellers)
|
|
h.db.Model(&models.MarketplacePurchase{}).Select("COUNT(DISTINCT buyer_id)").Row().Scan(&stats.TotalBuyers)
|
|
h.db.Model(&models.MarketplacePurchase{}).Where("status = ?", "completed").Select("COALESCE(SUM(price), 0)").Row().Scan(&stats.TotalRevenue)
|
|
h.db.Model(&models.MarketplaceItem{}).Where("status = ? AND is_approved = ?", "published", true).Select("COALESCE(AVG(rating), 0)").Row().Scan(&stats.AverageRating)
|
|
h.db.Model(&models.MarketplaceReview{}).Where("status = ?", "published").Count(&stats.TotalReviews)
|
|
h.db.Model(&models.MarketplaceItem{}).Select("COALESCE(SUM(download_count), 0)").Row().Scan(&stats.TotalDownloads)
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|