Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
95f6ab9
chore(frontend): legg til react-router
laspark Oct 20, 2026
93b184f
feat(frontend): GraphQL-spørring for ett spill (#37)
laspark Oct 20, 2026
be7c511
feat(frontend): stil for detaljsiden (#37)
laspark Oct 20, 2026
6eb70e4
feat(frontend): detaljside for spill (#37)
laspark Oct 20, 2026
57b6d76
feat(frontend): spillkortet lenker til detaljsiden (#37)
laspark Oct 20, 2026
91b2c66
feat(frontend): router med basename /project2 (#37)
laspark Oct 20, 2026
f4ca480
feat(frontend): ruter for forside og detaljside (#37)
laspark Oct 20, 2026
a3c6d87
Merge pull request #71 from IT2810-H26/feature/detaljside
laspark Oct 20, 2026
d515342
feat(frontend): X-knapp som tømmer søket og viser standardlista
Oct 20, 2026
a696e7d
style(frontend): X-knapp til høyre i søkefeltet
Oct 20, 2026
f9c9530
feat(frontend): logoen er lenke til forsiden
Oct 20, 2026
c44363e
style(frontend): logo som lenke med synlig fokus
Oct 20, 2026
02a7db9
feat(frontend): send onHome og nullstilling av søkefelt gjennom Header
Oct 20, 2026
26f51de
feat(frontend): goHome nullstiller søk, filtre og favorittvisning
Oct 20, 2026
32d951e
Merge pull request #77 from IT2810-H26/feat/x-og-logosend-home
clemenbe Oct 20, 2026
580a8a3
new import script and review table
Oct 21, 2026
b02658f
requirement numpy
Oct 21, 2026
cca6add
Merge pull request #81 from IT2810-H26/database/data-cleanup
albertpl Oct 21, 2026
0279139
fix backend qraphql queries
Oct 21, 2026
ec98916
Merge pull request #82 from IT2810-H26/backend/fix-queries
albertpl Oct 21, 2026
f15fd70
Merge branch 'main' into dev
albertpl Oct 21, 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
1 change: 0 additions & 1 deletion backend/src/resolvers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ const GAME_COLS = `
ratings_count AS "ratingsCount",
playtime,
esrb_rating AS "esrbRating",
dominant_color AS "dominantColor",
background_image AS "backgroundImage",
background_image_additional AS "backgroundImageAdditional",
COALESCE(screenshots, '{}') AS "screenshots"
Expand Down
1 change: 0 additions & 1 deletion backend/src/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,6 @@ export const typeDefs = `#graphql
ratingsCount: Int!,
playtime: Int,
esrbRating: String,
dominantColor: String,
backgroundImage: String,
backgroundImageAdditional: String,
screenshots: [String!]!
Expand Down
2 changes: 1 addition & 1 deletion db/grants.sql
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,5 @@ GRANT INSERT ON users TO webapp;
GRANT USAGE ON SEQUENCE users_id_seq TO webapp;

GRANT INSERT, DELETE ON reviews TO webapp;
GRANT UPDATE (rating, body) ON reviews TO webapp;
GRANT UPDATE (rating, body, hours_played) ON reviews TO webapp;
GRANT USAGE ON SEQUENCE reviews_id_seq TO webapp;
48 changes: 32 additions & 16 deletions db/load.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import html
import os
import sys
import tempfile
Expand All @@ -12,6 +13,9 @@
FORCE = "--force" in sys.argv
WIPE_USERS = "--wipe-user-data" in sys.argv

# Tags used by fewer games than this are dropped (long tail of one-off, noisy tags).
MIN_TAG_GAMES = int(os.environ.get("MIN_TAG_GAMES", "50"))

# (parquet column, lookup table, junction table, junction fk column)
RELATIONS = [
("genres", "genres", "game_genres", "genre_id"),
Expand All @@ -24,20 +28,28 @@
]

GAME_COLUMNS = (
"id, slug, name, name_original, alternative_names, search_names, released, "
"id, slug, name, alternative_names, search_names, released, "
"background_image, background_image_additional, screenshots, rating, ratings_count, "
"added, metacritic, playtime, esrb_rating, description, website, dominant_color"
"added, metacritic, playtime, esrb_rating, description, website"
)

def clean(col: str) -> str:
"""Empty strings and the text 'None' (a Python null written as a string) become NULL."""
return f"NULLIF(NULLIF(trim({col}), ''), 'None')"
"""Empty strings and the texts 'None'/'nan' (nulls written as strings) become NULL."""
return f"CASE WHEN lower(trim({col})) IN ('', 'none', 'nan') THEN NULL ELSE trim({col}) END"


# Pipe-separated names, trimmed, empty entries removed.
# Pipe-separated names, trimmed, empty and null-like entries removed.
ALT_NAMES = (
f"list_filter(list_transform(string_split({clean('alternative_names')}, '|'), s -> trim(s)), "
"s -> s NOT IN ('', 'None'))"
"s -> lower(s) NOT IN ('', 'none', 'nan'))"
)

# description_raw is plain text; description is the fallback (tags stripped, HTML entities kept).
# Both get entities decoded and leftover Markdown headings / HTML tags removed.
DESCRIPTION = (
"NULLIF(trim(regexp_replace(unescape(COALESCE("
f"{clean('description_raw')}, {clean('description')})), "
r"'#{2,}\s*|<[^>]+>', '', 'g')), '')"
)

# short_screenshots holds Python-style dicts ({'id': 123, 'image': '...'}), not JSON.
Expand Down Expand Up @@ -69,22 +81,23 @@ def pg_array(list_expr: str) -> str:

def export_csvs(out: Path) -> None:
con = duckdb.connect()
con.create_function("unescape", html.unescape, ["VARCHAR"], "VARCHAR")
con.execute(
f"""CREATE VIEW src AS
SELECT * FROM read_parquet('{PARQUET}')
WHERE added > 0 AND name IS NOT NULL"""
WHERE added > 0 AND name IS NOT NULL AND trim(name) <> ''"""
)
con.execute(
f"""COPY (
SELECT
id,
slug,
name,
NULLIF({clean('name_original')}, name) AS name_original,
{pg_array(ALT_NAMES)} AS alternative_names,
concat_ws(' | ', name, NULLIF({clean('name_original')}, name),
array_to_string({ALT_NAMES}, ' | ')) AS search_names,
TRY_CAST(released AS DATE) AS released,
concat_ws(' | ', name, array_to_string({ALT_NAMES}, ' | ')) AS search_names,
-- Dates far in the future are placeholders, not real release dates.
CASE WHEN TRY_CAST(released AS DATE) <= current_date + INTERVAL 5 YEAR
THEN TRY_CAST(released AS DATE) END AS released,
{clean('background_image')} AS background_image,
{clean('background_image_additional')} AS background_image_additional,
{pg_array(SCREENSHOTS)} AS screenshots,
Expand All @@ -94,9 +107,8 @@ def export_csvs(out: Path) -> None:
TRY_CAST(metacritic AS INTEGER) AS metacritic,
TRY_CAST(playtime AS INTEGER) AS playtime,
{ESRB} AS esrb_rating,
{clean('description_raw')} AS description,
{clean('website')} AS website,
{clean('dominant_color')} AS dominant_color
{DESCRIPTION} AS description,
{clean('website')} AS website
FROM src
) TO '{out}/games.csv' (FORMAT csv, HEADER)"""
)
Expand All @@ -106,10 +118,14 @@ def export_csvs(out: Path) -> None:
SELECT id AS game_id, trim(unnest(string_split({col}, '|'))) AS name
FROM src WHERE {col} IS NOT NULL"""
)
min_games = MIN_TAG_GAMES if table == "tags" else 1
con.execute(
"""CREATE OR REPLACE TEMP TABLE lk AS
f"""CREATE OR REPLACE TEMP TABLE lk AS
SELECT row_number() OVER (ORDER BY name) AS id, name
FROM (SELECT DISTINCT name FROM pairs WHERE name NOT IN ('', 'None'))"""
FROM (SELECT name FROM pairs
WHERE name NOT IN ('', 'None')
GROUP BY name
HAVING count(DISTINCT game_id) >= {min_games})"""
)
con.execute(f"COPY (SELECT id, name FROM lk) TO '{out}/{table}.csv' (FORMAT csv, HEADER)")
con.execute(
Expand Down
1 change: 1 addition & 0 deletions db/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
duckdb
numpy
psycopg[binary]
5 changes: 2 additions & 3 deletions db/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ CREATE TABLE games (
id BIGINT PRIMARY KEY,
slug TEXT,
name TEXT NOT NULL,
name_original TEXT,
alternative_names TEXT[],
search_names TEXT NOT NULL,
released DATE,
Expand All @@ -22,8 +21,7 @@ CREATE TABLE games (
playtime INTEGER,
esrb_rating TEXT,
description TEXT,
website TEXT,
dominant_color TEXT
website TEXT
);

CREATE TABLE genres (id INTEGER PRIMARY KEY, name TEXT NOT NULL UNIQUE);
Expand Down Expand Up @@ -83,6 +81,7 @@ CREATE TABLE reviews (
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
rating SMALLINT NOT NULL CHECK (rating BETWEEN 1 AND 5),
body TEXT NOT NULL CHECK (char_length(body) BETWEEN 1 AND 2000),
hours_played SMALLINT CHECK (hours_played BETWEEN 1 AND 10000),
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
UNIQUE (game_id, user_id)
);
3 changes: 2 additions & 1 deletion frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"@apollo/client": "^4.3.1",
"graphql": "^17.0.2",
"react": "^19.2.8",
"react-dom": "^19.2.8"
"react-dom": "^19.2.8",
"react-router": "^8.4.0"
},
"devDependencies": {
"@babel/core": "^7.29.7",
Expand Down
Loading