From 1aee49be9a87ff77edb8e0924bb5ae9d20b8e204 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Fri, 21 Feb 2025 21:58:37 +0900 Subject: [PATCH 01/22] Refactor codes --- .../codepair/v1/models/model_hello_request.go | 1 - backend-go/internal/core/handlers.go | 26 ----------- backend-go/internal/core/hello/handler.go | 45 ++++++++++++------- backend-go/internal/core/hello/model.go | 1 + backend-go/internal/core/hello/repository.go | 7 ++- backend-go/internal/core/hello/server.go | 15 +++++++ backend-go/internal/core/hello/service.go | 27 ++++++----- .../internal/infra/database/mongo/hello.go | 37 ++++++++++++--- backend-go/internal/server/routes.go | 12 ----- backend-go/internal/server/server.go | 6 +-- 10 files changed, 97 insertions(+), 80 deletions(-) delete mode 100644 backend-go/internal/core/handlers.go create mode 100644 backend-go/internal/core/hello/server.go delete mode 100644 backend-go/internal/server/routes.go diff --git a/backend-go/api/codepair/v1/models/model_hello_request.go b/backend-go/api/codepair/v1/models/model_hello_request.go index 453ccf26..13ff94c2 100644 --- a/backend-go/api/codepair/v1/models/model_hello_request.go +++ b/backend-go/api/codepair/v1/models/model_hello_request.go @@ -1,7 +1,6 @@ package models type HelloRequest struct { - // New nickname to say hello Nickname string `json:"nickname"` } diff --git a/backend-go/internal/core/handlers.go b/backend-go/internal/core/handlers.go deleted file mode 100644 index 1a2da3d3..00000000 --- a/backend-go/internal/core/handlers.go +++ /dev/null @@ -1,26 +0,0 @@ -package core - -import ( - "github.com/yorkie-team/codepair/backend/internal/core/hello" - "github.com/yorkie-team/codepair/backend/internal/infra/database/mongo" -) - -type Handlers struct { - Hello *hello.Handler -} - -// NewHandlers creates a new handlers. -func NewHandlers() *Handlers { - // Repositories - helloRepository := mongo.NewHelloRepository() - - // Services - helloService := hello.NewService(helloRepository) - - // Handlers - helloHandler := hello.NewHandler(helloService) - - return &Handlers{ - Hello: helloHandler, - } -} diff --git a/backend-go/internal/core/hello/handler.go b/backend-go/internal/core/hello/handler.go index 5dc96208..214d3991 100644 --- a/backend-go/internal/core/hello/handler.go +++ b/backend-go/internal/core/hello/handler.go @@ -1,6 +1,9 @@ package hello import ( + "fmt" + "strconv" + "github.com/labstack/echo/v4" "github.com/yorkie-team/codepair/backend/api/codepair/v1/models" @@ -8,32 +11,40 @@ import ( ) type Handler struct { - helloService *Service -} - -// NewHandler creates a new handler for hello. -func NewHandler(service *Service) *Handler { - return &Handler{ - helloService: service, - } + service *Service } -// HelloCodePair returns a hello message for a given CodePairVisitor. -func (h *Handler) HelloCodePair(e echo.Context) error { +// createHello returns a hello message for a given CodePairVisitor. +func (h *Handler) createHello(c echo.Context) error { req := new(models.HelloRequest) - if err := http.BindAndValidateRequest(e, req); err != nil { - return err + if err := http.BindAndValidateRequest(c, req); err != nil { + return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) + } + + if err := h.service.createHello(CodePairVisitor{Nickname: req.Nickname}); err != nil { + return http.NewErrorResponse(c, fmt.Errorf("failed to create hello message for visitor %s: %w", req.Nickname, err)) } - helloMessage, err := h.helloService.HelloCodePair(e, CodePairVisitor{ - Nickname: req.Nickname, + return http.NewOkResponse(c, models.HelloResponse{ + Message: fmt.Sprintf("Hello, %s!", req.Nickname), }) +} + +// readHello returns a hello message for a given CodePairVisitor. +func (h *Handler) readHello(c echo.Context) error { + idStr := c.Param("id") + id, err := strconv.Atoi(idStr) + if err != nil { + return http.NewErrorResponse(c, fmt.Errorf("invalid ID format: %s", idStr)) + } + + nickname, err := h.service.readNickname(id) if err != nil { - return http.NewErrorResponse(e, err) + return http.NewErrorResponse(c, fmt.Errorf("failed to find hello message for visitor with ID %d: %w", id, err)) } - return http.NewOkResponse(e, models.HelloResponse{ - Message: helloMessage, + return http.NewOkResponse(c, models.HelloResponse{ + Message: fmt.Sprintf("Hello, %s!", nickname), }) } diff --git a/backend-go/internal/core/hello/model.go b/backend-go/internal/core/hello/model.go index 3acf1bd1..dbde36c1 100644 --- a/backend-go/internal/core/hello/model.go +++ b/backend-go/internal/core/hello/model.go @@ -3,5 +3,6 @@ package hello // CodePairVisitor is a visitor for CodePair type CodePairVisitor struct { // Nickname is the nickname of the visitor + ID int Nickname string } diff --git a/backend-go/internal/core/hello/repository.go b/backend-go/internal/core/hello/repository.go index 5a718599..85c256d5 100644 --- a/backend-go/internal/core/hello/repository.go +++ b/backend-go/internal/core/hello/repository.go @@ -1,6 +1,9 @@ package hello type Repository interface { - // ReadHelloMessageFor reads a hello message for a given CodePairVisitor - ReadHelloMessageFor(codePairVisitor CodePairVisitor) (string, error) + // FindHelloMessage find a hello message for a given CodePairVisitor + FindHelloMessage(id int) (CodePairVisitor, error) + + // CreateHelloMessage creates a hello message for given CodePairVisitor + CreateHelloMessage(codePairVisitor CodePairVisitor) error } diff --git a/backend-go/internal/core/hello/server.go b/backend-go/internal/core/hello/server.go new file mode 100644 index 00000000..ea456698 --- /dev/null +++ b/backend-go/internal/core/hello/server.go @@ -0,0 +1,15 @@ +package hello + +import "github.com/labstack/echo/v4" + +// New creates a new handler for hello. +func New(e *echo.Echo, repo Repository) { + svc := &Service{ + repository: repo, + } + handler := &Handler{ + service: svc, + } + e.GET("/hello", handler.readHello) + e.POST("/hello", handler.createHello) +} diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index 98777f4d..c717941f 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -1,29 +1,28 @@ package hello import ( - "github.com/labstack/echo/v4" - - "github.com/yorkie-team/codepair/backend/internal/transport/http" + "fmt" ) type Service struct { - helloRepository Repository + repository Repository } -// NewService creates a new service for hello. -func NewService(repository Repository) *Service { - return &Service{ - helloRepository: repository, +// createHello returns a hello message for a given CodePairVisitor +func (s *Service) createHello(codePairVisitor CodePairVisitor) error { + if err := s.repository.CreateHelloMessage(codePairVisitor); err != nil { + return fmt.Errorf("failed to create hello message for visitor %v: %w", codePairVisitor, err) } + + return nil } -// HelloCodePair returns a hello message for a given CodePairVisitor -func (s *Service) HelloCodePair(e echo.Context, codePairVisitor CodePairVisitor) (string, error) { - helloMessage, err := s.helloRepository.ReadHelloMessageFor(codePairVisitor) +// readNickname returns a hello message for a given CodePairVisitor +func (s *Service) readNickname(id int) (string, error) { + hello, err := s.repository.FindHelloMessage(id) if err != nil { - e.Logger().Fatal(err) - return "", http.ErrInternalServerError + return "", fmt.Errorf("failed to find hello message for ID %d: %w", id, err) } - return helloMessage, nil + return hello.Nickname, nil } diff --git a/backend-go/internal/infra/database/mongo/hello.go b/backend-go/internal/infra/database/mongo/hello.go index 52aa5d7c..16d23eec 100644 --- a/backend-go/internal/infra/database/mongo/hello.go +++ b/backend-go/internal/infra/database/mongo/hello.go @@ -1,14 +1,41 @@ package mongo -import "github.com/yorkie-team/codepair/backend/internal/core/hello" +import ( + "time" + + "github.com/yorkie-team/codepair/backend/internal/core/hello" +) + +type CodePairVisitor struct { + ID int + Nickname string + CreatedAt time.Time + UpdatedAt time.Time +} + +func (c *CodePairVisitor) ToDomain() hello.CodePairVisitor { + return hello.CodePairVisitor{ + ID: c.ID, + Nickname: c.Nickname, + } +} type HelloRepository struct{} // NewHelloRepository creates a new HelloRepository. -func NewHelloRepository() HelloRepository { - return HelloRepository{} +func NewHelloRepository() *HelloRepository { + return &HelloRepository{} +} + +func (h *HelloRepository) FindHelloMessage(id int) (hello.CodePairVisitor, error) { + he := &CodePairVisitor{ + ID: id, + Nickname: "find", + } + return he.ToDomain(), nil } -func (h HelloRepository) ReadHelloMessageFor(codePairVisitor hello.CodePairVisitor) (string, error) { - return "Hello, " + codePairVisitor.Nickname + "!", nil +func (h *HelloRepository) CreateHelloMessage(codePairVisitor hello.CodePairVisitor) error { + + return nil } diff --git a/backend-go/internal/server/routes.go b/backend-go/internal/server/routes.go deleted file mode 100644 index d9fc1ec2..00000000 --- a/backend-go/internal/server/routes.go +++ /dev/null @@ -1,12 +0,0 @@ -package server - -import ( - "github.com/labstack/echo/v4" - - "github.com/yorkie-team/codepair/backend/internal/core" -) - -// RegisterRoutes registers routes for the server. -func RegisterRoutes(e *echo.Echo, handlers *core.Handlers) { - e.POST("/hello", handlers.Hello.HelloCodePair) -} diff --git a/backend-go/internal/server/server.go b/backend-go/internal/server/server.go index 4da21f18..583ca960 100644 --- a/backend-go/internal/server/server.go +++ b/backend-go/internal/server/server.go @@ -8,7 +8,8 @@ import ( "github.com/labstack/echo/v4" "github.com/yorkie-team/codepair/backend/internal/config" - "github.com/yorkie-team/codepair/backend/internal/core" + "github.com/yorkie-team/codepair/backend/internal/core/hello" + "github.com/yorkie-team/codepair/backend/internal/infra/database/mongo" ) type CodePair struct { @@ -18,8 +19,7 @@ type CodePair struct { // New creates a new CodePair server. func New(e *echo.Echo, conf *config.Config) *CodePair { - handlers := core.NewHandlers() - RegisterRoutes(e, handlers) + hello.New(e, mongo.NewHelloRepository()) cp := &CodePair{ config: conf, From d6e733323aeab804006dcf67806e5ad4aee88091 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Fri, 21 Feb 2025 22:18:44 +0900 Subject: [PATCH 02/22] Lint --- backend-go/internal/infra/database/mongo/.gitkeep | 1 - backend-go/internal/infra/database/mongo/hello.go | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 backend-go/internal/infra/database/mongo/.gitkeep diff --git a/backend-go/internal/infra/database/mongo/.gitkeep b/backend-go/internal/infra/database/mongo/.gitkeep deleted file mode 100644 index 8b137891..00000000 --- a/backend-go/internal/infra/database/mongo/.gitkeep +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend-go/internal/infra/database/mongo/hello.go b/backend-go/internal/infra/database/mongo/hello.go index 16d23eec..b94d9306 100644 --- a/backend-go/internal/infra/database/mongo/hello.go +++ b/backend-go/internal/infra/database/mongo/hello.go @@ -35,7 +35,7 @@ func (h *HelloRepository) FindHelloMessage(id int) (hello.CodePairVisitor, error return he.ToDomain(), nil } -func (h *HelloRepository) CreateHelloMessage(codePairVisitor hello.CodePairVisitor) error { +func (h *HelloRepository) CreateHelloMessage(_ hello.CodePairVisitor) error { return nil } From 50783b956a44c286c141f16a34a976a37e414bd1 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 23 Feb 2025 11:25:05 +0900 Subject: [PATCH 03/22] Add docker-compose --- backend-go/docker/docekr-compose.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 backend-go/docker/docekr-compose.yml diff --git a/backend-go/docker/docekr-compose.yml b/backend-go/docker/docekr-compose.yml new file mode 100644 index 00000000..0e207cfe --- /dev/null +++ b/backend-go/docker/docekr-compose.yml @@ -0,0 +1,22 @@ +version: "3.8" + +services: + mongo: + build: + context: ./mongodb_replica + args: + MONGO_VERSION: 4 + environment: + MONGO_REPLICA_HOST: 127.0.0.1 + MONGO_REPLICA_PORT: 27017 + MONGO_INITDB_DATABASE: "codepair" + MONGO_COMMAND: "mongo" + ports: + - "27017:27017" + restart: unless-stopped + healthcheck: + test: + ["CMD", "mongo", "admin", "--port", "27017", "--eval", "db.adminCommand('ping').ok"] + interval: 5s + timeout: 2s + retries: 20 From 38a9a3e2e022f4debd132afdc743a9aa15e76f9d Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 23 Feb 2025 11:25:57 +0900 Subject: [PATCH 04/22] Add mongo connection --- backend-go/go.mod | 1 + backend-go/go.sum | 2 ++ .../internal/infra/database/mongodb/client.go | 33 +++++++++++++++++++ 3 files changed, 36 insertions(+) create mode 100644 backend-go/internal/infra/database/mongodb/client.go diff --git a/backend-go/go.mod b/backend-go/go.mod index 1c898c67..676db2eb 100644 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -33,6 +33,7 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect + go.mongodb.org/mongo-driver/v2 v2.0.1 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.32.0 // indirect diff --git a/backend-go/go.sum b/backend-go/go.sum index 21b9d179..9e610a21 100644 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -75,6 +75,8 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +go.mongodb.org/mongo-driver/v2 v2.0.1 h1:mhB/ZJkLSv6W6LGzY7sEjpZif47+JdfEEXjlLCIv7Qc= +go.mongodb.org/mongo-driver/v2 v2.0.1/go.mod h1:w7iFnTcQDMXtdXwcvyG3xljYpoBa1ErkI0yOzbkZ9b8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= diff --git a/backend-go/internal/infra/database/mongodb/client.go b/backend-go/internal/infra/database/mongodb/client.go new file mode 100644 index 00000000..0328eef5 --- /dev/null +++ b/backend-go/internal/infra/database/mongodb/client.go @@ -0,0 +1,33 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" + "go.mongodb.org/mongo-driver/v2/mongo/readpref" + + "github.com/yorkie-team/codepair/backend/internal/config" +) + +// Dial creates an instance of Mongo and dials the given MongoDB. +func Dial(conf *config.Mongo) (*mongo.Client, error) { + client, err := mongo.Connect( + options.Client(). + ApplyURI(conf.ConnectionURI). + SetConnectTimeout(conf.ConnectionTimeout), + ) + if err != nil { + return nil, fmt.Errorf("connect to mongo: %w", err) + } + + ctxPing, cancel := context.WithTimeout(context.Background(), conf.PingTimeout) + defer cancel() + + if err := client.Ping(ctxPing, readpref.Primary()); err != nil { + return nil, fmt.Errorf("ping mongo: %w", err) + } + + return client, nil +} From 19a0bc32559fe94aed3eb95b9570617375aa4e34 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 23 Feb 2025 13:55:00 +0900 Subject: [PATCH 05/22] Refactor - remove repository in `core` - `core` imports `database` - `mongodb` imports `database` - add CRUD operations about `hello` --- backend-go/cmd/codepair/main.go | 5 +- backend-go/internal/core/hello/handler.go | 52 +++++-- backend-go/internal/core/hello/model.go | 8 -- backend-go/internal/core/hello/repository.go | 9 -- backend-go/internal/core/hello/server.go | 16 ++- backend-go/internal/core/hello/service.go | 48 +++++-- backend-go/internal/infra/database/hello.go | 30 ++++ .../internal/infra/database/mongo/hello.go | 41 ------ .../internal/infra/database/mongodb/hello.go | 130 ++++++++++++++++++ backend-go/internal/server/server.go | 13 +- 10 files changed, 258 insertions(+), 94 deletions(-) delete mode 100644 backend-go/internal/core/hello/model.go delete mode 100644 backend-go/internal/core/hello/repository.go create mode 100644 backend-go/internal/infra/database/hello.go delete mode 100644 backend-go/internal/infra/database/mongo/hello.go create mode 100644 backend-go/internal/infra/database/mongodb/hello.go diff --git a/backend-go/cmd/codepair/main.go b/backend-go/cmd/codepair/main.go index 71b26bb6..94fd8142 100644 --- a/backend-go/cmd/codepair/main.go +++ b/backend-go/cmd/codepair/main.go @@ -24,7 +24,10 @@ func main() { e.Logger.Fatal(err) } - cp := server.New(e, conf) + cp, err := server.New(e, conf) + if err != nil { + e.Logger.Fatal(err) + } if err := cp.Start(); err != nil { e.Logger.Fatal(err) diff --git a/backend-go/internal/core/hello/handler.go b/backend-go/internal/core/hello/handler.go index 214d3991..60bcce55 100644 --- a/backend-go/internal/core/hello/handler.go +++ b/backend-go/internal/core/hello/handler.go @@ -2,7 +2,6 @@ package hello import ( "fmt" - "strconv" "github.com/labstack/echo/v4" @@ -10,41 +9,64 @@ import ( "github.com/yorkie-team/codepair/backend/internal/transport/http" ) +// Handler handles HTTP requests for hello messages. type Handler struct { service *Service } -// createHello returns a hello message for a given CodePairVisitor. +// createHello handles the POST /hello endpoint. +// It creates a new visitor record and returns a hello message. func (h *Handler) createHello(c echo.Context) error { req := new(models.HelloRequest) if err := http.BindAndValidateRequest(c, req); err != nil { return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) } - - if err := h.service.createHello(CodePairVisitor{Nickname: req.Nickname}); err != nil { - return http.NewErrorResponse(c, fmt.Errorf("failed to create hello message for visitor %s: %w", req.Nickname, err)) + if err := h.service.createHello(req); err != nil { + return http.NewErrorResponse(c, err) } - return http.NewOkResponse(c, models.HelloResponse{ Message: fmt.Sprintf("Hello, %s!", req.Nickname), }) } -// readHello returns a hello message for a given CodePairVisitor. +// readHello handles the GET /hello/:id endpoint. +// It retrieves a visitor record by its id and returns a hello message. func (h *Handler) readHello(c echo.Context) error { - idStr := c.Param("id") - id, err := strconv.Atoi(idStr) - if err != nil { - return http.NewErrorResponse(c, fmt.Errorf("invalid ID format: %s", idStr)) - } - + id := c.Param("id") nickname, err := h.service.readNickname(id) if err != nil { - return http.NewErrorResponse(c, fmt.Errorf("failed to find hello message for visitor with ID %d: %w", id, err)) + return http.NewErrorResponse(c, fmt.Errorf("failed to find hello message for visitor with ID %s: %w", id, err)) } - return http.NewOkResponse(c, models.HelloResponse{ Message: fmt.Sprintf("Hello, %s!", nickname), }) } + +// updateHello handles the PUT /hello/:id endpoint. +// It updates an existing visitor record and returns a confirmation message. +func (h *Handler) updateHello(c echo.Context) error { + id := c.Param("id") + req := new(models.HelloRequest) + if err := http.BindAndValidateRequest(c, req); err != nil { + return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) + } + if err := h.service.updateHello(id, req); err != nil { + return http.NewErrorResponse(c, err) + } + return http.NewOkResponse(c, models.HelloResponse{ + Message: fmt.Sprintf("Hello updated, new nickname: %s", req.Nickname), + }) +} + +// deleteHello handles the DELETE /hello/:id endpoint. +// It deletes a visitor record by its id and returns a confirmation message. +func (h *Handler) deleteHello(c echo.Context) error { + id := c.Param("id") + if err := h.service.deleteHello(id); err != nil { + return http.NewErrorResponse(c, err) + } + return http.NewOkResponse(c, map[string]string{ + "message": fmt.Sprintf("Visitor with id %s deleted", id), + }) +} diff --git a/backend-go/internal/core/hello/model.go b/backend-go/internal/core/hello/model.go deleted file mode 100644 index dbde36c1..00000000 --- a/backend-go/internal/core/hello/model.go +++ /dev/null @@ -1,8 +0,0 @@ -package hello - -// CodePairVisitor is a visitor for CodePair -type CodePairVisitor struct { - // Nickname is the nickname of the visitor - ID int - Nickname string -} diff --git a/backend-go/internal/core/hello/repository.go b/backend-go/internal/core/hello/repository.go deleted file mode 100644 index 85c256d5..00000000 --- a/backend-go/internal/core/hello/repository.go +++ /dev/null @@ -1,9 +0,0 @@ -package hello - -type Repository interface { - // FindHelloMessage find a hello message for a given CodePairVisitor - FindHelloMessage(id int) (CodePairVisitor, error) - - // CreateHelloMessage creates a hello message for given CodePairVisitor - CreateHelloMessage(codePairVisitor CodePairVisitor) error -} diff --git a/backend-go/internal/core/hello/server.go b/backend-go/internal/core/hello/server.go index ea456698..f16a6efa 100644 --- a/backend-go/internal/core/hello/server.go +++ b/backend-go/internal/core/hello/server.go @@ -1,15 +1,21 @@ package hello -import "github.com/labstack/echo/v4" +import ( + "github.com/labstack/echo/v4" + "github.com/yorkie-team/codepair/backend/internal/infra/database" +) -// New creates a new handler for hello. -func New(e *echo.Echo, repo Repository) { +// New creates a new handler for hello endpoints and registers the routes. +func New(e *echo.Echo, repo database.Hello) { svc := &Service{ - repository: repo, + repo: repo, } handler := &Handler{ service: svc, } - e.GET("/hello", handler.readHello) + e.POST("/hello", handler.createHello) + e.GET("/hello/:id", handler.readHello) + e.PUT("/hello/:id", handler.updateHello) + e.DELETE("/hello/:id", handler.deleteHello) } diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index c717941f..af2cbff1 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -2,27 +2,53 @@ package hello import ( "fmt" + + "github.com/yorkie-team/codepair/backend/api/codepair/v1/models" + "github.com/yorkie-team/codepair/backend/internal/infra/database" ) +// Service provides business logic for handling hello messages. type Service struct { - repository Repository + repo database.Hello } -// createHello returns a hello message for a given CodePairVisitor -func (s *Service) createHello(codePairVisitor CodePairVisitor) error { - if err := s.repository.CreateHelloMessage(codePairVisitor); err != nil { - return fmt.Errorf("failed to create hello message for visitor %v: %w", codePairVisitor, err) +// createHello creates a new visitor record based on the provided hello request. +func (s *Service) createHello(req *models.HelloRequest) error { + visitor := database.Visitor{ + Nickname: req.Nickname, + } + if err := s.repo.CreateVisitor(visitor); err != nil { + return fmt.Errorf("failed to create hello message for visitor %v: %w", req, err) } - return nil } -// readNickname returns a hello message for a given CodePairVisitor -func (s *Service) readNickname(id int) (string, error) { - hello, err := s.repository.FindHelloMessage(id) +// readNickname retrieves the nickname of a visitor record by its unique identifier. +func (s *Service) readNickname(id string) (string, error) { + visitor, err := s.repo.FindVisitor(id) if err != nil { - return "", fmt.Errorf("failed to find hello message for ID %d: %w", id, err) + return "", fmt.Errorf("failed to find hello message for ID %s: %w", id, err) } + return visitor.Nickname, nil +} - return hello.Nickname, nil +// updateHello updates an existing visitor record with new data from the hello request. +func (s *Service) updateHello(id string, req *models.HelloRequest) error { + visitor := database.Visitor{ + ID: id, + Nickname: req.Nickname, + // Note: CreatedAt is not modified here; the repository is responsible for setting UpdatedAt. + } + if err := s.repo.UpdateVisitor(visitor); err != nil { + return fmt.Errorf("failed to update hello message for visitor with ID %s: %w", id, err) + } + return nil +} + +// deleteHello removes a visitor record by its unique identifier. +func (s *Service) deleteHello(id string) error { + if err := s.repo.DeleteVisitor(id); err != nil { + return fmt.Errorf("failed to delete hello message for visitor with ID %s: %w", id, err) + } + return nil } diff --git a/backend-go/internal/infra/database/hello.go b/backend-go/internal/infra/database/hello.go new file mode 100644 index 00000000..52b1663a --- /dev/null +++ b/backend-go/internal/infra/database/hello.go @@ -0,0 +1,30 @@ +package database + +import "time" + +// Visitor represents a visitor record in the hello repository. +type Visitor struct { + ID string `bson:"_id,omitempty"` // Auto-generated by MongoDB if empty. + Nickname string `bson:"nickname"` // Visitor's nickname. + CreatedAt time.Time `bson:"created_at"` // Timestamp when the record was created. + UpdatedAt time.Time `bson:"updated_at"` // Timestamp when the record was last updated. +} + +// Hello defines the interface for CRUD operations on visitor records. +type Hello interface { + // CreateVisitor creates a new visitor record. + // It accepts a Visitor object and returns an error if creation fails. + CreateVisitor(visitor Visitor) error + + // FindVisitor retrieves a visitor record by its unique identifier. + // It returns the Visitor and an error if the record cannot be found. + FindVisitor(id string) (Visitor, error) + + // UpdateVisitor updates an existing visitor record. + // It accepts a Visitor object (including its ID) and returns an error if the update fails. + UpdateVisitor(visitor Visitor) error + + // DeleteVisitor removes a visitor record by its unique identifier. + // It returns an error if the deletion operation fails. + DeleteVisitor(id string) error +} diff --git a/backend-go/internal/infra/database/mongo/hello.go b/backend-go/internal/infra/database/mongo/hello.go deleted file mode 100644 index b94d9306..00000000 --- a/backend-go/internal/infra/database/mongo/hello.go +++ /dev/null @@ -1,41 +0,0 @@ -package mongo - -import ( - "time" - - "github.com/yorkie-team/codepair/backend/internal/core/hello" -) - -type CodePairVisitor struct { - ID int - Nickname string - CreatedAt time.Time - UpdatedAt time.Time -} - -func (c *CodePairVisitor) ToDomain() hello.CodePairVisitor { - return hello.CodePairVisitor{ - ID: c.ID, - Nickname: c.Nickname, - } -} - -type HelloRepository struct{} - -// NewHelloRepository creates a new HelloRepository. -func NewHelloRepository() *HelloRepository { - return &HelloRepository{} -} - -func (h *HelloRepository) FindHelloMessage(id int) (hello.CodePairVisitor, error) { - he := &CodePairVisitor{ - ID: id, - Nickname: "find", - } - return he.ToDomain(), nil -} - -func (h *HelloRepository) CreateHelloMessage(_ hello.CodePairVisitor) error { - - return nil -} diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go new file mode 100644 index 00000000..9aeb2c4c --- /dev/null +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -0,0 +1,130 @@ +package mongodb + +import ( + "context" + "fmt" + "time" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + + "github.com/yorkie-team/codepair/backend/internal/config" + "github.com/yorkie-team/codepair/backend/internal/infra/database" +) + +// ColVisitor is the name of the collection storing visitor records. +const ColVisitor = "hello_visitors" + +// HelloRepository implements the CRUD operations for the hello service. +type HelloRepository struct { + conf *config.Mongo + client *mongo.Client +} + +// NewHelloRepo creates a new instance of HelloRepository. +func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { + return &HelloRepository{ + conf: conf, + client: client, + } +} + +// CreateVisitor inserts a new visitor record into the database, +// letting MongoDB auto-generate the _id field. +func (r *HelloRepository) CreateVisitor(visitor database.Visitor) error { + now := time.Now() + visitor.CreatedAt = now + visitor.UpdatedAt = now + // Ensure ID is empty so that MongoDB will auto-generate it. + visitor.ID = "" + + result, err := r.collection().InsertOne(context.Background(), visitor) + if err != nil { + return fmt.Errorf("failed to create visitor: %w", err) + } + + // Optionally, update the visitor with the generated id. + if oid, ok := result.InsertedID.(bson.ObjectID); ok { + visitor.ID = oid.Hex() + } + return nil +} + +// FindVisitor retrieves a visitor record by its id. +func (r *HelloRepository) FindVisitor(id string) (database.Visitor, error) { + // Convert string id to ObjectID. + oid, err := bson.ObjectIDFromHex(id) + if err != nil { + return database.Visitor{}, fmt.Errorf("invalid id format: %w", err) + } + + // Use an intermediate type to correctly decode the auto-generated ObjectID. + var resultModel struct { + ID bson.ObjectID `bson:"_id"` + Nickname string `bson:"nickname"` + CreatedAt time.Time `bson:"created_at"` + UpdatedAt time.Time `bson:"updated_at"` + } + + filter := bson.M{"_id": oid} + err = r.collection().FindOne(context.Background(), filter).Decode(&resultModel) + if err != nil { + return database.Visitor{}, fmt.Errorf("failed to find visitor: %w", err) + } + + return database.Visitor{ + ID: resultModel.ID.Hex(), + Nickname: resultModel.Nickname, + CreatedAt: resultModel.CreatedAt, + UpdatedAt: resultModel.UpdatedAt, + }, nil +} + +// UpdateVisitor updates an existing visitor record. +func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { + oid, err := bson.ObjectIDFromHex(visitor.ID) + if err != nil { + return fmt.Errorf("invalid id format: %w", err) + } + + visitor.UpdatedAt = time.Now() + filter := bson.M{"_id": oid} + update := bson.M{ + "$set": bson.M{ + "nickname": visitor.Nickname, + "updated_at": visitor.UpdatedAt, + }, + } + + result, err := r.collection().UpdateOne(context.Background(), filter, update) + if err != nil { + return fmt.Errorf("failed to update visitor: %w", err) + } + if result.MatchedCount == 0 { + return fmt.Errorf("no visitor found with id %s", visitor.ID) + } + return nil +} + +// DeleteVisitor removes a visitor record by its id. +func (r *HelloRepository) DeleteVisitor(id string) error { + oid, err := bson.ObjectIDFromHex(id) + if err != nil { + return fmt.Errorf("invalid id format: %w", err) + } + + filter := bson.M{"_id": oid} + result, err := r.collection().DeleteOne(context.Background(), filter) + if err != nil { + return fmt.Errorf("failed to delete visitor: %w", err) + } + if result.DeletedCount == 0 { + return fmt.Errorf("no visitor found with id %s", id) + } + return nil +} + +// collection returns a reference to the MongoDB collection for visitor records. +func (r *HelloRepository) collection() *mongo.Collection { + return r.client.Database(r.conf.DatabaseName).Collection(ColVisitor) +} diff --git a/backend-go/internal/server/server.go b/backend-go/internal/server/server.go index 583ca960..45a561fe 100644 --- a/backend-go/internal/server/server.go +++ b/backend-go/internal/server/server.go @@ -9,7 +9,7 @@ import ( "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/core/hello" - "github.com/yorkie-team/codepair/backend/internal/infra/database/mongo" + "github.com/yorkie-team/codepair/backend/internal/infra/database/mongodb" ) type CodePair struct { @@ -18,14 +18,19 @@ type CodePair struct { } // New creates a new CodePair server. -func New(e *echo.Echo, conf *config.Config) *CodePair { - hello.New(e, mongo.NewHelloRepository()) +func New(e *echo.Echo, conf *config.Config) (*CodePair, error) { + db, err := mongodb.Dial(conf.Mongo) + if err != nil { + return nil, fmt.Errorf("failed to dial mongo: %w", err) + } + + hello.New(e, mongodb.NewHelloRepo(conf.Mongo, db)) cp := &CodePair{ config: conf, echo: e, } - return cp + return cp, nil } // Start starts the server. From a1c22746be08b46be1eb905e444dd32bbd609e53 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Sun, 23 Feb 2025 13:55:34 +0900 Subject: [PATCH 06/22] Renew openapi spec - add CURD operations to `hello` --- api/backend-openapi-spec.json | 116 +++++++++++++++++- .../codepair/v1/models/model_hello_request.go | 1 + 2 files changed, 111 insertions(+), 6 deletions(-) diff --git a/api/backend-openapi-spec.json b/api/backend-openapi-spec.json index 9ce83346..9e1f9214 100644 --- a/api/backend-openapi-spec.json +++ b/api/backend-openapi-spec.json @@ -3,10 +3,9 @@ "paths": { "/hello": { "post": { - "operationId": "helloCodePair", - "summary": "Example API", - "description": "Sample API endpoint for CodePair", - "parameters": [], + "operationId": "createHello", + "summary": "Create Hello Message", + "description": "Creates a new hello message for a visitor.", "requestBody": { "required": true, "content": { @@ -18,8 +17,113 @@ } }, "responses": { - "default": { - "description": "", + "200": { + "description": "Hello message created successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloResponse" + } + } + } + } + }, + "tags": [ + "Hello" + ] + } + }, + "/hello/{id}": { + "get": { + "operationId": "readHello", + "summary": "Get Hello Message", + "description": "Retrieves a hello message for the visitor with the given ID.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Unique identifier for the visitor", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hello message retrieved successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloResponse" + } + } + } + } + }, + "tags": [ + "Hello" + ] + }, + "put": { + "operationId": "updateHello", + "summary": "Update Hello Message", + "description": "Updates the hello message for the visitor with the given ID.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Unique identifier for the visitor", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Hello message updated successfully.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/HelloResponse" + } + } + } + } + }, + "tags": [ + "Hello" + ] + }, + "delete": { + "operationId": "deleteHello", + "summary": "Delete Hello Message", + "description": "Deletes the hello message for the visitor with the given ID.", + "parameters": [ + { + "name": "id", + "in": "path", + "description": "Unique identifier for the visitor", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "Hello message deleted successfully.", "content": { "application/json": { "schema": { diff --git a/backend-go/api/codepair/v1/models/model_hello_request.go b/backend-go/api/codepair/v1/models/model_hello_request.go index 13ff94c2..453ccf26 100644 --- a/backend-go/api/codepair/v1/models/model_hello_request.go +++ b/backend-go/api/codepair/v1/models/model_hello_request.go @@ -1,6 +1,7 @@ package models type HelloRequest struct { + // New nickname to say hello Nickname string `json:"nickname"` } From 16bdc06dcb987bf19b9349f6feb6c84ef2402fc0 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 14:13:28 +0900 Subject: [PATCH 07/22] Use collection as singleton --- .../internal/infra/database/mongodb/hello.go | 19 ++++++------------- 1 file changed, 6 insertions(+), 13 deletions(-) diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index 9aeb2c4c..4689cc82 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -17,15 +17,13 @@ const ColVisitor = "hello_visitors" // HelloRepository implements the CRUD operations for the hello service. type HelloRepository struct { - conf *config.Mongo - client *mongo.Client + collection *mongo.Collection } // NewHelloRepo creates a new instance of HelloRepository. func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { return &HelloRepository{ - conf: conf, - client: client, + collection: client.Database(conf.DatabaseName).Collection(ColVisitor), } } @@ -38,7 +36,7 @@ func (r *HelloRepository) CreateVisitor(visitor database.Visitor) error { // Ensure ID is empty so that MongoDB will auto-generate it. visitor.ID = "" - result, err := r.collection().InsertOne(context.Background(), visitor) + result, err := r.collection.InsertOne(context.Background(), visitor) if err != nil { return fmt.Errorf("failed to create visitor: %w", err) } @@ -67,7 +65,7 @@ func (r *HelloRepository) FindVisitor(id string) (database.Visitor, error) { } filter := bson.M{"_id": oid} - err = r.collection().FindOne(context.Background(), filter).Decode(&resultModel) + err = r.collection.FindOne(context.Background(), filter).Decode(&resultModel) if err != nil { return database.Visitor{}, fmt.Errorf("failed to find visitor: %w", err) } @@ -96,7 +94,7 @@ func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { }, } - result, err := r.collection().UpdateOne(context.Background(), filter, update) + result, err := r.collection.UpdateOne(context.Background(), filter, update) if err != nil { return fmt.Errorf("failed to update visitor: %w", err) } @@ -114,7 +112,7 @@ func (r *HelloRepository) DeleteVisitor(id string) error { } filter := bson.M{"_id": oid} - result, err := r.collection().DeleteOne(context.Background(), filter) + result, err := r.collection.DeleteOne(context.Background(), filter) if err != nil { return fmt.Errorf("failed to delete visitor: %w", err) } @@ -123,8 +121,3 @@ func (r *HelloRepository) DeleteVisitor(id string) error { } return nil } - -// collection returns a reference to the MongoDB collection for visitor records. -func (r *HelloRepository) collection() *mongo.Collection { - return r.client.Database(r.conf.DatabaseName).Collection(ColVisitor) -} From 23d5e4f770ee6234239929241f3800467499b0a1 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 19:33:13 +0900 Subject: [PATCH 08/22] Use `ID` and register codec - for mongoDB use `database` type directly --- backend-go/internal/core/hello/service.go | 6 +-- backend-go/internal/infra/database/hello.go | 10 ++-- backend-go/internal/infra/database/id.go | 3 ++ .../internal/infra/database/mongodb/client.go | 3 +- .../internal/infra/database/mongodb/hello.go | 53 ++++--------------- .../infra/database/mongodb/registry.go | 47 ++++++++++++++++ 6 files changed, 70 insertions(+), 52 deletions(-) create mode 100644 backend-go/internal/infra/database/id.go create mode 100644 backend-go/internal/infra/database/mongodb/registry.go diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index af2cbff1..d79d71f9 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -25,7 +25,7 @@ func (s *Service) createHello(req *models.HelloRequest) error { // readNickname retrieves the nickname of a visitor record by its unique identifier. func (s *Service) readNickname(id string) (string, error) { - visitor, err := s.repo.FindVisitor(id) + visitor, err := s.repo.FindVisitor(database.ID(id)) if err != nil { return "", fmt.Errorf("failed to find hello message for ID %s: %w", id, err) } @@ -35,7 +35,7 @@ func (s *Service) readNickname(id string) (string, error) { // updateHello updates an existing visitor record with new data from the hello request. func (s *Service) updateHello(id string, req *models.HelloRequest) error { visitor := database.Visitor{ - ID: id, + ID: database.ID(id), Nickname: req.Nickname, // Note: CreatedAt is not modified here; the repository is responsible for setting UpdatedAt. } @@ -47,7 +47,7 @@ func (s *Service) updateHello(id string, req *models.HelloRequest) error { // deleteHello removes a visitor record by its unique identifier. func (s *Service) deleteHello(id string) error { - if err := s.repo.DeleteVisitor(id); err != nil { + if err := s.repo.DeleteVisitor(database.ID(id)); err != nil { return fmt.Errorf("failed to delete hello message for visitor with ID %s: %w", id, err) } return nil diff --git a/backend-go/internal/infra/database/hello.go b/backend-go/internal/infra/database/hello.go index 52b1663a..134fb19c 100644 --- a/backend-go/internal/infra/database/hello.go +++ b/backend-go/internal/infra/database/hello.go @@ -1,10 +1,12 @@ package database -import "time" +import ( + "time" +) // Visitor represents a visitor record in the hello repository. type Visitor struct { - ID string `bson:"_id,omitempty"` // Auto-generated by MongoDB if empty. + ID ID `bson:"_id,omitempty"` // Auto-generated by MongoDB if empty. Nickname string `bson:"nickname"` // Visitor's nickname. CreatedAt time.Time `bson:"created_at"` // Timestamp when the record was created. UpdatedAt time.Time `bson:"updated_at"` // Timestamp when the record was last updated. @@ -18,7 +20,7 @@ type Hello interface { // FindVisitor retrieves a visitor record by its unique identifier. // It returns the Visitor and an error if the record cannot be found. - FindVisitor(id string) (Visitor, error) + FindVisitor(id ID) (Visitor, error) // UpdateVisitor updates an existing visitor record. // It accepts a Visitor object (including its ID) and returns an error if the update fails. @@ -26,5 +28,5 @@ type Hello interface { // DeleteVisitor removes a visitor record by its unique identifier. // It returns an error if the deletion operation fails. - DeleteVisitor(id string) error + DeleteVisitor(id ID) error } diff --git a/backend-go/internal/infra/database/id.go b/backend-go/internal/infra/database/id.go new file mode 100644 index 00000000..ed35c895 --- /dev/null +++ b/backend-go/internal/infra/database/id.go @@ -0,0 +1,3 @@ +package database + +type ID string diff --git a/backend-go/internal/infra/database/mongodb/client.go b/backend-go/internal/infra/database/mongodb/client.go index 0328eef5..a67a4d3c 100644 --- a/backend-go/internal/infra/database/mongodb/client.go +++ b/backend-go/internal/infra/database/mongodb/client.go @@ -16,7 +16,8 @@ func Dial(conf *config.Mongo) (*mongo.Client, error) { client, err := mongo.Connect( options.Client(). ApplyURI(conf.ConnectionURI). - SetConnectTimeout(conf.ConnectionTimeout), + SetConnectTimeout(conf.ConnectionTimeout). + SetRegistry(NewRegistry()), ) if err != nil { return nil, fmt.Errorf("connect to mongo: %w", err) diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index 4689cc82..90b2c642 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -33,60 +33,30 @@ func (r *HelloRepository) CreateVisitor(visitor database.Visitor) error { now := time.Now() visitor.CreatedAt = now visitor.UpdatedAt = now - // Ensure ID is empty so that MongoDB will auto-generate it. - visitor.ID = "" - result, err := r.collection.InsertOne(context.Background(), visitor) + _, err := r.collection.InsertOne(context.Background(), visitor) if err != nil { return fmt.Errorf("failed to create visitor: %w", err) } - // Optionally, update the visitor with the generated id. - if oid, ok := result.InsertedID.(bson.ObjectID); ok { - visitor.ID = oid.Hex() - } return nil } // FindVisitor retrieves a visitor record by its id. -func (r *HelloRepository) FindVisitor(id string) (database.Visitor, error) { - // Convert string id to ObjectID. - oid, err := bson.ObjectIDFromHex(id) - if err != nil { - return database.Visitor{}, fmt.Errorf("invalid id format: %w", err) - } - - // Use an intermediate type to correctly decode the auto-generated ObjectID. - var resultModel struct { - ID bson.ObjectID `bson:"_id"` - Nickname string `bson:"nickname"` - CreatedAt time.Time `bson:"created_at"` - UpdatedAt time.Time `bson:"updated_at"` - } - - filter := bson.M{"_id": oid} - err = r.collection.FindOne(context.Background(), filter).Decode(&resultModel) - if err != nil { +func (r *HelloRepository) FindVisitor(id database.ID) (database.Visitor, error) { + var visitor database.Visitor + filter := bson.M{"_id": id} + if err := r.collection.FindOne(context.Background(), filter).Decode(&visitor); err != nil { return database.Visitor{}, fmt.Errorf("failed to find visitor: %w", err) } - return database.Visitor{ - ID: resultModel.ID.Hex(), - Nickname: resultModel.Nickname, - CreatedAt: resultModel.CreatedAt, - UpdatedAt: resultModel.UpdatedAt, - }, nil + return visitor, nil } // UpdateVisitor updates an existing visitor record. func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { - oid, err := bson.ObjectIDFromHex(visitor.ID) - if err != nil { - return fmt.Errorf("invalid id format: %w", err) - } - visitor.UpdatedAt = time.Now() - filter := bson.M{"_id": oid} + filter := bson.M{"_id": visitor.ID} update := bson.M{ "$set": bson.M{ "nickname": visitor.Nickname, @@ -105,13 +75,8 @@ func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { } // DeleteVisitor removes a visitor record by its id. -func (r *HelloRepository) DeleteVisitor(id string) error { - oid, err := bson.ObjectIDFromHex(id) - if err != nil { - return fmt.Errorf("invalid id format: %w", err) - } - - filter := bson.M{"_id": oid} +func (r *HelloRepository) DeleteVisitor(id database.ID) error { + filter := bson.M{"_id": id} result, err := r.collection.DeleteOne(context.Background(), filter) if err != nil { return fmt.Errorf("failed to delete visitor: %w", err) diff --git a/backend-go/internal/infra/database/mongodb/registry.go b/backend-go/internal/infra/database/mongodb/registry.go new file mode 100644 index 00000000..feb2a58d --- /dev/null +++ b/backend-go/internal/infra/database/mongodb/registry.go @@ -0,0 +1,47 @@ +package mongodb + +import ( + "reflect" + + "github.com/yorkie-team/codepair/backend/internal/infra/database" + + "go.mongodb.org/mongo-driver/v2/bson" +) + +var tID = reflect.TypeOf(database.ID("")) + +func iDEncoder(_ bson.EncodeContext, vw bson.ValueWriter, val reflect.Value) error { + if val.Type() != tID { + return bson.ValueEncoderError{Name: "iDEncoder", Types: []reflect.Type{tID}, Received: val} + } + + oid, err := bson.ObjectIDFromHex(val.String()) + if err != nil { + return err + } + + return vw.WriteObjectID(oid) +} + +func iDDecoder(_ bson.DecodeContext, vr bson.ValueReader, val reflect.Value) error { + if val.Type() != tID { + return bson.ValueDecoderError{Name: "iDDecoder", Types: []reflect.Type{tID}, Received: val} + } + + oid, err := vr.ReadObjectID() + if err != nil { + return err + } + + val.SetString(oid.Hex()) + return nil +} + +func NewRegistry() *bson.Registry { + rb := bson.NewRegistry() + + rb.RegisterTypeEncoder(tID, bson.ValueEncoderFunc(iDEncoder)) + rb.RegisterTypeDecoder(tID, bson.ValueDecoderFunc(iDDecoder)) + + return rb +} From d8858e8e4c47da96d412caf49e22e7905a3a735a Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 19:47:44 +0900 Subject: [PATCH 09/22] go mod tidy - add mongo driver --- backend-go/go.mod | 11 +++++++++-- backend-go/go.sum | 50 +++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/backend-go/go.mod b/backend-go/go.mod index 676db2eb..2760c9c4 100644 --- a/backend-go/go.mod +++ b/backend-go/go.mod @@ -3,10 +3,12 @@ module github.com/yorkie-team/codepair/backend go 1.21 require ( + github.com/go-playground/validator/v10 v10.24.0 github.com/labstack/echo/v4 v4.13.3 github.com/labstack/gommon v0.4.2 github.com/spf13/viper v1.19.0 github.com/stretchr/testify v1.10.0 + go.mongodb.org/mongo-driver/v2 v2.0.1 ) require ( @@ -15,8 +17,9 @@ require ( github.com/gabriel-vasile/mimetype v1.4.8 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.24.0 // indirect + github.com/golang/snappy v0.0.4 // indirect github.com/hashicorp/hcl v1.0.0 // indirect + github.com/klauspost/compress v1.17.2 // indirect github.com/leodido/go-urn v1.4.0 // indirect github.com/magiconair/properties v1.8.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -33,12 +36,16 @@ require ( github.com/subosito/gotenv v1.6.0 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect - go.mongodb.org/mongo-driver/v2 v2.0.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect golang.org/x/crypto v0.32.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/net v0.34.0 // indirect + golang.org/x/sync v0.10.0 // indirect golang.org/x/sys v0.29.0 // indirect golang.org/x/text v0.21.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect diff --git a/backend-go/go.sum b/backend-go/go.sum index 9e610a21..3af7575c 100644 --- a/backend-go/go.sum +++ b/backend-go/go.sum @@ -8,16 +8,22 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= github.com/go-playground/validator/v10 v10.24.0 h1:KHQckvo8G6hlWnrPX4NJJ+aBfWNAE/HH+qdL2cBpCmg= github.com/go-playground/validator/v10 v10.24.0/go.mod h1:GGzBIJMuE98Ic/kJsBXbz1x/7cByt++cQ+YOuDM5wus= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/klauspost/compress v1.17.2 h1:RlWWUY/Dr4fL8qk9YG7DTZ7PDgME2V4csBXA8L/ixi4= +github.com/klauspost/compress v1.17.2/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -75,30 +81,58 @@ github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6Kllzaw github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/fasttemplate v1.2.2 h1:lxLXG0uE3Qnshl9QyaK6XJxMXlQZELvChBOCmQD0Loo= github.com/valyala/fasttemplate v1.2.2/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 h1:ilQV1hzziu+LLM3zUTJ0trRztfwgjqKnBWNtSRkbmwM= +github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78/go.mod h1:aL8wCCfTfSfmXjznFBSZNN13rSJjlIOI1fUNAtF7rmI= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= go.mongodb.org/mongo-driver/v2 v2.0.1 h1:mhB/ZJkLSv6W6LGzY7sEjpZif47+JdfEEXjlLCIv7Qc= go.mongodb.org/mongo-driver/v2 v2.0.1/go.mod h1:w7iFnTcQDMXtdXwcvyG3xljYpoBa1ErkI0yOzbkZ9b8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= -golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc= golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc= golang.org/x/exp v0.0.0-20230905200255-921286631fa9 h1:GoHiUyI/Tp2nVkLI2mCxVkOjsbSXD66ic0XW0js0R9g= golang.org/x/exp v0.0.0-20230905200255-921286631fa9/go.mod h1:S2oDrQGGwySpoQPVqRShND87VCbxmc6bL1Yd2oYrm6k= -golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= -golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= -golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= From 2bf24bc72e1cf15482317e6bd6d0f3e45b716817 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 19:49:39 +0900 Subject: [PATCH 10/22] Fix typo correction --- backend-go/docker/{docekr-compose.yml => docker-compose.yml} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename backend-go/docker/{docekr-compose.yml => docker-compose.yml} (100%) diff --git a/backend-go/docker/docekr-compose.yml b/backend-go/docker/docker-compose.yml similarity index 100% rename from backend-go/docker/docekr-compose.yml rename to backend-go/docker/docker-compose.yml From c575a60a020f84f75924b6bbfa7592bd0469e322 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 20:02:15 +0900 Subject: [PATCH 11/22] Fix create returns id --- backend-go/internal/core/hello/handler.go | 5 +++-- backend-go/internal/core/hello/service.go | 9 +++++---- backend-go/internal/infra/database/hello.go | 2 +- backend-go/internal/infra/database/mongodb/hello.go | 12 ++++++++---- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/backend-go/internal/core/hello/handler.go b/backend-go/internal/core/hello/handler.go index 60bcce55..a71cf435 100644 --- a/backend-go/internal/core/hello/handler.go +++ b/backend-go/internal/core/hello/handler.go @@ -22,11 +22,12 @@ func (h *Handler) createHello(c echo.Context) error { if err := http.BindAndValidateRequest(c, req); err != nil { return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) } - if err := h.service.createHello(req); err != nil { + id, err := h.service.createHello(req) + if err != nil { return http.NewErrorResponse(c, err) } return http.NewOkResponse(c, models.HelloResponse{ - Message: fmt.Sprintf("Hello, %s!", req.Nickname), + Message: fmt.Sprintf("Hello, id:%s, nickname: %s!", id, req.Nickname), }) } diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index d79d71f9..f1ebf9fa 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -13,14 +13,15 @@ type Service struct { } // createHello creates a new visitor record based on the provided hello request. -func (s *Service) createHello(req *models.HelloRequest) error { +func (s *Service) createHello(req *models.HelloRequest) (string, error) { visitor := database.Visitor{ Nickname: req.Nickname, } - if err := s.repo.CreateVisitor(visitor); err != nil { - return fmt.Errorf("failed to create hello message for visitor %v: %w", req, err) + visitor, err := s.repo.CreateVisitor(visitor) + if err != nil { + return "", fmt.Errorf("failed to create hello message for visitor %v: %w", req, err) } - return nil + return string(visitor.ID), nil } // readNickname retrieves the nickname of a visitor record by its unique identifier. diff --git a/backend-go/internal/infra/database/hello.go b/backend-go/internal/infra/database/hello.go index 134fb19c..1eba7d33 100644 --- a/backend-go/internal/infra/database/hello.go +++ b/backend-go/internal/infra/database/hello.go @@ -16,7 +16,7 @@ type Visitor struct { type Hello interface { // CreateVisitor creates a new visitor record. // It accepts a Visitor object and returns an error if creation fails. - CreateVisitor(visitor Visitor) error + CreateVisitor(visitor Visitor) (Visitor, error) // FindVisitor retrieves a visitor record by its unique identifier. // It returns the Visitor and an error if the record cannot be found. diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index 90b2c642..e1f142a0 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -29,17 +29,21 @@ func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { // CreateVisitor inserts a new visitor record into the database, // letting MongoDB auto-generate the _id field. -func (r *HelloRepository) CreateVisitor(visitor database.Visitor) error { +func (r *HelloRepository) CreateVisitor(visitor database.Visitor) (database.Visitor, error) { now := time.Now() visitor.CreatedAt = now visitor.UpdatedAt = now - _, err := r.collection.InsertOne(context.Background(), visitor) + res, err := r.collection.InsertOne(context.Background(), visitor) if err != nil { - return fmt.Errorf("failed to create visitor: %w", err) + return database.Visitor{}, fmt.Errorf("failed to create visitor: %w", err) } - return nil + if oid, ok := res.InsertedID.(bson.ObjectID); ok { + visitor.ID = database.ID(oid.String()) + } + + return visitor, nil } // FindVisitor retrieves a visitor record by its id. From b83839520f8b9a59374988d26fdb9d031868bed9 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 20:08:21 +0900 Subject: [PATCH 12/22] lint --- .../internal/infra/database/mongodb/registry.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/backend-go/internal/infra/database/mongodb/registry.go b/backend-go/internal/infra/database/mongodb/registry.go index feb2a58d..ac8845ad 100644 --- a/backend-go/internal/infra/database/mongodb/registry.go +++ b/backend-go/internal/infra/database/mongodb/registry.go @@ -1,11 +1,12 @@ package mongodb import ( + "fmt" "reflect" - "github.com/yorkie-team/codepair/backend/internal/infra/database" - "go.mongodb.org/mongo-driver/v2/bson" + + "github.com/yorkie-team/codepair/backend/internal/infra/database" ) var tID = reflect.TypeOf(database.ID("")) @@ -17,10 +18,13 @@ func iDEncoder(_ bson.EncodeContext, vw bson.ValueWriter, val reflect.Value) err oid, err := bson.ObjectIDFromHex(val.String()) if err != nil { - return err + return fmt.Errorf("convert hex to ObjectID: %w", err) } - return vw.WriteObjectID(oid) + if err = vw.WriteObjectID(oid); err != nil { + return fmt.Errorf("write ObjectID: %w", err) + } + return nil } func iDDecoder(_ bson.DecodeContext, vr bson.ValueReader, val reflect.Value) error { @@ -30,7 +34,7 @@ func iDDecoder(_ bson.DecodeContext, vr bson.ValueReader, val reflect.Value) err oid, err := vr.ReadObjectID() if err != nil { - return err + return fmt.Errorf("read ObjectID: %w", err) } val.SetString(oid.Hex()) From 87e344193942a3093a8a1c15bd7fb598ad49a0f3 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Mon, 24 Feb 2025 20:10:43 +0900 Subject: [PATCH 13/22] lint --- backend-go/internal/core/hello/server.go | 1 + 1 file changed, 1 insertion(+) diff --git a/backend-go/internal/core/hello/server.go b/backend-go/internal/core/hello/server.go index f16a6efa..1d6bb8c1 100644 --- a/backend-go/internal/core/hello/server.go +++ b/backend-go/internal/core/hello/server.go @@ -2,6 +2,7 @@ package hello import ( "github.com/labstack/echo/v4" + "github.com/yorkie-team/codepair/backend/internal/infra/database" ) From f98f32e2c49ec8f1ec9483a3c41525890ca977e3 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 10:53:29 +0900 Subject: [PATCH 14/22] Define common error in database --- backend-go/internal/infra/database/errors.go | 17 +++++++++++++ backend-go/internal/infra/database/id.go | 3 +++ .../internal/infra/database/mongodb/client.go | 6 ++--- .../internal/infra/database/mongodb/hello.go | 24 ++++++++++++------- 4 files changed, 39 insertions(+), 11 deletions(-) create mode 100644 backend-go/internal/infra/database/errors.go diff --git a/backend-go/internal/infra/database/errors.go b/backend-go/internal/infra/database/errors.go new file mode 100644 index 00000000..3ccda488 --- /dev/null +++ b/backend-go/internal/infra/database/errors.go @@ -0,0 +1,17 @@ +package database + +import "errors" + +var ( + // ErrDocumentNotFound No matching document found + ErrDocumentNotFound = errors.New("document not found") + + // ErrDuplicatedKey Duplicate key error (e.g., unique constraint violation) + ErrDuplicatedKey = errors.New("duplicate key error") + + // ErrNetwork Network connectivity issue + ErrNetwork = errors.New("network error") + + // ErrDisconnected Database connection lost + ErrDisconnected = errors.New("database disconnected") +) diff --git a/backend-go/internal/infra/database/id.go b/backend-go/internal/infra/database/id.go index ed35c895..84f52829 100644 --- a/backend-go/internal/infra/database/id.go +++ b/backend-go/internal/infra/database/id.go @@ -1,3 +1,6 @@ package database +// ID is a string type used as an identifier in the database. +// It is designed for encoding and decoding MongoDB Object IDs. +// This allows us to use the database type directly with MongoDB encoders and decoders. type ID string diff --git a/backend-go/internal/infra/database/mongodb/client.go b/backend-go/internal/infra/database/mongodb/client.go index a67a4d3c..3a623b05 100644 --- a/backend-go/internal/infra/database/mongodb/client.go +++ b/backend-go/internal/infra/database/mongodb/client.go @@ -2,13 +2,13 @@ package mongodb import ( "context" - "fmt" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" "github.com/yorkie-team/codepair/backend/internal/config" + "github.com/yorkie-team/codepair/backend/internal/infra/database" ) // Dial creates an instance of Mongo and dials the given MongoDB. @@ -20,14 +20,14 @@ func Dial(conf *config.Mongo) (*mongo.Client, error) { SetRegistry(NewRegistry()), ) if err != nil { - return nil, fmt.Errorf("connect to mongo: %w", err) + return nil, database.ErrDisconnected } ctxPing, cancel := context.WithTimeout(context.Background(), conf.PingTimeout) defer cancel() if err := client.Ping(ctxPing, readpref.Primary()); err != nil { - return nil, fmt.Errorf("ping mongo: %w", err) + return nil, database.ErrNetwork } return client, nil diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index e1f142a0..6ec5605c 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -2,6 +2,7 @@ package mongodb import ( "context" + "errors" "fmt" "time" @@ -36,7 +37,10 @@ func (r *HelloRepository) CreateVisitor(visitor database.Visitor) (database.Visi res, err := r.collection.InsertOne(context.Background(), visitor) if err != nil { - return database.Visitor{}, fmt.Errorf("failed to create visitor: %w", err) + if mongo.IsDuplicateKeyError(err) { + return database.Visitor{}, database.ErrDuplicatedKey + } + return database.Visitor{}, fmt.Errorf("create visitor: %w", err) } if oid, ok := res.InsertedID.(bson.ObjectID); ok { @@ -48,10 +52,14 @@ func (r *HelloRepository) CreateVisitor(visitor database.Visitor) (database.Visi // FindVisitor retrieves a visitor record by its id. func (r *HelloRepository) FindVisitor(id database.ID) (database.Visitor, error) { - var visitor database.Visitor + visitor := database.Visitor{} filter := bson.M{"_id": id} - if err := r.collection.FindOne(context.Background(), filter).Decode(&visitor); err != nil { - return database.Visitor{}, fmt.Errorf("failed to find visitor: %w", err) + + err := r.collection.FindOne(context.Background(), filter).Decode(&visitor) + if errors.Is(err, mongo.ErrNoDocuments) { + return database.Visitor{}, database.ErrDocumentNotFound + } else if err != nil { + return database.Visitor{}, fmt.Errorf("find visitor: %w", err) } return visitor, nil @@ -70,10 +78,10 @@ func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { result, err := r.collection.UpdateOne(context.Background(), filter, update) if err != nil { - return fmt.Errorf("failed to update visitor: %w", err) + return fmt.Errorf("update visitor: %w", err) } if result.MatchedCount == 0 { - return fmt.Errorf("no visitor found with id %s", visitor.ID) + return database.ErrDocumentNotFound } return nil } @@ -83,10 +91,10 @@ func (r *HelloRepository) DeleteVisitor(id database.ID) error { filter := bson.M{"_id": id} result, err := r.collection.DeleteOne(context.Background(), filter) if err != nil { - return fmt.Errorf("failed to delete visitor: %w", err) + return fmt.Errorf("delete visitor: %w", err) } if result.DeletedCount == 0 { - return fmt.Errorf("no visitor found with id %s", id) + return database.ErrDocumentNotFound } return nil } From 903b8709a78b40221854d6c12a76f70a95e3bce8 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 10:56:40 +0900 Subject: [PATCH 15/22] initializing struct reference - ref `https://github.com/uber-go/guide/blob/master/style.md#initializing-struct-references` --- backend-go/internal/core/hello/handler.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend-go/internal/core/hello/handler.go b/backend-go/internal/core/hello/handler.go index a71cf435..f54279f6 100644 --- a/backend-go/internal/core/hello/handler.go +++ b/backend-go/internal/core/hello/handler.go @@ -17,7 +17,7 @@ type Handler struct { // createHello handles the POST /hello endpoint. // It creates a new visitor record and returns a hello message. func (h *Handler) createHello(c echo.Context) error { - req := new(models.HelloRequest) + req := &models.HelloRequest{} if err := http.BindAndValidateRequest(c, req); err != nil { return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) @@ -48,7 +48,7 @@ func (h *Handler) readHello(c echo.Context) error { // It updates an existing visitor record and returns a confirmation message. func (h *Handler) updateHello(c echo.Context) error { id := c.Param("id") - req := new(models.HelloRequest) + req := &models.HelloRequest{} if err := http.BindAndValidateRequest(c, req); err != nil { return http.NewErrorResponse(c, fmt.Errorf("invalid request: %w", err)) } From 659fd66c82fc8e3969c0e90baed9b10a9a505489 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 11:00:06 +0900 Subject: [PATCH 16/22] Add interface compliance --- backend-go/internal/infra/database/mongodb/hello.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index 6ec5605c..fadb3fe1 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -21,6 +21,8 @@ type HelloRepository struct { collection *mongo.Collection } +var _ database.Hello = (*HelloRepository)(nil) + // NewHelloRepo creates a new instance of HelloRepository. func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { return &HelloRepository{ From 9e5199d29326fb73684b6a595be824e78fa2ba01 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 11:34:54 +0900 Subject: [PATCH 17/22] Add index for visitor nickname --- .../internal/infra/database/mongodb/client.go | 9 ++++- .../internal/infra/database/mongodb/hello.go | 3 -- .../infra/database/mongodb/indexes.go | 38 +++++++++++++++++++ backend-go/internal/server/server.go | 2 +- 4 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 backend-go/internal/infra/database/mongodb/indexes.go diff --git a/backend-go/internal/infra/database/mongodb/client.go b/backend-go/internal/infra/database/mongodb/client.go index 3a623b05..848aa306 100644 --- a/backend-go/internal/infra/database/mongodb/client.go +++ b/backend-go/internal/infra/database/mongodb/client.go @@ -7,12 +7,13 @@ import ( "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" + "github.com/labstack/echo/v4" "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/infra/database" ) // Dial creates an instance of Mongo and dials the given MongoDB. -func Dial(conf *config.Mongo) (*mongo.Client, error) { +func Dial(conf *config.Mongo, logger echo.Logger) (*mongo.Client, error) { client, err := mongo.Connect( options.Client(). ApplyURI(conf.ConnectionURI). @@ -30,5 +31,11 @@ func Dial(conf *config.Mongo) (*mongo.Client, error) { return nil, database.ErrNetwork } + if err := ensureIndexes(context.Background(), client.Database(conf.DatabaseName)); err != nil { + return nil, err + } + + logger.Infof("MongoDB connected, URI: %s, DB: %s", conf.ConnectionURI, conf.DatabaseName) + return client, nil } diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/hello.go index fadb3fe1..5dc6bf1c 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/hello.go @@ -13,9 +13,6 @@ import ( "github.com/yorkie-team/codepair/backend/internal/infra/database" ) -// ColVisitor is the name of the collection storing visitor records. -const ColVisitor = "hello_visitors" - // HelloRepository implements the CRUD operations for the hello service. type HelloRepository struct { collection *mongo.Collection diff --git a/backend-go/internal/infra/database/mongodb/indexes.go b/backend-go/internal/infra/database/mongodb/indexes.go new file mode 100644 index 00000000..afd1834a --- /dev/null +++ b/backend-go/internal/infra/database/mongodb/indexes.go @@ -0,0 +1,38 @@ +package mongodb + +import ( + "context" + "fmt" + + "go.mongodb.org/mongo-driver/v2/bson" + "go.mongodb.org/mongo-driver/v2/mongo" + "go.mongodb.org/mongo-driver/v2/mongo/options" +) + +// ColVisitor is the name of the collection storing visitor records. +const ColVisitor = "hello_visitors" + +type collectionInfo struct { + name string + indexes []mongo.IndexModel +} + +var collectionInfos = []collectionInfo{ + { + name: ColVisitor, + indexes: []mongo.IndexModel{{ + Keys: bson.D{{Key: "nickname", Value: 1}}, + Options: options.Index().SetUnique(true), + }}, + }, +} + +func ensureIndexes(ctx context.Context, db *mongo.Database) error { + for _, info := range collectionInfos { + _, err := db.Collection(info.name).Indexes().CreateMany(ctx, info.indexes) + if err != nil { + return fmt.Errorf("create indexes: %w", err) + } + } + return nil +} diff --git a/backend-go/internal/server/server.go b/backend-go/internal/server/server.go index 45a561fe..25547881 100644 --- a/backend-go/internal/server/server.go +++ b/backend-go/internal/server/server.go @@ -19,7 +19,7 @@ type CodePair struct { // New creates a new CodePair server. func New(e *echo.Echo, conf *config.Config) (*CodePair, error) { - db, err := mongodb.Dial(conf.Mongo) + db, err := mongodb.Dial(conf.Mongo, e.Logger) if err != nil { return nil, fmt.Errorf("failed to dial mongo: %w", err) } From d749e10df4808e4f9c484aba11f650b7cb41b3a0 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 11:58:14 +0900 Subject: [PATCH 18/22] Use database error in service --- backend-go/internal/core/hello/service.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index f1ebf9fa..11676a41 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -1,6 +1,7 @@ package hello import ( + "errors" "fmt" "github.com/yorkie-team/codepair/backend/api/codepair/v1/models" @@ -19,6 +20,9 @@ func (s *Service) createHello(req *models.HelloRequest) (string, error) { } visitor, err := s.repo.CreateVisitor(visitor) if err != nil { + if errors.Is(err, database.ErrDuplicatedKey) { + return "", fmt.Errorf("nickname '%s' already exists: %w", req.Nickname, err) + } return "", fmt.Errorf("failed to create hello message for visitor %v: %w", req, err) } return string(visitor.ID), nil @@ -28,6 +32,9 @@ func (s *Service) createHello(req *models.HelloRequest) (string, error) { func (s *Service) readNickname(id string) (string, error) { visitor, err := s.repo.FindVisitor(database.ID(id)) if err != nil { + if errors.Is(err, database.ErrDocumentNotFound) { + return "", fmt.Errorf("visitor with ID '%s' not found: %w", id, err) + } return "", fmt.Errorf("failed to find hello message for ID %s: %w", id, err) } return visitor.Nickname, nil @@ -38,9 +45,12 @@ func (s *Service) updateHello(id string, req *models.HelloRequest) error { visitor := database.Visitor{ ID: database.ID(id), Nickname: req.Nickname, - // Note: CreatedAt is not modified here; the repository is responsible for setting UpdatedAt. } + if err := s.repo.UpdateVisitor(visitor); err != nil { + if errors.Is(err, database.ErrDocumentNotFound) { + return fmt.Errorf("cannot update: visitor with ID '%s' not found: %w", id, err) + } return fmt.Errorf("failed to update hello message for visitor with ID %s: %w", id, err) } return nil @@ -49,6 +59,9 @@ func (s *Service) updateHello(id string, req *models.HelloRequest) error { // deleteHello removes a visitor record by its unique identifier. func (s *Service) deleteHello(id string) error { if err := s.repo.DeleteVisitor(database.ID(id)); err != nil { + if errors.Is(err, database.ErrDocumentNotFound) { + return fmt.Errorf("cannot delete: visitor with ID '%s' not found: %w", id, err) + } return fmt.Errorf("failed to delete hello message for visitor with ID %s: %w", id, err) } return nil From fa04b9be278096b9daf3a701d6c53da6cc59c792 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 11:59:12 +0900 Subject: [PATCH 19/22] Lint --- backend-go/internal/infra/database/mongodb/client.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend-go/internal/infra/database/mongodb/client.go b/backend-go/internal/infra/database/mongodb/client.go index 848aa306..b47fe956 100644 --- a/backend-go/internal/infra/database/mongodb/client.go +++ b/backend-go/internal/infra/database/mongodb/client.go @@ -3,11 +3,11 @@ package mongodb import ( "context" + "github.com/labstack/echo/v4" "go.mongodb.org/mongo-driver/v2/mongo" "go.mongodb.org/mongo-driver/v2/mongo/options" "go.mongodb.org/mongo-driver/v2/mongo/readpref" - "github.com/labstack/echo/v4" "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/infra/database" ) From a0dec495f3e25450255eaf313696d32ed933f128 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Tue, 25 Feb 2025 13:07:17 +0900 Subject: [PATCH 20/22] Rename `New` -> `Register` - `New` is some strange because it doesn't return anything --- backend-go/internal/core/hello/server.go | 4 ++-- backend-go/internal/server/server.go | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/backend-go/internal/core/hello/server.go b/backend-go/internal/core/hello/server.go index 1d6bb8c1..0e888ebb 100644 --- a/backend-go/internal/core/hello/server.go +++ b/backend-go/internal/core/hello/server.go @@ -6,8 +6,8 @@ import ( "github.com/yorkie-team/codepair/backend/internal/infra/database" ) -// New creates a new handler for hello endpoints and registers the routes. -func New(e *echo.Echo, repo database.Hello) { +// Register creates a new handler for hello endpoints and registers the routes. +func Register(e *echo.Echo, repo database.Hello) { svc := &Service{ repo: repo, } diff --git a/backend-go/internal/server/server.go b/backend-go/internal/server/server.go index 25547881..f694d3ca 100644 --- a/backend-go/internal/server/server.go +++ b/backend-go/internal/server/server.go @@ -24,7 +24,7 @@ func New(e *echo.Echo, conf *config.Config) (*CodePair, error) { return nil, fmt.Errorf("failed to dial mongo: %w", err) } - hello.New(e, mongodb.NewHelloRepo(conf.Mongo, db)) + hello.Register(e, mongodb.NewHelloRepo(conf.Mongo, db)) cp := &CodePair{ config: conf, From e6714a636ee620e3b055be0bba7841c063b510fc Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Wed, 26 Feb 2025 11:20:47 +0900 Subject: [PATCH 21/22] Apply kokodak's suggestions --- backend-go/internal/core/hello/server.go | 2 +- backend-go/internal/core/hello/service.go | 13 ++++---- .../infra/database/{ => entity}/id.go | 2 +- .../internal/infra/database/entity/visitor.go | 11 +++++++ backend-go/internal/infra/database/hello.go | 32 ------------------- .../infra/database/mongodb/registry.go | 4 +-- .../database/mongodb/{hello.go => visitor.go} | 23 +++++++------ backend-go/internal/infra/database/visitor.go | 24 ++++++++++++++ 8 files changed, 57 insertions(+), 54 deletions(-) rename backend-go/internal/infra/database/{ => entity}/id.go (93%) create mode 100644 backend-go/internal/infra/database/entity/visitor.go delete mode 100644 backend-go/internal/infra/database/hello.go rename backend-go/internal/infra/database/mongodb/{hello.go => visitor.go} (75%) create mode 100644 backend-go/internal/infra/database/visitor.go diff --git a/backend-go/internal/core/hello/server.go b/backend-go/internal/core/hello/server.go index 0e888ebb..2c1a3b45 100644 --- a/backend-go/internal/core/hello/server.go +++ b/backend-go/internal/core/hello/server.go @@ -7,7 +7,7 @@ import ( ) // Register creates a new handler for hello endpoints and registers the routes. -func Register(e *echo.Echo, repo database.Hello) { +func Register(e *echo.Echo, repo database.Visitor) { svc := &Service{ repo: repo, } diff --git a/backend-go/internal/core/hello/service.go b/backend-go/internal/core/hello/service.go index 11676a41..297cb5d8 100644 --- a/backend-go/internal/core/hello/service.go +++ b/backend-go/internal/core/hello/service.go @@ -6,16 +6,17 @@ import ( "github.com/yorkie-team/codepair/backend/api/codepair/v1/models" "github.com/yorkie-team/codepair/backend/internal/infra/database" + "github.com/yorkie-team/codepair/backend/internal/infra/database/entity" ) // Service provides business logic for handling hello messages. type Service struct { - repo database.Hello + repo database.Visitor } // createHello creates a new visitor record based on the provided hello request. func (s *Service) createHello(req *models.HelloRequest) (string, error) { - visitor := database.Visitor{ + visitor := entity.Visitor{ Nickname: req.Nickname, } visitor, err := s.repo.CreateVisitor(visitor) @@ -30,7 +31,7 @@ func (s *Service) createHello(req *models.HelloRequest) (string, error) { // readNickname retrieves the nickname of a visitor record by its unique identifier. func (s *Service) readNickname(id string) (string, error) { - visitor, err := s.repo.FindVisitor(database.ID(id)) + visitor, err := s.repo.FindVisitor(entity.ID(id)) if err != nil { if errors.Is(err, database.ErrDocumentNotFound) { return "", fmt.Errorf("visitor with ID '%s' not found: %w", id, err) @@ -42,8 +43,8 @@ func (s *Service) readNickname(id string) (string, error) { // updateHello updates an existing visitor record with new data from the hello request. func (s *Service) updateHello(id string, req *models.HelloRequest) error { - visitor := database.Visitor{ - ID: database.ID(id), + visitor := entity.Visitor{ + ID: entity.ID(id), Nickname: req.Nickname, } @@ -58,7 +59,7 @@ func (s *Service) updateHello(id string, req *models.HelloRequest) error { // deleteHello removes a visitor record by its unique identifier. func (s *Service) deleteHello(id string) error { - if err := s.repo.DeleteVisitor(database.ID(id)); err != nil { + if err := s.repo.DeleteVisitor(entity.ID(id)); err != nil { if errors.Is(err, database.ErrDocumentNotFound) { return fmt.Errorf("cannot delete: visitor with ID '%s' not found: %w", id, err) } diff --git a/backend-go/internal/infra/database/id.go b/backend-go/internal/infra/database/entity/id.go similarity index 93% rename from backend-go/internal/infra/database/id.go rename to backend-go/internal/infra/database/entity/id.go index 84f52829..ddfd6144 100644 --- a/backend-go/internal/infra/database/id.go +++ b/backend-go/internal/infra/database/entity/id.go @@ -1,4 +1,4 @@ -package database +package entity // ID is a string type used as an identifier in the database. // It is designed for encoding and decoding MongoDB Object IDs. diff --git a/backend-go/internal/infra/database/entity/visitor.go b/backend-go/internal/infra/database/entity/visitor.go new file mode 100644 index 00000000..ad8bc593 --- /dev/null +++ b/backend-go/internal/infra/database/entity/visitor.go @@ -0,0 +1,11 @@ +package entity + +import "time" + +// Visitor represents a visitor record in the hello repository. +type Visitor struct { + ID ID `bson:"_id,omitempty"` // Auto-generated by MongoDB if empty. + Nickname string `bson:"nickname"` // Visitor's nickname. + CreatedAt time.Time `bson:"created_at"` // Timestamp when the record was created. + UpdatedAt time.Time `bson:"updated_at"` // Timestamp when the record was last updated. +} diff --git a/backend-go/internal/infra/database/hello.go b/backend-go/internal/infra/database/hello.go deleted file mode 100644 index 1eba7d33..00000000 --- a/backend-go/internal/infra/database/hello.go +++ /dev/null @@ -1,32 +0,0 @@ -package database - -import ( - "time" -) - -// Visitor represents a visitor record in the hello repository. -type Visitor struct { - ID ID `bson:"_id,omitempty"` // Auto-generated by MongoDB if empty. - Nickname string `bson:"nickname"` // Visitor's nickname. - CreatedAt time.Time `bson:"created_at"` // Timestamp when the record was created. - UpdatedAt time.Time `bson:"updated_at"` // Timestamp when the record was last updated. -} - -// Hello defines the interface for CRUD operations on visitor records. -type Hello interface { - // CreateVisitor creates a new visitor record. - // It accepts a Visitor object and returns an error if creation fails. - CreateVisitor(visitor Visitor) (Visitor, error) - - // FindVisitor retrieves a visitor record by its unique identifier. - // It returns the Visitor and an error if the record cannot be found. - FindVisitor(id ID) (Visitor, error) - - // UpdateVisitor updates an existing visitor record. - // It accepts a Visitor object (including its ID) and returns an error if the update fails. - UpdateVisitor(visitor Visitor) error - - // DeleteVisitor removes a visitor record by its unique identifier. - // It returns an error if the deletion operation fails. - DeleteVisitor(id ID) error -} diff --git a/backend-go/internal/infra/database/mongodb/registry.go b/backend-go/internal/infra/database/mongodb/registry.go index ac8845ad..bcb7c7d8 100644 --- a/backend-go/internal/infra/database/mongodb/registry.go +++ b/backend-go/internal/infra/database/mongodb/registry.go @@ -6,10 +6,10 @@ import ( "go.mongodb.org/mongo-driver/v2/bson" - "github.com/yorkie-team/codepair/backend/internal/infra/database" + "github.com/yorkie-team/codepair/backend/internal/infra/database/entity" ) -var tID = reflect.TypeOf(database.ID("")) +var tID = reflect.TypeOf(entity.ID("")) func iDEncoder(_ bson.EncodeContext, vw bson.ValueWriter, val reflect.Value) error { if val.Type() != tID { diff --git a/backend-go/internal/infra/database/mongodb/hello.go b/backend-go/internal/infra/database/mongodb/visitor.go similarity index 75% rename from backend-go/internal/infra/database/mongodb/hello.go rename to backend-go/internal/infra/database/mongodb/visitor.go index 5dc6bf1c..22af7c34 100644 --- a/backend-go/internal/infra/database/mongodb/hello.go +++ b/backend-go/internal/infra/database/mongodb/visitor.go @@ -11,6 +11,7 @@ import ( "github.com/yorkie-team/codepair/backend/internal/config" "github.com/yorkie-team/codepair/backend/internal/infra/database" + "github.com/yorkie-team/codepair/backend/internal/infra/database/entity" ) // HelloRepository implements the CRUD operations for the hello service. @@ -18,8 +19,6 @@ type HelloRepository struct { collection *mongo.Collection } -var _ database.Hello = (*HelloRepository)(nil) - // NewHelloRepo creates a new instance of HelloRepository. func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { return &HelloRepository{ @@ -29,7 +28,7 @@ func NewHelloRepo(conf *config.Mongo, client *mongo.Client) *HelloRepository { // CreateVisitor inserts a new visitor record into the database, // letting MongoDB auto-generate the _id field. -func (r *HelloRepository) CreateVisitor(visitor database.Visitor) (database.Visitor, error) { +func (r *HelloRepository) CreateVisitor(visitor entity.Visitor) (entity.Visitor, error) { now := time.Now() visitor.CreatedAt = now visitor.UpdatedAt = now @@ -37,35 +36,35 @@ func (r *HelloRepository) CreateVisitor(visitor database.Visitor) (database.Visi res, err := r.collection.InsertOne(context.Background(), visitor) if err != nil { if mongo.IsDuplicateKeyError(err) { - return database.Visitor{}, database.ErrDuplicatedKey + return entity.Visitor{}, database.ErrDuplicatedKey } - return database.Visitor{}, fmt.Errorf("create visitor: %w", err) + return entity.Visitor{}, fmt.Errorf("create visitor: %w", err) } if oid, ok := res.InsertedID.(bson.ObjectID); ok { - visitor.ID = database.ID(oid.String()) + visitor.ID = entity.ID(oid.String()) } return visitor, nil } // FindVisitor retrieves a visitor record by its id. -func (r *HelloRepository) FindVisitor(id database.ID) (database.Visitor, error) { - visitor := database.Visitor{} +func (r *HelloRepository) FindVisitor(id entity.ID) (entity.Visitor, error) { + visitor := entity.Visitor{} filter := bson.M{"_id": id} err := r.collection.FindOne(context.Background(), filter).Decode(&visitor) if errors.Is(err, mongo.ErrNoDocuments) { - return database.Visitor{}, database.ErrDocumentNotFound + return entity.Visitor{}, database.ErrDocumentNotFound } else if err != nil { - return database.Visitor{}, fmt.Errorf("find visitor: %w", err) + return entity.Visitor{}, fmt.Errorf("find visitor: %w", err) } return visitor, nil } // UpdateVisitor updates an existing visitor record. -func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { +func (r *HelloRepository) UpdateVisitor(visitor entity.Visitor) error { visitor.UpdatedAt = time.Now() filter := bson.M{"_id": visitor.ID} update := bson.M{ @@ -86,7 +85,7 @@ func (r *HelloRepository) UpdateVisitor(visitor database.Visitor) error { } // DeleteVisitor removes a visitor record by its id. -func (r *HelloRepository) DeleteVisitor(id database.ID) error { +func (r *HelloRepository) DeleteVisitor(id entity.ID) error { filter := bson.M{"_id": id} result, err := r.collection.DeleteOne(context.Background(), filter) if err != nil { diff --git a/backend-go/internal/infra/database/visitor.go b/backend-go/internal/infra/database/visitor.go new file mode 100644 index 00000000..bf39108e --- /dev/null +++ b/backend-go/internal/infra/database/visitor.go @@ -0,0 +1,24 @@ +package database + +import ( + "github.com/yorkie-team/codepair/backend/internal/infra/database/entity" +) + +// Visitor defines the interface for CRUD operations on visitor records. +type Visitor interface { + // CreateVisitor creates a new visitor record. + // It accepts a Visitor object and returns an error if creation fails. + CreateVisitor(visitor entity.Visitor) (entity.Visitor, error) + + // FindVisitor retrieves a visitor record by its unique identifier. + // It returns the Visitor and an error if the record cannot be found. + FindVisitor(id entity.ID) (entity.Visitor, error) + + // UpdateVisitor updates an existing visitor record. + // It accepts a Visitor object (including its ID) and returns an error if the update fails. + UpdateVisitor(visitor entity.Visitor) error + + // DeleteVisitor removes a visitor record by its unique identifier. + // It returns an error if the deletion operation fails. + DeleteVisitor(id entity.ID) error +} From 8efa5f809b5a54b9b5e6b0c6639dd12ffa78ca45 Mon Sep 17 00:00:00 2001 From: Changyu Moon Date: Wed, 26 Feb 2025 11:43:08 +0900 Subject: [PATCH 22/22] Remove handler comment --- backend-go/internal/core/hello/handler.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backend-go/internal/core/hello/handler.go b/backend-go/internal/core/hello/handler.go index f54279f6..0b124741 100644 --- a/backend-go/internal/core/hello/handler.go +++ b/backend-go/internal/core/hello/handler.go @@ -14,8 +14,6 @@ type Handler struct { service *Service } -// createHello handles the POST /hello endpoint. -// It creates a new visitor record and returns a hello message. func (h *Handler) createHello(c echo.Context) error { req := &models.HelloRequest{} @@ -31,8 +29,6 @@ func (h *Handler) createHello(c echo.Context) error { }) } -// readHello handles the GET /hello/:id endpoint. -// It retrieves a visitor record by its id and returns a hello message. func (h *Handler) readHello(c echo.Context) error { id := c.Param("id") nickname, err := h.service.readNickname(id) @@ -44,8 +40,6 @@ func (h *Handler) readHello(c echo.Context) error { }) } -// updateHello handles the PUT /hello/:id endpoint. -// It updates an existing visitor record and returns a confirmation message. func (h *Handler) updateHello(c echo.Context) error { id := c.Param("id") req := &models.HelloRequest{} @@ -60,8 +54,6 @@ func (h *Handler) updateHello(c echo.Context) error { }) } -// deleteHello handles the DELETE /hello/:id endpoint. -// It deletes a visitor record by its id and returns a confirmation message. func (h *Handler) deleteHello(c echo.Context) error { id := c.Param("id") if err := h.service.deleteHello(id); err != nil {