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
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,13 @@ dist-ssr
*.njsproj
*.sln
*.sw?

# Backend local dataset & media caches (ETL)
server/data/raw/*
!server/data/raw/.gitkeep

server/data/covers/*
!server/data/covers/.gitkeep

# Environment variables
.env
32 changes: 1 addition & 31 deletions package-lock.json

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

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
"devDependencies": {
"@eslint/js": "^10.0.1",
"@types/node": "^24.13.3",
"@types/react": "^19.2.18",
"@types/react": "^19.3.0",
"@types/react-dom": "^19.2.7",
"@vitejs/plugin-react": "^6.1.1",
"eslint": "^10.10.0",
Expand Down
Empty file.
76 changes: 76 additions & 0 deletions server/src/database/migrations/schema.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS pg_trgm;

-- album-tabell
CREATE TABLE IF NOT EXISTS albums (
id VARCHAR(64) PRIMARY KEY, -- MusicBrainz release-group MBID
title VARCHAR(255) NOT NULL,
artist VARCHAR(255) NOT NULL,
release_year INT NOT NULL,
cover_url TEXT,
);

-- sjangre (mange-til-mange relasjon)
CREATE TABLE IF NOT EXISTS genres (
id SERIAL PRIMARY KEY,
name VARCHAR(100) UNIQUE NOT NULL
);

CREATE TABLE IF NOT EXISTS album_genres (
album_id VARCHAR(64) REFERENCES albums(id) ON DELETE CASCADE,
genre_id INT REFERENCES genres(id) ON DELETE CASCADE,
PRIMARY KEY (album_id, genre_id)
);

-- tracks-tabell (en-til-mange: et album har mange spor)
CREATE TABLE IF NOT EXISTS tracks (
id SERIAL PRIMARY KEY,
album_id VARCHAR(64) NOT NULL REFERENCES albums(id) ON DELETE CASCADE,
position INT NOT NULL,
title VARCHAR(255) NOT NULL,
duration VARCHAR(50) NOT NULL, -- f.eks. "3:45" eller millisekunder
);

-- reviews and ratings (for UI innhold)
CREATE TABLE IF NOT EXISTS reviews (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
album_id VARCHAR(64) NOT NULL REFERENCES albums(id) ON DELETE CASCADE,
user_name VARCHAR(100) NOT NULL DEFAULT 'Anonym',
rating INT NOT NULL CHECK (rating >= 1 AND rating <= 5),
comment TEXT,
created_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP
);

-- for collections/favs for brukere
DO $$ BEGIN
CREATE TYPE collection_type_enum AS ENUM ('favorites', 'has_cd', 'wants_cd');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;

CREATE TABLE IF NOT EXISTS user_collections (
user_id VARCHAR(255) NOT NULL,
album_id VARCHAR(64) REFERENCES albums(id) ON DELETE CASCADE,
collection_type collection_type_enum NOT NULL,
added_at TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, album_id, collection_type)
);


-- INDEKSER FOR HØY YTELSE (Søk, filtrering, sortering og paginering)

-- Indeks for tekstsøk på tittel og artist
CREATE INDEX IF NOT EXISTS idx_albums_title_trgm ON albums USING gin (title gin_trgm_ops);
CREATE INDEX IF NOT EXISTS idx_albums_artist_trgm ON albums USING gin (artist gin_trgm_ops);

-- Indeks for sortering på utgivelsesår
CREATE INDEX IF NOT EXISTS idx_albums_decade ON albums ((release_year / 10 * 10));

-- Indeks for hurtigoppslag på spor per album
CREATE INDEX IF NOT EXISTS idx_tracks_album_id ON tracks(album_id);

-- Indeks for anmeldelser per album
CREATE INDEX IF NOT EXISTS idx_reviews_album_id ON reviews(album_id);



30 changes: 30 additions & 0 deletions server/src/database/migrations/seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
-- Labels
INSERT INTO labels (id, name) VALUES (1, 'Columbia Records'), (2, 'Harvest Records') ON CONFLICT DO NOTHING;

-- Albumer
INSERT INTO albums (id, title, artist, release_year, cover_path, label_id) VALUES
('mbid-1', 'Abbey Road', 'The Beatles', 1969, '/covers/abbey-road.webp', 1),
('mbid-2', 'The Dark Side of the Moon', 'Pink Floyd', 1973, '/covers/dark-side.webp', 2),
('mbid-3', 'Thriller', 'Michael Jackson', 1982, '/covers/thriller.webp', 1),
('mbid-4', 'Back in Black', 'AC/DC', 1980, '/covers/back-in-black.webp', 1)
ON CONFLICT DO NOTHING;

-- Sjangre
INSERT INTO genres (id, name) VALUES (1, 'Rock'), (2, 'Progressive Rock'), (3, 'Pop') ON CONFLICT DO NOTHING;

-- Koble albumer og sjangre
INSERT INTO album_genres (album_id, genre_id) VALUES
('mbid-1', 1),
('mbid-2', 1),
('mbid-2', 2),
('mbid-3', 3),
('mbid-4', 1)
ON CONFLICT DO NOTHING;

-- Sporliste (Task 6)
INSERT INTO tracks (album_id, title, duration_ms, track_number) VALUES
('mbid-1', 'Come Together', 259000, 1),
('mbid-1', 'Something', 182000, 2),
('mbid-2', 'Speak to Me', 67000, 1),
('mbid-2', 'Breathe', 163000, 2)
ON CONFLICT DO NOTHING;
65 changes: 65 additions & 0 deletions server/src/graphQL/typeDefs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
export const typeDefs = `#graphql
type Label {
id: ID!
name: String!
}
type Track {
id: ID!
title: String!
durationMs: Int
trackNumber: Int!
}
type Album {
id: ID!
title: String!
artist: String!
releaseYear: Int!
coverPath: String
label: Label
genres: [String!]!
tracks: [Track!]!
}
type PageInfo {
endCursor: String
hasNextPage: Boolean!
}
type AlbumEdge {
cursor: String!
node: Album!
}
type AlbumConnection {
edges: [AlbumEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
enum SortOption {
TITLE_ASC
TITLE_DESC
YEAR_ASC
YEAR_DESC
}
type Query {
# Task 2 & 5: Paginert søk, filtrering på sjanger, tiår og sortering
albums(
search: String
genre: String
decade: Int
sortBy: SortOption
first: Int = 10
after: String
): AlbumConnection!
# Task 6: Detaljspørring for enkeltalbum
album(id: ID!): Album
# Hente alle sjangre for filter-dropdown i frontend
genres: [String!]!
}
`;
Empty file added server/tsconfig.json
Empty file.