@chevre/domain
Version:
Chevre Domain Library for Node.js
257 lines (234 loc) • 9.16 kB
text/typescript
// tslint:disable:no-console
import * as moment from 'moment-timezone';
import * as mongoose from 'mongoose';
import * as redis from 'redis';
import { chevre } from '../../../../lib/index';
const today = moment()
.tz('Asia/Tokyo')
.format('YYYYMMDD');
const project = { id: String(process.env.PROJECT_ID) };
const providerId = 'sampleProviderId';
const eventId = `sampleEventId${today}:03`;
const eventStartDate = new Date('2025-05-01T00:00:00Z');
const expires = moment(eventStartDate)
.add(1, 'week')
.toDate();
const seatSection = 'SampleSectionNameXXXXXXXXXXXXXXXXXXX';
// tslint:disable-next-line:no-magic-numbers prefer-array-literal
const allSeatNumbers = [...Array(10000)].map((__, seatKey) => `SampleSeatNumber-${seatKey}`);
const OPERATION_INTERVAL = 2000;
// const MAXIMUM_CAPACITY = 10002;
mongoose.Model.on('index', (...args) => {
console.error('******** index event emitted. ********\n', args);
});
async function sleep(waitTime: number): Promise<void> {
return new Promise<void>((resolve) => {
setTimeout(
() => {
resolve();
},
waitTime
);
});
}
const client = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
socket: {
port: Number(<string>process.env.REDIS_PORT),
host: <string>process.env.REDIS_HOST
},
password: <string>process.env.REDIS_KEY
})
.on('error', (err) => {
// eslint-disable-next-line no-console
console.error('createDefaultRedisClient: client onError:', err);
// reject(err);
});
client.connect();
mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
const formatter = new Intl.NumberFormat('ja-JP');
// => "1,000"
// tslint:disable-next-line:max-func-body-length
async function lockSeatsForcibly(params: {
maximumCapacity?: number;
}) {
const { maximumCapacity } = params;
let startTime: [number, number] = process.hrtime();
let diff: [number, number] = process.hrtime(startTime);
const stockHolderRepo = await chevre.repository.StockHolder.createInstance(
client,
mongoose.connection
);
startTime = process.hrtime();
console.log('counting unavailableOffers...');
let countUnavailableOffersResult = await stockHolderRepo.countUnavailableOffers({
project: { id: project.id },
event: {
id: eventId,
startDate: eventStartDate,
hasTicketedSeat: true
}
});
console.log('countUnavailableOffersResult:', countUnavailableOffersResult);
console.log('diff:', [diff[0], formatter.format(diff[1])]);
await sleep(OPERATION_INTERVAL);
startTime = process.hrtime();
console.log('counting searching holders...');
const searchHoldersResult = await stockHolderRepo.searchHolders({
project: { id: project.id },
eventId,
startDate: eventStartDate,
hasTicketedSeat: true,
offers: [
// tslint:disable-next-line:no-magic-numbers
...allSeatNumbers.slice(0, 100)
.map((seatNumber) => ({ seatSection, seatNumber }))
]
});
diff = process.hrtime(startTime);
console.log('searchHoldersResult.length:', searchHoldersResult.length);
console.log('diff:', [diff[0], formatter.format(diff[1])]);
// select 2 seats
// tslint:disable-next-line:insecure-random
const seatNumber1 = allSeatNumbers[Math.floor(allSeatNumbers.length * Math.random())];
const seatNumber2 = allSeatNumbers.filter((seatNumber) => seatNumber !== seatNumber1)[
// tslint:disable-next-line:insecure-random
Math.floor((allSeatNumbers.length - 1) * Math.random())
];
const seatNumbers = [seatNumber1, seatNumber2];
console.log('seats selected.', seatNumbers);
let locked = false;
while (!locked) {
await sleep(OPERATION_INTERVAL);
startTime = process.hrtime();
const currentHolders: string[] = [];
for (const seatNumber of seatNumbers) {
const currentHolder = await stockHolderRepo.getHolder({
project: { id: project.id },
eventId,
startDate: eventStartDate,
hasTicketedSeat: true,
offer: { seatSection, seatNumber }
});
if (typeof currentHolder === 'string') {
currentHolders.push(currentHolder);
}
}
diff = process.hrtime(startTime);
console.log('currentHolders:', currentHolders);
console.log('diff:', [diff[0], formatter.format(diff[1])]);
try {
await sleep(OPERATION_INTERVAL);
const newHolder = Date.now()
.toString();
const bookingTime = new Date();
startTime = process.hrtime();
console.log('locking...', newHolder, seatSection, seatNumbers);
if (typeof maximumCapacity === 'number') {
try {
await stockHolderRepo.lockIfNotLimitExceeded(
{
project: { id: project.id },
provider: { id: providerId },
eventId,
startDate: eventStartDate,
hasTicketedSeat: true,
offers: seatNumbers.map((seatNumber) => ({ seatSection, seatNumber })),
expires,
holder: newHolder,
bookingTime
},
maximumCapacity
);
} catch (error) {
if (error.message === 'maximumAttendeeCapacity exceeded') {
// ok
} else {
throw error;
}
}
} else {
await stockHolderRepo.lock({
project: { id: project.id },
provider: { id: providerId },
eventId,
startDate: eventStartDate,
hasTicketedSeat: true,
offers: seatNumbers.map((seatNumber) => ({ seatSection, seatNumber })),
expires,
holder: newHolder,
bookingTime
});
}
diff = process.hrtime(startTime);
console.log('locked.', newHolder);
console.log('diff:', [diff[0], formatter.format(diff[1])]);
locked = true;
} catch (error) {
if (error instanceof chevre.factory.errors.AlreadyInUse) {
console.log('lockResult:', error.name, error.message);
} else {
console.error(error);
}
}
await sleep(OPERATION_INTERVAL);
for (const currentHolder of currentHolders) {
for (const seatNumber of seatNumbers) {
startTime = process.hrtime();
console.log('unlocking...', currentHolder, seatSection, seatNumber);
const unlockResult = await stockHolderRepo.unlock({
project: { id: project.id },
eventId,
startDate: eventStartDate,
hasTicketedSeat: true,
offer: { seatSection, seatNumber },
holder: currentHolder
});
diff = process.hrtime(startTime);
console.log('unlockResult:', unlockResult);
console.log('diff:', [diff[0], formatter.format(diff[1])]);
}
}
}
await sleep(OPERATION_INTERVAL);
countUnavailableOffersResult = await stockHolderRepo.countUnavailableOffers({
project: { id: project.id },
event: {
id: eventId,
startDate: eventStartDate,
hasTicketedSeat: true
}
});
console.log('countUnavailableOffersResult:', countUnavailableOffersResult);
}
async function main() {
// tslint:disable-next-line:no-magic-numbers
const numLock = (typeof process.argv[2] === 'string') ? Number(process.argv[2]) : 1;
// tslint:disable-next-line:no-magic-numbers
const maximumCapacity = (typeof process.argv[3] === 'string') ? Number(process.argv[3]) : undefined;
const LOCK_INTERVAL = 300;
let i = 0;
let lockedCount = 0;
let timeout: NodeJS.Timeout;
const processStartDate = new Date();
function onSeatsLocked() {
lockedCount += 1;
console.log(lockedCount, 'lockSeatsForcibly executed!');
console.log('processed.', processStartDate, '~', new Date());
}
timeout = setInterval(
() => {
if (i >= numLock) {
clearInterval(timeout);
return;
}
i += 1;
lockSeatsForcibly({ maximumCapacity })
.then(onSeatsLocked)
.catch(console.error);
},
LOCK_INTERVAL
);
}
main()
.then()
.catch(console.error);