added all agent routes

This commit is contained in:
SwiftyOS
2024-09-12 15:38:46 +02:00
parent eb1df12ce8
commit 9cb55c5ac0
11 changed files with 344 additions and 44 deletions

View File

@@ -13,10 +13,10 @@ RUN go mod download
COPY . .
# Build the application
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main .
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o main .
# Run stage
FROM alpine:latest
FROM alpine:latest
RUN apk --no-cache add ca-certificates
ENV GIN_MODE=release
@@ -25,6 +25,7 @@ WORKDIR /root/
# Copy the pre-built binary file from the previous stage
COPY --from=builder /app/main .
COPY --from=builder /app/config.yaml .
# Expose port 8080 to the outside world
EXPOSE 8080

View File

@@ -3,6 +3,7 @@ package database
import (
"context"
"fmt"
"strings"
"time"
"github.com/google/uuid"
@@ -220,3 +221,193 @@ func GetAgentFile(ctx context.Context, db *pgxpool.Pool, agentID string) (*model
return &agentFile, nil
}
func GetTopAgentsByDownloads(ctx context.Context, db *pgxpool.Pool, page, pageSize int) ([]models.AgentWithDownloads, int, error) {
logger := zap.L().With(zap.String("function", "GetTopAgentsByDownloads"))
offset := (page - 1) * pageSize
query := `
SELECT a.id, a.name, a.description, a.author, a.keywords, a.categories, a.graph, at.downloads
FROM agents a
JOIN analytics_tracker at ON a.id = at.agent_id
WHERE a.submission_status = 'APPROVED'
ORDER BY at.downloads DESC
LIMIT $1 OFFSET $2
`
rows, err := db.Query(ctx, query, pageSize, offset)
if err != nil {
logger.Error("Failed to query top agents", zap.Error(err))
return nil, 0, err
}
defer rows.Close()
var agents []models.AgentWithDownloads
for rows.Next() {
var agent models.AgentWithDownloads
err := rows.Scan(
&agent.ID,
&agent.Name,
&agent.Description,
&agent.Author,
&agent.Keywords,
&agent.Categories,
&agent.Graph,
&agent.Downloads,
)
if err != nil {
logger.Error("Failed to scan agent row", zap.Error(err))
return nil, 0, err
}
agents = append(agents, agent)
}
var totalCount int
err = db.QueryRow(ctx, "SELECT COUNT(*) FROM agents WHERE submission_status = 'APPROVED'").Scan(&totalCount)
if err != nil {
logger.Error("Failed to get total count", zap.Error(err))
return nil, 0, err
}
logger.Info("Top agents retrieved", zap.Int("count", len(agents)))
return agents, totalCount, nil
}
func GetFeaturedAgents(ctx context.Context, db *pgxpool.Pool, category string, page, pageSize int) ([]models.Agent, int, error) {
logger := zap.L().With(zap.String("function", "GetFeaturedAgents"))
offset := (page - 1) * pageSize
query := `
SELECT a.id, a.name, a.description, a.author, a.keywords, a.categories, a.graph
FROM agents a
JOIN featured_agent fa ON a.id = fa.agent_id
WHERE $1 = ANY(fa.featured_categories) AND fa.is_active = true AND a.submission_status = 'APPROVED'
ORDER BY a.created_at DESC
LIMIT $2 OFFSET $3
`
rows, err := db.Query(ctx, query, category, pageSize, offset)
if err != nil {
logger.Error("Failed to query featured agents", zap.Error(err))
return nil, 0, err
}
defer rows.Close()
var agents []models.Agent
for rows.Next() {
var agent models.Agent
err := rows.Scan(
&agent.ID,
&agent.Name,
&agent.Description,
&agent.Author,
&agent.Keywords,
&agent.Categories,
&agent.Graph,
)
if err != nil {
logger.Error("Failed to scan featured agent row", zap.Error(err))
return nil, 0, err
}
agents = append(agents, agent)
}
var totalCount int
err = db.QueryRow(ctx, "SELECT COUNT(*) FROM featured_agent fa JOIN agents a ON fa.agent_id = a.id WHERE $1 = ANY(fa.featured_categories) AND fa.is_active = true AND a.submission_status = 'APPROVED'", category).Scan(&totalCount)
if err != nil {
logger.Error("Failed to get total count of featured agents", zap.Error(err))
return nil, 0, err
}
logger.Info("Featured agents retrieved", zap.Int("count", len(agents)))
return agents, totalCount, nil
}
func Search(ctx context.Context, db *pgxpool.Pool, query string, categories []string, page, pageSize int, sortBy, sortOrder string) ([]models.AgentWithRank, error) {
logger := zap.L().With(zap.String("function", "Search"))
offset := (page - 1) * pageSize
categoryFilter := ""
if len(categories) > 0 {
categoryConditions := make([]string, len(categories))
for i, cat := range categories {
categoryConditions[i] = fmt.Sprintf("'%s' = ANY(a.categories)", cat)
}
categoryFilter = "AND (" + strings.Join(categoryConditions, " OR ") + ")"
}
orderByClause := ""
switch sortBy {
case "createdAt", "updatedAt":
orderByClause = fmt.Sprintf(`a."%s" %s, rank DESC`, sortBy, sortOrder)
case "name":
orderByClause = fmt.Sprintf(`a.name %s, rank DESC`, sortOrder)
default:
orderByClause = `rank DESC, a."createdAt" DESC`
}
sqlQuery := fmt.Sprintf(`
WITH query AS (
SELECT to_tsquery(string_agg(lexeme || ':*', ' & ' ORDER BY positions)) AS q
FROM unnest(to_tsvector($1))
)
SELECT
a.id,
a.created_at,
a.updated_at,
a.version,
a.name,
LEFT(a.description, 500) AS description,
a.author,
a.keywords,
a.categories,
a.graph,
a.submission_status,
a.submission_date,
ts_rank(CAST(a.search AS tsvector), query.q) AS rank
FROM agents a, query
WHERE a.submission_status = 'APPROVED' %s
ORDER BY %s
LIMIT $2
OFFSET $3
`, categoryFilter, orderByClause)
rows, err := db.Query(ctx, sqlQuery, query, pageSize, offset)
if err != nil {
logger.Error("Failed to execute search query", zap.Error(err))
return nil, err
}
defer rows.Close()
var agents []models.AgentWithRank
for rows.Next() {
var agent models.AgentWithRank
err := rows.Scan(
&agent.ID,
&agent.CreatedAt,
&agent.UpdatedAt,
&agent.Version,
&agent.Name,
&agent.Description,
&agent.Author,
&agent.Keywords,
&agent.Categories,
&agent.Graph,
&agent.SubmissionStatus,
&agent.SubmissionDate,
&agent.Rank,
)
if err != nil {
logger.Error("Failed to scan search result row", zap.Error(err))
return nil, err
}
agents = append(agents, agent)
}
logger.Info("Search completed", zap.Int("results", len(agents)))
return agents, nil
}

View File

@@ -47,6 +47,7 @@ require (
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/stretchr/objx v0.5.2 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect

View File

@@ -99,6 +99,8 @@ github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=

View File

@@ -3,59 +3,58 @@ package handlers
import (
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
)
func CreateAgentEntry(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func CreateAgentEntry(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement CreateAgentEntry
c.JSON(501, gin.H{"message": "Not Implemented: CreateAgentEntry"})
}
}
func SetAgentFeatured(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func SetAgentFeatured(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement SetAgentFeatured
c.JSON(501, gin.H{"message": "Not Implemented: SetAgentFeatured"})
}
}
func GetAgentFeatured(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func GetAgentFeatured(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement GetAgentFeatured
c.JSON(501, gin.H{"message": "Not Implemented: GetAgentFeatured"})
}
}
func UnsetAgentFeatured(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func UnsetAgentFeatured(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement UnsetAgentFeatured
c.JSON(501, gin.H{"message": "Not Implemented: UnsetAgentFeatured"})
}
}
func GetNotFeaturedAgents(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func GetNotFeaturedAgents(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement GetNotFeaturedAgents
c.JSON(501, gin.H{"message": "Not Implemented: GetNotFeaturedAgents"})
}
}
func GetAgentSubmissions(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func GetAgentSubmissions(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement GetAgentSubmissions
c.JSON(501, gin.H{"message": "Not Implemented: GetAgentSubmissions"})
}
}
func ReviewSubmission(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func ReviewSubmission(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement ReviewSubmission
c.JSON(501, gin.H{"message": "Not Implemented: ReviewSubmission"})
}
}
func GetCategories(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func GetCategories(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement GetCategories
c.JSON(501, gin.H{"message": "Not Implemented: GetCategories"})

View File

@@ -3,6 +3,7 @@ package handlers
import (
"fmt"
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
@@ -76,7 +77,7 @@ func GetAgentDetails(db *pgxpool.Pool) gin.HandlerFunc {
agentID := c.Param("id")
logger.Debug("Agent ID", zap.String("agentID", agentID))
if agentID == "" {
logger.Error("Agent ID is required")
c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"})
@@ -169,25 +170,126 @@ func DownloadAgentFile(db *pgxpool.Pool) gin.HandlerFunc {
func TopAgentsByDownloads(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement the database function to get top agents by downloads
// agents, err := database.GetTopAgentsByDownloads(c.Request.Context(), db, page, pageSize)
// if err != nil {
// logger.Error("Failed to fetch top agents", zap.Error(err))
// c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch top agents"})
// return
// }
logger := zap.L().With(zap.String("function", "TopAgentsByDownloads"))
logger.Info("Handling request for top agents by downloads")
// c.JSON(http.StatusOK, agents)
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil || page < 1 {
logger.Error("Invalid page number", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page number"})
return
}
// For now, return a placeholder response
c.JSON(http.StatusOK, gin.H{"message": "Top agents by downloads will be implemented soon"})
pageSize, err := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
if err != nil || pageSize < 1 {
logger.Error("Invalid page size", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page size"})
return
}
agents, totalCount, err := database.GetTopAgentsByDownloads(c.Request.Context(), db, page, pageSize)
if err != nil {
logger.Error("Failed to fetch top agents", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch top agents"})
return
}
logger.Info("Successfully fetched top agents", zap.Int("count", len(agents)), zap.Int("totalCount", totalCount))
c.JSON(http.StatusOK, gin.H{
"agents": agents,
"totalCount": totalCount,
"page": page,
"pageSize": pageSize,
})
}
}
func GetFeaturedAgents(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func GetFeaturedAgents(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement GetFeaturedAgents
c.JSON(501, gin.H{"message": "Not Implemented: GetFeaturedAgents"})
logger := zap.L().With(zap.String("function", "GetFeaturedAgents"))
logger.Info("Handling request for featured agents")
category := c.Query("category")
if category == "" {
logger.Error("Category is required")
c.JSON(http.StatusBadRequest, gin.H{"error": "Category is required"})
return
}
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil || page < 1 {
logger.Error("Invalid page number", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page number"})
return
}
pageSize, err := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
if err != nil || pageSize < 1 {
logger.Error("Invalid page size", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page size"})
return
}
agents, totalCount, err := database.GetFeaturedAgents(c.Request.Context(), db, category, page, pageSize)
if err != nil {
logger.Error("Failed to fetch featured agents", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch featured agents"})
return
}
logger.Info("Successfully fetched featured agents", zap.Int("count", len(agents)), zap.Int("totalCount", totalCount))
c.JSON(http.StatusOK, gin.H{
"agents": agents,
"totalCount": totalCount,
"page": page,
"pageSize": pageSize,
})
}
}
func Search(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
logger := zap.L().With(zap.String("function", "Search"))
logger.Info("Handling search request")
query := c.Query("q")
if query == "" {
logger.Error("Search query is required")
c.JSON(http.StatusBadRequest, gin.H{"error": "Search query is required"})
return
}
categories := c.QueryArray("categories")
page, err := strconv.Atoi(c.DefaultQuery("page", "1"))
if err != nil || page < 1 {
logger.Error("Invalid page number", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page number"})
return
}
pageSize, err := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
if err != nil || pageSize < 1 {
logger.Error("Invalid page size", zap.Error(err))
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid page size"})
return
}
sortBy := c.DefaultQuery("sortBy", "rank")
sortOrder := c.DefaultQuery("sortOrder", "DESC")
agents, err := database.Search(c.Request.Context(), db, query, categories, page, pageSize, sortBy, sortOrder)
if err != nil {
logger.Error("Failed to perform search", zap.Error(err))
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to perform search"})
return
}
logger.Info("Successfully performed search", zap.Int("resultCount", len(agents)))
c.JSON(http.StatusOK, gin.H{
"agents": agents,
"page": page,
"pageSize": pageSize,
})
}
}

View File

@@ -3,10 +3,9 @@ package handlers
import (
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
)
func AgentInstalled(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
func AgentInstalled(db *pgxpool.Pool) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement AgentInstalled
c.JSON(501, gin.H{"message": "Not Implemented: AgentInstalled"})

View File

@@ -1,14 +0,0 @@
package handlers
import (
"github.com/gin-gonic/gin"
"github.com/jackc/pgx/v5/pgxpool"
"go.uber.org/zap"
)
func Search(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
return func(c *gin.Context) {
// TODO: Implement Search
c.JSON(501, gin.H{"message": "Not Implemented: Search"})
}
}

View File

@@ -29,7 +29,6 @@ func main() {
if err != nil {
logger.Fatal("Failed to connect to database", zap.Error(err))
}
// Initialize Gin router
r := gin.New()

View File

@@ -51,6 +51,17 @@ type AgentWithMetadata struct {
SubmissionReviewComments *string `json:"submissionReviewComments,omitempty"`
}
// AgentWithRank extends AgentWithMetadata with a rank field for search results
type AgentWithRank struct {
AgentWithMetadata
Rank float64 `json:"rank"`
}
type AgentWithDownloads struct {
AgentWithMetadata
Downloads int `json:"downloads"`
}
// AnalyticsTracker represents analytics data for an agent
type AnalyticsTracker struct {
ID string `json:"id"`

View File

@@ -3,10 +3,19 @@ package utils
import (
"github.com/swiftyos/market/config"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"os"
)
func NewLogger(cfg *config.Config) *zap.Logger {
logger, _ := zap.NewProduction()
encoderConfig := zap.NewProductionEncoderConfig()
encoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
consoleEncoder := zapcore.NewConsoleEncoder(encoderConfig)
consoleWriter := zapcore.AddSync(os.Stdout)
core := zapcore.NewCore(consoleEncoder, consoleWriter, zap.InfoLevel)
logger := zap.New(core)
return logger
}