From e2681ca6255588157bfdd28c1b11a6d06059e24b Mon Sep 17 00:00:00 2001 From: slhmy <31381093+slhmy@users.noreply.github.com> Date: Sat, 16 Sep 2023 21:33:28 +0800 Subject: [PATCH] Adjust code structure (#21) * Adjust code structure * Squashed commit of the following: commit 4cc24fe02f71bdda5c4e0e02bff32d4f798ca58d Author: slhmy <1484836413@qq.com> Date: Wed Sep 13 22:26:58 2023 +0800 Perf commit 75f907381d3373ff0a49235151eeff26219d3035 Author: slhmy <1484836413@qq.com> Date: Wed Sep 13 22:14:38 2023 +0800 Project refactor commit d132b9f5bd31e6c390d4d381fc81683868658938 Author: slhmy <31381093+slhmy@users.noreply.github.com> Date: Wed Sep 13 11:28:30 2023 +0000 Perf code structure * Resolve conflicts * Adjust project structure * Remove outdated part of README.md * Make code more real world * Refactor and support login * Perf according to new structure --- .gitignore | 4 +- README.md | 25 +--- application/migrate_db/main.go | 13 +- application/server/handler/problem.go | 50 +++---- application/server/handler/user.go | 66 +++++++++- application/server/main.go | 5 +- config/development.toml | 2 +- .../core => core/agent/gorm}/database.go | 5 +- {packages => core}/agent/judger/judge.go | 6 +- {packages => core}/agent/minio/client.go | 2 +- core/agent/minio/local.go | 53 ++++++++ core/agent/redis/client.go | 14 ++ core/agent/redis/login_session.go | 34 +++++ core/auth/jwt.go | 80 +++++++++++ core/auth/login_session.go | 53 ++++++++ {packages/core => core}/config.go | 0 core/error.go | 64 +++++++++ {packages/core => core}/log.go | 0 core/middleware/error.go | 33 +++++ core/middleware/login_session.go | 48 +++++++ go.mod | 2 +- oj-lab-services-structure.drawio | 124 ++++++++++++++++++ packages/agent/minio/problem.go | 50 ------- packages/core/error.go | 65 --------- packages/core/jwt.go | 51 ------- packages/core/jwt_test.go | 28 ---- packages/mapper/user_test.go | 33 ----- service/business/problem.go | 14 +- {packages => service}/mapper/problem.go | 16 +-- {packages => service}/mapper/user.go | 30 +++-- {packages => service}/model/meta.go | 0 {packages => service}/model/problem.go | 0 {packages => service}/model/submission.go | 0 {packages => service}/model/user.go | 10 +- service/problem.go | 21 +-- service/user.go | 54 ++++++++ {packages => test}/core/init_test.go | 4 +- test/core/jwt_test.go | 28 ++++ {packages => test}/mapper/problem_test.go | 13 +- test/mapper/user_test.go | 42 ++++++ {tests => test}/minio_test.go | 4 +- {tests => test}/redis_test.go | 2 +- 42 files changed, 814 insertions(+), 334 deletions(-) rename {packages/core => core/agent/gorm}/database.go (84%) rename {packages => core}/agent/judger/judge.go (87%) rename {packages => core}/agent/minio/client.go (97%) create mode 100644 core/agent/minio/local.go create mode 100644 core/agent/redis/client.go create mode 100644 core/agent/redis/login_session.go create mode 100644 core/auth/jwt.go create mode 100644 core/auth/login_session.go rename {packages/core => core}/config.go (100%) create mode 100644 core/error.go rename {packages/core => core}/log.go (100%) create mode 100644 core/middleware/error.go create mode 100644 core/middleware/login_session.go create mode 100644 oj-lab-services-structure.drawio delete mode 100644 packages/agent/minio/problem.go delete mode 100644 packages/core/error.go delete mode 100644 packages/core/jwt.go delete mode 100644 packages/core/jwt_test.go delete mode 100644 packages/mapper/user_test.go rename {packages => service}/mapper/problem.go (89%) rename {packages => service}/mapper/user.go (80%) rename {packages => service}/model/meta.go (100%) rename {packages => service}/model/problem.go (100%) rename {packages => service}/model/submission.go (100%) rename {packages => service}/model/user.go (63%) create mode 100644 service/user.go rename {packages => test}/core/init_test.go (80%) create mode 100644 test/core/jwt_test.go rename {packages => test}/mapper/problem_test.go (73%) create mode 100644 test/mapper/user_test.go rename {tests => test}/minio_test.go (93%) rename {tests => test}/redis_test.go (97%) diff --git a/.gitignore b/.gitignore index af2f80c..7c5f8c6 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ # vendor/ *.ini -!packages/config/ini/example.ini -!packages/config/ini/test.ini +!package/config/ini/example.ini +!package/config/ini/test.ini bin/ \ No newline at end of file diff --git a/README.md b/README.md index d3d6e13..ec3cf61 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,6 @@ # oj-lab-services -This is a collection of services for OJ-lab systems. - -Each service is supposed to run either separately or in a batch, -so that the deployment of the online-judge system can be more flexible. - -## Services - -To understand the philosophy of our design, -you can simply think an online-judge system consists of three main parts: -- user -- problem -- judge - -If every request is trusted -(and thanks to the usage of JWT authentication, this is now a solved problem), -each of the above parts can be considered as a single service. - -So separating them can make the build of functionality clearer than ever before. - -## Model migration - -There is a migration script in every service directory. - -You will need to run the migration script before you can use the service. +Currently the backend server for OJ Lab. ## Development diff --git a/application/migrate_db/main.go b/application/migrate_db/main.go index 1a7bdf1..cbc8c47 100644 --- a/application/migrate_db/main.go +++ b/application/migrate_db/main.go @@ -1,14 +1,14 @@ package main import ( - "github.com/OJ-lab/oj-lab-services/packages/core" - "github.com/OJ-lab/oj-lab-services/packages/mapper" - "github.com/OJ-lab/oj-lab-services/packages/model" + "github.com/OJ-lab/oj-lab-services/core/agent/gorm" + "github.com/OJ-lab/oj-lab-services/service/mapper" + "github.com/OJ-lab/oj-lab-services/service/model" "github.com/sirupsen/logrus" ) func main() { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() err := db.AutoMigrate(&model.User{}, &model.Problem{}) if err != nil { panic("failed to migrate database") @@ -20,5 +20,10 @@ func main() { Description: `Write a program that prints "Hello! %s" to the standard output (stdout).`, }) + mapper.CreateUser(model.User{ + Account: "admin", + Password: func() *string { s := "admin"; return &s }(), + }) + logrus.Info("migrate tables success") } diff --git a/application/server/handler/problem.go b/application/server/handler/problem.go index ec07081..d4b7df7 100644 --- a/application/server/handler/problem.go +++ b/application/server/handler/problem.go @@ -3,9 +3,9 @@ package handler import ( "net/http" - "github.com/OJ-lab/oj-lab-services/packages/agent/judger" - "github.com/OJ-lab/oj-lab-services/packages/mapper" + "github.com/OJ-lab/oj-lab-services/core/agent/judger" "github.com/OJ-lab/oj-lab-services/service" + "github.com/OJ-lab/oj-lab-services/service/mapper" "github.com/gin-gonic/gin" ) @@ -15,22 +15,22 @@ func SetupProblemRoute(r *gin.Engine) { g.GET("/greet", func(c *gin.Context) { c.String(http.StatusOK, "Hello, this is problem service") }) - g.GET("/:slug", GetProblemInfo) - g.PUT("/:slug/package", PutProblemPackage) - g.POST("/:slug/judge", Judge) + g.GET("/:slug", getProblemInfo) + g.PUT("/:slug/package", putProblemPackage) + g.POST("/:slug/judge", judge) } } -func GetProblemInfo(ctx *gin.Context) { - slug := ctx.Param("slug") +func getProblemInfo(ginCtx *gin.Context) { + slug := ginCtx.Param("slug") - problemInfo, err := service.GetProblemInfo(slug) + problemInfo, err := service.GetProblemInfo(ginCtx, slug) if err != nil { - ctx.Error(err) + ginCtx.Error(err) return } - ctx.JSON(200, gin.H{ + ginCtx.JSON(200, gin.H{ "slug": problemInfo.Slug, "title": problemInfo.Title, "description": problemInfo.Description, @@ -38,37 +38,37 @@ func GetProblemInfo(ctx *gin.Context) { }) } -func PutProblemPackage(ctx *gin.Context) { - slug := ctx.Param("slug") - file, err := ctx.FormFile("file") +func putProblemPackage(ginCtx *gin.Context) { + slug := ginCtx.Param("slug") + file, err := ginCtx.FormFile("file") if err != nil { - ctx.Error(err) + ginCtx.Error(err) return } zipFile := "/tmp/" + slug + ".zip" - if err := ctx.SaveUploadedFile(file, zipFile); err != nil { - ctx.Error(err) + if err := ginCtx.SaveUploadedFile(file, zipFile); err != nil { + ginCtx.Error(err) return } - service.PutProblemPackage(slug, zipFile) + service.PutProblemPackage(ginCtx, slug, zipFile) - ctx.Done() + ginCtx.Done() } -func Judge(ctx *gin.Context) { - slug := ctx.Param("slug") +func judge(ginCtx *gin.Context) { + slug := ginCtx.Param("slug") judgeRequest := judger.JudgeRequest{} - if err := ctx.ShouldBindJSON(&judgeRequest); err != nil { - ctx.Error(err) + if err := ginCtx.ShouldBindJSON(&judgeRequest); err != nil { + ginCtx.Error(err) return } - body, err := service.Judge(slug, judgeRequest) + body, err := service.Judge(ginCtx, slug, judgeRequest) if err != nil { - ctx.Error(err) + ginCtx.Error(err) return } - ctx.JSON(200, body) + ginCtx.JSON(200, body) } diff --git a/application/server/handler/user.go b/application/server/handler/user.go index f3aac02..e881917 100644 --- a/application/server/handler/user.go +++ b/application/server/handler/user.go @@ -3,14 +3,76 @@ package handler import ( "net/http" + "github.com/OJ-lab/oj-lab-services/core" + "github.com/OJ-lab/oj-lab-services/core/middleware" + "github.com/OJ-lab/oj-lab-services/service" "github.com/gin-gonic/gin" ) func SetupUserRouter(r *gin.Engine) { g := r.Group("/api/v1/user") { - g.GET("/health", func(c *gin.Context) { - c.String(http.StatusOK, "Hello, this is user service") + g.GET("/health", func(ginCtx *gin.Context) { + ginCtx.String(http.StatusOK, "Hello, this is user service") }) + g.POST("/login", login) + g.GET("/me", middleware.HandleRequireLogin, me) + g.GET("/check-exist", checkUserExist) } } + +type loginBody struct { + Account string `json:"account"` + Password string `json:"password"` +} + +func login(ginCtx *gin.Context) { + body := &loginBody{} + err := ginCtx.BindJSON(body) + if err != nil { + core.NewInvalidParamError("body", "invalid body").AppendToGin(ginCtx) + return + } + + lsId, svcErr := service.StartLoginSession(ginCtx, body.Account, body.Password) + if svcErr != nil { + svcErr.AppendToGin(ginCtx) + return + } + middleware.SetLoginSessionCookie(ginCtx, *lsId) + + ginCtx.String(http.StatusOK, "") +} + +func me(ginCtx *gin.Context) { + ls := middleware.GetLoginSession(ginCtx) + if ls == nil { + core.NewUnauthorizedError("not logined").AppendToGin(ginCtx) + return + } + user, svcErr := service.GetUser(ginCtx, ls.Account) + if svcErr != nil { + svcErr.AppendToGin(ginCtx) + return + } + + ginCtx.JSON(http.StatusOK, user) +} + +func checkUserExist(ginCtx *gin.Context) { + account := ginCtx.Query("account") + if account == "" { + core.NewInvalidParamError("account", "account cannot be empty").AppendToGin(ginCtx) + return + } + + exist, err := service.CheckUserExist(ginCtx, account) + if err != nil { + ginCtx.Error(err) + return + } + + ginCtx.JSON(http.StatusOK, gin.H{ + "exist": exist, + }) +} diff --git a/application/server/main.go b/application/server/main.go index dca8099..7b87f93 100644 --- a/application/server/main.go +++ b/application/server/main.go @@ -2,7 +2,8 @@ package main import ( "github.com/OJ-lab/oj-lab-services/application/server/handler" - "github.com/OJ-lab/oj-lab-services/packages/core" + "github.com/OJ-lab/oj-lab-services/core" + "github.com/OJ-lab/oj-lab-services/core/middleware" "github.com/gin-gonic/gin" ) @@ -23,7 +24,7 @@ func init() { func main() { r := gin.Default() - r.Use(core.HandleError) + r.Use(middleware.HandleError) gin.SetMode(serviceMode) handler.SetupUserRouter(r) handler.SetupProblemRoute(r) diff --git a/config/development.toml b/config/development.toml index 8637df3..a480f33 100644 --- a/config/development.toml +++ b/config/development.toml @@ -22,4 +22,4 @@ endpoint = "localhost:9000" accessKeyID = "minio-root-user" secretAccessKey = "minio-root-password" useSSL = false -bucketName = "oj-lab-problem-packages" +bucketName = "oj-lab-problem-package" diff --git a/packages/core/database.go b/core/agent/gorm/database.go similarity index 84% rename from packages/core/database.go rename to core/agent/gorm/database.go index 32415fe..f8f9f51 100644 --- a/packages/core/database.go +++ b/core/agent/gorm/database.go @@ -1,6 +1,7 @@ -package core +package gorm import ( + "github.com/OJ-lab/oj-lab-services/core" "gorm.io/driver/postgres" "gorm.io/gorm" ) @@ -12,7 +13,7 @@ var db *gorm.DB var dsn string func init() { - dsn = AppConfig.GetString(dsnProp) + dsn = core.AppConfig.GetString(dsnProp) if dsn == "" { panic("database dsn is not set") } diff --git a/packages/agent/judger/judge.go b/core/agent/judger/judge.go similarity index 87% rename from packages/agent/judger/judge.go rename to core/agent/judger/judge.go index 7345786..c0198ef 100644 --- a/packages/agent/judger/judge.go +++ b/core/agent/judger/judge.go @@ -6,7 +6,7 @@ import ( "net/http" "net/url" - "github.com/OJ-lab/oj-lab-services/packages/core" + "github.com/OJ-lab/oj-lab-services/core" ) const JUDGER_HOST_PROP = "judger.host" @@ -23,8 +23,8 @@ type JudgeRequest struct { SrcLanguage string `json:"src_language"` } -func PostJudgeSync(packageSlug string, judgeRequest JudgeRequest) ([]map[string]interface{}, error) { - url, err := url.JoinPath(judgerHost, JUDGER_JUDGE_PATH, packageSlug) +func PostJudgeSync(packagelug string, judgeRequest JudgeRequest) ([]map[string]interface{}, error) { + url, err := url.JoinPath(judgerHost, JUDGER_JUDGE_PATH, packagelug) if err != nil { return nil, err } diff --git a/packages/agent/minio/client.go b/core/agent/minio/client.go similarity index 97% rename from packages/agent/minio/client.go rename to core/agent/minio/client.go index a43fcd5..4036543 100644 --- a/packages/agent/minio/client.go +++ b/core/agent/minio/client.go @@ -4,7 +4,7 @@ import ( "context" "log" - "github.com/OJ-lab/oj-lab-services/packages/core" + "github.com/OJ-lab/oj-lab-services/core" "github.com/minio/minio-go/v7" "github.com/minio/minio-go/v7/pkg/credentials" ) diff --git a/core/agent/minio/local.go b/core/agent/minio/local.go new file mode 100644 index 0000000..c5974c5 --- /dev/null +++ b/core/agent/minio/local.go @@ -0,0 +1,53 @@ +package minio + +import ( + "context" + "io/fs" + "path/filepath" + "strings" + + "github.com/minio/minio-go/v7" +) + +func PutLocalObjects(ctx context.Context, rootName, localPath string) error { + minioClient := GetMinioClient() + bucketName := GetBucketName() + + // Remove old + objectsCh := minioClient.ListObjects(ctx, bucketName, minio.ListObjectsOptions{ + Prefix: rootName, + Recursive: true, + }) + for objInfo := range objectsCh { + if objInfo.Err != nil { + return objInfo.Err + } + + err := minioClient.RemoveObject(ctx, bucketName, objInfo.Key, minio.RemoveObjectOptions{}) + if err != nil { + return err + } + } + + filepath.Walk(localPath, func(path string, info fs.FileInfo, err error) error { + if err != nil { + return err + } + + if info.IsDir() { + return nil + } + + objectName := filepath.Join(rootName, strings.Replace(path, localPath, "", 1)) + _, err = minioClient.FPutObject(ctx, bucketName, + objectName, path, + minio.PutObjectOptions{}) + if err != nil { + return err + } + + return nil + }) + + return nil +} diff --git a/core/agent/redis/client.go b/core/agent/redis/client.go new file mode 100644 index 0000000..1b2fb4f --- /dev/null +++ b/core/agent/redis/client.go @@ -0,0 +1,14 @@ +package redis + +import "github.com/redis/go-redis/v9" + +var redisClient *redis.Client + +func GetDefaultRedisClient() *redis.Client { + if redisClient == nil { + redisClient = redis.NewClient(&redis.Options{ + Addr: "localhost:6379", + }) + } + return redisClient +} diff --git a/core/agent/redis/login_session.go b/core/agent/redis/login_session.go new file mode 100644 index 0000000..3dd4e83 --- /dev/null +++ b/core/agent/redis/login_session.go @@ -0,0 +1,34 @@ +package redis + +import ( + "context" + "fmt" + "time" +) + +const loginSessionKeyFormat = "LS_%s" +const loginSessionDuration = time.Second * 30 + +func SetLoginSession(ctx context.Context, sessionId string, sessionString string) error { + redisClient := GetDefaultRedisClient() + key := fmt.Sprintf(loginSessionKeyFormat, sessionId) + + err := redisClient.Set(ctx, key, sessionString, loginSessionDuration).Err() + if err != nil { + return err + } + + return nil +} + +func GetLoginSession(ctx context.Context, sessionId string) (*string, error) { + redisClient := GetDefaultRedisClient() + key := fmt.Sprintf(loginSessionKeyFormat, sessionId) + + val, err := redisClient.Get(ctx, key).Result() + if err != nil { + return nil, err + } + + return &val, nil +} diff --git a/core/auth/jwt.go b/core/auth/jwt.go new file mode 100644 index 0000000..ecab85c --- /dev/null +++ b/core/auth/jwt.go @@ -0,0 +1,80 @@ +package auth + +import ( + "errors" + "time" + + "github.com/OJ-lab/oj-lab-services/core" + "github.com/golang-jwt/jwt/v4" +) + +var jwtSecret string +var jwtDuration time.Duration + +func init() { + jwtSecret = core.AppConfig.GetString("jwt.secret") + jwtDuration = core.AppConfig.GetDuration("jwt.duration") +} + +type AuthToken struct { + Account string + Roles []string + Expires time.Time +} + +func (a *AuthToken) Valid() error { + if a.Expires.Before(time.Now()) { + return errors.New("token is expired") + } + return nil +} + +func (a *AuthToken) ToJWTMapClaims() jwt.MapClaims { + return jwt.MapClaims{ + "account": a.Account, + "roles": a.Roles, + "exp": a.Expires.Unix(), + } +} + +func GetAuthTokenFromJWTMapClaims(claims jwt.MapClaims) *AuthToken { + roleInterface := claims["roles"].([]interface{}) + roles := make([]string, len(roleInterface)) + for i, role := range roleInterface { + roles[i] = role.(string) + } + return &AuthToken{ + Account: claims["account"].(string), + Roles: roles, + Expires: time.Unix(int64(claims["exp"].(float64)), 0), + } +} + +func GenerateAuthTokenString(account string, roles ...string) (string, error) { + duration := jwtDuration + token := AuthToken{ + Account: account, + Roles: roles, + Expires: time.Now().Add(duration), + } + + jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, token.ToJWTMapClaims()) + return jwtToken.SignedString([]byte(jwtSecret)) +} + +func ParseAuthTokenString(tokenString string) (string, []string, error) { + jwtToken, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { + return []byte(jwtSecret), nil + }) + + if jwtToken.Valid { + claims := jwtToken.Claims.(jwt.MapClaims) + token := GetAuthTokenFromJWTMapClaims(claims) + return token.Account, token.Roles, nil + } else if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorExpired != 0 { + return "", nil, errors.New("token is expired") + } + } + return "", nil, errors.New("invalid token") +} diff --git a/core/auth/login_session.go b/core/auth/login_session.go new file mode 100644 index 0000000..67b5b15 --- /dev/null +++ b/core/auth/login_session.go @@ -0,0 +1,53 @@ +package auth + +import ( + "context" + "encoding/json" + + "github.com/OJ-lab/oj-lab-services/core/agent/redis" + "github.com/google/uuid" +) + +type LoginSession struct { + Id string `json:"-"` + Account string `json:"account"` +} + +func NewLoginSession(account string) *LoginSession { + id := uuid.New().String() + + return &LoginSession{ + Id: id, + Account: account, + } +} + +func (ls *LoginSession) SaveToRedis(ctx context.Context) error { + lsBytes, err := json.Marshal(ls) + if err != nil { + return err + } + lsString := string(lsBytes) + + err = redis.SetLoginSession(ctx, ls.Id, lsString) + if err != nil { + return err + } + + return nil +} + +func CheckLoginSession(ctx context.Context, id string) (*LoginSession, error) { + lsString, err := redis.GetLoginSession(ctx, id) + if err != nil { + return nil, err + } + + ls := &LoginSession{} + err = json.Unmarshal([]byte(*lsString), ls) + if err != nil { + return nil, err + } + + return ls, nil +} diff --git a/packages/core/config.go b/core/config.go similarity index 100% rename from packages/core/config.go rename to core/config.go diff --git a/core/error.go b/core/error.go new file mode 100644 index 0000000..88cc0fa --- /dev/null +++ b/core/error.go @@ -0,0 +1,64 @@ +package core + +import ( + "fmt" + "runtime" + + "github.com/gin-gonic/gin" +) + +type SeviceError struct { + Code int `json:"code"` + Msg string `json:"msg"` + stackTrace []uintptr `json:"-"` +} + +func (se *SeviceError) ToGinError() *gin.Error { + return &gin.Error{ + Err: fmt.Errorf("%v", se.Msg), + Type: gin.ErrorTypePrivate, + Meta: se, + } +} + +func (se *SeviceError) AppendToGin(ginCtx *gin.Context) { + ginCtx.Errors = append(ginCtx.Errors, se.ToGinError()) +} + +func (se *SeviceError) CaptureStackTrace() *SeviceError { + se.stackTrace = []uintptr{} + runtime.Callers(2, se.stackTrace) + + return se +} + +func IsServiceError(err interface{}) bool { + _, ok := err.(*SeviceError) + return ok +} + +func NewInternalError(msg string) *SeviceError { + return &SeviceError{ + Code: 500, + Msg: msg, + } +} + +func NewUnauthorizedError(msg string) *SeviceError { + return &SeviceError{ + Code: 401, + Msg: msg, + } +} + +func NewInvalidParamError(param string, hints ...string) *SeviceError { + msg := fmt.Sprintf("invalid param: %s", param) + for _, hint := range hints { + msg += fmt.Sprintf(", %s", hint) + } + + return &SeviceError{ + Code: 400, + Msg: fmt.Sprintf("invalid param: %s", param), + } +} diff --git a/packages/core/log.go b/core/log.go similarity index 100% rename from packages/core/log.go rename to core/log.go diff --git a/core/middleware/error.go b/core/middleware/error.go new file mode 100644 index 0000000..85656ac --- /dev/null +++ b/core/middleware/error.go @@ -0,0 +1,33 @@ +package middleware + +import ( + "fmt" + + "github.com/OJ-lab/oj-lab-services/core" + "github.com/gin-gonic/gin" + "github.com/sirupsen/logrus" +) + +func GetServiceError(ginErr gin.Error) *core.SeviceError { + if core.IsServiceError(ginErr.Meta) { + return ginErr.Meta.(*core.SeviceError) + } else { + serviceErr := core.NewInternalError(fmt.Sprintf("%v", ginErr.Err)) + serviceErr.CaptureStackTrace() + return serviceErr + } +} + +func HandleError(ginCtx *gin.Context) { + ginCtx.Next() + + errCount := len(ginCtx.Errors) + if errCount > 0 { + logrus.Errorf("Last error from GIN middleware: %+v", ginCtx.Errors[errCount-1].Err) + err := GetServiceError(*ginCtx.Errors[errCount-1]) + ginCtx.JSON(err.Code, gin.H{ + "code": err.Code, + "msg": err.Msg, + }) + } +} diff --git a/core/middleware/login_session.go b/core/middleware/login_session.go new file mode 100644 index 0000000..5ad0042 --- /dev/null +++ b/core/middleware/login_session.go @@ -0,0 +1,48 @@ +package middleware + +import ( + "time" + + "github.com/OJ-lab/oj-lab-services/core" + "github.com/OJ-lab/oj-lab-services/core/auth" + "github.com/gin-gonic/gin" +) + +const ( + loginSessionCookieMaxAge = time.Hour * 24 * 7 + loginSessionIdCookieName = "LS_ID" + loginSessionGinCtxKey = "login_session" +) + +func HandleRequireLogin(ginCtx *gin.Context) { + cookie, err := ginCtx.Cookie(loginSessionIdCookieName) + if err != nil { + core.NewUnauthorizedError("login session not found").AppendToGin(ginCtx) + ginCtx.Abort() + return + } + + ls, err := auth.CheckLoginSession(ginCtx, cookie) + if err != nil { + core.NewUnauthorizedError("invalid login session").AppendToGin(ginCtx) + ginCtx.Abort() + return + } + + ginCtx.Set(loginSessionGinCtxKey, ls) + + ginCtx.Next() +} + +func GetLoginSession(ginCtx *gin.Context) *auth.LoginSession { + ls, exist := ginCtx.Get(loginSessionGinCtxKey) + if !exist { + return nil + } + return ls.(*auth.LoginSession) +} + +func SetLoginSessionCookie(ginCtx *gin.Context, lsId string) { + ginCtx.SetCookie(loginSessionIdCookieName, lsId, + int(loginSessionCookieMaxAge.Seconds()), "/", "", false, true) +} diff --git a/go.mod b/go.mod index 83f2ffb..df1fa25 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module github.com/OJ-lab/oj-lab-services go 1.20 require ( + github.com/google/uuid v1.3.1 github.com/sirupsen/logrus v1.9.3 github.com/spf13/viper v1.16.0 gorm.io/driver/postgres v1.5.2 @@ -18,7 +19,6 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/fsnotify/fsnotify v1.6.0 // indirect github.com/gabriel-vasile/mimetype v1.4.2 // indirect - github.com/google/uuid v1.3.1 // indirect github.com/hashicorp/hcl v1.0.0 // indirect github.com/jackc/pgx/v5 v5.4.3 // indirect github.com/klauspost/compress v1.16.7 // indirect diff --git a/oj-lab-services-structure.drawio b/oj-lab-services-structure.drawio new file mode 100644 index 0000000..a557572 --- /dev/null +++ b/oj-lab-services-structure.drawio @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/agent/minio/problem.go b/packages/agent/minio/problem.go deleted file mode 100644 index ce0178c..0000000 --- a/packages/agent/minio/problem.go +++ /dev/null @@ -1,50 +0,0 @@ -package minio - -import ( - "context" - "io/fs" - "log" - "path/filepath" - "strings" - - "github.com/minio/minio-go/v7" -) - -func PutProblemPackage(slug string, pkgDir string) error { - ctx := context.Background() - minioClient := GetMinioClient() - - // remove old package - objectsCh := minioClient.ListObjects(ctx, GetBucketName(), minio.ListObjectsOptions{ - Prefix: slug, - Recursive: true, - }) - for objInfo := range objectsCh { - if objInfo.Err != nil { - return objInfo.Err - } - - err := minioClient.RemoveObject(ctx, GetBucketName(), objInfo.Key, minio.RemoveObjectOptions{}) - if err != nil { - return err - } - } - - filepath.Walk(pkgDir, func(path string, info fs.FileInfo, err error) error { - if info.IsDir() { - return nil - } - relativePath := filepath.Join(slug, strings.Replace(path, pkgDir, "", 1)) - - _, minioErr := minioClient.FPutObject(ctx, GetBucketName(), - relativePath, - path, - minio.PutObjectOptions{}) - if minioErr != nil { - log.Fatalln(minioErr) - } - return minioErr - }) - - return nil -} diff --git a/packages/core/error.go b/packages/core/error.go deleted file mode 100644 index 61775bd..0000000 --- a/packages/core/error.go +++ /dev/null @@ -1,65 +0,0 @@ -package core - -import ( - "fmt" - "runtime" - - "github.com/gin-gonic/gin" - "github.com/sirupsen/logrus" -) - -type SeviceError struct { - Code int `json:"code"` - Msg string `json:"msg"` - stackTrace []uintptr `json:"-"` -} - -func (se *SeviceError) CaptureStackTrace() *SeviceError { - se.stackTrace = []uintptr{} - runtime.Callers(2, se.stackTrace) - - return se -} - -func IsServiceError(err interface{}) bool { - _, ok := err.(*SeviceError) - return ok -} - -func WrapToServiceError(err interface{}) *SeviceError { - if IsServiceError(err) { - return err.(*SeviceError) - } else { - serviceErr := NewInternalError(fmt.Sprintf("%v", err)) - serviceErr.CaptureStackTrace() - return serviceErr - } -} - -func NewInternalError(msg string) *SeviceError { - return &SeviceError{ - Code: 500, - Msg: msg, - } -} - -func NewUnAuthorizedError(msg string) *SeviceError { - return &SeviceError{ - Code: 401, - Msg: msg, - } -} - -func HandleError(ginCtx *gin.Context) { - ginCtx.Next() - - errCount := len(ginCtx.Errors) - if errCount > 0 { - logrus.Errorf("Last error from GIN middleware: %+v", ginCtx.Errors[errCount-1].Err) - err := WrapToServiceError(ginCtx.Errors[errCount-1].Err) - ginCtx.JSON(err.Code, gin.H{ - "code": err.Code, - "msg": err.Msg, - }) - } -} diff --git a/packages/core/jwt.go b/packages/core/jwt.go deleted file mode 100644 index f99d9f9..0000000 --- a/packages/core/jwt.go +++ /dev/null @@ -1,51 +0,0 @@ -package core - -import ( - "errors" - "time" - - "github.com/OJ-lab/oj-lab-services/packages/model" - "github.com/golang-jwt/jwt/v4" -) - -var jwtSecret string -var jwtDuration time.Duration - -func init() { - jwtSecret = AppConfig.GetString("jwt.secret") - jwtDuration = AppConfig.GetDuration("jwt.duration") -} - -func GenerateTokenString(account string, roles []*model.Role) (string, error) { - duration := jwtDuration - - token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ - "account": account, - "roles": roles, - "exp": time.Now().Add(duration).Unix(), - }) - return token.SignedString([]byte(jwtSecret)) -} - -func ParseTokenString(tokenString string) (string, []*model.Role, error) { - token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { - return []byte(jwtSecret), nil - }) - if err != nil { - return "", nil, err - } - if token.Valid { - roleInterface := token.Claims.(jwt.MapClaims)["roles"].([]interface{}) - roles := make([]*model.Role, len(roleInterface)) - for i, role := range roleInterface { - roleMap := role.(map[string]interface{}) - roles[i] = &model.Role{Name: roleMap["name"].(string)} - } - return token.Claims.(jwt.MapClaims)["account"].(string), roles, nil - } else if ve, ok := err.(*jwt.ValidationError); ok { - if ve.Errors&jwt.ValidationErrorExpired != 0 { - return "", nil, errors.New("token is expired") - } - } - return "", nil, errors.New("invalid token") -} diff --git a/packages/core/jwt_test.go b/packages/core/jwt_test.go deleted file mode 100644 index 23b658f..0000000 --- a/packages/core/jwt_test.go +++ /dev/null @@ -1,28 +0,0 @@ -package core - -import ( - "log" - "testing" - - "github.com/OJ-lab/oj-lab-services/packages/model" -) - -func TestGenerateTokenString(t *testing.T) { - tokenString, err := GenerateTokenString("account", []*model.Role{{Name: "admin"}}) - if err != nil { - panic(err) - } - log.Print(tokenString) -} - -func TestParseTokenString(t *testing.T) { - tokenString, err := GenerateTokenString("account", []*model.Role{{Name: "admin"}}) - if err != nil { - panic(err) - } - account, role, err := ParseTokenString(tokenString) - if err != nil { - panic(err) - } - log.Println(account, role) -} diff --git a/packages/mapper/user_test.go b/packages/mapper/user_test.go deleted file mode 100644 index c1e3c7e..0000000 --- a/packages/mapper/user_test.go +++ /dev/null @@ -1,33 +0,0 @@ -package mapper - -import ( - "encoding/json" - "fmt" - "testing" - - "github.com/OJ-lab/oj-lab-services/packages/model" -) - -func TestUserMapper(t *testing.T) { - password := "test" - user := model.User{ - Account: "test", - Password: &password, - Roles: []*model.Role{{Name: "admin"}}, - } - err := CreateUser(user) - if err != nil { - t.Error(err) - } - - dbUser, err := GetUser(user.Account) - if err != nil { - t.Error(err) - } - - userJson, err := json.MarshalIndent(dbUser, "", "\t") - if err != nil { - t.Error(err) - } - fmt.Printf("%+v\n", string(userJson)) -} diff --git a/service/business/problem.go b/service/business/problem.go index 7c07cee..e05bdd8 100644 --- a/service/business/problem.go +++ b/service/business/problem.go @@ -2,12 +2,15 @@ package business import ( "archive/zip" + "context" "io" "os" "path/filepath" + + "github.com/OJ-lab/oj-lab-services/core/agent/minio" ) -func UnzipProblemPackage(zipFile, targetDir string) error { +func UnzipProblemPackage(ctx context.Context, zipFile, targetDir string) error { err := os.RemoveAll(targetDir) if err != nil { return err @@ -45,3 +48,12 @@ func UnzipProblemPackage(zipFile, targetDir string) error { return nil } + +func PutProblemPackage(ctx context.Context, slug string, pkgDir string) error { + err := minio.PutLocalObjects(ctx, slug, pkgDir) + if err != nil { + return err + } + + return nil +} diff --git a/packages/mapper/problem.go b/service/mapper/problem.go similarity index 89% rename from packages/mapper/problem.go rename to service/mapper/problem.go index 5585a60..fa64109 100644 --- a/packages/mapper/problem.go +++ b/service/mapper/problem.go @@ -1,17 +1,17 @@ package mapper import ( - "github.com/OJ-lab/oj-lab-services/packages/core" - "github.com/OJ-lab/oj-lab-services/packages/model" + "github.com/OJ-lab/oj-lab-services/core/agent/gorm" + "github.com/OJ-lab/oj-lab-services/service/model" ) func CreateProblem(problem model.Problem) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() return db.Create(&problem).Error } func GetProblem(slug string) (*model.Problem, error) { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() db_problem := model.Problem{} err := db.Model(&model.Problem{}).Preload("Tags").Where("Slug = ?", slug).First(&db_problem).Error if err != nil { @@ -22,12 +22,12 @@ func GetProblem(slug string) (*model.Problem, error) { } func DeleteProblem(problem model.Problem) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() return db.Delete(&model.Problem{Slug: problem.Slug}).Error } func UpdateProblem(problem model.Problem) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() return db.Model(&model.Problem{Slug: problem.Slug}).Updates(problem).Error } @@ -40,7 +40,7 @@ type GetProblemOptions struct { } func CountProblemByOptions(options GetProblemOptions) (int64, error) { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() var count int64 tagsList := []string{} @@ -68,7 +68,7 @@ func GetProblemByOptions(options GetProblemOptions) ([]model.Problem, int64, err return nil, 0, err } - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() db_problems := []model.Problem{} tagsList := []string{} for _, tag := range options.Tags { diff --git a/packages/mapper/user.go b/service/mapper/user.go similarity index 80% rename from packages/mapper/user.go rename to service/mapper/user.go index d639144..a14519d 100644 --- a/packages/mapper/user.go +++ b/service/mapper/user.go @@ -1,13 +1,14 @@ package mapper import ( - "github.com/OJ-lab/oj-lab-services/packages/core" - "github.com/OJ-lab/oj-lab-services/packages/model" + "github.com/OJ-lab/oj-lab-services/core/agent/gorm" + "github.com/OJ-lab/oj-lab-services/service/model" "github.com/alexedwards/argon2id" ) +// Account, Password, Roles will be used to create a new user. func CreateUser(user model.User) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() hashedPassword, err := argon2id.CreateHash(*user.Password, argon2id.DefaultParams) if err != nil { return err @@ -23,7 +24,7 @@ func CreateUser(user model.User) error { } func GetUser(account string) (*model.User, error) { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() db_user := model.User{} err := db.Model(&model.User{}).Preload("Roles").Where("account = ?", account).First(&db_user).Error if err != nil { @@ -33,13 +34,24 @@ func GetUser(account string) (*model.User, error) { return &db_user, err } +func GetPublicUser(account string) (*model.PublicUser, error) { + db := gorm.GetDefaultDB() + db_user := model.PublicUser{} + err := db.Model(&model.User{}).Where("account = ?", account).First(&db_user).Error + if err != nil { + return nil, err + } + + return &db_user, err +} + func DeleteUser(user model.User) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() return db.Delete(&model.User{Account: user.Account}).Error } func UpdateUser(update model.User) error { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() old := model.User{} err := db.Where("account = ?", update.Account).First(&old).Error @@ -77,7 +89,7 @@ type GetUserOptions struct { // Count the total number of users that match the options, // ignoring the offset and limit. func CountUserByOptions(options GetUserOptions) (int64, error) { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() var count int64 tx := db. @@ -97,7 +109,7 @@ func GetUserByOptions(options GetUserOptions) ([]model.User, int64, error) { return nil, 0, err } - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() db_users := []model.User{} tx := db. @@ -120,7 +132,7 @@ func GetUserByOptions(options GetUserOptions) ([]model.User, int64, error) { } func CheckUserPassword(account string, password string) (bool, error) { - db := core.GetDefaultDB() + db := gorm.GetDefaultDB() user := model.User{} err := db.Where("account = ?", account).First(&user).Error if err != nil { diff --git a/packages/model/meta.go b/service/model/meta.go similarity index 100% rename from packages/model/meta.go rename to service/model/meta.go diff --git a/packages/model/problem.go b/service/model/problem.go similarity index 100% rename from packages/model/problem.go rename to service/model/problem.go diff --git a/packages/model/submission.go b/service/model/submission.go similarity index 100% rename from packages/model/submission.go rename to service/model/submission.go diff --git a/packages/model/user.go b/service/model/user.go similarity index 63% rename from packages/model/user.go rename to service/model/user.go index 9d64396..5ed33b0 100644 --- a/packages/model/user.go +++ b/service/model/user.go @@ -7,11 +7,17 @@ type User struct { Password *string `gorm:"-:all" json:"password,omitempty"` HashedPassword string `gorm:"not null" json:"-"` Roles []*Role `gorm:"many2many:user_roles;" json:"roles,omitempty"` - Email string `gorm:"unique" json:"email,omitempty"` - Mobile string `gorm:"unique" json:"mobile,omitempty"` + Email *string `gorm:"unique" json:"email,omitempty"` + Mobile *string `gorm:"unique" json:"mobile,omitempty"` } type Role struct { Name string `gorm:"primaryKey" json:"name"` Users []*User `gorm:"many2many:user_roles" json:"users,omitempty"` } + +// User model that will be exposed to the public. +type PublicUser struct { + Account string `json:"account"` + Name string `json:"name"` +} diff --git a/service/problem.go b/service/problem.go index d62a864..b26d14b 100644 --- a/service/problem.go +++ b/service/problem.go @@ -1,14 +1,15 @@ package service import ( - "github.com/OJ-lab/oj-lab-services/packages/agent/judger" - "github.com/OJ-lab/oj-lab-services/packages/agent/minio" - "github.com/OJ-lab/oj-lab-services/packages/mapper" - "github.com/OJ-lab/oj-lab-services/packages/model" + "context" + + "github.com/OJ-lab/oj-lab-services/core/agent/judger" "github.com/OJ-lab/oj-lab-services/service/business" + "github.com/OJ-lab/oj-lab-services/service/mapper" + "github.com/OJ-lab/oj-lab-services/service/model" ) -func GetProblemInfo(slug string) (*model.Problem, error) { +func GetProblemInfo(ctx context.Context, slug string) (*model.Problem, error) { problem, err := mapper.GetProblem(slug) if err != nil { return nil, err @@ -16,14 +17,14 @@ func GetProblemInfo(slug string) (*model.Problem, error) { return problem, nil } -func PutProblemPackage(slug, zipFile string) error { - targetDir := "/tmp/" + slug - err := business.UnzipProblemPackage(zipFile, targetDir) +func PutProblemPackage(ctx context.Context, slug, zipFile string) error { + localDir := "/tmp/" + slug + err := business.UnzipProblemPackage(ctx, zipFile, localDir) if err != nil { return err } - err = minio.PutProblemPackage(slug, targetDir) + err = business.PutProblemPackage(ctx, slug, localDir) if err != nil { return err } @@ -31,7 +32,7 @@ func PutProblemPackage(slug, zipFile string) error { return nil } -func Judge(slug string, judgeRequest judger.JudgeRequest) ( +func Judge(ctx context.Context, slug string, judgeRequest judger.JudgeRequest) ( []map[string]interface{}, error, ) { body, err := judger.PostJudgeSync(slug, judgeRequest) diff --git a/service/user.go b/service/user.go new file mode 100644 index 0000000..2543127 --- /dev/null +++ b/service/user.go @@ -0,0 +1,54 @@ +package service + +import ( + "context" + + "github.com/OJ-lab/oj-lab-services/core" + "github.com/OJ-lab/oj-lab-services/core/auth" + "github.com/OJ-lab/oj-lab-services/service/mapper" + "github.com/OJ-lab/oj-lab-services/service/model" + "github.com/sirupsen/logrus" +) + +func GetUser(ctx context.Context, account string) (*model.User, *core.SeviceError) { + user, err := mapper.GetUser(account) + if err != nil { + return nil, core.NewInternalError("failed to get user") + } + + return user, nil +} + +func CheckUserExist(ctx context.Context, account string) (bool, error) { + getOptions := mapper.GetUserOptions{ + Account: account, + } + count, err := mapper.CountUserByOptions(getOptions) + if err != nil { + return false, err + } + + if count > 1 { + logrus.Warnf("user %s has %d records", account, count) + } + + return count > 0, nil +} + +func StartLoginSession(ctx context.Context, account, password string) (*string, *core.SeviceError) { + match, err := mapper.CheckUserPassword(account, password) + if err != nil { + return nil, core.NewInternalError(err.Error()) + } + if !match { + return nil, core.NewUnauthorizedError("invalid account or password") + } + + loginSession := auth.NewLoginSession(account) + err = loginSession.SaveToRedis(ctx) + if err != nil { + return nil, core.NewInternalError(err.Error()) + } + + return &loginSession.Id, nil +} diff --git a/packages/core/init_test.go b/test/core/init_test.go similarity index 80% rename from packages/core/init_test.go rename to test/core/init_test.go index f65b43f..34ac83e 100644 --- a/packages/core/init_test.go +++ b/test/core/init_test.go @@ -1,4 +1,4 @@ -package core +package core_test import ( "testing" @@ -6,6 +6,8 @@ import ( "github.com/spf13/viper" ) +const logLevelProp = "log.level" + func TestInit(T *testing.T) { logLevel := viper.GetString(logLevelProp) if logLevel != "debug" { diff --git a/test/core/jwt_test.go b/test/core/jwt_test.go new file mode 100644 index 0000000..7daf526 --- /dev/null +++ b/test/core/jwt_test.go @@ -0,0 +1,28 @@ +package core_test + +import ( + "log" + "testing" + + "github.com/OJ-lab/oj-lab-services/core/auth" +) + +func TestGenerateTokenString(t *testing.T) { + tokenString, err := auth.GenerateAuthTokenString("account", []string{"admin"}...) + if err != nil { + panic(err) + } + log.Print(tokenString) +} + +func TestParseTokenString(t *testing.T) { + tokenString, err := auth.GenerateAuthTokenString("account", []string{"admin"}...) + if err != nil { + panic(err) + } + account, role, err := auth.ParseAuthTokenString(tokenString) + if err != nil { + panic(err) + } + log.Println(account, role) +} diff --git a/packages/mapper/problem_test.go b/test/mapper/problem_test.go similarity index 73% rename from packages/mapper/problem_test.go rename to test/mapper/problem_test.go index ea10e2d..87eecd6 100644 --- a/packages/mapper/problem_test.go +++ b/test/mapper/problem_test.go @@ -1,11 +1,12 @@ -package mapper +package mapper_test import ( "encoding/json" "fmt" "testing" - "github.com/OJ-lab/oj-lab-services/packages/model" + "github.com/OJ-lab/oj-lab-services/service/mapper" + "github.com/OJ-lab/oj-lab-services/service/model" ) func TestProblemMapper(t *testing.T) { @@ -15,12 +16,12 @@ func TestProblemMapper(t *testing.T) { Description: "Given two integer A and B, please output the answer of A+B.", Tags: []*model.AlgorithmTag{{Slug: "tag1"}, {Slug: "tag2"}}, } - err := CreateProblem(problem) + err := mapper.CreateProblem(problem) if err != nil { t.Error(err) } - dbProblem, err := GetProblem(problem.Slug) + dbProblem, err := mapper.GetProblem(problem.Slug) if err != nil { t.Error(err) } @@ -31,13 +32,13 @@ func TestProblemMapper(t *testing.T) { } fmt.Printf("%+v\n", string(problemJson)) - problemOption := GetProblemOptions{ + problemOption := mapper.GetProblemOptions{ Slug: "", Title: "", Tags: []*model.AlgorithmTag{{Slug: "tag1"}}, } - dbProblems, problemCount, err := GetProblemByOptions(problemOption) + dbProblems, problemCount, err := mapper.GetProblemByOptions(problemOption) if err != nil { t.Error(err) } diff --git a/test/mapper/user_test.go b/test/mapper/user_test.go new file mode 100644 index 0000000..f5ed49f --- /dev/null +++ b/test/mapper/user_test.go @@ -0,0 +1,42 @@ +package mapper_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/OJ-lab/oj-lab-services/service/mapper" + "github.com/OJ-lab/oj-lab-services/service/model" +) + +func TestUserMapper(t *testing.T) { + user := model.User{ + Account: "test", + Password: func() *string { s := "test"; return &s }(), + Roles: []*model.Role{{Name: "admin"}}, + } + err := mapper.CreateUser(user) + if err != nil { + t.Error(err) + } + + dbUser, err := mapper.GetUser(user.Account) + if err != nil { + t.Error(err) + } + userJson, err := json.MarshalIndent(dbUser, "", "\t") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", string(userJson)) + + dbPublicUser, err := mapper.GetPublicUser(user.Account) + if err != nil { + t.Error(err) + } + publicUserJson, err := json.MarshalIndent(dbPublicUser, "", "\t") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", string(publicUserJson)) +} diff --git a/tests/minio_test.go b/test/minio_test.go similarity index 93% rename from tests/minio_test.go rename to test/minio_test.go index d182f69..78cf5af 100644 --- a/tests/minio_test.go +++ b/test/minio_test.go @@ -1,4 +1,4 @@ -package tests +package test import ( "io/fs" @@ -7,7 +7,7 @@ import ( "strings" "testing" - minioAgent "github.com/OJ-lab/oj-lab-services/packages/agent/minio" + minioAgent "github.com/OJ-lab/oj-lab-services/core/agent/minio" "github.com/minio/minio-go/v7" ) diff --git a/tests/redis_test.go b/test/redis_test.go similarity index 97% rename from tests/redis_test.go rename to test/redis_test.go index 32e95a1..032fa02 100644 --- a/tests/redis_test.go +++ b/test/redis_test.go @@ -1,4 +1,4 @@ -package tests +package test import ( "context"