Compare commits
No commits in common. "3d616bff50f5d57e26158801a0aacc22ab65542c" and "80a5f1f8a8cbd1d6081d21bed31ff573abf3c9d1" have entirely different histories.
3d616bff50
...
80a5f1f8a8
64
README.md
64
README.md
@ -421,28 +421,27 @@ The following basic use cases are to be implemented at the first time.
|
|||||||
|
|
||||||
- [X] A user signs up
|
- [X] A user signs up
|
||||||
- [X] A user logs in
|
- [X] A user logs in
|
||||||
- [ ] A user lists their events (pagination)
|
- [X] A user lists their events (pagination)
|
||||||
- [ ] A user sees the detail of an event (description, members, amount)
|
- [X] A user sees the detail of an event (description, members, amount)
|
||||||
- [ ] A user sees the expenses of an event (total amount, personal expenses, pagination)
|
- [] A user sees the expenses of an event (total amount, personal expenses, pagination)
|
||||||
- [ ] A user sees the detail of an expense: (time, amount, payers, recipients)
|
- [] A user sees the detail of an expense: (time, amount, payers, recipients)
|
||||||
- [ ] A user adds an expense
|
- [] A user adds an expense
|
||||||
- [ ] A user updates/changes an expense (may handle some extra access control)
|
- [] A user updates/changes an expense (may handle some extra access control)
|
||||||
- [ ] A user can pay the debt to other members (just a special case of expense)
|
- [] A user deletes an expense (may handle some extra access control)
|
||||||
- [ ] A user creates an event (and participate to it)
|
- [] A user restore a deleted expense
|
||||||
- [ ] A user updates the event info
|
- [] A user can pay the debt to other members
|
||||||
- [ ] A user invites another user by sending a mail with a token.
|
- [X] A user creates an event (and participate to it)
|
||||||
- [ ] A user joins an event by accepting an invitation
|
- [X] A user updates the event info
|
||||||
- [ ] A user cannot see other user's information
|
- [X] A user invites another user by sending a mail with a token.
|
||||||
- [ ] A user cannot see the events that they didn't participated in.
|
- [X] A user joins an event by accepting an invitation
|
||||||
|
- [] ~A user quits an event (they cannot actually, but we can make as if they quitted)~
|
||||||
|
**No we can't quit!**
|
||||||
|
- [] A user cannot see other user's information
|
||||||
|
- [] A user cannot see the events that they didn't participated in.
|
||||||
|
|
||||||
For the second stage:
|
For the second stage:
|
||||||
|
|
||||||
- [ ] A user can archive an event
|
- [] A user can archive an event
|
||||||
- [ ] A user deletes an expense (may handle some extra access control)
|
|
||||||
- [ ] A user restore a deleted expense
|
|
||||||
- [ ] Audit log for expense updates/deletes
|
|
||||||
- [ ] ~A user quits an event (they cannot actually, but we can make as if they
|
|
||||||
quitted)~ **No we can't quit!**
|
|
||||||
|
|
||||||
With those functionalities, there will be an usable product. And then we can
|
With those functionalities, there will be an usable product. And then we can
|
||||||
work on other aspects. For example:
|
work on other aspects. For example:
|
||||||
@ -492,30 +491,3 @@ GROUP BY
|
|||||||
o.id, o.first_name, o.last_name;
|
o.id, o.first_name, o.last_name;
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 2024/10/19
|
|
||||||
|
|
||||||
I don't plan to handle deletions at this first stage, but I note down what I
|
|
||||||
have thought of.
|
|
||||||
|
|
||||||
1. Just delete. But keep a replica at the front end of the object that we are
|
|
||||||
deleting. And propose an option to restore (so a new record is added to the DB)
|
|
||||||
2. Just delete, but wait. The request is sent to a queue with a timeout of
|
|
||||||
several seconds, if the user regrets, they can cancel the request. This can be
|
|
||||||
done on the front, but also on the back. I think it is better to do in on the
|
|
||||||
front-end.
|
|
||||||
3. Never deletes. But keep a state in the DB `deleted`. They will just be
|
|
||||||
ignored when counting.
|
|
||||||
4. Deletes when doing database cleanup. They lines deleted will be processed
|
|
||||||
when we cleanup the DB. And they will be definitely deleted at that time.
|
|
||||||
|
|
||||||
I can create a audit log table to log all the critical
|
|
||||||
changes in my `expense` table (update or delete).
|
|
||||||
|
|
||||||
Finished with the basic SQL commands. Learned a lot from SQL about `JOIN`,
|
|
||||||
aggregation and `CTE`. SQL itself has quite amount of things to learn, this
|
|
||||||
is on my future learning plan!
|
|
||||||
|
|
||||||
_I found it quite interesting that simply with SQL, we can simulate the most
|
|
||||||
business logic. It is a must-have competence for software design and
|
|
||||||
development._
|
|
||||||
|
@ -1,72 +0,0 @@
|
|||||||
-- name: InsertExpense :one
|
|
||||||
INSERT INTO "expense" (
|
|
||||||
created_at, updated_at, amount, currency, event_id, name, place
|
|
||||||
) VALUES ( $1, $2, $3, $4, $5, $6, $7 )
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: DeleteExpense :exec
|
|
||||||
DELETE FROM "expense" WHERE id = $1;
|
|
||||||
|
|
||||||
-- name: DeleteTransactionsOfExpenseID :exec
|
|
||||||
DELETE FROM "transaction" WHERE transaction.expense_id = $1;
|
|
||||||
|
|
||||||
-- name: UpdateExpenseByID :one
|
|
||||||
UPDATE "expense"
|
|
||||||
SET updated_at = $2, amount = $3, currency = $4, name = $5, place = $6
|
|
||||||
WHERE id = $1
|
|
||||||
RETURNING *;
|
|
||||||
|
|
||||||
-- name: ListExpensesByEventID :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: 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,
|
|
||||||
json_agg(json_build_object(
|
|
||||||
'payer_id', p.id,
|
|
||||||
'payer_first_name', p.first_name,
|
|
||||||
'payer_last_name', p.last_name,
|
|
||||||
'amount', pt.amount,
|
|
||||||
'currency', pt.currency
|
|
||||||
)) AS payments
|
|
||||||
FROM "transaction" pt
|
|
||||||
JOIN "user" p ON p.id = pt.user_id
|
|
||||||
WHERE pt.is_income = FALSE
|
|
||||||
GROUP BY pt.expense_id
|
|
||||||
), -- For each expense, aggregate payment info
|
|
||||||
recipient_transaction as (
|
|
||||||
SELECT rt.expense_id,
|
|
||||||
json_agg(json_build_object(
|
|
||||||
'recipient_id', p.id,
|
|
||||||
'recipient_first_name', p.first_name,
|
|
||||||
'recipient_last_name', p.last_name,
|
|
||||||
'amount', rt.amount,
|
|
||||||
'currency', rt.currency
|
|
||||||
)) AS benefits
|
|
||||||
FROM "transaction" rt
|
|
||||||
JOIN "user" p ON p.id = rt.user_id
|
|
||||||
WHERE rt.is_income = TRUE
|
|
||||||
GROUP BY rt.expense_id
|
|
||||||
) -- For each expense, aggregate benefits info
|
|
||||||
SELECT
|
|
||||||
ex.id, ex.created_at, ex.updated_at, ex.amount, ex.currency, ex.event_id,
|
|
||||||
ex.name, ex.place,
|
|
||||||
COALESCE(pt.payments, '[]') AS payments,
|
|
||||||
COALESCE(rt.benefits, '[]') AS benefits
|
|
||||||
FROM "expense" ex
|
|
||||||
LEFT JOIN "payer_transaction" pt ON pt.expense_id = ex.id
|
|
||||||
LEFT JOIN "recipient_transaction" rt ON rt.expense_id = ex.id
|
|
||||||
WHERE ex.id = $1;
|
|
@ -1,264 +0,0 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.27.0
|
|
||||||
// source: expense.sql
|
|
||||||
|
|
||||||
package sqlc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"database/sql"
|
|
||||||
"encoding/json"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const deleteExpense = `-- name: DeleteExpense :exec
|
|
||||||
DELETE FROM "expense" WHERE id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) DeleteExpense(ctx context.Context, id int32) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, deleteExpense, id)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const deleteTransactionsOfExpenseID = `-- name: DeleteTransactionsOfExpenseID :exec
|
|
||||||
DELETE FROM "transaction" WHERE transaction.expense_id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
func (q *Queries) DeleteTransactionsOfExpenseID(ctx context.Context, expenseID int32) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, deleteTransactionsOfExpenseID, expenseID)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
const getExpenseByID = `-- name: GetExpenseByID :one
|
|
||||||
WITH payer_transaction as (
|
|
||||||
SELECT pt.expense_id,
|
|
||||||
json_agg(json_build_object(
|
|
||||||
'payer_id', p.id,
|
|
||||||
'payer_first_name', p.first_name,
|
|
||||||
'payer_last_name', p.last_name,
|
|
||||||
'amount', pt.amount,
|
|
||||||
'currency', pt.currency
|
|
||||||
)) AS payments
|
|
||||||
FROM "transaction" pt
|
|
||||||
JOIN "user" p ON p.id = pt.user_id
|
|
||||||
WHERE pt.is_income = FALSE
|
|
||||||
GROUP BY pt.expense_id
|
|
||||||
), -- For each expense, aggregate payment info
|
|
||||||
recipient_transaction as (
|
|
||||||
SELECT rt.expense_id,
|
|
||||||
json_agg(json_build_object(
|
|
||||||
'recipient_id', p.id,
|
|
||||||
'recipient_first_name', p.first_name,
|
|
||||||
'recipient_last_name', p.last_name,
|
|
||||||
'amount', rt.amount,
|
|
||||||
'currency', rt.currency
|
|
||||||
)) AS benefits
|
|
||||||
FROM "transaction" rt
|
|
||||||
JOIN "user" p ON p.id = rt.user_id
|
|
||||||
WHERE rt.is_income = TRUE
|
|
||||||
GROUP BY rt.expense_id
|
|
||||||
) -- For each expense, aggregate benefits info
|
|
||||||
SELECT
|
|
||||||
ex.id, ex.created_at, ex.updated_at, ex.amount, ex.currency, ex.event_id,
|
|
||||||
ex.name, ex.place,
|
|
||||||
COALESCE(pt.payments, '[]') AS payments,
|
|
||||||
COALESCE(rt.benefits, '[]') AS benefits
|
|
||||||
FROM "expense" ex
|
|
||||||
LEFT JOIN "payer_transaction" pt ON pt.expense_id = ex.id
|
|
||||||
LEFT JOIN "recipient_transaction" rt ON rt.expense_id = ex.id
|
|
||||||
WHERE ex.id = $1
|
|
||||||
`
|
|
||||||
|
|
||||||
type GetExpenseByIDRow struct {
|
|
||||||
ID int32
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
EventID int32
|
|
||||||
Name sql.NullString
|
|
||||||
Place sql.NullString
|
|
||||||
Payments json.RawMessage
|
|
||||||
Benefits json.RawMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) GetExpenseByID(ctx context.Context, id int32) (GetExpenseByIDRow, error) {
|
|
||||||
row := q.db.QueryRowContext(ctx, getExpenseByID, id)
|
|
||||||
var i GetExpenseByIDRow
|
|
||||||
err := row.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.CreatedAt,
|
|
||||||
&i.UpdatedAt,
|
|
||||||
&i.Amount,
|
|
||||||
&i.Currency,
|
|
||||||
&i.EventID,
|
|
||||||
&i.Name,
|
|
||||||
&i.Place,
|
|
||||||
&i.Payments,
|
|
||||||
&i.Benefits,
|
|
||||||
)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const insertExpense = `-- name: InsertExpense :one
|
|
||||||
INSERT INTO "expense" (
|
|
||||||
created_at, updated_at, amount, currency, event_id, name, place
|
|
||||||
) VALUES ( $1, $2, $3, $4, $5, $6, $7 )
|
|
||||||
RETURNING id, created_at, updated_at, amount, currency, event_id, name, place
|
|
||||||
`
|
|
||||||
|
|
||||||
type InsertExpenseParams struct {
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
EventID int32
|
|
||||||
Name sql.NullString
|
|
||||||
Place sql.NullString
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) InsertExpense(ctx context.Context, arg InsertExpenseParams) (Expense, error) {
|
|
||||||
row := q.db.QueryRowContext(ctx, insertExpense,
|
|
||||||
arg.CreatedAt,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.Amount,
|
|
||||||
arg.Currency,
|
|
||||||
arg.EventID,
|
|
||||||
arg.Name,
|
|
||||||
arg.Place,
|
|
||||||
)
|
|
||||||
var i Expense
|
|
||||||
err := row.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.CreatedAt,
|
|
||||||
&i.UpdatedAt,
|
|
||||||
&i.Amount,
|
|
||||||
&i.Currency,
|
|
||||||
&i.EventID,
|
|
||||||
&i.Name,
|
|
||||||
&i.Place,
|
|
||||||
)
|
|
||||||
return i, err
|
|
||||||
}
|
|
||||||
|
|
||||||
const listExpensesByEventID = `-- name: ListExpensesByEventID :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) ListExpensesByEventID(ctx context.Context, id int32) ([]Expense, error) {
|
|
||||||
rows, err := q.db.QueryContext(ctx, listExpensesByEventID, 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 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
|
|
||||||
WHERE id = $1
|
|
||||||
RETURNING id, created_at, updated_at, amount, currency, event_id, name, place
|
|
||||||
`
|
|
||||||
|
|
||||||
type UpdateExpenseByIDParams struct {
|
|
||||||
ID int32
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
Name sql.NullString
|
|
||||||
Place sql.NullString
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) UpdateExpenseByID(ctx context.Context, arg UpdateExpenseByIDParams) (Expense, error) {
|
|
||||||
row := q.db.QueryRowContext(ctx, updateExpenseByID,
|
|
||||||
arg.ID,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.Amount,
|
|
||||||
arg.Currency,
|
|
||||||
arg.Name,
|
|
||||||
arg.Place,
|
|
||||||
)
|
|
||||||
var i Expense
|
|
||||||
err := row.Scan(
|
|
||||||
&i.ID,
|
|
||||||
&i.CreatedAt,
|
|
||||||
&i.UpdatedAt,
|
|
||||||
&i.Amount,
|
|
||||||
&i.Currency,
|
|
||||||
&i.EventID,
|
|
||||||
&i.Name,
|
|
||||||
&i.Place,
|
|
||||||
)
|
|
||||||
return i, err
|
|
||||||
}
|
|
@ -27,17 +27,6 @@ type Event struct {
|
|||||||
TotalAmount sql.NullInt32
|
TotalAmount sql.NullInt32
|
||||||
}
|
}
|
||||||
|
|
||||||
type Expense struct {
|
|
||||||
ID int32
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
EventID int32
|
|
||||||
Name sql.NullString
|
|
||||||
Place sql.NullString
|
|
||||||
}
|
|
||||||
|
|
||||||
type Participation struct {
|
type Participation struct {
|
||||||
ID int32
|
ID int32
|
||||||
UserID int32
|
UserID int32
|
||||||
@ -47,17 +36,6 @@ type Participation struct {
|
|||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
type Transaction struct {
|
|
||||||
ID int32
|
|
||||||
ExpenseID int32
|
|
||||||
UserID int32
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
IsIncome bool
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
ID int32
|
ID int32
|
||||||
Email string
|
Email string
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
-- name: InsertTransaction :exec
|
|
||||||
INSERT INTO "transaction" (
|
|
||||||
created_at, updated_at, amount, currency, expense_id, user_id, is_income
|
|
||||||
) VALUES ( $1, $2, $3, $4, $5, $6, $7 )
|
|
||||||
RETURNING *;
|
|
@ -1,41 +0,0 @@
|
|||||||
// Code generated by sqlc. DO NOT EDIT.
|
|
||||||
// versions:
|
|
||||||
// sqlc v1.27.0
|
|
||||||
// source: transaction.sql
|
|
||||||
|
|
||||||
package sqlc
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
const insertTransaction = `-- name: InsertTransaction :exec
|
|
||||||
INSERT INTO "transaction" (
|
|
||||||
created_at, updated_at, amount, currency, expense_id, user_id, is_income
|
|
||||||
) VALUES ( $1, $2, $3, $4, $5, $6, $7 )
|
|
||||||
RETURNING id, expense_id, user_id, amount, currency, is_income, created_at, updated_at
|
|
||||||
`
|
|
||||||
|
|
||||||
type InsertTransactionParams struct {
|
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
Amount int32
|
|
||||||
Currency string
|
|
||||||
ExpenseID int32
|
|
||||||
UserID int32
|
|
||||||
IsIncome bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (q *Queries) InsertTransaction(ctx context.Context, arg InsertTransactionParams) error {
|
|
||||||
_, err := q.db.ExecContext(ctx, insertTransaction,
|
|
||||||
arg.CreatedAt,
|
|
||||||
arg.UpdatedAt,
|
|
||||||
arg.Amount,
|
|
||||||
arg.Currency,
|
|
||||||
arg.ExpenseID,
|
|
||||||
arg.UserID,
|
|
||||||
arg.IsIncome,
|
|
||||||
)
|
|
||||||
return err
|
|
||||||
}
|
|
@ -24,44 +24,16 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// {{{ Requrest
|
|
||||||
|
|
||||||
type ExpenseRequest struct {
|
type ExpenseRequest struct {
|
||||||
Amount Money `json:"money" binding:"required"`
|
Amount Money `json:"money" binding:"required,number"`
|
||||||
Payments []Payment `json:"payments" binding:"required"`
|
PayerIDs []int `json:"payer_ids" binding:"required"`
|
||||||
Benefits []Benefit `json:"benefits" binding:"required"`
|
RecipientIDs []int `json:"recipient_ids" binding:"required"`
|
||||||
EventID int `json:"event_id" binding:"required"`
|
EventID int `json:"event_id" binding:"required"`
|
||||||
Detail ExpenseDetail `json:"detail"`
|
Detail ExpenseDetail `json:"detail"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
|
||||||
// {{{ Response
|
|
||||||
|
|
||||||
type ExpensesListResponse struct {
|
|
||||||
ID int `json:"id"`
|
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
|
|
||||||
Amount Money `json:"money"`
|
|
||||||
EventID int `json:"event_id"`
|
|
||||||
|
|
||||||
Detail ExpenseDetail `json:"detail"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ExpenseGetResponse Expense
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
// {{{ Retrieved
|
|
||||||
|
|
||||||
type ExpensesListRetrieved ExpensesListResponse
|
|
||||||
|
|
||||||
// }}}
|
|
||||||
// {{{ Entity
|
|
||||||
|
|
||||||
type ExpenseEntity struct {
|
type ExpenseEntity struct {
|
||||||
ID int
|
ID int
|
||||||
CreatedAt time.Time
|
|
||||||
UpdatedAt time.Time
|
|
||||||
|
|
||||||
Amount int
|
Amount int
|
||||||
Currency string
|
Currency string
|
||||||
@ -70,42 +42,30 @@ type ExpenseEntity struct {
|
|||||||
// ExpenseDetail
|
// ExpenseDetail
|
||||||
Name string
|
Name string
|
||||||
Place string
|
Place string
|
||||||
}
|
|
||||||
|
|
||||||
// }}}
|
CreatedAt time.Time
|
||||||
// {{{ Domain Models
|
UpdatedAt time.Time
|
||||||
|
}
|
||||||
|
|
||||||
type ExpenseDetail struct {
|
type ExpenseDetail struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
Place string `json:"place"`
|
Place string `json:"place"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Payment struct {
|
|
||||||
PayerID int `json:"payer_id" binding:"required,number"`
|
|
||||||
PayerFirstName string `json:"payer_first_name"`
|
|
||||||
PayerLastName string `json:"payer_last_name"`
|
|
||||||
Amount Money `json:"amount" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Benefit struct {
|
|
||||||
RecipientID int `json:"recipient_id" binding:"required,number"`
|
|
||||||
RecipientFirstName string `json:"recipient_first_name"`
|
|
||||||
RecipientLastName string `json:"recipient_last_name"`
|
|
||||||
Amount Money `json:"amount" binding:"required"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Expense struct {
|
type Expense struct {
|
||||||
ID int `json:"id"`
|
ID int
|
||||||
CreatedAt time.Time `json:"created_at"`
|
|
||||||
UpdatedAt time.Time `json:"updated_at"`
|
|
||||||
|
|
||||||
Amount Money `json:"money"`
|
Amount Money
|
||||||
EventID int `json:"event_id"`
|
|
||||||
|
|
||||||
Detail ExpenseDetail `json:"detail"`
|
// Lazy aggregate using Transaction join
|
||||||
|
PayerIDs []int
|
||||||
|
|
||||||
Payments []Payment `json:"payments"`
|
// Lazy aggregate using Transaction join
|
||||||
Benefits []Benefit `json:"benefits"`
|
RecipientIDs []int
|
||||||
|
|
||||||
|
EventID int
|
||||||
|
Detail ExpenseDetail
|
||||||
|
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
|
||||||
|
@ -24,25 +24,18 @@ package model
|
|||||||
|
|
||||||
import "time"
|
import "time"
|
||||||
|
|
||||||
// {{{ Entity
|
|
||||||
|
|
||||||
type TransactionEntity Transaction
|
type TransactionEntity Transaction
|
||||||
|
|
||||||
// }}}
|
// Transaction is the association between Expenses and Users
|
||||||
// {{{ Domain object
|
|
||||||
|
|
||||||
type Transaction struct {
|
type Transaction struct {
|
||||||
ID int
|
ID int
|
||||||
|
|
||||||
ExpenseID int
|
ExpenseID Expense
|
||||||
UserID int
|
UserID int
|
||||||
Amount int
|
Amount int
|
||||||
Currency string
|
Currency string
|
||||||
IsIncome bool // To note that the direction of the money (payment or income)
|
IsIncome bool
|
||||||
|
|
||||||
CreatedAt time.Time
|
CreatedAt time.Time
|
||||||
UpdatedAt time.Time
|
UpdatedAt time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// }}}
|
|
||||||
// Transaction is the association between Expenses and Users
|
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE "transaction" (
|
|
||||||
"id" serial NOT NULL,
|
|
||||||
PRIMARY KEY ("id"),
|
|
||||||
"expense_id" integer NOT NULL,
|
|
||||||
"user_id" integer NOT NULL,
|
|
||||||
"amount" integer NOT NULL,
|
|
||||||
"currency" character varying(255) NOT NULL,
|
|
||||||
"is_income" boolean NOT NULL DEFAULT FALSE,
|
|
||||||
"created_at" date NOT NULL,
|
|
||||||
"updated_at" date NOT NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE "transaction"
|
|
||||||
ADD FOREIGN KEY ("user_id") REFERENCES "user" ("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
@ -1 +0,0 @@
|
|||||||
DROP TABLE "expense";
|
|
@ -1,14 +0,0 @@
|
|||||||
CREATE TABLE "expense" (
|
|
||||||
"id" serial NOT NULL,
|
|
||||||
PRIMARY KEY ("id"),
|
|
||||||
"created_at" date NOT NULL,
|
|
||||||
"updated_at" date NOT NULL,
|
|
||||||
"amount" integer NOT NULL,
|
|
||||||
"currency" character varying NOT NULL,
|
|
||||||
"event_id" integer NOT NULL,
|
|
||||||
"name" character varying(255) NULL,
|
|
||||||
"place" character varying(1000) NULL
|
|
||||||
);
|
|
||||||
|
|
||||||
ALTER TABLE "expense"
|
|
||||||
ADD FOREIGN KEY ("event_id") REFERENCES "event" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
@ -1 +0,0 @@
|
|||||||
DROP TABLE transaction;
|
|
@ -1,2 +0,0 @@
|
|||||||
ALTER TABLE "transaction"
|
|
||||||
ADD FOREIGN KEY ("expense_id") REFERENCES "expense" ("id") ON DELETE CASCADE ON UPDATE CASCADE
|
|
Loading…
x
Reference in New Issue
Block a user