From b30a5c5c2d0c0bbbb3ecef36a13ff9a0941099da Mon Sep 17 00:00:00 2001 From: Muyao CHEN Date: Thu, 24 Oct 2024 23:39:13 +0200 Subject: [PATCH] feat: implement repo expense methods --- internal/howmuch/adapter/repo/event.go | 2 +- internal/howmuch/adapter/repo/expense.go | 146 ++++++++++++++++++ internal/howmuch/adapter/repo/expense_test.go | 22 +++ .../howmuch/adapter/repo/sqlc/expense.sql | 8 - .../howmuch/adapter/repo/sqlc/expense.sql.go | 41 ----- internal/howmuch/adapter/repo/sqlc/querier.go | 1 - internal/howmuch/adapter/repo/user.go | 2 +- internal/howmuch/model/expense.go | 34 ++-- internal/howmuch/usecase/repo/event.go | 32 ++-- internal/howmuch/usecase/usecase/event.go | 20 +-- 10 files changed, 215 insertions(+), 93 deletions(-) diff --git a/internal/howmuch/adapter/repo/event.go b/internal/howmuch/adapter/repo/event.go index dbc0689..d36f7ac 100644 --- a/internal/howmuch/adapter/repo/event.go +++ b/internal/howmuch/adapter/repo/event.go @@ -35,7 +35,7 @@ import ( ) type eventRepository struct { - queries sqlc.Querier + queries *sqlc.Queries } func NewEventRepository(db *sql.DB) repo.EventRepository { diff --git a/internal/howmuch/adapter/repo/expense.go b/internal/howmuch/adapter/repo/expense.go index ea59a5b..c846eba 100644 --- a/internal/howmuch/adapter/repo/expense.go +++ b/internal/howmuch/adapter/repo/expense.go @@ -1,13 +1,75 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package repo import ( + "context" + "database/sql" "encoding/json" + "time" "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo/sqlc" "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/model" + "git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo" "git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" ) +type expenseRepository struct { + queries *sqlc.Queries +} + +func NewExpenseRepository(db *sql.DB) repo.ExpenseRepository { + return &expenseRepository{ + queries: sqlc.New(db), + } +} + +// DeleteExpense implements repo.ExpenseRepository. +func (e *expenseRepository) DeleteExpense(ctx context.Context, expenseID int) error { + return e.queries.DeleteExpense(ctx, int32(expenseID)) +} + +// DeleteTransactionsOfExpense implements repo.ExpenseRepository. +func (e *expenseRepository) DeleteTransactionsOfExpense(ctx context.Context, expenseID int) error { + return e.queries.DeleteTransactionsOfExpenseID(ctx, int32(expenseID)) +} + +// GetExpenseByID implements repo.ExpenseRepository. +func (e *expenseRepository) GetExpenseByID( + ctx context.Context, + expenseID int, +) (*model.ExpenseRetrieved, error) { + expenseDTO, err := e.queries.GetExpenseByID(ctx, int32(expenseID)) + if err != nil { + return nil, err + } + expense, err := convToExpenseRetrieved(&expenseDTO) + if err != nil { + return nil, err + } + return expense, nil +} + func convToPayments(raw json.RawMessage) ([]model.Payment, error) { var paymentsRetrieved []model.PaymentRetrieved err := json.Unmarshal(raw, &paymentsRetrieved) @@ -83,3 +145,87 @@ func convToExpenseRetrieved(expenseDTO *sqlc.GetExpenseByIDRow) (*model.ExpenseR return expenseRetrieved, nil } + +// InsertExpense implements repo.ExpenseRepository. +func (e *expenseRepository) InsertExpense( + ctx context.Context, + expenseEntity *model.ExpenseEntity, +) (*model.ExpenseEntity, error) { + expenseDTO, err := e.queries.InsertExpense(ctx, sqlc.InsertExpenseParams{ + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + Amount: int32(expenseEntity.Amount), + Currency: expenseEntity.Currency, + EventID: int32(expenseEntity.EventID), + Name: sql.NullString{String: expenseEntity.Name, Valid: true}, + Place: sql.NullString{String: expenseEntity.Place, Valid: true}, + }) + if err != nil { + return nil, err + } + return &model.ExpenseEntity{ + ID: int(expenseDTO.ID), + CreatedAt: expenseDTO.CreatedAt, + UpdatedAt: expenseDTO.CreatedAt, + Amount: int(expenseDTO.Amount), + Currency: expenseDTO.Currency, + EventID: int(expenseDTO.EventID), + Name: expenseDTO.Name.String, + Place: expenseDTO.Place.String, + }, nil +} + +// ListExpensesByEventID implements repo.ExpenseRepository. +func (e *expenseRepository) ListExpensesByEventID( + ctx context.Context, + id int, +) ([]model.ExpensesListRetrieved, error) { + listDTO, err := e.queries.ListExpensesByEventID(ctx, int32(id)) + if err != nil { + return nil, err + } + var res []model.ExpensesListRetrieved + for _, dto := range listDTO { + elem := model.ExpensesListRetrieved{ + ID: int(dto.ID), + CreatedAt: dto.CreatedAt, + UpdatedAt: dto.UpdatedAt, + Amount: model.MakeMoney(int(dto.Amount), model.Currency(dto.Currency)), + EventID: int(dto.EventID), + Detail: model.ExpenseDetail{ + Name: dto.Name.String, + Place: dto.Place.String, + }, + } + res = append(res, elem) + } + return res, nil +} + +// UpdateExpenseByID implements repo.ExpenseRepository. +func (e *expenseRepository) UpdateExpenseByID( + ctx context.Context, + expenseUpdate *model.ExpenseUpdateEntity, +) (*model.ExpenseEntity, error) { + expenseDTO, err := e.queries.UpdateExpenseByID(ctx, sqlc.UpdateExpenseByIDParams{ + ID: int32(expenseUpdate.ID), + UpdatedAt: time.Now(), + Amount: int32(expenseUpdate.Amount), + Currency: expenseUpdate.Currency, + Name: sql.NullString{String: expenseUpdate.Name, Valid: true}, + Place: sql.NullString{String: expenseUpdate.Place, Valid: true}, + }) + if err != nil { + return nil, err + } + return &model.ExpenseEntity{ + ID: int(expenseDTO.ID), + CreatedAt: expenseDTO.CreatedAt, + UpdatedAt: expenseDTO.CreatedAt, + Amount: int(expenseDTO.Amount), + Currency: expenseDTO.Currency, + EventID: int(expenseDTO.EventID), + Name: expenseDTO.Name.String, + Place: expenseDTO.Place.String, + }, nil +} diff --git a/internal/howmuch/adapter/repo/expense_test.go b/internal/howmuch/adapter/repo/expense_test.go index 438617b..3e02be3 100644 --- a/internal/howmuch/adapter/repo/expense_test.go +++ b/internal/howmuch/adapter/repo/expense_test.go @@ -1,3 +1,25 @@ +// MIT License +// +// Copyright (c) 2024 vinchent +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + package repo import ( diff --git a/internal/howmuch/adapter/repo/sqlc/expense.sql b/internal/howmuch/adapter/repo/sqlc/expense.sql index 40d42ba..d700f60 100644 --- a/internal/howmuch/adapter/repo/sqlc/expense.sql +++ b/internal/howmuch/adapter/repo/sqlc/expense.sql @@ -24,14 +24,6 @@ FROM "expense" ex JOIN "event" ev ON ev.id = ex.event_id WHERE ev.id = $1; --- name: ListExpensesByEventIDByUserID :many -SELECT - ex.id, ex.created_at, ex.updated_at, ex.amount, ex.currency, ex.event_id, - ex.name, ex.place -FROM "expense" ex -JOIN "event" ev ON ev.id = ex.event_id -WHERE ev.id = $1; - -- name: GetExpenseByID :one WITH payer_transaction as ( SELECT pt.expense_id, diff --git a/internal/howmuch/adapter/repo/sqlc/expense.sql.go b/internal/howmuch/adapter/repo/sqlc/expense.sql.go index 65d8dc3..712346c 100644 --- a/internal/howmuch/adapter/repo/sqlc/expense.sql.go +++ b/internal/howmuch/adapter/repo/sqlc/expense.sql.go @@ -183,47 +183,6 @@ func (q *Queries) ListExpensesByEventID(ctx context.Context, id int32) ([]Expens return items, nil } -const listExpensesByEventIDByUserID = `-- name: ListExpensesByEventIDByUserID :many -SELECT - ex.id, ex.created_at, ex.updated_at, ex.amount, ex.currency, ex.event_id, - ex.name, ex.place -FROM "expense" ex -JOIN "event" ev ON ev.id = ex.event_id -WHERE ev.id = $1 -` - -func (q *Queries) ListExpensesByEventIDByUserID(ctx context.Context, id int32) ([]Expense, error) { - rows, err := q.db.QueryContext(ctx, listExpensesByEventIDByUserID, id) - if err != nil { - return nil, err - } - defer rows.Close() - var items []Expense - for rows.Next() { - var i Expense - if err := rows.Scan( - &i.ID, - &i.CreatedAt, - &i.UpdatedAt, - &i.Amount, - &i.Currency, - &i.EventID, - &i.Name, - &i.Place, - ); 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 updateExpenseByID = `-- name: UpdateExpenseByID :one UPDATE "expense" SET updated_at = $2, amount = $3, currency = $4, name = $5, place = $6 diff --git a/internal/howmuch/adapter/repo/sqlc/querier.go b/internal/howmuch/adapter/repo/sqlc/querier.go index 601b36f..4a7161b 100644 --- a/internal/howmuch/adapter/repo/sqlc/querier.go +++ b/internal/howmuch/adapter/repo/sqlc/querier.go @@ -22,7 +22,6 @@ type Querier interface { InsertUser(ctx context.Context, arg InsertUserParams) (User, error) ListEventsByUserID(ctx context.Context, userID int32) ([]ListEventsByUserIDRow, error) ListExpensesByEventID(ctx context.Context, id int32) ([]Expense, error) - ListExpensesByEventIDByUserID(ctx context.Context, id int32) ([]Expense, error) UpdateEventByID(ctx context.Context, arg UpdateEventByIDParams) error UpdateExpenseByID(ctx context.Context, arg UpdateExpenseByIDParams) (Expense, error) } diff --git a/internal/howmuch/adapter/repo/user.go b/internal/howmuch/adapter/repo/user.go index 3528933..8dc5100 100644 --- a/internal/howmuch/adapter/repo/user.go +++ b/internal/howmuch/adapter/repo/user.go @@ -34,7 +34,7 @@ import ( ) type userRepository struct { - querier sqlc.Querier + querier *sqlc.Queries } const insertTimeout = 1 * time.Second diff --git a/internal/howmuch/model/expense.go b/internal/howmuch/model/expense.go index 53b17e4..b1ba163 100644 --- a/internal/howmuch/model/expense.go +++ b/internal/howmuch/model/expense.go @@ -37,7 +37,17 @@ type ExpenseRequest struct { // }}} // {{{ Response -type ExpensesListResponse struct { +type ( + ExpenseGetResponse Expense + ExpenseResponse ExpenseRetrieved +) + +// }}} +// {{{ Retrieved + +type ExpenseRetrieved Expense + +type ExpensesListRetrieved struct { ID int `json:"id"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -48,16 +58,6 @@ type ExpensesListResponse struct { Detail ExpenseDetail `json:"detail"` } -type ExpenseGetResponse Expense - -// }}} -// {{{ Retrieved - -type ( - ExpensesListRetrieved ExpensesListResponse - ExpenseRetrieved Expense -) - type PaymentRetrieved struct { PayerID int `json:"payer_id"` PayerFirstName string `json:"payer_first_name"` @@ -91,6 +91,18 @@ type ExpenseEntity struct { Place string } +type ExpenseUpdateEntity struct { + ID int + UpdatedAt time.Time + + Amount int + Currency string + + // Expense Detail + Name string + Place string +} + // }}} // {{{ Domain Models diff --git a/internal/howmuch/usecase/repo/event.go b/internal/howmuch/usecase/repo/event.go index 3c542c8..fbf69af 100644 --- a/internal/howmuch/usecase/repo/event.go +++ b/internal/howmuch/usecase/repo/event.go @@ -38,25 +38,21 @@ type EventRepository interface { // related to events of a user ListEventsByUserID(ctx context.Context, userID int) ([]model.EventListRetrieved, error) + + // CheckParticipation(ctx context.Context, userID, eventID int) error } type ExpenseRepository interface { - Create() - Update() - Delete() // Delete also the related transactions - - ListExpensesByUserID() - GetByID() -} - -type ParticipationRepository interface { - Create() - Delete() - CheckParticipation(ctx context.Context, userID, eventID int) error -} - -type TransactionRepository interface { - Create() - // Delete() might be handled in the Expense - // Transaction is a joined entity, we don't provide diret read operation + DeleteExpense(ctx context.Context, expenseID int) error + DeleteTransactionsOfExpense(ctx context.Context, expenseID int) error + GetExpenseByID(ctx context.Context, expenseID int) (*model.ExpenseRetrieved, error) + InsertExpense( + ctx context.Context, + expenseEntity *model.ExpenseEntity, + ) (*model.ExpenseEntity, error) + ListExpensesByEventID(ctx context.Context, id int) ([]model.ExpensesListRetrieved, error) + UpdateExpenseByID( + ctx context.Context, + expenseUpdate *model.ExpenseUpdateEntity, + ) (*model.ExpenseEntity, error) } diff --git a/internal/howmuch/usecase/usecase/event.go b/internal/howmuch/usecase/usecase/event.go index 35f66fe..8a1c049 100644 --- a/internal/howmuch/usecase/usecase/event.go +++ b/internal/howmuch/usecase/usecase/event.go @@ -33,11 +33,9 @@ import ( ) type eventUsecase struct { - userUC User - eventRepo repo.EventRepository - expenseRepo repo.ExpenseRepository - participationRepo repo.ParticipationRepository - transactionRepo repo.TransactionRepository + userUC User + eventRepo repo.EventRepository + expenseRepo repo.ExpenseRepository dbRepo repo.DBRepository } @@ -55,11 +53,9 @@ func NewEventUsecase( uuc User, ev repo.EventRepository, ex repo.ExpenseRepository, - pa repo.ParticipationRepository, // XXX: Might be handled in event - tr repo.TransactionRepository, // XXX: Might be handled in event db repo.DBRepository, ) Event { - return &eventUsecase{uuc, ev, ex, pa, tr, db} + return &eventUsecase{uuc, ev, ex, db} } func (evuc *eventUsecase) CreateEvent( @@ -133,10 +129,10 @@ func (evuc *eventUsecase) GetEventDetail( userID, eventID int, ) (*model.EventInfoResponse, error) { // Check if the user has the right to get this event - err := evuc.participationRepo.CheckParticipation(ctx, userID, eventID) - if err != nil { - return nil, ErrNoParticipation - } + // err := evuc.participationRepo.CheckParticipation(ctx, userID, eventID) + // if err != nil { + // return nil, ErrNoParticipation + // } // Get the eventDetail // TODO: This can also be put into the cache