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

handle get studio info #46

Merged
merged 2 commits into from
Jun 30, 2024
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
29 changes: 29 additions & 0 deletions src/common/pipes/builder-select-studio.pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { PipeTransform } from '@nestjs/common';
import { fieldsMap } from 'graphql-fields-list';
import { QueryStudioArg } from '~/graphql/types/args/query-studio.arg';
import { NotFoundStudioError } from '~/graphql/types/dtos/studio/not-found-studio.error';
import {
MapResultSelect,
reverseBooleanValueInObj,
} from '~/utils/tools/object';

export class BuilderSelectStudioPipe implements PipeTransform {
transform(value: any): MapResultSelect {
/*
* We don't want get fields from Error query and pass to Select TypeORM
*/
const fieldsToSkip = Object.getOwnPropertyNames(
new NotFoundStudioError({
requestObject: new QueryStudioArg(),
}),
);

const mappedSelect = reverseBooleanValueInObj(
fieldsMap(value, {
skip: [...fieldsToSkip, '__typename', 'anime.pageInfo', 'pageInfo'],
}),
);

return mappedSelect;
}
}
9 changes: 8 additions & 1 deletion src/contracts/repositories/studio-repository.interface.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
import { Studio } from '~/models/studio.model';
import { IBaseRepository } from './base-repository.interface';
import { MapResultSelect } from '~/utils/tools/object';
import { QueryStudioArg } from '~/graphql/types/args/query-studio.arg';

export interface IStudioRepository extends IBaseRepository<Studio> {}
export interface IStudioRepository extends IBaseRepository<Studio> {
getStudioByConditions(
mapResultSelectParam: MapResultSelect,
queryAnimeArg: QueryStudioArg,
): Promise<Studio | null>;
}

export const IStudioRepository = Symbol('IStudioRepository ');
17 changes: 15 additions & 2 deletions src/contracts/services/studio-service.interface.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,21 @@ import { StudioEdge } from '~/models/studio-edge.model';
import { Studio } from '~/models/studio.model';
import { StudioConnection } from '~/models/sub-models/studio-sub-models/studio-connection.model';
import { IPaginateResult } from '../dtos';
import { MapResultSelect } from '~/utils/tools/object';
import { QueryStudioArg } from '~/graphql/types/args/query-studio.arg';
import { Either } from '~/utils/tools/either';
import { NotFoundStudioError } from '~/graphql/types/dtos/studio/not-found-studio.error';

export interface IStudioService {
export interface IStudioExternalService {
getStudioByConditions(
mapResultSelect: MapResultSelect,
queryAnimeArg: QueryStudioArg,
): Promise<Either<NotFoundStudioError, never> | Either<never, Studio>>;
}

export const IStudioExternalService = Symbol('IStudioExternalService');

export interface IStudioInternalService {
saveManyStudio(studios: Partial<Studio>[]): Promise<Studio[] | null>;

getStudioListV1(
Expand All @@ -27,4 +40,4 @@ export interface IStudioService {
): Promise<(Partial<StudioConnection> & StudioConnection) | null>;
}

export const IStudioService = Symbol('IStudioService');
export const IStudioInternalService = Symbol('IStudioInternalService');
83 changes: 83 additions & 0 deletions src/graphql/resolvers/studio.resolver.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {
Args,
Info,
Parent,
Query,
ResolveField,
Resolver,
} from '@nestjs/graphql';
import { StudioDto } from '~/common/dtos/studio-dtos/studio.dto';
import { StudioResultUnion } from '../types/dtos/studio/studio.response';
import { StudioActions } from '../types/enums/actions.enum';
import { BuilderSelectStudioPipe } from '~/common/pipes/builder-select-studio.pipe';
import { MapResultSelect } from '~/utils/tools/object';
import { QueryStudioArg } from '../types/args/query-studio.arg';
import { Inject } from '@nestjs/common';
import {
IAnimeExternalService,
IStudioExternalService,
} from '~/contracts/services';
import { InjectMapper } from '@automapper/nestjs';
import { Mapper } from '@automapper/core';
import { Studio } from '~/models/studio.model';
import { nameof } from 'ts-simple-nameof';
import { AnimeConnectionDto } from '~/common/dtos/anime-dtos/anime-connection.dto';
import { QueryAnimeConnectionArg } from '../types/args/query-anime-connection.arg';
import { AnimeConnection } from '~/models/sub-models/anime-sub-models';

@Resolver(() => StudioDto)
export class StudioResolver {
constructor(
@Inject(IStudioExternalService)
private readonly studioExternalService: IStudioExternalService,

@Inject(IAnimeExternalService)
private readonly animeService: IAnimeExternalService,

@InjectMapper() private readonly mapper: Mapper,
) {}

@Query(() => [StudioResultUnion], { name: StudioActions.Studio })
public async getStudioInfo(
@Info(BuilderSelectStudioPipe) mapResultSelect: MapResultSelect,
@Args() queryAnimeArg: QueryStudioArg,
) {
const result = await this.studioExternalService.getStudioByConditions(
mapResultSelect,
queryAnimeArg,
);

if (result.isError()) {
return [result.value];
}

return [this.mapper.map(result.value, Studio, StudioDto)];
}

@ResolveField(
nameof<Studio>((s) => s.anime),
(returns) => AnimeConnectionDto,
{ nullable: true },
)
public async getAnimeByCharacter(
@Parent() studioDto: StudioDto,
@Args() queryAnimeConnectionArg: QueryAnimeConnectionArg,
@Info(BuilderSelectStudioPipe) mapResultSelect: MapResultSelect,
) {
if (!studioDto?.anime?.id) return null;

const animeConnection = await this.animeService.getAnimeConnectionPage(
studioDto?.anime?.id,
mapResultSelect,
queryAnimeConnectionArg,
);

if (!animeConnection) return null;

return this.mapper.map(
animeConnection,
AnimeConnection,
AnimeConnectionDto,
);
}
}
17 changes: 17 additions & 0 deletions src/graphql/types/args/query-studio.arg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ArgsType, Field, Int, ObjectType } from '@nestjs/graphql';

@ArgsType()
@ObjectType()
export class QueryStudioArg {
@Field({ nullable: true })
id?: string;

@Field({ nullable: true })
name?: string;

@Field({ nullable: true })
isAnimationStudio?: boolean;

@Field(() => Int)
idAnilist: number;
}
16 changes: 16 additions & 0 deletions src/graphql/types/dtos/studio/not-found-studio.error.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Field, ObjectType } from '@nestjs/graphql';
import { ErrorResponse } from '../error-response.interface';
import { QueryStudioArg } from '../../args/query-studio.arg';

@ObjectType({
implements: [ErrorResponse],
})
export class NotFoundStudioError extends ErrorResponse {
@Field(() => QueryStudioArg)
requestObject: QueryStudioArg;

constructor(partial?: Partial<NotFoundStudioError>) {
super('Studio not found');
Object.assign(this, partial);
}
}
8 changes: 8 additions & 0 deletions src/graphql/types/dtos/studio/studio.response.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { createUnionType } from '@nestjs/graphql';
import { StudioDto } from '~/common/dtos/studio-dtos/studio.dto';
import { NotFoundStudioError } from './not-found-studio.error';

export const StudioResultUnion = createUnionType({
name: 'StudioResult',
types: () => [StudioDto, NotFoundStudioError],
});
4 changes: 4 additions & 0 deletions src/graphql/types/enums/actions.enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,7 @@ export enum CharacterActions {
export enum StaffActions {
Staff = 'Staff',
}

export enum StudioActions {
Studio = 'Studio',
}
19 changes: 14 additions & 5 deletions src/modules/media.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,10 @@ import {
IAnimeTagService,
ICharacterInternalService,
IStaffInternalService,
IStudioService,
IStudioInternalService,
ICharacterExternalService,
IStaffExternalService,
IStudioExternalService,
} from '~/contracts/services';
import { MediaResolver } from '~/graphql/resolvers/media.resolver';
import { Anime, Character, CharacterEdge, Staff, StaffEdge } from '~/models';
Expand Down Expand Up @@ -80,6 +81,7 @@ import { StudioProfile } from '~/common/mapper-profiles/studio-profile';
import { CharacterResolver } from '~/graphql/resolvers/character.resolver';
import { StaffResolver } from '~/graphql/resolvers/staff.resolver';
import { StaffRepository } from '~/repositories/staff.repository';
import { StudioResolver } from '~/graphql/resolvers/studio.resolver';

const animeRepoProvider: Provider = {
provide: IAnimeRepository,
Expand Down Expand Up @@ -137,8 +139,12 @@ const studioRepositoryProvider: Provider = {
provide: IStudioRepository,
useClass: StudioRepository,
};
const studioServiceProvider: Provider = {
provide: IStudioService,
const studioInternalServiceProvider: Provider = {
provide: IStudioInternalService,
useClass: StudioService,
};
const studioExternalServiceProvider: Provider = {
provide: IStudioExternalService,
useClass: StudioService,
};

Expand Down Expand Up @@ -195,6 +201,7 @@ const studioServiceProvider: Provider = {
MediaResolver,
CharacterResolver,
StaffResolver,
StudioResolver,

animeRepoProvider,
animeServiceProvider,
Expand All @@ -212,7 +219,8 @@ const studioServiceProvider: Provider = {
staffInternalServiceProvider,
staffExternalServiceProvider,

studioServiceProvider,
studioInternalServiceProvider,
studioExternalServiceProvider,
studioRepositoryProvider,
],
exports: [
Expand All @@ -231,7 +239,8 @@ const studioServiceProvider: Provider = {
staffInternalServiceProvider,
staffExternalServiceProvider,

studioServiceProvider,
studioInternalServiceProvider,
studioExternalServiceProvider,
studioRepositoryProvider,
],
})
Expand Down
66 changes: 65 additions & 1 deletion src/repositories/studio.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@ import { Studio } from '~/models/studio.model';
import { BaseRepository } from './base.repository';
import { IStudioRepository } from '~/contracts/repositories';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { DataSource, Repository } from 'typeorm';
import { MapResultSelect } from '~/utils/tools/object';
import { QueryStudioArg } from '~/graphql/types/args/query-studio.arg';
import { QueryBuilderChainer } from './libs/query-builder-chainer';

@Injectable()
export class StudioRepository
Expand All @@ -13,7 +16,68 @@ export class StudioRepository
constructor(
@InjectRepository(Studio)
private readonly studioRepository: Repository<Studio>,

private dataSource: DataSource,
) {
super(studioRepository);
}

get studioAlias() {
return 'Studio';
}

get studioQueryBuilder() {
return this.dataSource
.getRepository(Studio)
.createQueryBuilder(this.studioAlias);
}

public async getStudioByConditions(
mapResultSelectParam: MapResultSelect,
queryAnimeArg: QueryStudioArg,
) {
const mapResultSelect = mapResultSelectParam as Record<string, any>;

const queryBuilder = this.createQueryStudioByConditionsBuilder(
mapResultSelect,
queryAnimeArg,
);

const studio = await queryBuilder.getOne();

return studio;
}

private createQueryStudioByConditionsBuilder(
mapResultSelect: Record<string, any>,
queryAnimeArg: QueryStudioArg,
) {
const { id, idAnilist, isAnimationStudio, name } = queryAnimeArg;

return (
new QueryBuilderChainer(this.studioQueryBuilder)
// query studio scalar fields
.addSelect(mapResultSelect, this.studioAlias, true)

// query studio.anime
.applyJoinConditionally(
!!mapResultSelect['anime'],
this.studioAlias,
'anime',
)
.addSelect(mapResultSelect['anime'], 'anime', false, ['nodes', 'edges'])

// where filters
.applyWhereConditionally(this.studioAlias, 'id', id)
.applyWhereConditionally(this.studioAlias, 'idAnilist', idAnilist)
.applyWhereConditionally(
this.studioAlias,
'isAnimationStudio',
isAnimationStudio,
)
.applyWhereConditionally(this.studioAlias, 'name', name)

.getQueryBuilder()
);
}
}
6 changes: 3 additions & 3 deletions src/services/anilist.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import {
IAnimeTagService,
ICharacterInternalService,
IStaffInternalService,
IStudioService,
IStudioInternalService,
} from '~/contracts/services';
import { Anime, Character } from '~/models';
import { AnimeEdge } from '~/models/anime-edge.model';
Expand Down Expand Up @@ -70,8 +70,8 @@ export class AnilistService implements IAnilistService {
@Inject(IAnimeGenreService)
private readonly animeGenreService: IAnimeGenreService,

@Inject(IStudioService)
private readonly studioService: IStudioService,
@Inject(IStudioInternalService)
private readonly studioService: IStudioInternalService,

@Inject(IAnimeTagService)
private readonly animeTagService: IAnimeTagService,
Expand Down
Loading
Loading