UNPKG

@book000/pixivts

Version:

pixiv Unofficial API Library for TypeScript

263 lines 10.2 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ResponseDatabase = void 0; const typeorm_1 = require("typeorm"); const typeorm_naming_strategies_1 = require("typeorm-naming-strategies"); const response_entity_1 = require("./response-entity"); const node_crypto_1 = __importDefault(require("node:crypto")); /** * レスポンスを保存するデータベース */ class ResponseDatabase { constructor(options = {}) { const configuration = { DB_HOSTNAME: options.hostname ?? process.env.RESPONSE_DB_HOSTNAME, DB_PORT: options.port ?? process.env.RESPONSE_DB_PORT, DB_USERNAME: options.username ?? process.env.RESPONSE_DB_USERNAME, DB_PASSWORD: options.password ?? process.env.RESPONSE_DB_PASSWORD, DB_DATABASE: options.database ?? process.env.RESPONSE_DB_DATABASE, }; // DB_PORTがintパースできない場合はエラー // DB_PORTがundefinedの場合はデフォルトポートを使用する const port = this.parsePort(configuration.DB_PORT); this.dataSource = new typeorm_1.DataSource({ type: 'mysql', host: configuration.DB_HOSTNAME, port, username: configuration.DB_USERNAME, password: configuration.DB_PASSWORD, database: configuration.DB_DATABASE, synchronize: true, logging: process.env.PRINT_DB_LOGS === 'true', namingStrategy: new typeorm_naming_strategies_1.SnakeNamingStrategy(), entities: [response_entity_1.DBResponse], subscribers: [], migrations: [], timezone: '+09:00', supportBigNumbers: true, bigNumberStrings: true, }); } /** * データソースを初期化する * * @returns 初期化に成功したかどうか */ async init() { if (this.dataSource.isInitialized) { return true; } try { await this.dataSource.initialize(); ResponseDatabase.printDebug('Responses database initialized'); return true; } catch (error) { ResponseDatabase.printDebug('Responses database initialization failed', error); return false; } } /** * データソースをマイグレーションする */ async migrate() { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } if (this.dataSource.migrations.length === 0) { return; } await this.dataSource.runMigrations(); } /** * データソーススキーマを同期する */ async sync() { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } await this.dataSource.synchronize(); } /** * レスポンスを追加する * * @param options レスポンスの追加オプション * @returns 追加されたレスポンス */ async addResponse(options) { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } const response = new response_entity_1.DBResponse(); response.method = options.method; response.endpoint = options.endpoint; response.url = options.url; response.urlHash = node_crypto_1.default .createHash('sha256') .update(options.url ?? '') .digest('hex'); response.requestHeaders = options.requestHeaders; response.requestBody = options.requestBody; response.responseType = options.responseType; response.statusCode = options.statusCode; response.responseHeaders = options.responseHeaders; response.responseBody = options.responseBody; response.createdAt = new Date(); return response.save(); } /** * レスポンスを取得する。但し直近90日間のレスポンスのみ取得可能 * * @param endpoint エンドポイントの情報。指定しない場合はすべてのレスポンスを取得する * @param rangeOptions 取得するレスポンスの範囲 * * @returns レスポンスの配列 */ async getResponses(endpoint, rangeOptions) { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } const options = rangeOptions ?? {}; const page = options.page; const limit = options.limit; // ResponseEndPointWithCountで来る場合があるので、ResponseEndPointに変換する // 配列でない場合は配列に変換する const endpoints = endpoint ? Array.isArray(endpoint) ? endpoint.map((v) => ({ method: v.method, endpoint: v.endpoint, statusCode: v.statusCode, })) : [ { method: endpoint.method, endpoint: endpoint.endpoint, statusCode: endpoint.statusCode, // 90日前以降のレスポンスのみ取得する createdAt: (0, typeorm_1.MoreThanOrEqual)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }, ] : { // 90日前以降のレスポンスのみ取得する createdAt: (0, typeorm_1.MoreThanOrEqual)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }; if (page === undefined || limit === undefined) { return response_entity_1.DBResponse.find({ where: endpoints, order: { id: 'DESC' } }); } if (page <= 0 || limit <= 0) { throw new Error(`Responses database range is invalid (page: ${page}, limit: ${limit})`); } return response_entity_1.DBResponse.find({ where: endpoints, order: { id: 'DESC' }, skip: (page - 1) * limit, take: limit, }); } /** * レスポンスの数を取得する。但し直近90日間のレスポンスのみ取得可能 * * @param endpoint エンドポイントの情報。指定しない場合はすべてのレスポンスを取得する * @returns レスポンスの数 */ async getResponseCount(endpoint) { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } const endpoints = endpoint ? Array.isArray(endpoint) ? endpoint.map((v) => ({ method: v.method, endpoint: v.endpoint, statusCode: v.statusCode, createdAt: (0, typeorm_1.MoreThanOrEqual)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), })) : [ { method: endpoint.method, endpoint: endpoint.endpoint, statusCode: endpoint.statusCode, createdAt: (0, typeorm_1.MoreThanOrEqual)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }, ] : { // 90日前以降のレスポンスのみ取得する createdAt: (0, typeorm_1.MoreThanOrEqual)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }; return response_entity_1.DBResponse.count({ where: endpoints }); } /** * エンドポイントを取得する。但し直近90日間のレスポンスのみ取得可能 */ async getEndpoints() { if (!this.dataSource.isInitialized) { throw new Error('Responses database is not initialized'); } // method, endpointの組み合わせを取得する return response_entity_1.DBResponse.createQueryBuilder() .where({ responseType: 'JSON', createdAt: (0, typeorm_1.MoreThan)(new Date(Date.now() - 1000 * 60 * 60 * 24 * 90)), }) .groupBy('method, endpoint, status_code') .select([ 'method', 'endpoint', 'status_code AS statusCode', 'COUNT(*) AS count', ]) .getRawMany(); } async close() { if (!this.dataSource.isInitialized) { return; } await this.dataSource.destroy(); } isInitialized() { return this.dataSource.isInitialized; } /** * データソースを取得する * * @returns データソース */ getDataSource() { return this.dataSource; } /** * 環境変数で指定されたデータベースのポートをパースする。 * 数値にパースできない場合と1以上でない場合はエラーを投げる。 * undefinedの場合は、各データベースのデフォルトポートを使用する * * @param port データベースのポート * @returns パースされたポート */ parsePort(port) { if (port === undefined) { return 3306; } const parsedPort = Number.parseInt(port); if (Number.isNaN(parsedPort) || parsedPort <= 0) { throw new Error('Responses database port is invalid'); } return parsedPort; } static printDebug(text, error) { if (process.env.NODE_ENV !== 'development' && process.env.NODE_ENV !== 'test') { return; } if (error !== undefined) { console.error(`[PixivTs@ResponseDatabase] ${text}`, error); return; } console.debug(`[PixivTs@ResponseDatabase] ${text}`); } } exports.ResponseDatabase = ResponseDatabase; //# sourceMappingURL=index.js.map