@chevre/domain 23.2.0-alpha.40 → 23.2.0-alpha.42

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.
@@ -36,7 +36,10 @@ async function main() {
36
36
  $unset: {}
37
37
  }
38
38
  ],
39
- { update: true }
39
+ {
40
+ update: true,
41
+ productType: chevre.factory.product.ProductType.EventService
42
+ }
40
43
  );
41
44
  console.log('result:', result);
42
45
  }
@@ -36,7 +36,10 @@ async function main() {
36
36
  $unset: {}
37
37
  }
38
38
  ],
39
- { update: true }
39
+ {
40
+ update: false,
41
+ productType: chevre.factory.product.ProductType.EventService
42
+ }
40
43
  // { update: false }
41
44
  );
42
45
  console.log('result:', result);
@@ -1,7 +1,7 @@
1
1
  // tslint:disable:no-console
2
2
  import * as mongoose from 'mongoose';
3
3
 
4
- import { chevre } from '../../../lib/index';
4
+ import { chevre } from '../../../../lib/index';
5
5
 
6
6
  const PROJECT_ID = String(process.env.PROJECT_ID);
7
7
  const PRICE = 1234;
@@ -49,8 +49,10 @@ async function main() {
49
49
  project: { typeOf: chevre.factory.organizationType.Project, id: PROJECT_ID },
50
50
  typeOf: chevre.factory.offerType.Offer
51
51
  }
52
- ]
53
- // { replace: true }
52
+ ],
53
+ {
54
+ productType: chevre.factory.product.ProductType.EventService
55
+ }
54
56
  );
55
57
  // tslint:disable-next-line:no-null-keyword
56
58
  console.dir(result, { depth: null });
@@ -0,0 +1,14 @@
1
+ // tslint:disable:no-console
2
+ // tslint:disable-next-line:no-require-imports no-var-requires
3
+ const fpe = require('node-fpe-v1');
4
+
5
+ // const orderNumber = 'CIN1-3796505-5174753';
6
+
7
+ const timestamp = '1769837978000';
8
+ const cipher = fpe({ secret: '3:v2-xxxxxx' });
9
+ // .encrypt(orderNumber);
10
+
11
+ const orderNumber = cipher.encrypt(`${timestamp}1`);
12
+ console.log(orderNumber);
13
+ console.log(cipher.decrypt(orderNumber));
14
+ // '1234567'
@@ -0,0 +1,34 @@
1
+ // tslint:disable:no-console
2
+ import * as mongoose from 'mongoose';
3
+ import * as fpe2Module from 'node-fpe-v2';
4
+
5
+ const fpe2 = (fpe2Module as unknown) as typeof fpe2Module.default;
6
+
7
+ import { chevre } from '../../../../lib/index';
8
+
9
+ // tslint:disable-next-line:max-func-body-length
10
+ async function main() {
11
+ await mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
12
+
13
+ const transactionNumberRepo = await chevre.repository.TransactionNumber.createInstance({
14
+ connection: mongoose.connection
15
+ });
16
+
17
+ const result = await transactionNumberRepo.publishByTimestamp({ startDate: new Date() });
18
+ console.log(result);
19
+ console.log('length:', result.transactionNumber.length);
20
+
21
+ const decryptResult = fpe2({ secret: String(result.secret) })
22
+ // tslint:disable-next-line:no-magic-numbers
23
+ .decrypt(result.transactionNumber.slice(2));
24
+ console.log(decryptResult);
25
+ console.log(decryptResult === `${result.timestamp}${result.incrReply.toString()
26
+ // tslint:disable-next-line:no-magic-numbers
27
+ .padStart(2, '0')}`);
28
+ }
29
+
30
+ main()
31
+ .then(() => {
32
+ console.log('success!');
33
+ })
34
+ .catch(console.error);
@@ -0,0 +1,42 @@
1
+ // tslint:disable:object-literal-key-quotes no-console
2
+ import * as mongoose from 'mongoose';
3
+
4
+ import { chevre } from '../../../../lib/index';
5
+
6
+ async function testGeneration() {
7
+ await mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
8
+
9
+ const transactionNumberRepo = await chevre.repository.TransactionNumber.createInstance({
10
+ connection: mongoose.connection
11
+ });
12
+
13
+ const ts = new Date(); // 同一ミリ秒固定
14
+ const results: Record<string, string>[] = [];
15
+
16
+ console.log(`--- 同一ミリ秒(${ts})内での連続生成テスト ---`);
17
+
18
+ // tslint:disable-next-line:no-magic-numbers
19
+ for (let i = 0; i < 100; i += 1) {
20
+ // 同一ミリ秒内でシーケンス(00-99)だけが変わる状況を再現
21
+ const { transactionNumber, timestamp, incrReply } = await transactionNumberRepo.publishByTimestamp({ startDate: ts });
22
+
23
+ // 復号して元のデータに戻るかチェック
24
+ const decoded = await transactionNumberRepo.decrypt(transactionNumber);
25
+ const isSuccess = decoded === `${timestamp}${incrReply.toString()
26
+ // tslint:disable-next-line:no-magic-numbers
27
+ .padStart(2, '0')}`;
28
+
29
+ results.push({
30
+ timestamp: timestamp,
31
+ 'シーケンス': incrReply.toString(),
32
+ '生成された番号 (17桁)': transactionNumber,
33
+ 'ソルト(2文字目)': transactionNumber[1],
34
+ '復号結果': decoded,
35
+ '復号確認': isSuccess ? '✅OK' : '❌NG'
36
+ });
37
+ }
38
+
39
+ console.table(results);
40
+ }
41
+
42
+ testGeneration();
@@ -74,7 +74,12 @@ export declare class AggregateOfferRepo {
74
74
  /**
75
75
  * コードをキーにして冪等置換(2023-12-13~)
76
76
  */
77
- upsertByIdentifier(params: Omit<factory.unitPriceOffer.IUnitPriceOffer, 'id'>[]): Promise<{
77
+ upsertByIdentifier(params: Omit<factory.unitPriceOffer.IUnitPriceOffer, 'id'>[], options: {
78
+ /**
79
+ * プロダクトタイプでfilter(2026-01-30~)
80
+ */
81
+ productType: factory.product.ProductType;
82
+ }): Promise<{
78
83
  bulkWriteResult: BulkWriteResult;
79
84
  modifiedOffers: {
80
85
  id: string;
@@ -648,22 +648,30 @@ class AggregateOfferRepo {
648
648
  /**
649
649
  * コードをキーにして冪等置換(2023-12-13~)
650
650
  */
651
- upsertByIdentifier(params) {
651
+ upsertByIdentifier(params, options) {
652
652
  return __awaiter(this, void 0, void 0, function* () {
653
+ const { productType } = options;
653
654
  const uniqid = yield Promise.resolve().then(() => require('uniqid'));
654
655
  const insertBulkWriteOps = [];
655
656
  // const updateBulkWriteOps: AnyBulkWriteOperation<factory.aggregateOffer.IAggregateOffer>[] = [];
656
657
  const queryFilters = [];
657
658
  if (Array.isArray(params)) {
658
659
  params.forEach((p) => {
660
+ var _a;
661
+ // 指定されたproductTypeを強制する
662
+ if (((_a = p.itemOffered) === null || _a === void 0 ? void 0 : _a.typeOf) !== productType) {
663
+ throw new factory.errors.Argument('itemOffered.typeOf', `not matched with productType: ${productType}`);
664
+ }
659
665
  // リソースのユニークネスを保証するfilter
660
666
  const filter = {
661
667
  'project.id': { $eq: p.project.id },
662
- 'offers.identifier': { $exists: true, $eq: p.identifier }
668
+ 'offers.identifier': { $exists: true, $eq: p.identifier },
669
+ 'offers.itemOffered.typeOf': { $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
663
670
  };
664
671
  queryFilters.push({
665
672
  'project.id': { $eq: p.project.id },
666
- 'offers.identifier': { $exists: true, $eq: p.identifier }
673
+ 'offers.identifier': { $exists: true, $eq: p.identifier },
674
+ 'offers.itemOffered.typeOf': { $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
667
675
  });
668
676
  const newOfferId = uniqid(); // setOnInsert時のみに利用する新ID
669
677
  // サブオファー最適化(2023-12-22~)
@@ -14,7 +14,7 @@ const cdigit = require("cdigit");
14
14
  const moment = require("moment-timezone");
15
15
  // import { RedisClientType } from 'redis';
16
16
  // tslint:disable-next-line:no-require-imports no-var-requires
17
- const fpe = require('node-fpe');
17
+ const fpe = require('node-fpe-v1');
18
18
  // import { createSchema as createSettingSchema, modelName as settingModelName } from './mongoose/schemas/setting';
19
19
  const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
20
20
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
@@ -110,6 +110,16 @@ export interface IJWTSetting {
110
110
  */
111
111
  algorithm: Algorithm;
112
112
  }
113
+ export interface ITransactionNumberSetting {
114
+ /**
115
+ * fpe暗号鍵
116
+ */
117
+ fpeSecret: string;
118
+ /**
119
+ * "1"
120
+ */
121
+ version: string;
122
+ }
113
123
  export interface ISetting {
114
124
  defaultSenderEmail?: string;
115
125
  jwt?: IJWTSetting;
@@ -127,6 +137,7 @@ export interface ISetting {
127
137
  };
128
138
  quota?: IQuotaSettings;
129
139
  storage?: IStorageSettings;
140
+ transactionNumber?: ITransactionNumberSetting;
130
141
  /**
131
142
  * 通知設定
132
143
  */
@@ -19,6 +19,7 @@ const schemaDefinition = {
19
19
  onTransactionStatusChanged: mongoose_1.SchemaTypes.Mixed,
20
20
  quota: mongoose_1.SchemaTypes.Mixed,
21
21
  storage: mongoose_1.SchemaTypes.Mixed,
22
+ transactionNumber: mongoose_1.SchemaTypes.Mixed,
22
23
  triggerWebhook: mongoose_1.SchemaTypes.Mixed,
23
24
  useInformResourceTypes: [String],
24
25
  userPoolIdOld: String,
@@ -40,6 +40,10 @@ export declare class OfferCatalogRepo {
40
40
  * support only update(2026-01-30~)
41
41
  */
42
42
  update: boolean;
43
+ /**
44
+ * プロダクトタイプでfilter(2026-01-30~)
45
+ */
46
+ productType: factory.product.ProductType;
43
47
  }): Promise<{
44
48
  bulkWriteResult: BulkWriteResult;
45
49
  modifiedCatalogs: {
@@ -197,7 +197,7 @@ class OfferCatalogRepo {
197
197
  */
198
198
  upsertManyByIdentifier(params, options) {
199
199
  return __awaiter(this, void 0, void 0, function* () {
200
- const { update } = options;
200
+ const { productType, update } = options;
201
201
  const uniqid = yield Promise.resolve().then(() => require('uniqid'));
202
202
  const bulkWriteOps = [];
203
203
  const queryFilters = [];
@@ -207,14 +207,20 @@ class OfferCatalogRepo {
207
207
  if (typeof identifier !== 'string' || identifier.length === 0) {
208
208
  throw new factory.errors.ArgumentNull('identifier');
209
209
  }
210
+ // 指定されたproductTypeを強制する
211
+ if (setFields.itemOffered.typeOf !== productType) {
212
+ throw new factory.errors.Argument('itemOffered.typeOf', `not matched with productType: ${productType}`);
213
+ }
210
214
  // リソースのユニークネスを保証するfilter
211
215
  const filter = {
212
216
  'project.id': { $eq: project.id },
213
- identifier: { $eq: identifier }
217
+ identifier: { $eq: identifier },
218
+ 'itemOffered.typeOf': { $exists: true, $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
214
219
  };
215
220
  queryFilters.push({
216
221
  'project.id': { $eq: project.id },
217
- identifier: { $eq: identifier }
222
+ identifier: { $eq: identifier },
223
+ 'itemOffered.typeOf': { $exists: true, $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
218
224
  });
219
225
  if (update === true) {
220
226
  const updateOne = {
@@ -37,6 +37,10 @@ export declare class OfferCatalogItemRepo {
37
37
  * support only update(2026-01-30~)
38
38
  */
39
39
  update: boolean;
40
+ /**
41
+ * プロダクトタイプでfilter(2026-01-30~)
42
+ */
43
+ productType: factory.product.ProductType;
40
44
  }): Promise<{
41
45
  bulkWriteResult: BulkWriteResult;
42
46
  modifiedCatalogs: {
@@ -161,7 +161,7 @@ class OfferCatalogItemRepo {
161
161
  */
162
162
  upsertManyByIdentifier(params, options) {
163
163
  return __awaiter(this, void 0, void 0, function* () {
164
- const { update } = options;
164
+ const { productType, update } = options;
165
165
  const bulkWriteOps = [];
166
166
  const queryFilters = [];
167
167
  if (Array.isArray(params)) {
@@ -170,14 +170,20 @@ class OfferCatalogItemRepo {
170
170
  if (typeof identifier !== 'string' || identifier.length === 0) {
171
171
  throw new factory.errors.ArgumentNull('identifier');
172
172
  }
173
+ // 指定されたproductTypeを強制する
174
+ if (setFields.itemOffered.typeOf !== productType) {
175
+ throw new factory.errors.Argument('itemOffered.typeOf', `not matched with productType: ${productType}`);
176
+ }
173
177
  // リソースのユニークネスを保証するfilter
174
178
  const filter = {
175
179
  'project.id': { $eq: project.id },
176
- identifier: { $eq: identifier }
180
+ identifier: { $eq: identifier },
181
+ 'itemOffered.typeOf': { $exists: true, $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
177
182
  };
178
183
  queryFilters.push({
179
184
  'project.id': { $eq: project.id },
180
- identifier: { $eq: identifier }
185
+ identifier: { $eq: identifier },
186
+ 'itemOffered.typeOf': { $exists: true, $eq: productType } // プロダクトタイプを変更させないためにフィルター追加(2026-01-30~)
181
187
  });
182
188
  if (update === true) {
183
189
  const updateOne = {
@@ -14,7 +14,7 @@ const cdigit = require("cdigit");
14
14
  const moment = require("moment-timezone");
15
15
  // import { RedisClientType } from 'redis';
16
16
  // tslint:disable-next-line:no-require-imports no-var-requires
17
- const fpe = require('node-fpe');
17
+ const fpe = require('node-fpe-v1');
18
18
  // import { createSchema as createSettingSchema, modelName as settingModelName } from './mongoose/schemas/setting';
19
19
  const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
20
20
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
@@ -14,7 +14,7 @@ const cdigit = require("cdigit");
14
14
  const moment = require("moment-timezone");
15
15
  // import { RedisClientType } from 'redis';
16
16
  // tslint:disable-next-line:no-require-imports no-var-requires
17
- const fpe = require('node-fpe');
17
+ const fpe = require('node-fpe-v1');
18
18
  // import { createSchema as createSettingSchema, modelName as settingModelName } from './mongoose/schemas/setting';
19
19
  const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
20
20
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
@@ -1,12 +1,16 @@
1
1
  import type { Connection } from 'mongoose';
2
2
  interface IPublishResult {
3
3
  transactionNumber: string;
4
+ timestamp: string;
5
+ incrReply: number;
6
+ secret?: string;
4
7
  }
5
8
  /**
6
9
  * 取引番号リポジトリ
7
10
  */
8
11
  export declare class TransactionNumberRepo {
9
12
  private readonly counterRepo;
13
+ private readonly settingModel;
10
14
  constructor(params: {
11
15
  connection: Connection;
12
16
  });
@@ -16,5 +20,7 @@ export declare class TransactionNumberRepo {
16
20
  publishByTimestamp(params: {
17
21
  startDate: Date;
18
22
  }): Promise<IPublishResult>;
23
+ decrypt(id: string): Promise<string>;
24
+ private findSetting;
19
25
  }
20
26
  export {};
@@ -11,11 +11,15 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
11
11
  Object.defineProperty(exports, "__esModule", { value: true });
12
12
  exports.TransactionNumberRepo = void 0;
13
13
  const cdigit = require("cdigit");
14
+ const crypto_1 = require("crypto");
14
15
  const moment = require("moment-timezone");
15
- // import { RedisClientType } from 'redis';
16
+ const fpe2Module = require("node-fpe-v2");
17
+ // 型定義が新しいtypescriptに適合しないので、強制的に型変更
18
+ const fpe2 = fpe2Module;
16
19
  // tslint:disable-next-line:no-require-imports no-var-requires
17
- const fpe = require('node-fpe');
18
- // import { createSchema as createSettingSchema, modelName as settingModelName } from './mongoose/schemas/setting';
20
+ const fpe1 = require('node-fpe-v1');
21
+ const factory = require("../factory");
22
+ const setting_1 = require("./mongoose/schemas/setting");
19
23
  const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
20
24
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
21
25
  /**
@@ -23,25 +27,15 @@ const transactionNumberCounter_1 = require("./transactionNumberCounter");
23
27
  */
24
28
  class TransactionNumberRepo {
25
29
  constructor(params) {
26
- // const { connection } = params;
27
- // this.settingModel = connection.model(settingModelName, createSettingSchema());
28
30
  this.counterRepo = new transactionNumberCounter_1.TransactionNumberCounterRepo(params);
31
+ this.settingModel = params.connection.model(setting_1.modelName, (0, setting_1.createSchema)());
29
32
  }
30
- // private static createKey(params: {
31
- // startDate: Date;
32
- // timestamp: string;
33
- // }): string {
34
- // return util.format(
35
- // '%s:%s',
36
- // TransactionNumberRepo.REDIS_KEY_PREFIX,
37
- // params.timestamp
38
- // );
39
- // }
40
33
  /**
41
34
  * タイムスタンプから発行する
42
35
  */
43
36
  publishByTimestamp(params) {
44
37
  return __awaiter(this, void 0, void 0, function* () {
38
+ const { fpeSecret, version } = yield this.findSetting();
45
39
  const timestamp = moment(params.startDate)
46
40
  .valueOf()
47
41
  .toString();
@@ -57,13 +51,67 @@ class TransactionNumberRepo {
57
51
  includedInDataCatalog: { identifier: transactionNumber_1.DataCatalogIdentifier.transactionNumber },
58
52
  expires: dataFeedExpires
59
53
  });
60
- let transactionNumber = `${timestamp}${incrReply}`;
61
- // checkdigit
62
- const cd = cdigit.luhn.compute(transactionNumber);
63
- transactionNumber = fpe({ password: cd })
64
- .encrypt(transactionNumber);
65
- transactionNumber = `${cd}${transactionNumber}`;
66
- return { transactionNumber };
54
+ let transactionNumber;
55
+ let secret;
56
+ if (typeof fpeSecret === 'string' && typeof version === 'string') {
57
+ // tslint:disable-next-line:no-magic-numbers
58
+ const saltDigit = (0, crypto_1.randomInt)(0, 10);
59
+ const saltStr = saltDigit.toString();
60
+ secret = `${fpeSecret}${saltStr}`;
61
+ const cipher = fpe2({ secret });
62
+ // incrReplyが 0〜99 なら "00"〜"99" になり(15桁)
63
+ // incrReplyが 100〜999 なら "100"〜"999" になる(16桁)
64
+ const rawBody = `${timestamp}${incrReply.toString()
65
+ // tslint:disable-next-line:no-magic-numbers
66
+ .padStart(2, '0')}`;
67
+ const encryptedBody = cipher.encrypt(rawBody);
68
+ // ソルトを「あえて」2文字目に含める
69
+ transactionNumber = `${version}${saltStr}${encryptedBody}`;
70
+ }
71
+ else {
72
+ transactionNumber = `${timestamp}${incrReply}`;
73
+ // checkdigit
74
+ const cd = cdigit.luhn.compute(transactionNumber);
75
+ transactionNumber = fpe1({ password: cd })
76
+ .encrypt(transactionNumber);
77
+ transactionNumber = `${cd}${transactionNumber}`;
78
+ }
79
+ return { transactionNumber, timestamp, incrReply, secret };
80
+ });
81
+ }
82
+ decrypt(id) {
83
+ return __awaiter(this, void 0, void 0, function* () {
84
+ const { fpeSecret } = yield this.findSetting();
85
+ const saltStr = id[1]; // 2文字目からソルト(鍵のヒント)を回収
86
+ // tslint:disable-next-line:no-magic-numbers
87
+ const encryptedBody = id.slice(2);
88
+ const cipher = fpe2({
89
+ secret: `${fpeSecret}${saltStr}`
90
+ });
91
+ return cipher.decrypt(encryptedBody);
92
+ });
93
+ }
94
+ findSetting() {
95
+ return __awaiter(this, void 0, void 0, function* () {
96
+ const filterQuery = { 'project.id': { $eq: '*' } };
97
+ const projection = {
98
+ _id: 0,
99
+ transactionNumber: 1
100
+ };
101
+ const setting = yield this.settingModel.findOne(filterQuery, projection)
102
+ .lean()
103
+ .exec();
104
+ if (setting === null || setting.transactionNumber === undefined) {
105
+ return {};
106
+ }
107
+ const { fpeSecret, version } = setting.transactionNumber;
108
+ if (typeof fpeSecret !== 'string' || fpeSecret === '') {
109
+ throw new factory.errors.NotFound('setting.transactionNumber.secret');
110
+ }
111
+ if (typeof version !== 'string' || version === '') {
112
+ throw new factory.errors.NotFound('setting.transactionNumber.version');
113
+ }
114
+ return { fpeSecret, version };
67
115
  });
68
116
  }
69
117
  }
package/package.json CHANGED
@@ -25,7 +25,8 @@
25
25
  "lodash.difference": "^4.5.0",
26
26
  "moment": "^2.29.1",
27
27
  "moment-timezone": "^0.5.33",
28
- "node-fpe": "1.0.0",
28
+ "node-fpe-v1": "npm:node-fpe@1.0.0",
29
+ "node-fpe-v2": "npm:node-fpe@2.0.4",
29
30
  "pug": "^2.0.4",
30
31
  "uniqid": "5.4.0",
31
32
  "uuid": "^3.4.0"
@@ -116,5 +117,5 @@
116
117
  "postversion": "git push origin --tags",
117
118
  "prepublishOnly": "npm run clean && npm run build && npm test && npm run doc"
118
119
  },
119
- "version": "23.2.0-alpha.40"
120
+ "version": "23.2.0-alpha.42"
120
121
  }