From a0a31f556b6d7ac1860b6406703a21af6c1387a3 Mon Sep 17 00:00:00 2001 From: albdert Date: Thu, 22 Oct 2026 12:02:03 +0200 Subject: [PATCH 1/3] reviews kan legges inn, redigeres, og slettes --- backend/src/resolvers.ts | 89 +++++++++++++++++++++++++++++++++++++++- backend/src/schema.ts | 53 ++++++++++++++++-------- 2 files changed, 123 insertions(+), 19 deletions(-) diff --git a/backend/src/resolvers.ts b/backend/src/resolvers.ts index 5f9f586..88d6acf 100644 --- a/backend/src/resolvers.ts +++ b/backend/src/resolvers.ts @@ -1,11 +1,12 @@ import { GraphQLError } from 'graphql/error'; import { decodeCursor, encodeCursor } from './cursor.js'; import { pool } from './db.js'; -import { hashPassword, verifyPassword, signToken } from './auth.js'; +import { hashPassword, verifyPassword, signToken, requireUser } from './auth.js'; import type { Context } from './loaders.js'; // type definitions type AuthArgs = { username: string; password: string }; +type SaveReviewArgs = { gameId: string; rating: number; body: string }; type GamesArgs = { search?: string | null; @@ -125,6 +126,43 @@ const SQL_REGISTER = ` RETURNING id, username `; +// SQL reviews +// to_jason gir ut en formattert streng som kan konverteres direkte +// med new Date(), slipper derfor pg sitt Date objekt +const REVIEW_COLS = ` + r.id, + r.rating, + r.body, + to_json(r.created_at) #>> '{}' AS "createdAt", + json_build_object('id', u.id::text, 'username', u.username) AS "user" +`; +const SQL_REVIEWS = ` + SELECT ${REVIEW_COLS} + FROM reviews r JOIN users u ON u.id = r.user_id + WHERE r.game_id = $1 + ORDER BY r.created_at DESC, r.id DESC + LIMIT 50 +`; +const SQL_MY_REVIEW = ` + SELECT ${REVIEW_COLS} + FROM reviews r JOIN users u ON u.id = r.user_id + WHERE r.game_id = $1 AND r.user_id = $2 +`; +const SQL_SAVE_REVIEW = ` + WITH saved AS ( + INSERT INTO reviews (game_id, user_id, rating, body) + VALUES ($1, $2, $3, $4) + ON CONFLICT (game_id, user_id) + DO UPDATE SET rating = EXCLUDED.rating, body = EXCLUDED.body + RETURNING * + ) + SELECT ${REVIEW_COLS} + FROM saved r JOIN users u ON u.id = r.user_id +`; +const SQL_DELETE_REVIEW = ` + DELETE FROM reviews WHERE game_id = $1 AND user_id = $2 +`; + // bruker-funksjoner const badInput = (message: string) => new GraphQLError(message, { extensions: { code: 'BAD_USER_INPUT' } }); @@ -164,6 +202,50 @@ async function me(_parent: unknown, _args: unknown, ctx: Context) { return (await pool.query(SQL_USER, [ctx.userId])).rows[0] ?? null; } +// review funksjoner +async function saveReview(_parent: unknown, args: SaveReviewArgs, ctx: Context) { + const userId = requireUser(ctx); + const body = args.body.trim(); + + if (!VALID_ID_REGEX.test(args.gameId)) throw badInput('Ugyldig spill-id'); + if (!Number.isInteger(args.rating) || args.rating < 1 || args.rating > 5) { + throw badInput('Vurdering må være mellom 1 og 5'); + } + if (body.length < 1 || body.length > 2000) { + throw badInput('Anmeldelse må være 1-2000 tegn...'); + } + + try { + const res = await pool.query(SQL_SAVE_REVIEW, [args.gameId, userId, args.rating, body]); + return res.rows[0]; + } catch (err) { + if ((err as { code?: string }).code === '23503') { + throw badInput('Spillet ble ikke funnet...'); + } + throw err; + } +} + +// autentiserer bruker og forsøker å slette review for gitt spill om det finnes +// returnerer false hvis ingen review for spillet fantes +async function deleteReview(_parent: unknown, args: { gameId: string }, ctx: Context) { + const userId = requireUser(ctx); + if (!VALID_ID_REGEX.test(args.gameId)) return false; + const res = await pool.query(SQL_DELETE_REVIEW, [args.gameId, userId]); + return (res.rowCount ?? 0) > 0; +} + +// resolver for alle reviews for et gitt spill +async function gameReviews(game: { id: string }) { + return (await pool.query(SQL_REVIEWS, [game.id])).rows; +} + +// review for den geldende brukeren +async function myReview(game: { id: string }, _args: unknown, ctx: Context) { + if (!ctx.userId) return null; + return (await pool.query(SQL_MY_REVIEW, [game.id, ctx.userId])).rows[0] ?? null; +} + // erstatter den gamle løsningen med direkte sql og variabler, // denne gir mer fleksibilitet i hvordan man sorterer // og fungerer med pagineringen vår @@ -299,7 +381,7 @@ async function platforms() { // resolvers for å hente kjøre spørringer mot databasen export const resolvers = { Query: { game, games, genres, platforms, me }, - Mutation: { register, login }, + Mutation: { register, login, saveReview, deleteReview }, Game: { genres: relation('genres'), @@ -308,6 +390,9 @@ export const resolvers = { developers: relation('developers'), publishers: relation('publishers'), tags: relation('tags'), + + reviews: gameReviews, + myReview }, GameConnection: { totalCount }, diff --git a/backend/src/schema.ts b/backend/src/schema.ts index 54a998a..cc33712 100644 --- a/backend/src/schema.ts +++ b/backend/src/schema.ts @@ -55,6 +55,9 @@ export const typeDefs = `#graphql developers: [Facet!]! publishers: [Facet!]! tags: [Facet!]! + + reviews: [Review!]! + myReview: Review } """ @@ -86,6 +89,34 @@ export const typeDefs = `#graphql endCursor: String } + """ + Bruker definert ved brukerid og brukernavn. + """ + type User { + id: ID! + username: String! + } + + """ + Informasjon om en review inkludert id, rating, body, dato, og bruker. + """ + type Review { + id: ID! + rating: Int! + body: String! + createdAt: String! + user: User! + } + + """ + Payload som sendes for å autentisere en bruker, både brukeren sin token + bruker typen skal sendes. + """ + type AuthPayload { + token: String! + user: User! + } + """ Alle gyldige queries mot backend er definert her. """ @@ -106,23 +137,6 @@ export const typeDefs = `#graphql me: User } - """ - Bruker definert ved brukerid og brukernavn. - """ - type User { - id: ID! - username: String! - } - - """ - Payload som sendes for å autentisere en bruker, både brukeren sin token - bruker typen skal sendes. - """ - type AuthPayload { - token: String! - user: User! - } - """ Mutation for å mutere data i databasen. Register regestrerer en ny bruker i databasen. @@ -131,5 +145,10 @@ export const typeDefs = `#graphql type Mutation { register(username: String!, password: String!): AuthPayload! login(username: String!, password: String!): AuthPayload! + + saveReview(gameId: ID!, rating: Int!, body: String!): Review! + deleteReview(gameId: ID!): Boolean! } + + `; From 2b5060d304d4a4925bab34dee5e66646a5d58b73 Mon Sep 17 00:00:00 2001 From: albdert Date: Thu, 22 Oct 2026 12:46:32 +0200 Subject: [PATCH 2/3] dokumentasjon --- backend/backend-api.md | 344 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 327 insertions(+), 17 deletions(-) diff --git a/backend/backend-api.md b/backend/backend-api.md index 6f97c0c..7082049 100644 --- a/backend/backend-api.md +++ b/backend/backend-api.md @@ -1,5 +1,41 @@ # Backend-API +1. [Autentisering](#autentisering) +2. [Queries](#queries) + 1. [game](#game) + 2. [games](#games) + 3. [genres](#genres) + 4. [platforms](#platforms) + 5. [me](#me) +3. [Mutations](#mutations) + 1. [register](#register) + 2. [login](#login) + 3. [saveReview](#savereview) + 4. [deleteReview](#deletereview) +4. [Typer](#types) + 1. [Game](#game-1) + 2. [Facet](#facet) + 3. [GameConnection](#gameconnection) + 4. [GameEdge](#gameedge) + 5. [GameFilter](#gamefilter) + 6. [GameSort](#gamesort) + 7. [PageInfo](#pageinfo) + 8. [User](#user) + 9. [AuthPayload](#authpayload) + 10. [Review](#review) + +## Autentisering + +`register` og `login` returnerer et token. Send det i headeren på alle forespørsler som krever innlogging: + +``` +Authorization: Bearer +``` + +Tokenet er gyldig i 7 dager. Et ugyldig eller utløpt token behandles som om brukeren ikke er innlogget. + +--- + ## Queries ### game @@ -50,7 +86,7 @@ game(id: ID!): Game ### games -Henter en side med spill, med valgfritt søk og filtre. Resultatet er sortert etter popularitet. +Henter en side med spill, med valgfritt søk og filtre. Uten `sort` er resultatet er sortert etter popularitet. ```graphql games( @@ -71,9 +107,9 @@ games( | `search` | `String` | — | Søk i tittel og alternative navn. Skiller ikke mellom store og små bokstaver. | | `filter` | [`GameFilter`](#gamefilter) | — | Filtrering på sjanger, plattform og utgivelsesår. | | `sort` | [`GameSort!`](#gamesort) | `POPULARITY` | Sorteringsrekkefølge. | -| `first` | `Int` | `20` | Antall spill fremover fra `after`. Begrenses til 1–50. | +| `first` | `Int` | `20` | Antall spill fremover fra `after`. Begrenses til 1-50. | | `after` | `String` | — | Cursor fra `pageInfo.endCursor`. Henter spillene etter denne. | -| `last` | `Int` | — | Antall spill bakover fra `before`. Begrenses til 1–50. | +| `last` | `Int` | — | Antall spill bakover fra `before`. Begrenses til 1-50. | | `before` | `String` | — | Cursor fra `pageInfo.startCursor`. Henter spillene før denne. | `first`/`after` og `last`/`before` kan ikke kombineres. `last` uten `before` henter siste side. @@ -116,7 +152,7 @@ Gir feilen `BAD_USER_INPUT` hvis begge retningene brukes samtidig, eller hvis cu #### Eksempel: filtre -Action- og RPG-spill på PC, utgitt 2015–2020: +Action- og RPG-spill på PC, utgitt 2015-2020: ```graphql { @@ -355,9 +391,235 @@ Merk: ID-ene her er plattformfamilier og er ikke de samme som ID-ene i `Game.pla } ``` +--- + +### me + +Henter brukeren som er logget inn. + +```graphql +me: User +``` + +#### Returnerer + +[`User`](#user). Returnerer `null` hvis brukeren ikke er logget inn. + +#### Eksempel + +```graphql +{ + me { + id + username + } +} +``` + +```json +{ + "data": { + "me": { + "id": "1", + "username": "testbruker" + } + } +} +``` + +--- + ## Mutations -> Dette skal legges til når funksjonalitet for brukere blir lagt til +### register + +Oppretter en ny bruker og logger den inn. + +```graphql +register(username: String!, password: String!): AuthPayload! +``` + +#### Argumenter + +| Navn | Type | Default | Beskrivelse | +| ---------- | --------- | ------- | ------------------------------------------------------------ | +| `username` | `String!` | — | 3-30 tegn: bokstaver A-Z, tall og `_`. Må være unikt uavhengig av store og små bokstaver. | +| `password` | `String!` | — | 8-72 tegn. | + +#### Returnerer + +[`AuthPayload!`](#authpayload). Gir feilen `BAD_USER_INPUT` hvis brukernavnet eller passordet er ugyldig, eller brukernavnet er tatt. + +#### Eksempel + +```graphql +mutation { + register(username: "testbruker", password: "hemmelig123") { + token + user { + id + username + } + } +} +``` + +```json +{ + "data": { + "register": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "1", + "username": "testbruker" + } + } + } +} +``` + +--- + +### login + +Logger inn en eksisterende bruker. + +```graphql +login(username: String!, password: String!): AuthPayload! +``` + +#### Argumenter + +| Navn | Type | Default | Beskrivelse | +| ---------- | --------- | ------- | ----------------------------------------------------- | +| `username` | `String!` | — | Brukernavn. Skiller ikke mellom store og små bokstaver. | +| `password` | `String!` | — | Passord. | + +#### Returnerer + +[`AuthPayload!`](#authpayload). Gir feilen `UNAUTHENTICATED` hvis brukernavnet eller passordet er feil. + +#### Eksempel + +```graphql +mutation { + login(username: "testbruker", password: "hemmelig123") { + token + user { + username + } + } +} +``` + +```json +{ + "data": { + "login": { + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "username": "testbruker" + } + } + } +} +``` + +--- + +### saveReview + +Lagrer innlogget brukers review av et spill. Har brukeren allerede en review av spillet, blir den erstattet. + +```graphql +saveReview(gameId: ID!, rating: Int!, body: String!): Review! +``` + +Krever innlogging. + +#### Argumenter + +| Navn | Type | Default | Beskrivelse | +| -------- | --------- | ------- | ---------------------------------------------------- | +| `gameId` | `ID!` | — | ID til spillet. | +| `rating` | `Int!` | — | Vurdering fra 1 til 5. | +| `body` | `String!` | — | Teksten i reviewen, 1-2000 tegn etter at mellomrom i start og slutt er fjernet. | + +#### Returnerer + +[`Review!`](#review). Gir feilen `UNAUTHENTICATED` uten innlogging, og `BAD_USER_INPUT` hvis et argument er ugyldig eller spillet ikke finnes. + +#### Eksempel + +```graphql +mutation { + saveReview(gameId: "3498", rating: 4, body: "Bra spill") { + id + rating + body + createdAt + user { + username + } + } +} +``` + +```json +{ + "data": { + "saveReview": { + "id": "1", + "rating": 4, + "body": "Bra spill", + "createdAt": "2026-10-22T11:50:12.761383+02:00", + "user": { + "username": "testbruker" + } + } + } +} +``` + +--- + +### deleteReview + +Sletter innlogget brukers review av et spill. + +```graphql +deleteReview(gameId: ID!): Boolean! +``` + +Krever innlogging. + +#### Argumenter + +| Navn | Type | Default | Beskrivelse | +| -------- | ----- | ------- | --------------- | +| `gameId` | `ID!` | — | ID til spillet. | + +#### Returnerer + +`Boolean!`. `true` hvis en review ble slettet, `false` hvis brukeren ikke hadde noen review av spillet. Gir feilen `UNAUTHENTICATED` uten innlogging. + +#### Eksempel + +```graphql +mutation { + deleteReview(gameId: "3498") +} +``` + +```json +{ + "data": { + "deleteReview": true + } +} +``` + +--- ## Types @@ -373,12 +635,11 @@ Et spill med detaljer og relasjoner. | `released` | `String` | Utgivelsesdato, `YYYY-MM-DD`. | | `website` | `String` | Offisiell nettside. | | `added` | `Int!` | Antall RAWG-brukere som har lagt spillet til i en liste. Brukes som mål på popularitet. | -| `metacritic` | `Int` | Metacritic-score, 0–100. | -| `rating` | `Float` | RAWG-brukernes snittvurdering, 0–5. | +| `metacritic` | `Int` | Metacritic-score, 0-100. | +| `rating` | `Float` | RAWG-brukernes snittvurdering, 0-5. | | `ratingsCount` | `Int!` | Antall vurderinger bak `rating`. | | `playtime` | `Int` | Gjennomsnittlig spilletid i timer. | | `esrbRating` | `String` | Amerikansk aldersgrense, f.eks. `Mature`. | -| `dominantColor` | `String` | Hovedfargen i forsidebildet som hex, f.eks. `0f0f0f`. | | `backgroundImage` | `String` | URL til forsidebildet. | | `backgroundImageAdditional` | `String` | URL til et ekstra bilde. | | `screenshots` | `[String!]!` | URL-er til skjermbilder. Tom liste hvis ingen. | @@ -388,6 +649,8 @@ Et spill med detaljer og relasjoner. | `developers` | [`[Facet!]!`](#facet) | Utviklere. | | `publishers` | [`[Facet!]!`](#facet) | Utgivere. | | `tags` | [`[Facet!]!`](#facet) | Tagger fra RAWG, f.eks. Open World. | +| `reviews` | [`[Review!]!`](#review) | De 50 nyeste reviewene av spillet, nyeste først. | +| `myReview` | [`Review`](#review) | Innlogget brukers review av spillet. `null` uten innlogging eller review. | Relasjonene returnerer en tom liste hvis spillet ikke har noen. @@ -427,15 +690,6 @@ Ett spill i en [`GameConnection`](#gameconnection). --- -### PageInfo - -| Felt | Type | Beskrivelse | -| ------------- | ---------- | ------------------------------------------------------------------- | -| `hasNextPage` | `Boolean!` | `true` hvis det finnes flere spill etter denne siden. | -| `endCursor` | `String` | Cursor til siste spill på siden. Sendes som `after` for neste side. | - ---- - ### GameFilter Input-type for filtrering i [`games`](#games). Alle felt er valgfrie. @@ -448,3 +702,59 @@ Input-type for filtrering i [`games`](#games). Alle felt er valgfrie. | `releasedTo` | `Int` | Utgitt dette året eller tidligere, f.eks. `2020`. | Merk: Spill uten utgivelsesdato utelates når `releasedFrom` eller `releasedTo` er satt. ID-er som ikke er tall ignoreres. + +--- + +### GameSort + +Enum for sortering i [`games`](#games). + +| Verdi | Beskrivelse | +| ------------ | ------------------------------------------------------------ | +| `POPULARITY` | Mest populære først, etter `added`. | +| `NEWEST` | Nyeste utgivelsesdato først. Spill uten dato kommer sist. | +| `METACRITIC` | Høyeste Metacritic-score først. Spill uten score kommer sist. | +| `NAME` | Alfabetisk etter tittel. | + +--- + +### PageInfo + +| Felt | Type | Beskrivelse | +| ----------------- | ---------- | ----------------------------------------------------------------------- | +| `hasNextPage` | `Boolean!` | `true` hvis det finnes flere spill etter denne siden. | +| `hasPrevPage` | `Boolean!` | `true` hvis det finnes spill før denne siden. | +| `startCursor` | `String` | Cursor til første spill på siden. Sendes som `before` for forrige side. | +| `endCursor` | `String` | Cursor til siste spill på siden. Sendes som `after` for neste side. | + +--- + +### User + +| Felt | Type | Beskrivelse | +| ---------- | --------- | --------------- | +| `id` | `ID!` | ID til brukeren. | +| `username` | `String!` | Brukernavn. | + +--- + +### AuthPayload + +| Felt | Type | Beskrivelse | +| ------- | ------------------- | -------------------------------------------- | +| `token` | `String!` | Token til `Authorization`-headeren. | +| `user` | [`User!`](#user) | Brukeren som ble registrert eller logget inn. | + +--- + +### Review + +| Felt | Type | Beskrivelse | +| ----------- | ---------------- | ----------------------------------------------- | +| `id` | `ID!` | ID til reviewen. | +| `rating` | `Int!` | Vurdering fra 1 til 5. | +| `body` | `String!` | Teksten i reviewen. | +| `createdAt` | `String!` | Tidspunktet reviewen først ble lagret, i ISO 8601. | +| `user` | [`User!`](#user) | Brukeren som skrev reviewen. | + +--- From ec19448b79b0bf0ad5add5230fa0c2dce3bfdbef Mon Sep 17 00:00:00 2001 From: albdert Date: Thu, 22 Oct 2026 12:57:04 +0200 Subject: [PATCH 3/3] format --- backend/backend-api.md | 116 +++++++++++++++++++-------------------- backend/src/auth.ts | 2 +- backend/src/index.ts | 3 +- backend/src/loaders.ts | 2 +- backend/src/resolvers.ts | 9 ++- 5 files changed, 65 insertions(+), 67 deletions(-) diff --git a/backend/backend-api.md b/backend/backend-api.md index 7082049..b35b585 100644 --- a/backend/backend-api.md +++ b/backend/backend-api.md @@ -391,7 +391,7 @@ Merk: ID-ene her er plattformfamilier og er ikke de samme som ID-ene i `Game.pla } ``` ---- +--- ### me @@ -441,10 +441,10 @@ register(username: String!, password: String!): AuthPayload! #### Argumenter -| Navn | Type | Default | Beskrivelse | -| ---------- | --------- | ------- | ------------------------------------------------------------ | +| Navn | Type | Default | Beskrivelse | +| ---------- | --------- | ------- | ----------------------------------------------------------------------------------------- | | `username` | `String!` | — | 3-30 tegn: bokstaver A-Z, tall og `_`. Må være unikt uavhengig av store og små bokstaver. | -| `password` | `String!` | — | 8-72 tegn. | +| `password` | `String!` | — | 8-72 tegn. | #### Returnerer @@ -490,10 +490,10 @@ login(username: String!, password: String!): AuthPayload! #### Argumenter -| Navn | Type | Default | Beskrivelse | -| ---------- | --------- | ------- | ----------------------------------------------------- | +| Navn | Type | Default | Beskrivelse | +| ---------- | --------- | ------- | ------------------------------------------------------- | | `username` | `String!` | — | Brukernavn. Skiller ikke mellom store og små bokstaver. | -| `password` | `String!` | — | Passord. | +| `password` | `String!` | — | Passord. | #### Returnerer @@ -539,10 +539,10 @@ Krever innlogging. #### Argumenter -| Navn | Type | Default | Beskrivelse | -| -------- | --------- | ------- | ---------------------------------------------------- | -| `gameId` | `ID!` | — | ID til spillet. | -| `rating` | `Int!` | — | Vurdering fra 1 til 5. | +| Navn | Type | Default | Beskrivelse | +| -------- | --------- | ------- | ------------------------------------------------------------------------------- | +| `gameId` | `ID!` | — | ID til spillet. | +| `rating` | `Int!` | — | Vurdering fra 1 til 5. | | `body` | `String!` | — | Teksten i reviewen, 1-2000 tegn etter at mellomrom i start og slutt er fjernet. | #### Returnerer @@ -627,30 +627,30 @@ mutation { Et spill med detaljer og relasjoner. -| Felt | Type | Beskrivelse | -| --------------------------- | --------------------- | --------------------------------------------------------------------------------------- | -| `id` | `ID!` | ID til spillet. | -| `name` | `String!` | Tittel. | -| `description` | `String` | Beskrivelse i ren tekst. | -| `released` | `String` | Utgivelsesdato, `YYYY-MM-DD`. | -| `website` | `String` | Offisiell nettside. | -| `added` | `Int!` | Antall RAWG-brukere som har lagt spillet til i en liste. Brukes som mål på popularitet. | -| `metacritic` | `Int` | Metacritic-score, 0-100. | -| `rating` | `Float` | RAWG-brukernes snittvurdering, 0-5. | -| `ratingsCount` | `Int!` | Antall vurderinger bak `rating`. | -| `playtime` | `Int` | Gjennomsnittlig spilletid i timer. | -| `esrbRating` | `String` | Amerikansk aldersgrense, f.eks. `Mature`. | -| `backgroundImage` | `String` | URL til forsidebildet. | -| `backgroundImageAdditional` | `String` | URL til et ekstra bilde. | -| `screenshots` | `[String!]!` | URL-er til skjermbilder. Tom liste hvis ingen. | -| `genres` | [`[Facet!]!`](#facet) | Sjangre. | -| `platforms` | [`[Facet!]!`](#facet) | Enkeltkonsoller, f.eks. PlayStation 4. | -| `stores` | [`[Facet!]!`](#facet) | Butikker spillet selges i. | -| `developers` | [`[Facet!]!`](#facet) | Utviklere. | -| `publishers` | [`[Facet!]!`](#facet) | Utgivere. | -| `tags` | [`[Facet!]!`](#facet) | Tagger fra RAWG, f.eks. Open World. | -| `reviews` | [`[Review!]!`](#review) | De 50 nyeste reviewene av spillet, nyeste først. | -| `myReview` | [`Review`](#review) | Innlogget brukers review av spillet. `null` uten innlogging eller review. | +| Felt | Type | Beskrivelse | +| --------------------------- | ----------------------- | --------------------------------------------------------------------------------------- | +| `id` | `ID!` | ID til spillet. | +| `name` | `String!` | Tittel. | +| `description` | `String` | Beskrivelse i ren tekst. | +| `released` | `String` | Utgivelsesdato, `YYYY-MM-DD`. | +| `website` | `String` | Offisiell nettside. | +| `added` | `Int!` | Antall RAWG-brukere som har lagt spillet til i en liste. Brukes som mål på popularitet. | +| `metacritic` | `Int` | Metacritic-score, 0-100. | +| `rating` | `Float` | RAWG-brukernes snittvurdering, 0-5. | +| `ratingsCount` | `Int!` | Antall vurderinger bak `rating`. | +| `playtime` | `Int` | Gjennomsnittlig spilletid i timer. | +| `esrbRating` | `String` | Amerikansk aldersgrense, f.eks. `Mature`. | +| `backgroundImage` | `String` | URL til forsidebildet. | +| `backgroundImageAdditional` | `String` | URL til et ekstra bilde. | +| `screenshots` | `[String!]!` | URL-er til skjermbilder. Tom liste hvis ingen. | +| `genres` | [`[Facet!]!`](#facet) | Sjangre. | +| `platforms` | [`[Facet!]!`](#facet) | Enkeltkonsoller, f.eks. PlayStation 4. | +| `stores` | [`[Facet!]!`](#facet) | Butikker spillet selges i. | +| `developers` | [`[Facet!]!`](#facet) | Utviklere. | +| `publishers` | [`[Facet!]!`](#facet) | Utgivere. | +| `tags` | [`[Facet!]!`](#facet) | Tagger fra RAWG, f.eks. Open World. | +| `reviews` | [`[Review!]!`](#review) | De 50 nyeste reviewene av spillet, nyeste først. | +| `myReview` | [`Review`](#review) | Innlogget brukers review av spillet. `null` uten innlogging eller review. | Relasjonene returnerer en tom liste hvis spillet ikke har noen. @@ -709,52 +709,52 @@ Merk: Spill uten utgivelsesdato utelates når `releasedFrom` eller `releasedTo` Enum for sortering i [`games`](#games). -| Verdi | Beskrivelse | -| ------------ | ------------------------------------------------------------ | -| `POPULARITY` | Mest populære først, etter `added`. | -| `NEWEST` | Nyeste utgivelsesdato først. Spill uten dato kommer sist. | +| Verdi | Beskrivelse | +| ------------ | ------------------------------------------------------------- | +| `POPULARITY` | Mest populære først, etter `added`. | +| `NEWEST` | Nyeste utgivelsesdato først. Spill uten dato kommer sist. | | `METACRITIC` | Høyeste Metacritic-score først. Spill uten score kommer sist. | -| `NAME` | Alfabetisk etter tittel. | +| `NAME` | Alfabetisk etter tittel. | --- ### PageInfo -| Felt | Type | Beskrivelse | -| ----------------- | ---------- | ----------------------------------------------------------------------- | -| `hasNextPage` | `Boolean!` | `true` hvis det finnes flere spill etter denne siden. | +| Felt | Type | Beskrivelse | +| ------------- | ---------- | ----------------------------------------------------------------------- | +| `hasNextPage` | `Boolean!` | `true` hvis det finnes flere spill etter denne siden. | | `hasPrevPage` | `Boolean!` | `true` hvis det finnes spill før denne siden. | -| `startCursor` | `String` | Cursor til første spill på siden. Sendes som `before` for forrige side. | -| `endCursor` | `String` | Cursor til siste spill på siden. Sendes som `after` for neste side. | +| `startCursor` | `String` | Cursor til første spill på siden. Sendes som `before` for forrige side. | +| `endCursor` | `String` | Cursor til siste spill på siden. Sendes som `after` for neste side. | --- ### User -| Felt | Type | Beskrivelse | -| ---------- | --------- | --------------- | +| Felt | Type | Beskrivelse | +| ---------- | --------- | ---------------- | | `id` | `ID!` | ID til brukeren. | -| `username` | `String!` | Brukernavn. | +| `username` | `String!` | Brukernavn. | --- ### AuthPayload -| Felt | Type | Beskrivelse | -| ------- | ------------------- | -------------------------------------------- | -| `token` | `String!` | Token til `Authorization`-headeren. | -| `user` | [`User!`](#user) | Brukeren som ble registrert eller logget inn. | +| Felt | Type | Beskrivelse | +| ------- | ---------------- | --------------------------------------------- | +| `token` | `String!` | Token til `Authorization`-headeren. | +| `user` | [`User!`](#user) | Brukeren som ble registrert eller logget inn. | --- ### Review -| Felt | Type | Beskrivelse | -| ----------- | ---------------- | ----------------------------------------------- | -| `id` | `ID!` | ID til reviewen. | -| `rating` | `Int!` | Vurdering fra 1 til 5. | -| `body` | `String!` | Teksten i reviewen. | +| Felt | Type | Beskrivelse | +| ----------- | ---------------- | -------------------------------------------------- | +| `id` | `ID!` | ID til reviewen. | +| `rating` | `Int!` | Vurdering fra 1 til 5. | +| `body` | `String!` | Teksten i reviewen. | | `createdAt` | `String!` | Tidspunktet reviewen først ble lagret, i ISO 8601. | -| `user` | [`User!`](#user) | Brukeren som skrev reviewen. | +| `user` | [`User!`](#user) | Brukeren som skrev reviewen. | --- diff --git a/backend/src/auth.ts b/backend/src/auth.ts index 30bdfff..c3d37ec 100644 --- a/backend/src/auth.ts +++ b/backend/src/auth.ts @@ -35,4 +35,4 @@ export function requireUser(ctx: { userId: string | null }): string { throw new GraphQLError('Not logged in', { extensions: { code: 'UNAUTHENTICATED' } }); } return ctx.userId; -} \ No newline at end of file +} diff --git a/backend/src/index.ts b/backend/src/index.ts index 25c0de8..b8534c6 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -6,7 +6,6 @@ import { resolvers } from './resolvers.js'; import { Context, createLoaders } from './loaders.js'; import { userIdFromHeader } from './auth.js'; - // apollo-server port number [default port 3001] const port = Number(process.env.PORT ?? 3001); @@ -24,7 +23,7 @@ const server = new ApolloServer({ typeDefs, resolvers }); // pass apolloserver to standaloneserver const { url } = await startStandaloneServer(server, { listen: { port }, - context: async ({ req }) => ({ + context: async ({ req }) => ({ loaders: createLoaders(), userId: userIdFromHeader(req.headers.authorization), }), diff --git a/backend/src/loaders.ts b/backend/src/loaders.ts index cfec17c..79ae0c8 100644 --- a/backend/src/loaders.ts +++ b/backend/src/loaders.ts @@ -37,4 +37,4 @@ export function createLoaders() { export type Context = { loaders: ReturnType; userId: string | null; -}; \ No newline at end of file +}; diff --git a/backend/src/resolvers.ts b/backend/src/resolvers.ts index 88d6acf..d0c29d1 100644 --- a/backend/src/resolvers.ts +++ b/backend/src/resolvers.ts @@ -127,7 +127,7 @@ const SQL_REGISTER = ` `; // SQL reviews -// to_jason gir ut en formattert streng som kan konverteres direkte +// to_jason gir ut en formattert streng som kan konverteres direkte // med new Date(), slipper derfor pg sitt Date objekt const REVIEW_COLS = ` r.id, @@ -219,7 +219,7 @@ async function saveReview(_parent: unknown, args: SaveReviewArgs, ctx: Context) const res = await pool.query(SQL_SAVE_REVIEW, [args.gameId, userId, args.rating, body]); return res.rows[0]; } catch (err) { - if ((err as { code?: string }).code === '23503') { + if ((err as { code?: string }).code === '23503') { throw badInput('Spillet ble ikke funnet...'); } throw err; @@ -292,8 +292,7 @@ function filterValues(args: GamesArgs) { } function relation(key: keyof Context['loaders']) { - return (game: { id: string }, _args: unknown, ctx: Context) => - ctx.loaders[key].load(game.id); + return (game: { id: string }, _args: unknown, ctx: Context) => ctx.loaders[key].load(game.id); } // resolver-funksjoner @@ -392,7 +391,7 @@ export const resolvers = { tags: relation('tags'), reviews: gameReviews, - myReview + myReview, }, GameConnection: { totalCount },