Skip to content

Commit

Permalink
Artist controller: Access by slug or id
Browse files Browse the repository at this point in the history
  • Loading branch information
Arthi-chaud committed Jul 15, 2022
2 parents 41f2bc1 + fda92a3 commit 0044e5b
Show file tree
Hide file tree
Showing 13 changed files with 112 additions and 37 deletions.
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

0 comments on commit 0044e5b

Please sign in to comment.