From e1dd05401833d74d034ccaac34ca4b7186230f87 Mon Sep 17 00:00:00 2001 From: KanadeSiina Date: Sun, 3 Sep 2023 09:01:04 +0800 Subject: [PATCH] Add basic problem service (#16) * Add problem mapper * Add problem option search test * Move service code * Problem router & service --- Makefile | 4 +- packages/mapper/problem.go | 102 +++++++++++++++++++++++ packages/mapper/problem_test.go | 54 ++++++++++++ packages/model/problem.go | 2 +- {user-service => service}/application.go | 3 +- service/problem/problem.go | 23 +++++ service/router/setup.go | 27 ++++++ user-service/router/setup.go | 16 ---- 8 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 packages/mapper/problem_test.go rename {user-service => service}/application.go (88%) create mode 100644 service/problem/problem.go create mode 100644 service/router/setup.go delete mode 100644 user-service/router/setup.go diff --git a/Makefile b/Makefile index 5072330..a6325e8 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ build: @echo "Building on $(OS)" go mod tidy go build -o bin/migrate_db migration/migrate_db.go - go build -o bin/user_service user-service/application.go + go build -o bin/service service/application.go .PHONY: clear-db clear-db: @@ -29,7 +29,7 @@ test: build check setup-db .PHONY: run run: build check setup-db - ./bin/user_service + ./bin/service .PHONY: help help: diff --git a/packages/mapper/problem.go b/packages/mapper/problem.go index ad5eac3..284a59b 100644 --- a/packages/mapper/problem.go +++ b/packages/mapper/problem.go @@ -1 +1,103 @@ package mapper + +import ( + "github.com/OJ-lab/oj-lab-services/packages/application" + "github.com/OJ-lab/oj-lab-services/packages/model" +) + +func CreateProblem(problem model.Problem) error { + db := application.GetDefaultDB() + return db.Create(&problem).Error +} + +func GetProblem(slug string) (model.Problem, error) { + db := application.GetDefaultDB() + db_problem := model.Problem{} + err := db.Model(&model.Problem{}).Preload("Tags").Where("Slug = ?", slug).First(&db_problem).Error + return db_problem, err +} + +func DeleteProblem(problem model.Problem) error { + db := application.GetDefaultDB() + return db.Delete(&model.Problem{Slug: problem.Slug}).Error +} + +func UpdateProblem(problem model.Problem) error { + db := application.GetDefaultDB() + return db.Model(&model.Problem{Slug: problem.Slug}).Updates(problem).Error +} + +type GetProblemOptions struct { + Slug string + Title string + Tags []*model.AlgorithmTag + Offset *int + Limit *int +} + +func CountProblemByOptions(options GetProblemOptions) (int64, error) { + db := application.GetDefaultDB() + var count int64 + + tagsList := []string{} + for _, tag := range options.Tags { + tagsList = append(tagsList, tag.Slug) + } + + tx := db. + Model(&model.Problem{}). + Joins("JOIN problem_algorithm_tags ON problem_algorithm_tags.problem_slug = problems.slug"). + Where("problem_algorithm_tags.algorithm_tag_slug in ?", tagsList). + Or("Slug = ?", options.Slug). + Or("Title = ?", options.Title). + Distinct(). + Preload("Tags") + + err := tx.Count(&count).Error + + return count, err +} + +func GetProblemByOptions(options GetProblemOptions) ([]model.Problem, int64, error) { + total, err := CountProblemByOptions(options) + if err != nil { + return nil, 0, err + } + + db := application.GetDefaultDB() + db_problems := []model.Problem{} + tagsList := []string{} + for _, tag := range options.Tags { + tagsList = append(tagsList, tag.Slug) + } + tx := db. + Model(&model.Problem{}). + Joins("JOIN problem_algorithm_tags ON problem_algorithm_tags.problem_slug = problems.slug"). + Where("problem_algorithm_tags.algorithm_tag_slug in ?", tagsList). + Or("Slug = ?", options.Slug). + Or("Title = ?", options.Title). + Distinct(). + Preload("Tags") + + if options.Offset != nil { + tx = tx.Offset(*options.Offset) + } + if options.Limit != nil { + tx = tx.Limit(*options.Limit) + } + + err = tx.Find(&db_problems).Error + if err != nil { + return nil, 0, err + } + + return db_problems, total, nil +} + +func GetTagsList(problem model.Problem) []string { + tagsList := []string{} + for _, tag := range problem.Tags { + tagsList = append(tagsList, tag.Slug) + } + return tagsList +} diff --git a/packages/mapper/problem_test.go b/packages/mapper/problem_test.go new file mode 100644 index 0000000..ea10e2d --- /dev/null +++ b/packages/mapper/problem_test.go @@ -0,0 +1,54 @@ +package mapper + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/OJ-lab/oj-lab-services/packages/model" +) + +func TestProblemMapper(t *testing.T) { + problem := model.Problem{ + Slug: "a+b-problem", + Title: "A+B Problem", + Description: "Given two integer A and B, please output the answer of A+B.", + Tags: []*model.AlgorithmTag{{Slug: "tag1"}, {Slug: "tag2"}}, + } + err := CreateProblem(problem) + if err != nil { + t.Error(err) + } + + dbProblem, err := GetProblem(problem.Slug) + if err != nil { + t.Error(err) + } + + problemJson, err := json.MarshalIndent(dbProblem, "", "\t") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", string(problemJson)) + + problemOption := GetProblemOptions{ + Slug: "", + Title: "", + Tags: []*model.AlgorithmTag{{Slug: "tag1"}}, + } + + dbProblems, problemCount, err := GetProblemByOptions(problemOption) + if err != nil { + t.Error(err) + } + fmt.Printf("problemCount: %d\n", problemCount) + if problemCount != 1 { + t.Error("problemCount should be 1") + } + + problemsJson, err := json.MarshalIndent(dbProblems, "", "\t") + if err != nil { + t.Error(err) + } + fmt.Printf("%+v\n", string(problemsJson)) +} diff --git a/packages/model/problem.go b/packages/model/problem.go index 0aac58d..0cd233a 100644 --- a/packages/model/problem.go +++ b/packages/model/problem.go @@ -4,7 +4,7 @@ type Problem struct { MetaFields Slug string `gorm:"primaryKey"` Title string `gorm:"not null"` - Discription string `gorm:"not null"` + Description string `gorm:"not null"` Tags []*AlgorithmTag `gorm:"many2many:problem_algorithm_tags;"` } diff --git a/user-service/application.go b/service/application.go similarity index 88% rename from user-service/application.go rename to service/application.go index 188fd38..3c563f0 100644 --- a/user-service/application.go +++ b/service/application.go @@ -2,7 +2,7 @@ package main import ( "github.com/OJ-lab/oj-lab-services/packages/application" - "github.com/OJ-lab/oj-lab-services/user-service/router" + "github.com/OJ-lab/oj-lab-services/service/router" "github.com/gin-gonic/gin" ) @@ -25,6 +25,7 @@ func main() { r := gin.Default() gin.SetMode(serviceMode) router.SetupUserRouter(r) + router.SetupProblemRoute(r) err := r.Run(servicePort) if err != nil { diff --git a/service/problem/problem.go b/service/problem/problem.go new file mode 100644 index 0000000..ac71dc3 --- /dev/null +++ b/service/problem/problem.go @@ -0,0 +1,23 @@ +package problem + +import ( + "github.com/OJ-lab/oj-lab-services/packages/mapper" + "github.com/OJ-lab/oj-lab-services/packages/utils" + "github.com/gin-gonic/gin" +) + +func GetProblemInfo(c *gin.Context) { + slug := c.Param("slug") + problem, err := mapper.GetProblem(slug) + if err != nil { + utils.ApplyError(c, err) + return + } + + c.JSON(200, gin.H{ + "slug": problem.Slug, + "title": problem.Title, + "description": problem.Description, + "tags": mapper.GetTagsList(problem), + }) +} diff --git a/service/router/setup.go b/service/router/setup.go new file mode 100644 index 0000000..196a06c --- /dev/null +++ b/service/router/setup.go @@ -0,0 +1,27 @@ +package router + +import ( + "net/http" + + "github.com/OJ-lab/oj-lab-services/service/problem" + "github.com/gin-gonic/gin" +) + +func SetupUserRouter(r *gin.Engine) { + g := r.Group("/api/user") + { + g.GET("/health", func(c *gin.Context) { + c.String(http.StatusOK, "Hello, this is user service") + }) + } +} + +func SetupProblemRoute(r *gin.Engine) { + g := r.Group("/api/v1/problem") + { + g.GET("/health", func(c *gin.Context) { + c.String(http.StatusOK, "Hello, this is problem service") + }) + g.GET("/:slug", problem.GetProblemInfo) + } +} diff --git a/user-service/router/setup.go b/user-service/router/setup.go deleted file mode 100644 index 5924e14..0000000 --- a/user-service/router/setup.go +++ /dev/null @@ -1,16 +0,0 @@ -package router - -import ( - "net/http" - - "github.com/gin-gonic/gin" -) - -func SetupUserRouter(r *gin.Engine) { - g := r.Group("/api/user") - { - g.GET("/health", func(c *gin.Context) { - c.String(http.StatusOK, "Hello, this is user service") - }) - } -}