Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
84214c4
Jeg har lagt til GraphQL-typer og mutasjoner for handleliste
taraa Oct 6, 2026
1f0fe37
Mutatiion for handlelist så handleliste faktisk blir laget
taraa Oct 6, 2026
c3a31d9
Rettet opp i feil ved plassering av mutation-kode
taraa Oct 6, 2026
7a4f256
Sett opp Apollo Client for issue #14
annisaam Oct 7, 2026
1c79b96
Merge remote-tracking branch 'origin/dev' into feature/14-apollo-inte…
annisaam Oct 7, 2026
478c2cf
feat(#15): lagt til funksjonalitet for å legge varer i handleliste
taraa Oct 8, 2026
99927e1
finpussing av kode
taraa Oct 8, 2026
4932b16
Merge remote-tracking branch 'origin/main' into feature/14-apollo-int…
annisaam Oct 8, 2026
5a74123
feat(#14): vis bilde og pris, debounce i søk
annisaam Oct 8, 2026
f849ad3
feat(#13): legg til GraphQL-skjema og resolvere for varer og kategorier
taraa Oct 7, 2026
fb0efd9
Merge pull request #26 from IT2810-H26/25-cleanup-merge-in-til-dev-is…
lindeav Oct 9, 2026
ec6803b
Merge pull request #24 from IT2810-H26/feature/14-apollo-integration
lindeav Oct 9, 2026
743a0cb
Merge branch 'dev' into 15-task-handleliste-opprette-liste-og-legge-t…
lindeav Oct 9, 2026
eaa8b25
Merge pull request #23 from IT2810-H26/15-task-handleliste-opprette-l…
lindeav Oct 9, 2026
b20369b
feat: lagt til figma styling
lindeav Oct 9, 2026
8676bd3
docs: installasjon, kjøring, API og status i README (#17)
ingrihka Oct 9, 2026
f3d31ed
Merge pull request #29 from IT2810-H26/docs/17-installasjon-kjoring
ingrihka Oct 9, 2026
efdfb16
Sett base til /project2/ for bygg på VM
lindeav Oct 9, 2026
f1d42e7
Merge pull request #28 from IT2810-H26/figma-styling
taraa Oct 9, 2026
99cd8eb
Merge pull request #30 from IT2810-H26/fix/vite-base
taraa Oct 9, 2026
70ded8e
La til figma styling med komponenter og theme. Endret litt på App.tsx…
lindeav Oct 12, 2026
87f296e
docs: full datadump og seed i README (#34)
ingrihka Oct 12, 2026
4b17fd1
Merge pull request #33 from IT2810-H26/figma-styling
annisaam Oct 14, 2026
8d8d593
chore: merge main inn i dev for å løse konflikt i #31
ingrihka Oct 15, 2026
53610a2
Merge branch 'dev' of git.ntnu.no:IT2810-H26/T27-Project-2 into fix/m…
ingrihka Oct 15, 2026
812dd39
Merge pull request #38 from IT2810-H26/fix/merge-main-into-dev
lindeav Oct 15, 2026
d73ee4c
Merge pull request #35 from IT2810-H26/docs/full-dump-readme
ingrihka Oct 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
367 changes: 340 additions & 27 deletions README.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
PORT=3001
DATABASE_URL=postgres://localhost:5432/handlelapp
# Trengs bare for `npm run import`. Seed og vanlig kjøring fungerer uten.
KASSALAPP_API_KEY=
315 changes: 310 additions & 5 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,327 @@
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!]!
shoppingList(id: ID!): ShoppingList
}
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;
},


shoppingList: async (
_parent: unknown,
args: { id: string },
) => {
const result = await pool.query(
`
SELECT
id,
name,
status,
in_use AS "inUse"
FROM shopping_lists
WHERE id = $1
`,
[args.id],
);

return result.rows[0] ?? null;
},
},

Mutation: {
createShoppingList: async (
_parent: unknown,
args: { name: string },
) => {
const result = await pool.query(
`
INSERT INTO shopping_lists (owner_id, name)
VALUES ($1, $2)
RETURNING
id,
name,
status,
in_use AS "inUse"
`,
['anonymous', args.name],
);

return result.rows[0];
},

addItemToList: async (
_parent: unknown,
args: {
listId: string;
ean: string;
quantity?: number;
},
) => {
const quantity = args.quantity ?? 1;

if (quantity <= 0) {
throw new Error('Antall må være større enn 0');
}

// Sjekk at handlelisten finnes
const listResult = await pool.query(
`
SELECT id
FROM shopping_lists
WHERE id = $1
`,
[args.listId],
);

if (listResult.rows.length === 0) {
throw new Error('Handlelisten finnes ikke');
}

// Sjekk at produktet finnes
const productResult = await pool.query(
`
SELECT ean
FROM products
WHERE ean = $1
`,
[args.ean],
);

if (productResult.rows.length === 0) {
throw new Error('Produktet finnes ikke');
}

// Legg til varen.
// Hvis varen allerede finnes i listen, økes quantity.
await pool.query(
`
INSERT INTO list_items (list_id, ean, quantity)
VALUES ($1, $2, $3)
ON CONFLICT (list_id, ean)
DO UPDATE SET
quantity = list_items.quantity + EXCLUDED.quantity
`,
[args.listId, args.ean, quantity],
);

// Returner handlelisten
const result = await pool.query(
`
SELECT
id,
name,
status,
in_use AS "inUse"
FROM shopping_lists
WHERE id = $1
`,
[args.listId],
);

return result.rows[0];
},
},

ShoppingList: {
items: async (list: { id: string }) => {
const result = await pool.query(
`
SELECT
id,
ean,
quantity,
note,
checked
FROM list_items
WHERE list_id = $1
ORDER BY id
`,
[list.id],
);

return result.rows;
},
},

//evt en ekstra {

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);
Expand Down
1 change: 1 addition & 0 deletions frontend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
VITE_GRAPHQL_URL=http://localhost:3001/
1 change: 1 addition & 0 deletions frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ node_modules
dist
dist-ssr
*.local
.env

# Editor directories and files
.vscode/*
Expand Down
Loading