From f849ad308b9c06482c95063aa8dd99cd67b1efe2 Mon Sep 17 00:00:00 2001 From: Tara Aqeel Date: Wed, 7 Oct 2026 10:23:58 +0200 Subject: [PATCH] feat(#13): legg til GraphQL-skjema og resolvere for varer og kategorier --- backend/src/index.ts | 175 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 170 insertions(+), 5 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index d2f2e4c..bfe2554 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,22 +1,187 @@ import 'dotenv/config'; import { ApolloServer } from '@apollo/server'; import { startStandaloneServer } from '@apollo/server/standalone'; +import { pool } from './db.js'; -// Midlertidig skjema for å sjekke at serveren kjører. -// Erstattes av det ekte skjemaet når databasen er på plass. const typeDefs = `#graphql + type Category { + id: ID! + parentId: ID + name: String! + } + + type Price { + storeCode: String! + price: Float! + unitPrice: Float + url: String + } + + type Product { + ean: String! + name: String! + brand: String + imageUrl: String + categoryId: ID + category: Category + prices: [Price!]! + } + type Query { - hello: String! + products( + search: String + category: ID + limit: Int = 20 + offset: Int = 0 + ): [Product!]! + + product(ean: String!): Product + + categories: [Category!]! + + } + + type ShoppingListItem { + id: ID! + ean: String! + quantity: Int! + note: String + checked: Boolean! + } + + type ShoppingList { + id: ID! + name: String! + status: String! + inUse: Boolean! + items: [ShoppingListItem!]! + } + + type Mutation { + createShoppingList(name: String!): ShoppingList! + + addItemToList( + listId: ID! + ean: String! + quantity: Int = 1 + ): ShoppingList! } `; const resolvers = { Query: { - hello: () => 'Hei fra Handlelappen-backend!', + categories: async () => { + const result = await pool.query(` + SELECT + id, + parent_id AS "parentId", + name + FROM categories + ORDER BY name + `); + + return result.rows; + }, + + product: async (_parent: unknown, args: { ean: string }) => { + const result = await pool.query( + ` + SELECT + ean, + name, + brand, + image_url AS "imageUrl", + category_id AS "categoryId" + FROM products + WHERE ean = $1 + `, + [args.ean], + ); + + return result.rows[0] ?? null; + }, + + products: async ( + _parent: unknown, + args: { + search?: string; + category?: string; + limit?: number; + offset?: number; + }, + ) => { + const search = args.search ?? null; + const category = args.category ?? null; + const limit = args.limit ?? 20; + const offset = args.offset ?? 0; + + const result = await pool.query( + ` + SELECT + ean, + name, + brand, + image_url AS "imageUrl", + category_id AS "categoryId" + FROM products + WHERE + ($1::text IS NULL OR name ILIKE '%' || $1 || '%') + AND + ($2::integer IS NULL OR category_id = $2) + ORDER BY name + LIMIT $3 + OFFSET $4 + `, + [search, category, limit, offset], + ); + + return result.rows; + }, + }, + + Product: { + category: async (product: { categoryId?: number | null }) => { + if (!product.categoryId) { + return null; + } + + const result = await pool.query( + ` + SELECT + id, + parent_id AS "parentId", + name + FROM categories + WHERE id = $1 + `, + [product.categoryId], + ); + + return result.rows[0] ?? null; + }, + + prices: async (product: { ean: string }) => { + const result = await pool.query( + ` + SELECT + store_code AS "storeCode", + price, + unit_price AS "unitPrice", + url + FROM prices + WHERE ean = $1 + ORDER BY price + `, + [product.ean], + ); + + return result.rows; + }, }, }; -const server = new ApolloServer({ typeDefs, resolvers }); + +const server = new ApolloServer({ typeDefs, resolvers, introspection: true, }); // Port 3001 er kravet i oppgaven, men kan overstyres i .env const port = Number(process.env.PORT ?? 3001);