diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go index d8831bb..7a01ad1 100644 --- a/backend/cmd/server/wire_gen.go +++ b/backend/cmd/server/wire_gen.go @@ -57,13 +57,14 @@ func newServer(dir string) (*Server, error) { openAIRepo := repo2.NewOpenAIRepo(client) openAIUsecase := openai.NewOpenAIUsecase(configConfig, openAIRepo, slogLogger) proxyMiddleware := middleware.NewProxyMiddleware(proxyUsecase) - v1Handler := v1.NewV1Handler(slogLogger, web, domainProxy, openAIUsecase, proxyMiddleware) + redisClient := store.NewRedisCli(configConfig) + activeMiddleware := middleware.NewActiveMiddleware(redisClient, slogLogger) + v1Handler := v1.NewV1Handler(slogLogger, web, domainProxy, openAIUsecase, proxyMiddleware, activeMiddleware) modelRepo := repo3.NewModelRepo(client) modelUsecase := usecase2.NewModelUsecase(slogLogger, modelRepo, configConfig) sessionSession := session.NewSession(configConfig) authMiddleware := middleware.NewAuthMiddleware(sessionSession, slogLogger) modelHandler := v1_2.NewModelHandler(web, modelUsecase, authMiddleware, slogLogger) - redisClient := store.NewRedisCli(configConfig) userRepo := repo4.NewUserRepo(client) userUsecase := usecase3.NewUserUsecase(configConfig, redisClient, userRepo, slogLogger) userHandler := v1_3.NewUserHandler(web, userUsecase, authMiddleware, sessionSession, slogLogger, configConfig) diff --git a/backend/consts/user.go b/backend/consts/user.go index 03a8f89..a2ab249 100644 --- a/backend/consts/user.go +++ b/backend/consts/user.go @@ -1,5 +1,9 @@ package consts +const ( + UserActiveKeyFmt = "user:active:%s" +) + type UserStatus string const ( diff --git a/backend/go.mod b/backend/go.mod index 34955bf..1855d9f 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -4,7 +4,7 @@ go 1.23.7 require ( entgo.io/ent v0.14.4 - github.com/GoYoko/web v1.1.0 + github.com/GoYoko/web v1.0.0 github.com/golang-migrate/migrate/v4 v4.18.3 github.com/google/uuid v1.6.0 github.com/google/wire v0.6.0 diff --git a/backend/go.sum b/backend/go.sum index 54d652c..9f2f330 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -8,8 +8,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60= github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM= -github.com/GoYoko/web v1.1.0 h1:nIbtol5z0Y03d0nHsvGjv+W0fgmFRGUL8fzPN3kmrOY= -github.com/GoYoko/web v1.1.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= +github.com/GoYoko/web v1.0.0 h1:kcNxz8BvpKavE0/iqatOmUeCXVghaoD5xYDiHDulVaE= +github.com/GoYoko/web v1.0.0/go.mod h1:DL9/gvuUG2jcBE1XUIY+9QBrrhdshzPEdxMCzR9jUHo= github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= diff --git a/backend/internal/middleware/active.go b/backend/internal/middleware/active.go new file mode 100644 index 0000000..d6d04c5 --- /dev/null +++ b/backend/internal/middleware/active.go @@ -0,0 +1,37 @@ +package middleware + +import ( + "context" + "fmt" + "log/slog" + "time" + + "github.com/chaitin/MonkeyCode/backend/consts" + "github.com/labstack/echo/v4" + "github.com/redis/go-redis/v9" +) + +type ActiveMiddleware struct { + redis *redis.Client + logger *slog.Logger +} + +func NewActiveMiddleware(redis *redis.Client, logger *slog.Logger) *ActiveMiddleware { + return &ActiveMiddleware{ + redis: redis, + logger: logger, + } +} + +func (a *ActiveMiddleware) Active() echo.MiddlewareFunc { + return func(next echo.HandlerFunc) echo.HandlerFunc { + return func(c echo.Context) error { + if apikey := GetApiKey(c); apikey != nil { + if err := a.redis.Set(context.Background(), fmt.Sprintf(consts.UserActiveKeyFmt, apikey.UserID), time.Now().Unix(), 0).Err(); err != nil { + a.logger.With("error", err).ErrorContext(c.Request().Context(), "failed to set user active status in Redis") + } + } + return next(c) + } + } +} diff --git a/backend/internal/openai/handler/v1/v1.go b/backend/internal/openai/handler/v1/v1.go index 24226c2..158c360 100644 --- a/backend/internal/openai/handler/v1/v1.go +++ b/backend/internal/openai/handler/v1/v1.go @@ -25,6 +25,7 @@ func NewV1Handler( proxy domain.Proxy, usecase domain.OpenAIUsecase, middleware *middleware.ProxyMiddleware, + active *middleware.ActiveMiddleware, ) *V1Handler { h := &V1Handler{ logger: logger.With(slog.String("handler", "openai")), @@ -35,10 +36,10 @@ func NewV1Handler( g := w.Group("/v1", middleware.Auth()) g.GET("/models", web.BaseHandler(h.ModelList)) - g.POST("/completion/accept", web.BindHandler(h.AcceptCompletion)) - g.POST("/chat/completions", web.BindHandler(h.ChatCompletion)) - g.POST("/completions", web.BindHandler(h.Completions)) - g.POST("/embeddings", web.BindHandler(h.Embeddings)) + g.POST("/completion/accept", web.BindHandler(h.AcceptCompletion), active.Active()) + g.POST("/chat/completions", web.BindHandler(h.ChatCompletion), active.Active()) + g.POST("/completions", web.BindHandler(h.Completions), active.Active()) + g.POST("/embeddings", web.BindHandler(h.Embeddings), active.Active()) return h } diff --git a/backend/internal/provider.go b/backend/internal/provider.go index 548e1b3..48a5862 100644 --- a/backend/internal/provider.go +++ b/backend/internal/provider.go @@ -39,6 +39,7 @@ var Provider = wire.NewSet( dashrepo.NewDashboardRepo, middleware.NewProxyMiddleware, middleware.NewAuthMiddleware, + middleware.NewActiveMiddleware, userV1.NewUserHandler, userrepo.NewUserRepo, userusecase.NewUserUsecase, diff --git a/backend/internal/proxy/proxy.go b/backend/internal/proxy/proxy.go index cfabd44..dfe9a32 100644 --- a/backend/internal/proxy/proxy.go +++ b/backend/internal/proxy/proxy.go @@ -629,7 +629,7 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon "apiBase", m.APIBase, "work_mode", mode, "requestHeader", newReq.Header, - "requestBody", newReq, + "requestBody", req, "taskID", taskID, "messages", cvt.Filter(req.Messages, func(i int, v openai.ChatCompletionMessage) (openai.ChatCompletionMessage, bool) { return v, v.Role != "system" diff --git a/backend/internal/user/usecase/user.go b/backend/internal/user/usecase/user.go index ff936cc..3d2f957 100644 --- a/backend/internal/user/usecase/user.go +++ b/backend/internal/user/usecase/user.go @@ -60,14 +60,36 @@ func (u *UserUsecase) List(ctx context.Context, req domain.ListReq) (*domain.Lis return nil, err } + ids := cvt.Iter(users, func(_ int, u *db.User) string { return u.ID.String() }) + m, err := u.getUserActive(ctx, ids) + if err != nil { + return nil, err + } + return &domain.ListUserResp{ PageInfo: p, Users: cvt.Iter(users, func(_ int, e *db.User) *domain.User { - return cvt.From(e, &domain.User{}).From(e) + return cvt.From(e, &domain.User{ + LastActiveAt: m[e.ID.String()], + }) }), }, nil } +func (u *UserUsecase) getUserActive(ctx context.Context, ids []string) (map[string]int64, error) { + m := make(map[string]int64) + for _, id := range ids { + key := fmt.Sprintf(consts.UserActiveKeyFmt, id) + if t, err := u.redis.Get(ctx, key).Int64(); err != nil { + u.logger.With("key", key).With("error", err).Warn("get user active time failed") + } else { + m[id] = t + } + } + + return m, nil +} + // AdminList implements domain.UserUsecase. func (u *UserUsecase) AdminList(ctx context.Context, page *web.Pagination) (*domain.ListAdminUserResp, error) { admins, p, err := u.repo.AdminList(ctx, page)