@book000/pixivts
Version:
pixiv Unofficial API Library for TypeScript
263 lines • 10.2 kB
JavaScript
;
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