Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Artist controller: Access by slug or id #75

Merged
merged 6 commits into from
Jul 15, 2022
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
6 changes: 4 additions & 2 deletions src/album/models/album.query-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ namespace AlbumQueryParameters {
*/
export function buildQueryParametersForMany(where: ManyWhereInput): Prisma.AlbumWhereInput {
return {
artist: where.byArtist ?
ArtistQueryParameters.buildQueryParametersForOne(where.byArtist)
artist: where.byArtist
? where.byArtist.compilationArtist
? null
: ArtistQueryParameters.buildQueryParametersForOne(where.byArtist)
: where.byArtist,
name: buildStringSearchParameters(where.byName),
releases: where.byLibrarySource ? {
Expand Down
20 changes: 20 additions & 0 deletions src/artist/artist.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import TrackModule from "src/track/track.module";
import ReleaseModule from "src/release/release.module";
import MetadataModule from "src/metadata/metadata.module";
import ReleaseService from "src/release/release.service";
import compilationAlbumArtistKeyword from "src/utils/compilation";

describe('Artist Controller', () => {
let artistService: ArtistService;
Expand Down Expand Up @@ -161,6 +162,19 @@ describe('Artist Controller', () => {
});
});
});

it("should get the artist (w/ slug)", () => {
return request(app.getHttpServer())
.get(`/artists/${artist1.slug}`)
.expect(200)
.expect((res) => {
let artist: Artist = res.body;
expect(artist).toStrictEqual({
...artist1,
illustration: `http://meelo.com/artists/${artist1.id}/illustration`
});
});
});
it("should get the artist w/ songs", () => {
return request(app.getHttpServer())
.get(`/artists/${artist1.id}?with=songs`)
Expand All @@ -185,6 +199,12 @@ describe('Artist Controller', () => {
.get(`/artists/${-1}`)
.expect(404);
});

it("should return an error, as the compilation artist 'does not exist'", () => {
return request(app.getHttpServer())
.get(`/artists/${compilationAlbumArtistKeyword}`)
.expect(400);
});
});

describe('Get Artist\'s Songs (GET /artists/:id/songs)', () => {
Expand Down
32 changes: 16 additions & 16 deletions src/artist/artist.controller.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { Body, Controller, DefaultValuePipe, forwardRef, Get, Inject, Param, ParseBoolPipe, Post, Query, Response } from '@nestjs/common';
import AlbumService from 'src/album/album.service';
import AlbumQueryParameters from 'src/album/models/album.query-parameters';
import { ParseIdPipe } from 'src/id/id.pipe';
import IllustrationService from 'src/illustration/illustration.service';
import type { IllustrationDownloadDto } from 'src/illustration/models/illustration-dl.dto';
import type { PaginationParameters } from 'src/pagination/models/pagination-parameters';
import ParsePaginationParameterPipe from 'src/pagination/pagination.pipe';
import Slug from 'src/slug/slug';
import SongQueryParameters from 'src/song/models/song.query-params';
import SongService from 'src/song/song.service';
import ParseArtistIdentifierPipe from './artist.pipe';
import ArtistService from './artist.service';
import ArtistQueryParameters from './models/artist.query-parameters';

Expand Down Expand Up @@ -48,23 +48,23 @@ export default class ArtistController {

@Get(':id')
async getArtist(
@Param('id', ParseIdPipe)
artistId: number,
@Param(ParseArtistIdentifierPipe)
where: ArtistQueryParameters.WhereInput,
@Query('with', ArtistQueryParameters.ParseRelationIncludePipe)
include: ArtistQueryParameters.RelationInclude
) {
let artist = await this.artistService.getArtist({ id: artistId }, include);
let artist = await this.artistService.getArtist(where, include);
return this.artistService.buildArtistResponse(artist);
}

@Get(':id/illustration')
async getArtistIllustration(
@Param('id', ParseIdPipe)
artistId: number,
@Param(ParseArtistIdentifierPipe)
where: ArtistQueryParameters.WhereInput,
@Response({ passthrough: true })
res: Response
) {
let artist = await this.artistService.getArtist({ id: artistId });
let artist = await this.artistService.getArtist(where);
return this.illustrationService.streamIllustration(
this.illustrationService.buildArtistIllustrationPath(new Slug(artist.slug)),
artist.slug, res
Expand All @@ -73,12 +73,12 @@ export default class ArtistController {

@Post('/:id/illustration')
async updateArtistIllustration(
@Param('id', ParseIdPipe)
artistId: number,
@Param(ParseArtistIdentifierPipe)
where: ArtistQueryParameters.WhereInput,
@Body()
illustrationDto: IllustrationDownloadDto
) {
let artist = await this.artistService.getArtist({ id: artistId });
let artist = await this.artistService.getArtist(where);
const artistIllustrationPath = this.illustrationService.buildArtistIllustrationPath(new Slug(artist.slug));
return this.illustrationService.downloadIllustration(
illustrationDto.url,
Expand All @@ -90,13 +90,13 @@ export default class ArtistController {
async getArtistAlbums(
@Query(ParsePaginationParameterPipe)
paginationParameters: PaginationParameters,
@Param('id', ParseIdPipe)
artistId: number,
@Param(ParseArtistIdentifierPipe)
where: ArtistQueryParameters.WhereInput,
@Query('with', AlbumQueryParameters.ParseRelationIncludePipe)
include: AlbumQueryParameters.RelationInclude
) {
let albums = await this.albumService.getAlbums({
byArtist: { id: artistId }
byArtist: where
}, paginationParameters, include);
return albums.map((album) => this.albumService.buildAlbumResponse(album));
}
Expand All @@ -105,13 +105,13 @@ export default class ArtistController {
async getArtistSongs(
@Query(ParsePaginationParameterPipe)
paginationParameters: PaginationParameters,
@Param('id', ParseIdPipe)
artistId: number,
@Param(ParseArtistIdentifierPipe)
where: ArtistQueryParameters.WhereInput,
@Query('with', SongQueryParameters.ParseRelationIncludePipe)
include: SongQueryParameters.RelationInclude
) {
let songs = await this.songService.getSongs({
artist: { id: artistId }
artist: where
}, paginationParameters, include);
return songs.map((song) => this.songService.buildSongResponse(song));
}
Expand Down
8 changes: 7 additions & 1 deletion src/artist/artist.exceptions.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AlreadyExistsException, NotFoundException } from "src/exceptions/meelo-exception";
import { AlreadyExistsException, InvalidRequestException, NotFoundException } from "src/exceptions/meelo-exception";
import type Slug from "src/slug/slug";

export class ArtistNotFoundException extends NotFoundException {
Expand All @@ -7,6 +7,12 @@ export class ArtistNotFoundException extends NotFoundException {
}
}

export class CompilationArtistException extends InvalidRequestException {
constructor(resourceName: string) {
super(`The '${resourceName}' resource can not be accessed for the 'Compilation' artist`);
}
}

export class ArtistNotFoundByIDException extends NotFoundException {
constructor(artistId: number) {
super(`Artist with id '${artistId}' not found`);
Expand Down
15 changes: 15 additions & 0 deletions src/artist/artist.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ArgumentMetadata } from "@nestjs/common";
import ParseResourceIdentifierPipe from "src/identifier/identifier.pipe";
import compilationAlbumArtistKeyword from "src/utils/compilation";
import type ArtistQueryParameters from "./models/artist.query-parameters";

class ParseArtistIdentifierPipe extends ParseResourceIdentifierPipe<ArtistQueryParameters.WhereInput> {
transform<T extends { id: any; }>(value: T, _metadata: ArgumentMetadata): ArtistQueryParameters.WhereInput {
const transformedIdentifier = super.transform(value, _metadata);
if (transformedIdentifier.slug?.toString() == compilationAlbumArtistKeyword) {
return { compilationArtist: true };
}
return transformedIdentifier;
}
};
export default ParseArtistIdentifierPipe;
8 changes: 7 additions & 1 deletion src/artist/artist.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import Slug from 'src/slug/slug';
import { ArtistAlreadyExistsException as ArtistAlreadyExistsException, ArtistNotFoundByIDException, ArtistNotFoundException } from './artist.exceptions';
import { ArtistAlreadyExistsException as ArtistAlreadyExistsException, ArtistNotFoundByIDException, ArtistNotFoundException, CompilationArtistException } from './artist.exceptions';
import type { Album, Artist, Song } from '@prisma/client';
import PrismaService from 'src/prisma/prisma.service';
import ArtistQueryParameters from './models/artist.query-parameters';
Expand Down Expand Up @@ -49,6 +49,8 @@ export default class ArtistService {
* @param include the relations to include in the returned artist
*/
async getArtist(where: ArtistQueryParameters.WhereInput, include?: ArtistQueryParameters.RelationInclude) {
if (where.compilationArtist)
throw new CompilationArtistException('Artist');
try {
return await this.prismaService.artist.findUnique({
rejectOnNotFound: true,
Expand Down Expand Up @@ -93,6 +95,8 @@ export default class ArtistService {
* @returns the updated artist
*/
async updateArtist(what: ArtistQueryParameters.UpdateInput, where: ArtistQueryParameters.WhereInput): Promise<Artist> {
if (where.compilationArtist)
throw new CompilationArtistException('Artist');
try {
return await this.prismaService.artist.update({
data: {
Expand All @@ -114,6 +118,8 @@ export default class ArtistService {
* @param where the query parameters to find the album to delete
*/
async deleteArtist(where: ArtistQueryParameters.WhereInput): Promise<void> {
if (where.compilationArtist)
throw new CompilationArtistException('Artist');
try {
await this.prismaService.artist.delete({
where: ArtistQueryParameters.buildQueryParametersForOne(where)
Expand Down
3 changes: 2 additions & 1 deletion src/artist/models/artist.query-parameters.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@ namespace ArtistQueryParameters {
*/
export type WhereInput = RequireOnlyOne<{
id: number,
slug: Slug
slug: Slug,
compilationArtist: true
}>;
/**
* Build the query parameters for ORM, to select one artist
Expand Down
7 changes: 7 additions & 0 deletions src/identifier/id.exceptions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { InvalidRequestException } from "src/exceptions/meelo-exception";

export default class InvalidIdParsingInput extends InvalidRequestException {
constructor(badInput: string) {
super(`Parsing Resource's ID failed, expected a number, got '${badInput}'`);
}
}
12 changes: 12 additions & 0 deletions src/identifier/id.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { PipeTransform, Injectable, ArgumentMetadata } from '@nestjs/common';
import InvalidIdParsingInput from './id.exceptions';
@Injectable()
export class ParseIdPipe implements PipeTransform<string> {
transform(value: string, _metadata: ArgumentMetadata): number {
let parsedId: number = parseInt(value);
if (isNaN(parsedId)) {
throw new InvalidIdParsingInput(value);
}
return parsedId;
}
}
15 changes: 15 additions & 0 deletions src/identifier/identifier.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { ArgumentMetadata, PipeTransform } from "@nestjs/common";
import { ParseIdPipe } from "src/identifier/id.pipe";
import Slug from "src/slug/slug";

export default class ParseResourceIdentifierPipe<W extends Partial<{ id: number, slug: Slug}>> implements PipeTransform {
transform<T extends { id: any }>(value: T, _metadata: ArgumentMetadata): W {
try {
const id = new ParseIdPipe().transform(value.id, _metadata);
return <W>{ id: id };
} catch {
return <W>{ slug: new Slug(value.id) };
}
}

}
3 changes: 1 addition & 2 deletions src/library/library.controller.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Body, Controller, Get, Logger, Param, Post, Query } from '@nestjs/common';

import LibraryService from './library.service';
import { LibraryDto } from './models/library.dto';
import type { Library } from '@prisma/client';
Expand All @@ -16,8 +15,8 @@ import TrackQueryParameters from 'src/track/models/track.query-parameters';
import TrackService from 'src/track/track.service';
import ReleaseService from 'src/release/release.service';
import SongService from 'src/song/song.service';
import ParseLibraryIdentifierPipe from './library.pipe';
import type LibraryQueryParameters from './models/library.query-parameters';
import ParseLibraryIdentifierPipe from './library.pipe';

@Controller('libraries')
export default class LibraryController {
Expand Down
17 changes: 3 additions & 14 deletions src/library/library.pipe.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,5 @@
import type { ArgumentMetadata, PipeTransform } from "@nestjs/common";
import { ParseIdPipe } from "src/id/id.pipe";
import Slug from "src/slug/slug";
import ParseResourceIdentifierPipe from "src/identifier/identifier.pipe";
import type LibraryQueryParameters from "./models/library.query-parameters";

export default class ParseLibraryIdentifierPipe<T extends { id: any }> implements PipeTransform<T> {
transform(value: T, _metadata: ArgumentMetadata): LibraryQueryParameters.WhereInput {
try {
const id = new ParseIdPipe().transform(value.id, _metadata);
return { id: id };
} catch {
return { slug: new Slug(value.id) }
}
}

}
class ParseLibraryIdentifierPipe extends ParseResourceIdentifierPipe<LibraryQueryParameters.WhereInput> {};
export default ParseLibraryIdentifierPipe;
3 changes: 3 additions & 0 deletions src/song/models/song.query-params.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { RelationInclude as BaseRelationInclude } from "src/relation-includ
import type LibraryQueryParameters from "src/library/models/library.query-parameters";
import TrackQueryParameters from "src/track/models/track.query-parameters";
import ParseBaseRelationIncludePipe from 'src/relation-include/relation-include.pipe';
import { CompilationArtistException } from "src/artist/artist.exceptions";

namespace SongQueryParameters {
type OmitArtistId<T> = Omit<T, 'artistId'>;
Expand Down Expand Up @@ -65,6 +66,8 @@ namespace SongQueryParameters {
* @returns the ORM-ready query parameters
*/
export function buildQueryParametersForMany(where: ManyWhereInput) {
if (where.artist?.compilationArtist)
throw new CompilationArtistException('Song');
return {
artistId: where.artist?.id,
artist: where.artist?.slug ? {
Expand Down