Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,18 @@ swag:

migrate_local:
go run cmd/csghub-server/main.go migration migrate --config local.toml

db_migrate:
@go run -tags "$(GO_TAGS)" cmd/csghub-server/main.go migration migrate --config local.toml

db_rollback:
@go run -tags "$(GO_TAGS)" cmd/csghub-server/main.go migration rollback --config local.toml

start_server:
@go run -tags "$(GO_TAGS)" cmd/csghub-server/main.go start server -l Info -f json --config local.toml

start_user:
@go run -tags "$(GO_TAGS)" cmd/csghub-server/main.go user launch -l Info -f json --config local.toml

error_doc:
@go run cmd/csghub-server/main.go errorx doc-gen
35 changes: 35 additions & 0 deletions api/middleware/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package middleware

import (
"time"

cache "github.com/chenyahui/gin-cache"
"github.com/gin-gonic/gin"
"opencsg.com/csghub-server/api/httpbase"
)

func CacheStrategyTrendingRepos() cache.Option {
return cache.WithCacheStrategyByRequest(getCacheStrategyTrendingReposByRequest)
}

func getCacheStrategyTrendingReposByRequest(c *gin.Context) (bool, cache.Strategy) {
// only cache anonymous users access the trending repositories
if httpbase.GetCurrentUser(c) != "" {
return false, cache.Strategy{}
}

sort := c.Query("sort")
if sort != "trending" {
return false, cache.Strategy{}
}

search := c.Query("search")
if search != "" {
return false, cache.Strategy{}
}

return true, cache.Strategy{
CacheKey: c.Request.RequestURI,
CacheDuration: 2 * time.Minute,
}
}
203 changes: 203 additions & 0 deletions api/middleware/cache_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
package middleware

import (
"net/http"
"net/http/httptest"
"testing"
"time"

cache "github.com/chenyahui/gin-cache"
"github.com/chenyahui/gin-cache/persist"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"opencsg.com/csghub-server/api/httpbase"
)

func TestCacheStrategyTrendingRepos(t *testing.T) {
gin.SetMode(gin.TestMode)

tests := []struct {
name string
user string
sortParam string
searchParam string
expectedCache bool
}{
{
name: "Anonymous user, trending sort, no search",
user: "",
sortParam: "trending",
searchParam: "",
expectedCache: true,
},
{
name: "Logged-in user, trending sort, no search",
user: "testuser",
sortParam: "trending",
searchParam: "",
expectedCache: false,
},
{
name: "Anonymous user, non-trending sort, no search",
user: "",
sortParam: "popular",
searchParam: "",
expectedCache: false,
},
{
name: "Anonymous user, trending sort, with search",
user: "",
sortParam: "trending",
searchParam: "testquery",
expectedCache: false,
},
{
name: "Anonymous user, no sort, no search",
user: "",
sortParam: "",
searchParam: "",
expectedCache: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Create a memory store for testing
store := persist.NewMemoryStore(time.Minute)

// Create a test router with cache middleware
router := gin.New()

// Counter to track how many times the handler is called
callCount := 0
testHandler := gin.HandlerFunc(func(c *gin.Context) {
callCount++
c.JSON(200, gin.H{"message": "test", "call": callCount})
})

// Apply cache middleware with our strategy
cacheMiddleware := cache.Cache(store, 2*time.Minute, CacheStrategyTrendingRepos())
router.GET("/test", func(c *gin.Context) {
// Set user context if provided
if tt.user != "" {
c.Set(httpbase.CurrentUserCtxVar, tt.user)
}
// Call next to continue to cache middleware
c.Next()
}, cacheMiddleware, testHandler)

// Build the request URL
url := "/test"
if tt.sortParam != "" || tt.searchParam != "" {
url += "?sort=" + tt.sortParam + "&search=" + tt.searchParam
}

// First request
req1, _ := http.NewRequest(http.MethodGet, url, nil)
w1 := httptest.NewRecorder()
router.ServeHTTP(w1, req1)

assert.Equal(t, 200, w1.Code)
initialCallCount := callCount

if tt.expectedCache {
// For cached responses, make a second identical request
// The handler should NOT be called again if caching is working
req2, _ := http.NewRequest(http.MethodGet, url, nil)
w2 := httptest.NewRecorder()
router.ServeHTTP(w2, req2)

assert.Equal(t, 200, w2.Code)
// Call count should remain the same for cached response
assert.Equal(t, initialCallCount, callCount, "Handler should not be called again for cached response")
} else {
// For non-cached responses, make a second identical request
// The handler SHOULD be called again
req2, _ := http.NewRequest(http.MethodGet, url, nil)
w2 := httptest.NewRecorder()
router.ServeHTTP(w2, req2)

assert.Equal(t, 200, w2.Code)
// Call count should increase for non-cached response
assert.Greater(t, callCount, initialCallCount, "Handler should be called again for non-cached response")
}
})
}
}

// Test the cache strategy function directly by replicating its logic
func TestCacheStrategyLogic(t *testing.T) {
gin.SetMode(gin.TestMode)

tests := []struct {
name string
user string
sortParam string
searchParam string
expectedCache bool
expectedKey string
}{
{
name: "Anonymous user, trending sort, no search",
user: "",
sortParam: "trending",
searchParam: "",
expectedCache: true,
expectedKey: "/test?sort=trending&search=",
},
{
name: "Logged-in user, trending sort, no search",
user: "testuser",
sortParam: "trending",
searchParam: "",
expectedCache: false,
expectedKey: "",
},
{
name: "Anonymous user, non-trending sort, no search",
user: "",
sortParam: "popular",
searchParam: "",
expectedCache: false,
expectedKey: "",
},
{
name: "Anonymous user, trending sort, with search",
user: "",
sortParam: "trending",
searchParam: "testquery",
expectedCache: false,
expectedKey: "",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
w := httptest.NewRecorder()
c, _ := gin.CreateTestContext(w)

// Simulate request with query parameters
url := "/test?sort=" + tt.sortParam + "&search=" + tt.searchParam
req, _ := http.NewRequest(http.MethodGet, url, nil)
req.RequestURI = url // Manually set RequestURI since http.NewRequest doesn't set it
c.Request = req

// Set the current user in context if provided
if tt.user != "" {
c.Set(httpbase.CurrentUserCtxVar, tt.user)
}

// Test the cache strategy logic directly
shouldCache, strategy := getCacheStrategyTrendingReposByRequest(c)

assert.Equal(t, tt.expectedCache, shouldCache)
if tt.expectedCache {
assert.Equal(t, tt.expectedKey, strategy.CacheKey)
assert.Equal(t, 2*time.Minute, strategy.CacheDuration)
} else {
assert.Empty(t, strategy.CacheKey)
assert.Zero(t, strategy.CacheDuration)
}
})
}
}
4 changes: 3 additions & 1 deletion api/router/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -496,12 +496,14 @@ func createModelRoutes(config *config.Config,
modelHandler *handler.ModelHandler,
repoCommonHandler *handler.RepoHandler,
monitorHandler *handler.MonitorHandler) {
// gin cache
memoryStore := persist.NewMemoryStore(2 * time.Minute)
// Models routes
modelsGroup := apiGroup.Group("/models")
modelsGroup.Use(middleware.RepoType(types.ModelRepo), middlewareCollection.Repo.RepoExists)
{
modelsGroup.POST("", middlewareCollection.Auth.NeedLogin, modelHandler.Create)
modelsGroup.GET("", modelHandler.Index)
modelsGroup.GET("", cache.Cache(memoryStore, time.Minute, middleware.CacheStrategyTrendingRepos()), modelHandler.Index)
modelsGroup.PUT("/:namespace/:name", middlewareCollection.Auth.NeedLogin, modelHandler.Update)
modelsGroup.DELETE("/:namespace/:name", middlewareCollection.Auth.NeedLogin, modelHandler.Delete)
modelsGroup.GET("/:namespace/:name", modelHandler.Show)
Expand Down
Loading
Loading