mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-12 00:28:31 -05:00
Compare commits
54 Commits
dependabot
...
go
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e0dad5a96 | ||
|
|
f598ba27b6 | ||
|
|
286202cc66 | ||
|
|
55eb917162 | ||
|
|
c2843eecfa | ||
|
|
3971fbd800 | ||
|
|
e04838feb5 | ||
|
|
3ef1f6e380 | ||
|
|
705c63b801 | ||
|
|
da007e3a22 | ||
|
|
9e18c26e65 | ||
|
|
30e3d65711 | ||
|
|
b9c26b1a6b | ||
|
|
220a127e51 | ||
|
|
004e49edb1 | ||
|
|
bf21bb1fa5 | ||
|
|
530ddf2c34 | ||
|
|
33ee2f2ee5 | ||
|
|
ae1d410b65 | ||
|
|
196a5d6b59 | ||
|
|
82c1249d33 | ||
|
|
9389a30298 | ||
|
|
9b58faeeb6 | ||
|
|
9cb55c5ac0 | ||
|
|
eb1df12ce8 | ||
|
|
12f40596b3 | ||
|
|
3af26a9379 | ||
|
|
4041ed3e33 | ||
|
|
ee78653425 | ||
|
|
76c5a27044 | ||
|
|
c6aba70dd4 | ||
|
|
470e7036b9 | ||
|
|
f6608754aa | ||
|
|
bd70ab00e0 | ||
|
|
2c940b381a | ||
|
|
03b30ebf5b | ||
|
|
0d8c2a820e | ||
|
|
5ce562e11f | ||
|
|
fcf2247c20 | ||
|
|
7326ee1221 | ||
|
|
70c7a3b1f3 | ||
|
|
6bdab5b777 | ||
|
|
5f5e31ac19 | ||
|
|
9e71b658d6 | ||
|
|
b7b23d68b4 | ||
|
|
630f401cee | ||
|
|
94fbcfb501 | ||
|
|
8102f78030 | ||
|
|
b1eb259bb3 | ||
|
|
c738eb3bc6 | ||
|
|
60fca5c5f0 | ||
|
|
bba9836735 | ||
|
|
ad76bd1300 | ||
|
|
fa16c207e0 |
@@ -46,7 +46,7 @@ export default class MarketplaceAPI {
|
||||
pageSize: number = 10,
|
||||
): Promise<AgentListResponse> {
|
||||
return this._get(
|
||||
`/top-downloads/agents?page=${page}&page_size=${pageSize}`,
|
||||
`agents/top-downloads?page=${page}&page_size=${pageSize}`,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ export default class MarketplaceAPI {
|
||||
page: number = 1,
|
||||
pageSize: number = 10,
|
||||
): Promise<AgentListResponse> {
|
||||
return this._get(`/featured/agents?page=${page}&page_size=${pageSize}`);
|
||||
return this._get(`/agents/featured?page=${page}&page_size=${pageSize}`);
|
||||
}
|
||||
|
||||
async searchAgents(
|
||||
|
||||
@@ -163,7 +163,7 @@ services:
|
||||
environment:
|
||||
- DATABASE_URL=postgresql://agpt_user:pass123@postgres:5432/agpt_local?connect_timeout=60&schema=market
|
||||
ports:
|
||||
- "8015:8015"
|
||||
- "8015:8000"
|
||||
networks:
|
||||
- app-network
|
||||
|
||||
|
||||
@@ -58,4 +58,4 @@ FROM server_dependencies AS server
|
||||
ENV DATABASE_URL=""
|
||||
ENV PORT=8015
|
||||
|
||||
CMD ["poetry", "run", "app"]
|
||||
CMD ["uvicorn", "market.app:app", "--reload"]
|
||||
|
||||
48
rnd/rest-api-go/.gitignore
vendored
Normal file
48
rnd/rest-api-go/.gitignore
vendored
Normal file
@@ -0,0 +1,48 @@
|
||||
# Binaries for programs and plugins
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
|
||||
# Test binary, built with `go test -c`
|
||||
*.test
|
||||
|
||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||
*.out
|
||||
|
||||
# Dependency directories (remove the comment below to include it)
|
||||
# vendor/
|
||||
|
||||
# Go workspace file
|
||||
go.work
|
||||
|
||||
# IDE-specific files
|
||||
.idea/
|
||||
.vscode/
|
||||
|
||||
# OS-specific files
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Binary output directory
|
||||
/bin/
|
||||
|
||||
# Log files
|
||||
*.log
|
||||
|
||||
# Environment variables file
|
||||
.env
|
||||
|
||||
# Air temporary files (if using Air for live reloading)
|
||||
tmp/
|
||||
|
||||
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||
*.o
|
||||
*.a
|
||||
|
||||
# Debug files
|
||||
debug
|
||||
|
||||
# Project-specific build outputs
|
||||
/gosrv
|
||||
34
rnd/rest-api-go/Dockerfile
Normal file
34
rnd/rest-api-go/Dockerfile
Normal file
@@ -0,0 +1,34 @@
|
||||
# Build stage
|
||||
FROM golang:1.23.1-alpine AS builder
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Copy go mod and sum files
|
||||
COPY go.mod go.sum ./
|
||||
|
||||
# Download all dependencies
|
||||
RUN go mod download
|
||||
|
||||
# Copy the source code
|
||||
COPY . .
|
||||
|
||||
# Build the application
|
||||
RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -ldflags="-w -s" -o main .
|
||||
|
||||
# Run stage
|
||||
FROM alpine:latest
|
||||
|
||||
RUN apk --no-cache add ca-certificates
|
||||
ENV GIN_MODE=release
|
||||
|
||||
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
|
||||
|
||||
# Command to run the executable
|
||||
CMD ["./main"]
|
||||
122
rnd/rest-api-go/README.md
Normal file
122
rnd/rest-api-go/README.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Market API
|
||||
|
||||
This project is a Go-based API for a marketplace application. It provides endpoints for managing agents, handling user authentication, and performing administrative tasks.
|
||||
|
||||
## Project Structure
|
||||
|
||||
The project is organized into several packages:
|
||||
|
||||
- `config`: Handles configuration loading and management
|
||||
- `docs`: Contains the Swagger documentation
|
||||
- `database`: Contains database migrations and interaction logic
|
||||
- `handlers`: Implements HTTP request handlers
|
||||
- `middleware`: Contains middleware functions for the API
|
||||
- `models`: Defines data structures used throughout the application
|
||||
- `utils`: Provides utility functions
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Go 1.16 or later
|
||||
- PostgreSQL
|
||||
- [golang-migrate](https://github.com/golang-migrate/migrate)
|
||||
|
||||
## Setup
|
||||
|
||||
1. Clone the repository
|
||||
2. Install dependencies:
|
||||
```
|
||||
go mod tidy
|
||||
```
|
||||
3. Set up the database:
|
||||
- Create a PostgreSQL database
|
||||
- Update the `DatabaseURL` in your configuration file
|
||||
|
||||
4. Run database migrations:
|
||||
```
|
||||
migrate -source file://database/migrations -database "postgresql://agpt_user:pass123@localhost:5432/apgt_marketplace?sslmode=disable" up
|
||||
```
|
||||
|
||||
## Running the Application
|
||||
|
||||
To run the application in development mode with hot reloading:
|
||||
|
||||
```
|
||||
air
|
||||
```
|
||||
|
||||
For production, build and run the binary:
|
||||
|
||||
```
|
||||
go build -o market-api
|
||||
./market-api
|
||||
```
|
||||
|
||||
## Testing
|
||||
|
||||
Run tests with coverage:
|
||||
```
|
||||
go test -cover ./...
|
||||
```
|
||||
|
||||
## Code Formatting
|
||||
|
||||
Format the code using:
|
||||
|
||||
```
|
||||
gofmt -w .
|
||||
```
|
||||
|
||||
## Database Migrations
|
||||
|
||||
Create a new migration:
|
||||
|
||||
```
|
||||
migrate create -ext sql -dir database/migrations -seq <migration_name>
|
||||
```
|
||||
|
||||
Apply migrations:
|
||||
|
||||
```
|
||||
migrate -source file://database/migrations -database "postgresql://user:password@localhost:5432/dbname?sslmode=disable" up
|
||||
```
|
||||
|
||||
Revert the last migration:
|
||||
|
||||
```
|
||||
migrate -source file://database/migrations -database "postgresql://user:password@localhost:5432/dbname?sslmode=disable" down 1
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
The API provides various endpoints for agent management, user authentication, and administrative tasks. Some key endpoints include:
|
||||
|
||||
- `/api/agents`: Get list of agents
|
||||
- `/api/agents/:agent_id`: Get agent details
|
||||
- `/api/agents/submit`: Submit a new agent
|
||||
- `/api/admin/*`: Various administrative endpoints (requires admin authentication)
|
||||
|
||||
Refer to the `main.go` file for a complete list of endpoints and their corresponding handlers.
|
||||
|
||||
|
||||
# Swagger Documentation
|
||||
|
||||
This project uses `gin-swagger` and `Swaggo` tools for automatic generation of API documentation in OpenAPI (Swagger) format. The documentation is based on comments added to the code using Swagger annotations.
|
||||
|
||||
To view and interact with the generated Swagger documentation, follow these steps:
|
||||
|
||||
1. Run your Gin server.
|
||||
2. Access the Swagger UI by navigating to `http://localhost:8015/docs/index.html` in your web browser.
|
||||
|
||||
Alternatively, you can view the raw OpenAPI specification at `http://localhost:8015/docs/doc.json`.
|
||||
|
||||
## Regenerating Swagger Documentation
|
||||
|
||||
If you make changes to your codebase and want to regenerate the Swagger documentation, follow these steps:
|
||||
|
||||
1. Run the `swag init` command in your project directory to create a new `docs.go` file (or update an existing one) with Swagger documentation comments based on your code:
|
||||
```bash
|
||||
swag init -g main.go
|
||||
```
|
||||
Replace `main.go` with the name of your main Go source file.
|
||||
|
||||
3. Run your Gin server, and access the updated Swagger UI at `http://localhost:8015/docs/index.html`. You should see your documentation reflecting the latest changes in your codebase.
|
||||
7
rnd/rest-api-go/config.yaml
Normal file
7
rnd/rest-api-go/config.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
ServerAddress: ":8015"
|
||||
DatabaseURL: "postgresql://agpt_user:pass123@localhost:5433/agpt_marketplace?connect_timeout=60"
|
||||
JWTSecret: "Z86RsQ+nhSk+A8ODJX1kQA11JCk9nlw8n+MRdSgmR+P1sMPTTDG1rjBTwj7Ucjb3TRHSVxkCNPgXISmzU/vMkA=="
|
||||
JWTAlgorithm: "HS256"
|
||||
CORSAllowOrigins:
|
||||
- "http://localhost:3000"
|
||||
- "http://127.0.0.1:3000"
|
||||
63
rnd/rest-api-go/config/config.go
Normal file
63
rnd/rest-api-go/config/config.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
ServerAddress string `mapstructure:"serveraddress"`
|
||||
DatabaseURL string `mapstructure:"databaseurl"`
|
||||
AuthEnabled bool `mapstructure:"authenabled"`
|
||||
JWTSecret string `mapstructure:"jwtsecret"`
|
||||
JWTAlgorithm string `mapstructure:"jwtalgorithm"`
|
||||
CORSAllowOrigins []string `mapstructure:"corsalloworigins"`
|
||||
}
|
||||
|
||||
func Load(configFile ...string) (*Config, error) {
|
||||
logger := zap.L().With(zap.String("function", "Load"))
|
||||
|
||||
if len(configFile) > 0 {
|
||||
viper.SetConfigFile(configFile[0])
|
||||
} else {
|
||||
viper.SetConfigName("config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
}
|
||||
|
||||
viper.SetEnvPrefix("AGPT")
|
||||
viper.AutomaticEnv()
|
||||
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
logger.Error("Failed to read config file", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
logger.Error("Failed to unmarshal config", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if config.ServerAddress == "" {
|
||||
logger.Error("ServerAddress is required")
|
||||
return nil, fmt.Errorf("ServerAddress is required")
|
||||
}
|
||||
if config.DatabaseURL == "" {
|
||||
logger.Error("DatabaseURL is required")
|
||||
return nil, fmt.Errorf("DatabaseURL is required")
|
||||
}
|
||||
if config.JWTSecret == "" {
|
||||
logger.Error("JWTSecret is required")
|
||||
return nil, fmt.Errorf("JWTSecret is required")
|
||||
}
|
||||
if config.JWTAlgorithm == "" {
|
||||
logger.Error("JWTAlgorithm is required")
|
||||
return nil, fmt.Errorf("JWTAlgorithm is required")
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
||||
78
rnd/rest-api-go/config/config_test.go
Normal file
78
rnd/rest-api-go/config/config_test.go
Normal file
@@ -0,0 +1,78 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoadValidConfig(t *testing.T) {
|
||||
// Create a temporary config file for testing
|
||||
tempFile, err := os.CreateTemp("", "test-config*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Write test configuration to the temp file
|
||||
testConfig := []byte(`
|
||||
serveraddress: ":8080"
|
||||
databaseurl: "postgres://user:pass@localhost:5432/testdb"
|
||||
authenabled: true
|
||||
jwtsecret: "test-secret"
|
||||
jwtalgorithm: "HS256"
|
||||
`)
|
||||
if _, err := tempFile.Write(testConfig); err != nil {
|
||||
t.Fatalf("Failed to write to temp file: %v", err)
|
||||
}
|
||||
tempFile.Close()
|
||||
|
||||
// Test the Load function with a specific config file
|
||||
config, err := Load(tempFile.Name())
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, config)
|
||||
|
||||
// Verify the loaded configuration
|
||||
assert.Equal(t, ":8080", config.ServerAddress)
|
||||
assert.Equal(t, "postgres://user:pass@localhost:5432/testdb", config.DatabaseURL)
|
||||
assert.True(t, config.AuthEnabled)
|
||||
assert.Equal(t, "test-secret", config.JWTSecret)
|
||||
assert.Equal(t, "HS256", config.JWTAlgorithm)
|
||||
}
|
||||
|
||||
func TestLoadDefaultConfigFile(t *testing.T) {
|
||||
// Test with default config file (should fail in test environment)
|
||||
config, err := Load()
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, config)
|
||||
}
|
||||
|
||||
func TestLoadMissingConfigFile(t *testing.T) {
|
||||
// Test with missing config file
|
||||
config, err := Load("non_existent_config.yaml")
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, config)
|
||||
}
|
||||
|
||||
func TestLoadInvalidConfigFormat(t *testing.T) {
|
||||
// Create a temporary config file for testing
|
||||
tempFile, err := os.CreateTemp("", "test-config*.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create temp file: %v", err)
|
||||
}
|
||||
defer os.Remove(tempFile.Name())
|
||||
|
||||
// Test with invalid config format
|
||||
invalidConfig := []byte(`
|
||||
serveraddress: ":8080"
|
||||
databaseurl: 123 # Invalid type, should be string
|
||||
`)
|
||||
if err := os.WriteFile(tempFile.Name(), invalidConfig, 0644); err != nil {
|
||||
t.Fatalf("Failed to write invalid config: %v", err)
|
||||
}
|
||||
|
||||
config, err := Load(tempFile.Name())
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, config)
|
||||
}
|
||||
805
rnd/rest-api-go/database/db.go
Normal file
805
rnd/rest-api-go/database/db.go
Normal file
@@ -0,0 +1,805 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/swiftyos/market/config"
|
||||
"github.com/swiftyos/market/models"
|
||||
"github.com/swiftyos/market/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func NewDB(cfg *config.Config) (*pgxpool.Pool, error) {
|
||||
return pgxpool.New(context.Background(), cfg.DatabaseURL)
|
||||
}
|
||||
|
||||
func GetAgents(ctx context.Context, db *pgxpool.Pool, logger *zap.Logger, page int, pageSize int, name *string, keywords *string, categories *string) ([]models.Agent, error) {
|
||||
logger = logger.With(zap.String("function", "GetAgents")).With(zap.String("file", "db.go"))
|
||||
|
||||
logger.Debug("Query parameters",
|
||||
zap.Int("page", page),
|
||||
zap.Int("pageSize", pageSize),
|
||||
zap.String("name", utils.StringOrNil(name)),
|
||||
zap.String("keywords", utils.StringOrNil(keywords)),
|
||||
zap.String("categories", utils.StringOrNil(categories)))
|
||||
|
||||
query := `
|
||||
SELECT "id", "name", "description", "author", "keywords", "categories", "graph" FROM "Agents"
|
||||
WHERE "submissionStatus" = 'APPROVED'
|
||||
AND ($3::text IS NULL OR name ILIKE '%' || $3 || '%')
|
||||
AND ($4::text IS NULL OR $4 = ANY(keywords))
|
||||
AND ($5::text IS NULL OR $5 = ANY(categories))
|
||||
ORDER BY "createdAt" DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
rows, err := db.Query(ctx, query, pageSize, (page-1)*pageSize, name, keywords, categories)
|
||||
if err != nil {
|
||||
logger.Error("Error querying agents", zap.Error(err))
|
||||
return nil, 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("Error scanning agent", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
agents = append(agents, agent)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
logger.Error("Error iterating over agents", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
logger.Info("Found agents", zap.Int("count", len(agents)))
|
||||
|
||||
if agents == nil {
|
||||
agents = []models.Agent{}
|
||||
}
|
||||
return agents, err
|
||||
}
|
||||
|
||||
func SubmitAgent(ctx context.Context, db *pgxpool.Pool, request models.AddAgentRequest, user interface{}) (*models.AgentWithMetadata, error) {
|
||||
logger := zap.L().With(zap.String("function", "SubmitAgent"))
|
||||
logger.Info("Submitting new agent")
|
||||
|
||||
// Generate a new UUID for the agent
|
||||
agentID := uuid.New().String()
|
||||
|
||||
// Create the Agent struct
|
||||
agent := models.Agent{
|
||||
ID: agentID,
|
||||
Name: request.Graph.Name,
|
||||
Description: request.Graph.Description,
|
||||
Author: request.Author,
|
||||
Keywords: request.Keywords,
|
||||
Categories: request.Categories,
|
||||
Graph: request.Graph,
|
||||
}
|
||||
|
||||
// Create the AgentWithMetadata struct
|
||||
agentWithMetadata := models.AgentWithMetadata{
|
||||
Agent: agent,
|
||||
Version: 1,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
SubmissionDate: time.Now(),
|
||||
SubmissionStatus: models.SubmissionStatusPending,
|
||||
}
|
||||
|
||||
// Start a transaction
|
||||
tx, err := db.Begin(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to begin transaction", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback(ctx)
|
||||
|
||||
// Insert the agent into the database
|
||||
_, err = tx.Exec(ctx, `
|
||||
INSERT INTO "Agents" (id, name, description, author, keywords, categories, graph, version, created_at, updated_at, submission_date, submission_status)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
`, agentWithMetadata.ID, agentWithMetadata.Name, agentWithMetadata.Description, agentWithMetadata.Author,
|
||||
agentWithMetadata.Keywords, agentWithMetadata.Categories, agentWithMetadata.Graph,
|
||||
agentWithMetadata.Version, agentWithMetadata.CreatedAt, agentWithMetadata.UpdatedAt,
|
||||
agentWithMetadata.SubmissionDate, agentWithMetadata.SubmissionStatus)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to insert agent", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Commit the transaction
|
||||
err = tx.Commit(ctx)
|
||||
if err != nil {
|
||||
logger.Error("Failed to commit transaction", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Successfully submitted new agent", zap.String("agentID", agentID))
|
||||
return &agentWithMetadata, nil
|
||||
}
|
||||
|
||||
func GetAgentDetails(ctx context.Context, db *pgxpool.Pool, agentID string) (*models.AgentWithMetadata, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetAgentDetails"))
|
||||
|
||||
query := `
|
||||
SELECT id, name, description, author, keywords, categories, graph, version, created_at, updated_at, submission_date, submission_status
|
||||
FROM "Agents"
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var agent models.AgentWithMetadata
|
||||
err := db.QueryRow(ctx, query, agentID).Scan(
|
||||
&agent.ID,
|
||||
&agent.Name,
|
||||
&agent.Description,
|
||||
&agent.Author,
|
||||
&agent.Keywords,
|
||||
&agent.Categories,
|
||||
&agent.Graph,
|
||||
&agent.Version,
|
||||
&agent.CreatedAt,
|
||||
&agent.UpdatedAt,
|
||||
&agent.SubmissionDate,
|
||||
&agent.SubmissionStatus,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
logger.Error("Agent not found", zap.String("agentID", agentID))
|
||||
return nil, fmt.Errorf("agent not found")
|
||||
}
|
||||
logger.Error("Error querying agent details", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Agent details retrieved", zap.String("agentID", agentID))
|
||||
return &agent, nil
|
||||
}
|
||||
|
||||
func IncrementDownloadCount(ctx context.Context, db *pgxpool.Pool, agentID string) error {
|
||||
logger := zap.L().With(zap.String("function", "IncrementDownloadCount"))
|
||||
|
||||
query := `
|
||||
UPDATE "Agents"
|
||||
SET download_count = download_count + 1
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
_, err := db.Exec(ctx, query, agentID)
|
||||
if err != nil {
|
||||
logger.Error("Failed to increment download count", zap.Error(err), zap.String("agentID", agentID))
|
||||
return err
|
||||
}
|
||||
|
||||
logger.Info("Download count incremented", zap.String("agentID", agentID))
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetAgentFile(ctx context.Context, db *pgxpool.Pool, agentID string) (*models.AgentFile, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetAgentFile"))
|
||||
|
||||
query := `
|
||||
SELECT id, name, graph
|
||||
FROM "Agents"
|
||||
WHERE id = $1
|
||||
`
|
||||
|
||||
var agentFile models.AgentFile
|
||||
err := db.QueryRow(ctx, query, agentID).Scan(
|
||||
&agentFile.ID,
|
||||
&agentFile.Name,
|
||||
&agentFile.Graph,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
logger.Error("Agent not found", zap.String("agentID", agentID))
|
||||
return nil, fmt.Errorf("agent not found")
|
||||
}
|
||||
logger.Error("Error querying agent file", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Agent file retrieved", zap.String("agentID", agentID))
|
||||
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 "AnalyticsTracker" 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 "FeaturedAgent" 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 "FeaturedAgent" 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
|
||||
}
|
||||
|
||||
func CreateAgentInstalledEvent(ctx context.Context, db *pgxpool.Pool, eventData models.InstallTracker) error {
|
||||
logger := zap.L().With(zap.String("function", "CreateAgentInstalledEvent"))
|
||||
logger.Info("Creating agent installed event")
|
||||
|
||||
query := `
|
||||
INSERT INTO install_tracker (marketplace_agent_id, installed_agent_id, installation_location)
|
||||
VALUES ($1, $2, $3)
|
||||
`
|
||||
|
||||
_, err := db.Exec(ctx, query,
|
||||
eventData.MarketplaceAgentID,
|
||||
eventData.InstalledAgentID,
|
||||
eventData.InstallationLocation,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to create agent installed event", zap.Error(err))
|
||||
return fmt.Errorf("failed to create agent installed event: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Agent installed event created successfully")
|
||||
return nil
|
||||
}
|
||||
|
||||
// Admin Queries
|
||||
|
||||
func CreateAgentEntry(ctx context.Context, db *pgxpool.Pool, agent models.Agent) (models.Agent, error) {
|
||||
logger := zap.L().With(zap.String("function", "CreateAgentEntry"))
|
||||
logger.Info("Creating agent entry")
|
||||
|
||||
query := `
|
||||
INSERT INTO agents (id, name, description, author, keywords, categories, graph)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
||||
RETURNING id, name, description, author, keywords, categories, graph
|
||||
`
|
||||
var createdAgent models.Agent
|
||||
err := db.QueryRow(ctx, query,
|
||||
agent.ID,
|
||||
agent.Name,
|
||||
agent.Description,
|
||||
agent.Author,
|
||||
agent.Keywords,
|
||||
agent.Categories,
|
||||
agent.Graph,
|
||||
).Scan(
|
||||
&createdAgent.ID,
|
||||
&createdAgent.Name,
|
||||
&createdAgent.Description,
|
||||
&createdAgent.Author,
|
||||
&createdAgent.Keywords,
|
||||
&createdAgent.Categories,
|
||||
&createdAgent.Graph,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to create agent entry", zap.Error(err))
|
||||
return models.Agent{}, err
|
||||
}
|
||||
|
||||
logger.Info("Agent entry created successfully", zap.String("agentID", agent.ID))
|
||||
return createdAgent, nil
|
||||
}
|
||||
|
||||
func SetAgentFeatured(ctx context.Context, db *pgxpool.Pool, agentID string, isActive bool, featuredCategories []string) (*models.FeaturedAgent, error) {
|
||||
logger := zap.L().With(zap.String("function", "SetAgentFeatured"))
|
||||
logger.Info("Setting agent featured status", zap.String("agentID", agentID), zap.Bool("isActive", isActive))
|
||||
|
||||
// Check if the agent exists
|
||||
var exists bool
|
||||
err := db.QueryRow(ctx, `SELECT EXISTS(SELECT 1 FROM "Agents" WHERE id = $1)`, agentID).Scan(&exists)
|
||||
if err != nil {
|
||||
logger.Error("Failed to check if agent exists", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to check if agent exists: %w", err)
|
||||
}
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("agent with ID %s not found", agentID)
|
||||
}
|
||||
|
||||
var query string
|
||||
var args []interface{}
|
||||
|
||||
if isActive {
|
||||
// Set the agent as featured
|
||||
query = `
|
||||
INSERT INTO "FeaturedAgent" (agent_id, featured_categories, is_active)
|
||||
VALUES ($1, $2, $3)
|
||||
ON CONFLICT (agent_id) DO UPDATE
|
||||
SET featured_categories = $2, is_active = $3
|
||||
RETURNING agent_id, featured_categories, is_active
|
||||
`
|
||||
args = []interface{}{agentID, featuredCategories, isActive}
|
||||
} else {
|
||||
// Unset the agent as featured
|
||||
query = `
|
||||
DELETE FROM "FeaturedAgent"
|
||||
WHERE agent_id = $1
|
||||
RETURNING agent_id, featured_categories, is_active
|
||||
`
|
||||
args = []interface{}{agentID}
|
||||
}
|
||||
|
||||
var featuredAgent models.FeaturedAgent
|
||||
err = db.QueryRow(ctx, query, args...).Scan(
|
||||
&featuredAgent.AgentID,
|
||||
&featuredAgent.FeaturedCategories,
|
||||
&featuredAgent.IsActive,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows && !isActive {
|
||||
logger.Info("Agent was not featured, no action needed", zap.String("agentID", agentID))
|
||||
return nil, nil
|
||||
}
|
||||
logger.Error("Failed to set agent featured status", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to set agent featured status: %w", err)
|
||||
}
|
||||
|
||||
if isActive {
|
||||
logger.Info("Agent set as featured successfully", zap.String("agentID", agentID))
|
||||
} else {
|
||||
logger.Info("Agent unset as featured successfully", zap.String("agentID", agentID))
|
||||
}
|
||||
return &featuredAgent, nil
|
||||
}
|
||||
|
||||
func GetAgentFeatured(ctx context.Context, db *pgxpool.Pool, agentID string) (*models.FeaturedAgent, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetAgentFeatured"))
|
||||
logger.Info("Getting featured agent", zap.String("agentID", agentID))
|
||||
|
||||
query := `
|
||||
SELECT agent_id, featured_categories, is_active
|
||||
FROM "FeaturedAgent"
|
||||
WHERE agent_id = $1
|
||||
`
|
||||
|
||||
var featuredAgent models.FeaturedAgent
|
||||
err := db.QueryRow(ctx, query, agentID).Scan(
|
||||
&featuredAgent.AgentID,
|
||||
&featuredAgent.FeaturedCategories,
|
||||
&featuredAgent.IsActive,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to get featured agent", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to get featured agent: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Featured agent retrieved successfully", zap.String("agentID", agentID))
|
||||
return &featuredAgent, nil
|
||||
}
|
||||
|
||||
func RemoveFeaturedCategory(ctx context.Context, db *pgxpool.Pool, agentID string, category string) (*models.FeaturedAgent, error) {
|
||||
logger := zap.L().With(zap.String("function", "RemoveFeaturedCategory"))
|
||||
logger.Info("Removing featured category", zap.String("agentID", agentID), zap.String("category", category))
|
||||
|
||||
query := `
|
||||
UPDATE "FeaturedAgent"
|
||||
SET featured_categories = array_remove(featured_categories, $1)
|
||||
WHERE agent_id = $2
|
||||
RETURNING agent_id, featured_categories, is_active
|
||||
`
|
||||
|
||||
var featuredAgent models.FeaturedAgent
|
||||
err := db.QueryRow(ctx, query, category, agentID).Scan(
|
||||
&featuredAgent.AgentID,
|
||||
&featuredAgent.FeaturedCategories,
|
||||
&featuredAgent.IsActive,
|
||||
)
|
||||
|
||||
if err == pgx.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Failed to remove featured category", zap.Error(err))
|
||||
return nil, fmt.Errorf("failed to remove featured category: %w", err)
|
||||
}
|
||||
|
||||
logger.Info("Featured category removed successfully", zap.String("agentID", agentID), zap.String("category", category))
|
||||
return &featuredAgent, nil
|
||||
}
|
||||
|
||||
func GetNotFeaturedAgents(ctx context.Context, db *pgxpool.Pool, page, pageSize int) ([]models.Agent, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetNotFeaturedAgents"))
|
||||
logger.Info("Getting not featured agents", zap.Int("page", page), zap.Int("pageSize", pageSize))
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query := `
|
||||
SELECT a.id, a.name, a.description, a.author, a.keywords, a.categories, a.graph
|
||||
FROM "Agents" a
|
||||
LEFT JOIN "FeaturedAgent" fa ON a.id = fa.agent_id
|
||||
WHERE (fa.agent_id IS NULL OR fa.featured_categories = '{}')
|
||||
AND a.submission_status = 'APPROVED'
|
||||
ORDER BY a.created_at DESC
|
||||
LIMIT $1 OFFSET $2
|
||||
`
|
||||
|
||||
rows, err := db.Query(ctx, query, pageSize, offset)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query not featured agents", zap.Error(err))
|
||||
return nil, 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 not featured agent row", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
agents = append(agents, agent)
|
||||
}
|
||||
|
||||
logger.Info("Not featured agents retrieved", zap.Int("count", len(agents)))
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
func GetAgentSubmissions(ctx context.Context, db *pgxpool.Pool, page, pageSize int, name, keyword, category *string, sortBy, sortOrder string) ([]models.AgentWithMetadata, int, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetAgentSubmissions"))
|
||||
logger.Info("Getting agent submissions", zap.Int("page", page), zap.Int("pageSize", pageSize))
|
||||
|
||||
offset := (page - 1) * pageSize
|
||||
|
||||
query := `
|
||||
SELECT a.id, a.name, a.description, a.author, a.keywords, a.categories, a.graph, a.created_at, a.updated_at, a.version, a.submission_status, a.submission_review_date, a.submission_review_comments
|
||||
FROM "Agents" a
|
||||
WHERE a.submission_status = 'PENDING'
|
||||
`
|
||||
|
||||
args := []interface{}{}
|
||||
argCount := 1
|
||||
|
||||
if name != nil {
|
||||
query += fmt.Sprintf(" AND a.name ILIKE $%d", argCount)
|
||||
args = append(args, "%"+*name+"%")
|
||||
argCount++
|
||||
}
|
||||
|
||||
if keyword != nil {
|
||||
query += fmt.Sprintf(" AND $%d = ANY(a.keywords)", argCount)
|
||||
args = append(args, *keyword)
|
||||
argCount++
|
||||
}
|
||||
|
||||
if category != nil {
|
||||
query += fmt.Sprintf(" AND $%d = ANY(a.categories)", argCount)
|
||||
args = append(args, *category)
|
||||
argCount++
|
||||
}
|
||||
|
||||
// Add sorting
|
||||
query += fmt.Sprintf(" ORDER BY a.%s %s", sortBy, sortOrder)
|
||||
|
||||
// Add pagination
|
||||
query += fmt.Sprintf(" LIMIT $%d OFFSET $%d", argCount, argCount+1)
|
||||
args = append(args, pageSize, offset)
|
||||
|
||||
rows, err := db.Query(ctx, query, args...)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query agent submissions", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var agents []models.AgentWithMetadata
|
||||
for rows.Next() {
|
||||
var agent models.AgentWithMetadata
|
||||
err := rows.Scan(
|
||||
&agent.ID,
|
||||
&agent.Name,
|
||||
&agent.Description,
|
||||
&agent.Author,
|
||||
&agent.Keywords,
|
||||
&agent.Categories,
|
||||
&agent.Graph,
|
||||
&agent.CreatedAt,
|
||||
&agent.UpdatedAt,
|
||||
&agent.Version,
|
||||
&agent.SubmissionStatus,
|
||||
&agent.SubmissionReviewDate,
|
||||
&agent.SubmissionReviewComments,
|
||||
)
|
||||
if err != nil {
|
||||
logger.Error("Failed to scan agent submission row", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
agents = append(agents, agent)
|
||||
}
|
||||
|
||||
// Get total count
|
||||
countQuery := `SELECT COUNT(*) FROM "Agents" WHERE submission_status = 'PENDING'`
|
||||
var totalCount int
|
||||
err = db.QueryRow(ctx, countQuery).Scan(&totalCount)
|
||||
if err != nil {
|
||||
logger.Error("Failed to get total count of agent submissions", zap.Error(err))
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
logger.Info("Agent submissions retrieved", zap.Int("count", len(agents)))
|
||||
return agents, totalCount, nil
|
||||
}
|
||||
|
||||
func ReviewSubmission(ctx context.Context, db *pgxpool.Pool, agentID string, version int, status models.SubmissionStatus, comments *string) (*models.AgentWithMetadata, error) {
|
||||
logger := zap.L().With(zap.String("function", "ReviewSubmission"))
|
||||
logger.Info("Reviewing agent submission", zap.String("agentID", agentID), zap.Int("version", version))
|
||||
|
||||
query := `
|
||||
UPDATE "Agents"
|
||||
SET submission_status = $1,
|
||||
submission_review_date = NOW(),
|
||||
submission_review_comments = $2
|
||||
WHERE id = $3 AND version = $4
|
||||
RETURNING id, name, description, author, keywords, categories, graph, created_at, updated_at, version, submission_status, submission_review_date, submission_review_comments
|
||||
`
|
||||
|
||||
var agent models.AgentWithMetadata
|
||||
err := db.QueryRow(ctx, query, status, comments, agentID, version).Scan(
|
||||
&agent.ID,
|
||||
&agent.Name,
|
||||
&agent.Description,
|
||||
&agent.Author,
|
||||
&agent.Keywords,
|
||||
&agent.Categories,
|
||||
&agent.Graph,
|
||||
&agent.CreatedAt,
|
||||
&agent.UpdatedAt,
|
||||
&agent.Version,
|
||||
&agent.SubmissionStatus,
|
||||
&agent.SubmissionReviewDate,
|
||||
&agent.SubmissionReviewComments,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
if err == pgx.ErrNoRows {
|
||||
logger.Error("Agent submission not found", zap.String("agentID", agentID), zap.Int("version", version))
|
||||
return nil, fmt.Errorf("agent submission not found")
|
||||
}
|
||||
logger.Error("Failed to review agent submission", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logger.Info("Agent submission reviewed successfully", zap.String("agentID", agentID), zap.Int("version", version))
|
||||
return &agent, nil
|
||||
}
|
||||
|
||||
func GetAllCategories(ctx context.Context, db *pgxpool.Pool) ([]string, error) {
|
||||
logger := zap.L().With(zap.String("function", "GetAllCategories"))
|
||||
logger.Info("Getting all categories")
|
||||
|
||||
query := `
|
||||
SELECT DISTINCT unnest(categories) AS category
|
||||
FROM "Agents"
|
||||
ORDER BY category
|
||||
`
|
||||
|
||||
rows, err := db.Query(ctx, query)
|
||||
if err != nil {
|
||||
logger.Error("Failed to query categories", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var categories []string
|
||||
for rows.Next() {
|
||||
var category string
|
||||
err := rows.Scan(&category)
|
||||
if err != nil {
|
||||
logger.Error("Failed to scan category row", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
categories = append(categories, category)
|
||||
}
|
||||
|
||||
logger.Info("Categories retrieved", zap.Int("count", len(categories)))
|
||||
return categories, nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
-- Drop foreign key constraints
|
||||
ALTER TABLE "AnalyticsTracker" DROP CONSTRAINT IF EXISTS "AnalyticsTracker_agentId_fkey";
|
||||
ALTER TABLE "InstallTracker" DROP CONSTRAINT IF EXISTS "InstallTracker_marketplaceAgentId_fkey";
|
||||
ALTER TABLE "FeaturedAgent" DROP CONSTRAINT IF EXISTS "FeaturedAgent_agentId_fkey";
|
||||
|
||||
-- Drop indexes
|
||||
DROP INDEX IF EXISTS "FeaturedAgent_agentId_key";
|
||||
DROP INDEX IF EXISTS "FeaturedAgent_id_key";
|
||||
DROP INDEX IF EXISTS "InstallTracker_marketplaceAgentId_installedAgentId_key";
|
||||
DROP INDEX IF EXISTS "AnalyticsTracker_agentId_key";
|
||||
DROP INDEX IF EXISTS "AnalyticsTracker_id_key";
|
||||
DROP INDEX IF EXISTS "Agents_id_key";
|
||||
|
||||
-- Drop tables
|
||||
DROP TABLE IF EXISTS "FeaturedAgent";
|
||||
DROP TABLE IF EXISTS "InstallTracker";
|
||||
DROP TABLE IF EXISTS "AnalyticsTracker";
|
||||
DROP TABLE IF EXISTS "Agents";
|
||||
|
||||
-- Drop enums
|
||||
DROP TYPE IF EXISTS "InstallationLocation";
|
||||
DROP TYPE IF EXISTS "SubmissionStatus";
|
||||
@@ -0,0 +1,86 @@
|
||||
-- CreateEnum
|
||||
CREATE TYPE "SubmissionStatus" AS ENUM ('PENDING', 'APPROVED', 'REJECTED');
|
||||
|
||||
-- CreateEnum
|
||||
CREATE TYPE "InstallationLocation" AS ENUM ('LOCAL', 'CLOUD');
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "Agents" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"version" INTEGER NOT NULL DEFAULT 1,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"submissionDate" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"submissionReviewDate" TIMESTAMP(3),
|
||||
"submissionStatus" "SubmissionStatus" NOT NULL DEFAULT 'PENDING',
|
||||
"submissionReviewComments" TEXT,
|
||||
"name" TEXT,
|
||||
"description" TEXT,
|
||||
"author" TEXT,
|
||||
"keywords" TEXT[],
|
||||
"categories" TEXT[],
|
||||
"search" tsvector DEFAULT ''::tsvector,
|
||||
"graph" JSONB NOT NULL,
|
||||
|
||||
CONSTRAINT "Agents_pkey" PRIMARY KEY ("id","version")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "AnalyticsTracker" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"agentId" UUID NOT NULL,
|
||||
"views" INTEGER NOT NULL,
|
||||
"downloads" INTEGER NOT NULL,
|
||||
|
||||
CONSTRAINT "AnalyticsTracker_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "InstallTracker" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"marketplaceAgentId" UUID NOT NULL,
|
||||
"installedAgentId" UUID NOT NULL,
|
||||
"installationLocation" "InstallationLocation" NOT NULL,
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "InstallTracker_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateTable
|
||||
CREATE TABLE "FeaturedAgent" (
|
||||
"id" UUID NOT NULL DEFAULT gen_random_uuid(),
|
||||
"agentId" UUID NOT NULL,
|
||||
"isActive" BOOLEAN NOT NULL DEFAULT false,
|
||||
"featuredCategories" TEXT[],
|
||||
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"updatedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
|
||||
CONSTRAINT "FeaturedAgent_pkey" PRIMARY KEY ("id")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Agents_id_key" ON "Agents"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AnalyticsTracker_id_key" ON "AnalyticsTracker"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "AnalyticsTracker_agentId_key" ON "AnalyticsTracker"("agentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "InstallTracker_marketplaceAgentId_installedAgentId_key" ON "InstallTracker"("marketplaceAgentId", "installedAgentId");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FeaturedAgent_id_key" ON "FeaturedAgent"("id");
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "FeaturedAgent_agentId_key" ON "FeaturedAgent"("agentId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "AnalyticsTracker" ADD CONSTRAINT "AnalyticsTracker_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "InstallTracker" ADD CONSTRAINT "InstallTracker_marketplaceAgentId_fkey" FOREIGN KEY ("marketplaceAgentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "FeaturedAgent" ADD CONSTRAINT "FeaturedAgent_agentId_fkey" FOREIGN KEY ("agentId") REFERENCES "Agents"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||
@@ -0,0 +1,11 @@
|
||||
-- Remove sample data from FeaturedAgent table
|
||||
DELETE FROM "FeaturedAgent" WHERE "agentId" IN ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 'eaa773b1-5efa-485f-b2f0-2e05bae6d297');
|
||||
|
||||
-- Remove sample data from InstallTracker table
|
||||
DELETE FROM "InstallTracker" WHERE "marketplaceAgentId" IN ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 'eaa773b1-5efa-485f-b2f0-2e05bae6d297', 'b47e40a7-ad5f-4b29-9eac-abd5b728f19a', 'a4d3598f-6180-4e6d-96bf-6e15c3de05a9', '9f332ff3-4c74-4f5b-9838-65938a06711f');
|
||||
|
||||
-- Remove sample data from AnalyticsTracker table
|
||||
DELETE FROM "AnalyticsTracker" WHERE "agentId" IN ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 'eaa773b1-5efa-485f-b2f0-2e05bae6d297', 'b47e40a7-ad5f-4b29-9eac-abd5b728f19a', 'a4d3598f-6180-4e6d-96bf-6e15c3de05a9', '9f332ff3-4c74-4f5b-9838-65938a06711f');
|
||||
|
||||
-- Remove sample data from Agents table
|
||||
DELETE FROM "Agents" WHERE "id" IN ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 'eaa773b1-5efa-485f-b2f0-2e05bae6d297', 'b47e40a7-ad5f-4b29-9eac-abd5b728f19a', 'a4d3598f-6180-4e6d-96bf-6e15c3de05a9', '9f332ff3-4c74-4f5b-9838-65938a06711f');
|
||||
@@ -0,0 +1,86 @@
|
||||
-- Sample data for Agents table (10 agents)
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph", "submissionStatus")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', 'AI Recruiter', 'An AI-powered tool that assists HR teams with talent acquisition, screening, and shortlisting.', 'Author1', ARRAY['recruitment', 'HR'], ARRAY['human resources', 'talent management'], '{"key": "value"}', 'APPROVED');
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph", "submissionStatus")
|
||||
VALUES ('3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 'Customer Service Bot', 'A chatbot that provides 24/7 support and assistance to customers, handling common inquiries and issues.', 'Author2', ARRAY['customer service', 'chatbot'], ARRAY['customer experience', 'artificial intelligence'], '{"key": "value"}', 'APPROVED');
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph", "submissionStatus")
|
||||
VALUES ('eaa773b1-5efa-485f-b2f0-2e05bae6d297', 'Financial Advisor', 'An AI-powered financial advisor that offers personalized investment recommendations and portfolio management.', 'Author3', ARRAY['finance', 'investment'], ARRAY['wealth management', 'artificial intelligence'], '{"key": "value"}', 'APPROVED');
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph", "submissionStatus")
|
||||
VALUES ('b47e40a7-ad5f-4b29-9eac-abd5b728f19a', 'AI Content Writer', 'An AI-powered tool that generates high-quality content for websites, blogs, and marketing materials.', 'Author4', ARRAY['content writing', 'AI'], ARRAY['marketing', 'artificial intelligence'], '{"key": "value"}', 'APPROVED');
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph", "submissionStatus")
|
||||
VALUES ('a4d3598f-6180-4e6d-96bf-6e15c3de05a9', 'AI Image Generator', 'An AI-powered tool that creates realistic images based on text prompts.', 'Author5', ARRAY['image generation', 'AI'], ARRAY['marketing', 'artificial intelligence'], '{"key": "value"}', 'APPROVED');
|
||||
|
||||
INSERT INTO "Agents" ("id", "name", "description", "author", "keywords", "categories", "graph")
|
||||
VALUES ('9f332ff3-4c74-4f5b-9838-65938a06711f', 'AI Video Editor', 'An AI-powered tool that edits and enhances videos with advanced AI algorithms.', 'Author6', ARRAY['video editing', 'AI'], ARRAY['marketing', 'artificial intelligence'], '{"key": "value"}');
|
||||
|
||||
-- Sample data for AnalyticsTracker table (10 agents)
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', 200, 80);
|
||||
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', 150, 60);
|
||||
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('eaa773b1-5efa-485f-b2f0-2e05bae6d297', 100, 40);
|
||||
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('b47e40a7-ad5f-4b29-9eac-abd5b728f19a', 120, 50);
|
||||
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('a4d3598f-6180-4e6d-96bf-6e15c3de05a9', 130, 55);
|
||||
|
||||
INSERT INTO "AnalyticsTracker" ("agentId", "views", "downloads")
|
||||
VALUES ('9f332ff3-4c74-4f5b-9838-65938a06711f', 140, 60);
|
||||
|
||||
|
||||
-- Sample data for InstallTracker table (10 agents)
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac11', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac12', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac13', 'LOCAL');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac14', 'LOCAL');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac15', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac16', 'LOCAL');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', '244f809e-1eee-4a36-a49b-ac2db008ac17', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', '244f809e-1eee-4a36-a49b-ac2db008ac18', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('eaa773b1-5efa-485f-b2f0-2e05bae6d297', '244f809e-1eee-4a36-a49b-ac2db008ac19', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('b47e40a7-ad5f-4b29-9eac-abd5b728f19a', '244f809e-1eee-4a36-a49b-ac2db008ac20', 'LOCAL');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('a4d3598f-6180-4e6d-96bf-6e15c3de05a9', '244f809e-1eee-4a36-a49b-ac2db008ac22', 'CLOUD');
|
||||
|
||||
INSERT INTO "InstallTracker" ("marketplaceAgentId", "installedAgentId", "installationLocation")
|
||||
VALUES ('9f332ff3-4c74-4f5b-9838-65938a06711f', '244f809e-1eee-4a36-a49b-ac2db008ac21', 'CLOUD');
|
||||
|
||||
-- Sample data for FeaturedAgent table (3 featured agents)
|
||||
INSERT INTO "FeaturedAgent" ("agentId", "isActive", "featuredCategories")
|
||||
VALUES ('b609e5fd-c992-4be9-b68f-afc1980f93c0', true, ARRAY['human resources', 'talent management']);
|
||||
|
||||
INSERT INTO "FeaturedAgent" ("agentId", "isActive", "featuredCategories")
|
||||
VALUES ('3b6d8f75-99d3-41e3-b484-4b2c5f835f5b', true, ARRAY['customer experience', 'artificial intelligence']);
|
||||
|
||||
INSERT INTO "FeaturedAgent" ("agentId", "isActive", "featuredCategories")
|
||||
VALUES ('eaa773b1-5efa-485f-b2f0-2e05bae6d297', true, ARRAY['wealth management', 'artificial intelligence']);
|
||||
47
rnd/rest-api-go/docker-compose.yml
Normal file
47
rnd/rest-api-go/docker-compose.yml
Normal file
@@ -0,0 +1,47 @@
|
||||
services:
|
||||
postgres:
|
||||
image: ankane/pgvector:latest
|
||||
environment:
|
||||
- POSTGRES_USER=agpt_user
|
||||
- POSTGRES_PASSWORD=pass123
|
||||
- POSTGRES_DB=agpt_marketplace
|
||||
healthcheck:
|
||||
test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 5
|
||||
ports:
|
||||
- "5433:5432"
|
||||
|
||||
market:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
ports:
|
||||
- "8015:8015"
|
||||
depends_on:
|
||||
postgres:
|
||||
condition: service_healthy
|
||||
healthcheck:
|
||||
test: ["CMD", "curl", "-f", "http://localhost:8015/metrics"]
|
||||
interval: 30s
|
||||
timeout: 10s
|
||||
retries: 3
|
||||
|
||||
prometheus:
|
||||
image: prom/prometheus:latest
|
||||
volumes:
|
||||
- ./prometheus.yml:/etc/prometheus/prometheus.yml
|
||||
command:
|
||||
- '--config.file=/etc/prometheus/prometheus.yml'
|
||||
ports:
|
||||
- "9090:9090"
|
||||
depends_on:
|
||||
- market
|
||||
|
||||
grafana:
|
||||
image: grafana/grafana:latest
|
||||
ports:
|
||||
- "9091:3000"
|
||||
depends_on:
|
||||
- prometheus
|
||||
631
rnd/rest-api-go/docs/docs.go
Normal file
631
rnd/rest-api-go/docs/docs.go
Normal file
@@ -0,0 +1,631 @@
|
||||
// Package docs Code generated by swaggo/swag. DO NOT EDIT
|
||||
package docs
|
||||
|
||||
import "github.com/swaggo/swag"
|
||||
|
||||
const docTemplate = `{
|
||||
"schemes": {{ marshal .Schemes }},
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"description": "{{escape .Description}}",
|
||||
"title": "{{.Title}}",
|
||||
"contact": {},
|
||||
"version": "{{.Version}}"
|
||||
},
|
||||
"host": "{{.Host}}",
|
||||
"basePath": "{{.BasePath}}",
|
||||
"paths": {
|
||||
"/agent/featured/{agent_id}": {
|
||||
"get": {
|
||||
"description": "Get the featured agent for a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Set an agent as featured in a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Set Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Unset an agent as featured in a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Unset Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/not-featured": {
|
||||
"get": {
|
||||
"description": "Get a list of agents that are not featured",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Not Featured Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/submissions": {
|
||||
"get": {
|
||||
"description": "Get a list of agent submissions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Agent Submissions",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/submissions/{agent_id}": {
|
||||
"post": {
|
||||
"description": "Review an agent submission",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Review Submission",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Status",
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents": {
|
||||
"get": {
|
||||
"description": "Get Agents",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent Name",
|
||||
"name": "name",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Keywords",
|
||||
"name": "keywords",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Categories",
|
||||
"name": "categories",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Submit an agent for review",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Submit Agent",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Agent details",
|
||||
"name": "agent",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.AddAgentRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/featured": {
|
||||
"get": {
|
||||
"description": "Get featured agents based on category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Featured Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/search": {
|
||||
"get": {
|
||||
"description": "Search for agents based on query and categories",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Search Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Categories",
|
||||
"name": "categories",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort by",
|
||||
"name": "sortBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort order",
|
||||
"name": "sortOrder",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/{id}": {
|
||||
"get": {
|
||||
"description": "Get details of a specific agent by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Agent Details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/{id}/download": {
|
||||
"get": {
|
||||
"description": "Download an agent file by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Download Agent File",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/categories": {
|
||||
"get": {
|
||||
"description": "Get a list of categories",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Categories",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"models.AddAgentRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"$ref": "#/definitions/models.Graph"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Agent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"graph": {
|
||||
"$ref": "#/definitions/models.Graph"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Graph": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}`
|
||||
|
||||
// SwaggerInfo holds exported Swagger Info so clients can modify it
|
||||
var SwaggerInfo = &swag.Spec{
|
||||
Version: "",
|
||||
Host: "",
|
||||
BasePath: "",
|
||||
Schemes: []string{},
|
||||
Title: "",
|
||||
Description: "",
|
||||
InfoInstanceName: "swagger",
|
||||
SwaggerTemplate: docTemplate,
|
||||
LeftDelim: "{{",
|
||||
RightDelim: "}}",
|
||||
}
|
||||
|
||||
func init() {
|
||||
swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo)
|
||||
}
|
||||
602
rnd/rest-api-go/docs/swagger.json
Normal file
602
rnd/rest-api-go/docs/swagger.json
Normal file
@@ -0,0 +1,602 @@
|
||||
{
|
||||
"swagger": "2.0",
|
||||
"info": {
|
||||
"contact": {}
|
||||
},
|
||||
"paths": {
|
||||
"/agent/featured/{agent_id}": {
|
||||
"get": {
|
||||
"description": "Get the featured agent for a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Set an agent as featured in a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Set Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"delete": {
|
||||
"description": "Unset an agent as featured in a specific category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Unset Agent Featured",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/not-featured": {
|
||||
"get": {
|
||||
"description": "Get a list of agents that are not featured",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Not Featured Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/submissions": {
|
||||
"get": {
|
||||
"description": "Get a list of agent submissions",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Agent Submissions",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "page_size",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agent/submissions/{agent_id}": {
|
||||
"post": {
|
||||
"description": "Review an agent submission",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Review Submission",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "agent_id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Status",
|
||||
"name": "status",
|
||||
"in": "query",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents": {
|
||||
"get": {
|
||||
"description": "Get Agents",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent Name",
|
||||
"name": "name",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Keywords",
|
||||
"name": "keywords",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Categories",
|
||||
"name": "categories",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"post": {
|
||||
"description": "Submit an agent for review",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Submit Agent",
|
||||
"parameters": [
|
||||
{
|
||||
"description": "Agent details",
|
||||
"name": "agent",
|
||||
"in": "body",
|
||||
"required": true,
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.AddAgentRequest"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/featured": {
|
||||
"get": {
|
||||
"description": "Get featured agents based on category",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Featured Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Category",
|
||||
"name": "category",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/search": {
|
||||
"get": {
|
||||
"description": "Search for agents based on query and categories",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Search Agents",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Search query",
|
||||
"name": "q",
|
||||
"in": "query",
|
||||
"required": true
|
||||
},
|
||||
{
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"collectionFormat": "csv",
|
||||
"description": "Categories",
|
||||
"name": "categories",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page number",
|
||||
"name": "page",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"description": "Page size",
|
||||
"name": "pageSize",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort by",
|
||||
"name": "sortBy",
|
||||
"in": "query"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Sort order",
|
||||
"name": "sortOrder",
|
||||
"in": "query"
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/{id}": {
|
||||
"get": {
|
||||
"description": "Get details of a specific agent by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Get Agent Details",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/agents/{id}/download": {
|
||||
"get": {
|
||||
"description": "Download an agent file by ID",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Agents"
|
||||
],
|
||||
"summary": "Download Agent File",
|
||||
"parameters": [
|
||||
{
|
||||
"type": "string",
|
||||
"description": "Agent ID",
|
||||
"name": "id",
|
||||
"in": "path",
|
||||
"required": true
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"$ref": "#/definitions/models.Agent"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"/categories": {
|
||||
"get": {
|
||||
"description": "Get a list of categories",
|
||||
"consumes": [
|
||||
"application/json"
|
||||
],
|
||||
"produces": [
|
||||
"application/json"
|
||||
],
|
||||
"tags": [
|
||||
"Admin"
|
||||
],
|
||||
"summary": "Get Categories",
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "OK",
|
||||
"schema": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"models.AddAgentRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"graph": {
|
||||
"$ref": "#/definitions/models.Graph"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Agent": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"author": {
|
||||
"type": "string"
|
||||
},
|
||||
"categories": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"graph": {
|
||||
"$ref": "#/definitions/models.Graph"
|
||||
},
|
||||
"id": {
|
||||
"type": "string"
|
||||
},
|
||||
"keywords": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"models.Graph": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"description": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
395
rnd/rest-api-go/docs/swagger.yaml
Normal file
395
rnd/rest-api-go/docs/swagger.yaml
Normal file
@@ -0,0 +1,395 @@
|
||||
definitions:
|
||||
models.AddAgentRequest:
|
||||
properties:
|
||||
author:
|
||||
type: string
|
||||
categories:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
graph:
|
||||
$ref: '#/definitions/models.Graph'
|
||||
keywords:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
type: object
|
||||
models.Agent:
|
||||
properties:
|
||||
author:
|
||||
type: string
|
||||
categories:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
description:
|
||||
type: string
|
||||
graph:
|
||||
$ref: '#/definitions/models.Graph'
|
||||
id:
|
||||
type: string
|
||||
keywords:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
models.Graph:
|
||||
properties:
|
||||
description:
|
||||
type: string
|
||||
name:
|
||||
type: string
|
||||
type: object
|
||||
info:
|
||||
contact: {}
|
||||
paths:
|
||||
/agent/featured/{agent_id}:
|
||||
delete:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Unset an agent as featured in a specific category
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: agent_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Category
|
||||
in: query
|
||||
name: category
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Unset Agent Featured
|
||||
tags:
|
||||
- Admin
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get the featured agent for a specific category
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: agent_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Category
|
||||
in: query
|
||||
name: category
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Get Agent Featured
|
||||
tags:
|
||||
- Admin
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Set an agent as featured in a specific category
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: agent_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Category
|
||||
in: query
|
||||
name: category
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Set Agent Featured
|
||||
tags:
|
||||
- Admin
|
||||
/agent/not-featured:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of agents that are not featured
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Get Not Featured Agents
|
||||
tags:
|
||||
- Admin
|
||||
/agent/submissions:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of agent submissions
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: page_size
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Get Agent Submissions
|
||||
tags:
|
||||
- Admin
|
||||
/agent/submissions/{agent_id}:
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Review an agent submission
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: agent_id
|
||||
required: true
|
||||
type: string
|
||||
- description: Status
|
||||
in: query
|
||||
name: status
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Review Submission
|
||||
tags:
|
||||
- Admin
|
||||
/agents:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get Agents
|
||||
parameters:
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: pageSize
|
||||
type: integer
|
||||
- description: Agent Name
|
||||
in: query
|
||||
name: name
|
||||
type: string
|
||||
- collectionFormat: csv
|
||||
description: Keywords
|
||||
in: query
|
||||
items:
|
||||
type: string
|
||||
name: keywords
|
||||
type: array
|
||||
- collectionFormat: csv
|
||||
description: Categories
|
||||
in: query
|
||||
items:
|
||||
type: string
|
||||
name: categories
|
||||
type: array
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
type: array
|
||||
summary: Get Agents
|
||||
tags:
|
||||
- Agents
|
||||
post:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Submit an agent for review
|
||||
parameters:
|
||||
- description: Agent details
|
||||
in: body
|
||||
name: agent
|
||||
required: true
|
||||
schema:
|
||||
$ref: '#/definitions/models.AddAgentRequest'
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Submit Agent
|
||||
tags:
|
||||
- Agents
|
||||
/agents/{id}:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get details of a specific agent by ID
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Get Agent Details
|
||||
tags:
|
||||
- Agents
|
||||
/agents/{id}/download:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Download an agent file by ID
|
||||
parameters:
|
||||
- description: Agent ID
|
||||
in: path
|
||||
name: id
|
||||
required: true
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
summary: Download Agent File
|
||||
tags:
|
||||
- Agents
|
||||
/agents/featured:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get featured agents based on category
|
||||
parameters:
|
||||
- description: Category
|
||||
in: query
|
||||
name: category
|
||||
type: string
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: pageSize
|
||||
type: integer
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
type: array
|
||||
summary: Get Featured Agents
|
||||
tags:
|
||||
- Agents
|
||||
/agents/search:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Search for agents based on query and categories
|
||||
parameters:
|
||||
- description: Search query
|
||||
in: query
|
||||
name: q
|
||||
required: true
|
||||
type: string
|
||||
- collectionFormat: csv
|
||||
description: Categories
|
||||
in: query
|
||||
items:
|
||||
type: string
|
||||
name: categories
|
||||
type: array
|
||||
- description: Page number
|
||||
in: query
|
||||
name: page
|
||||
type: integer
|
||||
- description: Page size
|
||||
in: query
|
||||
name: pageSize
|
||||
type: integer
|
||||
- description: Sort by
|
||||
in: query
|
||||
name: sortBy
|
||||
type: string
|
||||
- description: Sort order
|
||||
in: query
|
||||
name: sortOrder
|
||||
type: string
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
$ref: '#/definitions/models.Agent'
|
||||
type: array
|
||||
summary: Search Agents
|
||||
tags:
|
||||
- Agents
|
||||
/categories:
|
||||
get:
|
||||
consumes:
|
||||
- application/json
|
||||
description: Get a list of categories
|
||||
produces:
|
||||
- application/json
|
||||
responses:
|
||||
"200":
|
||||
description: OK
|
||||
schema:
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
summary: Get Categories
|
||||
tags:
|
||||
- Admin
|
||||
swagger: "2.0"
|
||||
84
rnd/rest-api-go/go.mod
Normal file
84
rnd/rest-api-go/go.mod
Normal file
@@ -0,0 +1,84 @@
|
||||
module github.com/swiftyos/market
|
||||
|
||||
go 1.23.1
|
||||
|
||||
require (
|
||||
github.com/Depado/ginprom v1.8.1
|
||||
github.com/gin-contrib/cors v1.7.2
|
||||
github.com/gin-contrib/gzip v1.0.1
|
||||
github.com/gin-contrib/zap v1.1.4
|
||||
github.com/gin-gonic/gin v1.10.0
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0
|
||||
github.com/google/uuid v1.4.0
|
||||
github.com/jackc/pgx/v5 v5.7.1
|
||||
github.com/spf13/viper v1.19.0
|
||||
github.com/stretchr/testify v1.9.0
|
||||
github.com/swaggo/files v1.0.1
|
||||
github.com/swaggo/gin-swagger v1.6.0
|
||||
github.com/swaggo/swag v1.16.3
|
||||
go.uber.org/zap v1.27.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/KyleBanks/depth v1.2.1 // indirect
|
||||
github.com/beorn7/perks v1.0.1 // indirect
|
||||
github.com/bytedance/sonic v1.12.2 // indirect
|
||||
github.com/bytedance/sonic/loader v0.2.0 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.2.0 // indirect
|
||||
github.com/cloudwego/base64x v0.1.4 // indirect
|
||||
github.com/cloudwego/iasm v0.2.0 // indirect
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
|
||||
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 // indirect
|
||||
github.com/gin-contrib/sse v0.1.0 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.0 // indirect
|
||||
github.com/go-playground/locales v0.14.1 // indirect
|
||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||
github.com/go-playground/validator/v10 v10.22.1 // indirect
|
||||
github.com/goccy/go-json v0.10.3 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/jackc/pgpassfile v1.0.0 // indirect
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect
|
||||
github.com/jackc/puddle/v2 v2.2.2 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 // indirect
|
||||
github.com/leodido/go-urn v1.4.0 // indirect
|
||||
github.com/magiconair/properties v1.8.7 // indirect
|
||||
github.com/mailru/easyjson v0.7.7 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/prometheus/client_golang v1.18.0 // indirect
|
||||
github.com/prometheus/client_model v0.5.0 // indirect
|
||||
github.com/prometheus/common v0.45.0 // indirect
|
||||
github.com/prometheus/procfs v0.12.0 // indirect
|
||||
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||
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/subosito/gotenv v1.6.0 // indirect
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/arch v0.10.0 // indirect
|
||||
golang.org/x/crypto v0.27.0 // indirect
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 // indirect
|
||||
golang.org/x/net v0.29.0 // indirect
|
||||
golang.org/x/sync v0.8.0 // indirect
|
||||
golang.org/x/sys v0.25.0 // indirect
|
||||
golang.org/x/text v0.18.0 // indirect
|
||||
golang.org/x/tools v0.25.0 // indirect
|
||||
google.golang.org/protobuf v1.34.2 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
217
rnd/rest-api-go/go.sum
Normal file
217
rnd/rest-api-go/go.sum
Normal file
@@ -0,0 +1,217 @@
|
||||
github.com/Depado/ginprom v1.8.1 h1:lrQTddbRqlHq1j6SpJDySDumJlR7FEybzdX0PS3HXPc=
|
||||
github.com/Depado/ginprom v1.8.1/go.mod h1:9Z+ahPJLSeMndDfnDTfiuBn2SKVAuL2yvihApWzof9A=
|
||||
github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc=
|
||||
github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE=
|
||||
github.com/appleboy/gofight/v2 v2.1.2 h1:VOy3jow4vIK8BRQJoC/I9muxyYlJ2yb9ht2hZoS3rf4=
|
||||
github.com/appleboy/gofight/v2 v2.1.2/go.mod h1:frW+U1QZEdDgixycTj4CygQ48yLTUhplt43+Wczp3rw=
|
||||
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
|
||||
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
|
||||
github.com/bytedance/sonic v1.12.2 h1:oaMFuRTpMHYLpCntGca65YWt5ny+wAceDERTkT2L9lg=
|
||||
github.com/bytedance/sonic v1.12.2/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk=
|
||||
github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM=
|
||||
github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU=
|
||||
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
|
||||
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||
github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y=
|
||||
github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w=
|
||||
github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg=
|
||||
github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4=
|
||||
github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4=
|
||||
github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw=
|
||||
github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E=
|
||||
github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE=
|
||||
github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4=
|
||||
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
|
||||
github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI=
|
||||
github.com/gin-contrib/zap v1.1.4 h1:xvxTybg6XBdNtcQLH3Tf0lFr4vhDkwzgLLrIGlNTqIo=
|
||||
github.com/gin-contrib/zap v1.1.4/go.mod h1:7lgEpe91kLbeJkwBTPgtVBy4zMa6oSBEcvj662diqKQ=
|
||||
github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU=
|
||||
github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
|
||||
github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ=
|
||||
github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY=
|
||||
github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ=
|
||||
github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4=
|
||||
github.com/go-openapi/spec v0.21.0 h1:LTVzPc3p/RzRnkQqLRndbAzjY0d0BCL72A6j3CdL9ZY=
|
||||
github.com/go-openapi/spec v0.21.0/go.mod h1:78u6VdPw81XU44qEWGhtr982gJ5BWg2c0I5XwVMotYk=
|
||||
github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE=
|
||||
github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ=
|
||||
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
|
||||
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
|
||||
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
|
||||
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
|
||||
github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY=
|
||||
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
|
||||
github.com/go-playground/validator/v10 v10.22.1 h1:40JcKH+bBNGFczGuoBYgX4I6m/i27HYW8P9FDk5PbgA=
|
||||
github.com/go-playground/validator/v10 v10.22.1/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM=
|
||||
github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA=
|
||||
github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg=
|
||||
github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
|
||||
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||
github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM=
|
||||
github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo=
|
||||
github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM=
|
||||
github.com/jackc/pgx/v5 v5.7.1 h1:x7SYsPBYDkHDksogeSmZZ5xzThcTgRz++I5E+ePFUcs=
|
||||
github.com/jackc/pgx/v5 v5.7.1/go.mod h1:e7O26IywZZ+naJtWWos6i6fvWK+29etgITqrqHLfoZA=
|
||||
github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo=
|
||||
github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4=
|
||||
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
|
||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
|
||||
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
|
||||
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
|
||||
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM=
|
||||
github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws=
|
||||
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
|
||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
|
||||
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
|
||||
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
|
||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0 h1:jWpvCLoY8Z/e3VKvlsiIGKtc+UG6U5vzxaoagmhXfyg=
|
||||
github.com/matttproud/golang_protobuf_extensions/v2 v2.0.0/go.mod h1:QUyp042oQthUoa9bqDv0ER0wrtXnBruoNd7aNjkbP+k=
|
||||
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg=
|
||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/prometheus/client_golang v1.18.0 h1:HzFfmkOzH5Q8L8G+kSJKUx5dtG87sewO+FoDDqP5Tbk=
|
||||
github.com/prometheus/client_golang v1.18.0/go.mod h1:T+GXkCk5wSJyOqMIzVgvvjFDlkOQntgjkJWKrN5txjA=
|
||||
github.com/prometheus/client_model v0.5.0 h1:VQw1hfvPvk3Uv6Qf29VrPF32JB6rtbgI6cYPYQjL0Qw=
|
||||
github.com/prometheus/client_model v0.5.0/go.mod h1:dTiFglRmd66nLR9Pv9f0mZi7B7fk5Pm3gvsjB5tr+kI=
|
||||
github.com/prometheus/common v0.45.0 h1:2BGz0eBc2hdMDLnO/8n0jeB3oPrt2D08CekT0lneoxM=
|
||||
github.com/prometheus/common v0.45.0/go.mod h1:YJmSTw9BoKxJplESWWxlbyttQR4uaEcGyv9MZjVOJsY=
|
||||
github.com/prometheus/procfs v0.12.0 h1:jluTpSng7V9hY0O2R9DzzJHYb2xULk9VTR1V1R/k6Bo=
|
||||
github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3cnaOZAZEfOo=
|
||||
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
|
||||
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
|
||||
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||
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/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=
|
||||
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/swaggo/files v1.0.1 h1:J1bVJ4XHZNq0I46UU90611i9/YzdrF7x92oX1ig5IdE=
|
||||
github.com/swaggo/files v1.0.1/go.mod h1:0qXmMNH6sXNf+73t65aKeB+ApmgxdnkQzVTAj2uaMUg=
|
||||
github.com/swaggo/gin-swagger v1.6.0 h1:y8sxvQ3E20/RCyrXeFfg60r6H0Z+SwpTjMYsMm+zy8M=
|
||||
github.com/swaggo/gin-swagger v1.6.0/go.mod h1:BG00cCEy294xtVpyIAHG6+e2Qzj/xKlRdOqDkvq0uzo=
|
||||
github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg=
|
||||
github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI=
|
||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||
github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE=
|
||||
github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
|
||||
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
golang.org/x/arch v0.10.0 h1:S3huipmSclq3PJMNe76NGwkBR504WFkQ5dhzWzP8ZW8=
|
||||
golang.org/x/arch v0.10.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||
golang.org/x/crypto v0.27.0 h1:GXm2NjJrPaiv/h1tb2UH8QfgC/hOf/+z0p6PT8o1w7A=
|
||||
golang.org/x/crypto v0.27.0/go.mod h1:1Xngt8kV6Dvbssa53Ziq6Eqn0HqbZi5Z6R0ZpwQzt70=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0 h1:e66Fs6Z+fZTbFBAxKfP3PALWBtpfqks2bwGcexMxgtk=
|
||||
golang.org/x/exp v0.0.0-20240909161429-701f63a606c0/go.mod h1:2TbTHSBQa924w8M6Xs1QcRcFwyucIwBGpK1p2f1YFFY=
|
||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
|
||||
golang.org/x/mod v0.21.0 h1:vvrHzRwRfVKSiLrG+d4FMl/Qi4ukBCE6kZlTUkDYRT0=
|
||||
golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||
golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
|
||||
golang.org/x/net v0.29.0 h1:5ORfpBpCs4HzDYoodCDBbwHzdR5UrLBZ3sOnUJmFoHo=
|
||||
golang.org/x/net v0.29.0/go.mod h1:gLkgy8jTGERgjzMic6DS9+SP0ajcu6Xu3Orq/SpETg0=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
|
||||
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.25.0 h1:r+8e+loiHxRqhXVl6ML1nO3l1+oFoWbnlu2Ehimmi34=
|
||||
golang.org/x/sys v0.25.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||
golang.org/x/text v0.18.0 h1:XvMDiNzPAl0jr17s6W9lcaIhGUfUORdGCNsuLmPG224=
|
||||
golang.org/x/text v0.18.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
|
||||
golang.org/x/tools v0.25.0 h1:oFU9pkj/iJgs+0DT+VMHrx+oBKs/LJMV+Uvg78sl+fE=
|
||||
golang.org/x/tools v0.25.0/go.mod h1:/vtpO8WL1N9cQC3FN5zPqb//fRXskFHbLKk4OW1Q7rg=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
google.golang.org/protobuf v1.34.2 h1:6xV6lTsCfpGD21XK49h7MhtcApnLqkfYgPcdHftf6hg=
|
||||
google.golang.org/protobuf v1.34.2/go.mod h1:qYOHts0dSfpeUzUFpOMr/WGzszTmLH+DiWniOlNbLDw=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
|
||||
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
|
||||
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50=
|
||||
282
rnd/rest-api-go/handlers/admin.go
Normal file
282
rnd/rest-api-go/handlers/admin.go
Normal file
@@ -0,0 +1,282 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/swiftyos/market/database"
|
||||
"github.com/swiftyos/market/models"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func requireAdminUser() gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "User not found in context"})
|
||||
return
|
||||
}
|
||||
|
||||
userModel, ok := user.(models.User)
|
||||
if !ok {
|
||||
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{"error": "Invalid user model"})
|
||||
return
|
||||
}
|
||||
|
||||
if userModel.Role != "admin" {
|
||||
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "Admin access required"})
|
||||
return
|
||||
}
|
||||
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
// @BasePath /api/v1/marketplace/admin
|
||||
|
||||
// CreateAgentEntry godoc
|
||||
// @Summary Create Agent Entry
|
||||
// @Description Create a new agent entry
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param request body models.AddAgentRequest true "Agent details"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agents [post]
|
||||
func CreateAgentEntry(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
var request models.AddAgentRequest
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := database.CreateAgentEntry(c.Request.Context(), db, models.Agent{
|
||||
Name: request.Graph.Name,
|
||||
Description: request.Graph.Description,
|
||||
Author: request.Author,
|
||||
Keywords: request.Keywords,
|
||||
Categories: request.Categories,
|
||||
Graph: request.Graph,
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
}
|
||||
|
||||
// SetAgentFeatured godoc
|
||||
// @Summary Set Agent Featured
|
||||
// @Description Set an agent as featured in a specific category
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param agent_id path string true "Agent ID"
|
||||
// @Param category query string false "Category"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/featured/{agent_id} [post]
|
||||
func SetAgentFeatured(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
categories := c.QueryArray("categories")
|
||||
if len(categories) == 0 {
|
||||
categories = []string{"featured"}
|
||||
}
|
||||
|
||||
featuredAgent, err := database.SetAgentFeatured(c.Request.Context(), db, agentID, true, categories)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, featuredAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// GetAgentFeatured godoc
|
||||
// @Summary Get Agent Featured
|
||||
// @Description Get the featured agent for a specific category
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param agent_id path string true "Agent ID"
|
||||
// @Param category query string false "Category"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/featured/{agent_id} [get]
|
||||
func GetAgentFeatured(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
|
||||
featuredAgent, err := database.GetAgentFeatured(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if featuredAgent == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "Featured agent not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, featuredAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// UnsetAgentFeatured godoc
|
||||
// @Summary Unset Agent Featured
|
||||
// @Description Unset an agent as featured in a specific category
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param agent_id path string true "Agent ID"
|
||||
// @Param category query string false "Category"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/featured/{agent_id} [delete]
|
||||
func UnsetAgentFeatured(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
agentID := c.Param("agent_id")
|
||||
category := c.DefaultQuery("category", "featured")
|
||||
|
||||
featuredAgent, err := database.RemoveFeaturedCategory(c.Request.Context(), db, agentID, category)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
if featuredAgent == nil {
|
||||
c.JSON(http.StatusNotFound, gin.H{"message": "Featured agent not found"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, featuredAgent)
|
||||
}
|
||||
}
|
||||
|
||||
// GetNotFeaturedAgents godoc
|
||||
// @Summary Get Not Featured Agents
|
||||
// @Description Get a list of agents that are not featured
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param page_size query int false "Page size"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/not-featured [get]
|
||||
func GetNotFeaturedAgents(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "10"))
|
||||
|
||||
agents, err := database.GetNotFeaturedAgents(c.Request.Context(), db, page, pageSize)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"agents": agents,
|
||||
"total_count": len(agents),
|
||||
"page": page,
|
||||
"page_size": pageSize,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// GetAgentSubmissions godoc
|
||||
// @Summary Get Agent Submissions
|
||||
// @Description Get a list of agent submissions
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param page_size query int false "Page size"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/submissions [get]
|
||||
func GetAgentSubmissions(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement GetAgentSubmissions
|
||||
c.JSON(http.StatusNotImplemented, gin.H{"message": "Not Implemented: GetAgentSubmissions"})
|
||||
}
|
||||
}
|
||||
|
||||
// ReviewSubmission godoc
|
||||
// @Summary Review Submission
|
||||
// @Description Review an agent submission
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param agent_id path string true "Agent ID"
|
||||
// @Param status query string true "Status"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agent/submissions/{agent_id} [post]
|
||||
func ReviewSubmission(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
requireAdminUser()(c)
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: Implement ReviewSubmission
|
||||
c.JSON(http.StatusNotImplemented, gin.H{"message": "Not Implemented: ReviewSubmission"})
|
||||
}
|
||||
}
|
||||
|
||||
// GetCategories godoc
|
||||
// @Summary Get Categories
|
||||
// @Description Get a list of categories
|
||||
// @Tags Admin
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Success 200 {array} string
|
||||
// @Router /categories [get]
|
||||
func GetCategories(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if c.IsAborted() {
|
||||
return
|
||||
}
|
||||
|
||||
categories, err := database.GetAllCategories(c.Request.Context(), db)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, categories)
|
||||
}
|
||||
}
|
||||
369
rnd/rest-api-go/handlers/agents.go
Normal file
369
rnd/rest-api-go/handlers/agents.go
Normal file
@@ -0,0 +1,369 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/swiftyos/market/database"
|
||||
"github.com/swiftyos/market/models"
|
||||
"github.com/swiftyos/market/utils"
|
||||
)
|
||||
|
||||
// @BasePath /api/v1/marketplace
|
||||
|
||||
// GetAgents godoc
|
||||
// @Summary Get Agents
|
||||
// @Schemes
|
||||
// @Description Get Agents
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param page query int false "Page number"
|
||||
// @Param pageSize query int false "Page size"
|
||||
// @Param name query string false "Agent Name"
|
||||
// @Param keywords query []string false "Keywords"
|
||||
// @Param categories query []string false "Categories"
|
||||
// @Success 200 {array} models.Agent
|
||||
// @Router /agents [get]
|
||||
func GetAgents(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "GetAgents")).With(zap.String("file", "handlers/agents.go"))
|
||||
|
||||
logger.Info("Get Agents Request Started")
|
||||
// Get pagination parameters from context
|
||||
page := getPageFromContext(c.Request.Context())
|
||||
pageSize := getPageSizeFromContext(c.Request.Context())
|
||||
|
||||
// Get filter parameters from context
|
||||
name := getNameFromContext(c.Request.Context())
|
||||
keywords := getKeywordsFromContext(c.Request.Context())
|
||||
categories := getCategoriesFromContext(c.Request.Context())
|
||||
|
||||
logger.Debug("Request parameters",
|
||||
zap.Int("page", page),
|
||||
zap.Int("pageSize", pageSize),
|
||||
zap.String("name", utils.StringOrNil(name)),
|
||||
zap.String("keywords", utils.StringOrNil(keywords)),
|
||||
zap.String("categories", utils.StringOrNil(categories)))
|
||||
|
||||
agents, err := database.GetAgents(c.Request.Context(), db, log_ctx, page, pageSize, name, keywords, categories)
|
||||
|
||||
if err != nil {
|
||||
logger.Error("Database requested returned error!", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch agents"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agents)
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Submit Agent
|
||||
// @Description Submit an agent for review
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param agent body models.AddAgentRequest true "Agent details"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agents [post]
|
||||
func SubmitAgent(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "SubmitAgent"))
|
||||
var request models.AddAgentRequest
|
||||
logger.Debug("Add Agent Request body", zap.Any("request", request))
|
||||
if err := c.ShouldBindJSON(&request); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "User not authenticated"})
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := database.SubmitAgent(c.Request.Context(), db, request, user)
|
||||
if err != nil {
|
||||
logger.Error("Failed to submit agent", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to submit agent"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Get Agent Details
|
||||
// @Description Get details of a specific agent by ID
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Agent ID"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agents/{id} [get]
|
||||
func GetAgentDetails(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "GetAgentDetails"))
|
||||
|
||||
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"})
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := database.GetAgentDetails(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
if err.Error() == "agent not found" {
|
||||
logger.Error("Agent not found", zap.String("agentID", agentID))
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"})
|
||||
return
|
||||
}
|
||||
logger.Error("Failed to fetch agent details", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch agent details"})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Download Agent
|
||||
// @Description Download an agent by ID
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Agent ID"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agents/{id}/download [get]
|
||||
func DownloadAgent(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "DownloadAgent"))
|
||||
|
||||
agentID := c.Param("id")
|
||||
if agentID == "" {
|
||||
logger.Error("Agent ID is required")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"})
|
||||
return
|
||||
}
|
||||
|
||||
agent, err := database.GetAgentDetails(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
if err.Error() == "agent not found" {
|
||||
logger.Error("Agent not found", zap.String("agentID", agentID))
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"})
|
||||
return
|
||||
}
|
||||
logger.Error("Failed to fetch agent details", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch agent details"})
|
||||
return
|
||||
}
|
||||
|
||||
err = database.IncrementDownloadCount(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
logger.Error("Failed to increment download count", zap.Error(err))
|
||||
// Continue with the download even if the count update fails
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, agent)
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Download Agent File
|
||||
// @Description Download an agent file by ID
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param id path string true "Agent ID"
|
||||
// @Success 200 {object} models.Agent
|
||||
// @Router /agents/{id}/download [get]
|
||||
func DownloadAgentFile(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "DownloadAgentFile"))
|
||||
|
||||
agentID := c.Param("id")
|
||||
if agentID == "" {
|
||||
logger.Error("Agent ID is required")
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Agent ID is required"})
|
||||
return
|
||||
}
|
||||
|
||||
agentFile, err := database.GetAgentFile(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
if err.Error() == "agent not found" {
|
||||
logger.Error("Agent not found", zap.String("agentID", agentID))
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "Agent not found"})
|
||||
return
|
||||
}
|
||||
logger.Error("Failed to fetch agent file", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch agent file"})
|
||||
return
|
||||
}
|
||||
|
||||
err = database.IncrementDownloadCount(c.Request.Context(), db, agentID)
|
||||
if err != nil {
|
||||
logger.Error("Failed to increment download count", zap.Error(err))
|
||||
// Continue with the download even if the count update fails
|
||||
}
|
||||
|
||||
fileName := fmt.Sprintf("agent_%s.json", agentID)
|
||||
c.Header("Content-Disposition", fmt.Sprintf("attachment; filename=%s", fileName))
|
||||
c.JSON(http.StatusOK, agentFile.Graph)
|
||||
}
|
||||
}
|
||||
|
||||
func TopAgentsByDownloads(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.With(zap.String("function", "TopAgentsByDownloads"))
|
||||
logger.Info("Handling request for top agents by downloads")
|
||||
|
||||
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.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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Get Featured Agents
|
||||
// @Description Get featured agents based on category
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param category query string false "Category"
|
||||
// @Param page query int false "Page number"
|
||||
// @Param pageSize query int false "Page size"
|
||||
// @Success 200 {array} models.Agent
|
||||
// @Router /agents/featured [get]
|
||||
func GetFeaturedAgents(db *pgxpool.Pool, logger *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := logger.With(zap.String("function", "GetFeaturedAgents"))
|
||||
logger.Info("Handling request for featured agents")
|
||||
|
||||
category := c.Query("category")
|
||||
if category == "" {
|
||||
logger.Debug("No category specified, fetching all featured agents")
|
||||
} else {
|
||||
logger.Debug("Fetching featured agents for category", zap.String("category", category))
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// @Summary Search Agents
|
||||
// @Description Search for agents based on query and categories
|
||||
// @Tags Agents
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Param q query string true "Search query"
|
||||
// @Param categories query []string false "Categories"
|
||||
// @Param page query int false "Page number"
|
||||
// @Param pageSize query int false "Page size"
|
||||
// @Param sortBy query string false "Sort by"
|
||||
// @Param sortOrder query string false "Sort order"
|
||||
// @Success 200 {array} models.Agent
|
||||
// @Router /agents/search [get]
|
||||
func SearchAgents(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := log_ctx.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,
|
||||
})
|
||||
}
|
||||
}
|
||||
41
rnd/rest-api-go/handlers/analytics.go
Normal file
41
rnd/rest-api-go/handlers/analytics.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
"github.com/swiftyos/market/database"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/swiftyos/market/models"
|
||||
)
|
||||
|
||||
func AgentInstalled(db *pgxpool.Pool, log_ctx *zap.Logger) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
logger := zap.L().With(zap.String("function", "AgentInstalled"))
|
||||
var eventData models.InstallTracker
|
||||
if err := c.ShouldBindJSON(&eventData); err != nil {
|
||||
logger.Error("Failed to bind JSON", zap.Error(err))
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request body"})
|
||||
return
|
||||
}
|
||||
|
||||
err := database.CreateAgentInstalledEvent(c.Request.Context(), db, models.InstallTracker{
|
||||
MarketplaceAgentID: eventData.MarketplaceAgentID,
|
||||
InstalledAgentID: eventData.InstalledAgentID,
|
||||
InstallationLocation: eventData.InstallationLocation,
|
||||
})
|
||||
if err != nil {
|
||||
logger.Error("Failed to process agent installed event", zap.Error(err))
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to process agent installed event"})
|
||||
return
|
||||
}
|
||||
logger.Info("Agent installed event processed successfully",
|
||||
zap.String("marketplaceAgentID", eventData.MarketplaceAgentID),
|
||||
zap.String("installedAgentID", eventData.InstalledAgentID),
|
||||
zap.String("installationLocation", string(eventData.InstallationLocation)))
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Agent installed event processed successfully"})
|
||||
}
|
||||
}
|
||||
95
rnd/rest-api-go/handlers/parameters.go
Normal file
95
rnd/rest-api-go/handlers/parameters.go
Normal file
@@ -0,0 +1,95 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strconv"
|
||||
)
|
||||
|
||||
const pageKey string = "page"
|
||||
|
||||
func getPageFromContext(ctx context.Context) int {
|
||||
defaultPage := 1
|
||||
if ctx == nil {
|
||||
return defaultPage
|
||||
}
|
||||
|
||||
pageValue := ctx.Value(pageKey)
|
||||
|
||||
if pageValue == nil {
|
||||
return defaultPage
|
||||
}
|
||||
|
||||
// Type assertion to check if the value is an int
|
||||
if page, ok := pageValue.(int); ok {
|
||||
if page < 1 {
|
||||
return defaultPage
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
// If it's not an int, try to convert from string
|
||||
if pageStr, ok := pageValue.(string); ok {
|
||||
page, err := strconv.Atoi(pageStr)
|
||||
if err != nil || page < 1 {
|
||||
return defaultPage
|
||||
}
|
||||
return page
|
||||
}
|
||||
|
||||
return defaultPage
|
||||
}
|
||||
|
||||
const pageSizeKey string = "page_size"
|
||||
|
||||
func getPageSizeFromContext(ctx context.Context) int {
|
||||
pageSizeValue := ctx.Value(pageSizeKey)
|
||||
if pageSizeValue == nil {
|
||||
return 10
|
||||
}
|
||||
if pageSizeValue, ok := pageSizeValue.(int); ok {
|
||||
if pageSizeValue < 1 {
|
||||
return 10
|
||||
}
|
||||
return pageSizeValue
|
||||
}
|
||||
return 10
|
||||
}
|
||||
|
||||
const nameKey string = "name"
|
||||
|
||||
func getNameFromContext(ctx context.Context) *string {
|
||||
nameValue := ctx.Value(nameKey)
|
||||
if nameValue == nil {
|
||||
return nil
|
||||
}
|
||||
if nameValue, ok := nameValue.(string); ok {
|
||||
return &nameValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const keywordsKey string = "keywords"
|
||||
|
||||
func getKeywordsFromContext(ctx context.Context) *string {
|
||||
keywordsValue := ctx.Value(keywordsKey)
|
||||
if keywordsValue == nil {
|
||||
return nil
|
||||
}
|
||||
if keywordsValue, ok := keywordsValue.(string); ok {
|
||||
return &keywordsValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
const categoriesKey string = "categories"
|
||||
|
||||
func getCategoriesFromContext(ctx context.Context) *string {
|
||||
categoriesValue := ctx.Value(categoriesKey)
|
||||
if categoriesValue == nil {
|
||||
return nil
|
||||
}
|
||||
if categoriesValue, ok := categoriesValue.(string); ok {
|
||||
return &categoriesValue
|
||||
}
|
||||
return nil
|
||||
}
|
||||
151
rnd/rest-api-go/handlers/parameters_test.go
Normal file
151
rnd/rest-api-go/handlers/parameters_test.go
Normal file
@@ -0,0 +1,151 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
)
|
||||
|
||||
func TestGetPageFromContext_ValidPage(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageKey, 5)
|
||||
result := getPageFromContext(ctx)
|
||||
assert.Equal(t, 5, result)
|
||||
}
|
||||
|
||||
func TestGetPageFromContext_InvalidPageZero(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageKey, 0)
|
||||
result := getPageFromContext(ctx)
|
||||
assert.Equal(t, 1, result)
|
||||
}
|
||||
|
||||
func TestGetPageFromContext_NoPageValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
result := getPageFromContext(ctx)
|
||||
assert.Equal(t, 1, result)
|
||||
}
|
||||
|
||||
func TestGetPageFromContext_InvalidPageNegative(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageKey, -1)
|
||||
result := getPageFromContext(ctx)
|
||||
assert.Equal(t, 1, result)
|
||||
}
|
||||
|
||||
func TestGetPageFromContext_InvalidType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageKey, "not an int")
|
||||
result := getPageFromContext(ctx)
|
||||
assert.Equal(t, 1, result)
|
||||
}
|
||||
|
||||
func TestGetPageSizeFromContext_ValidPageSize(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageSizeKey, 20)
|
||||
result := getPageSizeFromContext(ctx)
|
||||
assert.Equal(t, 20, result)
|
||||
}
|
||||
|
||||
func TestGetPageSizeFromContext_InvalidPageSizeNegative(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageSizeKey, -1)
|
||||
result := getPageSizeFromContext(ctx)
|
||||
assert.Equal(t, 10, result)
|
||||
}
|
||||
|
||||
func TestGetPageSizeFromContext_InvalidPageSizeZero(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageSizeKey, 0)
|
||||
result := getPageSizeFromContext(ctx)
|
||||
assert.Equal(t, 10, result)
|
||||
}
|
||||
|
||||
func TestGetPageSizeFromContext_NoPageSizeValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
result := getPageSizeFromContext(ctx)
|
||||
assert.Equal(t, 10, result)
|
||||
}
|
||||
|
||||
func TestGetPageSizeFromContext_InvalidType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), pageSizeKey, "not an int")
|
||||
result := getPageSizeFromContext(ctx)
|
||||
assert.Equal(t, 10, result)
|
||||
}
|
||||
|
||||
func TestGetNameFromContext_ValidName(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), nameKey, "Test Name")
|
||||
result := getNameFromContext(ctx)
|
||||
assert.Equal(t, strPtr("Test Name"), result)
|
||||
}
|
||||
|
||||
func TestGetNameFromContext_EmptyString(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), nameKey, "")
|
||||
result := getNameFromContext(ctx)
|
||||
assert.Equal(t, strPtr(""), result)
|
||||
}
|
||||
|
||||
func TestGetNameFromContext_NoNameValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
result := getNameFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestGetNameFromContext_InvalidType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), nameKey, 123)
|
||||
result := getNameFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestGetKeywordsFromContext_ValidKeywords(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), keywordsKey, "keyword1,keyword2")
|
||||
result := getKeywordsFromContext(ctx)
|
||||
assert.Equal(t, strPtr("keyword1,keyword2"), result)
|
||||
}
|
||||
|
||||
func TestGetKeywordsFromContext_EmptyString(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), keywordsKey, "")
|
||||
result := getKeywordsFromContext(ctx)
|
||||
assert.Equal(t, strPtr(""), result)
|
||||
}
|
||||
|
||||
func TestGetKeywordsFromContext_NoKeywordsValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
result := getKeywordsFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestGetKeywordsFromContext_InvalidType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), keywordsKey, 123)
|
||||
result := getKeywordsFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestGetCategoriesFromContext_ValidCategories(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), categoriesKey, "category1,category2")
|
||||
result := getCategoriesFromContext(ctx)
|
||||
assert.Equal(t, strPtr("category1,category2"), result)
|
||||
}
|
||||
|
||||
func TestGetCategoriesFromContext_EmptyString(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), categoriesKey, "")
|
||||
result := getCategoriesFromContext(ctx)
|
||||
assert.Equal(t, strPtr(""), result)
|
||||
}
|
||||
|
||||
func TestGetCategoriesFromContext_NoCategoriesValue(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
result := getCategoriesFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func TestGetCategoriesFromContext_InvalidType(t *testing.T) {
|
||||
ctx := context.WithValue(context.Background(), categoriesKey, 123)
|
||||
result := getCategoriesFromContext(ctx)
|
||||
assert.Nil(t, result)
|
||||
}
|
||||
|
||||
func strPtr(s string) *string {
|
||||
return &s
|
||||
}
|
||||
|
||||
func init() {
|
||||
logger := zaptest.NewLogger(nil)
|
||||
zap.ReplaceGlobals(logger)
|
||||
}
|
||||
14
rnd/rest-api-go/handlers/user.go
Normal file
14
rnd/rest-api-go/handlers/user.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/swiftyos/market/models"
|
||||
)
|
||||
|
||||
func GetUserFromContext(c *gin.Context) (models.User, bool) {
|
||||
user, exists := c.Get("user")
|
||||
if !exists {
|
||||
return models.User{}, false
|
||||
}
|
||||
return user.(models.User), true
|
||||
}
|
||||
45
rnd/rest-api-go/handlers/user_test.go
Normal file
45
rnd/rest-api-go/handlers/user_test.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/swiftyos/market/models"
|
||||
)
|
||||
|
||||
func TestGetUserFromContext(t *testing.T) {
|
||||
t.Run("User exists in context", func(t *testing.T) {
|
||||
// Create a new gin context
|
||||
c, _ := gin.CreateTestContext(nil)
|
||||
|
||||
// Create a test user
|
||||
testUser := models.User{
|
||||
UserID: "123",
|
||||
Role: "admin",
|
||||
Email: "test@example.com",
|
||||
}
|
||||
|
||||
// Set the user in the context
|
||||
c.Set("user", testUser)
|
||||
|
||||
// Call the function
|
||||
user, exists := GetUserFromContext(c)
|
||||
|
||||
// Assert the results
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, testUser, user)
|
||||
})
|
||||
|
||||
t.Run("User does not exist in context", func(t *testing.T) {
|
||||
// Create a new gin context
|
||||
c, _ := gin.CreateTestContext(nil)
|
||||
|
||||
// Call the function
|
||||
user, exists := GetUserFromContext(c)
|
||||
|
||||
// Assert the results
|
||||
assert.False(t, exists)
|
||||
assert.Equal(t, models.User{}, user)
|
||||
})
|
||||
}
|
||||
116
rnd/rest-api-go/main.go
Normal file
116
rnd/rest-api-go/main.go
Normal file
@@ -0,0 +1,116 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"github.com/Depado/ginprom"
|
||||
"github.com/gin-contrib/cors"
|
||||
"github.com/gin-contrib/zap"
|
||||
"github.com/gin-gonic/gin"
|
||||
swaggerfiles "github.com/swaggo/files"
|
||||
ginSwagger "github.com/swaggo/gin-swagger"
|
||||
"github.com/swiftyos/market/config"
|
||||
"github.com/swiftyos/market/database"
|
||||
docs "github.com/swiftyos/market/docs"
|
||||
"github.com/swiftyos/market/handlers"
|
||||
"github.com/swiftyos/market/middleware"
|
||||
"github.com/swiftyos/market/utils"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// Initialize configuration
|
||||
cfg, err := config.Load()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to load config: %v", err)
|
||||
}
|
||||
|
||||
// Initialize logger
|
||||
logger := utils.NewLogger(cfg)
|
||||
|
||||
// Initialize database connection
|
||||
db, err := database.NewDB(cfg)
|
||||
if err != nil {
|
||||
logger.Fatal("Failed to connect to database", zap.Error(err))
|
||||
}
|
||||
// Initialize Gin router
|
||||
r := gin.New()
|
||||
// Set the port
|
||||
port := cfg.ServerAddress
|
||||
if port == "" {
|
||||
port = "8080" // Default port if not specified in config
|
||||
}
|
||||
r.Run(":" + port)
|
||||
p := ginprom.New(
|
||||
ginprom.Engine(r),
|
||||
ginprom.Subsystem("gin"),
|
||||
ginprom.Path("/metrics"),
|
||||
)
|
||||
r.Use(p.Instrument())
|
||||
// Use middleware
|
||||
r.Use(ginzap.Ginzap(logger, time.RFC1123, true))
|
||||
r.Use(ginzap.RecoveryWithZap(logger, true))
|
||||
r.Use(middleware.Gzip())
|
||||
|
||||
// Update CORS configuration
|
||||
corsConfig := cors.DefaultConfig()
|
||||
if len(cfg.CORSAllowOrigins) > 0 {
|
||||
corsConfig.AllowOrigins = cfg.CORSAllowOrigins
|
||||
} else {
|
||||
corsConfig.AllowOrigins = []string{"*"} // Fallback to allow all origins if not specified
|
||||
}
|
||||
corsConfig.AllowHeaders = append(corsConfig.AllowHeaders, "Authorization")
|
||||
corsConfig.AllowMethods = []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"}
|
||||
corsConfig.AllowCredentials = true
|
||||
r.Use(cors.New(corsConfig))
|
||||
|
||||
// Route welcome
|
||||
r.GET("/", func(c *gin.Context) {
|
||||
c.String(http.StatusOK, "Welcome to the Marketplace API")
|
||||
})
|
||||
docs.SwaggerInfo.BasePath = "/api/v1/market/"
|
||||
|
||||
// Setup routes
|
||||
// [Error] Request header field Authorization is not allowed by Access-Control-Allow-Headers.
|
||||
// [Error] Fetch API cannot load http://localhost:8015/api/v1/market/featured/agents?page=1&page_size=10 due to access control checks.
|
||||
// [Error] Failed to load resource: Request header field Authorization is not allowed by Access-Control-Allow-Headers. (agents, line 0)
|
||||
api := r.Group("/api/v1/market/")
|
||||
{
|
||||
|
||||
agents := api.Group("/agents")
|
||||
{
|
||||
agents.GET("", handlers.GetAgents(db, logger))
|
||||
agents.GET("/:agent_id", handlers.GetAgentDetails(db, logger))
|
||||
agents.GET("/:agent_id/download", handlers.DownloadAgent(db, logger))
|
||||
agents.GET("/:agent_id/download-file", handlers.DownloadAgentFile(db, logger))
|
||||
agents.GET("/top-downloads", handlers.TopAgentsByDownloads(db, logger))
|
||||
agents.GET("/featured", handlers.GetFeaturedAgents(db, logger))
|
||||
agents.GET("/search", handlers.SearchAgents(db, logger))
|
||||
agents.POST("/submit", middleware.Auth(cfg), handlers.SubmitAgent(db, logger))
|
||||
}
|
||||
|
||||
// Admin routes
|
||||
admin := api.Group("/admin")
|
||||
{
|
||||
admin.POST("/agent", middleware.Auth(cfg), handlers.CreateAgentEntry(db, logger))
|
||||
admin.POST("/agent/featured/:agent_id", middleware.Auth(cfg), handlers.SetAgentFeatured(db, logger))
|
||||
admin.GET("/agent/featured/:agent_id", middleware.Auth(cfg), handlers.GetAgentFeatured(db, logger))
|
||||
admin.DELETE("/agent/featured/:agent_id", middleware.Auth(cfg), handlers.UnsetAgentFeatured(db, logger))
|
||||
admin.GET("/agent/not-featured", middleware.Auth(cfg), handlers.GetNotFeaturedAgents(db, logger))
|
||||
admin.GET("/agent/submissions", middleware.Auth(cfg), handlers.GetAgentSubmissions(db, logger))
|
||||
admin.POST("/agent/submissions", middleware.Auth(cfg), handlers.ReviewSubmission(db, logger))
|
||||
}
|
||||
|
||||
api.GET("/categories", handlers.GetCategories(db, logger))
|
||||
// Analytics routes
|
||||
api.POST("/agent-installed", handlers.AgentInstalled(db, logger))
|
||||
}
|
||||
r.GET("/docs/*any", ginSwagger.WrapHandler(swaggerfiles.Handler))
|
||||
|
||||
// Start server
|
||||
if err := r.Run(cfg.ServerAddress); err != nil {
|
||||
logger.Fatal("Failed to start server", zap.Error(err))
|
||||
}
|
||||
}
|
||||
83
rnd/rest-api-go/middleware/auth.go
Normal file
83
rnd/rest-api-go/middleware/auth.go
Normal file
@@ -0,0 +1,83 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/swiftyos/market/config"
|
||||
"github.com/swiftyos/market/models"
|
||||
)
|
||||
|
||||
func Auth(cfg *config.Config) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
if !cfg.AuthEnabled {
|
||||
// This handles the case when authentication is disabled
|
||||
defaultUser := models.User{
|
||||
UserID: "3e53486c-cf57-477e-ba2a-cb02dc828e1a",
|
||||
Role: "admin",
|
||||
}
|
||||
c.Set("user", defaultUser)
|
||||
c.Next()
|
||||
return
|
||||
}
|
||||
|
||||
authHeader := c.GetHeader("Authorization")
|
||||
if authHeader == "" {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Authorization header is missing"})
|
||||
return
|
||||
}
|
||||
|
||||
tokenString := strings.TrimPrefix(authHeader, "Bearer ")
|
||||
token, err := parseJWTToken(tokenString, cfg.JWTSecret)
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
claims, ok := token.Claims.(jwt.MapClaims)
|
||||
if !ok || !token.Valid {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "Invalid token"})
|
||||
return
|
||||
}
|
||||
|
||||
user, err := verifyUser(claims, false) // Pass 'true' for admin-only routes
|
||||
if err != nil {
|
||||
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
|
||||
c.Set("user", user)
|
||||
c.Next()
|
||||
}
|
||||
}
|
||||
|
||||
func verifyUser(payload jwt.MapClaims, adminOnly bool) (models.User, error) {
|
||||
user, err := models.NewUserFromPayload(payload)
|
||||
if err != nil {
|
||||
return models.User{}, err
|
||||
}
|
||||
|
||||
if adminOnly && user.Role != "admin" {
|
||||
return models.User{}, errors.New("Admin access required")
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func parseJWTToken(tokenString string, secret string) (*jwt.Token, error) {
|
||||
token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) {
|
||||
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
|
||||
return nil, errors.New("unexpected signing method")
|
||||
}
|
||||
return []byte(secret), nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
213
rnd/rest-api-go/middleware/auth_test.go
Normal file
213
rnd/rest-api-go/middleware/auth_test.go
Normal file
@@ -0,0 +1,213 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/swiftyos/market/config"
|
||||
"github.com/swiftyos/market/models"
|
||||
)
|
||||
|
||||
func TestVerifyUser(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
payload jwt.MapClaims
|
||||
adminOnly bool
|
||||
wantUser models.User
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid user",
|
||||
payload: jwt.MapClaims{
|
||||
"sub": "test-user",
|
||||
"email": "test@example.com",
|
||||
"role": "user",
|
||||
},
|
||||
adminOnly: false,
|
||||
wantUser: models.User{
|
||||
UserID: "test-user",
|
||||
Email: "test@example.com",
|
||||
Role: "user",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Valid admin",
|
||||
payload: jwt.MapClaims{
|
||||
"sub": "admin-user",
|
||||
"email": "admin@example.com",
|
||||
"role": "admin",
|
||||
},
|
||||
adminOnly: true,
|
||||
wantUser: models.User{
|
||||
UserID: "admin-user",
|
||||
Email: "admin@example.com",
|
||||
Role: "admin",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Non-admin accessing admin-only route",
|
||||
payload: jwt.MapClaims{
|
||||
"sub": "test-user",
|
||||
"email": "test@example.com",
|
||||
"role": "user",
|
||||
},
|
||||
adminOnly: true,
|
||||
wantUser: models.User{},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Missing sub claim",
|
||||
payload: jwt.MapClaims{},
|
||||
adminOnly: false,
|
||||
wantUser: models.User{},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
gotUser, err := verifyUser(tt.payload, tt.adminOnly)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.wantUser, gotUser)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseJWTToken(t *testing.T) {
|
||||
secret := "test-secret"
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
tokenString string
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Valid token",
|
||||
tokenString: createValidToken(secret),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid token",
|
||||
tokenString: "invalid.token.string",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "Empty token",
|
||||
tokenString: "",
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
token, err := parseJWTToken(tt.tokenString, secret)
|
||||
if tt.wantErr {
|
||||
assert.Error(t, err)
|
||||
assert.Nil(t, token)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, token)
|
||||
assert.True(t, token.Valid)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createValidToken(secret string) string {
|
||||
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
|
||||
"sub": "test-user",
|
||||
"email": "test@example.com",
|
||||
"role": "user",
|
||||
})
|
||||
tokenString, _ := token.SignedString([]byte(secret))
|
||||
return tokenString
|
||||
}
|
||||
|
||||
func TestAuth(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
JWTSecret: "test-secret",
|
||||
AuthEnabled: true,
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
authHeader string
|
||||
expectedUser models.User
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid token",
|
||||
authHeader: "Bearer " + createValidToken(cfg.JWTSecret),
|
||||
expectedUser: models.User{
|
||||
UserID: "test-user",
|
||||
Email: "test@example.com",
|
||||
Role: "user",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Invalid token",
|
||||
authHeader: "Bearer invalid.token.string",
|
||||
expectedUser: models.User{},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "Missing auth header",
|
||||
authHeader: "",
|
||||
expectedUser: models.User{},
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Create a mock gin.Context
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest("GET", "/", nil)
|
||||
c.Request.Header.Set("Authorization", tt.authHeader)
|
||||
|
||||
// Call the Auth middleware
|
||||
Auth(cfg)(c)
|
||||
|
||||
// Check the results
|
||||
if tt.expectedError {
|
||||
assert.True(t, c.IsAborted())
|
||||
} else {
|
||||
assert.False(t, c.IsAborted())
|
||||
user, exists := c.Get("user")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, tt.expectedUser, user.(models.User))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAuthDisabled(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
JWTSecret: "test-secret",
|
||||
AuthEnabled: false,
|
||||
}
|
||||
|
||||
// Create a mock gin.Context
|
||||
c, _ := gin.CreateTestContext(httptest.NewRecorder())
|
||||
c.Request = httptest.NewRequest("GET", "/", nil)
|
||||
|
||||
Auth(cfg)(c)
|
||||
|
||||
assert.False(t, c.IsAborted())
|
||||
user, exists := c.Get("user")
|
||||
assert.True(t, exists)
|
||||
assert.Equal(t, models.User{
|
||||
UserID: "3e53486c-cf57-477e-ba2a-cb02dc828e1a",
|
||||
Role: "admin",
|
||||
}, user.(models.User))
|
||||
}
|
||||
10
rnd/rest-api-go/middleware/gzip.go
Normal file
10
rnd/rest-api-go/middleware/gzip.go
Normal file
@@ -0,0 +1,10 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"github.com/gin-contrib/gzip"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func Gzip() gin.HandlerFunc {
|
||||
return gzip.Gzip(gzip.DefaultCompression)
|
||||
}
|
||||
104
rnd/rest-api-go/models/agent.go
Normal file
104
rnd/rest-api-go/models/agent.go
Normal file
@@ -0,0 +1,104 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
// Agent represents the basic agent information
|
||||
type Agent struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
Author string `json:"author"`
|
||||
Keywords []string `json:"keywords"`
|
||||
Categories []string `json:"categories"`
|
||||
Graph Graph `json:"graph"`
|
||||
}
|
||||
|
||||
// Graph represents the graph structure of an agent
|
||||
type Graph struct {
|
||||
Name string `json:"name"`
|
||||
Description string `json:"description"`
|
||||
// Add other fields as needed
|
||||
}
|
||||
|
||||
// AddAgentRequest represents the request structure for adding a new agent
|
||||
type AddAgentRequest struct {
|
||||
Graph Graph `json:"graph"`
|
||||
Author string `json:"author"`
|
||||
Keywords []string `json:"keywords"`
|
||||
Categories []string `json:"categories"`
|
||||
}
|
||||
|
||||
// SubmissionStatus represents the status of an agent submission
|
||||
type SubmissionStatus string
|
||||
|
||||
const (
|
||||
SubmissionStatusPending SubmissionStatus = "PENDING"
|
||||
SubmissionStatusApproved SubmissionStatus = "APPROVED"
|
||||
SubmissionStatusRejected SubmissionStatus = "REJECTED"
|
||||
)
|
||||
|
||||
// AgentWithMetadata extends Agent with additional metadata
|
||||
type AgentWithMetadata struct {
|
||||
Agent
|
||||
Version int `json:"version"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
SubmissionDate time.Time `json:"submissionDate"`
|
||||
SubmissionReviewDate *time.Time `json:"submissionReviewDate,omitempty"`
|
||||
SubmissionStatus SubmissionStatus `json:"submissionStatus"`
|
||||
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"`
|
||||
AgentID string `json:"agentId"`
|
||||
Views int `json:"views"`
|
||||
Downloads int `json:"downloads"`
|
||||
}
|
||||
|
||||
// InstallationLocation represents the location where an agent is installed
|
||||
type InstallationLocation string
|
||||
|
||||
const (
|
||||
InstallationLocationLocal InstallationLocation = "LOCAL"
|
||||
InstallationLocationCloud InstallationLocation = "CLOUD"
|
||||
)
|
||||
|
||||
// InstallTracker represents installation data for an agent
|
||||
type InstallTracker struct {
|
||||
ID string `json:"id"`
|
||||
MarketplaceAgentID string `json:"marketplaceAgentId"`
|
||||
InstalledAgentID string `json:"installedAgentId"`
|
||||
InstallationLocation InstallationLocation `json:"installationLocation"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
}
|
||||
|
||||
// FeaturedAgent represents a featured agent in the marketplace
|
||||
type FeaturedAgent struct {
|
||||
ID string `json:"id"`
|
||||
AgentID string `json:"agentId"`
|
||||
IsActive bool `json:"isActive"`
|
||||
FeaturedCategories []string `json:"featuredCategories"`
|
||||
CreatedAt time.Time `json:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt"`
|
||||
}
|
||||
|
||||
type AgentFile struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Graph interface{} `json:"graph"`
|
||||
}
|
||||
176
rnd/rest-api-go/models/agent_test.go
Normal file
176
rnd/rest-api-go/models/agent_test.go
Normal file
@@ -0,0 +1,176 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAgentJSON(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"id": "test-id",
|
||||
"name": "Test Agent",
|
||||
"description": "A test agent",
|
||||
"author": "Test Author",
|
||||
"keywords": ["test", "agent"],
|
||||
"categories": ["testing"],
|
||||
"graph": {
|
||||
"name": "Test Graph",
|
||||
"description": "A test graph"
|
||||
}
|
||||
}`
|
||||
|
||||
var agent Agent
|
||||
err := json.Unmarshal([]byte(jsonStr), &agent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "test-id", agent.ID)
|
||||
assert.Equal(t, "Test Agent", agent.Name)
|
||||
assert.Equal(t, "A test agent", agent.Description)
|
||||
assert.Equal(t, "Test Author", agent.Author)
|
||||
assert.Equal(t, []string{"test", "agent"}, agent.Keywords)
|
||||
assert.Equal(t, []string{"testing"}, agent.Categories)
|
||||
assert.Equal(t, "Test Graph", agent.Graph.Name)
|
||||
assert.Equal(t, "A test graph", agent.Graph.Description)
|
||||
}
|
||||
|
||||
func TestGraphJSON(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"name": "Test Graph",
|
||||
"description": "A test graph"
|
||||
}`
|
||||
|
||||
var graph Graph
|
||||
err := json.Unmarshal([]byte(jsonStr), &graph)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "Test Graph", graph.Name)
|
||||
assert.Equal(t, "A test graph", graph.Description)
|
||||
}
|
||||
|
||||
func TestAddAgentRequestJSON(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"graph": {
|
||||
"name": "Test Graph",
|
||||
"description": "A test graph"
|
||||
},
|
||||
"author": "Test Author",
|
||||
"keywords": ["test", "agent"],
|
||||
"categories": ["testing"]
|
||||
}`
|
||||
|
||||
var request AddAgentRequest
|
||||
err := json.Unmarshal([]byte(jsonStr), &request)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "Test Graph", request.Graph.Name)
|
||||
assert.Equal(t, "A test graph", request.Graph.Description)
|
||||
assert.Equal(t, "Test Author", request.Author)
|
||||
assert.Equal(t, []string{"test", "agent"}, request.Keywords)
|
||||
assert.Equal(t, []string{"testing"}, request.Categories)
|
||||
}
|
||||
|
||||
func TestAgentWithMetadataJSON(t *testing.T) {
|
||||
now := time.Now().UTC().Round(time.Second)
|
||||
jsonStr := `{
|
||||
"id": "test-id",
|
||||
"name": "Test Agent",
|
||||
"description": "A test agent",
|
||||
"author": "Test Author",
|
||||
"keywords": ["test", "agent"],
|
||||
"categories": ["testing"],
|
||||
"graph": {
|
||||
"name": "Test Graph",
|
||||
"description": "A test graph"
|
||||
},
|
||||
"version": 1,
|
||||
"createdAt": "` + now.Format(time.RFC3339) + `",
|
||||
"updatedAt": "` + now.Format(time.RFC3339) + `",
|
||||
"submissionDate": "` + now.Format(time.RFC3339) + `",
|
||||
"submissionStatus": "PENDING"
|
||||
}`
|
||||
|
||||
var agent AgentWithMetadata
|
||||
err := json.Unmarshal([]byte(jsonStr), &agent)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "test-id", agent.ID)
|
||||
assert.Equal(t, "Test Agent", agent.Name)
|
||||
assert.Equal(t, "A test agent", agent.Description)
|
||||
assert.Equal(t, "Test Author", agent.Author)
|
||||
assert.Equal(t, []string{"test", "agent"}, agent.Keywords)
|
||||
assert.Equal(t, []string{"testing"}, agent.Categories)
|
||||
assert.Equal(t, "Test Graph", agent.Graph.Name)
|
||||
assert.Equal(t, "A test graph", agent.Graph.Description)
|
||||
assert.Equal(t, 1, agent.Version)
|
||||
assert.Equal(t, now, agent.CreatedAt)
|
||||
assert.Equal(t, now, agent.UpdatedAt)
|
||||
assert.Equal(t, now, agent.SubmissionDate)
|
||||
assert.Equal(t, SubmissionStatusPending, agent.SubmissionStatus)
|
||||
assert.Nil(t, agent.SubmissionReviewDate)
|
||||
assert.Nil(t, agent.SubmissionReviewComments)
|
||||
}
|
||||
|
||||
func TestAnalyticsTrackerJSON(t *testing.T) {
|
||||
jsonStr := `{
|
||||
"id": "tracker-id",
|
||||
"agentId": "agent-id",
|
||||
"views": 100,
|
||||
"downloads": 50
|
||||
}`
|
||||
|
||||
var tracker AnalyticsTracker
|
||||
err := json.Unmarshal([]byte(jsonStr), &tracker)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "tracker-id", tracker.ID)
|
||||
assert.Equal(t, "agent-id", tracker.AgentID)
|
||||
assert.Equal(t, 100, tracker.Views)
|
||||
assert.Equal(t, 50, tracker.Downloads)
|
||||
}
|
||||
|
||||
func TestInstallTrackerJSON(t *testing.T) {
|
||||
now := time.Now().UTC().Round(time.Second)
|
||||
jsonStr := `{
|
||||
"id": "install-id",
|
||||
"marketplaceAgentId": "marketplace-agent-id",
|
||||
"installedAgentId": "installed-agent-id",
|
||||
"installationLocation": "LOCAL",
|
||||
"createdAt": "` + now.Format(time.RFC3339) + `"
|
||||
}`
|
||||
|
||||
var tracker InstallTracker
|
||||
err := json.Unmarshal([]byte(jsonStr), &tracker)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "install-id", tracker.ID)
|
||||
assert.Equal(t, "marketplace-agent-id", tracker.MarketplaceAgentID)
|
||||
assert.Equal(t, "installed-agent-id", tracker.InstalledAgentID)
|
||||
assert.Equal(t, InstallationLocationLocal, tracker.InstallationLocation)
|
||||
assert.Equal(t, now, tracker.CreatedAt)
|
||||
}
|
||||
|
||||
func TestFeaturedAgentJSON(t *testing.T) {
|
||||
now := time.Now().UTC().Round(time.Second)
|
||||
jsonStr := `{
|
||||
"id": "featured-id",
|
||||
"agentId": "agent-id",
|
||||
"isActive": true,
|
||||
"featuredCategories": ["category1", "category2"],
|
||||
"createdAt": "` + now.Format(time.RFC3339) + `",
|
||||
"updatedAt": "` + now.Format(time.RFC3339) + `"
|
||||
}`
|
||||
|
||||
var featured FeaturedAgent
|
||||
err := json.Unmarshal([]byte(jsonStr), &featured)
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, "featured-id", featured.ID)
|
||||
assert.Equal(t, "agent-id", featured.AgentID)
|
||||
assert.True(t, featured.IsActive)
|
||||
assert.Equal(t, []string{"category1", "category2"}, featured.FeaturedCategories)
|
||||
assert.Equal(t, now, featured.CreatedAt)
|
||||
assert.Equal(t, now, featured.UpdatedAt)
|
||||
}
|
||||
28
rnd/rest-api-go/models/user.go
Normal file
28
rnd/rest-api-go/models/user.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
UserID string `json:"user_id"`
|
||||
Email string `json:"email"`
|
||||
Role string `json:"role"`
|
||||
}
|
||||
|
||||
func NewUserFromPayload(claims jwt.MapClaims) (User, error) {
|
||||
userID, ok := claims["sub"].(string)
|
||||
if !ok {
|
||||
return User{}, fmt.Errorf("invalid or missing 'sub' claim")
|
||||
}
|
||||
|
||||
email, _ := claims["email"].(string)
|
||||
role, _ := claims["role"].(string)
|
||||
|
||||
return User{
|
||||
UserID: userID,
|
||||
Email: email,
|
||||
Role: role,
|
||||
}, nil
|
||||
}
|
||||
62
rnd/rest-api-go/models/user_test.go
Normal file
62
rnd/rest-api-go/models/user_test.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package models
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
|
||||
"github.com/golang-jwt/jwt/v4"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestNewUserFromPayload(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
payload string
|
||||
expectedUser User
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
name: "Valid payload",
|
||||
payload: `{"sub": "123", "email": "test@example.com", "role": "user"}`,
|
||||
expectedUser: User{
|
||||
UserID: "123",
|
||||
Email: "test@example.com",
|
||||
Role: "user",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
{
|
||||
name: "Missing sub claim",
|
||||
payload: `{"email": "test@example.com", "role": "user"}`,
|
||||
expectedUser: User{},
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
name: "Missing optional claims",
|
||||
payload: `{"sub": "456"}`,
|
||||
expectedUser: User{
|
||||
UserID: "456",
|
||||
Email: "",
|
||||
Role: "",
|
||||
},
|
||||
expectedError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
var claims jwt.MapClaims
|
||||
err := json.Unmarshal([]byte(tc.payload), &claims)
|
||||
assert.NoError(t, err)
|
||||
|
||||
user, err := NewUserFromPayload(claims)
|
||||
|
||||
if tc.expectedError {
|
||||
assert.Error(t, err)
|
||||
} else {
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tc.expectedUser, user)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
11
rnd/rest-api-go/prometheus.yml
Normal file
11
rnd/rest-api-go/prometheus.yml
Normal file
@@ -0,0 +1,11 @@
|
||||
global:
|
||||
scrape_interval: 15s
|
||||
evaluation_interval: 15s
|
||||
|
||||
scrape_configs:
|
||||
- job_name: 'market'
|
||||
static_configs:
|
||||
- targets: ['market:8015']
|
||||
metrics_path: '/metrics'
|
||||
scheme: 'http'
|
||||
|
||||
27
rnd/rest-api-go/utils/logger.go
Normal file
27
rnd/rest-api-go/utils/logger.go
Normal file
@@ -0,0 +1,27 @@
|
||||
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 {
|
||||
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
|
||||
}
|
||||
|
||||
func StringOrNil(s *string) string {
|
||||
if s == nil {
|
||||
return "nil"
|
||||
}
|
||||
return *s
|
||||
}
|
||||
Reference in New Issue
Block a user