From 1d5a81e2ff4b767848abe6cc1d4ca9e56df9e344 Mon Sep 17 00:00:00 2001 From: Christie Date: Wed, 27 Nov 2024 17:12:59 +0200 Subject: [PATCH 1/3] [server+web] Add group overview route #4 --- openapi.yml | 13 +++--- server/main.go | 64 +++++++++++++++++++++++++++- server/query.sql | 11 +++++ web/src/routes/g/[slug]/+page.svelte | 16 ++++--- web/src/routes/g/[slug]/+page.ts | 28 ++++++------ 5 files changed, 105 insertions(+), 27 deletions(-) diff --git a/openapi.yml b/openapi.yml index 250a86d..228a770 100644 --- a/openapi.yml +++ b/openapi.yml @@ -8,7 +8,7 @@ servers: security: # This makes every route by default auth protected - BearerAuthHeader: [] # Authorization header - - BearerAuthCookie: [] # Token in cookie + - BearerAuthCookie: [] # Token in cookie components: securitySchemes: BearerAuthHeader: @@ -59,15 +59,16 @@ components: type: string members: type: array - $ref: "#/components/schemas/Member" + items: + $ref: "#/components/schemas/Member" items: type: array - $ref: "#/components/schemas/Item" + items: + $ref: "#/components/schemas/Item" money_balance: type: integer required: - name - - members - money_balance requestBodies: register: @@ -93,7 +94,7 @@ components: content: application/json: schema: - $ref: '#/components/schemas/Item' + $ref: "#/components/schemas/Item" schemas: UserCredentials: type: object @@ -127,7 +128,7 @@ components: properties: id: type: integer - # readOnly: true # TODO unfortunately ogen doesn't support readOnly marker. But we want to reuse this schema for posting + # readOnly: true # TODO unfortunately ogen doesn't support readOnly marker. But we want to reuse this schema for posting # as well as getting for simplicity. So for now, the convention would be to set the id to 0 as it will get ignored either way. timestamp: type: integer diff --git a/server/main.go b/server/main.go index b1d4125..b1eda58 100644 --- a/server/main.go +++ b/server/main.go @@ -3,6 +3,7 @@ package main import ( "context" "database/sql" + "fmt" "log" "net/http" "os" @@ -71,8 +72,67 @@ func (h *Handler) GroupsNonauthedGet(ctx context.Context) ([]api.Group, error) { } func (h *Handler) GroupsIDGet(ctx context.Context, params api.GroupsIDGetParams) (*api.GroupOverview, error) { - // TODO task #4 - return nil, nil + //IDs + g_id := int64(params.ID) + userid := ctx.Value("user_id").(int64) + + //authorID + group, _ := qs.GetGroupByID(ctx, g_id) + + // Fetch members of the group + members, _ := qs.GetMembersOfGroup(ctx, g_id) + + var apiMembers []api.Member + + for _, member := range members { + apiMembers = append(apiMembers, api.Member{ + ID: string(member.ID), + Name: member.Username, + }) + } + + // Fetch items of the group + items, _ := qs.GetItemsOfGroup(ctx, g_id) + + var apiItems []api.Item + + for _, item := range items { + apiItems = append(apiItems, api.Item{ + ID: int(item.ID), + Timestamp: int(item.Timestamp), + Name: item.Name, + Price: item.Price, + AuthorID: int(item.AuthorID), + GroupID: int(g_id), + }) + } + + // Calculate money balance for the group + netAmountParams := data.GetNetAmountForUserInGroupParams{ + AuthorID: userid, + GroupID: g_id, + } + + netAmount, err := qs.GetNetAmountForUserInGroup(ctx, netAmountParams) + if err != nil { + return nil, fmt.Errorf("failed to fetch net amount: %w", err) + } + var balance int + + if netAmount.Valid { + balance = int(netAmount.Float64) + } else { + balance = 0 + } + + groupOverview := &api.GroupOverview{ + Name: group.Name, + Members: apiMembers, + Items: apiItems, + MoneyBalance: balance, + } + + return groupOverview, nil } func (h *Handler) GroupsIDMembersPost(ctx context.Context, req *api.GroupsIDMembersPostReq, params api.GroupsIDMembersPostParams) error { diff --git a/server/query.sql b/server/query.sql index ae09b19..a5f2c89 100644 --- a/server/query.sql +++ b/server/query.sql @@ -42,3 +42,14 @@ FROM member_groups mg1 INNER JOIN member_groups mg2 ON mg1.group_id = mg2.group_id WHERE mg1.member_id = ? AND mg2.member_id <> 3 GROUP BY mg2.member_id; + + +-- name: GetGroupByID :one +SELECT + id, + name +FROM + groups +WHERE + id = ?; + diff --git a/web/src/routes/g/[slug]/+page.svelte b/web/src/routes/g/[slug]/+page.svelte index 0c08425..2779a5a 100644 --- a/web/src/routes/g/[slug]/+page.svelte +++ b/web/src/routes/g/[slug]/+page.svelte @@ -6,12 +6,12 @@ import shoppingBagIcon from "@ktibow/iconset-material-symbols/shopping-bag"; import priceIcon from "@ktibow/iconset-material-symbols/price-check"; import check from "@ktibow/iconset-material-symbols/check"; - import { PUBLIC_SERVER_URL } from "$env/static/public"; + import { PUBLIC_SERVER_URL } from "$env/static/public"; export let data; let name = ""; let price = ""; - let addItem = (e : Event) => { + let addItem = (e: Event) => { e.preventDefault(); const item = { id: 14, @@ -24,12 +24,10 @@ method: "POST", body: JSON.stringify(item), }).then((_) => { - data.items = [item, ...data.items] - + data.items = [item, ...data.items]; }); }; let open = true; -
@@ -68,6 +66,12 @@
+
+

+ Balance: + {data.balance} +

+
{/if} @@ -78,7 +82,7 @@ display: flex; align-items: flex-start; height: 100%; - gap:1rem; + gap: 1rem; } .contents { padding: 1rem; diff --git a/web/src/routes/g/[slug]/+page.ts b/web/src/routes/g/[slug]/+page.ts index 283fc62..3222f1b 100644 --- a/web/src/routes/g/[slug]/+page.ts +++ b/web/src/routes/g/[slug]/+page.ts @@ -1,16 +1,18 @@ -import api from '$lib/api/api'; -import { error as errorOut } from '@sveltejs/kit'; +import api from "$lib/api/api"; +import { error as errorOut } from "@sveltejs/kit"; export async function load({ params }) { - const { - data, // only present if 2XX response - error, // only present if 4XX or 5XX response - response - } = await api.GET("/groups/{id}/items", {params:{path: { id: params.slug}}}) - if (response.status != 200) { - errorOut(response.status, response.statusText); - } - - return { group_id: params.slug, items: data } - + const { + data, // only present if 2XX response + error, + response, + } = await api.GET("/groups/{id}", { params: { path: { id: params.slug } } }); + if (response.status != 200) { + errorOut(response.status, response.statusText); + } + return { + items: data!.items, + group_id: params.slug, + balance: data?.money_balance, + }; } From 1b2728be0957da95ec43f00b4f43b36c9bcd3be3 Mon Sep 17 00:00:00 2001 From: Christie Date: Wed, 27 Nov 2024 18:43:53 +0200 Subject: [PATCH 2/3] [server] expense participants server adjustments #11 --- openapi.yml | 8 +++++++- server/main.go | 15 ++++++++++++++- server/query.sql | 11 +++++++++++ server/schema.sql | 8 ++++++++ 4 files changed, 40 insertions(+), 2 deletions(-) diff --git a/openapi.yml b/openapi.yml index 228a770..3e49d44 100644 --- a/openapi.yml +++ b/openapi.yml @@ -143,6 +143,12 @@ components: type: integer reimbursement: type: boolean + # New participant list should be returned from expense_participants schema + participants: + type: array + items: + $ref: "#/components/schemas/Member" + required: - id - timestamp @@ -154,7 +160,7 @@ components: type: object properties: id: - type: string + type: integer name: type: string displayName: diff --git a/server/main.go b/server/main.go index b1eda58..a6a5e87 100644 --- a/server/main.go +++ b/server/main.go @@ -86,11 +86,22 @@ func (h *Handler) GroupsIDGet(ctx context.Context, params api.GroupsIDGetParams) for _, member := range members { apiMembers = append(apiMembers, api.Member{ - ID: string(member.ID), + ID: int(member.ID), Name: member.Username, }) } + // new schema EP added converting to of type memeber th query of Expense_participants + eps, _ := qs.Expense_participants(ctx, 1) + var ep_members []api.Member + + for _, ep := range eps { + ep_members = append(ep_members, api.Member{ + ID: int(ep.MemberID), + Name: ep.Username, + DisplayName: ep.Displayname, + }) + } // Fetch items of the group items, _ := qs.GetItemsOfGroup(ctx, g_id) @@ -104,6 +115,8 @@ func (h *Handler) GroupsIDGet(ctx context.Context, params api.GroupsIDGetParams) Price: item.Price, AuthorID: int(item.AuthorID), GroupID: int(g_id), + // The new participant list!!!!!! + Participants: ep_members, }) } diff --git a/server/query.sql b/server/query.sql index a5f2c89..7c03d51 100644 --- a/server/query.sql +++ b/server/query.sql @@ -53,3 +53,14 @@ FROM WHERE id = ?; +-- name: Expense_participants :many +SELECT + ep.member_id, + i.name AS item_name, + m.username, + m.displayName +FROM expense_participants ep +JOIN items i ON ep.item_id = i.id +JOIN members m ON ep.member_id = m.id +WHERE ep.item_id = ?; + diff --git a/server/schema.sql b/server/schema.sql index 2c1aa03..bf4d42d 100644 --- a/server/schema.sql +++ b/server/schema.sql @@ -28,3 +28,11 @@ CREATE TABLE items ( --bs: medium FOREIGN KEY (group_id) REFERENCES groups (id), FOREIGN KEY (author_id) REFERENCES members (id) ); + +CREATE TABLE expense_participants ( + item_id INTEGER NOT NULL, -- Foreign key referencing the items table + member_id INTEGER NOT NULL, -- Foreign key referencing the members table + PRIMARY KEY (item_id, member_id), -- Composite primary key (ensures unique pairs) + FOREIGN KEY (item_id) REFERENCES items (id), -- Foreign key constraint to items table + FOREIGN KEY (member_id) REFERENCES members (id) -- Foreign key constraint to members table +); From c93dd67aa77c37e96959d8f625bbfc711a2d68cf Mon Sep 17 00:00:00 2001 From: christienetto Date: Fri, 7 Mar 2025 16:16:07 +0200 Subject: [PATCH 3/3] [Server] Expense participants in GroupsIDItemsPost #38 When posting Items, the items schema has a participants memeber struct list which is also uploaded --- server/main.go | 23 +++++++++++++++++++++-- server/query.sql | 4 ++++ server/schema.sql | 6 +++--- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/server/main.go b/server/main.go index a6a5e87..a03b6d1 100644 --- a/server/main.go +++ b/server/main.go @@ -119,7 +119,6 @@ func (h *Handler) GroupsIDGet(ctx context.Context, params api.GroupsIDGetParams) Participants: ep_members, }) } - // Calculate money balance for the group netAmountParams := data.GetNetAmountForUserInGroupParams{ AuthorID: userid, @@ -171,7 +170,8 @@ func (h *Handler) GroupsIDItemsGet(ctx context.Context, params api.GroupsIDItems } func (h *Handler) GroupsIDItemsPost(ctx context.Context, req *api.Item, params api.GroupsIDItemsPostParams) (int, error) { - g, _ := qs.AddItemToGroup(ctx, data.AddItemToGroupParams{ + // Add the new item to the 'items' table + g, err := qs.AddItemToGroup(ctx, data.AddItemToGroupParams{ Name: req.Name, Timestamp: int64(req.Timestamp), Price: req.Price, @@ -179,6 +179,25 @@ func (h *Handler) GroupsIDItemsPost(ctx context.Context, req *api.Item, params a AuthorID: int64(req.AuthorID), Reimbursement: sql.NullBool{Bool: req.Reimbursement.Value, Valid: req.Reimbursement.Set}, }) + if err != nil { + return 0, err + } + + // Ensure participants are passed in the request + if len(req.Participants) == 0 { + return 0, fmt.Errorf("no participants provided") + } + + // Add participants to the 'expense_participants' table + for _, member := range req.Participants { + err := qs.AddExpenseParticipant(ctx, data.AddExpenseParticipantParams{ + ItemID: g, // The ID of the item that was just added + MemberID: int64(member.ID), // The ID of the participant to add + }) + if err != nil { + return 0, err + } + } return int(g), nil } diff --git a/server/query.sql b/server/query.sql index 7c03d51..f352954 100644 --- a/server/query.sql +++ b/server/query.sql @@ -64,3 +64,7 @@ JOIN items i ON ep.item_id = i.id JOIN members m ON ep.member_id = m.id WHERE ep.item_id = ?; +-- name: AddExpenseParticipant :exec +INSERT INTO expense_participants (item_id, member_id) VALUES (?, ?); + + diff --git a/server/schema.sql b/server/schema.sql index bf4d42d..502a7f5 100644 --- a/server/schema.sql +++ b/server/schema.sql @@ -29,9 +29,9 @@ CREATE TABLE items ( --bs: medium FOREIGN KEY (author_id) REFERENCES members (id) ); -CREATE TABLE expense_participants ( - item_id INTEGER NOT NULL, -- Foreign key referencing the items table - member_id INTEGER NOT NULL, -- Foreign key referencing the members table +CREATE TABLE expense_participants ( --bs: medium + item_id INTEGER NOT NULL, --bs: rel + member_id INTEGER NOT NULL, --bs: rel PRIMARY KEY (item_id, member_id), -- Composite primary key (ensures unique pairs) FOREIGN KEY (item_id) REFERENCES items (id), -- Foreign key constraint to items table FOREIGN KEY (member_id) REFERENCES members (id) -- Foreign key constraint to members table