Skip to content

Commit d0a3097

Browse files
devleejbkokodak
andauthored
Implement Dummy API for Project Understanding (#445)
* Add hello api types * Implement dummy api * Apply comments * Fix lint * Apply comments * Fix lint errors --------- Co-authored-by: kokodak <[email protected]>
1 parent 62deec8 commit d0a3097

19 files changed

+844
-144
lines changed

api/backend-openapi-spec.json

+579-133
Large diffs are not rendered by default.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package models
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
)
7+
8+
func RequiredFieldError(field string) error {
9+
return errors.New(field + " is required")
10+
}
11+
12+
func MinLengthError(field string, min int) error {
13+
return errors.New(fmt.Sprintf("%s must be at least %d characters", field, min))
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package models
2+
3+
type HelloRequest struct {
4+
5+
// New nickname to say hello
6+
Nickname string `json:"nickname"`
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package models
2+
3+
type HelloResponse struct {
4+
5+
// Welcome message
6+
Message string `json:"message"`
7+
}

backend-go/api/codepair/v1/models/model_http_exception_response.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package models
33
type HttpExceptionResponse struct {
44

55
// HTTP status code
6-
StatusCode float32 `json:"statusCode"`
6+
StatusCode int `json:"statusCode"`
77

88
// Description of the error
99
Message string `json:"message"`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package models
2+
3+
func (r *HelloRequest) Validate() error {
4+
if r.Nickname == "" {
5+
return RequiredFieldError("nickname")
6+
}
7+
8+
if len(r.Nickname) < 2 {
9+
return MinLengthError("nickname", 2)
10+
}
11+
12+
return nil
13+
}

backend-go/internal/core/handlers.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package core
2+
3+
import (
4+
"github.com/yorkie-team/codepair/backend/internal/core/hello"
5+
"github.com/yorkie-team/codepair/backend/internal/infra/database/mongo"
6+
)
7+
8+
type Handlers struct {
9+
Hello *hello.Handler
10+
}
11+
12+
// NewHandlers creates a new handlers.
13+
func NewHandlers() *Handlers {
14+
// Repositories
15+
helloRepository := mongo.NewHelloRepository()
16+
17+
// Services
18+
helloService := hello.NewService(helloRepository)
19+
20+
// Handlers
21+
helloHandler := hello.NewHandler(helloService)
22+
23+
return &Handlers{
24+
Hello: helloHandler,
25+
}
26+
}
+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package hello
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/labstack/echo/v4"
7+
8+
"github.com/yorkie-team/codepair/backend/api/codepair/v1/models"
9+
"github.com/yorkie-team/codepair/backend/internal/transport/http"
10+
)
11+
12+
type Handler struct {
13+
helloService *Service
14+
}
15+
16+
// NewHandler creates a new handler for hello.
17+
func NewHandler(service *Service) *Handler {
18+
return &Handler{
19+
helloService: service,
20+
}
21+
}
22+
23+
// HelloCodePair returns a hello message for a given CodePairVisitor.
24+
func (h *Handler) HelloCodePair(e echo.Context) error {
25+
req := new(models.HelloRequest)
26+
27+
if err := http.BindAndValidateRequest(e, req); err != nil {
28+
return fmt.Errorf("%w", err)
29+
}
30+
31+
helloMessage, err := h.helloService.HelloCodePair(e, CodePairVisitor{
32+
Nickname: req.Nickname,
33+
})
34+
if err != nil {
35+
return fmt.Errorf("%w", http.NewErrorResponse(e, err))
36+
}
37+
38+
return fmt.Errorf("%w", http.NewOkResponse(e, models.HelloResponse{
39+
Message: helloMessage,
40+
}))
41+
}
+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package hello
2+
3+
// CodePairVisitor is a visitor for CodePair
4+
type CodePairVisitor struct {
5+
// Nickname is the nickname of the visitor
6+
Nickname string
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package hello
2+
3+
type Repository interface {
4+
// ReadHelloMessageFor reads a hello message for a given CodePairVisitor
5+
ReadHelloMessageFor(codePairVisitor CodePairVisitor) (string, error)
6+
}
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package hello
2+
3+
import (
4+
"github.com/labstack/echo/v4"
5+
"github.com/yorkie-team/codepair/backend/internal/transport/http"
6+
)
7+
8+
type Service struct {
9+
helloRepository Repository
10+
}
11+
12+
// NewService creates a new service for hello.
13+
func NewService(repository Repository) *Service {
14+
return &Service{
15+
helloRepository: repository,
16+
}
17+
}
18+
19+
// HelloCodePair returns a hello message for a given CodePairVisitor
20+
func (s *Service) HelloCodePair(e echo.Context, codePairVisitor CodePairVisitor) (string, error) {
21+
helloMessage, err := s.helloRepository.ReadHelloMessageFor(codePairVisitor)
22+
if err != nil {
23+
e.Logger().Fatal(err)
24+
return "", http.ErrInternalServerError
25+
}
26+
27+
return helloMessage, nil
28+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package mongo
2+
3+
import "github.com/yorkie-team/codepair/backend/internal/core/hello"
4+
5+
type HelloRepository struct{}
6+
7+
// NewHelloRepository creates a new HelloRepository.
8+
func NewHelloRepository() HelloRepository {
9+
return HelloRepository{}
10+
}
11+
12+
func (h HelloRepository) ReadHelloMessageFor(codePairVisitor hello.CodePairVisitor) (string, error) {
13+
return "Hello, " + codePairVisitor.Nickname + "!", nil
14+
}

backend-go/internal/server/routes.go

+4-8
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,11 @@
11
package server
22

33
import (
4-
"fmt"
5-
"net/http"
6-
74
"github.com/labstack/echo/v4"
5+
"github.com/yorkie-team/codepair/backend/internal/core"
86
)
97

10-
func RegisterRoutes(e *echo.Echo) {
11-
e.GET("/", func(c echo.Context) error {
12-
err := c.String(http.StatusOK, "Hello, World!")
13-
return fmt.Errorf("error: %w", err)
14-
})
8+
// RegisterRoutes registers routes for the server.
9+
func RegisterRoutes(e *echo.Echo, handlers *core.Handlers) {
10+
e.POST("/hello", handlers.Hello.HelloCodePair)
1511
}

backend-go/internal/server/server.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,18 @@ import (
88
"github.com/labstack/echo/v4"
99

1010
"github.com/yorkie-team/codepair/backend/internal/config"
11+
"github.com/yorkie-team/codepair/backend/internal/core"
1112
)
1213

1314
type CodePair struct {
1415
config *config.Config
1516
echo *echo.Echo
1617
}
1718

19+
// New creates a new CodePair server.
1820
func New(e *echo.Echo, conf *config.Config) *CodePair {
19-
RegisterRoutes(e)
21+
handlers := core.NewHandlers()
22+
RegisterRoutes(e, handlers)
2023

2124
cp := &CodePair{
2225
config: conf,
@@ -25,6 +28,7 @@ func New(e *echo.Echo, conf *config.Config) *CodePair {
2528
return cp
2629
}
2730

31+
// Start starts the server.
2832
func (c *CodePair) Start() error {
2933
addr := fmt.Sprintf(":%d", c.config.Server.Port)
3034
if err := c.echo.Start(addr); !errors.Is(err, http.ErrServerClosed) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package http
2+
3+
import (
4+
"errors"
5+
6+
"github.com/yorkie-team/codepair/backend/api/codepair/v1/models"
7+
)
8+
9+
// ConvertErrorToResponse converts an error to a response.
10+
func ConvertErrorToResponse(err error) models.HttpExceptionResponse {
11+
switch {
12+
case errors.Is(err, ErrInternalServerError):
13+
return NewInternalServerErrorResponse()
14+
default:
15+
return NewInvalidJSONErrorResponse()
16+
}
17+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package http
2+
3+
import (
4+
nethttp "net/http"
5+
6+
"github.com/yorkie-team/codepair/backend/api/codepair/v1/models"
7+
)
8+
9+
func newHTTPExceptionResponse(statusCode int, message string) models.HttpExceptionResponse {
10+
return models.HttpExceptionResponse{
11+
StatusCode: statusCode,
12+
Message: message,
13+
}
14+
}
15+
16+
// NewInvalidJSONErrorResponse creates a HttpExceptionResponse that represents an invalid JSON error.
17+
func NewInvalidJSONErrorResponse() models.HttpExceptionResponse {
18+
return newHTTPExceptionResponse(nethttp.StatusBadRequest, "Invalid JSON")
19+
}
20+
21+
// NewValidationErrorResponse creates a HttpExceptionResponse that represents a validation error.
22+
func NewValidationErrorResponse(reason string) models.HttpExceptionResponse {
23+
return newHTTPExceptionResponse(nethttp.StatusBadRequest, reason)
24+
}
25+
26+
// NewInternalServerErrorResponse creates a HttpExceptionResponse that represents a internal server error.
27+
func NewInternalServerErrorResponse() models.HttpExceptionResponse {
28+
return newHTTPExceptionResponse(nethttp.StatusInternalServerError, "Internal Server Error")
29+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package http
2+
3+
import "errors"
4+
5+
var (
6+
// ErrInternalServerError is an internal server error
7+
ErrInternalServerError = errors.New("internal server error")
8+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package http
2+
3+
import (
4+
"fmt"
5+
nethttp "net/http"
6+
7+
"github.com/labstack/echo/v4"
8+
)
9+
10+
type request interface {
11+
Validate() error
12+
}
13+
14+
// BindAndValidateRequest binds and validates the request.
15+
// If the request is invalid, it returns an error response.
16+
func BindAndValidateRequest(e echo.Context, req request) error {
17+
if err := e.Bind(req); err != nil {
18+
return fmt.Errorf("failed to bind request: %w", e.JSON(nethttp.StatusBadRequest, NewInvalidJSONErrorResponse()))
19+
}
20+
if err := req.Validate(); err != nil {
21+
return fmt.Errorf("validation failed: %w", e.JSON(nethttp.StatusBadRequest, NewValidationErrorResponse(err.Error())))
22+
}
23+
return nil
24+
}
25+
26+
// NewErrorResponse handles the creation and response of an error.
27+
// It converts the provided error into a response structure and sends it as a JSON response.
28+
// The response status code is determined by the error's associated status.
29+
func NewErrorResponse(e echo.Context, err error) error {
30+
resp := ConvertErrorToResponse(err)
31+
return fmt.Errorf("returning error response as JSON: %w", e.JSON(resp.StatusCode, ConvertErrorToResponse(err)))
32+
}
33+
34+
// NewOkResponse sends a JSON response with a status code of 200.
35+
func NewOkResponse(e echo.Context, resp interface{}) error {
36+
return fmt.Errorf("returning success response as JSON: %w", e.JSON(nethttp.StatusOK, resp))
37+
}

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"lint:check": "pnpm run --parallel lint:check",
1616
"format": "pnpm run --parallel format",
1717
"format:check": "pnpm run --parallel format:check",
18-
"generate:backend": "openapi-generator-cli generate -i ./api/backend-openapi-spec.json -g go-echo-server -o ./backend-go/api/codepair/v1 --global-property models",
18+
"generate:backend": "openapi-generator-cli generate -i ./api/backend-openapi-spec.json -g go-echo-server -o ./backend-go/api/codepair/v1 --global-property models --type-mappings int32=int",
1919
"generate:frontend": "openapi-generator-cli generate -i ./api/backend-openapi-spec.json -g typescript-axios -o ./frontend/src/api"
2020
},
2121
"devDependencies": {

0 commit comments

Comments
 (0)