diff --git a/README.md b/README.md index a984e6b..28ca887 100644 --- a/README.md +++ b/README.md @@ -421,7 +421,7 @@ The following basic use cases are to be implemented at the first time. - [X] A user signs up - [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) - [] 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) @@ -430,7 +430,7 @@ The following basic use cases are to be implemented at the first time. - [] A user deletes an expense (may handle some extra access control) - [] A user restore a deleted expense - [] A user can pay the debt to other members -- [X] A user creates an event +- [X] A user creates an event (and participate to it) - [] A user updates the event info - [] A user invites another user by sending a mail with a token. - [] A user joins an event by accepting an invitation @@ -449,3 +449,40 @@ work on other aspects. For example: - ex. Trip journal... Stop dreaming... Just do the simple stuff first! + +### 2024/10/18 + +I spent some time to figure out this one! But I don't actually need it for now. +So I just keep it here: + +```SQL +SELECT + e.id, + e.name, + e.description, + e.created_at, + json_build_object( + 'id', o.id, + 'first_name', o.first_name, + 'last_name', o.last_name + ) AS owner, + json_agg( + json_build_object( + 'id', u.id, + 'first_name', u.first_name, + 'last_name', u.last_name + ) + ) AS users -- Aggregation for users in the event +FROM "event" e +JOIN "participation" p ON p.event_id = e.id -- participation linked with the event +JOIN "user" u ON u.id = p.user_id -- and the query user +JOIN "user" o ON o.id = e.owner_id -- get the owner info +WHERE e.id IN ( + SELECT pt.event_id FROM participation pt WHERE pt.user_id = $1 + -- consider the events participated by user_id +) +GROUP BY + e.id, e.name, e.description, e.created_at, + o.id, o.first_name, o.last_name; + +``` diff --git a/internal/howmuch/adapter/repo/sqlc/event.sql b/internal/howmuch/adapter/repo/sqlc/event.sql new file mode 100644 index 0000000..4d762da --- /dev/null +++ b/internal/howmuch/adapter/repo/sqlc/event.sql @@ -0,0 +1,58 @@ +-- name: InsertEvent :one +INSERT INTO "event" ( + name, description, total_amount, default_currency, owner_id, created_at, updated_at +) VALUES ( $1, $2, $3, $4, $5, $6, $7 ) +RETURNING *; + +-- name: ListEventsByUserID :many +SELECT + e.id, + e.name, + e.description, + e.created_at, + json_build_object( + 'id', o.id, + 'first_name', o.first_name, + 'last_name', o.last_name + ) AS owner +FROM "event" e +JOIN "participation" p ON p.event_id = e.id -- participation linked with the event +JOIN "user" o ON o.id = e.owner_id -- get the owner info +WHERE e.id IN ( + SELECT pt.event_id FROM participation pt WHERE pt.user_id = $1 -- consider the events participated by user_id +) +GROUP BY + e.id, e.name, e.description, e.created_at, + o.id, o.first_name, o.last_name; + +-- name: GetEventByID :one +SELECT + e.id, + e.name, + e.description, + e.total_amount, + e.default_currency, + e.created_at, + e.updated_at, + json_build_object( + 'id', o.id, + 'first_name', o.first_name, + 'last_name', o.last_name + ) AS owner, + json_agg( + json_build_object( + 'id', u.id, + 'first_name', u.first_name, + 'last_name', u.last_name + ) + ) AS users -- Aggregation for users in the event +FROM "event" e +JOIN "participation" p ON p.event_id = e.id -- participation linked with the event +JOIN "user" u ON u.id = p.user_id -- and the query user +JOIN "user" o ON o.id = e.owner_id -- get the owner info +WHERE e.id = $1 +GROUP BY + e.id, e.name, e.description, e.created_at, e.updated_at, + e.total_amount, e.default_currency, + o.id, o.first_name, o.last_name; + diff --git a/internal/howmuch/adapter/repo/sqlc/event.sql.go b/internal/howmuch/adapter/repo/sqlc/event.sql.go new file mode 100644 index 0000000..ec14a9b --- /dev/null +++ b/internal/howmuch/adapter/repo/sqlc/event.sql.go @@ -0,0 +1,174 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: event.sql + +package sqlc + +import ( + "context" + "database/sql" + "encoding/json" + "time" +) + +const getEventByID = `-- name: GetEventByID :one +SELECT + e.id, + e.name, + e.description, + e.total_amount, + e.default_currency, + e.created_at, + e.updated_at, + json_build_object( + 'id', o.id, + 'first_name', o.first_name, + 'last_name', o.last_name + ) AS owner, + json_agg( + json_build_object( + 'id', u.id, + 'first_name', u.first_name, + 'last_name', u.last_name + ) + ) AS users -- Aggregation for users in the event +FROM "event" e +JOIN "participation" p ON p.event_id = e.id -- participation linked with the event +JOIN "user" u ON u.id = p.user_id -- and the query user +JOIN "user" o ON o.id = e.owner_id -- get the owner info +WHERE e.id = $1 +GROUP BY + e.id, e.name, e.description, e.created_at, e.updated_at, + e.total_amount, e.default_currency, + o.id, o.first_name, o.last_name +` + +type GetEventByIDRow struct { + ID int32 + Name string + Description sql.NullString + TotalAmount sql.NullInt32 + DefaultCurrency string + CreatedAt time.Time + UpdatedAt time.Time + Owner json.RawMessage + Users json.RawMessage +} + +func (q *Queries) GetEventByID(ctx context.Context, id int32) (GetEventByIDRow, error) { + row := q.db.QueryRowContext(ctx, getEventByID, id) + var i GetEventByIDRow + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.TotalAmount, + &i.DefaultCurrency, + &i.CreatedAt, + &i.UpdatedAt, + &i.Owner, + &i.Users, + ) + return i, err +} + +const insertEvent = `-- name: InsertEvent :one +INSERT INTO "event" ( + name, description, total_amount, default_currency, owner_id, created_at, updated_at +) VALUES ( $1, $2, $3, $4, $5, $6, $7 ) +RETURNING id, name, description, default_currency, owner_id, created_at, updated_at, total_amount +` + +type InsertEventParams struct { + Name string + Description sql.NullString + TotalAmount sql.NullInt32 + DefaultCurrency string + OwnerID int32 + CreatedAt time.Time + UpdatedAt time.Time +} + +func (q *Queries) InsertEvent(ctx context.Context, arg InsertEventParams) (Event, error) { + row := q.db.QueryRowContext(ctx, insertEvent, + arg.Name, + arg.Description, + arg.TotalAmount, + arg.DefaultCurrency, + arg.OwnerID, + arg.CreatedAt, + arg.UpdatedAt, + ) + var i Event + err := row.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.DefaultCurrency, + &i.OwnerID, + &i.CreatedAt, + &i.UpdatedAt, + &i.TotalAmount, + ) + return i, err +} + +const listEventsByUserID = `-- name: ListEventsByUserID :many +SELECT + e.id, + e.name, + e.description, + e.created_at, + json_build_object( + 'id', o.id, + 'first_name', o.first_name, + 'last_name', o.last_name + ) AS owner +FROM "event" e +JOIN "participation" p ON p.event_id = e.id -- participation linked with the event +JOIN "user" o ON o.id = e.owner_id -- get the owner info +WHERE e.id IN ( + SELECT pt.event_id FROM participation pt WHERE pt.user_id = $1 -- consider the events participated by user_id +) +GROUP BY + e.id, e.name, e.description, e.created_at, + o.id, o.first_name, o.last_name +` + +type ListEventsByUserIDRow struct { + ID int32 + Name string + Description sql.NullString + CreatedAt time.Time + Owner json.RawMessage +} + +func (q *Queries) ListEventsByUserID(ctx context.Context, userID int32) ([]ListEventsByUserIDRow, error) { + rows, err := q.db.QueryContext(ctx, listEventsByUserID, userID) + if err != nil { + return nil, err + } + defer rows.Close() + var items []ListEventsByUserIDRow + for rows.Next() { + var i ListEventsByUserIDRow + if err := rows.Scan( + &i.ID, + &i.Name, + &i.Description, + &i.CreatedAt, + &i.Owner, + ); 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 +} diff --git a/internal/howmuch/adapter/repo/sqlc/participation.sql b/internal/howmuch/adapter/repo/sqlc/participation.sql new file mode 100644 index 0000000..6798a40 --- /dev/null +++ b/internal/howmuch/adapter/repo/sqlc/participation.sql @@ -0,0 +1,5 @@ +-- name: InsertParticipation :one +INSERT INTO participation ( + user_id, event_id, invited_by_user_id, created_at, updated_at +) VALUES ($1, $2, $3, $4, $5) +RETURNING *; diff --git a/internal/howmuch/adapter/repo/sqlc/participation.sql.go b/internal/howmuch/adapter/repo/sqlc/participation.sql.go new file mode 100644 index 0000000..3cc969b --- /dev/null +++ b/internal/howmuch/adapter/repo/sqlc/participation.sql.go @@ -0,0 +1,47 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: participation.sql + +package sqlc + +import ( + "context" + "database/sql" + "time" +) + +const insertParticipation = `-- name: InsertParticipation :one +INSERT INTO participation ( + user_id, event_id, invited_by_user_id, created_at, updated_at +) VALUES ($1, $2, $3, $4, $5) +RETURNING id, user_id, event_id, invited_by_user_id, created_at, updated_at +` + +type InsertParticipationParams struct { + UserID int32 + EventID int32 + InvitedByUserID sql.NullInt32 + CreatedAt time.Time + UpdatedAt time.Time +} + +func (q *Queries) InsertParticipation(ctx context.Context, arg InsertParticipationParams) (Participation, error) { + row := q.db.QueryRowContext(ctx, insertParticipation, + arg.UserID, + arg.EventID, + arg.InvitedByUserID, + arg.CreatedAt, + arg.UpdatedAt, + ) + var i Participation + err := row.Scan( + &i.ID, + &i.UserID, + &i.EventID, + &i.InvitedByUserID, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/migrations/20241018175610_participation_add_unique_keys.postgres.down.sql b/migrations/20241018175610_participation_add_unique_keys.postgres.down.sql new file mode 100644 index 0000000..e69de29 diff --git a/migrations/20241018175610_participation_add_unique_keys.postgres.up.sql b/migrations/20241018175610_participation_add_unique_keys.postgres.up.sql new file mode 100644 index 0000000..e4c8883 --- /dev/null +++ b/migrations/20241018175610_participation_add_unique_keys.postgres.up.sql @@ -0,0 +1,2 @@ +ALTER TABLE "participation" +ADD CONSTRAINT unique_user_event UNIQUE ("user_id", "event_id");