Create Library module. Moved book controller to library controller. Added series addition to library while adding all known volumes in background. Fixed Google search context.
This commit is contained in:
@ -1,4 +1,6 @@
|
||||
class GoogleSearchContext extends SearchContext {
|
||||
import { SearchContext } from "./search.context";
|
||||
|
||||
export class GoogleSearchContext extends SearchContext {
|
||||
constructor(searchQuery: string, params: { [key: string]: string }) {
|
||||
super('google', searchQuery, params);
|
||||
}
|
||||
@ -19,12 +21,96 @@ class GoogleSearchContext extends SearchContext {
|
||||
...searchParams.map(p => this.params[p] ? p + ':"' + this.params[p] + '"' : ''),
|
||||
].filter(p => p.length > 0).join('');
|
||||
|
||||
return queryParams + '&' + searchQueryParam;
|
||||
return [queryParams, 'q=' + searchQueryParam].filter(q => q.length > 0).join('&');
|
||||
}
|
||||
|
||||
get maxResults(): number {
|
||||
return 'maxResults' in this.params ? parseInt(this.params['maxResults']) : 10;
|
||||
}
|
||||
|
||||
set maxResults(value: string) {
|
||||
if (!value || isNaN(parseInt(value))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.params['maxResults'] = value;
|
||||
}
|
||||
|
||||
get startIndex(): number {
|
||||
return 'startIndex' in this.params ? parseInt(this.params['startIndex']) : 10;
|
||||
}
|
||||
|
||||
set startIndex(value: string) {
|
||||
if (!value || isNaN(parseInt(value))) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.params['startIndex'] = value;
|
||||
}
|
||||
|
||||
get intitle(): string {
|
||||
return 'intitle' in this.params ? this.params['intitle'] : null;
|
||||
}
|
||||
|
||||
set intitle(value: string) {
|
||||
if (!value) {
|
||||
delete this.params['intitle'];
|
||||
} else {
|
||||
this.params['intitle'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get inpublisher(): string {
|
||||
return 'inpublisher' in this.params ? this.params['inpublisher'] : null;
|
||||
}
|
||||
|
||||
set inpublisher(value: string) {
|
||||
if (!value) {
|
||||
delete this.params['inpublisher'];
|
||||
} else {
|
||||
this.params['inpublisher'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get inauthor(): string {
|
||||
return 'inauthor' in this.params ? this.params['inauthor'] : null;
|
||||
}
|
||||
|
||||
set inauthor(value: string) {
|
||||
if (!value) {
|
||||
delete this.params['inauthor'];
|
||||
} else {
|
||||
this.params['inauthor'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get isbn(): string {
|
||||
return 'isbn' in this.params ? this.params['isbn'] : null;
|
||||
}
|
||||
|
||||
set isbn(value: string) {
|
||||
if (!value) {
|
||||
delete this.params['isbn'];
|
||||
} else {
|
||||
this.params['isbn'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
get subject(): string {
|
||||
return 'subject' in this.params ? this.params['subject'] : null;
|
||||
}
|
||||
|
||||
set subject(value: string) {
|
||||
if (!value) {
|
||||
delete this.params['subject'];
|
||||
} else {
|
||||
this.params['subject'] = value;
|
||||
}
|
||||
}
|
||||
|
||||
next() {
|
||||
const resultsPerPage = parseInt(this.params['maxResults']) ?? 10;
|
||||
const index = parseInt(this.params['startIndex']) ?? 0;
|
||||
const resultsPerPage = this.params['maxResults'] ? parseInt(this.params['maxResults']) : 10;
|
||||
const index = this.params['startIndex'] ? parseInt(this.params['startIndex']) : 0;
|
||||
|
||||
const data = { ...this.params };
|
||||
data['startIndex'] = (index + resultsPerPage).toString();
|
||||
|
@ -1,4 +1,4 @@
|
||||
abstract class SearchContext {
|
||||
export abstract class SearchContext {
|
||||
provider: string;
|
||||
search: string;
|
||||
params: { [key: string]: string };
|
||||
|
@ -3,6 +3,7 @@ import { BookSearchResultDto } from '../dto/book-search-result.dto';
|
||||
import { HttpService } from '@nestjs/axios';
|
||||
import { firstValueFrom, map, timeout } from 'rxjs';
|
||||
import { AxiosResponse } from 'axios';
|
||||
import { GoogleSearchContext } from '../contexts/google.search.context';
|
||||
|
||||
@Injectable()
|
||||
export class GoogleService {
|
||||
@ -11,7 +12,7 @@ export class GoogleService {
|
||||
) { }
|
||||
|
||||
async searchRaw(searchQuery: string): Promise<BookSearchResultDto[]> {
|
||||
const queryParams = 'printType=books&maxResults=10&fields=items(kind,id,volumeInfo(title,description,authors,publisher,publishedDate,industryIdentifiers,language,categories,maturityRating,imageLinks,canonicalVolumeLink,seriesInfo))&q=';
|
||||
const queryParams = 'langRestrict=en&printType=books&maxResults=10&fields=items(kind,id,volumeInfo(title,description,authors,publisher,publishedDate,industryIdentifiers,language,categories,maturityRating,imageLinks,canonicalVolumeLink,seriesInfo))&q=';
|
||||
|
||||
return await firstValueFrom(
|
||||
this.http.get('https://www.googleapis.com/books/v1/volumes?' + queryParams + searchQuery)
|
||||
@ -23,14 +24,19 @@ export class GoogleService {
|
||||
}
|
||||
|
||||
async search(context: GoogleSearchContext): Promise<BookSearchResultDto[]> {
|
||||
const defaultQueryParams = 'printType=books&fields=items(kind,id,volumeInfo(title,description,authors,publisher,publishedDate,industryIdentifiers,language,categories,maturityRating,imageLinks,canonicalVolumeLink,seriesInfo))';
|
||||
if (!context) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const defaultQueryParams = 'langRestrict=en&printType=books&fields=items(kind,id,volumeInfo(title,description,authors,publisher,publishedDate,industryIdentifiers,language,categories,maturityRating,imageLinks,canonicalVolumeLink,seriesInfo))';
|
||||
const customQueryParams = context.generateQueryParams();
|
||||
console.log(defaultQueryParams, customQueryParams);
|
||||
|
||||
return await firstValueFrom(
|
||||
this.http.get('https://www.googleapis.com/books/v1/volumes?' + defaultQueryParams + '&' + customQueryParams)
|
||||
.pipe(
|
||||
timeout({ first: 5000 }),
|
||||
map(this.transform),
|
||||
map(value => this.transform(value)),
|
||||
)
|
||||
);
|
||||
}
|
||||
@ -40,7 +46,9 @@ export class GoogleService {
|
||||
return [];
|
||||
}
|
||||
|
||||
return response.data.items.map(item => this.extract(item));
|
||||
return response.data.items
|
||||
//.filter(item => item.volumeInfo?.canonicalVolumeLink?.startsWith('https://play.google.com/store/books/details'))
|
||||
.map(item => this.extract(item));
|
||||
}
|
||||
|
||||
private extract(item: any): BookSearchResultDto {
|
||||
@ -49,12 +57,12 @@ export class GoogleService {
|
||||
providerSeriesId: item.volumeInfo.seriesInfo?.volumeSeries[0].seriesId,
|
||||
title: item.volumeInfo.title,
|
||||
desc: item.volumeInfo.description,
|
||||
volume: parseInt(item.volumeInfo.seriesInfo?.bookDisplayNumber),
|
||||
volume: item.volumeInfo.seriesInfo?.bookDisplayNumber ? parseInt(item.volumeInfo.seriesInfo?.bookDisplayNumber, 10) : undefined,
|
||||
publisher: item.volumeInfo.publisher,
|
||||
authors: item.volumeInfo.authors,
|
||||
categories: item.volumeInfo.categories,
|
||||
maturityRating: item.volumeInfo.maturityRating,
|
||||
industryIdentifiers: Object.assign({}, ...item.volumeInfo.industryIdentifiers.map(i => ({ [i.type]: i.identifier }))),
|
||||
industryIdentifiers: item.volumeInfo.industryIdentifiers ? Object.assign({}, ...item.volumeInfo.industryIdentifiers.map(i => ({ [i.type]: i.identifier }))) : [],
|
||||
publishedAt: new Date(item.volumeInfo.publishedDate),
|
||||
language: item.volumeInfo.language,
|
||||
thumbnail: item.volumeInfo.imageLinks?.thumbnail,
|
||||
@ -62,15 +70,13 @@ export class GoogleService {
|
||||
provider: 'google'
|
||||
}
|
||||
|
||||
if (result.providerSeriesId) {
|
||||
let regex = this.getRegexByPublisher(result.publisher);
|
||||
let regex = this.getRegexByPublisher(result.publisher);
|
||||
|
||||
const match = result.title.match(regex);
|
||||
if (match?.groups) {
|
||||
result.title = match.groups['title'].trim();
|
||||
if (!result.volume) {
|
||||
result.volume = parseInt(match.groups['volume']);
|
||||
}
|
||||
const match = result.title.match(regex);
|
||||
if (match?.groups) {
|
||||
result.title = match.groups['title'].trim();
|
||||
if (!result.volume || isNaN(result.volume)) {
|
||||
result.volume = parseInt(match.groups['volume'], 10);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { GoogleService } from './google/google.service';
|
||||
import { BookSearchResultDto } from './dto/book-search-result.dto';
|
||||
import { GoogleSearchContext } from './contexts/google.search.context';
|
||||
import { SearchContext } from './contexts/search.context';
|
||||
|
||||
@Injectable()
|
||||
export class ProvidersService {
|
||||
@ -10,7 +12,7 @@ export class ProvidersService {
|
||||
|
||||
generateSearchContext(providerName: string, searchQuery: string): SearchContext | null {
|
||||
let params: { [key: string]: string } = {};
|
||||
if (providerName == 'google') {
|
||||
if (providerName.toLowerCase() == 'google') {
|
||||
return new GoogleSearchContext(searchQuery, params);
|
||||
}
|
||||
return null;
|
||||
@ -28,7 +30,7 @@ export class ProvidersService {
|
||||
async search(context: SearchContext): Promise<BookSearchResultDto[]> {
|
||||
switch (context.provider.toLowerCase()) {
|
||||
case 'google':
|
||||
return await this.google.search(context);
|
||||
return await this.google.search(context as GoogleSearchContext);
|
||||
default:
|
||||
throw Error('Invalid provider name.');
|
||||
}
|
||||
|
Reference in New Issue
Block a user