Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
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
314 changes: 309 additions & 5 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,326 @@
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;
},
},

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
6 changes: 6 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.