diff --git a/openapi.yml b/openapi.yml index 250a86d..3e49d44 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 @@ -142,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 @@ -153,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 b1d4125..a03b6d1 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,79 @@ 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: 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) + + 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), + // The new participant list!!!!!! + Participants: ep_members, + }) + } + // 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 { @@ -98,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, @@ -106,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 ae09b19..f352954 100644 --- a/server/query.sql +++ b/server/query.sql @@ -42,3 +42,29 @@ 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 = ?; + +-- 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 = ?; + +-- name: AddExpenseParticipant :exec +INSERT INTO expense_participants (item_id, member_id) VALUES (?, ?); + + diff --git a/server/schema.sql b/server/schema.sql index 2c1aa03..502a7f5 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 ( --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 +); 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; -