@chevre/domain
Version:
Chevre Domain Library for Node.js
320 lines (319 loc) • 14.8 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.StockHolderRepo = void 0;
const createDebug = require("debug");
const moment = require("moment");
const factory = require("../factory");
// import { createSchema, IModel, ISetting, modelName } from './mongoose/schemas/setting';
const pendingReservation_1 = require("./pendingReservation");
const debug = createDebug('chevre-domain:repo:stockHolder');
const SEARCH_OFFERS_MAX_LENGTH = 100;
const USE_STOCK_HOLDER_CHECK_CONFLICT = process.env.USE_STOCK_HOLDER_CHECK_CONFLICT === '1';
/**
* イベントストックホルダーリポジトリ
*/
class StockHolderRepo {
constructor(redisClient, connection) {
this.redisClient = redisClient;
// this.settingModel = connection.model(modelName, createSchema());
this.pendingReservationRepo = new pendingReservation_1.PendingReservationRepo(connection);
}
static offer2field(params, hasTicketedSeat) {
var _a, _b;
if (hasTicketedSeat) {
return `${params.seatSection}:${params.seatNumber}`;
}
else {
// 予約IDをfieldにする場合
const serviceOutputId = (_b = (_a = params.itemOffered) === null || _a === void 0 ? void 0 : _a.serviceOutput) === null || _b === void 0 ? void 0 : _b.id;
if (typeof serviceOutputId === 'string') {
return serviceOutputId;
}
else {
throw new factory.errors.Internal('offer2field requires itemOffered.serviceOutput.id');
}
}
}
static createKey(params) {
if (!(params.startDate instanceof Date)) {
throw new factory.errors.Argument('startDate', 'must be Date');
}
return `${StockHolderRepo.KEY_PREFIX_NEW}:${params.eventId}`;
}
/**
* 座席をロックする(maxキャパシティチェック有)
*/
lockIfNotLimitExceeded(lockKey, maximum) {
return __awaiter(this, void 0, void 0, function* () {
const useMongoose = yield this.useMongoose({
project: { id: lockKey.project.id },
eventId: lockKey.eventId,
startDate: lockKey.startDate,
hasTicketedSeat: lockKey.hasTicketedSeat
});
if (useMongoose) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.lockIfNotLimitExceeded(lockKey, maximum);
}
else {
const key = StockHolderRepo.createKey({ eventId: lockKey.eventId, startDate: lockKey.startDate });
yield this.redisClient.watch(key);
const hashCount = yield this.redisClient.hLen(key);
// Process result
// Heavy and time consuming operation here
debug('checking hash count...hashCount:', hashCount);
if (hashCount + lockKey.offers.length > maximum) {
throw new factory.errors.Argument('Event', 'maximumAttendeeCapacity exceeded');
}
yield this.lock(lockKey);
}
});
}
/**
* 座席をロックする
*/
// tslint:disable-next-line:max-func-body-length
lock(lockKey) {
return __awaiter(this, void 0, void 0, function* () {
if (!(lockKey.expires instanceof Date)) {
throw new factory.errors.Argument('expires', 'must be Date');
}
const useMongoose = yield this.useMongoose({
project: { id: lockKey.project.id },
eventId: lockKey.eventId,
startDate: lockKey.startDate,
hasTicketedSeat: lockKey.hasTicketedSeat
});
const key = StockHolderRepo.createKey({ eventId: lockKey.eventId, startDate: lockKey.startDate });
// await this.checkIfConflicted({ key, eventId: lockKey.eventId, useMongoose });
if (useMongoose) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.lock(lockKey);
}
else {
const value = lockKey.holder;
const multi = this.redisClient.multi();
const fields = lockKey.offers.map((offer) => StockHolderRepo.offer2field(offer, lockKey.hasTicketedSeat));
// check uniqueness(2025-04-20~)
const uniqueFields = [...new Set(fields)];
if (uniqueFields.length !== fields.length) {
throw new factory.errors.Argument('offers', 'offers must be unique');
}
fields.forEach((field) => {
multi.hSetNX(key, field, value);
});
const results = yield multi.expireAt(key, moment(lockKey.expires)
.unix())
.exec();
const lockedFields = [];
if (Array.isArray(results)) {
results.slice(0, fields.length)
.forEach((r, index) => {
if (r === 1 || r === true) {
lockedFields.push(fields[index]);
}
});
}
const lockedAll = lockedFields.length === fields.length;
debug('lockedAll?', lockedAll);
// expireAtReplyの検証も追加する(2023-04-19~)
const expiredAll = results.slice(fields.length)
.every((r) => (r === 1 || r === true));
debug('expiredAll?', expiredAll);
if (!lockedAll || !expiredAll) {
if (lockedFields.length > 0) {
// 全て仮押さえできなければ仮押さえできたものは解除
yield this.redisClient.multi()
.hDel(key, lockedFields)
.exec();
}
if (!lockedAll) {
throw new factory.errors.AlreadyInUse(factory.reservationType.EventReservation, ['ticketedSeat'], 'Already hold');
}
else {
throw new factory.errors.Internal('timeout cannot be set unexpectedly');
}
}
}
});
}
/**
* 座席ロックを解除する
*/
unlock(params) {
return __awaiter(this, void 0, void 0, function* () {
const useMongoose = yield this.useMongoose({
project: { id: params.project.id },
eventId: params.eventId,
startDate: params.startDate,
hasTicketedSeat: params.hasTicketedSeat
});
const key = StockHolderRepo.createKey({ eventId: params.eventId, startDate: params.startDate });
// await this.checkIfConflicted({ key, eventId: params.eventId, useMongoose });
if (useMongoose) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.unlock(params);
}
else {
const field = StockHolderRepo.offer2field(params.offer, params.hasTicketedSeat);
yield this.redisClient.multi()
.hDel(key, field)
.exec();
}
});
}
/**
* 空席でない座席をカウントする
*/
countUnavailableOffers(params) {
return __awaiter(this, void 0, void 0, function* () {
if (yield this.useMongoose({
project: { id: params.project.id },
eventId: params.event.id,
startDate: params.event.startDate,
hasTicketedSeat: params.event.hasTicketedSeat
})) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.countUnavailableOffers(params);
}
else {
const key = StockHolderRepo.createKey({ eventId: params.event.id, startDate: params.event.startDate });
const reply = yield this.redisClient.hLen(key);
let fieldCount = 0;
if (typeof reply === 'number') {
fieldCount = Number(reply);
}
return fieldCount;
}
});
}
/**
* 保持者を取得する
*/
getHolder(params) {
return __awaiter(this, void 0, void 0, function* () {
if (yield this.useMongoose({
project: { id: params.project.id },
eventId: params.eventId,
startDate: params.startDate,
hasTicketedSeat: params.hasTicketedSeat
})) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.getHolder(params);
}
else {
const key = StockHolderRepo.createKey({ eventId: params.eventId, startDate: params.startDate });
const field = StockHolderRepo.offer2field(params.offer, params.hasTicketedSeat);
return this.redisClient.hGet(key, field);
}
});
}
searchHolders(params) {
return __awaiter(this, void 0, void 0, function* () {
// validate offers.length(2025-04-18~)
if (params.offers.length > SEARCH_OFFERS_MAX_LENGTH) {
throw new factory.errors.Argument('offers', `offers.length must be <= ${SEARCH_OFFERS_MAX_LENGTH}`);
}
if (yield this.useMongoose({
project: { id: params.project.id },
eventId: params.eventId,
startDate: params.startDate,
hasTicketedSeat: params.hasTicketedSeat
})) {
// throw new factory.errors.NotImplemented('new stock holder repository not implemented');
return this.pendingReservationRepo.searchHolders(params);
}
else {
const key = StockHolderRepo.createKey({ eventId: params.eventId, startDate: params.startDate });
const fields = params.offers.map((o) => {
return StockHolderRepo.offer2field(o, params.hasTicketedSeat);
});
// Array reply: list of values associated with the given fields, in the same order as they are requested.
const result = yield this.redisClient.hmGet(key, fields);
if (!Array.isArray(result)) {
throw new factory.errors.Internal(`searchAvailability got non-array: ${typeof result}`);
}
// そのまま返却(2023-04-17~)
return result;
}
});
}
redisKeyExists(params) {
return __awaiter(this, void 0, void 0, function* () {
const key = StockHolderRepo.createKey(params);
const existingRedisKeyCount = yield this.redisClient.exists(key);
if (typeof existingRedisKeyCount !== 'number') {
throw new factory.errors.Internal(`unexpected existingKeyCount: ${typeof existingRedisKeyCount}`);
}
return existingRedisKeyCount > 0;
});
}
checkIfConflicted(params) {
return __awaiter(this, void 0, void 0, function* () {
const redisKeyExists = yield this.redisKeyExists(params);
const mongoDocExists = yield this.pendingReservationRepo.docExists(params);
if (redisKeyExists && mongoDocExists) {
throw new factory.errors.Internal(`repository conflicted. eventId:${params.eventId}`);
}
});
}
/**
* 万が一に備えて、保留予約をredis->mongo移行
*/
migrate2mongoJustInCase(params) {
return __awaiter(this, void 0, void 0, function* () {
const redisKey = StockHolderRepo.createKey({ eventId: params.eventId, startDate: params.startDate });
return {
expireTime: yield this.redisClient.expireTime(redisKey),
hash: yield this.redisClient.hGetAll(redisKey)
};
});
}
/**
* 新リポジトリを使用するかどうか
*/
useMongoose(params) {
return __awaiter(this, void 0, void 0, function* () {
if (!(params.startDate instanceof Date)) {
throw new factory.errors.Argument('startDate', 'must be Date');
}
let useMongoose = false;
// useMongoAsStockHolder設定有の場合のみmongo利用(2025-04-18~)
// const useMongoAsStockHolderBySettings = await this.useMongoAsStockHolderBySettings({ project: { id: params.project.id } });
// if (useMongoAsStockHolderBySettings) {
// const redisKeyExists = await this.redisKeyExists(params);
// if (redisKeyExists) {
// useMongoose = false;
// } else {
// // redis keyが存在しなければmongo利用
// useMongoose = true;
// }
// }
// always use mongo(2025-05-21~)
const redisKeyExists = yield this.redisKeyExists(params);
if (redisKeyExists) {
useMongoose = false;
}
else {
// redis keyが存在しなければmongo利用
useMongoose = true;
}
// check confliction for test
if (USE_STOCK_HOLDER_CHECK_CONFLICT) {
yield this.checkIfConflicted(params);
}
return useMongoose;
});
}
}
exports.StockHolderRepo = StockHolderRepo;
StockHolderRepo.KEY_PREFIX_NEW = 'stockHolder';
;