Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

돌봄급구 조회 시, N+1 이슈를 해결하고 필터링 조건을 추가하였습니다. #65

Merged
merged 21 commits into from
Apr 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions cmd/server/handler/sos_post_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ func (h *SosPostHandler) WriteSosPost(c echo.Context) error {
// @Param page query int false "페이지 번호" default(1)
// @Param size query int false "페이지 사이즈" default(20)
// @Param sort_by query string false "정렬 기준" Enums(newest, deadline)
// @Param filter_type query string false "필터링 기준" Enums(dog, cat, all)
// @Success 200 {object} sos_post.FindSosPostListView
// @Router /posts/sos [get]
func (h *SosPostHandler) FindSosPosts(c echo.Context) error {
Expand All @@ -73,6 +74,10 @@ func (h *SosPostHandler) FindSosPosts(c echo.Context) error {
if sortByQuery := pnd.ParseOptionalStringQuery(c, "sort_by"); sortByQuery != nil {
sortBy = *sortByQuery
}
filterType := "all"
if filterTypeQuery := pnd.ParseOptionalStringQuery(c, "filter_type"); filterTypeQuery != nil {
filterType = *filterTypeQuery
}

page, size, err := pnd.ParsePaginationQueries(c, 1, 20)
if err != nil {
Expand All @@ -81,12 +86,12 @@ func (h *SosPostHandler) FindSosPosts(c echo.Context) error {

var res *sos_post.FindSosPostListView
if authorID != nil {
res, err = h.sosPostService.FindSosPostsByAuthorID(c.Request().Context(), *authorID, page, size, sortBy)
res, err = h.sosPostService.FindSosPostsByAuthorID(c.Request().Context(), *authorID, page, size, sortBy, filterType)
if err != nil {
return c.JSON(err.StatusCode, err)
}
} else {
res, err = h.sosPostService.FindSosPosts(c.Request().Context(), page, size, sortBy)
res, err = h.sosPostService.FindSosPosts(c.Request().Context(), page, size, sortBy, filterType)
if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand All @@ -99,17 +104,16 @@ func (h *SosPostHandler) FindSosPosts(c echo.Context) error {
// @Summary 게시글 ID로 돌봄급구 게시글을 조회합니다.
// @Description
// @Tags posts
// @Accept json
// @Produce json
// @Param id path string true "게시글 ID"
// @Param id path int true "게시글 ID"
// @Success 200 {object} sos_post.FindSosPostView
// @Router /posts/sos/{id} [get]
func (h *SosPostHandler) FindSosPostByID(c echo.Context) error {
SosPostID, err := pnd.ParseIDFromPath(c, "id")
id, err := pnd.ParseIDFromPath(c, "id")
if err != nil {
return c.JSON(err.StatusCode, err)
}
res, err := h.sosPostService.FindSosPostByID(c.Request().Context(), *SosPostID)
res, err := h.sosPostService.FindSosPostByID(c.Request().Context(), *id)
if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand Down
Empty file.
9 changes: 9 additions & 0 deletions db/migrations/000017_timestamp_with_timezone.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
ALTER TABLE sos_dates
ALTER created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC',
ALTER updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC',
ALTER deleted_at TYPE timestamptz USING deleted_at AT TIME ZONE 'UTC';

ALTER TABLE sos_posts_dates
ALTER created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC',
ALTER updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC',
ALTER deleted_at TYPE timestamptz USING deleted_at AT TIME ZONE 'UTC';
4 changes: 4 additions & 0 deletions db/migrations/000018_create_posts_view.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
DROP VIEW IF EXISTS v_sos_posts;
DROP VIEW IF EXISTS v_conditions;
DROP VIEW IF EXISTS v_pets_for_sos_posts;
DROP VIEW IF EXISTS v_media_for_sos_posts;
86 changes: 86 additions & 0 deletions db/migrations/000018_create_posts_view.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
-- 돌봄 급구(SosPosts) 테이블 VIEW 생성
CREATE OR REPLACE VIEW v_sos_posts AS
SELECT
sos_posts.id,
sos_posts.title,
sos_posts.content,
sos_posts.reward,
sos_posts.reward_type,
sos_posts.care_type,
sos_posts.carer_gender,
sos_posts.thumbnail_id,
sos_posts.author_id,
sos_posts.created_at,
sos_posts.updated_at,
MIN(sos_dates.date_start_at) AS earliest_date_start_at,
json_agg(sos_dates.*) FILTER (WHERE sos_dates.deleted_at IS NULL) AS dates
FROM
sos_posts
LEFT JOIN sos_posts_dates ON sos_posts.id = sos_posts_dates.sos_post_id
LEFT JOIN sos_dates ON sos_posts_dates.sos_dates_id = sos_dates.id
WHERE
sos_posts.deleted_at IS NULL
AND sos_dates.deleted_at IS NULL
AND sos_posts_dates.deleted_at IS NULL
GROUP BY sos_posts.id;

-- 돌봄 급구 Conditions 테이블 VIEW 생성
CREATE OR REPLACE VIEW v_conditions AS
SELECT
sos_posts_conditions.sos_post_id,
json_agg(sos_conditions.*) FILTER (WHERE sos_conditions.deleted_at IS NULL) AS conditions_info
FROM
sos_posts_conditions
LEFT JOIN sos_conditions ON sos_posts_conditions.sos_condition_id = sos_conditions.id
WHERE
sos_conditions.deleted_at IS NULL AND
sos_posts_conditions.deleted_at IS NULL
GROUP BY sos_posts_conditions.sos_post_id;

-- 돌봄 급구 관련 Pets 테이블 VIEW 생성
CREATE OR REPLACE VIEW v_pets_for_sos_posts AS
SELECT
sos_posts_pets.sos_post_id,
array_agg(pets.pet_type) AS pet_type_list,
json_agg(
json_build_object(
'id', pets.id,
'owner_id', pets.owner_id,
'name', pets.name,
'pet_type', pets.pet_type,
'sex', pets.sex,
'neutered', pets.neutered,
'breed', pets.breed,
'birth_date', pets.birth_date,
'weight_in_kg', pets.weight_in_kg,
'additional_note', pets.additional_note,
'created_at', pets.created_at,
'updated_at', pets.updated_at,
'deleted_at', pets.deleted_at,
'remarks', pets.remarks,
'profile_image_id', pets.profile_image_id,
'profile_image_url', media.url
)
) FILTER (WHERE pets.deleted_at IS NULL) AS pets_info
FROM
sos_posts_pets
INNER JOIN pets ON sos_posts_pets.pet_id = pets.id AND pets.deleted_at IS NULL
LEFT JOIN media ON pets.profile_image_id = media.id
WHERE
sos_posts_pets.deleted_at IS NULL
GROUP BY sos_posts_pets.sos_post_id;



-- 돌봄 급구 관련 Media 테이블 VIEW 생성
CREATE OR REPLACE VIEW v_media_for_sos_posts AS
SELECT
resource_media.resource_id AS sos_post_id,
json_agg(media.*) FILTER (WHERE media.deleted_at IS NULL) AS media_info
FROM
resource_media
LEFT JOIN media ON resource_media.media_id = media.id
WHERE
media.deleted_at IS NULL AND
resource_media.deleted_at IS NULL
GROUP BY resource_media.resource_id;
6 changes: 6 additions & 0 deletions internal/common/format.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,9 @@ func FormatDate(datetimeStr string) string {
func FormatTime(datetime time.Time) string {
return datetime.Format("15:04")
}

// FormatDateTime formats datetime time.Time to datetime string.
// Example: 2021-01-01T10:06:23.9999999Z -> 2021-01-01T10:06:23
func FormatDateTime(datetime time.Time) string {
return datetime.Format("2006-01-02T15:04:05")
}
12 changes: 6 additions & 6 deletions internal/domain/media/media.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,12 @@ const (
)

type Media struct {
ID int `field:"id"`
MediaType MediaType `field:"media_type"`
URL string `field:"url"`
CreatedAt string `field:"created_at"`
UpdatedAt string `field:"updated_at"`
DeletedAt string `field:"deleted_at"`
ID int `field:"id" json:"id"`
MediaType MediaType `field:"media_type" json:"media_type"`
URL string `field:"url" json:"url"`
CreatedAt string `field:"created_at" json:"created_at"`
UpdatedAt string `field:"updated_at" json:"updated_at"`
DeletedAt string `field:"deleted_at" json:"deleted_at"`
}

type MediaList []*Media
Expand Down
28 changes: 14 additions & 14 deletions internal/domain/pet/pet.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,19 @@ import (
)

type BasePet struct {
ID int `field:"id"`
OwnerID int `field:"owner_id"`
Name string `field:"name"`
PetType PetType `field:"pet_type"`
Sex PetSex `field:"sex"`
Neutered bool `field:"neutered"`
Breed string `field:"breed"`
BirthDate string `field:"birth_date"`
WeightInKg float64 `field:"weight_in_kg"`
Remarks string `field:"remarks"`
CreatedAt string `field:"created_at"`
UpdatedAt string `field:"updated_at"`
DeletedAt string `field:"deleted_at"`
ID int `field:"id" json:"id"`
OwnerID int `field:"owner_id" json:"owner_id"`
Name string `field:"name" json:"name"`
PetType PetType `field:"pet_type" json:"pet_type"`
Sex PetSex `field:"sex" json:"sex"`
Neutered bool `field:"neutered" json:"neutered"`
Breed string `field:"breed" json:"breed"`
BirthDate string `field:"birth_date" json:"birth_date"`
WeightInKg float64 `field:"weight_in_kg" json:"weight_in_kg"`
Remarks string `field:"remarks" json:"remarks"`
CreatedAt string `field:"created_at" json:"created_at"`
UpdatedAt string `field:"updated_at" json:"updated_at"`
DeletedAt string `field:"deleted_at" json:"deleted_at"`
}

type Pet struct {
Expand All @@ -32,7 +32,7 @@ type PetList []*Pet

type PetWithProfileImage struct {
BasePet
ProfileImageURL *string `field:"profile_image_url"`
ProfileImageURL *string `field:"profile_image_url" json:"profile_image_url"`
}

type PetWithProfileList []*PetWithProfileImage
Expand Down
2 changes: 1 addition & 1 deletion internal/domain/pet/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ func (pet *PetWithProfileImage) ToPetView() *PetView {
Sex: pet.Sex,
Neutered: pet.Neutered,
Breed: pet.Breed,
BirthDate: pet.BirthDate,
BirthDate: utils.FormatDate(pet.BirthDate),
WeightInKg: pet.WeightInKg,
Remarks: pet.Remarks,
ProfileImageURL: pet.ProfileImageURL,
Expand Down
51 changes: 40 additions & 11 deletions internal/domain/sos_post/sos_post.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ package sos_post

import (
"context"
"github.com/pet-sitter/pets-next-door-api/internal/infra/database"
"time"

pnd "github.com/pet-sitter/pets-next-door-api/api"
"github.com/pet-sitter/pets-next-door-api/internal/domain/media"
"github.com/pet-sitter/pets-next-door-api/internal/domain/pet"
"github.com/pet-sitter/pets-next-door-api/internal/infra/database"
"time"
)

type CareType string
Expand Down Expand Up @@ -49,27 +49,56 @@ type SosPostList struct {
*pnd.PaginatedView[SosPost]
}

type SosPostInfo struct {
ID int `field:"id" json:"id"`
AuthorID int `field:"author" json:"author"`
Title string `field:"title" json:"title"`
Content string `field:"content" json:"content"`
Media media.MediaList `field:"media" json:"media"`
Conditions ConditionList `field:"conditions" json:"conditions"`
Pets pet.PetWithProfileList `field:"pets" json:"pets"`
Reward string `field:"reward" json:"reward"`
Dates SosDatesList `field:"dates" json:"dates"`
CareType CareType `field:"careType" json:"careType"`
CarerGender CarerGender `field:"carerGender" json:"carerGender"`
RewardType RewardType `field:"rewardType" json:"rewardType"`
ThumbnailID int `field:"thumbnailId" json:"thumbnailId"`
CreatedAt time.Time `field:"createdAt" json:"createdAt"`
UpdatedAt time.Time `field:"updatedAt" json:"updatedAt"`
DeletedAt time.Time `field:"deletedAt" json:"deletedAt"`
}

type SosPostInfoList struct {
*pnd.PaginatedView[SosPostInfo]
}

func NewSosPostList(page int, size int) *SosPostList {
return &SosPostList{PaginatedView: pnd.NewPaginatedView(
page, size, false, make([]SosPost, 0),
)}
}

func NewSosPostInfoList(page int, size int) *SosPostInfoList {
return &SosPostInfoList{PaginatedView: pnd.NewPaginatedView(
page, size, false, make([]SosPostInfo, 0),
)}
}

type SosDates struct {
ID int `field:"id"`
DateStartAt string `field:"date_start_at"`
DateEndAt string `field:"date_end_at"`
CreatedAt time.Time `field:"created_at"`
UpdatedAt time.Time `field:"updated_at"`
DeletedAt time.Time `field:"deleted_at"`
ID int `field:"id" json:"id"`
DateStartAt string `field:"date_start_at" json:"date_start_at"`
DateEndAt string `field:"date_end_at" json:"date_end_at"`
CreatedAt time.Time `field:"created_at" json:"created_at"`
UpdatedAt time.Time `field:"updated_at" json:"updated_at"`
DeletedAt time.Time `field:"deleted_at" json:"deleted_at"`
}

type SosDatesList []*SosDates

type SosPostStore interface {
WriteSosPost(ctx context.Context, tx *database.Tx, authorID int, utcDateStart string, utcDateEnd string, request *WriteSosPostRequest) (*SosPost, *pnd.AppError)
FindSosPosts(ctx context.Context, tx *database.Tx, page int, size int, sortBy string) (*SosPostList, *pnd.AppError)
FindSosPostsByAuthorID(ctx context.Context, tx *database.Tx, authorID int, page int, size int, sortBy string) (*SosPostList, *pnd.AppError)
FindSosPosts(ctx context.Context, tx *database.Tx, page int, size int, sortBy string) (*SosPostInfoList, *pnd.AppError)
FindSosPostsByAuthorID(ctx context.Context, tx *database.Tx, authorID int, page int, size int, sortBy string) (*SosPostInfoList, *pnd.AppError)
FindSosPostByID(ctx context.Context, tx *database.Tx, id int) (*SosPost, *pnd.AppError)
UpdateSosPost(ctx context.Context, tx *database.Tx, request *UpdateSosPostRequest) (*SosPost, *pnd.AppError)
FindConditionByID(ctx context.Context, tx *database.Tx, id int) (*ConditionList, *pnd.AppError)
Expand Down
Loading