Skip to content

Commit

Permalink
feat : add chat room application service
Browse files Browse the repository at this point in the history
  • Loading branch information
JoeCP17 committed Sep 12, 2024
1 parent 9ded1c8 commit d5eaac3
Show file tree
Hide file tree
Showing 12 changed files with 476 additions and 517 deletions.
18 changes: 18 additions & 0 deletions api/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,3 +54,21 @@ func setHeaders(w http.ResponseWriter, headers map[string]string) {
w.Header().Set(key, value)
}
}

type CursorPaginatedView[T interface{}] struct {
Prev string `json:"prev"`
Next string `json:"next"`
HasPrev bool `json:"hasPrev"`
HasNext bool `json:"hasNext"`
Items []T `json:"items"`
}

func NewCursorPaginatedView[T interface{}](prev, next string, hasPrev, hasNext bool, items []T) *CursorPaginatedView[T] {
return &CursorPaginatedView[T]{
Prev: prev,
Next: next,
HasPrev: hasPrev,
HasNext: hasNext,
Items: items,
}
}
40 changes: 40 additions & 0 deletions api/web.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,43 @@ func ParsePaginationQueries(c echo.Context, defaultPage, defaultLimit int) (page

return page, size, nil
}

func ParseCursorPaginationQueries(c echo.Context, defaultLimit int) (prev int, next int, limit int, err *AppError) {
prevQuery := c.QueryParam("prev")
nextQuery := c.QueryParam("next")
sizeQuery := c.QueryParam("size")

if prevQuery == "" && nextQuery == "" {
return 0, 0, 0, ErrInvalidPagination(errors.New("expected either prev or next query"))
}

if prevQuery != "" {
var atoiError error
prev, atoiError = strconv.Atoi(prevQuery)
if atoiError != nil || prev <= 0 {
return 0, 0, 0, ErrInvalidPagination(errors.New("expected integer value bigger than 0 for query: prev"))
}
}

if nextQuery != "" {
var atoiError error
next, atoiError = strconv.Atoi(nextQuery)
if atoiError != nil || next <= 0 {
return 0, 0, 0, ErrInvalidPagination(errors.New("expected integer value bigger than 0 for query: next"))
}
}

if sizeQuery != "" {
var atoiError error
limit, atoiError = strconv.Atoi(sizeQuery)
if atoiError != nil || limit <= 0 {
return 0, 0, 0, ErrInvalidPagination(errors.New("expected integer value bigger than 0 for query: size"))
}
}

if limit == 0 {
limit = defaultLimit
}

return prev, next, limit, nil
}
49 changes: 39 additions & 10 deletions cmd/server/handler/chat_handler.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
package handler

import (
"net/http"

"github.com/labstack/echo/v4"
pnd "github.com/pet-sitter/pets-next-door-api/api"
domain "github.com/pet-sitter/pets-next-door-api/internal/domain/chat"
"github.com/pet-sitter/pets-next-door-api/internal/service"
"net/http"
)

type ChatHandler struct {
Expand Down Expand Up @@ -35,12 +34,17 @@ func NewChatHandler(
// @Success 200 {object} domain.Room
// @Router /chat/rooms/{roomID} [get]
func (h ChatHandler) FindRoomByID(c echo.Context) error {
foundUser, err := h.authService.VerifyAuthAndGetUser(c.Request().Context(), c.Request().Header.Get("Authorization"))
if err != nil {
return c.JSON(err.StatusCode, err)
}

roomID, err := pnd.ParseIDFromPath(c, "roomID")
if err != nil {
return c.JSON(err.StatusCode, err)
}

res, err := h.chatService.FindRoomByID(c.Request().Context(), int64(*roomID))
res, err := h.chatService.FindChatRoomByUIDAndRoomID(c.Request().Context(), foundUser.FirebaseUID, int64(*roomID))
if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand All @@ -60,11 +64,18 @@ func (h ChatHandler) FindRoomByID(c echo.Context) error {
// @Router /chat/rooms [post]
func (h ChatHandler) CreateRoom(c echo.Context) error {
var createRoomRequest domain.CreateRoomRequest

if err := pnd.ParseBody(c, &createRoomRequest); err != nil {
return c.JSON(err.StatusCode, err)
}

res, err := h.chatService.CreateRoom(c.Request().Context(), createRoomRequest.RoomName, createRoomRequest.RoomType)
res, err := h.chatService.CreateRoom(
c.Request().Context(),
createRoomRequest.RoomName,
createRoomRequest.RoomType,
createRoomRequest.JoinUserIds,
)

if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand All @@ -80,7 +91,7 @@ func (h ChatHandler) CreateRoom(c echo.Context) error {
// @Produce json
// @Param roomID path int true "채팅방 ID"
// @Security FirebaseAuth
// @Success 200 {object} domain.JoinRoomView
// @Success 200 {object} domain.JoinRoomsView
// @Router /chat/rooms/{roomID}/join [post]
func (h ChatHandler) JoinChatRoom(c echo.Context) error {
foundUser, err := h.authService.VerifyAuthAndGetUser(c.Request().Context(), c.Request().Header.Get("Authorization"))
Expand Down Expand Up @@ -127,16 +138,22 @@ func (h ChatHandler) LeaveChatRoom(c echo.Context) error {
}

// FindAllRooms godoc
// @Summary 모든 채팅방을 조회합니다.
// @Description 모든 채팅방을 조회합니다. ( 현재는 Mock 데이터로 응답합니다 )
// @Summary 유저가 소속되어 있는 모든 채팅방을 조회합니다.
// @Description 유저가 소속되어 있는 채팅방 전체 목록을 조회합니다.
// @Tags chat
// @Accept json
// @Produce json
// @Security FirebaseAuth
// @Success 200 {object} []domain.Room
// @Router /chat/rooms [get]
func (h ChatHandler) FindAllRooms(c echo.Context) error {
rooms, err := h.chatService.MockFindAllChatRooms()
foundUser, err := h.authService.VerifyAuthAndGetUser(c.Request().Context(), c.Request().Header.Get("Authorization"))

if err != nil {
return c.JSON(err.StatusCode, err)
}

rooms, err := h.chatService.FindAllByUserUID(c.Request().Context(), foundUser.FirebaseUID)
if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand All @@ -145,7 +162,7 @@ func (h ChatHandler) FindAllRooms(c echo.Context) error {

// FindMessagesByRoomID godoc
// @Summary 채팅방의 메시지를 조회합니다.
// @Description 채팅방의 메시지를 조회합니다. ( 현재는 Mock 데이터로 응답합니다 )
// @Description 채팅방의 메시지를 조회합니다.
// @Tags chat
// @Accept json
// @Produce json
Expand All @@ -159,7 +176,19 @@ func (h ChatHandler) FindMessagesByRoomID(c echo.Context) error {
return c.JSON(err.StatusCode, err)
}

res, err := h.chatService.MockFindMessagesByRoomID(int64(*roomID))
prev, next, limit, appError := pnd.ParseCursorPaginationQueries(c, 30)
if appError != nil {
return c.JSON(appError.StatusCode, appError)
}

res, err := h.chatService.FindChatRoomMessagesByRoomID(
c.Request().Context(),
int64(*roomID),
int64(prev),
int64(next),
int64(limit),
)

if err != nil {
return c.JSON(err.StatusCode, err)
}
Expand Down
2 changes: 0 additions & 2 deletions cmd/server/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,6 @@ func NewRouter(app *firebaseinfra.FirebaseApp) (*echo.Echo, error) {
chatAPIGroup.PUT("/rooms/:roomID/join", chatHandler.JoinChatRoom)
chatAPIGroup.PUT("/rooms/:roomID/leave", chatHandler.LeaveChatRoom)
chatAPIGroup.GET("/rooms/:roomID", chatHandler.FindRoomByID)

// Mock
chatAPIGroup.GET("/rooms", chatHandler.FindAllRooms)
chatAPIGroup.GET("/rooms/:roomID/messages", chatHandler.FindMessagesByRoomID)
}
Expand Down
82 changes: 54 additions & 28 deletions internal/domain/chat/model.go
Original file line number Diff line number Diff line change
@@ -1,48 +1,74 @@
package chat

import "time"
import (
"database/sql"
"time"
)

type (
RoomType string
MessageType string
)

func (t RoomType) IsValid() bool {
switch t {
case EVENT_ROOM_TYPE:
return true
default:
return false
}
}

const (
RoomTypePersonal = "personal"
RoomTypeGathering = "gathering"
EVENT_ROOM_TYPE = "event"
)

const (
MessageTypeNormal = "normal"
MessageTypePromise = "promise"
EVENT_MESSAGE = "event"
)

type Room struct {
ID int64 `field:"id" json:"id"`
Name string `field:"name" json:"name"`
RoomType RoomType `field:"RoomType" json:"RoomType"`
CreatedAt time.Time `field:"createdAt" json:"createdAt"`
UpdatedAt time.Time `field:"updatedAt" json:"updatedAt"`
DeletedAt time.Time `field:"deletedAt" json:"deletedAt"`
type RoomSimpleInfo struct {
ID string `field:"id" json:"id"`
RoomName string `field:"roomName" json:"roomName"`
RoomType string `field:"roomType" json:"roomType"`
JoinUsers *[]JoinUsersSimpleInfo `field:"joinUsers" json:"joinUsers"`
CreatedAt time.Time `field:"createdAt" json:"createdAt"`
UpdatedAt time.Time `field:"updatedAt" json:"updatedAt"`
}

type Message struct {
ID int64 `field:"id" json:"id"`
UserID int64 `field:"userID" json:"userID"`
RoomID int64 `field:"roomID" json:"roomID"`
MessageType MessageType `field:"messageType" json:"messageType"`
Content string `field:"content" json:"content"`
CreatedAt time.Time `field:"createdAt" json:"createdAt"`
UpdatedAt time.Time `field:"updatedAt" json:"updatedAt"`
DeletedAt time.Time `field:"deletedAt" json:"deletedAt"`
type JoinUsersSimpleInfo struct {
ID string `field:"id" json:"userId"`
UserNickname string `field:"nickname" json:"userNickname"`
UserProfileImage sql.NullString `field:"profileImage" json:"profileImageId"`
}

type JoinRoom struct {
UserID string
RoomID string
JoinedAt time.Time
}

type UserChatRoom struct {
ID int64 `field:"id" json:"id"`
UserID int64 `field:"userID" json:"userID"`
RoomID int64 `field:"roomID" json:"roomID"`
JoinedAt time.Time `field:"joinedAt" json:"joinedAt"`
LeftAt time.Time `field:"leftAt" json:"leftAt"`
// 조회 시 Room 정보를 반환하는 View
type JoinRoomsView struct {
Items []RoomSimpleInfo `field:"items" json:"items"`
}

type UserChatRoomList []*UserChatRoom
type UserChatRoomMessageView struct {
ID string `field:"id" json:"id"`
MessageType string `field:"messageType" json:"messageType"`
}

type Message struct {
ID int64 `field:"id" json:"id"`
UserID int64 `field:"userID" json:"userID"`
RoomID int64 `field:"roomID" json:"roomID"`
MessageType string `field:"messageType" json:"messageType"`
Content string `field:"content" json:"content"`
CreatedAt time.Time `field:"createdAt" json:"createdAt"`
}

type MessageCursorView struct {
HasNext *bool `field:"hasNext" json:"hasNext"`
HasPrev *bool `field:"hasPrev" json:"hasPrev"`
Items []Message `field:"items" json:"items,omitempty"`
}
7 changes: 3 additions & 4 deletions internal/domain/chat/request.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package chat

// FIXME : 정책상 채팅방 생성시 필요 정보가 있을 경우 추가 해야함
type CreateRoomRequest struct {
RoomName string `json:"roomName" validate:"required"`
// FIXME : Model 항목을 상속받고 있는데 위계질서에 어긋남 수정 필요
RoomType RoomType `json:"roomType" validate:"required"`
RoomName string `json:"roomName" validate:"required"`
RoomType string `json:"roomType" validate:"required"`
JoinUserIds *[]int64 `json:"joinUsers"`
}
14 changes: 14 additions & 0 deletions internal/domain/chat/validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package chat

import "errors"

// Validate to validate CreateRoomRequest
func (r CreateRoomRequest) RoomTypeValidate() error {
// RoomType이 Model에 정의된 값인지 확인
switch r.RoomType {
case EVENT_ROOM_TYPE:
return nil
default:
return errors.New("invalid room type. please check room type")
}
}
Loading

0 comments on commit d5eaac3

Please sign in to comment.