Skip to content

Commit c1dabc3

Browse files
committed
Add lesson04 / video01
1 parent d862b07 commit c1dabc3

File tree

14 files changed

+635
-0
lines changed

14 files changed

+635
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package bootstrap
2+
3+
import (
4+
"database/sql"
5+
"fmt"
6+
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal/creating"
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal/platform/server"
9+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal/platform/storage/mysql"
10+
_ "github.com/go-sql-driver/mysql"
11+
)
12+
13+
const (
14+
host = "localhost"
15+
port = 8080
16+
17+
dbUser = "codely"
18+
dbPass = "codely"
19+
dbHost = "localhost"
20+
dbPort = "3306"
21+
dbName = "codely"
22+
)
23+
24+
func Run() error {
25+
mysqlURI := fmt.Sprintf("%s:%s@tcp(%s:%s)/%s", dbUser, dbPass, dbHost, dbPort, dbName)
26+
db, err := sql.Open("mysql", mysqlURI)
27+
if err != nil {
28+
return err
29+
}
30+
31+
courseRepository := mysql.NewCourseRepository(db)
32+
33+
creatingCourseService := creating.NewCourseService(courseRepository)
34+
35+
srv := server.New(host, port, creatingCourseService)
36+
return srv.Run()
37+
}
+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package main
2+
3+
import (
4+
"log"
5+
6+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/cmd/api/bootstrap"
7+
)
8+
9+
func main() {
10+
if err := bootstrap.Run(); err != nil {
11+
log.Fatal(err)
12+
}
13+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
package mooc
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
8+
"github.com/google/uuid"
9+
)
10+
11+
var ErrInvalidCourseID = errors.New("invalid Course ID")
12+
13+
// CourseID represents the course unique identifier.
14+
type CourseID struct {
15+
value string
16+
}
17+
18+
// NewCourseID instantiate the VO for CourseID
19+
func NewCourseID(value string) (CourseID, error) {
20+
v, err := uuid.Parse(value)
21+
if err != nil {
22+
return CourseID{}, fmt.Errorf("%w: %s", ErrInvalidCourseID, value)
23+
}
24+
25+
return CourseID{
26+
value: v.String(),
27+
}, nil
28+
}
29+
30+
// String type converts the CourseID into string.
31+
func (id CourseID) String() string {
32+
return id.value
33+
}
34+
35+
var ErrEmptyCourseName = errors.New("the field Course Name can not be empty")
36+
37+
// CourseName represents the course name.
38+
type CourseName struct {
39+
value string
40+
}
41+
42+
// NewCourseName instantiate VO for CourseName
43+
func NewCourseName(value string) (CourseName, error) {
44+
if value == "" {
45+
return CourseName{}, ErrEmptyCourseName
46+
}
47+
48+
return CourseName{
49+
value: value,
50+
}, nil
51+
}
52+
53+
// String type converts the CourseName into string.
54+
func (name CourseName) String() string {
55+
return name.value
56+
}
57+
58+
var ErrEmptyDuration = errors.New("the field Duration can not be empty")
59+
60+
// CourseDuration represents the course duration.
61+
type CourseDuration struct {
62+
value string
63+
}
64+
65+
func NewCourseDuration(value string) (CourseDuration, error) {
66+
if value == "" {
67+
return CourseDuration{}, ErrEmptyDuration
68+
}
69+
70+
return CourseDuration{
71+
value: value,
72+
}, nil
73+
}
74+
75+
// String type converts the CourseDuration into string.
76+
func (duration CourseDuration) String() string {
77+
return duration.value
78+
}
79+
80+
// Course is the data structure that represents a course.
81+
type Course struct {
82+
id CourseID
83+
name CourseName
84+
duration CourseDuration
85+
}
86+
87+
// CourseRepository defines the expected behaviour from a course storage.
88+
type CourseRepository interface {
89+
Save(ctx context.Context, course Course) error
90+
}
91+
92+
//go:generate mockery --case=snake --outpkg=storagemocks --output=platform/storage/storagemocks --name=CourseRepository
93+
94+
// NewCourse creates a new course.
95+
func NewCourse(id, name, duration string) (Course, error) {
96+
idVO, err := NewCourseID(id)
97+
if err != nil {
98+
return Course{}, err
99+
}
100+
101+
nameVO, err := NewCourseName(name)
102+
if err != nil {
103+
return Course{}, err
104+
}
105+
106+
durationVO, err := NewCourseDuration(duration)
107+
if err != nil {
108+
return Course{}, err
109+
}
110+
111+
return Course{
112+
id: idVO,
113+
name: nameVO,
114+
duration: durationVO,
115+
}, nil
116+
}
117+
118+
// ID returns the course unique identifier.
119+
func (c Course) ID() CourseID {
120+
return c.id
121+
}
122+
123+
// Name returns the course name.
124+
func (c Course) Name() CourseName {
125+
return c.name
126+
}
127+
128+
// Duration returns the course duration.
129+
func (c Course) Duration() CourseDuration {
130+
return c.duration
131+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package creating
2+
3+
import (
4+
"context"
5+
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal"
7+
)
8+
9+
// CourseService is the default CourseService interface
10+
// implementation returned by creating.NewCourseService.
11+
type CourseService struct {
12+
courseRepository mooc.CourseRepository
13+
}
14+
15+
// NewCourseService returns the default Service interface implementation.
16+
func NewCourseService(courseRepository mooc.CourseRepository) CourseService {
17+
return CourseService{
18+
courseRepository: courseRepository,
19+
}
20+
}
21+
22+
// CreateCourse implements the creating.CourseService interface.
23+
func (s CourseService) CreateCourse(ctx context.Context, id, name, duration string) error {
24+
course, err := mooc.NewCourse(id, name, duration)
25+
if err != nil {
26+
return err
27+
}
28+
return s.courseRepository.Save(ctx, course)
29+
}
30+
31+
32+
33+
34+
35+
36+
37+
38+
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package creating
2+
3+
import (
4+
"context"
5+
"errors"
6+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal"
7+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal/platform/storage/storagemocks"
8+
"testing"
9+
10+
"github.com/stretchr/testify/mock"
11+
"github.com/stretchr/testify/require"
12+
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
func Test_CourseService_CreateCourse_RepositoryError(t *testing.T) {
17+
courseID := "37a0f027-15e6-47cc-a5d2-64183281087e"
18+
courseName := "Test Course"
19+
courseDuration := "10 months"
20+
21+
course, err := mooc.NewCourse(courseID, courseName, courseDuration)
22+
require.NoError(t, err)
23+
24+
courseRepositoryMock := new(storagemocks.CourseRepository)
25+
courseRepositoryMock.On("Save", mock.Anything, course).Return(errors.New("something unexpected happened"))
26+
27+
courseService := NewCourseService(courseRepositoryMock)
28+
29+
err = courseService.CreateCourse(context.Background(), courseID, courseName, courseDuration)
30+
31+
courseRepositoryMock.AssertExpectations(t)
32+
assert.Error(t, err)
33+
}
34+
35+
func Test_CourseService_CreateCourse_Succeed(t *testing.T) {
36+
courseID := "37a0f027-15e6-47cc-a5d2-64183281087e"
37+
courseName := "Test Course"
38+
courseDuration := "10 months"
39+
40+
course, err := mooc.NewCourse(courseID, courseName, courseDuration)
41+
require.NoError(t, err)
42+
43+
courseRepositoryMock := new(storagemocks.CourseRepository)
44+
courseRepositoryMock.On("Save", mock.Anything, course).Return(nil)
45+
46+
courseService := NewCourseService(courseRepositoryMock)
47+
48+
err = courseService.CreateCourse(context.Background(), courseID, courseName, courseDuration)
49+
50+
courseRepositoryMock.AssertExpectations(t)
51+
assert.NoError(t, err)
52+
}
53+
54+
55+
56+
57+
58+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package courses
2+
3+
import (
4+
"errors"
5+
"net/http"
6+
7+
mooc "github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal"
8+
"github.com/CodelyTV/go-hexagonal_http_api-course/04-01-application-service/internal/creating"
9+
"github.com/gin-gonic/gin"
10+
)
11+
12+
type createRequest struct {
13+
ID string `json:"id" binding:"required"`
14+
Name string `json:"name" binding:"required"`
15+
Duration string `json:"duration" binding:"required"`
16+
}
17+
18+
// CreateHandler returns an HTTP handler for courses creation.
19+
func CreateHandler(creatingCourseService creating.CourseService) gin.HandlerFunc {
20+
return func(ctx *gin.Context) {
21+
var req createRequest
22+
if err := ctx.BindJSON(&req); err != nil {
23+
ctx.JSON(http.StatusBadRequest, err.Error())
24+
return
25+
}
26+
27+
err := creatingCourseService.CreateCourse(ctx, req.ID, req.Name, req.Duration)
28+
29+
if err != nil {
30+
switch {
31+
case errors.Is(err, mooc.ErrInvalidCourseID),
32+
errors.Is(err, mooc.ErrEmptyCourseName), errors.Is(err, mooc.ErrInvalidCourseID):
33+
ctx.JSON(http.StatusBadRequest, err.Error())
34+
return
35+
default:
36+
ctx.JSON(http.StatusInternalServerError, err.Error())
37+
return
38+
}
39+
}
40+
41+
ctx.Status(http.StatusCreated)
42+
}
43+
}

0 commit comments

Comments
 (0)