Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
11 changes: 11 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"recommendations": [
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"Prisma.prisma",
"GraphQL.vscode-graphql",
"ms-playwright.playwright",
"vitest.explorer"
]
}
1 change: 1 addition & 0 deletions client/src/lib/apolloClient.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ApolloClient, HttpLink, InMemoryCache } from '@apollo/client';


// adressen til graphql backend
const httpLink = new HttpLink({
uri: import.meta.env.VITE_GRAPHQL_URL,
Expand Down
2 changes: 2 additions & 0 deletions server/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
DATABASE_URL=
PORT=
11 changes: 9 additions & 2 deletions server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"author": "",
"license": "ISC",
"type": "commonjs",
"prisma": { "seed": "tsx prisma/seed.ts" },
"prisma": {
"seed": "tsx prisma/seed.ts"
},
"devDependencies": {
"@types/cookie-parser": "^1.4.10",
"@types/cors": "^2.8.19",
Expand All @@ -29,6 +31,11 @@
"vitest": "^5.0.2"
},
"dependencies": {
"@prisma/client": "^6.19.3"
"@apollo/server": "^5.5.1",
"@as-integrations/express5": "^1.1.2",
"@prisma/client": "^6.19.3",
"cors": "^2.8.6",
"express": "^5.2.1",
"graphql": "^16.14.2"
}
}
6 changes: 4 additions & 2 deletions server/prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ async function main() {
const tagPasta = await prisma.tag.upsert({ where: { name: 'vegetar' }, update: {}, create: { name: 'vegetar'}});

const user = await prisma.user.create({
data: { name: 'Test Testesen', email: 'testesen@eksempel.no' },
where: { id: 'dev-user' },
update: {},
create: { id: 'dev-user', name: 'Test Bruker', email: 'test@example.com'}
});

await prisma.recipe.create({
Expand All @@ -16,7 +18,7 @@ async function main() {
servings: 1,
category: 'middag',
status: 'PUBLISHED',
authorId: user.id,
authorId: 'dev-user',
tags: { connect: [{ id: tagPasta.id}]},
ingredients: {
create: [
Expand Down
2 changes: 2 additions & 0 deletions server/src/db/client.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import { PrismaClient } from '@prisma/client';
export const prisma = new PrismaClient();
24 changes: 21 additions & 3 deletions server/src/index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
//# sourceMappingURL=index.js.map
import express from 'express';
import cors from 'cors';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@apollo/server/express4';
import { readFileSync } from 'fs';
import { join } from 'path';
import { recipeResolvers } from './schema/resolvers/recipe';

const typeDefs = readFileSync(join(__dirname, 'schema/typeDefs/recipe.graphql'), 'utf-8');

async function start() {
const app = express();
const server = new ApolloServer({ typeDefs, resolvers: recipeResolvers });
await server.start();

app.use('/graphql', cors(), express.json(), expressMiddleware(server));

app.listen(3001, () => console.log('Server kjører på http://localhost:3001/graphql'));
}

start();
21 changes: 21 additions & 0 deletions server/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import express from 'express';
import cors from 'cors';
import { ApolloServer } from '@apollo/server';
import { expressMiddleware } from '@as-integrations/express5';
import { readFileSync } from 'fs';
import { join } from 'path';
import { recipeResolvers } from './schema/resolvers/recipe';

const typeDefs = readFileSync(join(__dirname, 'schema/typeDefs/recipe.graphql'), 'utf-8');

async function start() {
const app = express();
const server = new ApolloServer({ typeDefs, resolvers: recipeResolvers });
await server.start();

app.use('/graphql', cors(), express.json(), expressMiddleware(server));

app.listen(3001, () => console.log('Server kjører på http://localhost:3001/graphql'));
}

start();
49 changes: 49 additions & 0 deletions server/src/schema/resolvers/recipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { prisma } from '../../db/client';

export const recipeResolvers = {
Query: {
recipes: async (_: unknown, args: { search?: string; category?: string; page?: number; pageSize?: number }) => {
const { search, category, page = 1, pageSize = 20 } = args;
return prisma.recipe.findMany({
where: {
status: 'PUBLISHED',
...(search && { title: { contains: search, mode: 'insensitive' } }),
...(category && { category }),
},
include: { ingredients: true, steps: true, tags: true },
skip: (page - 1) * pageSize,
take: pageSize,
orderBy: { createdAt: 'desc' },
});
},
recipe: async (_: unknown, args: { id: string }) => {
return prisma.recipe.findUnique({
where: { id: args.id },
include: { ingredients: true, steps: true, tags: true },
});
},
},
Mutation: {
createRecipe: async (_: unknown, args: { input: any }) => {
const { ingredients, steps, tagNames, ...rest } = args.input;
return prisma.recipe.create({
data: {
...rest,
status: 'PUBLISHED',
authorId: 'MIDLERTIDIG-ID', // bytt ut når Person 2 har auth klart
ingredients: { create: ingredients },
steps: { create: steps },
tags: tagNames
? {
connectOrCreate: tagNames.map((name: string) => ({
where: { name },
create: { name },
})),
}
: undefined,
},
include: { ingredients: true, steps: true, tags: true },
});
},
},
};
66 changes: 66 additions & 0 deletions server/src/schema/typeDefs/recipe.graphql
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
type RecipeIngredient {
id: ID!
name: String!
amount: Float!
unit: String!
order: Int!
}

type Step {
id: ID!
order: Int!
instruction: String!
}

type Tag {
id: ID!
name: String!
}

type Recipe {
id: ID!
title: String!
description: String!
prepTimeMinutes: Int!
servings: Int!
category: String!
imageUrl: String
status: String!
createdAt: String!
ingredients: [RecipeIngredient!]!
steps: [Step!]!
tags: [Tag!]!
}

input RecipeIngredientInput {
name: String!
amount: Float!
unit: String!
order: Int!
}

input StepInput {
order: Int!
instruction: String!
}

input CreateRecipeInput {
title: String!
description: String!
prepTimeMinutes: Int!
servings: Int!
category: String!
imageUrl: String
ingredients: [RecipeIngredientInput!]!
steps: [StepInput!]!
tagNames: [String!]
}

type Query {
recipes(search: String, category: String, page: Int, pageSize: Int): [Recipe!]!
recipe(id: ID!): Recipe
}

type Mutation {
createRecipe(input: CreateRecipeInput!): Recipe!
}