Skip to content
Merged
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
174 changes: 115 additions & 59 deletions server/src/graphQL/resolvers.ts
Original file line number Diff line number Diff line change
@@ -1,113 +1,169 @@
import { pool } from '../database/connection.js';

const formatCoverUrl = (filename: string | null) => {
if (!filename) return null;
if (filename.startsWith('http://') || filename.startsWith('https://')) {
return filename;
}
const baseUrl = process.env.SERVER_URL || 'http://it2810-05.idi.ntnu.no/project2';
const cleanFilename = filename.replace(/^\/+/, '');
return `${baseUrl}/covers/${cleanFilename}`;
};

export const resolvers = {
Query: {
albums: async (_: unknown, args: { search?: string; genre?: string; decade?: number; first?: number }) => {
let sql = `
SELECT
a.id,
a.title,
a.artist,
a.release_year AS "releaseYear",
g.name AS genre,
a.cover_path AS "coverUrl"
FROM albums a
LEFT JOIN album_genres ag ON ag.album_id = a.id
LEFT JOIN genres g ON g.id = ag.genre_id
WHERE 1=1
`;
albums: async (
_: unknown,
args: {
search?: string;
genre?: string;
decade?: number;
first?: number;
after?: string;
}
) => {
const queryParams: (string | number)[] = [];

const conditions: string[] = [];

if (args.search) {
queryParams.push(`%${args.search}%`);
sql += ` AND (LOWER(a.title) LIKE LOWER($${queryParams.length}) OR LOWER(a.artist) LIKE LOWER($${queryParams.length}))`;
}
const searchParam = queryParams.length;

conditions.push(
`(LOWER(a.title) LIKE LOWER($${searchParam}) OR LOWER(a.artist) LIKE LOWER($${searchParam}))`
);
}

if (args.genre) {
queryParams.push(args.genre);
sql += ` AND LOWER(g.name) = LOWER($${queryParams.length})`;
}
const genreParam = queryParams.length;

conditions.push(
`LOWER(g.name) = LOWER($${genreParam})`
);
}

if (args.decade) {
if (args.decade !== undefined) {
queryParams.push(args.decade);
sql += ` AND a.release_year >= $${queryParams.length} AND a.release_year < $${queryParams.length} + 10`;
const decadeParam = queryParams.length;

conditions.push(
`a.release_year >= $${decadeParam} AND a.release_year < $${decadeParam} + 10`
);
}

const limit = args.first || 10;

const whereClause =
conditions.length > 0
? `WHERE ${conditions.join(' AND ')}`
: '';

queryParams.push(limit + 1);
sql += ` ORDER BY a.title ASC LIMIT $${queryParams.length}`;
const countSql = `
SELECT COUNT(DISTINCT a.id)::int AS count
FROM albums a
LEFT JOIN album_genres ag ON ag.album_id = a.id
LEFT JOIN genres g ON g.id = ag.genre_id
${whereClause}
`;

const { rows } = await pool.query(sql, queryParams);
const { rows: countRows } = await pool.query(
countSql,
queryParams
);

const hasNextPage = rows.length > limit;
const sliced = hasNextPage ? rows.slice(0, limit) : rows;
const totalCount = countRows[0]?.count ?? 0;

const formattedRows = sliced.map((album) => ({
...album,
coverUrl: formatCoverUrl(album.coverUrl),
}));
const limit = args.first ?? 8;

const parsedOffset = Number.parseInt(
args.after ?? '0',
10
);

const offset = Number.isNaN(parsedOffset)
? 0
: parsedOffset;

const dataParams = [...queryParams];

dataParams.push(limit + 1);
const limitParam = dataParams.length;

dataParams.push(offset);
const offsetParam = dataParams.length;

const sql = `
SELECT
a.id,
a.title,
a.artist,
a.release_year AS "releaseYear",
g.name AS genre,
a.cover_path AS "coverUrl"
FROM albums a
LEFT JOIN album_genres ag ON ag.album_id = a.id
LEFT JOIN genres g ON g.id = ag.genre_id
${whereClause}
ORDER BY a.title ASC
LIMIT $${limitParam}
OFFSET $${offsetParam}
`;

const { rows } = await pool.query(
sql,
dataParams
);

const hasNextPage = rows.length > limit;
const sliced = hasNextPage
? rows.slice(0, limit)
: rows;

return {
edges: formattedRows,
edges: sliced,
totalCount,
pageInfo: {
endCursor: formattedRows.length > 0 ? formattedRows[formattedRows.length - 1].id : null,
endCursor:
sliced.length > 0
? String(offset + sliced.length)
: null,
hasNextPage,
},
};
},

album: async (_: unknown, args: { id: string }) => {
const albumSql = `
SELECT
a.id,
a.title,
a.artist,
a.release_year AS "releaseYear",
g.name AS genre,
SELECT
a.id,
a.title,
a.artist,
a.release_year AS "releaseYear",
g.name AS genre,
a.cover_path AS "coverUrl"
FROM albums a
LEFT JOIN album_genres ag ON ag.album_id = a.id
LEFT JOIN genres g ON g.id = ag.genre_id
WHERE a.id = $1
`;
const { rows: albumRows } = await pool.query(albumSql, [args.id]);

const { rows: albumRows } = await pool.query(
albumSql,
[args.id]
);

if (albumRows.length === 0) {
return null;
}

const album = albumRows[0];


const tracksSql = `
SELECT
id,
title,
duration_ms AS "duration"
FROM tracks
SELECT
id,
title,
duration_ms AS "duration"
FROM tracks
WHERE album_id = $1
ORDER BY track_number ASC
`;
const { rows: trackRows } = await pool.query(tracksSql, [args.id]);

const { rows: trackRows } = await pool.query(
tracksSql,
[args.id]
);

return {
...album,
coverUrl: formatCoverUrl(album.coverUrl),
tracks: trackRows,
};
},
Expand Down