Skip to content

Commit 1c402e4

Browse files
committed
04-03. Add fetching course command
1 parent bfd8a00 commit 1c402e4

File tree

10 files changed

+243
-0
lines changed

10 files changed

+243
-0
lines changed

04-03-command-bus/cmd/api/bootstrap/boostrap.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package bootstrap
33
import (
44
"database/sql"
55
"fmt"
6+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/fetching"
67

78
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/creating"
89
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal/platform/bus/inmemory"
@@ -36,9 +37,13 @@ func Run() error {
3637
courseRepository := mysql.NewCourseRepository(db)
3738

3839
creatingCourseService := creating.NewCourseService(courseRepository)
40+
fetchingCourseService := fetching.NewCourseFetchingService(courseRepository)
3941

4042
createCourseCommandHandler := creating.NewCourseCommandHandler(creatingCourseService)
43+
fetchingCourseCommandHandler := fetching.NewCourseCommandHandler(fetchingCourseService)
44+
4145
commandBus.Register(creating.CourseCommandType, createCourseCommandHandler)
46+
commandBus.Register(fetching.CourseCommandType, fetchingCourseCommandHandler)
4247

4348
srv := server.New(host, port, commandBus)
4449
return srv.Run()

04-03-command-bus/internal/course.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ type Course struct {
8787
// CourseRepository defines the expected behaviour from a course storage.
8888
type CourseRepository interface {
8989
Save(ctx context.Context, course Course) error
90+
GetAll(ctx context.Context) ([]Course, error)
9091
}
9192

9293
//go:generate mockery --case=snake --outpkg=storagemocks --output=platform/storage/storagemocks --name=CourseRepository

04-03-command-bus/internal/creating/service.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ func (s CourseService) CreateCourse(ctx context.Context, id, name, duration stri
2727
}
2828
return s.courseRepository.Save(ctx, course)
2929
}
30+
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package fetching
2+
3+
import (
4+
"context"
5+
"errors"
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal"
7+
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
9+
)
10+
11+
const CourseCommandType command.Type = "command.fetching.courses"
12+
13+
// CourseCommand is the command dispatched to create a new course.
14+
type CourseCommand struct {
15+
}
16+
// NewCourseCommand creates a new CourseCommand.
17+
func NewFetchCourseCommand() CourseCommand {
18+
return CourseCommand{}
19+
}
20+
21+
func (c CourseCommand) Type() command.Type {
22+
return CourseCommandType
23+
}
24+
25+
// CourseCommandHandler is the command handler
26+
// responsible for creating courses.
27+
type CourseCommandHandler struct {
28+
service FetchingCourseService
29+
}
30+
31+
// NewCourseCommandHandler initializes a new CourseCommandHandler.
32+
func NewCourseCommandHandler(service FetchingCourseService) CourseCommandHandler {
33+
return CourseCommandHandler{
34+
service: service,
35+
}
36+
}
37+
38+
// Handle implements the command.Handler interface.
39+
func (h CourseCommandHandler) Handle(ctx context.Context, cmd command.Command) ([]mooc.Course, error) {
40+
_, ok := cmd.(CourseCommand)
41+
if !ok {
42+
return nil, errors.New("unexpected command")
43+
}
44+
45+
return h.service.GetAll(ctx)
46+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package fetching
2+
3+
import (
4+
"context"
5+
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/internal"
7+
)
8+
9+
// CourseService is the default CourseService interface
10+
// implementation returned by fetching.NewCourseFetchingService.
11+
type FetchingCourseService struct {
12+
courseRepository mooc.CourseRepository
13+
}
14+
15+
// NewCourseService returns the default Service interface implementation.
16+
func NewCourseFetchingService(courseRepository mooc.CourseRepository) FetchingCourseService {
17+
return FetchingCourseService{
18+
courseRepository: courseRepository,
19+
}
20+
}
21+
22+
// CreateCourse implements the creating.CourseService interface.
23+
func (s FetchingCourseService) GetAll(ctx context.Context) ([]mooc.Course, error) {
24+
return s.courseRepository.GetAll(ctx)
25+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package courses
2+
3+
import (
4+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-03-command-bus/kit/command"
5+
"github.com/gin-gonic/gin"
6+
)
7+
8+
type getResponse struct {
9+
Id string `json:"id"`
10+
Name string `json:"name"`
11+
Duration string `json:"duration"`
12+
}
13+
14+
// GetHandler returns an HTTP handler for courses.
15+
func GetHandler(commandBus command.Bus) gin.HandlerFunc {
16+
return func(ctx *gin.Context) {
17+
/*var courses, err := commandBus.Dispatch(ctx, fetching.NewFetchCourseCommand())
18+
19+
if err != nil {
20+
// Si quiero devolver error en ves de la lista se rompe me genera un error de unmarshal
21+
ctx.JSON(http.StatusInternalServerError, []getResponse{})
22+
return
23+
}
24+
response := make([]getResponse, 0, len(courses))
25+
for _, course := range courses {
26+
response = append(response, getResponse{
27+
Id: course.ID().String(),
28+
Name: course.Name().String(),
29+
Duration: course.Duration().String(),
30+
})
31+
}
32+
ctx.JSON(http.StatusOK, response)*/
33+
}
34+
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
package courses
2+
3+
import (
4+
"encoding/json"
5+
"errors"
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/02-04-domain-validations/internal"
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/02-04-domain-validations/internal/platform/storage/storagemocks"
8+
"github.com/gin-gonic/gin"
9+
"github.com/stretchr/testify/assert"
10+
"github.com/stretchr/testify/mock"
11+
"github.com/stretchr/testify/require"
12+
"log"
13+
"net/http"
14+
"net/http/httptest"
15+
"testing"
16+
)
17+
18+
func TestGetHandler(t *testing.T) {
19+
tests := map[string]struct {
20+
mockData []mooc.Course
21+
mockError error
22+
expectedResponse []getResponse
23+
expectedStatus int
24+
}{
25+
"Empty repo return 200 with empty list": {
26+
mockData: []mooc.Course{},
27+
mockError: nil,
28+
expectedStatus:http.StatusOK,
29+
expectedResponse: []getResponse{}},
30+
"Repo error return 500": {
31+
mockData: []mooc.Course{},
32+
mockError: errors.New("the field Duration can not be empty"),
33+
expectedStatus:http.StatusInternalServerError,
34+
expectedResponse: []getResponse{}},
35+
"Fully repo return 200 with list of courses":{
36+
mockData: []mooc.Course{ mockCourse("8a1c5cdc-ba57-445a-994d-aa412d23723f", "Courses Complete", "123")},
37+
mockError: nil,
38+
expectedStatus:http.StatusOK,
39+
expectedResponse: []getResponse{{Id: "8a1c5cdc-ba57-445a-994d-aa412d23723f", Name: "Courses Complete", Duration: "123"}}},
40+
}
41+
for key, value := range tests {
42+
t.Run(key, func(t *testing.T) {
43+
44+
courseRepository := new(storagemocks.CourseRepository)
45+
courseRepository.On("GetAll", mock.Anything).Return(value.mockData, value.mockError)
46+
gin.SetMode(gin.TestMode)
47+
r := gin.New()
48+
r.GET("/courses", GetHandler(courseRepository))
49+
50+
req, err := http.NewRequest(http.MethodGet, "/courses", nil)
51+
require.NoError(t, err)
52+
53+
rec := httptest.NewRecorder()
54+
r.ServeHTTP(rec, req)
55+
56+
res := rec.Result()
57+
defer res.Body.Close()
58+
59+
assert.Equal(t, value.expectedStatus, res.StatusCode)
60+
var response []getResponse
61+
if err := json.NewDecoder(res.Body).Decode(&response); err != nil {
62+
log.Fatalln(err)
63+
}
64+
65+
assert.Equal(t, value.expectedResponse, response)
66+
})
67+
}
68+
}
69+
70+
func mockCourse(id string, name string, duration string) mooc.Course {
71+
course, err := mooc.NewCourse(id, name, duration)
72+
if err != nil{
73+
log.Fatalln(err)
74+
}
75+
return course
76+
}

04-03-command-bus/internal/platform/server/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,4 +38,5 @@ func (s *Server) Run() error {
3838
func (s *Server) registerRoutes() {
3939
s.engine.GET("/health", health.CheckHandler())
4040
s.engine.POST("/courses", courses.CreateHandler(s.commandBus))
41+
s.engine.GET("/courses", courses.GetHandler(s.commandBus))
4142
}

04-03-command-bus/internal/platform/storage/mysql/course_repository.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,34 @@ func (r *CourseRepository) Save(ctx context.Context, course mooc.Course) error {
3737

3838
return nil
3939
}
40+
41+
42+
// GetAll implements the mooc.CourseRepository interface.
43+
func (r *CourseRepository) GetAll(ctx context.Context) (courses []mooc.Course, err error) {
44+
courseSQLStruct := sqlbuilder.NewSelectBuilder()
45+
courseSQLStruct.Select("id", "name", "duration")
46+
courseSQLStruct.From(sqlCourseTable)
47+
48+
sqlQuery, args := courseSQLStruct.Build()
49+
50+
51+
rows, err := r.db.QueryContext(ctx, sqlQuery, args...)
52+
if err != nil {
53+
return nil, fmt.Errorf("error trying to get course on database: %v", err)
54+
}
55+
defer rows.Close()
56+
courses = []mooc.Course{}
57+
for rows.Next() {
58+
var sqlCourse sqlCourse
59+
err := rows.Scan(sqlCourse.ID, sqlCourse.Name, sqlCourse.Duration)
60+
if err != nil {
61+
return nil, err
62+
}
63+
course, err := mooc.NewCourse(sqlCourse.ID, sqlCourse.Name, sqlCourse.Duration)
64+
if err != nil {
65+
return nil, err
66+
}
67+
courses = append(courses, course)
68+
}
69+
return courses, nil
70+
}

04-03-command-bus/internal/platform/storage/storagemocks/course_repository.go

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)