Added series subscriptions. Added series searching. Fixed database relations. Added logging for library controller.
This commit is contained in:
@@ -54,7 +54,6 @@ CREATE TABLE
|
|||||||
published_at timestamp default NULL,
|
published_at timestamp default NULL,
|
||||||
added_at timestamp default NULL,
|
added_at timestamp default NULL,
|
||||||
PRIMARY KEY (book_id),
|
PRIMARY KEY (book_id),
|
||||||
-- FOREIGN KEY (series_id) REFERENCES series (series_id),
|
|
||||||
FOREIGN KEY (provider, provider_series_id) REFERENCES series (provider, provider_series_id) ON DELETE CASCADE,
|
FOREIGN KEY (provider, provider_series_id) REFERENCES series (provider, provider_series_id) ON DELETE CASCADE,
|
||||||
UNIQUE NULLS NOT DISTINCT (provider_series_id, provider_book_id, book_volume)
|
UNIQUE NULLS NOT DISTINCT (provider_series_id, provider_book_id, book_volume)
|
||||||
);
|
);
|
||||||
@@ -131,7 +130,7 @@ CREATE INDEX book_statuses_user_id_login_idx ON users (user_id);
|
|||||||
CREATE TABLE
|
CREATE TABLE
|
||||||
series_subscriptions (
|
series_subscriptions (
|
||||||
user_id uuid,
|
user_id uuid,
|
||||||
provider text,
|
provider varchar(12) NOT NULL,
|
||||||
provider_series_id text,
|
provider_series_id text,
|
||||||
added_at timestamp default NULL,
|
added_at timestamp default NULL,
|
||||||
PRIMARY KEY (user_id, provider, provider_series_id),
|
PRIMARY KEY (user_id, provider, provider_series_id),
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
|
||||||
import { BooksController } from './books.controller';
|
|
||||||
|
|
||||||
describe('BooksController', () => {
|
|
||||||
let controller: BooksController;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
|
||||||
controllers: [BooksController],
|
|
||||||
}).compile();
|
|
||||||
|
|
||||||
controller = module.get<BooksController>(BooksController);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should be defined', () => {
|
|
||||||
expect(controller).toBeDefined();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { BookEntity } from './entities/book.entity';
|
import { BookEntity } from './entities/book.entity';
|
||||||
import { In, InsertResult, Repository } from 'typeorm';
|
import { DeleteResult, In, InsertResult, Repository } from 'typeorm';
|
||||||
import { BookOriginEntity } from './entities/book-origin.entity';
|
import { BookOriginEntity } from './entities/book-origin.entity';
|
||||||
import { BookStatusEntity } from './entities/book-status.entity';
|
import { BookStatusEntity } from './entities/book-status.entity';
|
||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
@@ -10,6 +10,8 @@ import { CreateBookOriginDto } from './dto/create-book-origin.dto';
|
|||||||
import { CreateBookStatusDto } from './dto/create-book-status.dto';
|
import { CreateBookStatusDto } from './dto/create-book-status.dto';
|
||||||
import { DeleteBookStatusDto } from './dto/delete-book-status.dto';
|
import { DeleteBookStatusDto } from './dto/delete-book-status.dto';
|
||||||
import { SeriesDto } from 'src/series/dto/series.dto';
|
import { SeriesDto } from 'src/series/dto/series.dto';
|
||||||
|
import { SeriesSubscriptionDto } from 'src/series/dto/series-subscription.dto';
|
||||||
|
import { BookOriginDto } from './dto/book-origin.dto';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class BooksService {
|
export class BooksService {
|
||||||
@@ -37,16 +39,18 @@ export class BooksService {
|
|||||||
return await this.bookOriginRepository.insert(origin);
|
return await this.bookOriginRepository.insert(origin);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBookOrigin(origin: CreateBookOriginDto) {
|
async deleteBookOrigin(origin: BookOriginDto[]): Promise<DeleteResult> {
|
||||||
return await this.bookOriginRepository.createQueryBuilder()
|
return await this.bookOriginRepository.createQueryBuilder()
|
||||||
.delete()
|
.delete()
|
||||||
.where({
|
.where({
|
||||||
whereFactory: origin,
|
whereFactory: {
|
||||||
|
bookOriginId: In(origin.map(o => o.bookOriginId)),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteBookStatus(status: DeleteBookStatusDto) {
|
async deleteBookStatus(status: DeleteBookStatusDto): Promise<DeleteResult> {
|
||||||
return await this.bookStatusRepository.createQueryBuilder()
|
return await this.bookStatusRepository.createQueryBuilder()
|
||||||
.delete()
|
.delete()
|
||||||
.where({
|
.where({
|
||||||
@@ -55,7 +59,7 @@ export class BooksService {
|
|||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBooksByIds(bookIds: UUID[]) {
|
async findBooksByIds(bookIds: UUID[]): Promise<BookEntity[]> {
|
||||||
return await this.bookRepository.find({
|
return await this.bookRepository.find({
|
||||||
where: {
|
where: {
|
||||||
bookId: In(bookIds)
|
bookId: In(bookIds)
|
||||||
@@ -63,7 +67,7 @@ export class BooksService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBooksFromSeries(series: SeriesDto) {
|
async findBooksFromSeries(series: SeriesDto): Promise<BookEntity[]> {
|
||||||
return await this.bookRepository.find({
|
return await this.bookRepository.find({
|
||||||
where: {
|
where: {
|
||||||
providerSeriesId: series.providerSeriesId,
|
providerSeriesId: series.providerSeriesId,
|
||||||
@@ -72,25 +76,23 @@ export class BooksService {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async findBookStatusesTrackedBy(userId: UUID): Promise<BookStatusEntity[]> {
|
async findActualBookStatusesTrackedBy(userId: UUID, series: SeriesDto): Promise<BookStatusEntity[]> {
|
||||||
return await this.bookStatusRepository.createQueryBuilder('s')
|
return await this.bookStatusRepository.createQueryBuilder('s')
|
||||||
.select(['s.book_id', 's.user_id'])
|
|
||||||
.where('s.user_id = :id', { id: userId })
|
|
||||||
.innerJoin('s.book', 'b')
|
.innerJoin('s.book', 'b')
|
||||||
|
.where('s.user_id = :id', { id: userId })
|
||||||
|
.andWhere('b.provider = :provider', { provider: series.provider })
|
||||||
|
.andWhere('b.providerSeriesId = :id', { id: series.providerSeriesId })
|
||||||
.addSelect(['b.book_title', 'b.book_desc', 'b.book_volume', 'b.provider', 'b.providerSeriesId'])
|
.addSelect(['b.book_title', 'b.book_desc', 'b.book_volume', 'b.provider', 'b.providerSeriesId'])
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async findSeriesTrackedBy(userId: UUID) {
|
async findBookStatusesTrackedBy(subscription: SeriesSubscriptionDto): Promise<any> {
|
||||||
return await this.bookStatusRepository.createQueryBuilder('s')
|
return await this.bookRepository.createQueryBuilder('b')
|
||||||
.where({
|
.where('b.provider = :provider', { provider: subscription.provider })
|
||||||
whereFactory: {
|
.andWhere(`b.provider_series_id = :id`, { id: subscription.providerSeriesId })
|
||||||
userId: userId
|
.leftJoin('b.statuses', 's')
|
||||||
}
|
.where(`s.user_id = :id`, { id: subscription.userId })
|
||||||
})
|
.addSelect(['s.state'])
|
||||||
.innerJoin('s.book', 'b')
|
|
||||||
.addSelect(['b.provider', 'b.providerSeriesId'])
|
|
||||||
.distinctOn(['b.provider', 'b.providerSeriesId'])
|
|
||||||
.getMany();
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -111,7 +113,7 @@ export class BooksService {
|
|||||||
await this.bookStatusRepository.createQueryBuilder()
|
await this.bookStatusRepository.createQueryBuilder()
|
||||||
.insert()
|
.insert()
|
||||||
.values(status)
|
.values(status)
|
||||||
.orUpdate(['state', 'modified_at'], ['user_id', 'book_id'], { skipUpdateIfNoValuesChanged: true })
|
.orUpdate(['state', 'modified_at'], ['user_id', 'book_id'])
|
||||||
.execute();
|
.execute();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import { IsNotEmpty, IsString } from "class-validator";
|
||||||
|
|
||||||
|
export class BookOriginDto {
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
bookOriginId: string;
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
import { IsNotEmpty, IsOptional, IsString, IsUUID } from 'class-validator';
|
import { IsNotEmpty, IsNumber, IsOptional, IsString, IsUUID, Max, Min } from 'class-validator';
|
||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
|
|
||||||
export class CreateBookStatusDto {
|
export class CreateBookStatusDto {
|
||||||
@@ -11,8 +11,10 @@ export class CreateBookStatusDto {
|
|||||||
@IsOptional()
|
@IsOptional()
|
||||||
readonly userId: UUID;
|
readonly userId: UUID;
|
||||||
|
|
||||||
@IsString()
|
@IsNumber()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
|
@Min(0)
|
||||||
|
@Max(6)
|
||||||
state: number;
|
state: number;
|
||||||
|
|
||||||
modifiedAt: Date;
|
modifiedAt: Date;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
import { BookOriginType } from 'src/shared/enums/book_origin_type';
|
import { BookOriginType } from 'src/shared/enums/book_origin_type';
|
||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn, Unique } from 'typeorm';
|
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn, Unique } from 'typeorm';
|
||||||
import { BookEntity } from './book.entity';
|
import { BookEntity } from './book.entity';
|
||||||
|
|
||||||
@Entity("book_origins")
|
@Entity("book_origins")
|
||||||
@@ -19,6 +19,9 @@ export class BookOriginEntity {
|
|||||||
value: string;
|
value: string;
|
||||||
|
|
||||||
@OneToOne(type => BookEntity, book => book.metadata)
|
@OneToOne(type => BookEntity, book => book.metadata)
|
||||||
@JoinColumn({ name: 'book_id' })
|
@JoinColumn({
|
||||||
|
name: 'book_id',
|
||||||
|
referencedColumnName: 'bookId',
|
||||||
|
})
|
||||||
book: BookEntity;
|
book: BookEntity;
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
import { UserEntity } from 'src/users/entities/users.entity';
|
import { UserEntity } from 'src/users/entities/users.entity';
|
||||||
import { Column, Entity, JoinColumn, ManyToOne, OneToOne, PrimaryColumn } from 'typeorm';
|
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn } from 'typeorm';
|
||||||
import { BookEntity } from './book.entity';
|
import { BookEntity } from './book.entity';
|
||||||
|
|
||||||
@Entity("book_statuses")
|
@Entity("book_statuses")
|
||||||
@@ -21,10 +21,16 @@ export class BookStatusEntity {
|
|||||||
modifiedAt: Date;
|
modifiedAt: Date;
|
||||||
|
|
||||||
@OneToOne(type => BookEntity, book => book.statuses)
|
@OneToOne(type => BookEntity, book => book.statuses)
|
||||||
@JoinColumn({ name: 'book_id' })
|
@JoinColumn({
|
||||||
|
name: 'book_id',
|
||||||
|
referencedColumnName: 'bookId',
|
||||||
|
})
|
||||||
book: BookEntity;
|
book: BookEntity;
|
||||||
|
|
||||||
@OneToOne(type => UserEntity, user => user.bookStatuses)
|
@OneToOne(type => UserEntity, user => user.bookStatuses)
|
||||||
@JoinColumn({ name: 'user_id' })
|
@JoinColumn({
|
||||||
|
name: 'user_id',
|
||||||
|
referencedColumnName: 'userId',
|
||||||
|
})
|
||||||
user: UserEntity;
|
user: UserEntity;
|
||||||
}
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
import { Column, Entity, JoinColumn, OneToMany, OneToOne, PrimaryColumn, Unique } from 'typeorm';
|
import { Column, Entity, JoinColumn, ManyToOne, OneToMany, OneToOne, PrimaryColumn, Unique } from 'typeorm';
|
||||||
import { BookOriginEntity } from './book-origin.entity';
|
import { BookOriginEntity } from './book-origin.entity';
|
||||||
import { BookStatusEntity } from './book-status.entity';
|
import { BookStatusEntity } from './book-status.entity';
|
||||||
import { SeriesEntity } from 'src/series/entities/series.entity';
|
import { SeriesEntity } from 'src/series/entities/series.entity';
|
||||||
@@ -34,14 +34,20 @@ export class BookEntity {
|
|||||||
@Column({ name: 'added_at', type: 'timestamptz', nullable: false })
|
@Column({ name: 'added_at', type: 'timestamptz', nullable: false })
|
||||||
addedAt: Date;
|
addedAt: Date;
|
||||||
|
|
||||||
@OneToMany(type => BookOriginEntity, origin => origin.bookId)
|
@OneToMany(type => BookOriginEntity, origin => origin.book)
|
||||||
metadata: BookOriginEntity[];
|
metadata: BookOriginEntity[];
|
||||||
|
|
||||||
@OneToMany(type => BookStatusEntity, status => status.bookId)
|
@OneToMany(type => BookStatusEntity, status => status.book)
|
||||||
statuses: BookStatusEntity[];
|
statuses: BookStatusEntity[];
|
||||||
|
|
||||||
@OneToOne(type => SeriesEntity, series => series.volumes)
|
@OneToOne(type => SeriesEntity, series => series.volumes)
|
||||||
@JoinColumn({ name: 'provider_series_id' })
|
@JoinColumn([{
|
||||||
@JoinColumn({ name: 'provider' })
|
name: 'provider_series_id',
|
||||||
|
referencedColumnName: 'providerSeriesId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provider',
|
||||||
|
referencedColumnName: 'provider',
|
||||||
|
}])
|
||||||
series: SeriesEntity;
|
series: SeriesEntity;
|
||||||
}
|
}
|
||||||
@@ -5,7 +5,7 @@ import { PinoLogger } from 'nestjs-pino';
|
|||||||
import { GoogleSearchContext } from 'src/providers/contexts/google.search.context';
|
import { GoogleSearchContext } from 'src/providers/contexts/google.search.context';
|
||||||
import { BookSearchResultDto } from 'src/providers/dto/book-search-result.dto';
|
import { BookSearchResultDto } from 'src/providers/dto/book-search-result.dto';
|
||||||
import { ProvidersService } from 'src/providers/providers.service';
|
import { ProvidersService } from 'src/providers/providers.service';
|
||||||
import { CreateSeriesSubscriptionJobDto } from 'src/series/dto/create-series-subscription-job.dto';
|
import { SeriesSubscriptionJobDto } from 'src/series/dto/series-subscription-job.dto';
|
||||||
import { LibraryService } from './library.service';
|
import { LibraryService } from './library.service';
|
||||||
|
|
||||||
@Processor('library')
|
@Processor('library')
|
||||||
@@ -27,7 +27,7 @@ export class LibraryConsumer extends WorkerHost {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (job.name == 'new_series') {
|
if (job.name == 'new_series') {
|
||||||
const series: CreateSeriesSubscriptionJobDto = job.data;
|
const series: SeriesSubscriptionJobDto = job.data;
|
||||||
const books = await this.search(job, series, null);
|
const books = await this.search(job, series, null);
|
||||||
|
|
||||||
let counter = 0;
|
let counter = 0;
|
||||||
@@ -51,8 +51,8 @@ export class LibraryConsumer extends WorkerHost {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (job.name == 'update_series') {
|
} else if (job.name == 'update_series') {
|
||||||
const series: CreateSeriesSubscriptionJobDto = job.data;
|
const series: SeriesSubscriptionJobDto = job.data;
|
||||||
const existingBooks = await this.library.getBooksFromSeries(series);
|
const existingBooks = await this.library.findBooksFromSeries(series);
|
||||||
const existingVolumes = existingBooks.map(b => b.volume);
|
const existingVolumes = existingBooks.map(b => b.volume);
|
||||||
const lastPublishedBook = existingBooks.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime())[0];
|
const lastPublishedBook = existingBooks.sort((a, b) => b.publishedAt.getTime() - a.publishedAt.getTime())[0];
|
||||||
const books = await this.search(job, series, lastPublishedBook?.publishedAt);
|
const books = await this.search(job, series, lastPublishedBook?.publishedAt);
|
||||||
@@ -100,7 +100,7 @@ export class LibraryConsumer extends WorkerHost {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async search(job: Job, series: CreateSeriesSubscriptionJobDto, after: Date|null): Promise<{ result: BookSearchResultDto, score: number }[]> {
|
private async search(job: Job, series: SeriesSubscriptionJobDto, after: Date | null): Promise<{ result: BookSearchResultDto, score: number }[]> {
|
||||||
let context = this.provider.generateSearchContext(series.provider, series.title) as GoogleSearchContext;
|
let context = this.provider.generateSearchContext(series.provider, series.title) as GoogleSearchContext;
|
||||||
context.maxResults = '40';
|
context.maxResults = '40';
|
||||||
if (after) {
|
if (after) {
|
||||||
@@ -131,7 +131,7 @@ export class LibraryConsumer extends WorkerHost {
|
|||||||
.sort((a, b) => a.result.volume - b.result.volume || b.score - a.score)
|
.sort((a, b) => a.result.volume - b.result.volume || b.score - a.score)
|
||||||
.filter((_, index, arr) => index == 0 || arr[index - 1].result.volume != arr[index].result.volume);
|
.filter((_, index, arr) => index == 0 || arr[index - 1].result.volume != arr[index].result.volume);
|
||||||
job.updateProgress(25);
|
job.updateProgress(25);
|
||||||
|
|
||||||
this.logger.debug({
|
this.logger.debug({
|
||||||
class: LibraryConsumer.name,
|
class: LibraryConsumer.name,
|
||||||
method: this.search.name,
|
method: this.search.name,
|
||||||
@@ -185,7 +185,7 @@ export class LibraryConsumer extends WorkerHost {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private toScore(book: BookSearchResultDto, series: CreateSeriesSubscriptionJobDto): ({ result: BookSearchResultDto, score: number }) {
|
private toScore(book: BookSearchResultDto, series: SeriesSubscriptionJobDto): ({ result: BookSearchResultDto, score: number }) {
|
||||||
if (!book) {
|
if (!book) {
|
||||||
return {
|
return {
|
||||||
result: null,
|
result: null,
|
||||||
|
|||||||
@@ -10,11 +10,13 @@ import { QueryFailedError } from 'typeorm';
|
|||||||
import { UpdateBookDto } from 'src/books/dto/update-book.dto';
|
import { UpdateBookDto } from 'src/books/dto/update-book.dto';
|
||||||
import { UpdateBookOriginDto } from 'src/books/dto/update-book-origin.dto';
|
import { UpdateBookOriginDto } from 'src/books/dto/update-book-origin.dto';
|
||||||
import { LibraryService } from './library.service';
|
import { LibraryService } from './library.service';
|
||||||
import { CreateSeriesSubscriptionDto } from 'src/series/dto/create-series-subscription.dto';
|
|
||||||
import { JwtAccessGuard } from 'src/auth/guards/jwt-access.guard';
|
import { JwtAccessGuard } from 'src/auth/guards/jwt-access.guard';
|
||||||
import { SeriesDto } from 'src/series/dto/series.dto';
|
import { SeriesDto } from 'src/series/dto/series.dto';
|
||||||
import { CreateBookOriginDto } from 'src/books/dto/create-book-origin.dto';
|
|
||||||
import { DeleteBookStatusDto } from 'src/books/dto/delete-book-status.dto';
|
import { DeleteBookStatusDto } from 'src/books/dto/delete-book-status.dto';
|
||||||
|
import { CreateBookStatusDto } from 'src/books/dto/create-book-status.dto';
|
||||||
|
import { JwtAccessAdminGuard } from 'src/auth/guards/jwt-access.admin.guard';
|
||||||
|
import { BookOriginDto } from 'src/books/dto/book-origin.dto';
|
||||||
|
import { CreateSeriesDto } from 'src/series/dto/create-series.dto';
|
||||||
|
|
||||||
@UseGuards(JwtAccessGuard)
|
@UseGuards(JwtAccessGuard)
|
||||||
@Controller('library')
|
@Controller('library')
|
||||||
@@ -30,7 +32,6 @@ export class LibraryController {
|
|||||||
@Get('series')
|
@Get('series')
|
||||||
async getSeries(
|
async getSeries(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
@Res({ passthrough: true }) response: Response,
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -41,16 +42,11 @@ export class LibraryController {
|
|||||||
@Post('series')
|
@Post('series')
|
||||||
async createSeries(
|
async createSeries(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
@Body() body: CreateSeriesSubscriptionDto,
|
@Body() body: CreateSeriesDto,
|
||||||
@Res({ passthrough: true }) response: Response,
|
@Res({ passthrough: true }) response: Response,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
await this.library.addSeries({
|
await this.library.addSeries(body);
|
||||||
provider: body.provider,
|
|
||||||
providerSeriesId: body.providerSeriesId,
|
|
||||||
title: body.title,
|
|
||||||
mediaType: body.mediaType,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -58,15 +54,32 @@ export class LibraryController {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof QueryFailedError) {
|
if (err instanceof QueryFailedError) {
|
||||||
if (err.driverError.code == '23505') {
|
if (err.driverError.code == '23505') {
|
||||||
// Subscription already exist.
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.createSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to create a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
response.statusCode = 409;
|
response.statusCode = 409;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error_message: 'Series subscription already exists.',
|
error_message: 'Series already exists.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.createSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to create a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
response.statusCode = 500;
|
response.statusCode = 500;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -78,34 +91,93 @@ export class LibraryController {
|
|||||||
@Patch('series')
|
@Patch('series')
|
||||||
async updateSeries(
|
async updateSeries(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
@Body() body: CreateSeriesSubscriptionDto,
|
@Body() body: CreateSeriesDto,
|
||||||
@Res({ passthrough: true }) response: Response,
|
@Res({ passthrough: true }) response: Response,
|
||||||
) {
|
) {
|
||||||
try {
|
try {
|
||||||
const series = await this.series.getSeries({
|
await this.library.updateSeries(body);
|
||||||
provider: body.provider,
|
|
||||||
providerSeriesId: body.providerSeriesId,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!series) {
|
|
||||||
response.statusCode = 404;
|
|
||||||
return {
|
|
||||||
success: false,
|
|
||||||
error_message: 'Series has not been added.'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.library.updateSeries({
|
|
||||||
provider: body.provider,
|
|
||||||
providerSeriesId: body.providerSeriesId,
|
|
||||||
title: body.title,
|
|
||||||
mediaType: body.mediaType,
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof QueryFailedError) {
|
||||||
|
if (err.driverError.code == '23505') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.updateSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
msg: 'Failed to update a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Subscription already exist.
|
||||||
|
response.statusCode = 409;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error_message: 'Series subscription already exists.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.updateSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to update a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
response.statusCode = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error_message: 'Something went wrong.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(JwtAccessAdminGuard)
|
||||||
|
@Delete('series')
|
||||||
|
async deleteSeries(
|
||||||
|
@Request() req,
|
||||||
|
@Body() body: SeriesDto,
|
||||||
|
@Res({ passthrough: true }) response: Response,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
const del = await this.series.deleteSeries(body);
|
||||||
|
return {
|
||||||
|
success: del && del.affected > 0,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof QueryFailedError) {
|
||||||
|
if (err.driverError.code == '23503') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.deleteSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to delete a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
response.statusCode = 404;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error_message: 'The series does not exist.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.deleteSeries.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to delete a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
response.statusCode = 500;
|
response.statusCode = 500;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -117,7 +189,6 @@ export class LibraryController {
|
|||||||
@Get('series/subscriptions')
|
@Get('series/subscriptions')
|
||||||
async getSeriesSubscriptions(
|
async getSeriesSubscriptions(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
@Res({ passthrough: true }) response: Response,
|
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
@@ -143,6 +214,15 @@ export class LibraryController {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof QueryFailedError) {
|
if (err instanceof QueryFailedError) {
|
||||||
if (err.driverError.code == '23505') {
|
if (err.driverError.code == '23505') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.subscribe.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to subscribe to a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
// Subscription already exists.
|
// Subscription already exists.
|
||||||
response.statusCode = 409;
|
response.statusCode = 409;
|
||||||
return {
|
return {
|
||||||
@@ -150,6 +230,15 @@ export class LibraryController {
|
|||||||
error_message: 'Series subscription already exists.',
|
error_message: 'Series subscription already exists.',
|
||||||
};
|
};
|
||||||
} else if (err.driverError.code == '23503') {
|
} else if (err.driverError.code == '23503') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.subscribe.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to subscribe to a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
// Series does not exist.
|
// Series does not exist.
|
||||||
response.statusCode = 404;
|
response.statusCode = 404;
|
||||||
return {
|
return {
|
||||||
@@ -159,6 +248,15 @@ export class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.subscribe.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to subscribe to a series.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
response.statusCode = 500;
|
response.statusCode = 500;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -167,13 +265,31 @@ export class LibraryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Delete('series/subscribe')
|
||||||
|
async deleteSeriesSubscription(
|
||||||
|
@Request() req,
|
||||||
|
@Body() body: SeriesDto,
|
||||||
|
) {
|
||||||
|
const del = await this.series.deleteSeriesSubscription({
|
||||||
|
...body,
|
||||||
|
userId: req.user.userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
success: del && del.affected > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Get('books')
|
@Get('books')
|
||||||
async getBooksFromUser(
|
async getBooksFromUser(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
|
@Body() body: SeriesDto,
|
||||||
) {
|
) {
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
data: await this.books.findBookStatusesTrackedBy(req.user.userId),
|
data: await this.library.findBooksFromSeries({
|
||||||
|
provider: body.provider,
|
||||||
|
providerSeriesId: body.providerSeriesId,
|
||||||
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -184,32 +300,18 @@ export class LibraryController {
|
|||||||
@Res({ passthrough: true }) response: Response,
|
@Res({ passthrough: true }) response: Response,
|
||||||
) {
|
) {
|
||||||
if (body.provider && body.providerSeriesId) {
|
if (body.provider && body.providerSeriesId) {
|
||||||
try {
|
this.logger.warn({
|
||||||
await this.series.updateSeries({
|
class: LibraryController.name,
|
||||||
provider: body.provider,
|
method: this.createBook.name,
|
||||||
providerSeriesId: body.providerSeriesId,
|
user: req.user,
|
||||||
title: body.title,
|
body: body,
|
||||||
mediaType: body.mediaType,
|
msg: 'Failed to create book due to book being part of a series.',
|
||||||
});
|
});
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof QueryFailedError) {
|
|
||||||
this.logger.error({
|
|
||||||
class: LibraryController.name,
|
|
||||||
method: this.createBook.name,
|
|
||||||
user: req.user,
|
|
||||||
msg: 'Failed to create a series for a book.',
|
|
||||||
error: err,
|
|
||||||
});
|
|
||||||
|
|
||||||
// Ignore if the series already exist.
|
response.statusCode = 400;
|
||||||
if (err.driverError.code != '23505') {
|
return {
|
||||||
response.statusCode = 500;
|
success: false,
|
||||||
return {
|
error_message: 'This book is part of a seris. Use the series route to create a series.',
|
||||||
success: false,
|
|
||||||
error_message: 'Something went wrong.',
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -220,15 +322,16 @@ export class LibraryController {
|
|||||||
};
|
};
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err instanceof QueryFailedError) {
|
if (err instanceof QueryFailedError) {
|
||||||
this.logger.error({
|
|
||||||
class: LibraryController.name,
|
|
||||||
method: this.createBook.name,
|
|
||||||
user: req.user,
|
|
||||||
msg: 'Failed to create book.',
|
|
||||||
error: err,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (err.driverError.code == '23505') {
|
if (err.driverError.code == '23505') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.createBook.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to create book.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
// Book exists already.
|
// Book exists already.
|
||||||
response.statusCode = 409;
|
response.statusCode = 409;
|
||||||
return {
|
return {
|
||||||
@@ -236,15 +339,33 @@ export class LibraryController {
|
|||||||
error_message: 'The book has already been added previously.',
|
error_message: 'The book has already been added previously.',
|
||||||
};
|
};
|
||||||
} else if (err.driverError.code == '23503') {
|
} else if (err.driverError.code == '23503') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.createBook.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to create book.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
// Series is missing.
|
// Series is missing.
|
||||||
response.statusCode = 500;
|
response.statusCode = 404;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
error_message: 'Series has not been added.',
|
error_message: 'Series does not exist.',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.createBook.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to create a book.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
response.statusCode = 500;
|
response.statusCode = 500;
|
||||||
return {
|
return {
|
||||||
success: false,
|
success: false,
|
||||||
@@ -262,33 +383,17 @@ export class LibraryController {
|
|||||||
|
|
||||||
const result = await this.books.updateBook(body.bookId, data);
|
const result = await this.books.updateBook(body.bookId, data);
|
||||||
return {
|
return {
|
||||||
success: result?.affected == 1,
|
success: result && result.affected > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@Delete('books/origins')
|
@Delete('books/origins')
|
||||||
async deleteBookOrigin(
|
async deleteBookOrigin(
|
||||||
@Body() body: CreateBookOriginDto,
|
@Body() body: BookOriginDto[],
|
||||||
) {
|
) {
|
||||||
const data = { ...body };
|
|
||||||
delete data['bookOriginId'];
|
|
||||||
|
|
||||||
const result = await this.books.deleteBookOrigin(body);
|
const result = await this.books.deleteBookOrigin(body);
|
||||||
return {
|
return {
|
||||||
success: result?.affected == 1,
|
success: result && result.affected > 0,
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Delete('books/status')
|
|
||||||
async deleteBookStatus(
|
|
||||||
@Body() body: DeleteBookStatusDto,
|
|
||||||
) {
|
|
||||||
const data = { ...body };
|
|
||||||
delete data['bookOriginId'];
|
|
||||||
|
|
||||||
const result = await this.books.deleteBookStatus(body);
|
|
||||||
return {
|
|
||||||
success: result?.affected == 1,
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -301,7 +406,76 @@ export class LibraryController {
|
|||||||
|
|
||||||
const result = await this.books.updateBookOrigin(body.bookOriginId, data);
|
const result = await this.books.updateBookOrigin(body.bookOriginId, data);
|
||||||
return {
|
return {
|
||||||
success: result?.affected == 1,
|
success: result && result.affected > 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('books/status')
|
||||||
|
async getBookStatus(
|
||||||
|
@Request() req,
|
||||||
|
@Body() body: SeriesDto,
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
data: await this.books.findActualBookStatusesTrackedBy(req.user.userId, body),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Put('books/status')
|
||||||
|
async updateBookStatus(
|
||||||
|
@Request() req,
|
||||||
|
@Body() body: CreateBookStatusDto,
|
||||||
|
@Res({ passthrough: true }) response: Response,
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
await this.books.updateBookStatus(body);
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof QueryFailedError) {
|
||||||
|
if (err.driverError.code == '23503') {
|
||||||
|
this.logger.warn({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.updateBookStatus.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to update the user\'s status of a book.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
response.statusCode = 404;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error_message: 'The book does not exist.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.error({
|
||||||
|
class: LibraryController.name,
|
||||||
|
method: this.updateBookStatus.name,
|
||||||
|
user: req.user,
|
||||||
|
body: body,
|
||||||
|
msg: 'Failed to update the user\'s status of a book.',
|
||||||
|
error: err,
|
||||||
|
});
|
||||||
|
|
||||||
|
response.statusCode = 500;
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
error_message: 'Something went wrong.',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete('books/status')
|
||||||
|
async deleteBookStatus(
|
||||||
|
@Body() body: DeleteBookStatusDto,
|
||||||
|
) {
|
||||||
|
const result = await this.books.deleteBookStatus(body);
|
||||||
|
return {
|
||||||
|
success: result && result.affected > 0,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -136,11 +136,11 @@ export class LibraryService {
|
|||||||
return bookId;
|
return bookId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async findBooksFromSeries(series: SeriesDto) {
|
||||||
|
return await this.books.findBooksFromSeries(series);
|
||||||
|
}
|
||||||
|
|
||||||
async updateSeries(series: CreateSeriesDto) {
|
async updateSeries(series: CreateSeriesDto) {
|
||||||
return await this.jobs.add('update_series', series);
|
return await this.jobs.add('update_series', series);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getBooksFromSeries(series: SeriesDto) {
|
|
||||||
return await this.books.findBooksFromSeries(series);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
import { SeriesDto } from './series.dto';
|
|
||||||
|
|
||||||
export class CreateSeriesSubscriptionDto extends SeriesDto {
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
title: string;
|
|
||||||
|
|
||||||
@IsString()
|
|
||||||
@IsNotEmpty()
|
|
||||||
mediaType: string;
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import { IsNotEmpty, IsString } from 'class-validator';
|
import { IsNotEmpty, IsString } from 'class-validator';
|
||||||
import { SeriesSubscriptionDto } from './series-subscription.dto';
|
import { SeriesSubscriptionDto } from './series-subscription.dto';
|
||||||
|
|
||||||
export class CreateSeriesSubscriptionJobDto extends SeriesSubscriptionDto {
|
export class SeriesSubscriptionJobDto extends SeriesSubscriptionDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsNotEmpty()
|
@IsNotEmpty()
|
||||||
title: string;
|
title: string;
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
import { Column, Entity, PrimaryColumn, Unique } from 'typeorm';
|
import { Column, Entity, JoinColumn, OneToOne, PrimaryColumn, Unique } from 'typeorm';
|
||||||
|
import { SeriesEntity } from './series.entity';
|
||||||
|
|
||||||
@Entity("series_subscriptions")
|
@Entity("series_subscriptions")
|
||||||
export class SeriesSubscriptionEntity {
|
export class SeriesSubscriptionEntity {
|
||||||
@@ -14,4 +15,15 @@ export class SeriesSubscriptionEntity {
|
|||||||
|
|
||||||
@Column({ name: 'added_at', type: 'timestamptz', nullable: false })
|
@Column({ name: 'added_at', type: 'timestamptz', nullable: false })
|
||||||
addedAt: Date;
|
addedAt: Date;
|
||||||
|
|
||||||
|
@OneToOne(type => SeriesEntity, series => series.subscriptions)
|
||||||
|
@JoinColumn([{
|
||||||
|
name: 'provider_series_id',
|
||||||
|
referencedColumnName: 'providerSeriesId',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'provider',
|
||||||
|
referencedColumnName: 'provider',
|
||||||
|
}])
|
||||||
|
series: SeriesSubscriptionEntity[];
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
import { BookEntity } from 'src/books/entities/book.entity';
|
import { BookEntity } from 'src/books/entities/book.entity';
|
||||||
import { Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm';
|
import { Column, Entity, OneToMany, PrimaryColumn, Unique } from 'typeorm';
|
||||||
|
import { SeriesSubscriptionEntity } from './series-subscription.entity';
|
||||||
|
|
||||||
@Entity("series")
|
@Entity("series")
|
||||||
@Unique(['provider', 'providerSeriesId'])
|
@Unique(['provider', 'providerSeriesId'])
|
||||||
@@ -25,4 +26,7 @@ export class SeriesEntity {
|
|||||||
|
|
||||||
@OneToMany(type => BookEntity, book => [book.provider, book.providerSeriesId])
|
@OneToMany(type => BookEntity, book => [book.provider, book.providerSeriesId])
|
||||||
volumes: BookEntity[];
|
volumes: BookEntity[];
|
||||||
|
|
||||||
|
@OneToMany(type => SeriesSubscriptionEntity, subscription => [subscription.provider, subscription.providerSeriesId])
|
||||||
|
subscriptions: SeriesSubscriptionEntity[];
|
||||||
}
|
}
|
||||||
@@ -12,9 +12,9 @@ import { SeriesSubscriptionDto } from './dto/series-subscription.dto';
|
|||||||
export class SeriesService {
|
export class SeriesService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SeriesEntity)
|
@InjectRepository(SeriesEntity)
|
||||||
private seriesRepository: Repository<SeriesEntity>,
|
private readonly seriesRepository: Repository<SeriesEntity>,
|
||||||
@InjectRepository(SeriesSubscriptionEntity)
|
@InjectRepository(SeriesSubscriptionEntity)
|
||||||
private seriesSubscriptionRepository: Repository<SeriesSubscriptionEntity>,
|
private readonly seriesSubscriptionRepository: Repository<SeriesSubscriptionEntity>,
|
||||||
) { }
|
) { }
|
||||||
|
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ export class SeriesService {
|
|||||||
async getSeries(series: SeriesDto) {
|
async getSeries(series: SeriesDto) {
|
||||||
return await this.seriesRepository.findOne({
|
return await this.seriesRepository.findOne({
|
||||||
where: series
|
where: series
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAllSeries() {
|
async getAllSeries() {
|
||||||
@@ -45,11 +45,15 @@ export class SeriesService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSeriesSubscribedBy(userId: UUID) {
|
async getSeriesSubscribedBy(userId: UUID) {
|
||||||
return await this.seriesSubscriptionRepository.find({
|
return await this.seriesRepository.createQueryBuilder('s')
|
||||||
where: {
|
.select(['s.seriesId', 's.providerSeriesId', 's.provider', 's.title', 's.mediaType', 's.addedAt'])
|
||||||
userId,
|
.innerJoinAndMapOne('s.subscriptions',
|
||||||
}
|
qb => qb
|
||||||
});
|
.select(['subscription.provider', 'subscription.provider_series_id', 'subscription.user_id'])
|
||||||
|
.from(SeriesSubscriptionEntity, 'subscription'),
|
||||||
|
'ss', `"ss"."subscription_provider" = "s"."provider" AND "ss"."provider_series_id" = "s"."provider_series_id"`)
|
||||||
|
.where(`ss.user_id = :id`, { id: userId })
|
||||||
|
.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async updateSeries(series: CreateSeriesDto) {
|
async updateSeries(series: CreateSeriesDto) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { LoginUserDto } from './dto/login-user.dto';
|
|||||||
import { UUID } from 'crypto';
|
import { UUID } from 'crypto';
|
||||||
|
|
||||||
class UserDto {
|
class UserDto {
|
||||||
userId: string;
|
userId: UUID;
|
||||||
userLogin: string;
|
userLogin: string;
|
||||||
userName: string;
|
userName: string;
|
||||||
isAdmin: boolean;
|
isAdmin: boolean;
|
||||||
|
|||||||
Reference in New Issue
Block a user