feat: implement repo expense methods
All checks were successful
Build and test / Build (push) Successful in 2m28s

This commit is contained in:
Muyao CHEN 2024-10-24 23:39:13 +02:00
parent 58cff774e6
commit b30a5c5c2d
10 changed files with 215 additions and 93 deletions

View File

@ -35,7 +35,7 @@ import (
) )
type eventRepository struct { type eventRepository struct {
queries sqlc.Querier queries *sqlc.Queries
} }
func NewEventRepository(db *sql.DB) repo.EventRepository { func NewEventRepository(db *sql.DB) repo.EventRepository {

View File

@ -1,13 +1,75 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// 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 package repo
import ( import (
"context"
"database/sql"
"encoding/json" "encoding/json"
"time"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/adapter/repo/sqlc" "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/model"
"git.vinchent.xyz/vinchent/howmuch/internal/howmuch/usecase/repo"
"git.vinchent.xyz/vinchent/howmuch/internal/pkg/log" "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) { func convToPayments(raw json.RawMessage) ([]model.Payment, error) {
var paymentsRetrieved []model.PaymentRetrieved var paymentsRetrieved []model.PaymentRetrieved
err := json.Unmarshal(raw, &paymentsRetrieved) err := json.Unmarshal(raw, &paymentsRetrieved)
@ -83,3 +145,87 @@ func convToExpenseRetrieved(expenseDTO *sqlc.GetExpenseByIDRow) (*model.ExpenseR
return expenseRetrieved, nil 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
}

View File

@ -1,3 +1,25 @@
// MIT License
//
// Copyright (c) 2024 vinchent <vinchent@vinchent.xyz>
//
// 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 package repo
import ( import (

View File

@ -24,14 +24,6 @@ FROM "expense" ex
JOIN "event" ev ON ev.id = ex.event_id JOIN "event" ev ON ev.id = ex.event_id
WHERE ev.id = $1; 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 -- name: GetExpenseByID :one
WITH payer_transaction as ( WITH payer_transaction as (
SELECT pt.expense_id, SELECT pt.expense_id,

View File

@ -183,47 +183,6 @@ func (q *Queries) ListExpensesByEventID(ctx context.Context, id int32) ([]Expens
return items, nil 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 const updateExpenseByID = `-- name: UpdateExpenseByID :one
UPDATE "expense" UPDATE "expense"
SET updated_at = $2, amount = $3, currency = $4, name = $5, place = $6 SET updated_at = $2, amount = $3, currency = $4, name = $5, place = $6

View File

@ -22,7 +22,6 @@ type Querier interface {
InsertUser(ctx context.Context, arg InsertUserParams) (User, error) InsertUser(ctx context.Context, arg InsertUserParams) (User, error)
ListEventsByUserID(ctx context.Context, userID int32) ([]ListEventsByUserIDRow, error) ListEventsByUserID(ctx context.Context, userID int32) ([]ListEventsByUserIDRow, error)
ListExpensesByEventID(ctx context.Context, id int32) ([]Expense, error) ListExpensesByEventID(ctx context.Context, id int32) ([]Expense, error)
ListExpensesByEventIDByUserID(ctx context.Context, id int32) ([]Expense, error)
UpdateEventByID(ctx context.Context, arg UpdateEventByIDParams) error UpdateEventByID(ctx context.Context, arg UpdateEventByIDParams) error
UpdateExpenseByID(ctx context.Context, arg UpdateExpenseByIDParams) (Expense, error) UpdateExpenseByID(ctx context.Context, arg UpdateExpenseByIDParams) (Expense, error)
} }

View File

@ -34,7 +34,7 @@ import (
) )
type userRepository struct { type userRepository struct {
querier sqlc.Querier querier *sqlc.Queries
} }
const insertTimeout = 1 * time.Second const insertTimeout = 1 * time.Second

View File

@ -37,7 +37,17 @@ type ExpenseRequest struct {
// }}} // }}}
// {{{ Response // {{{ Response
type ExpensesListResponse struct { type (
ExpenseGetResponse Expense
ExpenseResponse ExpenseRetrieved
)
// }}}
// {{{ Retrieved
type ExpenseRetrieved Expense
type ExpensesListRetrieved struct {
ID int `json:"id"` ID int `json:"id"`
CreatedAt time.Time `json:"created_at"` CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"` UpdatedAt time.Time `json:"updated_at"`
@ -48,16 +58,6 @@ type ExpensesListResponse struct {
Detail ExpenseDetail `json:"detail"` Detail ExpenseDetail `json:"detail"`
} }
type ExpenseGetResponse Expense
// }}}
// {{{ Retrieved
type (
ExpensesListRetrieved ExpensesListResponse
ExpenseRetrieved Expense
)
type PaymentRetrieved struct { type PaymentRetrieved struct {
PayerID int `json:"payer_id"` PayerID int `json:"payer_id"`
PayerFirstName string `json:"payer_first_name"` PayerFirstName string `json:"payer_first_name"`
@ -91,6 +91,18 @@ type ExpenseEntity struct {
Place string Place string
} }
type ExpenseUpdateEntity struct {
ID int
UpdatedAt time.Time
Amount int
Currency string
// Expense Detail
Name string
Place string
}
// }}} // }}}
// {{{ Domain Models // {{{ Domain Models

View File

@ -38,25 +38,21 @@ type EventRepository interface {
// related to events of a user // related to events of a user
ListEventsByUserID(ctx context.Context, userID int) ([]model.EventListRetrieved, error) ListEventsByUserID(ctx context.Context, userID int) ([]model.EventListRetrieved, error)
// CheckParticipation(ctx context.Context, userID, eventID int) error
} }
type ExpenseRepository interface { type ExpenseRepository interface {
Create() DeleteExpense(ctx context.Context, expenseID int) error
Update() DeleteTransactionsOfExpense(ctx context.Context, expenseID int) error
Delete() // Delete also the related transactions GetExpenseByID(ctx context.Context, expenseID int) (*model.ExpenseRetrieved, error)
InsertExpense(
ListExpensesByUserID() ctx context.Context,
GetByID() expenseEntity *model.ExpenseEntity,
} ) (*model.ExpenseEntity, error)
ListExpensesByEventID(ctx context.Context, id int) ([]model.ExpensesListRetrieved, error)
type ParticipationRepository interface { UpdateExpenseByID(
Create() ctx context.Context,
Delete() expenseUpdate *model.ExpenseUpdateEntity,
CheckParticipation(ctx context.Context, userID, eventID int) error ) (*model.ExpenseEntity, error)
}
type TransactionRepository interface {
Create()
// Delete() might be handled in the Expense
// Transaction is a joined entity, we don't provide diret read operation
} }

View File

@ -33,11 +33,9 @@ import (
) )
type eventUsecase struct { type eventUsecase struct {
userUC User userUC User
eventRepo repo.EventRepository eventRepo repo.EventRepository
expenseRepo repo.ExpenseRepository expenseRepo repo.ExpenseRepository
participationRepo repo.ParticipationRepository
transactionRepo repo.TransactionRepository
dbRepo repo.DBRepository dbRepo repo.DBRepository
} }
@ -55,11 +53,9 @@ func NewEventUsecase(
uuc User, uuc User,
ev repo.EventRepository, ev repo.EventRepository,
ex repo.ExpenseRepository, ex repo.ExpenseRepository,
pa repo.ParticipationRepository, // XXX: Might be handled in event
tr repo.TransactionRepository, // XXX: Might be handled in event
db repo.DBRepository, db repo.DBRepository,
) Event { ) Event {
return &eventUsecase{uuc, ev, ex, pa, tr, db} return &eventUsecase{uuc, ev, ex, db}
} }
func (evuc *eventUsecase) CreateEvent( func (evuc *eventUsecase) CreateEvent(
@ -133,10 +129,10 @@ func (evuc *eventUsecase) GetEventDetail(
userID, eventID int, userID, eventID int,
) (*model.EventInfoResponse, error) { ) (*model.EventInfoResponse, error) {
// Check if the user has the right to get this event // Check if the user has the right to get this event
err := evuc.participationRepo.CheckParticipation(ctx, userID, eventID) // err := evuc.participationRepo.CheckParticipation(ctx, userID, eventID)
if err != nil { // if err != nil {
return nil, ErrNoParticipation // return nil, ErrNoParticipation
} // }
// Get the eventDetail // Get the eventDetail
// TODO: This can also be put into the cache // TODO: This can also be put into the cache