Skip to content

Commit

Permalink
implement getTripById
Browse files Browse the repository at this point in the history
  • Loading branch information
Taehoya committed Mar 3, 2024
1 parent e96213f commit f6d998c
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 2 deletions.
33 changes: 31 additions & 2 deletions server/internal/pkg/dto/trip.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,24 @@ import (
type TripResponseDTO struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"sample-name"`
Budget float64 `json:"budget" example:"100.12"`
Budget float64 `json:"budget" example:"12345.12"`
CountryProperty CountryResponseDTO `json:"countryProperty" binding:"required"`
NoteProperty TripNoteProperty `json:"noteProperty"`
Transaction []*TransactionResponseDTO `json:"transactions"`
StartDateTime time.Time `json:"startDateTime" example:"2024-01-02T15:04:05Z"`
EndDateTime time.Time `json:"endDateTime" example:"2024-01-02T15:04:05Z"`
}

// Need to add top5 transactions
type DetailedTripResponseDTO struct {
ID int `json:"id" example:"1"`
Name string `json:"name" example:"sample-name"`
Budget float64 `json:"budget" example:"12345.12"`
CountryProperty CountryResponseDTO `json:"countryProperty" binding:"required"`
NoteProperty TripNoteProperty `json:"noteProperty"`
Transaction []*TransactionResponseDTO `json:"transactions"`
TotalExpense float64 `json:"totalExpense" example:"100.12"`
T5Transactions []*TransactionResponseDTO `json:"top5Transactions"`
Description string `json:"description" example:"sample-description"`
StartDateTime time.Time `json:"startDateTime" example:"2024-01-02T15:04:05Z"`
EndDateTime time.Time `json:"endDateTime" example:"2024-01-02T15:04:05Z"`
Expand All @@ -37,7 +51,7 @@ type TripNoteOptions struct {

type TripRequestDTO struct {
Name string `json:"name" binding:"required" example:"sample-name"`
Budget float64 `json:"budget" binding:"required" example:"2000.12"`
Budget float64 `json:"budget" binding:"required" example:"2000.02"`
CountryId int `json:"countryId" binding:"required" example:"1"`
Description string `json:"description" binding:"required" example:"sample-description"`
NoteProperty TripNoteProperty `json:"noteProperty" binding:"required"`
Expand All @@ -51,8 +65,23 @@ func NewTripResponse(trip *entities.Trip, country *entities.Country) *TripRespon
Name: trip.Name(),
Budget: trip.Budget(),
CountryProperty: *NewCountryResponse(country),
NoteProperty: TripNoteProperty{NoteType: trip.Note().NoteType, NoteColor: trip.Note().NoteColor, BoundColor: trip.Note().BoundColor},
Transaction: NewTransactionResponseList(trip.Transactions()),
StartDateTime: trip.StartDateTime(),
EndDateTime: trip.EndDateTime(),
}
}

func NewDetailedTripResponse(trip *entities.Trip, country *entities.Country, totalExpense float64, top5Transactions []*entities.Transaction) *DetailedTripResponseDTO {
return &DetailedTripResponseDTO{
ID: trip.ID(),
Name: trip.Name(),
Budget: trip.Budget(),
TotalExpense: totalExpense,
CountryProperty: *NewCountryResponse(country),
Description: trip.Description(),
NoteProperty: TripNoteProperty{NoteType: trip.Note().NoteType, NoteColor: trip.Note().NoteColor, BoundColor: trip.Note().BoundColor},
T5Transactions: NewTransactionResponseList(top5Transactions),
Transaction: NewTransactionResponseList(trip.Transactions()),
StartDateTime: trip.StartDateTime(),
EndDateTime: trip.EndDateTime(),
Expand Down
1 change: 1 addition & 0 deletions server/internal/pkg/handlers/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ func (h *Handler) InitRoutes() http.Handler {
apiGroup.POST("/users", h.Register)
apiGroup.POST("/users/login", h.Login)
apiGroup.GET("/trips", middlewares.JwtAuthMiddleware(), h.GetTrip)
apiGroup.GET("/trips/:id", middlewares.JwtAuthMiddleware(), h.GetTripById)
apiGroup.POST("/trips", middlewares.JwtAuthMiddleware(), h.RegisterTrip)
apiGroup.DELETE("/trips/:id", middlewares.JwtAuthMiddleware(), h.DeleteTrip)
apiGroup.PUT("/trips/:id", middlewares.JwtAuthMiddleware(), h.UpdateTrip)
Expand Down
46 changes: 46 additions & 0 deletions server/internal/pkg/handlers/trip.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ type TripUseCase interface {
GetTripsByStatus(ctx context.Context, userId int) (*dto.TripStatusResponseDTO, error)
DeleteTrip(ctx context.Context, tripId int) error
UpdateTrip(ctx context.Context, tripId int, dto dto.TripRequestDTO) error
GetTripById(ctx context.Context, tripId int) (*dto.DetailedTripResponseDTO, error)
}

// register trip
Expand Down Expand Up @@ -98,6 +99,51 @@ func (h *Handler) GetTrip(ctx *gin.Context) {
ctx.JSON(http.StatusOK, trips)
}

// get trip by id
//
// @Summary get trip by id
// @Description get trip by id
// @Tags trip
// @Accept json
// @Produce json
// @Security bearer
// @param Authorization header string true "Authorization"
// @Param id path int true "id"
// @Success 200 {object} dto.DetailedTripResponseDTO
// @Failure 400 {object} dto.ErrorResponseDTO
// @Failure 401 {object} dto.ErrorResponseDTO
// @Failure 500 {object} dto.ErrorResponseDTO
// @Router /v1/trips/{id} [get]
func (h *Handler) GetTripById(ctx *gin.Context) {
tripId := ctx.Param("id")

if tripId == "" {
ctx.JSON(http.StatusBadRequest, gin.H{
"error_message": "bad request",
})
return
}

tripIdInt, err := strconv.Atoi(tripId)
if err != nil {
ctx.JSON(http.StatusBadRequest, gin.H{
"error_message": "bad request",
})
return
}

trip, err := h.TripUseCase.GetTripById(ctx, tripIdInt)

if err != nil {
ctx.JSON(http.StatusInternalServerError, gin.H{
"error_message": err,
})
return
}

ctx.JSON(http.StatusOK, trip)
}

// delete trip
//
// @Summary delete trip
Expand Down
30 changes: 30 additions & 0 deletions server/internal/pkg/handlers/trip_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,36 @@ func TestGetTrips(t *testing.T) {
})
}

func TestGetTripById(t *testing.T) {
t.Run("successfully get the list of user's trip", func(t *testing.T) {
projectRootDir, _ := pathutil.GetProjectRootDir()
err := godotenv.Load(fmt.Sprintf("%s/.env", projectRootDir))
assert.NoError(t, err)

rr := httptest.NewRecorder()
tripUseCase := tripMocks.NewTripUseCase()
countryUseCase := countryMocks.NewCountryUseCase()
userUseCase := userMocks.NewUserUeseCase()
transactionUseCase := transactionMocks.NewTransactionUseCase()
handler := New(tripUseCase, countryUseCase, userUseCase, transactionUseCase)
router := handler.InitRoutes()

userId := 1
tripId := 1

token, err := token.MakeToken(userId)
assert.NoError(t, err)

tripResponseDTO := dto.DetailedTripResponseDTO{}
tripUseCase.On("GetTripById", mock.Anything, tripId).Return(&tripResponseDTO, nil)
request, err := http.NewRequest(http.MethodGet, fmt.Sprintf("/api/v1/trips/%d", tripId), nil)
request.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))
router.ServeHTTP(rr, request)
assert.Equal(t, 200, rr.Code)
assert.NoError(t, err)
})
}

func TestUpdateTrip(t *testing.T) {
t.Run("successfully update user's trip", func(t *testing.T) {
projectRootDir, _ := pathutil.GetProjectRootDir()
Expand Down
16 changes: 16 additions & 0 deletions server/internal/pkg/mocks/trip/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,19 @@ func (m *TripUseCaseMock) GetTripsByStatus(ctx context.Context, userId int) (*dt

return r0, r1
}

func (m *TripUseCaseMock) GetTripById(ctx context.Context, tripId int) (*dto.DetailedTripResponseDTO, error) {
ret := m.Called(ctx, tripId)

var r0 *dto.DetailedTripResponseDTO
if ret.Get(0) != nil {
r0 = ret.Get(0).(*dto.DetailedTripResponseDTO)
}

var r1 error
if ret.Get(1) != nil {
r1 = ret.Get(1).(error)
}

return r0, r1
}
44 changes: 44 additions & 0 deletions server/internal/pkg/usecases/trip/usecase.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package usecase

import (
"context"
"sort"
"time"

"github.com/Taehoya/pocket-mate/internal/pkg/dto"
Expand Down Expand Up @@ -111,3 +112,46 @@ func (u *TripUseCase) UpdateTrip(ctx context.Context, tripId int, dto dto.TripRe

return u.tripRepository.UpdateTrip(ctx, tripId, dto.Name, dto.Budget, dto.CountryId, dto.Description, *note, dto.StartDateTime, dto.EndDateTime)
}

func (u *TripUseCase) GetTripById(ctx context.Context, tripId int) (*dto.DetailedTripResponseDTO, error) {
trip, err := u.tripRepository.GetTripById(ctx, tripId)
if err != nil {
return nil, err
}

country, err := u.countryRepository.GetCountryById(ctx, trip.CountryID())
if err != nil {
return nil, err
}

transactions, err := u.transactionRepository.GetTransactionByTripId(ctx, trip.ID())
if err != nil {
return nil, err
}
trip.SetTransactions(transactions)
totalExpense := getTripsAndTotalExpense(transactions)
top5Transactions := getTopTransactions(transactions, 5)
tripResponse := dto.NewDetailedTripResponse(trip, country, totalExpense, top5Transactions)
return tripResponse, nil
}

func getTripsAndTotalExpense(transactions []*entities.Transaction) float64 {
totalExpense := 0.0
for _, transaction := range transactions {
totalExpense += transaction.Amount()
}
return totalExpense
}

func getTopTransactions(transactionsParam []*entities.Transaction, top int) []*entities.Transaction {
transactions := transactionsParam
sort.Slice(transactions, func(i, j int) bool {
return transactions[i].Amount() > transactions[j].Amount()
})

if len(transactions) < top {
return transactions
}

return transactions[:top]
}
78 changes: 78 additions & 0 deletions server/internal/pkg/usecases/trip/usecase_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,84 @@ func TestGetTrips(t *testing.T) {
})
}

func TestGetTripById(t *testing.T) {
t.Run("successfully get trip by id", func(t *testing.T) {
tripRepository := tripMock.NewTripRepositoryMock()
userTripRepository := userTripMock.NewUserTripRepositoryMock()
countryRepository := countryMock.NewCountryRepositoryMock()
TransactionRepository := transactionMock.NewTransactionRepositoryMock()
usecase := NewTripUseCase(tripRepository, userTripRepository, countryRepository, TransactionRepository)

ctx := context.TODO()
tripId := 1

trip := entities.NewTrip(tripId, "test-name", 1000, true, 1, "test-description", entities.Note{}, time.Now(), time.Now(), time.Now(), time.Now())
tripRepository.Mock.On("GetTripById", ctx, tripId).Return(trip, nil)
countryRepository.Mock.On("GetCountryById", ctx, trip.CountryID()).Return(&entities.Country{}, nil)
TransactionRepository.Mock.On("GetTransactionByTripId", ctx, tripId).Return([]*entities.Transaction{}, nil)

_, err := usecase.GetTripById(ctx, tripId)
assert.NoError(t, err)
tripRepository.AssertExpectations(t)
})

t.Run("failed to get trip", func(t *testing.T) {
tripRepository := tripMock.NewTripRepositoryMock()
userTripRepository := userTripMock.NewUserTripRepositoryMock()
countryRepository := countryMock.NewCountryRepositoryMock()
TransactionRepository := transactionMock.NewTransactionRepositoryMock()
usecase := NewTripUseCase(tripRepository, userTripRepository, countryRepository, TransactionRepository)

ctx := context.TODO()
tripId := 1

trip := entities.NewTrip(tripId, "test-name", 1000, true, 1, "test-description", entities.Note{}, time.Now(), time.Now(), time.Now(), time.Now())
tripRepository.Mock.On("GetTripById", ctx, tripId).Return(trip, fmt.Errorf("error"))
_, err := usecase.GetTripById(ctx, tripId)
assert.Error(t, err)
tripRepository.AssertExpectations(t)
})

t.Run("failed to get trip", func(t *testing.T) {
tripRepository := tripMock.NewTripRepositoryMock()
userTripRepository := userTripMock.NewUserTripRepositoryMock()
countryRepository := countryMock.NewCountryRepositoryMock()
TransactionRepository := transactionMock.NewTransactionRepositoryMock()
usecase := NewTripUseCase(tripRepository, userTripRepository, countryRepository, TransactionRepository)

ctx := context.TODO()
tripId := 1

trip := entities.NewTrip(tripId, "test-name", 1000, true, 1, "test-description", entities.Note{}, time.Now(), time.Now(), time.Now(), time.Now())
tripRepository.Mock.On("GetTripById", ctx, tripId).Return(trip, nil)
countryRepository.Mock.On("GetCountryById", ctx, trip.CountryID()).Return(nil, fmt.Errorf("error"))

_, err := usecase.GetTripById(ctx, tripId)
assert.Error(t, err)
tripRepository.AssertExpectations(t)
})

t.Run("failed to get trip", func(t *testing.T) {
tripRepository := tripMock.NewTripRepositoryMock()
userTripRepository := userTripMock.NewUserTripRepositoryMock()
countryRepository := countryMock.NewCountryRepositoryMock()
TransactionRepository := transactionMock.NewTransactionRepositoryMock()
usecase := NewTripUseCase(tripRepository, userTripRepository, countryRepository, TransactionRepository)

ctx := context.TODO()
tripId := 1

trip := entities.NewTrip(tripId, "test-name", 1000, true, 1, "test-description", entities.Note{}, time.Now(), time.Now(), time.Now(), time.Now())
tripRepository.Mock.On("GetTripById", ctx, tripId).Return(trip, nil)
countryRepository.Mock.On("GetCountryById", ctx, trip.CountryID()).Return(&entities.Country{}, nil)
TransactionRepository.Mock.On("GetTransactionByTripId", ctx, tripId).Return(nil, fmt.Errorf("error"))

_, err := usecase.GetTripById(ctx, tripId)
assert.Error(t, err)
tripRepository.AssertExpectations(t)
})
}

func TestDeleteTrip(t *testing.T) {
t.Run("successfully delete trip", func(t *testing.T) {
tripRepository := tripMock.NewTripRepositoryMock()
Expand Down

0 comments on commit f6d998c

Please sign in to comment.