added backend to cache manga updates requests
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -28,3 +28,5 @@ frontend/.vscode/*
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
dev
|
||||||
|
/backend/cache/
|
||||||
|
|||||||
4
backend/config.json
Normal file
4
backend/config.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"host": "localhost",
|
||||||
|
"port": 5000
|
||||||
|
}
|
||||||
29
backend/package.json
Normal file
29
backend/package.json
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "mangastatus-backend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"private": true,
|
||||||
|
"author": "wea_ondara",
|
||||||
|
"license": "ISC",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "run-p dev:1 dev:2",
|
||||||
|
"dev:1": "tsc --watch --preserveWatchOutput -p tsconfig.dev.json",
|
||||||
|
"dev:2": "wait-on dev/main.js && nodemon -q --watch dev/ dev/main.js",
|
||||||
|
"build": "run-s type-check build-only",
|
||||||
|
"build-only": "tsc",
|
||||||
|
"type-check": "tsc --noEmit --composite false"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"compression": "^1.7.4",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"node-schedule": "^2.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/compression": "^1.7.5",
|
||||||
|
"@types/express": "^4.17.21",
|
||||||
|
"@types/node-schedule": "^2.1.3",
|
||||||
|
"nodemon": "^3.0.1",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"typescript": "^5.2.2",
|
||||||
|
"wait-on": "^7.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
98
backend/src/cache/MangaUpdatesCache.ts
vendored
Normal file
98
backend/src/cache/MangaUpdatesCache.ts
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
type CacheEntry = { data: string, lastUpdateMs: number }
|
||||||
|
|
||||||
|
export class MangaUpdatesCache {
|
||||||
|
private readonly FILE_CACHE_DIR = 'cache';
|
||||||
|
private readonly FILE_SEARCH_BY_TITLE = this.FILE_CACHE_DIR + '/searchByTitle.json';
|
||||||
|
private readonly FILE_SERIES_BY_ID = this.FILE_CACHE_DIR + '/seriesById.json';
|
||||||
|
private readonly FILE_SERIES_GROUPS_BY_ID = this.FILE_CACHE_DIR + '/seriesGroupsById.json';
|
||||||
|
|
||||||
|
private readonly MAX_CACHE_AGE_SEARCH_BY_TITLE = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
private readonly MAX_CACHE_AGE_SERIES_BY_ID = 30 * 24 * 60 * 60 * 1000;
|
||||||
|
private readonly MAX_CACHE_AGE_SERIES_GROUPS_BY_ID = 1 * 24 * 60 * 60 * 1000;
|
||||||
|
|
||||||
|
private _searchByTitle = new Map<string, CacheEntry>();
|
||||||
|
private _seriesById = new Map<string, CacheEntry>();
|
||||||
|
private _seriesGroupsById = new Map<string, CacheEntry>();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.load();
|
||||||
|
}
|
||||||
|
|
||||||
|
private load(): void {
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(this.FILE_CACHE_DIR);
|
||||||
|
} catch (_) {
|
||||||
|
}
|
||||||
|
this._searchByTitle = this.loadJson(this.FILE_SEARCH_BY_TITLE);
|
||||||
|
this._seriesById = this.loadJson(this.FILE_SERIES_BY_ID);
|
||||||
|
this._seriesGroupsById = this.loadJson(this.FILE_SERIES_GROUPS_BY_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadJson(file: string): Map<string, CacheEntry> {
|
||||||
|
try {
|
||||||
|
const data = fs.readFileSync(file, {encoding: 'utf8', flag: 'r'});
|
||||||
|
const json = JSON.parse(data);
|
||||||
|
return new Map<string, CacheEntry>(Object.entries(json));
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to load cache from ' + file + ':' + (err as Error).message);
|
||||||
|
return new Map();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveJson(file: string, map: Map<string, CacheEntry>): void {
|
||||||
|
try {
|
||||||
|
const data = JSON.stringify(Object.fromEntries(map.entries()));
|
||||||
|
fs.writeFileSync(file, data, {encoding: 'utf8', flag: 'w'});
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getSearchByTitle(title: string): string | undefined {
|
||||||
|
const entry = this._searchByTitle.get(title);
|
||||||
|
return !entry || entry.lastUpdateMs + this.MAX_CACHE_AGE_SEARCH_BY_TITLE < Date.now() ? undefined : entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeriesById(id: string): string | undefined {
|
||||||
|
const entry = this._seriesById.get(id);
|
||||||
|
return !entry || entry.lastUpdateMs + this.MAX_CACHE_AGE_SERIES_BY_ID < Date.now() ? undefined : entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
getSeriesGroupsById(id: string): string | undefined {
|
||||||
|
const entry = this._seriesGroupsById.get(id);
|
||||||
|
return !entry || entry.lastUpdateMs + this.MAX_CACHE_AGE_SERIES_GROUPS_BY_ID < Date.now() ? undefined : entry.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
putSearchByTitle(title: string, value: string): void {
|
||||||
|
this._searchByTitle.set(title, {data: value, lastUpdateMs: Date.now()});
|
||||||
|
this.saveJson(this.FILE_SEARCH_BY_TITLE, this._searchByTitle);
|
||||||
|
}
|
||||||
|
|
||||||
|
putSeriesById(id: string, value: string): void {
|
||||||
|
this._seriesById.set(id, {data: value, lastUpdateMs: Date.now()});
|
||||||
|
this.saveJson(this.FILE_SERIES_BY_ID, this._seriesById);
|
||||||
|
}
|
||||||
|
|
||||||
|
putSeriesGroupsById(id: string, value: string): void {
|
||||||
|
this._seriesGroupsById.set(id, {data: value, lastUpdateMs: Date.now()});
|
||||||
|
this.saveJson(this.FILE_SERIES_GROUPS_BY_ID, this._seriesGroupsById);
|
||||||
|
}
|
||||||
|
|
||||||
|
getOutOfDateSearch(): string[] {
|
||||||
|
return Array.from(this._searchByTitle.entries())
|
||||||
|
.filter(([title, entry]) => entry.lastUpdateMs + this.MAX_CACHE_AGE_SEARCH_BY_TITLE < Date.now())
|
||||||
|
.map(([title, entry]) => title);
|
||||||
|
}
|
||||||
|
getOutOfDateSeries(): string[] {
|
||||||
|
return Array.from(this._seriesById.entries())
|
||||||
|
.filter(([title, entry]) => entry.lastUpdateMs + this.MAX_CACHE_AGE_SERIES_BY_ID < Date.now())
|
||||||
|
.map(([title, entry]) => title);
|
||||||
|
}
|
||||||
|
getOutOfDateSeriesGroups(): string[] {
|
||||||
|
return Array.from(this._seriesGroupsById.entries())
|
||||||
|
.filter(([title, entry]) => entry.lastUpdateMs + this.MAX_CACHE_AGE_SERIES_GROUPS_BY_ID < Date.now())
|
||||||
|
.map(([title, entry]) => title);
|
||||||
|
}
|
||||||
|
}
|
||||||
106
backend/src/controller/MangaUpdatesController.ts
Normal file
106
backend/src/controller/MangaUpdatesController.ts
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
import {NextFunction, Request, Response} from 'express';
|
||||||
|
import {MangaUpdatesCache} from '../cache/MangaUpdatesCache.js';
|
||||||
|
|
||||||
|
export class MangaUpdatesController {
|
||||||
|
private cache: MangaUpdatesCache;
|
||||||
|
|
||||||
|
constructor(cache: MangaUpdatesCache) {
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
async search(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const bjson = req.body;
|
||||||
|
if (bjson['stype'] !== 'title' || bjson['type'] !== 'Manga' || !bjson['search']?.trim().length) {
|
||||||
|
res.status(400).send('Only {stype: "title", type: "Manga", search: "some title"} allowed!');
|
||||||
|
next('router');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromCache = this.cache.getSearchByTitle(bjson['search']);
|
||||||
|
if (fromCache) {
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromCache);
|
||||||
|
next('router');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//throttle
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
//fetch from manga updates
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(bjson),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
res.status(fromApi.status).send(fromApi.body);
|
||||||
|
next('router');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSearchByTitle(bjson['search'], fromApiJson);
|
||||||
|
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromApiJson);
|
||||||
|
next('router');
|
||||||
|
}
|
||||||
|
|
||||||
|
async getById(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const id = req.params.id!.toLowerCase();
|
||||||
|
|
||||||
|
const fromCache = this.cache.getSeriesById(id);
|
||||||
|
if (fromCache) {
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromCache);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//throttle
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
//fetch from manga updates
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/' + id);
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
res.status(fromApi.status).send(fromApi.body);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSeriesById(id, fromApiJson);
|
||||||
|
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromApiJson);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getGroupById(req: Request, res: Response, next: NextFunction): Promise<void> {
|
||||||
|
const id = req.params.id!.toLowerCase();
|
||||||
|
|
||||||
|
const fromCache = this.cache.getSeriesGroupsById(id);
|
||||||
|
if (fromCache) {
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromCache);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//throttle
|
||||||
|
await new Promise((r) => setTimeout(r, 1000));
|
||||||
|
|
||||||
|
//fetch from manga updates
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/' + id + '/groups');
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
res.status(fromApi.status).send(fromApi.body);
|
||||||
|
next();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSeriesGroupsById(id, fromApiJson);
|
||||||
|
|
||||||
|
res.status(200).setHeader('Content-Type', 'application/json').send(fromApiJson);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
}
|
||||||
29
backend/src/main.ts
Normal file
29
backend/src/main.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import compression from 'compression';
|
||||||
|
import express from 'express';
|
||||||
|
import aniListRouter from './router/AniListRouter.js';
|
||||||
|
import mangaUpdatesRouter from './router/MangaUpdatesRouter.js';
|
||||||
|
import Scheduler from './schedule/Scheduler.js';
|
||||||
|
import {MangaUpdatesCache} from './cache/MangaUpdatesCache.js';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
const config = JSON.parse(fs.readFileSync('config.json').toString())
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const mangaUpdatesCache = new MangaUpdatesCache();
|
||||||
|
const scheduler = new Scheduler(mangaUpdatesCache);
|
||||||
|
|
||||||
|
scheduler.registerJobs();
|
||||||
|
|
||||||
|
//middlewares
|
||||||
|
app.use(compression());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
//router
|
||||||
|
app.use('/anilist', aniListRouter());
|
||||||
|
app.use('/mangaupdates', mangaUpdatesRouter(mangaUpdatesCache));
|
||||||
|
app.use(express.static('_client')) //for production
|
||||||
|
|
||||||
|
//start
|
||||||
|
app.listen(config.port, config.host, () => {
|
||||||
|
console.log('Started server on http://' + config.host + ':' + config.port + '/');
|
||||||
|
});
|
||||||
24
backend/src/router/AniListRouter.ts
Normal file
24
backend/src/router/AniListRouter.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import {NextFunction, Request, Response, Router} from 'express';
|
||||||
|
|
||||||
|
export default function aniListRouter(): Router {
|
||||||
|
const router = Router();
|
||||||
|
router.post('/graphql', async (req: Request, res: Response, next: NextFunction) => {
|
||||||
|
try {
|
||||||
|
const fromApi = await fetch('https://graphql.anilist.co/graphql', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(req.body),
|
||||||
|
});
|
||||||
|
|
||||||
|
const fromApiText = await fromApi.text()
|
||||||
|
res.status(fromApi.status).send(fromApiText);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
return router;
|
||||||
|
}
|
||||||
12
backend/src/router/MangaUpdatesRouter.ts
Normal file
12
backend/src/router/MangaUpdatesRouter.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import {Router} from 'express';
|
||||||
|
import {MangaUpdatesController} from '../controller/MangaUpdatesController.js';
|
||||||
|
import {MangaUpdatesCache} from '../cache/MangaUpdatesCache.js';
|
||||||
|
|
||||||
|
export default function mangaUpdatesRouter(cache: MangaUpdatesCache): Router {
|
||||||
|
const controller = new MangaUpdatesController(cache);
|
||||||
|
const router = Router();
|
||||||
|
router.post('/v1/series/search', controller.search.bind(controller));
|
||||||
|
router.get('/v1/series/:id', controller.getById.bind(controller));
|
||||||
|
router.get('/v1/series/:id/groups', controller.getGroupById.bind(controller));
|
||||||
|
return router;
|
||||||
|
}
|
||||||
4
backend/src/schedule/IJob.ts
Normal file
4
backend/src/schedule/IJob.ts
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
export default interface IJob<T> {
|
||||||
|
get schedule(): Date | string;
|
||||||
|
execute(): Promise<T>;
|
||||||
|
}
|
||||||
28
backend/src/schedule/MangaUpdateCacheRenewJob.ts
Normal file
28
backend/src/schedule/MangaUpdateCacheRenewJob.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import IJob from './IJob';
|
||||||
|
import MangaUpdateCacheRenewService from '../service/MangaUpdateCacheRenewService.js';
|
||||||
|
import {MangaUpdatesCache} from '../cache/MangaUpdatesCache.js';
|
||||||
|
|
||||||
|
export default class MangaUpdateCacheRenewJob implements IJob<void> {
|
||||||
|
private readonly service: MangaUpdateCacheRenewService;
|
||||||
|
private lock: boolean = false;
|
||||||
|
|
||||||
|
constructor(cache: MangaUpdatesCache) {
|
||||||
|
this.service = new MangaUpdateCacheRenewService(cache);
|
||||||
|
}
|
||||||
|
|
||||||
|
get schedule(): Date | string {
|
||||||
|
return '0 0 * * * *'; //every hour
|
||||||
|
}
|
||||||
|
|
||||||
|
async execute(): Promise<void> {
|
||||||
|
if (this.lock) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.lock = true;
|
||||||
|
try {
|
||||||
|
await this.service.renew();
|
||||||
|
} finally {
|
||||||
|
this.lock = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
22
backend/src/schedule/Scheduler.ts
Normal file
22
backend/src/schedule/Scheduler.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import {MangaUpdatesCache} from '../cache/MangaUpdatesCache.js';
|
||||||
|
import IJob from './IJob.js';
|
||||||
|
import MangaUpdateCacheRenewJob from './MangaUpdateCacheRenewJob.js';
|
||||||
|
import {gracefulShutdown, scheduleJob} from 'node-schedule';
|
||||||
|
|
||||||
|
export default class Scheduler {
|
||||||
|
private readonly jobs: IJob<any>[] = [];
|
||||||
|
|
||||||
|
constructor(cache: MangaUpdatesCache) {
|
||||||
|
this.jobs.push(
|
||||||
|
new MangaUpdateCacheRenewJob(cache),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
registerJobs(): void {
|
||||||
|
this.jobs.forEach(e => scheduleJob(e.schedule, e.execute.bind(e)));
|
||||||
|
}
|
||||||
|
|
||||||
|
cancelJobs(): Promise<void> {
|
||||||
|
return gracefulShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
86
backend/src/service/MangaUpdateCacheRenewService.ts
Normal file
86
backend/src/service/MangaUpdateCacheRenewService.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import {MangaUpdatesCache} from '../cache/MangaUpdatesCache';
|
||||||
|
|
||||||
|
export default class MangaUpdateCacheRenewService {
|
||||||
|
private static readonly delay = 3000;
|
||||||
|
|
||||||
|
private readonly cache: MangaUpdatesCache;
|
||||||
|
|
||||||
|
constructor(cache: MangaUpdatesCache) {
|
||||||
|
this.cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
async renew(): Promise<void> {
|
||||||
|
console.log('Renewing cache ...');
|
||||||
|
await this.renewRelations();
|
||||||
|
await this.renewSeries();
|
||||||
|
await this.renewUpdates();
|
||||||
|
console.log('Renewing cache done');
|
||||||
|
}
|
||||||
|
|
||||||
|
async renewRelations(): Promise<void> {
|
||||||
|
const titles = this.cache.getOutOfDateSearch();
|
||||||
|
console.log(titles.length + ' out-of-date relations');
|
||||||
|
|
||||||
|
for (let title of titles) {
|
||||||
|
await new Promise((r) => setTimeout(r, MangaUpdateCacheRenewService.delay));
|
||||||
|
try {
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/search', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({stype: 'title', type: 'Manga', search: title}),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSearchByTitle(title, fromApiJson);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async renewSeries(): Promise<void> {
|
||||||
|
const ids = this.cache.getOutOfDateSeries();
|
||||||
|
console.log(ids.length + ' out-of-date series');
|
||||||
|
|
||||||
|
for (let id of ids) {
|
||||||
|
await new Promise((r) => setTimeout(r, MangaUpdateCacheRenewService.delay));
|
||||||
|
try {
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/' + id);
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSeriesById(id, fromApiJson);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async renewUpdates(): Promise<void> {
|
||||||
|
const ids = this.cache.getOutOfDateSeriesGroups();
|
||||||
|
console.log(ids.length + ' out-of-date series updates');
|
||||||
|
|
||||||
|
for (let id of ids) {
|
||||||
|
await new Promise((r) => setTimeout(r, MangaUpdateCacheRenewService.delay));
|
||||||
|
try {
|
||||||
|
const fromApi = await fetch('https://api.mangaupdates.com/v1/series/' + id + '/groups');
|
||||||
|
if (fromApi.status !== 200) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromApiJson = await fromApi.text();
|
||||||
|
this.cache.putSeriesGroupsById(id, fromApiJson);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
111
backend/tsconfig.dev.json
Normal file
111
backend/tsconfig.dev.json
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
// "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
// "module": "ESNext",
|
||||||
|
/* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./dev", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
// "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
// "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
// "strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
// "skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
109
backend/tsconfig.json
Normal file
109
backend/tsconfig.json
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
|
||||||
|
/* Emit */
|
||||||
|
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
"sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./dist", /* Specify an output folder for all emitted files. */
|
||||||
|
"removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
"noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
"strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
"strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
"strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
"strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
"noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
"useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
"alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
"noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
"exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
"noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
"noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
"noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
"noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
||||||
24
build.sh
Executable file
24
build.sh
Executable file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
|
||||||
|
mkdir dist
|
||||||
|
rm -rf dist/*
|
||||||
|
|
||||||
|
# build
|
||||||
|
(cd backend && npm i && npm run build) || exit 1
|
||||||
|
(cd frontend && npm i && npm run build-only) || exit 1
|
||||||
|
|
||||||
|
# copy backend
|
||||||
|
cp -r backend/dist/* dist
|
||||||
|
cp -r backend/node_modules dist
|
||||||
|
cp backend/config.json dist
|
||||||
|
|
||||||
|
# copy front end
|
||||||
|
mkdir dist/_client
|
||||||
|
cp -r frontend/dist/* dist/_client
|
||||||
|
|
||||||
|
# create start script
|
||||||
|
echo '#!/bin/bash' > dist/start.sh
|
||||||
|
echo 'cd "$(dirname "$0")"' >> dist/start.sh
|
||||||
|
echo 'node main.js' >> dist/start.sh
|
||||||
|
chmod +x dist/start.sh
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
{
|
{
|
||||||
"name": "mangastatus",
|
"name": "mangastatus-frontend",
|
||||||
"version": "0.0.0",
|
"version": "1.0.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
"author": "wea_ondara",
|
||||||
|
"license": "ISC",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
"build": "run-p type-check \"build-only {@}\" --",
|
"build": "run-p type-check \"build-only {@}\" --",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type {AniListMangaListCollection} from '@/data/models/anilist/AniListMang
|
|||||||
|
|
||||||
export default class AniListApi {
|
export default class AniListApi {
|
||||||
async fetchUser(userName: string): Promise<AniListUser> {
|
async fetchUser(userName: string): Promise<AniListUser> {
|
||||||
const res = fetch('/graphql', {
|
const res = fetch('/anilist/graphql', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -18,7 +18,7 @@ export default class AniListApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fetchManga(userId: number): Promise<AniListMangaListCollection> {
|
async fetchManga(userId: number): Promise<AniListMangaListCollection> {
|
||||||
const res = fetch('/graphql', {
|
const res = fetch('/anilist/graphql', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ export default class MangaUpdatesDataService {
|
|||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
} finally {
|
} finally {
|
||||||
await new Promise((r) => setTimeout(r, 1000));
|
// await new Promise((r) => setTimeout(r, 1000));
|
||||||
}
|
}
|
||||||
matching = results.results
|
matching = results.results
|
||||||
.filter(e => stringSimilarity(title, e.record.title, 2, false) >= 0.95)
|
.filter(e => stringSimilarity(title, e.record.title, 2, false) >= 0.95)
|
||||||
|
|||||||
@@ -23,25 +23,14 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
clearScreen: false,
|
clearScreen: false,
|
||||||
server: {
|
server: {
|
||||||
proxy: {
|
proxy: {//for dev
|
||||||
'/graphql': {
|
'^/anilist/.*$': {
|
||||||
target: 'https://graphql.anilist.co',
|
target: 'http://localhost:5000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
configure: (proxy, options) => {
|
|
||||||
proxy.on('proxyRes', proxyRes => {
|
|
||||||
delete proxyRes.headers['set-cookie'];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
'^/mangaupdates/.*$': {
|
'^/mangaupdates/.*$': {
|
||||||
target: 'https://api.mangaupdates.com',
|
target: 'http://localhost:5000',
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
rewrite: (path) => path.replace(/^\/mangaupdates/, ''),
|
|
||||||
configure: (proxy, options) => {
|
|
||||||
proxy.on('proxyRes', proxyRes => {
|
|
||||||
delete proxyRes.headers['set-cookie'];
|
|
||||||
});
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
Reference in New Issue
Block a user