From 5a1a01acfbc226059ec99dca2743fb979ccafdfd Mon Sep 17 00:00:00 2001 From: litsynp Date: Mon, 14 Oct 2024 22:06:48 +0900 Subject: [PATCH 01/13] feat: add dummy event API --- api/common.go | 8 ++ cmd/server/handler/event_handler.go | 186 ++++++++++++++++++++++++++++ cmd/server/router.go | 10 ++ internal/domain/event/request.go | 46 +++++++ internal/domain/event/view.go | 30 +++++ internal/domain/event/vo.go | 50 ++++++++ 6 files changed, 330 insertions(+) create mode 100644 cmd/server/handler/event_handler.go create mode 100644 internal/domain/event/request.go create mode 100644 internal/domain/event/view.go create mode 100644 internal/domain/event/vo.go diff --git a/api/common.go b/api/common.go index 4efbe3bc..85d22909 100644 --- a/api/common.go +++ b/api/common.go @@ -3,8 +3,16 @@ package pnd import ( "encoding/json" "net/http" + + "github.com/google/uuid" ) +type CursorPaginatedView[T interface{}] struct { + Items []T `json:"items"` + Prev uuid.NullUUID `json:"prev"` + Next uuid.NullUUID `json:"next"` +} + type PaginatedView[T interface{}] struct { Page int `json:"page"` Size int `json:"size"` diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go new file mode 100644 index 00000000..b22fcbf2 --- /dev/null +++ b/cmd/server/handler/event_handler.go @@ -0,0 +1,186 @@ +package handler + +import ( + "log" + "net/http" + "time" + + "github.com/google/uuid" + "github.com/labstack/echo/v4" + pnd "github.com/pet-sitter/pets-next-door-api/api" + "github.com/pet-sitter/pets-next-door-api/internal/domain/event" + "github.com/pet-sitter/pets-next-door-api/internal/domain/media" + "github.com/pet-sitter/pets-next-door-api/internal/domain/user" + "github.com/pet-sitter/pets-next-door-api/internal/service" +) + +type EventHandler struct { + authService service.AuthService +} + +func NewEventHandler(authService service.AuthService) *EventHandler { + return &EventHandler{ + authService: authService, + } +} + +func generateDummyEvent() event.ShortTermView { + profileImageURL := "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" + now := time.Now() + return event.ShortTermView{ + BaseView: event.BaseView{ + ID: uuid.New(), + EventType: event.ShortTerm, + Author: user.WithoutPrivateInfo{ + ID: uuid.New(), + Nickname: "멍냥이", + ProfileImageURL: &profileImageURL, + }, + Name: "name", + Description: "description", + Media: media.DetailView{ + ID: uuid.New(), + MediaType: media.TypeImage, + URL: "https://images.unsplash.com/" + + "photo-1493225457124-a3eb161ffa5f?q=80&w=2970&auto=format" + + "&fit=crop&ixlib=rb-4.0.3&ixid=" + + "M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", + CreatedAt: now, + }, + Topic: event.ETC, + }, + } +} + +// FindEvents godoc +// @Summary 이벤트를 조회합니다. +// @Description +// @Tags events +// @Accept json +// @Produce json +// @Param author_id query string false "작성자 ID" +// @Param page query int false "페이지 번호" default(1) +// @Param size query int false "페이지 사이즈" default(20) +// @Success 200 {object} pnd.CursorPaginatedView[event.View] +// @Router /events [get] +func (h *EventHandler) FindEvents(c echo.Context) error { + return c.JSON( + http.StatusOK, + pnd.CursorPaginatedView[event.ShortTermView]{ + Items: []event.ShortTermView{generateDummyEvent()}, + }, + ) +} + +// FindEventByID godoc +// @Summary ID로 이벤트를 조회합니다. +// @Description +// @Tags events +// @Produce json +// @Param id path string true "이벤트 ID" +// @Success 200 {object} event.View +// @Router /events/{id} [get] +func (h *EventHandler) FindEventByID(c echo.Context) error { + id, err := pnd.ParseIDFromPath(c, "id") + if err != nil { + return err + } + + res := generateDummyEvent() + res.ID = id + return c.JSON(http.StatusOK, res) +} + +// CreateEvent godoc +// @Summary 이벤트를 생성합니다. +// @Description +// @Tags events +// @Accept json +// @Produce json +// @Param request body event.CreateRequest true "이벤트 생성 요청" +// @Security FirebaseAuth +// @Success 201 {object} event.View +// @Router /events [post] +func (h *EventHandler) CreateEvent(c echo.Context) error { + foundUser, err := h.authService.VerifyAuthAndGetUser( + c.Request().Context(), + c.Request().Header.Get("Authorization"), + ) + if err != nil { + return err + } + uid := foundUser.FirebaseUID + + var reqBody event.CreateRequest + if err := pnd.ParseBody(c, &reqBody); err != nil { + return err + } + + log.Printf("uid: %s, reqBody: %+v", uid, reqBody) + // TODO: Implement create event logic + + created := generateDummyEvent() + created.ID = uuid.New() + + return c.JSON(http.StatusCreated, created) +} + +// UpdateEvent godoc +// @Summary 이벤트를 수정합니다. +// @Description +// @Tags events +// @Accept json +// @Produce json +// @Security FirebaseAuth +// @Param request body event.UpdateRequest true "이벤트 수정 요청" +// @Success 200 +// @Router /events [put] +func (h *EventHandler) UpdateEvent(c echo.Context) error { + foundUser, err := h.authService.VerifyAuthAndGetUser( + c.Request().Context(), + c.Request().Header.Get("Authorization"), + ) + if err != nil { + return err + } + uid := foundUser.FirebaseUID + + var reqBody event.UpdateRequest + if err := pnd.ParseBody(c, &reqBody); err != nil { + return err + } + + log.Printf("uid: %s, reqBody: %+v", uid, reqBody) + // TODO: Implement update event logic + + return c.JSON(http.StatusOK, nil) +} + +// DeleteEvent godoc +// @Summary 이벤트를 삭제합니다. +// @Description +// @Tags events +// @Security FirebaseAuth +// @Param id path string true "이벤트 ID" +// @Success 200 +// @Router /events/{id} [delete] +func (h *EventHandler) DeleteEvent(c echo.Context) error { + foundUser, err := h.authService.VerifyAuthAndGetUser( + c.Request().Context(), + c.Request().Header.Get("Authorization"), + ) + if err != nil { + return err + } + uid := foundUser.FirebaseUID + + id, err := pnd.ParseIDFromPath(c, "id") + if err != nil { + return err + } + + log.Printf("uid: %s, id: %s", uid, id) + // TODO: Implement delete event logic + + return c.JSON(http.StatusOK, nil) +} diff --git a/cmd/server/router.go b/cmd/server/router.go index 68969ae9..a67e84f3 100644 --- a/cmd/server/router.go +++ b/cmd/server/router.go @@ -68,6 +68,7 @@ func NewRouter(app *firebaseinfra.FirebaseApp) (*echo.Echo, error) { sosPostHandler := handler.NewSOSPostHandler(*sosPostService, authService) conditionHandler := handler.NewConditionHandler(*conditionService) chatHandler := handler.NewChatHandler(authService, *chatService) + eventHandler := handler.NewEventHandler(authService) // // InMemoryStateManager는 클라이언트와 채팅방의 상태를 메모리에 저장하고 관리합니다. // // 이 메서드는 단순하고 빠르며 테스트 목적으로 적합합니다. @@ -170,6 +171,15 @@ func NewRouter(app *firebaseinfra.FirebaseApp) (*echo.Echo, error) { postAPIGroup.GET("/sos/conditions", conditionHandler.FindConditions) } + eventAPIGroup := apiRouteGroup.Group("/events") + { + eventAPIGroup.GET("", eventHandler.FindEvents) + eventAPIGroup.GET("/:id", eventHandler.FindEventByID) + eventAPIGroup.POST("", eventHandler.CreateEvent) + eventAPIGroup.PUT("/:id", eventHandler.UpdateEvent) + eventAPIGroup.DELETE("/:id", eventHandler.DeleteEvent) + } + upgrader := wschat.NewDefaultUpgrader() wsServerV2 := wschat.NewWSServer(upgrader, authService, *mediaService) diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go new file mode 100644 index 00000000..37af6d89 --- /dev/null +++ b/internal/domain/event/request.go @@ -0,0 +1,46 @@ +package event + +type CreateRequest struct { + BaseCreateRequest + RecurringPeriod *EventRecurringPeriod `json:"recurringPeriod,omitempty"` +} + +type BaseCreateRequest struct { + EventType EventType `json:"type"` + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topic EventTopic `json:"topic"` +} + +type ShortTermCreateRequest struct { + BaseCreateRequest +} + +type RecurringCreateRequest struct { + BaseCreateRequest + RecurringPeriod EventRecurringPeriod `json:"recurringPeriod"` +} + +type UpdateRequest struct { + BaseUpdateRequest + RecurringPeriod *EventRecurringPeriod `json:"recurringPeriod,omitempty"` +} + +type BaseUpdateRequest struct { + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topic EventTopic `json:"topic"` +} + +type ShortTermUpdateRequest struct { + BaseUpdateRequest +} + +type RecurringUpdateRequest struct { + BaseUpdateRequest + ReccuringPeriod EventRecurringPeriod `json:"recurringPeriod"` +} diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go new file mode 100644 index 00000000..552cd1ea --- /dev/null +++ b/internal/domain/event/view.go @@ -0,0 +1,30 @@ +package event + +import ( + "github.com/google/uuid" + "github.com/pet-sitter/pets-next-door-api/internal/domain/media" + "github.com/pet-sitter/pets-next-door-api/internal/domain/user" +) + +type View struct { + ShortTermView + RecurringPeriod *EventRecurringPeriod `json:"recurringPeriod,omitempty"` +} + +type BaseView struct { + ID uuid.UUID `json:"id"` + EventType EventType `json:"type"` + Author user.WithoutPrivateInfo `json:"author"` + Name string `json:"name"` + Description string `json:"description"` + Media media.DetailView `json:"media"` + Topic EventTopic `json:"topic"` +} + +type ShortTermView struct { + BaseView +} +type RecurringView struct { + BaseView + RecurringPeriod EventRecurringPeriod `json:"recurringPeriod"` +} diff --git a/internal/domain/event/vo.go b/internal/domain/event/vo.go new file mode 100644 index 00000000..a8e1cc89 --- /dev/null +++ b/internal/domain/event/vo.go @@ -0,0 +1,50 @@ +package event + +type EventType string + +const ( + // 단기 + ShortTerm EventType = "SHORT_TERM" + // 정기 + Recurring EventType = "RECURRING" +) + +func (e EventType) IsValid() bool { + switch e { + case ShortTerm, Recurring: + return true + } + return false +} + +type EventTopic string + +const ( + ETC EventTopic = "ETC" +) + +func (e EventTopic) IsValid() bool { + // TODO: Add more topics + return e == ETC +} + +type EventRecurringPeriod string + +const ( + // 매일 + Daily EventRecurringPeriod = "DAILY" + // 매주 + Weekly EventRecurringPeriod = "WEEKLY" + // 2주에 한 번 + Biweekly EventRecurringPeriod = "BIWEEKLY" + // 매달 + Monthly EventRecurringPeriod = "MONTHLY" +) + +func (e EventRecurringPeriod) IsValid() bool { + switch e { + case Daily, Weekly, Biweekly, Monthly: + return true + } + return false +} From 3b6da84ea07c90917c4a88570b65a2e346ede7ab Mon Sep 17 00:00:00 2001 From: litsynp Date: Fri, 22 Nov 2024 22:48:55 +0900 Subject: [PATCH 02/13] fix: topic to topics --- cmd/server/handler/event_handler.go | 2 +- internal/domain/event/request.go | 22 +++++++++++----------- internal/domain/event/view.go | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index b22fcbf2..e1482488 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -47,7 +47,7 @@ func generateDummyEvent() event.ShortTermView { "M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", CreatedAt: now, }, - Topic: event.ETC, + Topics: []event.EventTopic{event.ETC}, }, } } diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index 37af6d89..5c0477ab 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -6,12 +6,12 @@ type CreateRequest struct { } type BaseCreateRequest struct { - EventType EventType `json:"type"` - AuthorID int `json:"authorId"` - Name string `json:"name"` - Description string `json:"description"` - MediaID int `json:"mediaId"` - Topic EventTopic `json:"topic"` + EventType EventType `json:"type"` + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topics []EventTopic `json:"topics"` } type ShortTermCreateRequest struct { @@ -29,11 +29,11 @@ type UpdateRequest struct { } type BaseUpdateRequest struct { - AuthorID int `json:"authorId"` - Name string `json:"name"` - Description string `json:"description"` - MediaID int `json:"mediaId"` - Topic EventTopic `json:"topic"` + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topics []EventTopic `json:"topics"` } type ShortTermUpdateRequest struct { diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 552cd1ea..5ff09581 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -18,7 +18,7 @@ type BaseView struct { Name string `json:"name"` Description string `json:"description"` Media media.DetailView `json:"media"` - Topic EventTopic `json:"topic"` + Topics []EventTopic `json:"topics"` } type ShortTermView struct { From 2030f9881a7d910ef3875294c21faad39ba3b964 Mon Sep 17 00:00:00 2001 From: litsynp Date: Fri, 22 Nov 2024 22:51:12 +0900 Subject: [PATCH 03/13] fix: add createdAt and updatedAt to event --- cmd/server/handler/event_handler.go | 4 +++- internal/domain/event/view.go | 4 ++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index e1482488..eed9356c 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -47,7 +47,9 @@ func generateDummyEvent() event.ShortTermView { "M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", CreatedAt: now, }, - Topics: []event.EventTopic{event.ETC}, + Topics: []event.EventTopic{event.ETC}, + CreatedAt: now, + UpdatedAt: now, }, } } diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 5ff09581..4eecd0dd 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -1,6 +1,8 @@ package event import ( + "time" + "github.com/google/uuid" "github.com/pet-sitter/pets-next-door-api/internal/domain/media" "github.com/pet-sitter/pets-next-door-api/internal/domain/user" @@ -19,6 +21,8 @@ type BaseView struct { Description string `json:"description"` Media media.DetailView `json:"media"` Topics []EventTopic `json:"topics"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type ShortTermView struct { From a81f7e627e431f65ee2ea9f8b0948c666134f2e8 Mon Sep 17 00:00:00 2001 From: litsynp Date: Fri, 22 Nov 2024 23:30:59 +0900 Subject: [PATCH 04/13] feat: add gender condition for event with stringer --- Makefile | 1 + cmd/server/handler/event_handler.go | 7 ++-- .../domain/event/gendercondition_string.go | 25 ++++++++++++ internal/domain/event/request.go | 24 ++++++------ internal/domain/event/view.go | 19 ++++----- internal/domain/event/vo.go | 39 +++++++++++++++++++ 6 files changed, 92 insertions(+), 23 deletions(-) create mode 100644 internal/domain/event/gendercondition_string.go diff --git a/Makefile b/Makefile index 17b4611d..2380ec1c 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ clean: make docs:clean compile: + go generate ./... go build -o ${BUILD_DIR}/${SERVER_BINARY_NAME} ./cmd/server build: diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index eed9356c..22ef0df5 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -47,9 +47,10 @@ func generateDummyEvent() event.ShortTermView { "M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", CreatedAt: now, }, - Topics: []event.EventTopic{event.ETC}, - CreatedAt: now, - UpdatedAt: now, + Topics: []event.EventTopic{event.ETC}, + GenderCondition: "all", + CreatedAt: now, + UpdatedAt: now, }, } } diff --git a/internal/domain/event/gendercondition_string.go b/internal/domain/event/gendercondition_string.go new file mode 100644 index 00000000..7e7dfa6f --- /dev/null +++ b/internal/domain/event/gendercondition_string.go @@ -0,0 +1,25 @@ +// Code generated by "stringer -type=GenderCondition"; DO NOT EDIT. + +package event + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Male-0] + _ = x[Female-1] + _ = x[All-2] +} + +const _GenderCondition_name = "MaleFemaleAll" + +var _GenderCondition_index = [...]uint8{0, 4, 10, 13} + +func (i GenderCondition) String() string { + if i < 0 || i >= GenderCondition(len(_GenderCondition_index)-1) { + return "GenderCondition(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _GenderCondition_name[_GenderCondition_index[i]:_GenderCondition_index[i+1]] +} diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index 5c0477ab..bab0e0be 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -6,12 +6,13 @@ type CreateRequest struct { } type BaseCreateRequest struct { - EventType EventType `json:"type"` - AuthorID int `json:"authorId"` - Name string `json:"name"` - Description string `json:"description"` - MediaID int `json:"mediaId"` - Topics []EventTopic `json:"topics"` + EventType EventType `json:"type"` + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topics []EventTopic `json:"topics"` + GenderCondition string `json:"genderCondition" enums:"male,female,all"` } type ShortTermCreateRequest struct { @@ -29,11 +30,12 @@ type UpdateRequest struct { } type BaseUpdateRequest struct { - AuthorID int `json:"authorId"` - Name string `json:"name"` - Description string `json:"description"` - MediaID int `json:"mediaId"` - Topics []EventTopic `json:"topics"` + AuthorID int `json:"authorId"` + Name string `json:"name"` + Description string `json:"description"` + MediaID int `json:"mediaId"` + Topics []EventTopic `json:"topics"` + GenderCondition string `json:"genderCondition" enums:"male,female,all"` } type ShortTermUpdateRequest struct { diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 4eecd0dd..c089aac8 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -14,15 +14,16 @@ type View struct { } type BaseView struct { - ID uuid.UUID `json:"id"` - EventType EventType `json:"type"` - Author user.WithoutPrivateInfo `json:"author"` - Name string `json:"name"` - Description string `json:"description"` - Media media.DetailView `json:"media"` - Topics []EventTopic `json:"topics"` - CreatedAt time.Time `json:"createdAt"` - UpdatedAt time.Time `json:"updatedAt"` + ID uuid.UUID `json:"id"` + EventType EventType `json:"type"` + Author user.WithoutPrivateInfo `json:"author"` + Name string `json:"name"` + Description string `json:"description"` + Media media.DetailView `json:"media"` + Topics []EventTopic `json:"topics"` + GenderCondition string `json:"genderCondition"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` } type ShortTermView struct { diff --git a/internal/domain/event/vo.go b/internal/domain/event/vo.go index a8e1cc89..6dbae357 100644 --- a/internal/domain/event/vo.go +++ b/internal/domain/event/vo.go @@ -1,5 +1,11 @@ package event +import ( + "encoding/json" + "errors" + "strings" +) + type EventType string const ( @@ -48,3 +54,36 @@ func (e EventRecurringPeriod) IsValid() bool { } return false } + +//go:generate stringer -type=GenderCondition +type GenderCondition int + +const ( + Male GenderCondition = iota + Female + All +) + +func (g *GenderCondition) MarshalJSON() ([]byte, error) { + return json.Marshal(strings.ToLower(g.String())) +} + +func (g *GenderCondition) UnmarshalJSON(b []byte) error { + var s string + if err := json.Unmarshal(b, &s); err != nil { + return err + } + + switch strings.ToLower(s) { + case "male": + *g = Male + case "female": + *g = Female + case "all": + *g = All + default: + return errors.New("invalid GenderCondition") + } + + return nil +} From 67ad61af1356f3c17fb976ee30b0e2525d710a76 Mon Sep 17 00:00:00 2001 From: litsynp Date: Sat, 23 Nov 2024 16:46:46 +0900 Subject: [PATCH 05/13] feat: add max participants to event --- cmd/server/handler/event_handler.go | 2 ++ internal/domain/event/request.go | 6 ++++-- internal/domain/event/view.go | 1 + 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index 22ef0df5..7893d9b8 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -27,6 +27,7 @@ func NewEventHandler(authService service.AuthService) *EventHandler { func generateDummyEvent() event.ShortTermView { profileImageURL := "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" now := time.Now() + maxParticipants := 3 return event.ShortTermView{ BaseView: event.BaseView{ ID: uuid.New(), @@ -48,6 +49,7 @@ func generateDummyEvent() event.ShortTermView { CreatedAt: now, }, Topics: []event.EventTopic{event.ETC}, + MaxParticipants: &maxParticipants, GenderCondition: "all", CreatedAt: now, UpdatedAt: now, diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index bab0e0be..a65c4a48 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -12,7 +12,8 @@ type BaseCreateRequest struct { Description string `json:"description"` MediaID int `json:"mediaId"` Topics []EventTopic `json:"topics"` - GenderCondition string `json:"genderCondition" enums:"male,female,all"` + MaxParticipants *int `json:"maxParticipants,omitempty"` + GenderCondition string `json:"genderCondition" enums:"male,female,all"` } type ShortTermCreateRequest struct { @@ -35,7 +36,8 @@ type BaseUpdateRequest struct { Description string `json:"description"` MediaID int `json:"mediaId"` Topics []EventTopic `json:"topics"` - GenderCondition string `json:"genderCondition" enums:"male,female,all"` + MaxParticipants *int `json:"maxParticipants,omitempty"` + GenderCondition string `json:"genderCondition" enums:"male,female,all"` } type ShortTermUpdateRequest struct { diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index c089aac8..5beb04a8 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -21,6 +21,7 @@ type BaseView struct { Description string `json:"description"` Media media.DetailView `json:"media"` Topics []EventTopic `json:"topics"` + MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` From 05eb55254f44ee19dc2ee6fae418a035421194aa Mon Sep 17 00:00:00 2001 From: litsynp Date: Sat, 23 Nov 2024 16:50:44 +0900 Subject: [PATCH 06/13] feat: add start time to event --- cmd/server/handler/event_handler.go | 2 ++ internal/domain/event/request.go | 4 ++++ internal/domain/event/view.go | 1 + 3 files changed, 7 insertions(+) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index 7893d9b8..c393ead1 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -27,6 +27,7 @@ func NewEventHandler(authService service.AuthService) *EventHandler { func generateDummyEvent() event.ShortTermView { profileImageURL := "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" now := time.Now() + startAt := now.AddDate(0, 0, 1) maxParticipants := 3 return event.ShortTermView{ BaseView: event.BaseView{ @@ -51,6 +52,7 @@ func generateDummyEvent() event.ShortTermView { Topics: []event.EventTopic{event.ETC}, MaxParticipants: &maxParticipants, GenderCondition: "all", + StartAt: &startAt, CreatedAt: now, UpdatedAt: now, }, diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index a65c4a48..5eee1f12 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -1,5 +1,7 @@ package event +import "time" + type CreateRequest struct { BaseCreateRequest RecurringPeriod *EventRecurringPeriod `json:"recurringPeriod,omitempty"` @@ -14,6 +16,7 @@ type BaseCreateRequest struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition" enums:"male,female,all"` + StartAt *time.Time `json:"startAt,omitempty"` } type ShortTermCreateRequest struct { @@ -38,6 +41,7 @@ type BaseUpdateRequest struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition" enums:"male,female,all"` + StartAt *time.Time `json:"startAt,omitempty"` } type ShortTermUpdateRequest struct { diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 5beb04a8..f036b90b 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -23,6 +23,7 @@ type BaseView struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition"` + StartAt *time.Time `json:"startAt,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } From a768a66db2ece9a10088dc7f74dc05c34d83125a Mon Sep 17 00:00:00 2001 From: litsynp Date: Sat, 23 Nov 2024 16:56:53 +0900 Subject: [PATCH 07/13] feat: add fee to event --- cmd/server/handler/event_handler.go | 1 + internal/domain/event/request.go | 2 ++ internal/domain/event/view.go | 1 + 3 files changed, 4 insertions(+) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index c393ead1..e0476186 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -52,6 +52,7 @@ func generateDummyEvent() event.ShortTermView { Topics: []event.EventTopic{event.ETC}, MaxParticipants: &maxParticipants, GenderCondition: "all", + Fee: 10000, StartAt: &startAt, CreatedAt: now, UpdatedAt: now, diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index 5eee1f12..fd9a866f 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -16,6 +16,7 @@ type BaseCreateRequest struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition" enums:"male,female,all"` + Fee *int `json:"fee,omitempty"` StartAt *time.Time `json:"startAt,omitempty"` } @@ -41,6 +42,7 @@ type BaseUpdateRequest struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition" enums:"male,female,all"` + Fee *int `json:"fee,omitempty"` StartAt *time.Time `json:"startAt,omitempty"` } diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index f036b90b..1342b3a0 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -23,6 +23,7 @@ type BaseView struct { Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` GenderCondition string `json:"genderCondition"` + Fee int `json:"fee"` StartAt *time.Time `json:"startAt,omitempty"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` From 16c2ad37d3907f40c7c65c87485a862af8adeafe Mon Sep 17 00:00:00 2001 From: litsynp Date: Tue, 3 Dec 2024 21:43:57 +0900 Subject: [PATCH 08/13] chore: remove genderCondition from event --- cmd/server/handler/event_handler.go | 1 - .../domain/event/gendercondition_string.go | 25 ------------ internal/domain/event/request.go | 2 - internal/domain/event/view.go | 1 - internal/domain/event/vo.go | 39 ------------------- 5 files changed, 68 deletions(-) delete mode 100644 internal/domain/event/gendercondition_string.go diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index e0476186..27029e09 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -51,7 +51,6 @@ func generateDummyEvent() event.ShortTermView { }, Topics: []event.EventTopic{event.ETC}, MaxParticipants: &maxParticipants, - GenderCondition: "all", Fee: 10000, StartAt: &startAt, CreatedAt: now, diff --git a/internal/domain/event/gendercondition_string.go b/internal/domain/event/gendercondition_string.go deleted file mode 100644 index 7e7dfa6f..00000000 --- a/internal/domain/event/gendercondition_string.go +++ /dev/null @@ -1,25 +0,0 @@ -// Code generated by "stringer -type=GenderCondition"; DO NOT EDIT. - -package event - -import "strconv" - -func _() { - // An "invalid array index" compiler error signifies that the constant values have changed. - // Re-run the stringer command to generate them again. - var x [1]struct{} - _ = x[Male-0] - _ = x[Female-1] - _ = x[All-2] -} - -const _GenderCondition_name = "MaleFemaleAll" - -var _GenderCondition_index = [...]uint8{0, 4, 10, 13} - -func (i GenderCondition) String() string { - if i < 0 || i >= GenderCondition(len(_GenderCondition_index)-1) { - return "GenderCondition(" + strconv.FormatInt(int64(i), 10) + ")" - } - return _GenderCondition_name[_GenderCondition_index[i]:_GenderCondition_index[i+1]] -} diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index fd9a866f..1b9ae814 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -15,7 +15,6 @@ type BaseCreateRequest struct { MediaID int `json:"mediaId"` Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` - GenderCondition string `json:"genderCondition" enums:"male,female,all"` Fee *int `json:"fee,omitempty"` StartAt *time.Time `json:"startAt,omitempty"` } @@ -41,7 +40,6 @@ type BaseUpdateRequest struct { MediaID int `json:"mediaId"` Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` - GenderCondition string `json:"genderCondition" enums:"male,female,all"` Fee *int `json:"fee,omitempty"` StartAt *time.Time `json:"startAt,omitempty"` } diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 1342b3a0..3179911a 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -22,7 +22,6 @@ type BaseView struct { Media media.DetailView `json:"media"` Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` - GenderCondition string `json:"genderCondition"` Fee int `json:"fee"` StartAt *time.Time `json:"startAt,omitempty"` CreatedAt time.Time `json:"createdAt"` diff --git a/internal/domain/event/vo.go b/internal/domain/event/vo.go index 6dbae357..a8e1cc89 100644 --- a/internal/domain/event/vo.go +++ b/internal/domain/event/vo.go @@ -1,11 +1,5 @@ package event -import ( - "encoding/json" - "errors" - "strings" -) - type EventType string const ( @@ -54,36 +48,3 @@ func (e EventRecurringPeriod) IsValid() bool { } return false } - -//go:generate stringer -type=GenderCondition -type GenderCondition int - -const ( - Male GenderCondition = iota - Female - All -) - -func (g *GenderCondition) MarshalJSON() ([]byte, error) { - return json.Marshal(strings.ToLower(g.String())) -} - -func (g *GenderCondition) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - - switch strings.ToLower(s) { - case "male": - *g = Male - case "female": - *g = Female - case "all": - *g = All - default: - return errors.New("invalid GenderCondition") - } - - return nil -} From 51bd51b434c52e513eba54e8b0500bf03e8a09b4 Mon Sep 17 00:00:00 2001 From: litsynp Date: Tue, 3 Dec 2024 21:45:03 +0900 Subject: [PATCH 09/13] chore: remove authorId from event request --- internal/domain/event/request.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index 1b9ae814..40d49770 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -9,7 +9,6 @@ type CreateRequest struct { type BaseCreateRequest struct { EventType EventType `json:"type"` - AuthorID int `json:"authorId"` Name string `json:"name"` Description string `json:"description"` MediaID int `json:"mediaId"` @@ -34,7 +33,6 @@ type UpdateRequest struct { } type BaseUpdateRequest struct { - AuthorID int `json:"authorId"` Name string `json:"name"` Description string `json:"description"` MediaID int `json:"mediaId"` From 3ebf1e61c33c9f20e0a41821f6d5e727d9726e1c Mon Sep 17 00:00:00 2001 From: litsynp Date: Tue, 3 Dec 2024 21:46:59 +0900 Subject: [PATCH 10/13] fix: event query from page to prev and next --- cmd/server/handler/event_handler.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index 27029e09..2793ac19 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -66,7 +66,8 @@ func generateDummyEvent() event.ShortTermView { // @Accept json // @Produce json // @Param author_id query string false "작성자 ID" -// @Param page query int false "페이지 번호" default(1) +// @Param prev query int false "이전 페이지" +// @Param next query int false "다음 페이지" // @Param size query int false "페이지 사이즈" default(20) // @Success 200 {object} pnd.CursorPaginatedView[event.View] // @Router /events [get] From 4f63236d60bdbc62e5e8b158ed5c1c754edca264 Mon Sep 17 00:00:00 2001 From: litsynp Date: Mon, 9 Dec 2024 21:25:43 +0900 Subject: [PATCH 11/13] feat: add sql for event --- db/migrations/000022_event.down.sql | 1 + db/migrations/000022_event.up.sql | 15 + internal/infra/database/gen/breeds.sql.go | 2 +- internal/infra/database/gen/chats.sql.go | 2 +- internal/infra/database/gen/db.go | 2 +- internal/infra/database/gen/events.sql.go | 320 ++++++++++++++++++ internal/infra/database/gen/media.sql.go | 2 +- internal/infra/database/gen/models.go | 18 +- internal/infra/database/gen/pets.sql.go | 2 +- .../infra/database/gen/resource_media.sql.go | 2 +- .../infra/database/gen/sos_conditions.sql.go | 2 +- internal/infra/database/gen/sos_posts.sql.go | 2 +- internal/infra/database/gen/users.sql.go | 2 +- queries/events.sql | 135 ++++++++ 14 files changed, 497 insertions(+), 10 deletions(-) create mode 100644 db/migrations/000022_event.down.sql create mode 100644 db/migrations/000022_event.up.sql create mode 100644 internal/infra/database/gen/events.sql.go create mode 100644 queries/events.sql diff --git a/db/migrations/000022_event.down.sql b/db/migrations/000022_event.down.sql new file mode 100644 index 00000000..653c7400 --- /dev/null +++ b/db/migrations/000022_event.down.sql @@ -0,0 +1 @@ +DROP TABLE IF EXISTS events; diff --git a/db/migrations/000022_event.up.sql b/db/migrations/000022_event.up.sql new file mode 100644 index 00000000..b3e8e71d --- /dev/null +++ b/db/migrations/000022_event.up.sql @@ -0,0 +1,15 @@ +CREATE TABLE IF NOT EXISTS events ( + id UUID PRIMARY KEY, + event_type VARCHAR NOT NULL, + author_id UUID NOT NULL, + name VARCHAR NOT NULL, + description TEXT NOT NULL, + media_id UUID, + topics TEXT[] NOT NULL, + max_participants INT, + fee INT NOT NULL, + start_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP, + deleted_at TIMESTAMPTZ +); diff --git a/internal/infra/database/gen/breeds.sql.go b/internal/infra/database/gen/breeds.sql.go index de0861df..5e3a881c 100644 --- a/internal/infra/database/gen/breeds.sql.go +++ b/internal/infra/database/gen/breeds.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: breeds.sql package databasegen diff --git a/internal/infra/database/gen/chats.sql.go b/internal/infra/database/gen/chats.sql.go index fde39e74..5e72d969 100644 --- a/internal/infra/database/gen/chats.sql.go +++ b/internal/infra/database/gen/chats.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: chats.sql package databasegen diff --git a/internal/infra/database/gen/db.go b/internal/infra/database/gen/db.go index c298514d..b635c22f 100644 --- a/internal/infra/database/gen/db.go +++ b/internal/infra/database/gen/db.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package databasegen diff --git a/internal/infra/database/gen/events.sql.go b/internal/infra/database/gen/events.sql.go new file mode 100644 index 00000000..e682d8c2 --- /dev/null +++ b/internal/infra/database/gen/events.sql.go @@ -0,0 +1,320 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: events.sql + +package databasegen + +import ( + "context" + "database/sql" + "time" + + "github.com/google/uuid" + "github.com/lib/pq" +) + +const createEvent = `-- name: CreateEvent :one +INSERT INTO + events ( + id, + event_type, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at + ) +VALUES + ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + NOW(), + NOW() + ) +RETURNING + id, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at +` + +type CreateEventParams struct { + ID uuid.UUID + EventType string + AuthorID uuid.UUID + Name string + Description string + MediaID uuid.NullUUID + Topics []string + MaxParticipants sql.NullInt32 + Fee int32 + StartAt sql.NullTime +} + +type CreateEventRow struct { + ID uuid.UUID + AuthorID uuid.UUID + Name string + Description string + MediaID uuid.NullUUID + Topics []string + MaxParticipants sql.NullInt32 + Fee int32 + StartAt sql.NullTime + CreatedAt time.Time + UpdatedAt time.Time +} + +func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (CreateEventRow, error) { + row := q.db.QueryRowContext(ctx, createEvent, + arg.ID, + arg.EventType, + arg.AuthorID, + arg.Name, + arg.Description, + arg.MediaID, + pq.Array(arg.Topics), + arg.MaxParticipants, + arg.Fee, + arg.StartAt, + ) + var i CreateEventRow + err := row.Scan( + &i.ID, + &i.AuthorID, + &i.Name, + &i.Description, + &i.MediaID, + pq.Array(&i.Topics), + &i.MaxParticipants, + &i.Fee, + &i.StartAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} + +const deleteEvent = `-- name: DeleteEvent :exec +UPDATE events +SET + deleted_at = NOW() +WHERE + id = $1 +` + +func (q *Queries) DeleteEvent(ctx context.Context, id uuid.UUID) error { + _, err := q.db.ExecContext(ctx, deleteEvent, id) + return err +} + +const findEventByID = `-- name: FindEventByID :one +SELECT + ( + events.id, + events.event_type, + events.author_id, + events.name, + events.description, + events.media_id, + events.topics, + events.max_participants, + events.fee, + events.start_at, + events.created_at, + events.updated_at + ) +FROM + events +WHERE + events.id = $1 + AND ( + events.deleted_at IS NULL + OR $2::boolean = TRUE + ) +LIMIT + 1 +` + +type FindEventByIDParams struct { + ID uuid.NullUUID + IncludeDeleted bool +} + +func (q *Queries) FindEventByID(ctx context.Context, arg FindEventByIDParams) (interface{}, error) { + row := q.db.QueryRowContext(ctx, findEventByID, arg.ID, arg.IncludeDeleted) + var column_1 interface{} + err := row.Scan(&column_1) + return column_1, err +} + +const findEvents = `-- name: FindEvents :many +SELECT + ( + events.id, + events.event_type, + events.author_id, + events.name, + events.description, + events.media_id, + events.topics, + events.max_participants, + events.fee, + events.start_at, + events.created_at, + events.updated_at + ) +FROM + events + LEFT OUTER JOIN media ON events.media_id = media.id +WHERE + ( + events.deleted_at IS NULL + OR $2::boolean = TRUE + ) + AND id > $3::uuid + AND id < $4::uuid + AND events.author_id = $5 +ORDER BY + events.created_at DESC +LIMIT + $1 +` + +type FindEventsParams struct { + Limit int32 + IncludeDeleted bool + Prev uuid.NullUUID + Next uuid.NullUUID + AuthorID uuid.NullUUID +} + +func (q *Queries) FindEvents(ctx context.Context, arg FindEventsParams) ([]interface{}, error) { + rows, err := q.db.QueryContext(ctx, findEvents, + arg.Limit, + arg.IncludeDeleted, + arg.Prev, + arg.Next, + arg.AuthorID, + ) + if err != nil { + return nil, err + } + defer rows.Close() + var items []interface{} + for rows.Next() { + var column_1 interface{} + if err := rows.Scan(&column_1); err != nil { + return nil, err + } + items = append(items, column_1) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + +const updateEvent = `-- name: UpdateEvent :one +UPDATE events +SET + name = $1, + description = $2, + media_id = $3, + topics = $4, + max_participants = $5, + fee = $6, + start_at = $7, + updated_at = NOW() +WHERE + id = $8 +RETURNING + id, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at +` + +type UpdateEventParams struct { + Name string + Description string + MediaID uuid.NullUUID + Topics []string + MaxParticipants sql.NullInt32 + Fee int32 + StartAt sql.NullTime + ID uuid.UUID +} + +type UpdateEventRow struct { + ID uuid.UUID + AuthorID uuid.UUID + Name string + Description string + MediaID uuid.NullUUID + Topics []string + MaxParticipants sql.NullInt32 + Fee int32 + StartAt sql.NullTime + CreatedAt time.Time + UpdatedAt time.Time +} + +func (q *Queries) UpdateEvent(ctx context.Context, arg UpdateEventParams) (UpdateEventRow, error) { + row := q.db.QueryRowContext(ctx, updateEvent, + arg.Name, + arg.Description, + arg.MediaID, + pq.Array(arg.Topics), + arg.MaxParticipants, + arg.Fee, + arg.StartAt, + arg.ID, + ) + var i UpdateEventRow + err := row.Scan( + &i.ID, + &i.AuthorID, + &i.Name, + &i.Description, + &i.MediaID, + pq.Array(&i.Topics), + &i.MaxParticipants, + &i.Fee, + &i.StartAt, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/internal/infra/database/gen/media.sql.go b/internal/infra/database/gen/media.sql.go index 2e3e620e..f7640e90 100644 --- a/internal/infra/database/gen/media.sql.go +++ b/internal/infra/database/gen/media.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: media.sql package databasegen diff --git a/internal/infra/database/gen/models.go b/internal/infra/database/gen/models.go index a922a213..15081448 100644 --- a/internal/infra/database/gen/models.go +++ b/internal/infra/database/gen/models.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 package databasegen @@ -41,6 +41,22 @@ type ChatRoom struct { ID uuid.UUID } +type Event struct { + ID uuid.UUID + EventType string + AuthorID uuid.UUID + Name string + Description string + MediaID uuid.NullUUID + Topics []string + MaxParticipants sql.NullInt32 + Fee int32 + StartAt sql.NullTime + CreatedAt time.Time + UpdatedAt time.Time + DeletedAt sql.NullTime +} + type Medium struct { MediaType string Url string diff --git a/internal/infra/database/gen/pets.sql.go b/internal/infra/database/gen/pets.sql.go index e332e4e4..20344add 100644 --- a/internal/infra/database/gen/pets.sql.go +++ b/internal/infra/database/gen/pets.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: pets.sql package databasegen diff --git a/internal/infra/database/gen/resource_media.sql.go b/internal/infra/database/gen/resource_media.sql.go index 920df515..1792c230 100644 --- a/internal/infra/database/gen/resource_media.sql.go +++ b/internal/infra/database/gen/resource_media.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: resource_media.sql package databasegen diff --git a/internal/infra/database/gen/sos_conditions.sql.go b/internal/infra/database/gen/sos_conditions.sql.go index 2b16ca1a..3c4c048a 100644 --- a/internal/infra/database/gen/sos_conditions.sql.go +++ b/internal/infra/database/gen/sos_conditions.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: sos_conditions.sql package databasegen diff --git a/internal/infra/database/gen/sos_posts.sql.go b/internal/infra/database/gen/sos_posts.sql.go index 44d0e145..3361e0aa 100644 --- a/internal/infra/database/gen/sos_posts.sql.go +++ b/internal/infra/database/gen/sos_posts.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: sos_posts.sql package databasegen diff --git a/internal/infra/database/gen/users.sql.go b/internal/infra/database/gen/users.sql.go index c9dcc3a9..56ce8f6a 100644 --- a/internal/infra/database/gen/users.sql.go +++ b/internal/infra/database/gen/users.sql.go @@ -1,6 +1,6 @@ // Code generated by sqlc. DO NOT EDIT. // versions: -// sqlc v1.26.0 +// sqlc v1.27.0 // source: users.sql package databasegen diff --git a/queries/events.sql b/queries/events.sql new file mode 100644 index 00000000..cabd7dd2 --- /dev/null +++ b/queries/events.sql @@ -0,0 +1,135 @@ +-- name: CreateEvent :one +INSERT INTO + events ( + id, + event_type, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at + ) +VALUES + ( + $1, + $2, + $3, + $4, + $5, + $6, + $7, + $8, + $9, + $10, + NOW(), + NOW() + ) +RETURNING + id, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at; + +-- name: FindEvents :many +SELECT + ( + events.id, + events.event_type, + events.author_id, + events.name, + events.description, + events.media_id, + events.topics, + events.max_participants, + events.fee, + events.start_at, + events.created_at, + events.updated_at + ) +FROM + events + LEFT OUTER JOIN media ON events.media_id = media.id +WHERE + ( + events.deleted_at IS NULL + OR sqlc.arg ('include_deleted')::boolean = TRUE + ) + AND id > sqlc.narg ('prev')::uuid + AND id < sqlc.narg ('next')::uuid + AND events.author_id = sqlc.narg ('author_id') +ORDER BY + events.created_at DESC +LIMIT + $1; + +-- name: FindEventByID :one +SELECT + ( + events.id, + events.event_type, + events.author_id, + events.name, + events.description, + events.media_id, + events.topics, + events.max_participants, + events.fee, + events.start_at, + events.created_at, + events.updated_at + ) +FROM + events +WHERE + events.id = sqlc.narg ('id') + AND ( + events.deleted_at IS NULL + OR sqlc.arg ('include_deleted')::boolean = TRUE + ) +LIMIT + 1; + +-- name: UpdateEvent :one +UPDATE events +SET + name = $1, + description = $2, + media_id = $3, + topics = $4, + max_participants = $5, + fee = $6, + start_at = $7, + updated_at = NOW() +WHERE + id = $8 +RETURNING + id, + author_id, + name, + description, + media_id, + topics, + max_participants, + fee, + start_at, + created_at, + updated_at; + +-- name: DeleteEvent :exec +UPDATE events +SET + deleted_at = NOW() +WHERE + id = $1; From b3c1182bc29bfb26268934fe4edfe0028e0109c8 Mon Sep 17 00:00:00 2001 From: litsynp Date: Mon, 23 Dec 2024 17:42:23 +0900 Subject: [PATCH 12/13] feat: DB conn for create/find event --- .golangci.yml | 30 ++-- cmd/server/handler/event_handler.go | 144 +++++++++------ cmd/server/router.go | 3 +- internal/common/null.go | 29 ++++ internal/domain/event/model.go | 25 +++ internal/domain/event/request.go | 24 +-- internal/domain/event/vo.go | 8 + internal/domain/user/view.go | 15 ++ internal/infra/database/database.go | 1 + internal/infra/database/gen/events.sql.go | 162 +++++++---------- internal/infra/database/gen/users.sql.go | 49 ++++++ internal/service/event_service.go | 166 ++++++++++++++++++ internal/service/sos_post_service.go | 7 - internal/service/tests/event_service_test.go | 173 +++++++++++++++++++ internal/service/user_service.go | 11 ++ internal/tests/service.go | 4 + queries/events.sql | 67 ++----- queries/users.sql | 13 ++ 18 files changed, 687 insertions(+), 244 deletions(-) create mode 100644 internal/domain/event/model.go create mode 100644 internal/service/event_service.go create mode 100644 internal/service/tests/event_service_test.go mode change 100644 => 100755 queries/events.sql diff --git a/.golangci.yml b/.golangci.yml index 274175e0..985dbb21 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -40,19 +40,19 @@ linters-settings: - switch - map - funlen: - # Checks the number of lines in a function. - # If lower than 0, disable the check. - # Default: 60 - lines: 150 - # Checks the number of statements in a function. - # If lower than 0, disable the check. - # Default: 40 - statements: 100 - - # Ignore comments when counting lines. - # Default false - ignore-comments: true + # funlen: + # # Checks the number of lines in a function. + # # If lower than 0, disable the check. + # # Default: 60 + # lines: 150 + # # Checks the number of statements in a function. + # # If lower than 0, disable the check. + # # Default: 40 + # statements: 100 + # + # # Ignore comments when counting lines. + # # Default false + # ignore-comments: true gocognit: # Minimal code complexity to report @@ -470,7 +470,7 @@ linters: - exhaustive # checks exhaustiveness of enum switch statements - exportloopref # checks for pointers to enclosing loop variables - forbidigo # forbids identifiers - - funlen # tool for detection of long functions + # - funlen # tool for detection of long functions - gocheckcompilerdirectives - gochecknoinits # checks that no init functions are present in Go code - gocognit # computes and checks the cognitive complexity of functions @@ -585,7 +585,7 @@ issues: - govet - bodyclose - dupl - - funlen + # - funlen - goconst - gosec - noctx diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index 2793ac19..4f282306 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -3,59 +3,28 @@ package handler import ( "log" "net/http" - "time" "github.com/google/uuid" "github.com/labstack/echo/v4" pnd "github.com/pet-sitter/pets-next-door-api/api" + utils "github.com/pet-sitter/pets-next-door-api/internal/common" "github.com/pet-sitter/pets-next-door-api/internal/domain/event" - "github.com/pet-sitter/pets-next-door-api/internal/domain/media" - "github.com/pet-sitter/pets-next-door-api/internal/domain/user" + databasegen "github.com/pet-sitter/pets-next-door-api/internal/infra/database/gen" "github.com/pet-sitter/pets-next-door-api/internal/service" ) type EventHandler struct { - authService service.AuthService + authService service.AuthService + eventService service.EventService } -func NewEventHandler(authService service.AuthService) *EventHandler { +func NewEventHandler( + authService service.AuthService, + eventService service.EventService, +) *EventHandler { return &EventHandler{ - authService: authService, - } -} - -func generateDummyEvent() event.ShortTermView { - profileImageURL := "https://cdn.pixabay.com/photo/2015/10/05/22/37/blank-profile-picture-973460_1280.png" - now := time.Now() - startAt := now.AddDate(0, 0, 1) - maxParticipants := 3 - return event.ShortTermView{ - BaseView: event.BaseView{ - ID: uuid.New(), - EventType: event.ShortTerm, - Author: user.WithoutPrivateInfo{ - ID: uuid.New(), - Nickname: "멍냥이", - ProfileImageURL: &profileImageURL, - }, - Name: "name", - Description: "description", - Media: media.DetailView{ - ID: uuid.New(), - MediaType: media.TypeImage, - URL: "https://images.unsplash.com/" + - "photo-1493225457124-a3eb161ffa5f?q=80&w=2970&auto=format" + - "&fit=crop&ixlib=rb-4.0.3&ixid=" + - "M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D", - CreatedAt: now, - }, - Topics: []event.EventTopic{event.ETC}, - MaxParticipants: &maxParticipants, - Fee: 10000, - StartAt: &startAt, - CreatedAt: now, - UpdatedAt: now, - }, + authService: authService, + eventService: eventService, } } @@ -72,10 +41,55 @@ func generateDummyEvent() event.ShortTermView { // @Success 200 {object} pnd.CursorPaginatedView[event.View] // @Router /events [get] func (h *EventHandler) FindEvents(c echo.Context) error { + prev, next, size, err := pnd.ParseCursorPaginationQueries(c, 20) + if err != nil { + return err + } + authorID, err := pnd.ParseOptionalUUIDQuery(c, "author_id") + if err != nil { + return err + } + + ctx := c.Request().Context() + events, err := h.eventService.FindEvents(ctx, databasegen.FindEventsParams{ + AuthorID: authorID, + Prev: prev, + Next: next, + Limit: int32(size), + }) + if err != nil { + return err + } + + items := make([]event.ShortTermView, len(events)) + for i, e := range events { + // Turn topics (string[]) to EventTopic[] + topics := make([]event.EventTopic, len(e.Event.Topics)) + for i, topic := range e.Event.Topics { + topics[i] = event.EventTopic(topic) + } + + items[i] = event.ShortTermView{ + BaseView: event.BaseView{ + ID: e.Event.ID, + EventType: event.EventType(e.Event.EventType), + Author: e.Author, + Name: e.Event.Name, + Description: e.Event.Description, + Media: *e.Media, + Topics: topics, + MaxParticipants: utils.NullInt32ToIntPtr(e.Event.MaxParticipants), + Fee: int(e.Event.Fee), + StartAt: utils.NullTimeToTimePtr(e.Event.StartAt), + CreatedAt: e.Event.CreatedAt, + UpdatedAt: e.Event.UpdatedAt, + }, + } + } return c.JSON( http.StatusOK, pnd.CursorPaginatedView[event.ShortTermView]{ - Items: []event.ShortTermView{generateDummyEvent()}, + Items: items, }, ) } @@ -94,9 +108,37 @@ func (h *EventHandler) FindEventByID(c echo.Context) error { return err } - res := generateDummyEvent() - res.ID = id - return c.JSON(http.StatusOK, res) + ctx := c.Request().Context() + eventData, err := h.eventService.FindEvent( + ctx, + databasegen.FindEventParams{ID: uuid.NullUUID{UUID: id, Valid: true}}, + ) + if err != nil { + return err + } + + topics := make([]event.EventTopic, len(eventData.Event.Topics)) + for i, topic := range eventData.Event.Topics { + topics[i] = event.EventTopic(topic) + } + view := event.ShortTermView{ + BaseView: event.BaseView{ + ID: eventData.Event.ID, + Author: eventData.Author, + EventType: event.EventType(eventData.Event.EventType), + Name: eventData.Event.Name, + Description: eventData.Event.Description, + Media: *eventData.Media, + Topics: topics, + MaxParticipants: utils.NullInt32ToIntPtr(eventData.Event.MaxParticipants), + Fee: int(eventData.Event.Fee), + StartAt: utils.NullTimeToTimePtr(eventData.Event.StartAt), + CreatedAt: eventData.Event.CreatedAt, + UpdatedAt: eventData.Event.UpdatedAt, + }, + } + + return c.JSON(http.StatusOK, view) } // CreateEvent godoc @@ -117,18 +159,18 @@ func (h *EventHandler) CreateEvent(c echo.Context) error { if err != nil { return err } - uid := foundUser.FirebaseUID + authorID := foundUser.ID var reqBody event.CreateRequest if err := pnd.ParseBody(c, &reqBody); err != nil { return err } - log.Printf("uid: %s, reqBody: %+v", uid, reqBody) - // TODO: Implement create event logic - - created := generateDummyEvent() - created.ID = uuid.New() + ctx := c.Request().Context() + created, err := h.eventService.CreateEvent(ctx, authorID, reqBody) + if err != nil { + return err + } return c.JSON(http.StatusCreated, created) } diff --git a/cmd/server/router.go b/cmd/server/router.go index a67e84f3..a17eed20 100644 --- a/cmd/server/router.go +++ b/cmd/server/router.go @@ -58,6 +58,7 @@ func NewRouter(app *firebaseinfra.FirebaseApp) (*echo.Echo, error) { breedService := service.NewBreedService(db) sosPostService := service.NewSOSPostService(db) conditionService := service.NewSOSConditionService(db) + eventService := service.NewEventService(db, userService, mediaService) chatService := service.NewChatService(db) // Initialize handlers @@ -68,7 +69,7 @@ func NewRouter(app *firebaseinfra.FirebaseApp) (*echo.Echo, error) { sosPostHandler := handler.NewSOSPostHandler(*sosPostService, authService) conditionHandler := handler.NewConditionHandler(*conditionService) chatHandler := handler.NewChatHandler(authService, *chatService) - eventHandler := handler.NewEventHandler(authService) + eventHandler := handler.NewEventHandler(authService, *eventService) // // InMemoryStateManager는 클라이언트와 채팅방의 상태를 메모리에 저장하고 관리합니다. // // 이 메서드는 단순하고 빠르며 테스트 목적으로 적합합니다. diff --git a/internal/common/null.go b/internal/common/null.go index a44c17dd..1fd41883 100644 --- a/internal/common/null.go +++ b/internal/common/null.go @@ -42,6 +42,21 @@ func StrToNullStr(val string) sql.NullString { } } +func NullInt32ToIntPtr(val sql.NullInt32) *int { + if val.Valid { + intValue := int(val.Int32) + return &intValue + } + return nil +} + +func NullInt32ToInt32Ptr(val sql.NullInt32) *int32 { + if val.Valid { + return &val.Int32 + } + return nil +} + func NullInt64ToInt64Ptr(val sql.NullInt64) *int64 { if val.Valid { return &val.Int64 @@ -118,3 +133,17 @@ func NullTimeToStr(val sql.NullTime) string { } return "" } + +func NullTimeToTimePtr(val sql.NullTime) *time.Time { + if val.Valid { + return &val.Time + } + return nil +} + +func TimePtrToNullTime(val *time.Time) sql.NullTime { + return sql.NullTime{ + Time: DerefOrEmpty(val), + Valid: IsNotNil(val), + } +} diff --git a/internal/domain/event/model.go b/internal/domain/event/model.go new file mode 100644 index 00000000..f41f8067 --- /dev/null +++ b/internal/domain/event/model.go @@ -0,0 +1,25 @@ +package event + +import ( + "github.com/pet-sitter/pets-next-door-api/internal/domain/media" + "github.com/pet-sitter/pets-next-door-api/internal/domain/user" + databasegen "github.com/pet-sitter/pets-next-door-api/internal/infra/database/gen" +) + +type Event struct { + Event databasegen.Event + Author user.WithoutPrivateInfo + Media *media.DetailView +} + +func ToDomainFromFind( + eventData databasegen.Event, + authorData user.WithoutPrivateInfo, + mediaData *media.DetailView, +) *Event { + return &Event{ + Event: eventData, + Author: authorData, + Media: mediaData, + } +} diff --git a/internal/domain/event/request.go b/internal/domain/event/request.go index 40d49770..db513fb5 100644 --- a/internal/domain/event/request.go +++ b/internal/domain/event/request.go @@ -1,6 +1,10 @@ package event -import "time" +import ( + "time" + + "github.com/google/uuid" +) type CreateRequest struct { BaseCreateRequest @@ -11,10 +15,10 @@ type BaseCreateRequest struct { EventType EventType `json:"type"` Name string `json:"name"` Description string `json:"description"` - MediaID int `json:"mediaId"` + MediaID *uuid.UUID `json:"mediaId,omitempty"` Topics []EventTopic `json:"topics"` MaxParticipants *int `json:"maxParticipants,omitempty"` - Fee *int `json:"fee,omitempty"` + Fee int `json:"fee"` StartAt *time.Time `json:"startAt,omitempty"` } @@ -33,13 +37,13 @@ type UpdateRequest struct { } type BaseUpdateRequest struct { - Name string `json:"name"` - Description string `json:"description"` - MediaID int `json:"mediaId"` - Topics []EventTopic `json:"topics"` - MaxParticipants *int `json:"maxParticipants,omitempty"` - Fee *int `json:"fee,omitempty"` - StartAt *time.Time `json:"startAt,omitempty"` + Name string `json:"name"` + Description string `json:"description"` + MediaID uuid.NullUUID `json:"mediaId,omitempty"` + Topics []EventTopic `json:"topics"` + MaxParticipants *int `json:"maxParticipants,omitempty"` + Fee int `json:"fee"` + StartAt *time.Time `json:"startAt,omitempty"` } type ShortTermUpdateRequest struct { diff --git a/internal/domain/event/vo.go b/internal/domain/event/vo.go index a8e1cc89..61365553 100644 --- a/internal/domain/event/vo.go +++ b/internal/domain/event/vo.go @@ -9,6 +9,10 @@ const ( Recurring EventType = "RECURRING" ) +func (e EventType) String() string { + return string(e) +} + func (e EventType) IsValid() bool { switch e { case ShortTerm, Recurring: @@ -23,6 +27,10 @@ const ( ETC EventTopic = "ETC" ) +func (e EventTopic) String() string { + return string(e) +} + func (e EventTopic) IsValid() bool { // TODO: Add more topics return e == ETC diff --git a/internal/domain/user/view.go b/internal/domain/user/view.go index a6ef3e97..9ee72120 100644 --- a/internal/domain/user/view.go +++ b/internal/domain/user/view.go @@ -117,3 +117,18 @@ func ToListWithoutPrivateInfo( ul.CalcLastPage() return ul } + +func ToListWithoutPrivateInfoFromFindByIDs( + rows []databasegen.FindUsersByIDsRow, +) []WithoutPrivateInfo { + var items []WithoutPrivateInfo + for _, row := range rows { + items = append(items, WithoutPrivateInfo{ + ID: row.ID, + Nickname: row.Nickname, + ProfileImageURL: utils.NullStrToStrPtr(row.ProfileImageUrl), + }) + } + + return items +} diff --git a/internal/infra/database/database.go b/internal/infra/database/database.go index 0f895760..3166934a 100644 --- a/internal/infra/database/database.go +++ b/internal/infra/database/database.go @@ -44,6 +44,7 @@ func (db *DB) Flush() error { tableNames := []string{ "users", "breeds", + "events", "resource_media", "sos_posts_pets", "media", diff --git a/internal/infra/database/gen/events.sql.go b/internal/infra/database/gen/events.sql.go index e682d8c2..19ee9b49 100644 --- a/internal/infra/database/gen/events.sql.go +++ b/internal/infra/database/gen/events.sql.go @@ -8,7 +8,6 @@ package databasegen import ( "context" "database/sql" - "time" "github.com/google/uuid" "github.com/lib/pq" @@ -46,17 +45,7 @@ VALUES NOW() ) RETURNING - id, - author_id, - name, - description, - media_id, - topics, - max_participants, - fee, - start_at, - created_at, - updated_at + id, event_type, author_id, name, description, media_id, topics, max_participants, fee, start_at, created_at, updated_at, deleted_at ` type CreateEventParams struct { @@ -72,21 +61,7 @@ type CreateEventParams struct { StartAt sql.NullTime } -type CreateEventRow struct { - ID uuid.UUID - AuthorID uuid.UUID - Name string - Description string - MediaID uuid.NullUUID - Topics []string - MaxParticipants sql.NullInt32 - Fee int32 - StartAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time -} - -func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (CreateEventRow, error) { +func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Event, error) { row := q.db.QueryRowContext(ctx, createEvent, arg.ID, arg.EventType, @@ -99,9 +74,10 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Creat arg.Fee, arg.StartAt, ) - var i CreateEventRow + var i Event err := row.Scan( &i.ID, + &i.EventType, &i.AuthorID, &i.Name, &i.Description, @@ -112,6 +88,7 @@ func (q *Queries) CreateEvent(ctx context.Context, arg CreateEventParams) (Creat &i.StartAt, &i.CreatedAt, &i.UpdatedAt, + &i.DeletedAt, ) return i, err } @@ -129,73 +106,60 @@ func (q *Queries) DeleteEvent(ctx context.Context, id uuid.UUID) error { return err } -const findEventByID = `-- name: FindEventByID :one +const findEvent = `-- name: FindEvent :one SELECT - ( - events.id, - events.event_type, - events.author_id, - events.name, - events.description, - events.media_id, - events.topics, - events.max_participants, - events.fee, - events.start_at, - events.created_at, - events.updated_at - ) + events.id, events.event_type, events.author_id, events.name, events.description, events.media_id, events.topics, events.max_participants, events.fee, events.start_at, events.created_at, events.updated_at, events.deleted_at FROM events WHERE - events.id = $1 - AND ( + ( events.deleted_at IS NULL - OR $2::boolean = TRUE + OR $1::boolean = TRUE ) + AND (events.id = $2 OR $2 IS NULL) LIMIT 1 ` -type FindEventByIDParams struct { - ID uuid.NullUUID +type FindEventParams struct { IncludeDeleted bool + ID uuid.NullUUID } -func (q *Queries) FindEventByID(ctx context.Context, arg FindEventByIDParams) (interface{}, error) { - row := q.db.QueryRowContext(ctx, findEventByID, arg.ID, arg.IncludeDeleted) - var column_1 interface{} - err := row.Scan(&column_1) - return column_1, err +func (q *Queries) FindEvent(ctx context.Context, arg FindEventParams) (Event, error) { + row := q.db.QueryRowContext(ctx, findEvent, arg.IncludeDeleted, arg.ID) + var i Event + err := row.Scan( + &i.ID, + &i.EventType, + &i.AuthorID, + &i.Name, + &i.Description, + &i.MediaID, + pq.Array(&i.Topics), + &i.MaxParticipants, + &i.Fee, + &i.StartAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + ) + return i, err } const findEvents = `-- name: FindEvents :many SELECT - ( - events.id, - events.event_type, - events.author_id, - events.name, - events.description, - events.media_id, - events.topics, - events.max_participants, - events.fee, - events.start_at, - events.created_at, - events.updated_at - ) + events.id, events.event_type, events.author_id, events.name, events.description, events.media_id, events.topics, events.max_participants, events.fee, events.start_at, events.created_at, events.updated_at, events.deleted_at FROM events - LEFT OUTER JOIN media ON events.media_id = media.id WHERE ( events.deleted_at IS NULL OR $2::boolean = TRUE ) - AND id > $3::uuid - AND id < $4::uuid - AND events.author_id = $5 + AND (id > $3::uuid OR $3 IS NULL) + AND (id < $4::uuid OR $4 IS NULL) + AND (events.author_id = $5 OR $5 IS NULL) ORDER BY events.created_at DESC LIMIT @@ -210,7 +174,7 @@ type FindEventsParams struct { AuthorID uuid.NullUUID } -func (q *Queries) FindEvents(ctx context.Context, arg FindEventsParams) ([]interface{}, error) { +func (q *Queries) FindEvents(ctx context.Context, arg FindEventsParams) ([]Event, error) { rows, err := q.db.QueryContext(ctx, findEvents, arg.Limit, arg.IncludeDeleted, @@ -222,13 +186,27 @@ func (q *Queries) FindEvents(ctx context.Context, arg FindEventsParams) ([]inter return nil, err } defer rows.Close() - var items []interface{} + var items []Event for rows.Next() { - var column_1 interface{} - if err := rows.Scan(&column_1); err != nil { + var i Event + if err := rows.Scan( + &i.ID, + &i.EventType, + &i.AuthorID, + &i.Name, + &i.Description, + &i.MediaID, + pq.Array(&i.Topics), + &i.MaxParticipants, + &i.Fee, + &i.StartAt, + &i.CreatedAt, + &i.UpdatedAt, + &i.DeletedAt, + ); err != nil { return nil, err } - items = append(items, column_1) + items = append(items, i) } if err := rows.Close(); err != nil { return nil, err @@ -253,17 +231,7 @@ SET WHERE id = $8 RETURNING - id, - author_id, - name, - description, - media_id, - topics, - max_participants, - fee, - start_at, - created_at, - updated_at + id, event_type, author_id, name, description, media_id, topics, max_participants, fee, start_at, created_at, updated_at, deleted_at ` type UpdateEventParams struct { @@ -277,21 +245,7 @@ type UpdateEventParams struct { ID uuid.UUID } -type UpdateEventRow struct { - ID uuid.UUID - AuthorID uuid.UUID - Name string - Description string - MediaID uuid.NullUUID - Topics []string - MaxParticipants sql.NullInt32 - Fee int32 - StartAt sql.NullTime - CreatedAt time.Time - UpdatedAt time.Time -} - -func (q *Queries) UpdateEvent(ctx context.Context, arg UpdateEventParams) (UpdateEventRow, error) { +func (q *Queries) UpdateEvent(ctx context.Context, arg UpdateEventParams) (Event, error) { row := q.db.QueryRowContext(ctx, updateEvent, arg.Name, arg.Description, @@ -302,9 +256,10 @@ func (q *Queries) UpdateEvent(ctx context.Context, arg UpdateEventParams) (Updat arg.StartAt, arg.ID, ) - var i UpdateEventRow + var i Event err := row.Scan( &i.ID, + &i.EventType, &i.AuthorID, &i.Name, &i.Description, @@ -315,6 +270,7 @@ func (q *Queries) UpdateEvent(ctx context.Context, arg UpdateEventParams) (Updat &i.StartAt, &i.CreatedAt, &i.UpdatedAt, + &i.DeletedAt, ) return i, err } diff --git a/internal/infra/database/gen/users.sql.go b/internal/infra/database/gen/users.sql.go index 56ce8f6a..25694913 100644 --- a/internal/infra/database/gen/users.sql.go +++ b/internal/infra/database/gen/users.sql.go @@ -11,6 +11,7 @@ import ( "time" "github.com/google/uuid" + "github.com/lib/pq" ) const createUser = `-- name: CreateUser :one @@ -243,6 +244,54 @@ func (q *Queries) FindUsers(ctx context.Context, arg FindUsersParams) ([]FindUse return items, nil } +const findUsersByIDs = `-- name: FindUsersByIDs :many +SELECT users.id, + users.nickname, + media.url AS profile_image_url +FROM users + LEFT OUTER JOIN + media + ON + users.profile_image_id = media.id +WHERE (users.id = ANY ($1::uuid[])) + AND (users.deleted_at IS NULL OR $2::boolean = TRUE) +ORDER BY users.created_at DESC +` + +type FindUsersByIDsParams struct { + Ids []uuid.UUID + IncludeDeleted bool +} + +type FindUsersByIDsRow struct { + ID uuid.UUID + Nickname string + ProfileImageUrl sql.NullString +} + +func (q *Queries) FindUsersByIDs(ctx context.Context, arg FindUsersByIDsParams) ([]FindUsersByIDsRow, error) { + rows, err := q.db.QueryContext(ctx, findUsersByIDs, pq.Array(arg.Ids), arg.IncludeDeleted) + if err != nil { + return nil, err + } + defer rows.Close() + var items []FindUsersByIDsRow + for rows.Next() { + var i FindUsersByIDsRow + if err := rows.Scan(&i.ID, &i.Nickname, &i.ProfileImageUrl); err != nil { + return nil, err + } + items = append(items, i) + } + if err := rows.Close(); err != nil { + return nil, err + } + if err := rows.Err(); err != nil { + return nil, err + } + return items, nil +} + const updateUserByFbUID = `-- name: UpdateUserByFbUID :one UPDATE users diff --git a/internal/service/event_service.go b/internal/service/event_service.go new file mode 100644 index 00000000..c6468b5b --- /dev/null +++ b/internal/service/event_service.go @@ -0,0 +1,166 @@ +package service + +import ( + "context" + "errors" + + "github.com/google/uuid" + pnd "github.com/pet-sitter/pets-next-door-api/api" + utils "github.com/pet-sitter/pets-next-door-api/internal/common" + "github.com/pet-sitter/pets-next-door-api/internal/domain/event" + "github.com/pet-sitter/pets-next-door-api/internal/domain/media" + "github.com/pet-sitter/pets-next-door-api/internal/domain/user" + "github.com/pet-sitter/pets-next-door-api/internal/infra/database" + databasegen "github.com/pet-sitter/pets-next-door-api/internal/infra/database/gen" +) + +type EventService struct { + conn *database.DB + mediaService *MediaService + userService *UserService +} + +func NewEventService( + conn *database.DB, + userService *UserService, + mediaService *MediaService, +) *EventService { + return &EventService{ + conn: conn, + userService: userService, + mediaService: mediaService, + } +} + +func (s *EventService) CreateEvent( + ctx context.Context, + authorID uuid.UUID, + req event.CreateRequest, +) (*databasegen.Event, error) { + id, err := uuid.NewV7() + if err != nil { + return nil, pnd.ErrUnknown(errors.New("failed to generate id")) + } + + q := databasegen.New(s.conn) + + // Check if media exists + if req.MediaID != nil { + if _, err = s.mediaService.FindMediaByID(ctx, *req.MediaID); err != nil { + return nil, err + } + } + mediaID := uuid.NullUUID{} + if req.MediaID != nil { + mediaID = uuid.NullUUID{UUID: *req.MediaID, Valid: true} + } + + // Map topic[] to string[] + topics := make([]string, len(req.Topics)) + for i, topic := range req.Topics { + topics[i] = topic.String() + } + + eventData, err := q.CreateEvent(ctx, databasegen.CreateEventParams{ + ID: id, + AuthorID: authorID, + EventType: req.EventType.String(), + Name: req.Name, + Description: req.Description, + MediaID: mediaID, + Topics: topics, + MaxParticipants: utils.IntPtrToNullInt32(req.MaxParticipants), + Fee: int32(req.Fee), + StartAt: utils.TimePtrToNullTime(req.StartAt), + }) + if err != nil { + return nil, err + } + + return &eventData, nil +} + +func (s *EventService) FindEvent( + ctx context.Context, + params databasegen.FindEventParams, +) (*event.Event, error) { + q := databasegen.New(s.conn) + + eventData, err := q.FindEvent(ctx, params) + if err != nil { + return nil, err + } + + authorData, err := s.userService.FindUserProfile( + ctx, + user.FindUserParams{ID: uuid.NullUUID{UUID: eventData.AuthorID, Valid: true}}, + ) + if err != nil { + return nil, err + } + authorView := user.WithoutPrivateInfo{ + ID: authorData.ID, + Nickname: authorData.Nickname, + ProfileImageURL: authorData.ProfileImageURL, + } + + var mediaData *media.DetailView + if eventData.MediaID.Valid { + mediaData, err = s.mediaService.FindMediaByID(ctx, eventData.MediaID.UUID) + if err != nil { + return nil, err + } + } + + return event.ToDomainFromFind(eventData, authorView, mediaData), nil +} + +func (s *EventService) FindEvents( + ctx context.Context, + params databasegen.FindEventsParams, +) ([]*event.Event, error) { + q := databasegen.New(s.conn) + + eventsData, err := q.FindEvents(ctx, params) + if err != nil { + return nil, err + } + + authorIDs := make([]uuid.UUID, len(eventsData)) + for i, eventData := range eventsData { + authorIDs[i] = eventData.AuthorID + } + authorsData, err := s.userService.FindUsersByIDs(ctx, databasegen.FindUsersByIDsParams{ + Ids: authorIDs, + }) + if err != nil { + return nil, err + } + authorIDsMap := make(map[uuid.UUID]user.WithoutPrivateInfo) + for _, authorData := range authorsData { + authorIDsMap[authorData.ID] = authorData + } + + mediaIDs := make([]uuid.UUID, len(eventsData)) + for i, eventData := range eventsData { + if eventData.MediaID.Valid { + mediaIDs[i] = eventData.MediaID.UUID + } + } + mediasData, err := s.mediaService.FindMediasByIDs(ctx, mediaIDs) + if err != nil { + return nil, err + } + mediasDataMap := make(map[uuid.UUID]media.DetailView) + for _, mediaData := range mediasData { + mediasDataMap[mediaData.ID] = mediaData + } + + events := make([]*event.Event, len(eventsData)) + for i, eventData := range eventsData { + authorData := authorIDsMap[eventData.AuthorID] + mediaData := mediasDataMap[eventData.MediaID.UUID] + events[i] = event.ToDomainFromFind(eventData, authorData, &mediaData) + } + return events, nil +} diff --git a/internal/service/sos_post_service.go b/internal/service/sos_post_service.go index 9a64b052..abfa7646 100644 --- a/internal/service/sos_post_service.go +++ b/internal/service/sos_post_service.go @@ -2,7 +2,6 @@ package service import ( "context" - "log" "time" "github.com/google/uuid" @@ -464,8 +463,6 @@ func (service *SOSPostService) SaveLinkSOSPostImage( ctx context.Context, tx *databasegen.Queries, imageIDs []uuid.UUID, sosPostID uuid.UUID, ) error { for _, mediaID := range imageIDs { - log.Default().Println("mediaID", mediaID) - if err := tx.LinkResourceMedia(ctx, databasegen.LinkResourceMediaParams{ ID: datatype.NewUUIDV7(), MediaID: mediaID, @@ -482,8 +479,6 @@ func (service *SOSPostService) SaveLinkConditions( ctx context.Context, tx *databasegen.Queries, conditionIDs []uuid.UUID, sosPostID uuid.UUID, ) error { for _, conditionID := range conditionIDs { - log.Default().Println("conditionID", conditionID) - if err := tx.LinkSOSPostCondition(ctx, databasegen.LinkSOSPostConditionParams{ ID: datatype.NewUUIDV7(), SosPostID: sosPostID, @@ -499,8 +494,6 @@ func (service *SOSPostService) SaveLinkPets( ctx context.Context, tx *databasegen.Queries, petIDs []uuid.UUID, sosPostID uuid.UUID, ) error { for _, petID := range petIDs { - log.Default().Println("petID", petID) - if err := tx.LinkSOSPostPet(ctx, databasegen.LinkSOSPostPetParams{ ID: datatype.NewUUIDV7(), SosPostID: sosPostID, diff --git a/internal/service/tests/event_service_test.go b/internal/service/tests/event_service_test.go new file mode 100644 index 00000000..501e29e9 --- /dev/null +++ b/internal/service/tests/event_service_test.go @@ -0,0 +1,173 @@ +package service_test + +import ( + "context" + "testing" + "time" + + "github.com/google/uuid" + "gopkg.in/go-playground/assert.v1" + + "github.com/pet-sitter/pets-next-door-api/internal/domain/event" + databasegen "github.com/pet-sitter/pets-next-door-api/internal/infra/database/gen" + + "github.com/pet-sitter/pets-next-door-api/internal/domain/media" + + "github.com/pet-sitter/pets-next-door-api/internal/tests" +) + +func TestCreateEvent(t *testing.T) { + t.Run("이벤트를 새로 생성한다", func(t *testing.T) { + db, tearDown := tests.SetUp(t) + defer tearDown(t) + ctx := context.Background() + mediaService := tests.NewMockMediaService(db) + userService := tests.NewMockUserService(db) + eventService := tests.NewMockEventService(db) + + // Given + profileImage, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "profile_image.jpg") + author, _ := userService.RegisterUser( + ctx, + tests.NewDummyRegisterUserRequest(uuid.NullUUID{UUID: profileImage.ID, Valid: true}), + ) + eventMedia, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "event_thumbnail.jpg") + + // When + now := time.Now().UTC() + maxParticipants := 10 + created, _ := eventService.CreateEvent(ctx, author.ID, event.CreateRequest{ + BaseCreateRequest: event.BaseCreateRequest{ + EventType: event.ShortTerm, + Name: "테스트 이벤트", + Description: "테스트 이벤트 설명", + Topics: []event.EventTopic{event.ETC}, + MediaID: &eventMedia.ID, + MaxParticipants: &maxParticipants, + Fee: 3000, + StartAt: &now, + }, + }) + + // Then + found, err := eventService.FindEvent( + ctx, + databasegen.FindEventParams{ID: uuid.NullUUID{UUID: created.ID, Valid: true}}, + ) + assert.Equal(t, err, nil) + assertEventEquals(t, created, found) + }) +} + +func TestFindEvents(t *testing.T) { + t.Run("이벤트 목록을 조회한다", func(t *testing.T) { + db, tearDown := tests.SetUp(t) + defer tearDown(t) + ctx := context.Background() + mediaService := tests.NewMockMediaService(db) + userService := tests.NewMockUserService(db) + eventService := tests.NewMockEventService(db) + + // Given + profileImage, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "profile_image.jpg") + owner, _ := userService.RegisterUser( + ctx, + tests.NewDummyRegisterUserRequest(uuid.NullUUID{UUID: profileImage.ID, Valid: true}), + ) + eventMedia, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "event_thumbnail.jpg") + + // When + now := time.Now().UTC() + maxParticipants := 10 + created, _ := eventService.CreateEvent(ctx, owner.ID, event.CreateRequest{ + BaseCreateRequest: event.BaseCreateRequest{ + EventType: event.ShortTerm, + Name: "테스트 이벤트", + Description: "테스트 이벤트 설명", + Topics: []event.EventTopic{event.ETC}, + MediaID: &eventMedia.ID, + MaxParticipants: &maxParticipants, + Fee: 3000, + StartAt: &now, + }, + }) + + // Then + found, err := eventService.FindEvents( + ctx, + databasegen.FindEventsParams{ + Limit: 10, + AuthorID: uuid.NullUUID{UUID: uuid.Nil, Valid: false}, + }, + ) + assert.Equal(t, err, nil) + assert.Equal(t, 1, len(found)) + assertEventEquals(t, created, found[0]) + }) + + t.Run("작성자 ID로 이벤트 목록을 조회할 수 있다", func(t *testing.T) { + db, tearDown := tests.SetUp(t) + defer tearDown(t) + ctx := context.Background() + mediaService := tests.NewMockMediaService(db) + userService := tests.NewMockUserService(db) + eventService := tests.NewMockEventService(db) + + // Given + profileImage, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "profile_image.jpg") + author, _ := userService.RegisterUser( + ctx, + tests.NewDummyRegisterUserRequest(uuid.NullUUID{UUID: profileImage.ID, Valid: true}), + ) + eventMedia, _ := mediaService.UploadMedia(ctx, nil, media.TypeImage, "event_thumbnail.jpg") + + // When + now := time.Now().UTC() + maxParticipants := 10 + created, _ := eventService.CreateEvent(ctx, author.ID, event.CreateRequest{ + BaseCreateRequest: event.BaseCreateRequest{ + EventType: event.ShortTerm, + Name: "테스트 이벤트", + Description: "테스트 이벤트 설명", + Topics: []event.EventTopic{event.ETC}, + MediaID: &eventMedia.ID, + MaxParticipants: &maxParticipants, + Fee: 3000, + StartAt: &now, + }, + }) + + // Then + found, err := eventService.FindEvents( + ctx, + databasegen.FindEventsParams{ + Limit: 10, + AuthorID: uuid.NullUUID{UUID: author.ID, Valid: true}, + }, + ) + assert.Equal(t, err, nil) + assert.Equal(t, 1, len(found)) + assertEventEquals(t, created, found[0]) + }) +} + +func assertEventEquals( + t *testing.T, + expected *databasegen.Event, + actual *event.Event, +) { + t.Helper() + assert.Equal(t, expected.ID, actual.Event.ID) + assert.Equal(t, expected.AuthorID, actual.Event.AuthorID) + assert.Equal(t, expected.EventType, actual.Event.EventType) + assert.Equal(t, expected.Name, actual.Event.Name) + assert.Equal(t, expected.Description, actual.Event.Description) + assert.Equal(t, expected.MediaID, actual.Event.MediaID) + assert.Equal(t, expected.Topics, actual.Event.Topics) + assert.Equal(t, expected.MaxParticipants, actual.Event.MaxParticipants) + assert.Equal(t, expected.Fee, actual.Event.Fee) + assert.Equal(t, expected.StartAt, actual.Event.StartAt) + assert.Equal(t, expected.CreatedAt, actual.Event.CreatedAt) + assert.Equal(t, expected.UpdatedAt, actual.Event.UpdatedAt) + assert.Equal(t, expected.DeletedAt, actual.Event.DeletedAt) +} diff --git a/internal/service/user_service.go b/internal/service/user_service.go index 618576b4..565249ec 100644 --- a/internal/service/user_service.go +++ b/internal/service/user_service.go @@ -76,6 +76,17 @@ func (service *UserService) FindUsers( return user.ToListWithoutPrivateInfo(params.Page, params.Size, rows), nil } +func (service *UserService) FindUsersByIDs( + ctx context.Context, params databasegen.FindUsersByIDsParams, +) ([]user.WithoutPrivateInfo, error) { + rows, err := databasegen.New(service.conn).FindUsersByIDs(ctx, params) + if err != nil { + return nil, err + } + + return user.ToListWithoutPrivateInfoFromFindByIDs(rows), nil +} + func (service *UserService) FindUser( ctx context.Context, params user.FindUserParams, diff --git a/internal/tests/service.go b/internal/tests/service.go index 6e9dca89..2c1ecb45 100644 --- a/internal/tests/service.go +++ b/internal/tests/service.go @@ -46,6 +46,10 @@ func NewMockSOSConditionService(db *database.DB) *service.SOSConditionService { return service.NewSOSConditionService(db) } +func NewMockEventService(db *database.DB) *service.EventService { + return service.NewEventService(db, NewMockUserService(db), NewMockMediaService(db)) +} + func NewMockChatService(db *database.DB) *service.ChatService { return service.NewChatService(db) } diff --git a/queries/events.sql b/queries/events.sql old mode 100644 new mode 100755 index cabd7dd2..02c6a704 --- a/queries/events.sql +++ b/queries/events.sql @@ -30,74 +30,37 @@ VALUES NOW() ) RETURNING - id, - author_id, - name, - description, - media_id, - topics, - max_participants, - fee, - start_at, - created_at, - updated_at; + *; -- name: FindEvents :many SELECT - ( - events.id, - events.event_type, - events.author_id, - events.name, - events.description, - events.media_id, - events.topics, - events.max_participants, - events.fee, - events.start_at, - events.created_at, - events.updated_at - ) + events.* FROM events - LEFT OUTER JOIN media ON events.media_id = media.id WHERE ( events.deleted_at IS NULL OR sqlc.arg ('include_deleted')::boolean = TRUE ) - AND id > sqlc.narg ('prev')::uuid - AND id < sqlc.narg ('next')::uuid - AND events.author_id = sqlc.narg ('author_id') + AND (id > sqlc.narg ('prev')::uuid OR sqlc.narg ('prev') IS NULL) + AND (id < sqlc.narg ('next')::uuid OR sqlc.narg ('next') IS NULL) + AND (events.author_id = sqlc.narg ('author_id') OR sqlc.narg ('author_id') IS NULL) ORDER BY events.created_at DESC LIMIT $1; --- name: FindEventByID :one +-- name: FindEvent :one SELECT - ( - events.id, - events.event_type, - events.author_id, - events.name, - events.description, - events.media_id, - events.topics, - events.max_participants, - events.fee, - events.start_at, - events.created_at, - events.updated_at - ) + events.* FROM events WHERE - events.id = sqlc.narg ('id') - AND ( + ( events.deleted_at IS NULL OR sqlc.arg ('include_deleted')::boolean = TRUE ) + AND (events.id = sqlc.narg('id') OR sqlc.narg('id') IS NULL) LIMIT 1; @@ -115,17 +78,7 @@ SET WHERE id = $8 RETURNING - id, - author_id, - name, - description, - media_id, - topics, - max_participants, - fee, - start_at, - created_at, - updated_at; + *; -- name: DeleteEvent :exec UPDATE events diff --git a/queries/users.sql b/queries/users.sql index f09cc52d..b8f57ec1 100644 --- a/queries/users.sql +++ b/queries/users.sql @@ -30,6 +30,19 @@ WHERE (users.id = sqlc.narg('id') OR sqlc.narg('id') IS NULL) ORDER BY users.created_at DESC LIMIT $1 OFFSET $2; +-- name: FindUsersByIDs :many +SELECT users.id, + users.nickname, + media.url AS profile_image_url +FROM users + LEFT OUTER JOIN + media + ON + users.profile_image_id = media.id +WHERE (users.id = ANY (sqlc.arg('ids')::uuid[])) + AND (users.deleted_at IS NULL OR sqlc.arg('include_deleted')::boolean = TRUE) +ORDER BY users.created_at DESC; + -- name: FindUser :one SELECT users.id, users.email, From 7ae5932d12436c9ffe5e4b8b2e386a659b234d0d Mon Sep 17 00:00:00 2001 From: litsynp Date: Mon, 23 Dec 2024 23:23:50 +0900 Subject: [PATCH 13/13] fix: use event domain as service DTO --- cmd/server/handler/event_handler.go | 51 ++------------------ internal/domain/event/model.go | 2 +- internal/domain/event/view.go | 26 ++++++++++ internal/service/event_service.go | 26 ++++++++-- internal/service/tests/event_service_test.go | 30 ++++++------ 5 files changed, 68 insertions(+), 67 deletions(-) diff --git a/cmd/server/handler/event_handler.go b/cmd/server/handler/event_handler.go index 4f282306..f2570817 100644 --- a/cmd/server/handler/event_handler.go +++ b/cmd/server/handler/event_handler.go @@ -7,7 +7,6 @@ import ( "github.com/google/uuid" "github.com/labstack/echo/v4" pnd "github.com/pet-sitter/pets-next-door-api/api" - utils "github.com/pet-sitter/pets-next-door-api/internal/common" "github.com/pet-sitter/pets-next-door-api/internal/domain/event" databasegen "github.com/pet-sitter/pets-next-door-api/internal/infra/database/gen" "github.com/pet-sitter/pets-next-door-api/internal/service" @@ -63,28 +62,7 @@ func (h *EventHandler) FindEvents(c echo.Context) error { items := make([]event.ShortTermView, len(events)) for i, e := range events { - // Turn topics (string[]) to EventTopic[] - topics := make([]event.EventTopic, len(e.Event.Topics)) - for i, topic := range e.Event.Topics { - topics[i] = event.EventTopic(topic) - } - - items[i] = event.ShortTermView{ - BaseView: event.BaseView{ - ID: e.Event.ID, - EventType: event.EventType(e.Event.EventType), - Author: e.Author, - Name: e.Event.Name, - Description: e.Event.Description, - Media: *e.Media, - Topics: topics, - MaxParticipants: utils.NullInt32ToIntPtr(e.Event.MaxParticipants), - Fee: int(e.Event.Fee), - StartAt: utils.NullTimeToTimePtr(e.Event.StartAt), - CreatedAt: e.Event.CreatedAt, - UpdatedAt: e.Event.UpdatedAt, - }, - } + items[i] = event.ToShortTermView(e) } return c.JSON( http.StatusOK, @@ -109,7 +87,7 @@ func (h *EventHandler) FindEventByID(c echo.Context) error { } ctx := c.Request().Context() - eventData, err := h.eventService.FindEvent( + found, err := h.eventService.FindEvent( ctx, databasegen.FindEventParams{ID: uuid.NullUUID{UUID: id, Valid: true}}, ) @@ -117,28 +95,7 @@ func (h *EventHandler) FindEventByID(c echo.Context) error { return err } - topics := make([]event.EventTopic, len(eventData.Event.Topics)) - for i, topic := range eventData.Event.Topics { - topics[i] = event.EventTopic(topic) - } - view := event.ShortTermView{ - BaseView: event.BaseView{ - ID: eventData.Event.ID, - Author: eventData.Author, - EventType: event.EventType(eventData.Event.EventType), - Name: eventData.Event.Name, - Description: eventData.Event.Description, - Media: *eventData.Media, - Topics: topics, - MaxParticipants: utils.NullInt32ToIntPtr(eventData.Event.MaxParticipants), - Fee: int(eventData.Event.Fee), - StartAt: utils.NullTimeToTimePtr(eventData.Event.StartAt), - CreatedAt: eventData.Event.CreatedAt, - UpdatedAt: eventData.Event.UpdatedAt, - }, - } - - return c.JSON(http.StatusOK, view) + return c.JSON(http.StatusOK, event.ToShortTermView(found)) } // CreateEvent godoc @@ -172,7 +129,7 @@ func (h *EventHandler) CreateEvent(c echo.Context) error { return err } - return c.JSON(http.StatusCreated, created) + return c.JSON(http.StatusCreated, event.ToShortTermView(created)) } // UpdateEvent godoc diff --git a/internal/domain/event/model.go b/internal/domain/event/model.go index f41f8067..1cef5ad4 100644 --- a/internal/domain/event/model.go +++ b/internal/domain/event/model.go @@ -12,7 +12,7 @@ type Event struct { Media *media.DetailView } -func ToDomainFromFind( +func ToEvent( eventData databasegen.Event, authorData user.WithoutPrivateInfo, mediaData *media.DetailView, diff --git a/internal/domain/event/view.go b/internal/domain/event/view.go index 3179911a..791a694c 100644 --- a/internal/domain/event/view.go +++ b/internal/domain/event/view.go @@ -4,6 +4,7 @@ import ( "time" "github.com/google/uuid" + utils "github.com/pet-sitter/pets-next-door-api/internal/common" "github.com/pet-sitter/pets-next-door-api/internal/domain/media" "github.com/pet-sitter/pets-next-door-api/internal/domain/user" ) @@ -35,3 +36,28 @@ type RecurringView struct { BaseView RecurringPeriod EventRecurringPeriod `json:"recurringPeriod"` } + +func ToShortTermView(eventData *Event) ShortTermView { + topics := make([]EventTopic, len(eventData.Event.Topics)) + for i, topic := range eventData.Event.Topics { + topics[i] = EventTopic(topic) + } + view := ShortTermView{ + BaseView: BaseView{ + ID: eventData.Event.ID, + Author: eventData.Author, + EventType: EventType(eventData.Event.EventType), + Name: eventData.Event.Name, + Description: eventData.Event.Description, + Media: *eventData.Media, + Topics: topics, + MaxParticipants: utils.NullInt32ToIntPtr(eventData.Event.MaxParticipants), + Fee: int(eventData.Event.Fee), + StartAt: utils.NullTimeToTimePtr(eventData.Event.StartAt), + CreatedAt: eventData.Event.CreatedAt, + UpdatedAt: eventData.Event.UpdatedAt, + }, + } + + return view +} diff --git a/internal/service/event_service.go b/internal/service/event_service.go index c6468b5b..94e5953b 100644 --- a/internal/service/event_service.go +++ b/internal/service/event_service.go @@ -36,7 +36,7 @@ func (s *EventService) CreateEvent( ctx context.Context, authorID uuid.UUID, req event.CreateRequest, -) (*databasegen.Event, error) { +) (*event.Event, error) { id, err := uuid.NewV7() if err != nil { return nil, pnd.ErrUnknown(errors.New("failed to generate id")) @@ -44,6 +44,20 @@ func (s *EventService) CreateEvent( q := databasegen.New(s.conn) + // Check if author exists + authorData, err := s.userService.FindUserProfile( + ctx, + user.FindUserParams{ID: uuid.NullUUID{UUID: authorID, Valid: true}}, + ) + if err != nil { + return nil, err + } + authorView := user.WithoutPrivateInfo{ + ID: authorData.ID, + Nickname: authorData.Nickname, + ProfileImageURL: authorData.ProfileImageURL, + } + // Check if media exists if req.MediaID != nil { if _, err = s.mediaService.FindMediaByID(ctx, *req.MediaID); err != nil { @@ -54,6 +68,10 @@ func (s *EventService) CreateEvent( if req.MediaID != nil { mediaID = uuid.NullUUID{UUID: *req.MediaID, Valid: true} } + mediaData, err := s.mediaService.FindMediaByID(ctx, mediaID.UUID) + if err != nil { + return nil, err + } // Map topic[] to string[] topics := make([]string, len(req.Topics)) @@ -77,7 +95,7 @@ func (s *EventService) CreateEvent( return nil, err } - return &eventData, nil + return event.ToEvent(eventData, authorView, mediaData), nil } func (s *EventService) FindEvent( @@ -112,7 +130,7 @@ func (s *EventService) FindEvent( } } - return event.ToDomainFromFind(eventData, authorView, mediaData), nil + return event.ToEvent(eventData, authorView, mediaData), nil } func (s *EventService) FindEvents( @@ -160,7 +178,7 @@ func (s *EventService) FindEvents( for i, eventData := range eventsData { authorData := authorIDsMap[eventData.AuthorID] mediaData := mediasDataMap[eventData.MediaID.UUID] - events[i] = event.ToDomainFromFind(eventData, authorData, &mediaData) + events[i] = event.ToEvent(eventData, authorData, &mediaData) } return events, nil } diff --git a/internal/service/tests/event_service_test.go b/internal/service/tests/event_service_test.go index 501e29e9..65f18d5d 100644 --- a/internal/service/tests/event_service_test.go +++ b/internal/service/tests/event_service_test.go @@ -52,7 +52,7 @@ func TestCreateEvent(t *testing.T) { // Then found, err := eventService.FindEvent( ctx, - databasegen.FindEventParams{ID: uuid.NullUUID{UUID: created.ID, Valid: true}}, + databasegen.FindEventParams{ID: uuid.NullUUID{UUID: created.Event.ID, Valid: true}}, ) assert.Equal(t, err, nil) assertEventEquals(t, created, found) @@ -153,21 +153,21 @@ func TestFindEvents(t *testing.T) { func assertEventEquals( t *testing.T, - expected *databasegen.Event, + expected *event.Event, actual *event.Event, ) { t.Helper() - assert.Equal(t, expected.ID, actual.Event.ID) - assert.Equal(t, expected.AuthorID, actual.Event.AuthorID) - assert.Equal(t, expected.EventType, actual.Event.EventType) - assert.Equal(t, expected.Name, actual.Event.Name) - assert.Equal(t, expected.Description, actual.Event.Description) - assert.Equal(t, expected.MediaID, actual.Event.MediaID) - assert.Equal(t, expected.Topics, actual.Event.Topics) - assert.Equal(t, expected.MaxParticipants, actual.Event.MaxParticipants) - assert.Equal(t, expected.Fee, actual.Event.Fee) - assert.Equal(t, expected.StartAt, actual.Event.StartAt) - assert.Equal(t, expected.CreatedAt, actual.Event.CreatedAt) - assert.Equal(t, expected.UpdatedAt, actual.Event.UpdatedAt) - assert.Equal(t, expected.DeletedAt, actual.Event.DeletedAt) + assert.Equal(t, expected.Event.ID, actual.Event.ID) + assert.Equal(t, expected.Event.AuthorID, actual.Event.AuthorID) + assert.Equal(t, expected.Event.EventType, actual.Event.EventType) + assert.Equal(t, expected.Event.Name, actual.Event.Name) + assert.Equal(t, expected.Event.Description, actual.Event.Description) + assert.Equal(t, expected.Event.MediaID, actual.Event.MediaID) + assert.Equal(t, expected.Event.Topics, actual.Event.Topics) + assert.Equal(t, expected.Event.MaxParticipants, actual.Event.MaxParticipants) + assert.Equal(t, expected.Event.Fee, actual.Event.Fee) + assert.Equal(t, expected.Event.StartAt, actual.Event.StartAt) + assert.Equal(t, expected.Event.CreatedAt, actual.Event.CreatedAt) + assert.Equal(t, expected.Event.UpdatedAt, actual.Event.UpdatedAt) + assert.Equal(t, expected.Event.DeletedAt, actual.Event.DeletedAt) }