Skip to content
Merged
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
5 changes: 3 additions & 2 deletions backend/cmd/server/wire_gen.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions backend/consts/user.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
package consts

const (
UserActiveKeyFmt = "user:active:%s"
)

type UserStatus string

const (
Expand Down
2 changes: 1 addition & 1 deletion backend/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions backend/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
37 changes: 37 additions & 0 deletions backend/internal/middleware/active.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
}
9 changes: 5 additions & 4 deletions backend/internal/openai/handler/v1/v1.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")),
Expand All @@ -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
}

Expand Down
1 change: 1 addition & 0 deletions backend/internal/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ var Provider = wire.NewSet(
dashrepo.NewDashboardRepo,
middleware.NewProxyMiddleware,
middleware.NewAuthMiddleware,
middleware.NewActiveMiddleware,
userV1.NewUserHandler,
userrepo.NewUserRepo,
userusecase.NewUserUsecase,
Expand Down
2 changes: 1 addition & 1 deletion backend/internal/proxy/proxy.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
24 changes: 23 additions & 1 deletion backend/internal/user/usecase/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
Loading