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

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.
@@ -0,0 +1,48 @@
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 orderNumberRepo = await chevre.repository.OrderNumber.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 orderNumber = await orderNumberRepo.publishByTimestamp({
22
+ project: { alternateName: 'CIN' },
23
+ orderDate: ts
24
+
25
+ });
26
+
27
+ // 復号して元のデータに戻るかチェック
28
+ const decoded = await orderNumberRepo.decrypt(orderNumber);
29
+ const isSuccess = decoded === `${ts.valueOf()}${(i + 1).toString()
30
+ // tslint:disable-next-line:no-magic-numbers
31
+ .padStart(2, '0')}`;
32
+
33
+ results.push({
34
+ timestamp: ts.valueOf()
35
+ .toString(),
36
+ 'シーケンス': (i + 1).toString(),
37
+ '生成された番号 (17桁)': orderNumber,
38
+ // tslint:disable-next-line:no-magic-numbers
39
+ 'ソルト(2文字目)': orderNumber[2],
40
+ '復号結果': decoded,
41
+ '復号確認': isSuccess ? '✅OK' : '❌NG'
42
+ });
43
+ }
44
+
45
+ console.table(results);
46
+ }
47
+
48
+ testGeneration();
@@ -11,7 +11,7 @@ mongoose.Model.on('index', (...args) => {
11
11
  async function main() {
12
12
  await mongoose.connect(<string>process.env.MONGOLAB_URI, { autoIndex: false });
13
13
 
14
- await chevre.repository.EventSeries.createInstance(mongoose.connection);
14
+ await chevre.repository.OfferCatalogItem.createInstance(mongoose.connection);
15
15
  console.log('success!');
16
16
  }
17
17
 
@@ -1,8 +1,5 @@
1
1
  // tslint:disable:no-console
2
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
3
 
7
4
  import { chevre } from '../../../../lib/index';
8
5
 
@@ -17,14 +14,6 @@ async function main() {
17
14
  const result = await transactionNumberRepo.publishByTimestamp({ startDate: new Date() });
18
15
  console.log(result);
19
16
  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
17
  }
29
18
 
30
19
  main()
@@ -0,0 +1,19 @@
1
+ /**
2
+ * 取引番号ファクトリー
3
+ */
4
+ export declare class TransactionNumberFactory {
5
+ private readonly fpeSecret;
6
+ private readonly version;
7
+ constructor(params: {
8
+ fpeSecret: string;
9
+ version: string;
10
+ });
11
+ /**
12
+ * 生成: SEQが100を超えると自動で18桁に拡張
13
+ */
14
+ generate(timestamp: string, seq: number): string;
15
+ /**
16
+ * 復号: 17桁でも18桁でも、2文字目以降をまるごとデコード
17
+ */
18
+ decrypt(transactionNumber: string): string;
19
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.TransactionNumberFactory = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const fpe2Module = require("node-fpe-v2");
6
+ const factory = require("../factory");
7
+ // 型定義が新しいtypescriptに適合しないので、強制的に型変更
8
+ const fpe2 = fpe2Module;
9
+ const TRANSACTION_NUMBER_MIN_LENGTH = 17;
10
+ const SEQUENCE_MAX_LENGTH = 2;
11
+ /**
12
+ * 取引番号ファクトリー
13
+ */
14
+ class TransactionNumberFactory {
15
+ constructor(params) {
16
+ const { fpeSecret, version } = params;
17
+ this.fpeSecret = fpeSecret;
18
+ this.version = version;
19
+ }
20
+ /**
21
+ * 生成: SEQが100を超えると自動で18桁に拡張
22
+ */
23
+ generate(timestamp, seq) {
24
+ // tslint:disable-next-line:no-magic-numbers
25
+ const saltDigit = (0, crypto_1.randomInt)(0, 10);
26
+ const saltStr = saltDigit.toString();
27
+ const cipher = fpe2({ secret: `${this.fpeSecret}${saltStr}` });
28
+ // incrReplyが 0〜99 なら "00"〜"99" になり(15桁)
29
+ // incrReplyが 100〜999 なら "100"〜"999" になる(16桁)
30
+ const zeroPaddingSequence = seq.toString()
31
+ .padStart(SEQUENCE_MAX_LENGTH, '0');
32
+ const rawBody = `${timestamp}${zeroPaddingSequence}`;
33
+ const encryptedBody = cipher.encrypt(rawBody);
34
+ // ソルトを「あえて」2文字目に含める
35
+ const transactionNumber = `${this.version}${saltStr}${encryptedBody}`;
36
+ if (transactionNumber.length < TRANSACTION_NUMBER_MIN_LENGTH) {
37
+ throw new factory.errors.Internal(`transactionNumber length should be >= ${TRANSACTION_NUMBER_MIN_LENGTH}`);
38
+ }
39
+ return transactionNumber;
40
+ }
41
+ /**
42
+ * 復号: 17桁でも18桁でも、2文字目以降をまるごとデコード
43
+ */
44
+ decrypt(transactionNumber) {
45
+ const saltStr = transactionNumber[1]; // 2文字目からソルト(鍵のヒント)を回収
46
+ // tslint:disable-next-line:no-magic-numbers
47
+ const encryptedBody = transactionNumber.slice(2);
48
+ const cipher = fpe2({
49
+ secret: `${this.fpeSecret}${saltStr}`
50
+ });
51
+ return cipher.decrypt(encryptedBody);
52
+ }
53
+ }
54
+ exports.TransactionNumberFactory = TransactionNumberFactory;
@@ -8,7 +8,7 @@ const settings_1 = require("../../../settings");
8
8
  const modelName = 'OfferCatalog';
9
9
  exports.modelName = modelName;
10
10
  const schemaDefinition = {
11
- project: mongoose_1.SchemaTypes.Mixed,
11
+ project: { type: mongoose_1.SchemaTypes.Mixed, required: true },
12
12
  _id: String,
13
13
  identifier: {
14
14
  type: String,
@@ -22,7 +22,7 @@ const schemaDefinition = {
22
22
  required: true
23
23
  },
24
24
  itemListElement: [mongoose_1.SchemaTypes.Mixed],
25
- itemOffered: mongoose_1.SchemaTypes.Mixed,
25
+ itemOffered: { type: mongoose_1.SchemaTypes.Mixed, required: true },
26
26
  additionalProperty: [mongoose_1.SchemaTypes.Mixed],
27
27
  dateSynced: Date // 追加(2023-09-13~)
28
28
  // createdAt: SchemaTypes.Mixed,
@@ -60,12 +60,6 @@ const indexes = [
60
60
  name: 'searchByIdentifier'
61
61
  }
62
62
  ],
63
- [
64
- { 'project.id': 1, identifier: 1 },
65
- {
66
- name: 'searchByProjectId-v20220721'
67
- }
68
- ],
69
63
  [
70
64
  { 'itemListElement.typeOf': 1, identifier: 1 },
71
65
  {
@@ -8,7 +8,7 @@ const settings_1 = require("../../../settings");
8
8
  const modelName = 'OfferCatalogItem';
9
9
  exports.modelName = modelName;
10
10
  const schemaDefinition = {
11
- project: mongoose_1.SchemaTypes.Mixed,
11
+ project: { type: mongoose_1.SchemaTypes.Mixed, required: true },
12
12
  identifier: {
13
13
  type: String,
14
14
  required: true
@@ -21,7 +21,7 @@ const schemaDefinition = {
21
21
  required: true
22
22
  },
23
23
  itemListElement: [mongoose_1.SchemaTypes.Mixed],
24
- itemOffered: mongoose_1.SchemaTypes.Mixed,
24
+ itemOffered: { type: mongoose_1.SchemaTypes.Mixed, required: true },
25
25
  additionalProperty: [mongoose_1.SchemaTypes.Mixed],
26
26
  dateSynced: Date,
27
27
  relatedOffer: mongoose_1.SchemaTypes.Mixed
@@ -61,6 +61,32 @@ const indexes = [
61
61
  unique: true,
62
62
  name: 'uniqueIdentifier'
63
63
  }
64
+ ],
65
+ [
66
+ { identifier: 1 },
67
+ { name: 'identifier' }
68
+ ],
69
+ [
70
+ { 'itemOffered.typeOf': 1, identifier: 1 },
71
+ { name: 'itemOfferedTypeOf' }
72
+ ],
73
+ [
74
+ { 'itemListElement.typeOf': 1, identifier: 1 },
75
+ {
76
+ name: 'itemListElementTypeOf',
77
+ partialFilterExpression: {
78
+ 'itemListElement.typeOf': { $exists: true }
79
+ }
80
+ }
81
+ ],
82
+ [
83
+ { 'itemListElement.id': 1, identifier: 1 },
84
+ {
85
+ name: 'itemListElementId',
86
+ partialFilterExpression: {
87
+ 'itemListElement.id': { $exists: true }
88
+ }
89
+ }
64
90
  ]
65
91
  ];
66
92
  exports.indexes = indexes;
@@ -110,6 +110,16 @@ export interface IJWTSetting {
110
110
  */
111
111
  algorithm: Algorithm;
112
112
  }
113
+ export interface IOrderNumberSetting {
114
+ /**
115
+ * fpe暗号鍵
116
+ */
117
+ fpeSecret: string;
118
+ /**
119
+ * "1"
120
+ */
121
+ version: string;
122
+ }
113
123
  export interface ITransactionNumberSetting {
114
124
  /**
115
125
  * fpe暗号鍵
@@ -131,6 +141,7 @@ export interface ISetting {
131
141
  informResource?: factory.project.IInformParams[];
132
142
  };
133
143
  onTransactionStatusChanged?: IOnTransactionStatusChanged;
144
+ orderNumber?: IOrderNumberSetting;
134
145
  project: {
135
146
  id: string;
136
147
  typeOf: factory.organizationType.Project;
@@ -17,6 +17,7 @@ const schemaDefinition = {
17
17
  onTaskStatusChanged: mongoose_1.SchemaTypes.Mixed,
18
18
  onResourceUpdated: mongoose_1.SchemaTypes.Mixed,
19
19
  onTransactionStatusChanged: mongoose_1.SchemaTypes.Mixed,
20
+ orderNumber: mongoose_1.SchemaTypes.Mixed,
20
21
  quota: mongoose_1.SchemaTypes.Mixed,
21
22
  storage: mongoose_1.SchemaTypes.Mixed,
22
23
  transactionNumber: mongoose_1.SchemaTypes.Mixed,
@@ -4,6 +4,7 @@ import type { Connection } from 'mongoose';
4
4
  */
5
5
  export declare class OrderNumberRepo {
6
6
  private readonly counterRepo;
7
+ private readonly settingModel;
7
8
  constructor(params: {
8
9
  connection: Connection;
9
10
  });
@@ -19,4 +20,6 @@ export declare class OrderNumberRepo {
19
20
  */
20
21
  orderDate: Date;
21
22
  }): Promise<string>;
23
+ decrypt(id: string): Promise<string>;
24
+ private findSetting;
22
25
  }
@@ -16,7 +16,10 @@ const moment = require("moment-timezone");
16
16
  // tslint:disable-next-line:no-require-imports no-var-requires
17
17
  const fpe = require('node-fpe-v1');
18
18
  // import { createSchema as createSettingSchema, modelName as settingModelName } from './mongoose/schemas/setting';
19
- const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
19
+ const factory = require("../factory");
20
+ const transactionNumber_1 = require("../factory/transactionNumber");
21
+ const setting_1 = require("./mongoose/schemas/setting");
22
+ const transactionNumber_2 = require("./mongoose/schemas/transactionNumber");
20
23
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
21
24
  const ORDER_NUMBER_SEPARATOR = '-';
22
25
  /**
@@ -27,6 +30,7 @@ class OrderNumberRepo {
27
30
  // const { connection } = params;
28
31
  // this.settingModel = connection.model(settingModelName, createSettingSchema());
29
32
  this.counterRepo = new transactionNumberCounter_1.TransactionNumberCounterRepo(params);
33
+ this.settingModel = params.connection.model(setting_1.modelName, (0, setting_1.createSchema)());
30
34
  }
31
35
  // private static createKey(params: {
32
36
  // orderDate: Date;
@@ -45,6 +49,7 @@ class OrderNumberRepo {
45
49
  */
46
50
  publishByTimestamp(params) {
47
51
  return __awaiter(this, void 0, void 0, function* () {
52
+ const { fpeSecret, version } = yield this.findSetting();
48
53
  const timestamp = moment(params.orderDate)
49
54
  .valueOf()
50
55
  .toString();
@@ -59,25 +64,79 @@ class OrderNumberRepo {
59
64
  .toDate();
60
65
  incrReply = yield this.counterRepo.incrementByMongo({
61
66
  identifier: dataFeedIdentifier,
62
- includedInDataCatalog: { identifier: transactionNumber_1.DataCatalogIdentifier.orderNumber },
67
+ includedInDataCatalog: { identifier: transactionNumber_2.DataCatalogIdentifier.orderNumber },
63
68
  expires: dataFeedExpires
64
69
  });
65
- let orderNumber = `${timestamp}${incrReply}`;
66
- // checkdigit
67
- const cd = cdigit.luhn.compute(orderNumber);
68
- orderNumber = fpe({ password: cd })
69
- .encrypt(orderNumber);
70
- orderNumber = `${projectPrefix}${cd}${orderNumber}`;
71
- orderNumber = `${[
72
- // tslint:disable-next-line:no-magic-numbers
73
- orderNumber.slice(0, 4),
74
- // tslint:disable-next-line:no-magic-numbers
75
- orderNumber.slice(4, 11),
76
- // tslint:disable-next-line:no-magic-numbers
77
- orderNumber.slice(11)
78
- ].join(ORDER_NUMBER_SEPARATOR)}`;
70
+ let orderNumber;
71
+ if (typeof fpeSecret === 'string' && typeof version === 'string') {
72
+ const transactionFactory = new transactionNumber_1.TransactionNumberFactory({ fpeSecret, version });
73
+ orderNumber = transactionFactory.generate(timestamp, incrReply);
74
+ orderNumber = `${projectPrefix.at(0)}${orderNumber}`;
75
+ orderNumber = `${[
76
+ // tslint:disable-next-line:no-magic-numbers
77
+ orderNumber.slice(0, 4),
78
+ // tslint:disable-next-line:no-magic-numbers
79
+ orderNumber.slice(4, 11),
80
+ // tslint:disable-next-line:no-magic-numbers
81
+ orderNumber.slice(11)
82
+ ].join(ORDER_NUMBER_SEPARATOR)}`;
83
+ }
84
+ else {
85
+ orderNumber = `${timestamp}${incrReply}`;
86
+ // checkdigit
87
+ const cd = cdigit.luhn.compute(orderNumber);
88
+ orderNumber = fpe({ password: cd })
89
+ .encrypt(orderNumber);
90
+ orderNumber = `${projectPrefix}${cd}${orderNumber}`;
91
+ orderNumber = `${[
92
+ // tslint:disable-next-line:no-magic-numbers
93
+ orderNumber.slice(0, 4),
94
+ // tslint:disable-next-line:no-magic-numbers
95
+ orderNumber.slice(4, 11),
96
+ // tslint:disable-next-line:no-magic-numbers
97
+ orderNumber.slice(11)
98
+ ].join(ORDER_NUMBER_SEPARATOR)}`;
99
+ }
79
100
  return orderNumber;
80
101
  });
81
102
  }
103
+ decrypt(id) {
104
+ return __awaiter(this, void 0, void 0, function* () {
105
+ const { fpeSecret, version } = yield this.findSetting();
106
+ if (typeof fpeSecret === 'string' && typeof version === 'string') {
107
+ const transactionFactory = new transactionNumber_1.TransactionNumberFactory({ fpeSecret, version });
108
+ const originalOrderNumber = id.replace(/-/g, '')
109
+ // tslint:disable-next-line:no-magic-numbers
110
+ .slice(1);
111
+ return transactionFactory.decrypt(originalOrderNumber);
112
+ }
113
+ else {
114
+ throw new factory.errors.NotImplemented('no version not implemented');
115
+ }
116
+ });
117
+ }
118
+ findSetting() {
119
+ return __awaiter(this, void 0, void 0, function* () {
120
+ const filterQuery = { 'project.id': { $eq: '*' } };
121
+ const projection = {
122
+ _id: 0,
123
+ orderNumber: 1
124
+ };
125
+ const setting = yield this.settingModel.findOne(filterQuery, projection)
126
+ .lean()
127
+ .exec();
128
+ if (setting === null || setting.orderNumber === undefined) {
129
+ return {};
130
+ }
131
+ const { fpeSecret, version } = setting.orderNumber;
132
+ if (typeof fpeSecret !== 'string' || fpeSecret === '') {
133
+ throw new factory.errors.NotFound('setting.orderNumber.secret');
134
+ }
135
+ if (typeof version !== 'string' || version === '') {
136
+ throw new factory.errors.NotFound('setting.orderNumber.version');
137
+ }
138
+ return { fpeSecret, version };
139
+ });
140
+ }
82
141
  }
83
142
  exports.OrderNumberRepo = OrderNumberRepo;
@@ -3,7 +3,6 @@ interface IPublishResult {
3
3
  transactionNumber: string;
4
4
  timestamp: string;
5
5
  incrReply: number;
6
- secret?: string;
7
6
  }
8
7
  /**
9
8
  * 取引番号リポジトリ
@@ -11,16 +11,13 @@ 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");
15
14
  const moment = require("moment-timezone");
16
- const fpe2Module = require("node-fpe-v2");
17
- // 型定義が新しいtypescriptに適合しないので、強制的に型変更
18
- const fpe2 = fpe2Module;
19
15
  // tslint:disable-next-line:no-require-imports no-var-requires
20
16
  const fpe1 = require('node-fpe-v1');
21
17
  const factory = require("../factory");
18
+ const transactionNumber_1 = require("../factory/transactionNumber");
22
19
  const setting_1 = require("./mongoose/schemas/setting");
23
- const transactionNumber_1 = require("./mongoose/schemas/transactionNumber");
20
+ const transactionNumber_2 = require("./mongoose/schemas/transactionNumber");
24
21
  const transactionNumberCounter_1 = require("./transactionNumberCounter");
25
22
  /**
26
23
  * 取引番号リポジトリ
@@ -48,25 +45,27 @@ class TransactionNumberRepo {
48
45
  .toDate();
49
46
  incrReply = yield this.counterRepo.incrementByMongo({
50
47
  identifier: dataFeedIdentifier,
51
- includedInDataCatalog: { identifier: transactionNumber_1.DataCatalogIdentifier.transactionNumber },
48
+ includedInDataCatalog: { identifier: transactionNumber_2.DataCatalogIdentifier.transactionNumber },
52
49
  expires: dataFeedExpires
53
50
  });
54
51
  let transactionNumber;
55
- let secret;
52
+ // let secret: string | undefined;
56
53
  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}`;
54
+ const transactionFactory = new transactionNumber_1.TransactionNumberFactory({ fpeSecret, version });
55
+ transactionNumber = transactionFactory.generate(timestamp, incrReply);
56
+ // // tslint:disable-next-line:no-magic-numbers
57
+ // const saltDigit = randomInt(0, 10);
58
+ // const saltStr = saltDigit.toString();
59
+ // secret = `${fpeSecret}${saltStr}`;
60
+ // const cipher = fpe2({ secret });
61
+ // // incrReplyが 0〜99 なら "00"〜"99" になり(15桁)
62
+ // // incrReplyが 100〜999 なら "100"〜"999" になる(16桁)
63
+ // const rawBody = `${timestamp}${incrReply.toString()
64
+ // // tslint:disable-next-line:no-magic-numbers
65
+ // .padStart(2, '0')}`;
66
+ // const encryptedBody = cipher.encrypt(rawBody);
67
+ // // ソルトを「あえて」2文字目に含める
68
+ // transactionNumber = `${version}${saltStr}${encryptedBody}`;
70
69
  }
71
70
  else {
72
71
  transactionNumber = `${timestamp}${incrReply}`;
@@ -76,19 +75,19 @@ class TransactionNumberRepo {
76
75
  .encrypt(transactionNumber);
77
76
  transactionNumber = `${cd}${transactionNumber}`;
78
77
  }
79
- return { transactionNumber, timestamp, incrReply, secret };
78
+ return { transactionNumber, timestamp, incrReply };
80
79
  });
81
80
  }
82
81
  decrypt(id) {
83
82
  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);
83
+ const { fpeSecret, version } = yield this.findSetting();
84
+ if (typeof fpeSecret === 'string' && typeof version === 'string') {
85
+ const transactionFactory = new transactionNumber_1.TransactionNumberFactory({ fpeSecret, version });
86
+ return transactionFactory.decrypt(id);
87
+ }
88
+ else {
89
+ throw new factory.errors.NotImplemented('no version not implemented');
90
+ }
92
91
  });
93
92
  }
94
93
  findSetting() {
package/package.json CHANGED
@@ -117,5 +117,5 @@
117
117
  "postversion": "git push origin --tags",
118
118
  "prepublishOnly": "npm run clean && npm run build && npm test && npm run doc"
119
119
  },
120
- "version": "23.2.0-alpha.42"
120
+ "version": "23.2.0-alpha.43"
121
121
  }