@chevre/domain 22.10.0-alpha.20 → 22.10.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/example/src/chevre/concurrentLock/lockOfferRateLimit.ts +64 -0
- package/example/src/chevre/concurrentLock/lockTransactionProcess.ts +44 -0
- package/example/src/chevre/transactionNumber/setUseMongo4orderNumberFrom.ts +41 -0
- package/example/src/redisMulti.ts +63 -0
- package/lib/chevre/repo/concurrentLock.d.ts +14 -0
- package/lib/chevre/repo/concurrentLock.js +48 -0
- package/lib/chevre/repo/concurrentLockAbstract.d.ts +42 -0
- package/lib/chevre/repo/concurrentLockAbstract.js +9 -0
- package/lib/chevre/repo/mongoose/schemas/setting.js +3 -1
- package/lib/chevre/repo/orderNumber.d.ts +7 -0
- package/lib/chevre/repo/orderNumber.js +13 -0
- package/lib/chevre/repo/passport.d.ts +1 -1
- package/lib/chevre/repo/passport.js +31 -13
- package/lib/chevre/repo/rateLimit/offer.d.ts +7 -2
- package/lib/chevre/repo/rateLimit/offer.js +41 -40
- package/lib/chevre/repo/transactionProcess.d.ts +7 -4
- package/lib/chevre/repo/transactionProcess.js +34 -13
- package/lib/chevre/service/aggregation/event/aggregateOffers.js +1 -0
- package/lib/chevre/service/assetTransaction/reserve/start.js +9 -1
- package/lib/chevre/service/offer/event/checkAvailability.d.ts +1 -1
- package/lib/chevre/service/offer/event/checkAvailability.js +1 -0
- package/lib/chevre/service/reserve/cancelReservation.d.ts +3 -0
- package/lib/chevre/service/reserve/cancelReservation.js +5 -1
- package/package.json +1 -1
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// tslint:disable:no-console
|
|
2
|
+
import * as redis from 'redis';
|
|
3
|
+
|
|
4
|
+
import { chevre } from '../../../../lib/index';
|
|
5
|
+
|
|
6
|
+
const project = { id: String(process.env.PROJECT_ID) };
|
|
7
|
+
|
|
8
|
+
const client = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
|
|
9
|
+
socket: {
|
|
10
|
+
port: Number(<string>process.env.REDIS_PORT),
|
|
11
|
+
host: <string>process.env.REDIS_HOST
|
|
12
|
+
},
|
|
13
|
+
password: <string>process.env.REDIS_KEY
|
|
14
|
+
})
|
|
15
|
+
.on('error', (err) => {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.error('createDefaultRedisClient: client onError:', err);
|
|
18
|
+
// reject(err);
|
|
19
|
+
});
|
|
20
|
+
client.connect();
|
|
21
|
+
|
|
22
|
+
// tslint:disable-next-line:max-func-body-length
|
|
23
|
+
async function main() {
|
|
24
|
+
const rateLimitRepo = await chevre.repository.rateLimit.Offer.createInstance(
|
|
25
|
+
client
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
// await rateLimitRepo.unlock({
|
|
29
|
+
// project: { id: project.id },
|
|
30
|
+
// reservedTicket: {
|
|
31
|
+
// ticketType: {
|
|
32
|
+
// validRateLimit: {
|
|
33
|
+
// scope: 'scope2',
|
|
34
|
+
// unitInSeconds: 60
|
|
35
|
+
// }
|
|
36
|
+
// }
|
|
37
|
+
// },
|
|
38
|
+
// reservationFor: {
|
|
39
|
+
// startDate: new Date()
|
|
40
|
+
// },
|
|
41
|
+
// reservationNumber: 'xxx'
|
|
42
|
+
// });
|
|
43
|
+
// console.log('unlocked');
|
|
44
|
+
await rateLimitRepo.lock({
|
|
45
|
+
project: { id: project.id },
|
|
46
|
+
reservedTicket: {
|
|
47
|
+
ticketType: {
|
|
48
|
+
validRateLimit: {
|
|
49
|
+
scope: 'scope2',
|
|
50
|
+
unitInSeconds: 60
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
reservationFor: {
|
|
55
|
+
startDate: new Date()
|
|
56
|
+
},
|
|
57
|
+
reservationNumber: 'xxx'
|
|
58
|
+
});
|
|
59
|
+
console.log('locked');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main()
|
|
63
|
+
.then()
|
|
64
|
+
.catch(console.error);
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// tslint:disable:no-console
|
|
2
|
+
import * as redis from 'redis';
|
|
3
|
+
|
|
4
|
+
import { chevre } from '../../../../lib/index';
|
|
5
|
+
|
|
6
|
+
const project = { id: String(process.env.PROJECT_ID) };
|
|
7
|
+
|
|
8
|
+
const client = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
|
|
9
|
+
socket: {
|
|
10
|
+
port: Number(<string>process.env.REDIS_PORT),
|
|
11
|
+
host: <string>process.env.REDIS_HOST
|
|
12
|
+
},
|
|
13
|
+
password: <string>process.env.REDIS_KEY
|
|
14
|
+
})
|
|
15
|
+
.on('error', (err) => {
|
|
16
|
+
// eslint-disable-next-line no-console
|
|
17
|
+
console.error('createDefaultRedisClient: client onError:', err);
|
|
18
|
+
// reject(err);
|
|
19
|
+
});
|
|
20
|
+
client.connect();
|
|
21
|
+
|
|
22
|
+
// tslint:disable-next-line:max-func-body-length
|
|
23
|
+
async function main() {
|
|
24
|
+
const processRepo = await chevre.repository.TransactionProcess.createInstance(
|
|
25
|
+
client,
|
|
26
|
+
{ lockExpiresInSeconds: 60 }
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
// await processRepo.unlock({
|
|
30
|
+
// id: 'xxx',
|
|
31
|
+
// typeOf: chevre.factory.transactionType.PlaceOrder
|
|
32
|
+
// });
|
|
33
|
+
// console.log('unlocked');
|
|
34
|
+
await processRepo.lock({
|
|
35
|
+
id: 'xxx',
|
|
36
|
+
typeOf: chevre.factory.transactionType.PlaceOrder,
|
|
37
|
+
project: { id: project.id }
|
|
38
|
+
});
|
|
39
|
+
console.log('locked');
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
main()
|
|
43
|
+
.then()
|
|
44
|
+
.catch(console.error);
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// tslint:disable:no-console
|
|
2
|
+
import * as moment from 'moment';
|
|
3
|
+
import * as mongoose from 'mongoose';
|
|
4
|
+
import * as redis from 'redis';
|
|
5
|
+
import { chevre } from '../../../../lib/index';
|
|
6
|
+
|
|
7
|
+
// const project = { id: String(process.env.PROJECT_ID) };
|
|
8
|
+
|
|
9
|
+
const redisClient = redis.createClient<redis.RedisDefaultModules, Record<string, never>, Record<string, never>>({
|
|
10
|
+
socket: {
|
|
11
|
+
port: Number(<string>process.env.REDIS_PORT),
|
|
12
|
+
host: <string>process.env.REDIS_HOST
|
|
13
|
+
},
|
|
14
|
+
password: <string>process.env.REDIS_KEY,
|
|
15
|
+
name: 'checkRedisKeyCount'
|
|
16
|
+
})
|
|
17
|
+
.on('error', (err) => {
|
|
18
|
+
// eslint-disable-next-line no-console
|
|
19
|
+
console.error('createDefaultRedisClient: client onError:', err);
|
|
20
|
+
// reject(err);
|
|
21
|
+
});
|
|
22
|
+
redisClient.connect();
|
|
23
|
+
mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const orderNumberRepo = await chevre.repository.OrderNumber.createInstance({
|
|
27
|
+
redisClient,
|
|
28
|
+
connection: mongoose.connection
|
|
29
|
+
});
|
|
30
|
+
const result = await orderNumberRepo.setUseMongo4orderNumberFrom({
|
|
31
|
+
useMongo4orderNumberFrom: moment('2025-05-26T05:40:00Z')
|
|
32
|
+
.toDate()
|
|
33
|
+
});
|
|
34
|
+
console.log(result);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
main()
|
|
38
|
+
.then(() => {
|
|
39
|
+
console.log('success!');
|
|
40
|
+
})
|
|
41
|
+
.catch(console.error);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
|
|
2
|
+
// tslint:disable:no-console no-magic-numbers no-null-keyword
|
|
3
|
+
import type * as Redis from 'redis';
|
|
4
|
+
|
|
5
|
+
const REDIS_PING_INTERVAL_MS: number = (typeof process.env.REDIS_PING_INTERVAL_MS === 'string')
|
|
6
|
+
? Number(process.env.REDIS_PING_INTERVAL_MS)
|
|
7
|
+
// tslint:disable-next-line:no-magic-numbers
|
|
8
|
+
: 30000;
|
|
9
|
+
const REDIS_HOST: string = String(process.env.REDIS_HOST);
|
|
10
|
+
const REDIS_PORT: number = Number(process.env.REDIS_PORT);
|
|
11
|
+
const REDIS_KEY: string = String(process.env.REDIS_KEY);
|
|
12
|
+
|
|
13
|
+
const redisClientOptions: Redis.RedisClientOptions<Redis.RedisDefaultModules, Record<string, never>, Record<string, never>> = {
|
|
14
|
+
socket: {
|
|
15
|
+
host: REDIS_HOST,
|
|
16
|
+
port: REDIS_PORT
|
|
17
|
+
// tls: (process.env.REDIS_TLS_SERVERNAME !== undefined) ? { servername: process.env.REDIS_TLS_SERVERNAME } : undefined
|
|
18
|
+
},
|
|
19
|
+
password: REDIS_KEY,
|
|
20
|
+
pingInterval: REDIS_PING_INTERVAL_MS,
|
|
21
|
+
disableOfflineQueue: false
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
let redis: typeof Redis | undefined;
|
|
25
|
+
async function createRedisClient(): Promise<Redis.RedisClientType> {
|
|
26
|
+
return new Promise<Redis.RedisClientType>(async (resolve, reject) => {
|
|
27
|
+
if (redis === undefined) {
|
|
28
|
+
redis = await import('redis');
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const client = redis.createClient<Redis.RedisDefaultModules, Record<string, never>, Record<string, never>>(redisClientOptions)
|
|
32
|
+
.on('error', (err) => {
|
|
33
|
+
// tslint:disable-next-line:no-console
|
|
34
|
+
console.error('createRedisClient: client onError:', err);
|
|
35
|
+
reject(err);
|
|
36
|
+
})
|
|
37
|
+
.on('connect', () => {
|
|
38
|
+
resolve(client);
|
|
39
|
+
});
|
|
40
|
+
// client.on('ready', () => {
|
|
41
|
+
// console.error('Redis Client ready');
|
|
42
|
+
// });
|
|
43
|
+
|
|
44
|
+
await client.connect()
|
|
45
|
+
.catch((err) => {
|
|
46
|
+
// tslint:disable-next-line:no-console
|
|
47
|
+
console.error('createRedisClient: redisClient.connect:', err);
|
|
48
|
+
reject(new Error('createRedisClient: failed in creating connection'));
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function main() {
|
|
54
|
+
const redisClient = await createRedisClient();
|
|
55
|
+
console.log('redisClient created.', redisClient.options?.name);
|
|
56
|
+
|
|
57
|
+
const multi = await redisClient.multi();
|
|
58
|
+
const results = await multi.exec();
|
|
59
|
+
console.log(results);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
main()
|
|
63
|
+
.catch(console.error);
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { RedisClientType } from 'redis';
|
|
2
|
+
import { AbstractConcurrentLockRepo, ILockParams, IUnlockParams } from './concurrentLockAbstract';
|
|
3
|
+
/**
|
|
4
|
+
* 同時実行ロックリポジトリ
|
|
5
|
+
*/
|
|
6
|
+
export declare class ConcurrentLockRepo implements AbstractConcurrentLockRepo {
|
|
7
|
+
private readonly redisClient;
|
|
8
|
+
constructor(params: {
|
|
9
|
+
redisClient: RedisClientType;
|
|
10
|
+
});
|
|
11
|
+
static CREATE_REDIS_KEY(params: Pick<ILockParams, 'about'>): string;
|
|
12
|
+
lock(params: ILockParams): Promise<void>;
|
|
13
|
+
unlock(params: IUnlockParams): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.ConcurrentLockRepo = void 0;
|
|
13
|
+
const factory = require("../factory");
|
|
14
|
+
/**
|
|
15
|
+
* 同時実行ロックリポジトリ
|
|
16
|
+
*/
|
|
17
|
+
class ConcurrentLockRepo {
|
|
18
|
+
constructor(params) {
|
|
19
|
+
const { redisClient } = params;
|
|
20
|
+
this.redisClient = redisClient;
|
|
21
|
+
}
|
|
22
|
+
static CREATE_REDIS_KEY(params) {
|
|
23
|
+
return params.about.identifier;
|
|
24
|
+
}
|
|
25
|
+
lock(params) {
|
|
26
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
27
|
+
const key = ConcurrentLockRepo.CREATE_REDIS_KEY(params);
|
|
28
|
+
const value = params.audience.identifier;
|
|
29
|
+
const results = yield this.redisClient.multi()
|
|
30
|
+
.setNX(key, value)
|
|
31
|
+
.expireAt(key, params.expires)
|
|
32
|
+
.exec();
|
|
33
|
+
if (Array.isArray(results) && (results[0] === 1 || results[0] === true)) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
throw new factory.errors.AlreadyInUse(params.about.identifier, []);
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
unlock(params) {
|
|
42
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
43
|
+
const key = ConcurrentLockRepo.CREATE_REDIS_KEY(params);
|
|
44
|
+
yield this.redisClient.del([key]);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.ConcurrentLockRepo = ConcurrentLockRepo;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import * as factory from '../factory';
|
|
2
|
+
export interface IOffer {
|
|
3
|
+
itemOffered?: {
|
|
4
|
+
serviceOutput?: {
|
|
5
|
+
/**
|
|
6
|
+
* 予約ID
|
|
7
|
+
*/
|
|
8
|
+
id?: string;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
seatSection: string;
|
|
12
|
+
seatNumber: string;
|
|
13
|
+
}
|
|
14
|
+
export interface IAbout {
|
|
15
|
+
identifier: string;
|
|
16
|
+
typeOf: 'Thing';
|
|
17
|
+
}
|
|
18
|
+
export interface IAudience {
|
|
19
|
+
identifier: string;
|
|
20
|
+
typeOf: 'Audience';
|
|
21
|
+
}
|
|
22
|
+
export interface IConcurrentLock {
|
|
23
|
+
project: {
|
|
24
|
+
id: string;
|
|
25
|
+
typeOf: factory.organizationType.Project;
|
|
26
|
+
};
|
|
27
|
+
typeOf: factory.creativeWorkType.Certification;
|
|
28
|
+
about: IAbout;
|
|
29
|
+
dateCreated: Date;
|
|
30
|
+
expires: Date;
|
|
31
|
+
audience: IAudience;
|
|
32
|
+
}
|
|
33
|
+
export type ILockParams = Pick<IConcurrentLock, 'about' | 'audience' | 'expires' | 'project'>;
|
|
34
|
+
export type IUnlockParams = Pick<IConcurrentLock, 'about' | 'audience'>;
|
|
35
|
+
export type IGetHolderResult = string | null;
|
|
36
|
+
/**
|
|
37
|
+
* 抽象同時実行ロックリポジトリ
|
|
38
|
+
*/
|
|
39
|
+
export declare abstract class AbstractConcurrentLockRepo {
|
|
40
|
+
abstract lock(lockKey: ILockParams): Promise<void>;
|
|
41
|
+
abstract unlock(params: IUnlockParams): Promise<void>;
|
|
42
|
+
}
|
|
@@ -25,7 +25,9 @@ const schemaDefinition = {
|
|
|
25
25
|
waiter: mongoose_1.SchemaTypes.Mixed,
|
|
26
26
|
useMongoAsStockHolder: Boolean,
|
|
27
27
|
useMongoAsStockHolderProjects: [String],
|
|
28
|
-
useMongo4confirmationNumberFrom: { type: Date, required: false }
|
|
28
|
+
useMongo4confirmationNumberFrom: { type: Date, required: false },
|
|
29
|
+
useMongo4orderNumberFrom: { type: Date, required: false },
|
|
30
|
+
useMongo4transactionNumberFrom: { type: Date, required: false }
|
|
29
31
|
};
|
|
30
32
|
const schemaOptions = {
|
|
31
33
|
autoIndex: settings_1.MONGO_AUTO_INDEX,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Connection } from 'mongoose';
|
|
2
2
|
import { RedisClientType } from 'redis';
|
|
3
|
+
import { ISetting } from './mongoose/schemas/setting';
|
|
3
4
|
/**
|
|
4
5
|
* 注文番号リポジトリ
|
|
5
6
|
*/
|
|
@@ -22,5 +23,11 @@ export declare class OrderNumberRepo {
|
|
|
22
23
|
*/
|
|
23
24
|
orderDate: Date;
|
|
24
25
|
}): Promise<string>;
|
|
26
|
+
/**
|
|
27
|
+
* DB移行時のみに使用目的の設定更新
|
|
28
|
+
*/
|
|
29
|
+
setUseMongo4orderNumberFrom(params: {
|
|
30
|
+
useMongo4orderNumberFrom: Date;
|
|
31
|
+
}): Promise<Pick<ISetting, "useMongo4orderNumberFrom"> | null>;
|
|
25
32
|
private useMongoBySettings;
|
|
26
33
|
}
|
|
@@ -84,6 +84,19 @@ class OrderNumberRepo {
|
|
|
84
84
|
return orderNumber;
|
|
85
85
|
});
|
|
86
86
|
}
|
|
87
|
+
/**
|
|
88
|
+
* DB移行時のみに使用目的の設定更新
|
|
89
|
+
*/
|
|
90
|
+
setUseMongo4orderNumberFrom(params) {
|
|
91
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
92
|
+
const { useMongo4orderNumberFrom } = params;
|
|
93
|
+
return this.settingModel.findOneAndUpdate({ 'project.id': { $eq: '*' } }, {
|
|
94
|
+
$set: { useMongo4orderNumberFrom }
|
|
95
|
+
}, { projection: { _id: 0, useMongo4orderNumberFrom: 1 } })
|
|
96
|
+
.lean()
|
|
97
|
+
.exec();
|
|
98
|
+
});
|
|
99
|
+
}
|
|
87
100
|
useMongoBySettings(params) {
|
|
88
101
|
return __awaiter(this, void 0, void 0, function* () {
|
|
89
102
|
const setting = yield this.settingModel.findOne({ 'project.id': { $eq: '*' } }, {
|
|
@@ -25,7 +25,7 @@ type IStartParams = (factory.transaction.placeOrder.IStartParamsWithoutDetail |
|
|
|
25
25
|
* 取引許可証リポジトリ
|
|
26
26
|
*/
|
|
27
27
|
export declare class PassportRepo {
|
|
28
|
-
private readonly
|
|
28
|
+
private readonly concurrentLockRepo;
|
|
29
29
|
private readonly options;
|
|
30
30
|
constructor(redisClient: RedisClientType, options: IOptions);
|
|
31
31
|
static CREATE_VERIFIED_PASSPORT(params: string | jwt.JwtPayload | undefined): IVerifiedPassport;
|
|
@@ -14,13 +14,15 @@ const createDebug = require("debug");
|
|
|
14
14
|
const jwt = require("jsonwebtoken");
|
|
15
15
|
const moment = require("moment");
|
|
16
16
|
const factory = require("../factory");
|
|
17
|
+
const concurrentLock_1 = require("./concurrentLock");
|
|
17
18
|
const debug = createDebug('chevre-domain:repo:passport');
|
|
18
19
|
/**
|
|
19
20
|
* 取引許可証リポジトリ
|
|
20
21
|
*/
|
|
21
22
|
class PassportRepo {
|
|
22
23
|
constructor(redisClient, options) {
|
|
23
|
-
this.
|
|
24
|
+
this.concurrentLockRepo = new concurrentLock_1.ConcurrentLockRepo({ redisClient });
|
|
25
|
+
// this.redisClient = redisClient;
|
|
24
26
|
this.options = options;
|
|
25
27
|
}
|
|
26
28
|
static CREATE_VERIFIED_PASSPORT(params) {
|
|
@@ -63,21 +65,37 @@ class PassportRepo {
|
|
|
63
65
|
const now = moment();
|
|
64
66
|
const passportExpires = moment.unix(params.exp);
|
|
65
67
|
const key = PassportRepo.CREATE_REDIS_KEY(params);
|
|
66
|
-
const
|
|
67
|
-
?
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
68
|
+
const expires = (passportExpires.isAfter(now))
|
|
69
|
+
? passportExpires.toDate()
|
|
70
|
+
: now.add(1, 'second')
|
|
71
|
+
.toDate();
|
|
72
|
+
// reimplement using concurrentLockRepo(2025-05-27~)
|
|
73
|
+
try {
|
|
74
|
+
yield this.concurrentLockRepo.lock({
|
|
75
|
+
project: { id: params.project.id, typeOf: factory.organizationType.Project },
|
|
76
|
+
about: { identifier: key, typeOf: 'Thing' },
|
|
77
|
+
expires,
|
|
78
|
+
audience: { identifier: '1', typeOf: 'Audience' }
|
|
79
|
+
});
|
|
80
|
+
debug('locked,', params.issueUnit.identifier, passportExpires);
|
|
77
81
|
}
|
|
78
|
-
|
|
82
|
+
catch (error) {
|
|
79
83
|
throw new factory.errors.AlreadyInUse('passport', [], 'passport already in use');
|
|
80
84
|
}
|
|
85
|
+
// const ttl = (passportExpires.isAfter(now))
|
|
86
|
+
// ? moment.unix(params.exp)
|
|
87
|
+
// .diff(moment(), 'seconds')
|
|
88
|
+
// : 1;
|
|
89
|
+
// const results = await this.redisClient.multi()
|
|
90
|
+
// .setNX(key, '1')
|
|
91
|
+
// .expire(key, ttl)
|
|
92
|
+
// .exec();
|
|
93
|
+
// debug('locked,', params.issueUnit.identifier, now, passportExpires, ttl, results);
|
|
94
|
+
// if (Array.isArray(results) && (results[0] === 1 || (<any>results)[0] === true)) {
|
|
95
|
+
// return;
|
|
96
|
+
// } else {
|
|
97
|
+
// throw new factory.errors.AlreadyInUse('passport', [], 'passport already in use');
|
|
98
|
+
// }
|
|
81
99
|
});
|
|
82
100
|
}
|
|
83
101
|
/**
|
|
@@ -4,6 +4,9 @@ import * as factory from '../../factory';
|
|
|
4
4
|
* レート制限キーインターフェース
|
|
5
5
|
*/
|
|
6
6
|
export interface IRateLimitKey {
|
|
7
|
+
project: {
|
|
8
|
+
id: string;
|
|
9
|
+
};
|
|
7
10
|
reservedTicket: {
|
|
8
11
|
ticketType: {
|
|
9
12
|
validRateLimit: factory.offer.IValidRateLimit;
|
|
@@ -19,13 +22,15 @@ export interface IRateLimitKey {
|
|
|
19
22
|
*/
|
|
20
23
|
export declare class OfferRateLimitRepo {
|
|
21
24
|
private static readonly KEY_PREFIX_NEW;
|
|
25
|
+
private readonly concurrentLockRepo;
|
|
22
26
|
private readonly redisClient;
|
|
23
27
|
constructor(redisClient: RedisClientType);
|
|
24
28
|
private static createKey;
|
|
25
29
|
/**
|
|
26
30
|
* ロックする
|
|
31
|
+
* discontinue array params(2025-05-26~)
|
|
27
32
|
*/
|
|
28
|
-
lock(
|
|
29
|
-
unlock(
|
|
33
|
+
lock(params: IRateLimitKey): Promise<void>;
|
|
34
|
+
unlock(params: IRateLimitKey): Promise<void>;
|
|
30
35
|
getHolder(ratelimitKey: IRateLimitKey): Promise<string | null>;
|
|
31
36
|
}
|
|
@@ -12,11 +12,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
12
12
|
exports.OfferRateLimitRepo = void 0;
|
|
13
13
|
const moment = require("moment");
|
|
14
14
|
const factory = require("../../factory");
|
|
15
|
+
const concurrentLock_1 = require("../concurrentLock");
|
|
15
16
|
/**
|
|
16
17
|
* オファーレート制限リポジトリ
|
|
17
18
|
*/
|
|
18
19
|
class OfferRateLimitRepo {
|
|
19
20
|
constructor(redisClient) {
|
|
21
|
+
this.concurrentLockRepo = new concurrentLock_1.ConcurrentLockRepo({ redisClient });
|
|
20
22
|
this.redisClient = redisClient;
|
|
21
23
|
}
|
|
22
24
|
static createKey(ratelimitKey) {
|
|
@@ -28,54 +30,53 @@ class OfferRateLimitRepo {
|
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
32
|
* ロックする
|
|
33
|
+
* discontinue array params(2025-05-26~)
|
|
31
34
|
*/
|
|
32
|
-
lock(ratelimitKeys) {
|
|
35
|
+
// public async lock(ratelimitKeys: IRateLimitKey[]): Promise<void> {
|
|
36
|
+
lock(params) {
|
|
33
37
|
return __awaiter(this, void 0, void 0, function* () {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
if (index % 2 === 0 && (r === 1 || r === true)) {
|
|
53
|
-
// tslint:disable-next-line:no-magic-numbers
|
|
54
|
-
lockedFields.push(datasets[index / 2].key);
|
|
55
|
-
}
|
|
56
|
-
});
|
|
57
|
-
const lockedAll = lockedFields.length === ratelimitKeys.length;
|
|
58
|
-
if (!lockedAll) {
|
|
59
|
-
if (lockedFields.length > 0) {
|
|
60
|
-
// 全てロックできなければロックできたものは解除
|
|
61
|
-
multi = this.redisClient.multi();
|
|
62
|
-
lockedFields.forEach((key) => {
|
|
63
|
-
multi.del(key);
|
|
64
|
-
});
|
|
65
|
-
yield multi.exec();
|
|
66
|
-
}
|
|
38
|
+
const key = OfferRateLimitRepo.createKey(params);
|
|
39
|
+
const value = params.reservationNumber;
|
|
40
|
+
const ttl = moment(params.reservationFor.startDate)
|
|
41
|
+
.add(params.reservedTicket.ticketType.validRateLimit.unitInSeconds, 'seconds')
|
|
42
|
+
.diff(moment(), 'seconds');
|
|
43
|
+
const expires = moment()
|
|
44
|
+
.add(ttl, 'seconds')
|
|
45
|
+
.toDate();
|
|
46
|
+
// reimplement using concurrentLockRepo(2025-05-27~)
|
|
47
|
+
try {
|
|
48
|
+
yield this.concurrentLockRepo.lock({
|
|
49
|
+
project: { id: params.project.id, typeOf: factory.organizationType.Project },
|
|
50
|
+
about: { identifier: key, typeOf: 'Thing' },
|
|
51
|
+
expires,
|
|
52
|
+
audience: { identifier: value, typeOf: 'Audience' }
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
67
56
|
throw new factory.errors.RateLimitExceeded('Offer');
|
|
68
57
|
}
|
|
58
|
+
// const multi = this.redisClient.multi();
|
|
59
|
+
// multi.setNX(key, value)
|
|
60
|
+
// .expire(key, ttl);
|
|
61
|
+
// const results = await multi.exec();
|
|
62
|
+
// if (Array.isArray(results) && (results[0] === 1 || (<any>results)[0] === true)) {
|
|
63
|
+
// return;
|
|
64
|
+
// } else {
|
|
65
|
+
// throw new factory.errors.RateLimitExceeded('Offer');
|
|
66
|
+
// }
|
|
69
67
|
});
|
|
70
68
|
}
|
|
71
|
-
|
|
69
|
+
// discontinue array params(2025-05-26~)
|
|
70
|
+
// public async unlock(ratelimitKeys: IRateLimitKey[]): Promise<void> {
|
|
71
|
+
unlock(params) {
|
|
72
72
|
return __awaiter(this, void 0, void 0, function* () {
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
const key = OfferRateLimitRepo.createKey(params);
|
|
74
|
+
// reimplement using concurrentLockRepo(2025-05-27~)
|
|
75
|
+
yield this.concurrentLockRepo.unlock({
|
|
76
|
+
about: { identifier: key, typeOf: 'Thing' },
|
|
77
|
+
audience: { identifier: params.reservationNumber, typeOf: 'Audience' }
|
|
77
78
|
});
|
|
78
|
-
|
|
79
|
+
// await this.redisClient.del(key);
|
|
79
80
|
});
|
|
80
81
|
}
|
|
81
82
|
getHolder(ratelimitKey) {
|
|
@@ -6,6 +6,9 @@ interface IProcessKey {
|
|
|
6
6
|
* 取引ID
|
|
7
7
|
*/
|
|
8
8
|
id: string;
|
|
9
|
+
project: {
|
|
10
|
+
id: string;
|
|
11
|
+
};
|
|
9
12
|
}
|
|
10
13
|
interface IOptions {
|
|
11
14
|
lockExpiresInSeconds: number;
|
|
@@ -14,11 +17,11 @@ interface IOptions {
|
|
|
14
17
|
* 取引プロセスリポジトリ
|
|
15
18
|
*/
|
|
16
19
|
export declare class TransactionProcessRepo {
|
|
20
|
+
private readonly concurrentLockRepo;
|
|
17
21
|
private readonly options;
|
|
18
|
-
private readonly redisClient;
|
|
19
22
|
constructor(redisClient: RedisClientType, options: IOptions);
|
|
20
|
-
static CREATE_REDIS_KEY(params: IProcessKey): string;
|
|
21
|
-
lock(params: IProcessKey): Promise<
|
|
22
|
-
unlock(params: IProcessKey): Promise<void>;
|
|
23
|
+
static CREATE_REDIS_KEY(params: Pick<IProcessKey, 'id' | 'typeOf'>): string;
|
|
24
|
+
lock(params: IProcessKey): Promise<void>;
|
|
25
|
+
unlock(params: Pick<IProcessKey, 'id' | 'typeOf'>): Promise<void>;
|
|
23
26
|
}
|
|
24
27
|
export {};
|
|
@@ -11,44 +11,65 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
|
|
|
11
11
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
12
|
exports.TransactionProcessRepo = void 0;
|
|
13
13
|
const createDebug = require("debug");
|
|
14
|
+
const moment = require("moment");
|
|
14
15
|
const factory = require("../factory");
|
|
16
|
+
const concurrentLock_1 = require("./concurrentLock");
|
|
15
17
|
const debug = createDebug('chevre-domain:repo:transactionProcess');
|
|
16
18
|
const DEFAULT_LOCK_EXPIRES = 120;
|
|
17
19
|
/**
|
|
18
20
|
* 取引プロセスリポジトリ
|
|
19
21
|
*/
|
|
20
22
|
class TransactionProcessRepo {
|
|
23
|
+
// private readonly redisClient: RedisClientType;
|
|
21
24
|
constructor(redisClient, options) {
|
|
22
|
-
this.
|
|
25
|
+
this.concurrentLockRepo = new concurrentLock_1.ConcurrentLockRepo({ redisClient });
|
|
26
|
+
// this.redisClient = redisClient;
|
|
23
27
|
this.options = options;
|
|
24
28
|
}
|
|
25
29
|
static CREATE_REDIS_KEY(params) {
|
|
26
|
-
// 最適化(2024-03-21~)
|
|
27
|
-
// return `chevre-api:${params.project.id}:transactionProcess:${params.typeOf}:${params.id}`;
|
|
28
30
|
return `chvrapi:lockTxn:${params.id}`;
|
|
29
31
|
}
|
|
30
32
|
lock(params) {
|
|
31
33
|
return __awaiter(this, void 0, void 0, function* () {
|
|
32
34
|
const key = TransactionProcessRepo.CREATE_REDIS_KEY(params);
|
|
33
|
-
// const ttl = TRANSACTION_RROCESS_LOCK_EXPIRES;
|
|
34
35
|
const ttl = (typeof this.options.lockExpiresInSeconds === 'number') ? this.options.lockExpiresInSeconds : DEFAULT_LOCK_EXPIRES;
|
|
35
|
-
const
|
|
36
|
-
.
|
|
37
|
-
.
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
36
|
+
const expires = moment()
|
|
37
|
+
.add(ttl, 'seconds')
|
|
38
|
+
.toDate();
|
|
39
|
+
// reimplement using concurrentLockRepo(2025-05-27~)
|
|
40
|
+
try {
|
|
41
|
+
yield this.concurrentLockRepo.lock({
|
|
42
|
+
project: { id: params.project.id, typeOf: factory.organizationType.Project },
|
|
43
|
+
about: { identifier: key, typeOf: 'Thing' },
|
|
44
|
+
expires,
|
|
45
|
+
audience: { identifier: '1', typeOf: 'Audience' }
|
|
46
|
+
});
|
|
47
|
+
debug('locked,', params.id);
|
|
42
48
|
}
|
|
43
|
-
|
|
49
|
+
catch (error) {
|
|
44
50
|
throw new factory.errors.AlreadyInUse(params.typeOf, [], 'Another transaction process in progress');
|
|
45
51
|
}
|
|
52
|
+
// const results = await this.redisClient.multi()
|
|
53
|
+
// .setNX(key, '1')
|
|
54
|
+
// .expire(key, ttl)
|
|
55
|
+
// .exec();
|
|
56
|
+
// debug('locked,', params.id, results);
|
|
57
|
+
// if (Array.isArray(results) && (results[0] === 1 || (<any>results)[0] === true)) {
|
|
58
|
+
// return;
|
|
59
|
+
// } else {
|
|
60
|
+
// throw new factory.errors.AlreadyInUse(params.typeOf, [], 'Another transaction process in progress');
|
|
61
|
+
// }
|
|
46
62
|
});
|
|
47
63
|
}
|
|
48
64
|
unlock(params) {
|
|
49
65
|
return __awaiter(this, void 0, void 0, function* () {
|
|
50
66
|
const key = TransactionProcessRepo.CREATE_REDIS_KEY(params);
|
|
51
|
-
|
|
67
|
+
// reimplement using concurrentLockRepo(2025-05-27~)
|
|
68
|
+
yield this.concurrentLockRepo.unlock({
|
|
69
|
+
about: { identifier: key, typeOf: 'Thing' },
|
|
70
|
+
audience: { identifier: '1', typeOf: 'Audience' }
|
|
71
|
+
});
|
|
72
|
+
// await this.redisClient.del([key]);
|
|
52
73
|
});
|
|
53
74
|
}
|
|
54
75
|
}
|
|
@@ -239,6 +239,7 @@ function isHoldByRateLimit(params) {
|
|
|
239
239
|
const unitInSeconds = (_b = params.offer.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds;
|
|
240
240
|
if (typeof scope === 'string' && typeof unitInSeconds === 'number') {
|
|
241
241
|
const rateLimitKey = {
|
|
242
|
+
project: { id: params.event.project.id },
|
|
242
243
|
reservedTicket: {
|
|
243
244
|
ticketType: {
|
|
244
245
|
validRateLimit: { scope: scope, unitInSeconds: unitInSeconds }
|
|
@@ -170,6 +170,7 @@ function addReservations(params) {
|
|
|
170
170
|
let lockedOfferRateLimitKeys = [];
|
|
171
171
|
try {
|
|
172
172
|
lockedOfferRateLimitKeys = yield processLockOfferRateLimit({
|
|
173
|
+
project: { id: params.event.project.id },
|
|
173
174
|
reservations: objectSubReservations,
|
|
174
175
|
reservationFor,
|
|
175
176
|
reservationNumber
|
|
@@ -179,6 +180,7 @@ function addReservations(params) {
|
|
|
179
180
|
if (lockedOfferRateLimitKeys.length > 0) {
|
|
180
181
|
yield Promise.all(objectSubReservations.map((reservation) => __awaiter(this, void 0, void 0, function* () {
|
|
181
182
|
yield (0, cancelReservation_1.processUnlockOfferRateLimit)({
|
|
183
|
+
project: { id: params.event.project.id },
|
|
182
184
|
reservation: {
|
|
183
185
|
reservationNumber,
|
|
184
186
|
reservedTicket: reservation.reservedTicket
|
|
@@ -469,6 +471,7 @@ function processLockOfferRateLimit(params) {
|
|
|
469
471
|
const unitInSeconds = (_b = reservation.reservedTicket.ticketType.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds;
|
|
470
472
|
if (typeof scope === 'string' && typeof unitInSeconds === 'number') {
|
|
471
473
|
const rateLimitKey = {
|
|
474
|
+
project: { id: params.project.id },
|
|
472
475
|
reservedTicket: {
|
|
473
476
|
ticketType: {
|
|
474
477
|
validRateLimit: {
|
|
@@ -489,7 +492,12 @@ function processLockOfferRateLimit(params) {
|
|
|
489
492
|
rateLimitKeys.push(rateLimitKey);
|
|
490
493
|
}
|
|
491
494
|
});
|
|
492
|
-
|
|
495
|
+
if (rateLimitKeys.length > 0) {
|
|
496
|
+
if (rateLimitKeys.length > 1) {
|
|
497
|
+
throw new factory.errors.Argument('acceptedOffers', `Number of offers with rateLimit must be less than or equal to 1`);
|
|
498
|
+
}
|
|
499
|
+
yield repos.offerRateLimit.lock(rateLimitKeys[0]);
|
|
500
|
+
}
|
|
493
501
|
return rateLimitKeys;
|
|
494
502
|
});
|
|
495
503
|
}
|
|
@@ -2,7 +2,7 @@ import type { IMinimizedIndividualEvent } from '../../../repo/event';
|
|
|
2
2
|
import type { OfferRateLimitRepo } from '../../../repo/rateLimit/offer';
|
|
3
3
|
import * as factory from '../../../factory';
|
|
4
4
|
declare function checkAvailability(params: {
|
|
5
|
-
event: Pick<IMinimizedIndividualEvent<factory.eventType.Event | factory.eventType.ScreeningEvent>, 'startDate'>;
|
|
5
|
+
event: Pick<IMinimizedIndividualEvent<factory.eventType.Event | factory.eventType.ScreeningEvent>, 'startDate' | 'project'>;
|
|
6
6
|
unitPriceOffer: factory.unitPriceOffer.IUnitPriceOffer;
|
|
7
7
|
}): (repos: {
|
|
8
8
|
offerRateLimit: OfferRateLimitRepo;
|
|
@@ -22,6 +22,7 @@ function checkAvailability(params) {
|
|
|
22
22
|
const unitInSeconds = (_b = unitPriceOffer.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds;
|
|
23
23
|
if (typeof scope === 'string' && typeof unitInSeconds === 'number') {
|
|
24
24
|
const rateLimitKey = {
|
|
25
|
+
project: { id: params.event.project.id },
|
|
25
26
|
reservedTicket: {
|
|
26
27
|
ticketType: {
|
|
27
28
|
validRateLimit: {
|
|
@@ -31,6 +31,9 @@ declare function cancelReservation(actionAttributesList: factory.action.cancel.r
|
|
|
31
31
|
assetTransaction: AssetTransactionRepo;
|
|
32
32
|
}) => Promise<void>;
|
|
33
33
|
declare function processUnlockOfferRateLimit(params: {
|
|
34
|
+
project: {
|
|
35
|
+
id: string;
|
|
36
|
+
};
|
|
34
37
|
reservation: {
|
|
35
38
|
reservationNumber: string;
|
|
36
39
|
reservedTicket: factory.reservation.ITicket;
|
|
@@ -104,6 +104,7 @@ function cancelPengindIfNotYet(params) {
|
|
|
104
104
|
: reserveTransactionId
|
|
105
105
|
})(repos);
|
|
106
106
|
yield processUnlockOfferRateLimit({
|
|
107
|
+
project: { id: reserveTransaction.project.id },
|
|
107
108
|
reservation: {
|
|
108
109
|
reservationNumber: reserveTransaction.object.reservationNumber,
|
|
109
110
|
reservedTicket: cancelingSubReservation.reservedTicket
|
|
@@ -204,6 +205,7 @@ function cancelReservation(actionAttributesList) {
|
|
|
204
205
|
: reserveTransaction.id
|
|
205
206
|
})(repos);
|
|
206
207
|
yield processUnlockOfferRateLimit({
|
|
208
|
+
project: { id: reserveTransaction.project.id },
|
|
207
209
|
reservation: {
|
|
208
210
|
reservationNumber: reserveTransaction.object.reservationNumber,
|
|
209
211
|
reservedTicket: cancelingSubReservation.reservedTicket
|
|
@@ -253,6 +255,7 @@ function cancelReservation(actionAttributesList) {
|
|
|
253
255
|
})(repos);
|
|
254
256
|
}
|
|
255
257
|
yield processUnlockOfferRateLimit({
|
|
258
|
+
project: { id: reservation.project.id },
|
|
256
259
|
reservation: {
|
|
257
260
|
reservationNumber: reservation.reservationNumber,
|
|
258
261
|
reservedTicket: reservation.reservedTicket
|
|
@@ -401,6 +404,7 @@ function processUnlockOfferRateLimit(params) {
|
|
|
401
404
|
const unitInSeconds = (_b = reservation.reservedTicket.ticketType.validRateLimit) === null || _b === void 0 ? void 0 : _b.unitInSeconds;
|
|
402
405
|
if (typeof scope === 'string' && typeof unitInSeconds === 'number') {
|
|
403
406
|
const rateLimitKey = {
|
|
407
|
+
project: { id: params.project.id },
|
|
404
408
|
reservedTicket: {
|
|
405
409
|
ticketType: {
|
|
406
410
|
validRateLimit: {
|
|
@@ -420,7 +424,7 @@ function processUnlockOfferRateLimit(params) {
|
|
|
420
424
|
};
|
|
421
425
|
const holder = yield repos.offerRateLimit.getHolder(rateLimitKey);
|
|
422
426
|
if (holder === rateLimitKey.reservationNumber) {
|
|
423
|
-
yield repos.offerRateLimit.unlock(
|
|
427
|
+
yield repos.offerRateLimit.unlock(rateLimitKey);
|
|
424
428
|
}
|
|
425
429
|
}
|
|
426
430
|
});
|
package/package.json
CHANGED