Compare commits

...

251 Commits

Author SHA1 Message Date
shimoda.m
3d9a254b63 2025/1/27 PH1エンハンス 本番リリース
メンテナンス告知文削除
2025-01-24 04:04:55 +00:00
shimoda.m
ff5533b647 Merged PR 1010: 2025/1/27 本番リリース 2025-01-21 05:23:54 +00:00
shimoda.m
b3845187f6 Merged PR 1009: Revert "Merged PR 1006: 2025/1/27 PH1エンハンス 本番リリース"
Revert "Merged PR 1006: 2025/1/27 PH1エンハンス 本番リリース"

Reverted commit `b5293888`.

デプロイミスによる切り戻し
2025-01-21 04:47:21 +00:00
shimoda.m
019c818a19 Merged PR 1008: Revert "2025/1/27 PH1エンハンス 本番リリース
Revert "2025/1/27 PH1エンハンス 本番リリース
メンテナンス文言削除"

Reverted commit `1137d826`.

デプロイミスによる切り戻し。
2025-01-21 04:46:02 +00:00
shimoda.m
1137d826ae 2025/1/27 PH1エンハンス 本番リリース
メンテナンス文言削除
2025-01-21 04:00:54 +00:00
shimoda.m
b529388871 Merged PR 1006: 2025/1/27 PH1エンハンス 本番リリース 2025-01-21 02:59:31 +00:00
shimoda.m
aef17893d9 Merged PR 1004: 2025/1/27 本番リリース メンテナンス文言追加
## 概要

2025/1/27の本番リリースに向けて、トップページにメンテナンス文言を追加

リテラルは以下で確認中。(日付を変えただけなので問題ない認識)
OMDS_IS-459 【リリース】エンハンスリリース(1/27)のスケジュールについて

## UIの変更内容
![image.png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/1004/attachments/image.png)
2025-01-21 01:52:47 +00:00
nik.n
4f598b0017 Merged PR 988: pipelineエラー解消のためglobal npmアップデート削除
### 概要
Dockerfileから `RUN npm install -g npm` 削除。Node.js(v18.17.1)とnpm(11.0.0)のバージョン互換性の問題により、pipeline が失敗していたためです。
現在のNode.jsイメージに含まれている npmバージョン(9.6.7)でプロジェクトの要件は満たされているため、グローバルnpmのアップグレードは不要と判断した

### 参考リンク
失敗した Pipeline ビルド
- [#1931 • Merged PR 986: 保守対応の内容反映](https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_build/results?buildId=1931&view=results)
2025-01-08 04:36:06 +00:00
SAITO-PC-3\saito.k
b71ec627d7 特別な文字列をエスケープしてからreplaceAllするように修正 2024-12-11 14:07:39 +09:00
SAITO-PC-3\saito.k
af56f8ccad Supportページにリンク追加。
画面デザイン修正
2024-12-04 10:07:12 +09:00
SAITO-PC-3\saito.k
0b451ed62f NotificationHub登録方法修正 2024-11-27 11:16:46 +09:00
saito.k
11395279af Merged PR 954: NotificationHubへのデバイス登録方法を修正
## 概要
[Task4580: NotificationHubへのデバイス登録方法を修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4580)

- NotificationHubのデバイス登録時に付与するInstallationIDをuuidからuserIdに変更

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 修正範囲がNotificationHubのserviceに閉じているため、既存のテストが通ることを確認したのみ

## 補足
- 相談、参考資料などがあれば
2024-11-26 08:45:27 +00:00
x.itou.t
a07cfe51aa Merged PR 948: PH1エンハンス先行リリース対応
## 概要
[ユーザー ストーリー 4489: 【PH1エンハンス】Dictation Finishedになったファイルのステータスを変更したい](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation-2nd/_workitems/edit/4489)
[ユーザー ストーリー 4491: 【PH1エンハンス】通知にユーザーIDを付加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation-2nd/_workitems/edit/4491)
2024-11-11 05:23:38 +00:00
saito.k
ad397f6fe7 Merged PR 933: Functions修正
## 概要
[Task4557: Functions修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4557)

- 国情報を追加
- テスト修正

## レビューポイント
- 特になし

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-10-16 07:40:27 +00:00
saito.k
85fdec2e5a Merged PR 922: Functions
## 概要
[Task4485: Functions](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4485)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-09-25 01:06:33 +00:00
saito.k
f1b75a7ff0 Merged PR 921: API修正
## 概要
[Task4478: API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4478)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-09-18 01:35:28 +00:00
saito.k
6690302ac3 Merged PR 920: API修正
## 概要
[Task4336: API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4336)

- 文字起こし完了時のメールを文字起こし担当のTypistに送信しないようにする

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認
- ほかのテストケースがすべて通ることを確認
- メール送信処理を確認するテストケースを追加

## 補足
- 相談、参考資料などがあれば
2024-08-09 07:47:33 +00:00
saito.k
1320222d79 Update azure-pipelines-staging.yml for Azure Pipelines 2024-08-09 06:13:40 +00:00
saito.k
baea8ce5e5 Update azure-pipelines-staging.yml for Azure Pipelines 2024-08-09 04:57:41 +00:00
saito.k
d69126a980 Update azure-pipelines-staging.yml for Azure Pipelines 2024-08-09 04:55:36 +00:00
SAITO-PC-3\saito.k
79a2b9f0a3 yml修正 2024-08-09 13:42:37 +09:00
SAITO-PC-3\saito.k
2a755f2bd3 使用するVM修正 2024-08-09 12:27:25 +09:00
SAITO-PC-3\saito.k
edb8a79f94 docker-composeをダウンロードするymlに修正 2024-08-09 12:24:00 +09:00
SAITO-PC-3\saito.k
4a5136deee yml修正3 2024-08-09 11:47:36 +09:00
SAITO-PC-3\saito.k
f2eaba7e5f yml修正2 2024-08-09 11:39:51 +09:00
SAITO-PC-3\saito.k
3b576c6a47 パイプラインのYamlを修正 2024-08-09 11:36:06 +09:00
saito.k
557fc48d05 Merged PR 919: 画面修正
## 概要
[Task4331: 画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4331)

- サポートページにアプリダウンロードリンクを追加

## レビューポイント
- 特になし

## 動作確認状況
- ローカルで確認
- サポートページのみで他画面に影響なし

## 補足
- 相談、参考資料などがあれば
2024-08-09 01:23:21 +00:00
saito.k
353d5ad462 Merged PR 918: analysisLicenses修正
## 概要
[Task4313: analysisLicenses修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4313)

- ライセンス推移CSVの出力内容を修正
  - 移行ライセンスの利用状況も出力する
  - 出力する移行ライセンスの条件は以下
     - 第五階層
     - 割り当て済
     - 有効期限が当日以降
     - 年間ライセンス
- テスト修正
  - 移行ライセンスの取得・変換ロジックのテスト修正

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-07-23 03:35:55 +00:00
saito.k
df55be5e19 Merged PR 917: 画面遷移してもインターバルが初期化されないように修正
## 概要
[Task4248: 画面遷移してもインターバルが初期化されないように修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4248)

- UpdateTokenTimerを修正
  - F5キーリロードの時もトークンをチェックする
- UpdateTokenTimerを各画面ごとではなくApp.tsxに配置する
  - 画面遷移のたびにSetIntervalが初期化されるのを防ぐ
- 各画面のUpdateTokenTimerを削除
  - App.tsxで管理するため

## レビューポイント
- いかの問題が解消されるかを可能であれば確認してほしい。
  - Dictation画面でトークンのチェック~自動更新ができること
  - 画面遷移をしてもトークンチェックのインターバルが初期化されないこと
  - F5キーリロードをした時にトークンチェックが走ること

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 各画面で取得系の処理が正しく動作することを確認

## 補足
- 相談、参考資料などがあれば
2024-07-02 06:43:41 +00:00
SAITO-PC-3\saito.k
35425c576a ローカル環境のDBスキーマの参照先を変更 2024-06-14 16:14:11 +09:00
SAITO-PC-3\saito.k
d4abc0fcfb スキーマを変更 2024-06-14 11:57:28 +09:00
SAITO-PC-3\saito.k
11c0937214 メンテナンス告知を削除 2024-06-14 11:54:27 +09:00
SAITO-PC-3\saito.k
12fe7ac9bb 本番環境で参照するスキーマはomdsのため参照先を修正 2024-06-13 17:06:53 +09:00
SAITO-PC-3\saito.k
b997b928b8 Merge branch 'release-ccb' into hotfix-task4228 2024-06-13 16:45:30 +09:00
SAITO-PC-3\saito.k
10f57c6aeb Merge branch 'develop' into release-ccb 2024-06-12 14:50:18 +09:00
SAITO-PC-3\saito.k
254cbdf0d6 リテラル反映漏れ修正 2024-06-12 14:04:16 +09:00
SAITO-PC-3\saito.k
6b1286ba13 Merge branch 'develop' into release-ccb 2024-06-11 17:00:35 +09:00
saito.k
aea66f4616 Merged PR 914: ユーザー削除API修正
## 概要
[Task4223: ユーザー削除API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4223)

- Authorの削除条件の修正
  - Authorが作成したタスクがある場合は削除できない
- Typistの削除条件の修正
  - 文字起こし担当のタスクがある場合は削除できないように修正
    - 挙動は変わらない(記述を修正した)
- リテラル反映
- テストケース追加

## レビューポイント
- テストケースは足りているか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- 64行目のクエリ(Author/Typistともに)
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/Task4223?csf=1&web=1&e=UsjxWP

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - Authorが作成したタスクがある場合にそのAuthorは削除できないことを確認(タスクのステータスはFinished・Backup)
    - AuthorのAuthorIDが設定されているタスクがある場合はそのAuthorは削除できないことを確認(すべてのステータス)
    - Typistが文字起こし担当のタスクがある場合そのTypistは削除できないことを確認(タスクのステータスはFinished・Backup)

## 補足
- 相談、参考資料などがあれば
2024-06-11 05:05:53 +00:00
SAITO-PC-3\saito.k
c73340ec51 Merge branch 'develop' into release-ccb 2024-06-11 11:20:34 +09:00
saito.k
07904c35e6 Merged PR 885: メンテナンス通知画面実装
## 概要
[Task4148: メンテナンス通知画面実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4148)

- TOPページにメンテナンス通知を追加

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Hotfix4148?csf=1&web=1&e=aGsZP6

## 動作確認状況
- ローカルで確認
- TOPページに文言を追加したのみ
  - 画面サイズを小さくしてもデザインが崩れないことを確認

## 補足
- メンテナンスモードに入る日時については未定のため、OMDS定例で決まり次第修正し、決められた日時にmainにマージする
2024-06-11 01:24:52 +00:00
saito.k
474cdd56c6 Merged PR 913: ライセンス取得APIの修正
## 概要
[Task4220: ライセンス取得APIの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4220)

- 割り当て可能ライセンス取得APIを修正
  - 有効期限が当日のライセンスは割り当て可能としないように修正
    - 有効期限が翌日以降のライセンスを取得
- テスト修正
- ライセンス割り当ての処理にコメント追加
  - 結果として有効期限が当日のライセンスは割り当てられないので問題はないが、記述方法が紛らわしいのでコメントを追加した。

## レビューポイント
- テストケースは足りているか
- 追加したコメントの意図は伝わるか

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- クエリ部分を修正したが、基準となる日付を変更したのみなので、クエリ自体に変更はなし
  - Before/Afterで確認したが、変更はなかった

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - テストを追加して、デグレがないことを検証した

## 補足
- 相談、参考資料などがあれば
2024-06-10 02:31:35 +00:00
saito.k
83732fa1b5 Merged PR 911: 画面修正(ファイル名変更ボタンのデザイン)
## 概要
[Task4213: 画面修正(ファイル名変更ボタンのデザイン)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4213)

- ファイル名変更ボタンのデザイン修正
  - 言語を英語以外にすると、ファイル名の入力欄と重なってしまうのを修正
    - ボタンの位置を入力欄の下に修正
- リテラル修正も含めて対応
  - https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_sprints/taskboard/OMDSDictation%20%E3%83%81%E3%83%BC%E3%83%A0/OMDSDictation/%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%2036-1?workitem=4214

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task4213?csf=1&web=1&e=ftBwWm

## 動作確認状況
- ローカルで確認
- デザインの修正のみのためデグレは発生しない

## 補足
- 修正したデザインに対してはOMDS様に確認済み
2024-06-06 07:36:17 +00:00
saito.k
6b1650a634 Merged PR 910: メール文面修正&ユーザー一括登録のテンプレートファイル修正
## 概要
[Task4190: メール文面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4190)
[Task4191: ユーザー一括登録のテンプレートファイル修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4191)

- ユーザー一括追加時に送信するメールの内容を修正
  - SCVとなっていたところをCSVに修正
- ユーザー一括追加用のエクセルの項目名を画面上の項目名と合わせる
  - auto_renewとなっていたところをauto_assignに修正

## レビューポイント
- 修正内容の認識は合っているか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - CSV変換のテストが通ることを確認
    - メール内容はdev動作確認で確認

## 補足
- 相談、参考資料などがあれば
2024-06-04 06:54:42 +00:00
makabe.t
4f7d65f0e8 Merged PR 905: POST /accounts/worktypes/{id}
## 概要
[Task3986: POST /accounts/worktypes/{id}](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3986)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-06-04 04:25:54 +00:00
SAITO-PC-3\saito.k
1f10a3f096 Merge branch 'develop' into release-ccb 2024-06-04 13:07:39 +09:00
saito.k
909c2a6d55 Merged PR 909: 画面修正(CSVファイルの変換処理)
## 概要
[Task4202: 画面修正(CSVファイルの変換処理)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4202)

- CSVデータの変換関数を修正
  - author_id,encryption_passwordが数値のみの場合でも、文字列として扱うようにする

## レビューポイント
- ほかに確認したほうが良いテストケースはあるか

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 既存のテストはすべて通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-06-03 06:36:05 +00:00
saito.k
37666a00c7 Merged PR 908: API修正(タスク削除条件のステータスチェックを修正)
## 概要
[Task4201: API修正(タスク削除条件のステータスチェックを修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4201)

- タスク削除不可条件を修正
  - Pendingの時も削除できないようにする

## レビューポイント
- 修正の認識があっているか

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - タスクの各ステータスで削除可否を確かめられるように修正

## 補足
- 相談、参考資料などがあれば
2024-06-03 05:39:53 +00:00
saito.k
b7925f311b Merged PR 907: 画面修正(タスク削除ボタンの活性条件を修正)
## 概要
[Task4200: 画面修正(タスク削除ボタンの活性条件を修正)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4200)

- タスク削除ボタンの活性条件を修正
  - Pendingの時は削除できないようにする

## レビューポイント
- 特になし

## UIの変更
- https://ndstokyo.sharepoint.com/:i:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task4200/Pending%E5%89%8A%E9%99%A4%E4%B8%8D%E5%8F%AF.png?csf=1&web=1&e=ATzEZ8

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - Inprogressのステータスで削除できないこと
  - Uploaded・Finished・backupで削除できる

## 補足
- 相談、参考資料などがあれば
2024-06-03 05:07:08 +00:00
SAITO-PC-3\saito.k
97620881d3 Merge branch 'develop' into release-ccb 2024-05-31 14:05:54 +09:00
saito.k
0e68f26c57 Merged PR 906: ユーザー認証API修正
## 概要
[Task4182: ユーザー認証API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4182)

- 認証済みチェックをパスワード変更より先に行うように修正
- パスワード変更に失敗したら、認証済みフラグをfalseにするリカバリ処理追加
  - リカバリに失敗したら手動復旧ログを出力
- メール送信に失敗したらエラーを返すように修正
  - メール送信に失敗したらリカバリ処理を行うように修正
    - リカバリに失敗したら手動復旧ログを出力
- テスト修正
  - リカバリ処理を考慮したケースを追加

## レビューポイント
- リカバリ処理の記述
- メール送信でエラーが起きたときにエラーを握りつぶさないようにしたが問題ないか
  - メール送信で失敗したときにエラーを握りつぶすと、ユーザーは届かないメールを待つしかなくなる
    - 失敗を伝えて、リカバリをしてあげると再実行してもらうことができる。

## クエリの変更
- クエリの変更はなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 既存のテストケースをDBを使うテストに置き換え
    - 結果は変えずに通ることを確認
  - テストケースを追加し、新たな観点でテストを作成

## 補足
- 相談、参考資料などがあれば
2024-05-30 00:18:59 +00:00
makabe.t
9736d23653 Merged PR 904: POST /accounts/worktypes
## 概要
[Task3985: POST /accounts/worktypes](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3985)

- `POST /accounts/worktypes`のリクエストバリデータにテストを追加しました。

## レビューポイント
- テストケースに不足はないか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストの追加のみなので影響なし
2024-05-28 05:58:17 +00:00
makabe.t
261a5afbdd Merged PR 903: POST /notification/register
## 概要
[Task3983: POST /notification/register](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3983)

- `POST /notification/register`APIのバリデータに対するテストを追加しました。

## レビューポイント
- テストケースに不足はないか。

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストの追加のみなのでほかに影響なし。
2024-05-28 05:38:33 +00:00
SAITO-PC-3\saito.k
3a7c1765ee Merge branch 'main' into develop
# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
2024-05-24 17:26:50 +09:00
SAITO-PC-3\saito.k
c16a8b023c Merge branch 'develop' into release-ccb 2024-05-24 16:17:03 +09:00
saito.k
4b16e6a004 Merged PR 900: 画面修正(フッターに規約リンク追加)
## 概要
[Task4084: 画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4084)

- フッターに規約リンクを追加
  - デザインはOMDS様に確認済み
- サポートページにフッターを追加

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Hotfix4084?csf=1&web=1&e=bQBkQI

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-05-24 05:23:26 +00:00
saito.k
bd8f035c46 Merged PR 902: POST /files/audio/upload-finished
## 概要
[Task3982: POST /files/audio/upload-finished](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3982)

- /files/audio/upload-finished のバリデーションテスト作成

## レビューポイント
- テストケースで抜けているところはないか
  - IsNotEmpty()は一つに省略

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - テスト追加のため既存機能に影響なし

## 補足
- 相談、参考資料などがあれば
2024-05-23 07:26:04 +00:00
makabe.t
bdcce37b5a Merged PR 898: 対応
## 概要
[Task4174: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4174)

- アカウント情報画面からディーラーを変更する際に、同名ディーラーがいてもそれぞれ別個に指定できるようにIDで検索するように修正しました。

## レビューポイント
- 対応方針は認識通りでしょうか?

## UIの変更
- なし

## クエリの変更
- クライアントのみ変更なのでなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ローカルで同名ディーラーでそれぞれを指定して変更できることを確認
2024-05-23 01:12:31 +00:00
SAITO-PC-3\saito.k
7af5c50a27 Merge branch 'develop' into release-ccb 2024-05-20 13:57:26 +09:00
saito.k
5860da285e Merged PR 899: 階層構造変更API修正
## 概要
[Task4179: 階層構造変更API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4179)

- 階層構造変更API修正
  - 第四⇔第五の切り替え処理の時は国・リージョンの一致を確認しない
  - 不要なエラーコード・エラーハンドリングを削除
- テスト修正

## レビューポイント
- 修正箇所の認識は合っているか
- テストケースは足りているか

## 動作確認状況
- ローカルで確認
  - 国の違うDealerに付け替える処理を行い成功した
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 国の違う第四⇔第五間の付け替えのテストが成功することを確認
    - ほかのテストが成功することを確認

## 補足
- 相談、参考資料などがあれば
2024-05-17 06:49:26 +00:00
SAITO-PC-3\saito.k
3ece576476 Merge branch 'develop' into release-ccb 2024-05-16 14:06:54 +09:00
SAITO-PC-3\saito.k
4dac420bed typeORMのモジュール設定が漏れていたので修正 2024-05-15 17:00:20 +09:00
makabe.t
ddf338bc72 Merged PR 890: 翻訳情報反映
## 概要
[Task4058: 翻訳情報反映](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4058)

- 6月末リリース分の翻訳をクライアントに適用しました。
  - いただいた翻訳内容は開発の管理ファイルに反映しています。(メンテナンスについては未反映です。)
[ラベル・メッセージ管理](https://sonyjpn.sharepoint.com//r/sites/S127-OMDS-EXTERNAL-NDS/_layouts/15/doc2.aspx?sourcedoc=%7BCBFBB1F9-3AB4-4E2E-A21D-572684D20B29%7D&file=%E3%83%A9%E3%83%99%E3%83%AB%E3%83%BB%E3%83%A1%E3%83%83%E3%82%BB%E3%83%BC%E3%82%B8%E7%AE%A1%E7%90%86_dictation.xlsx&action=default&mobileredirect=true)

## レビューポイント
- 翻訳の適用は適切か?

## UIの変更
- 翻訳の適用

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - リテラルの内容のみなので影響なし
2024-05-15 07:15:35 +00:00
saito.k
0cca61517c Merged PR 896: バージョンアップ用SQLを作成
## 概要
[Task4044: バージョンアップ用SQLを作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4044)

- jobNumberの初期値を設定するマイグレーションファイル作成
  - タスクテーブルにレコードがある(=タスクを作成したことがある)アカウントに対しては最新のJobNumberで初期値をセットする
  - タスクテーブルにレコードがない(=タスク作成をしたことがない)アカウントに対しては`00000000`をセットする

## レビューポイント
- セットする初期値は認識あっているか
- migrate downの処理は問題ないか

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - マイグレーションファイルの作成のみなのでほかに影響はない想定

## 補足
- 相談、参考資料などがあれば
2024-05-15 06:15:22 +00:00
saito.k
fe5e8b8e1c Merged PR 895: API修正(アカウント作成系)
## 概要
[Task4043: API修正(アカウント作成系)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4043)

- アカウント作成時にJobNumberの初期値を設定するように修正
- パートナーアカウント作成時にJobNumberの初期値を設定するように修正
- リカバリ処理にJobNumberのレコード削除を追加
- テスト修正

## レビューポイント
- JobNumber作成処理の追加する箇所に問題はないか
- テストケースに不足はないか

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- 既存のクエリに修正はなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 既存テストが通ることを確認
    - パートナーアカウント作成のテストにメール送信内容のチェックを追加
    - ソート条件が作成・削除されていることを確認するテストを追加

## 補足
- 相談、参考資料などがあれば
2024-05-14 07:18:57 +00:00
saito.k
dfdc6a33ad Merged PR 894: API修正(アカウント削除系)
## 概要
[Task4034: API修正(アカウント削除系)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4034)

- アカウント削除時にJobNumberテーブルのレコードも削除するように修正
- パートナー削除時にJobNumberテーブルのレコードも削除するように修正
- テスト修正

## レビューポイント
- テストケースに不足はないか
- ジョブナンバーテーブルの削除順に問題はないか

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- JobNumberテーブルのレコードを削除する処理を追加した
  - 既存のクエリに影響はなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 既存のテストが通ることを確認
  - テストしていなかった観点(ソート条件も削除されているか等)も確認するように修正

## 補足
- 相談、参考資料などがあれば
2024-05-14 02:12:41 +00:00
saito.k
35e2d626a0 Merged PR 893: API修正(upload-finished)
## 概要
[Task4033: API修正(upload-finished)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4033)

- JobNumberテーブルから取得したJOBNUMBERを使用してタスクを作成する。
- テスト追加

## レビューポイント
- テストケースは足りているか
- JOBNUMBERの採番ロジックに誤りはないか

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
-  JobNumberテーブルからの取得と更新クエリを追加した
  - 既存のクエリを変更はしていない

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストケースを修正し、既存テストがすべて通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-05-13 05:04:16 +00:00
saito.k
228e21ba78 Merged PR 892: migration修正
## 概要
[Task4035: migration修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4035)

- job_numberテーブル作成
- マイグレーションのコマンド修正

## レビューポイント
- インデックス・ユニーク制約・外部キー制約の認識は合っているか
- マイグレーションのコマンドは基本的にccbで認識あっているか

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - マイグレーションファイル作成のみでほかに影響なし

## 補足
- 相談、参考資料などがあれば
2024-05-10 03:56:48 +00:00
makabe.t
68df7cd728 Merged PR 887: POST /auth/token
## 概要
[Task3981: POST /auth/token](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3981)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- `POST /auth/token` のバリデータのUTを追加しました。

## レビューポイント
- テスト項目は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストの修正のみなので影響なし
2024-05-09 05:28:03 +00:00
makabe.t
ffd6eb4e68 Merged PR 886: GET /tasks
## 概要
[Task3980: GET /tasks](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3980)

- `GET /tasks` のバリデータのUTを追加しました。

## レビューポイント
- テスト項目は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストの修正のみなので影響なし
2024-05-09 01:20:57 +00:00
SAITO-PC-3\saito.k
e205209b12 Merge branch 'develop' into release-ccb 2024-05-08 16:15:14 +09:00
SAITO-PC-3\saito.k
3ea84a3597 Merge branch 'main' into develop 2024-05-08 15:55:30 +09:00
saito.k
b71c4398d2 Merged PR 891: 不具合対応
## 概要
[Task4163: 不具合対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4163)

- ビルド成果物にメール用のテンプレートも含めるように修正

## レビューポイント
- 特になし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - コードの修正はなし

## 補足
- 相談、参考資料などがあれば
2024-05-08 05:41:20 +00:00
makabe
c26ad130ed Merge branch 'develop' into release-ccb 2024-05-07 16:49:42 +09:00
makabe.t
279a9ab037 Merged PR 889: function修正
## 概要
[Task4164: function修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4164)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-05-07 07:11:39 +00:00
SAITO-PC-3\saito.k
4e00c03ef2 Merge branch 'main' into develop 2024-05-07 15:42:39 +09:00
saito.k
c813ddc0ac Merged PR 888: 対応
## 概要
[Task4160: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4160)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-05-07 05:37:16 +00:00
makabe
23f8b54011 Merge branch 'main' into develop 2024-05-07 12:04:03 +09:00
makabe.t
8122f6f4e1 Merged PR 884: FunctionにX-Requested-Withヘッダを適用
## 概要
[Task4142: FunctionにX-Requested-Withヘッダを適用](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4142)

- Functionのユーザー一括登録の処理中でOMDS CloudのAPIを呼び出す処理があるので、X-Requested-Withヘッダを適用しました。
- 一括登録失敗時のメール文面の翻訳でエラーがない場合のメッセージが日本語のままになっていたので各言語に対応しました。

## レビューポイント
- ヘッダの適用は適切でしょうか?
- 翻訳の適用方法で、言語ごとに割り当てる内容を定数としていますが、文面の置き換え方法に問題はないでしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ローカルからAPIを叩い検証証
2024-05-07 00:05:17 +00:00
SAITO-PC-3\saito.k
e76242bddd Functionsのテストで使用する環境変数を修正 2024-04-25 18:19:48 +09:00
saito.k
af0ba78ae9 Merged PR 883: Functions修正
## 概要
[Task4132: Functions修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4132)

- 自動割り当てを行うライセンスの取得条件を変更
  - 有効期限が近いライセンスまたは有効期限が設定されていないライセンス(新規ライセンス)を取得する
  - 有効期限が近いものから割り当てを行うので、ソートはサーバー側で行うようにした
- メール送信処理を追加

## レビューポイント
- テンプレート取得からメール送信までの実装で漏れはないか
- テストケースは足りているか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- 2行目で割り当てるライセンスを取得しているが、その条件を修正した
- https://ndstokyo.sharepoint.com/:u:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/task4132/after.log?csf=1&web=1&e=jh49c3

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 追加したメール送信処理を確認するように各テストを修正し、テストが通っている
  - テストの観点を拡充したうえでテストが通っていることを確認
    - ライセンス割り当て履歴の内容をより詳細に確認するようにした

## 補足
- 相談、参考資料などがあれば
2024-04-25 08:56:03 +00:00
makabe
1904b95adf Merge branch 'develop' into release-ccb 2024-04-24 11:35:58 +09:00
makabe.t
d43cece48a Merged PR 882: 追加開発分をステージングに反映するPipelineを作成
## 概要
[Task4016: 追加開発分をステージングに反映するPipelineを作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4016)

- 追加開発分CCBをステージングに反映するPipeline定義のYamlファイルを追加しました。

## レビューポイント
- 参照元ブランチを変えた以外はSTGデプロイそのままにしていますが、対応の不足はないでしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- マージ後に確認
2024-04-22 08:01:37 +00:00
SAITO-PC-3\saito.k
ba7196cac1 本番デプロイようにコメントアウトした実装をもとに戻す 2024-04-22 10:44:56 +09:00
SAITO-PC-3\saito.k
7eecb001c6 4/22の本番環境デプロイのために確認未実施の修正をコメントアウト 2024-04-21 22:41:44 +09:00
SAITO-PC-3\saito.k
0b01da936d CacheModule作成時にTTLを設定 2024-04-19 19:15:23 +09:00
saito.k
b88c0d9b96 Merged PR 877: ディーラー取得APIの修正
## 概要
[Task4104: ディーラー取得APIの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4104)

- メールの文面を各言語版に置き換えました。
  - 環境変数に設定されたアカウントIDのDealerはResponseに含めないように修正

## レビューポイント
- 環境変数からインスタンス変数に代入するときの処理に問題はあるか
- 環境変数のフォーマットはこれで良いか
  - もっとよいやり方があれば指摘いただきたいです
- テストケースに不足はないか

## UIの変更
- なし

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - ほかのテストに影響が出ていない
2024-04-19 04:47:32 +00:00
makabe
b7554e30ff CCBからDBの向き先を変更 2024-04-19 13:38:24 +09:00
makabe
b03cda3ccc Merge branch 'ccb' into develop 2024-04-19 11:17:17 +09:00
makabe.t
7bfd424a64 Merged PR 875: メールの多言語対応
## 概要
[Task3859: メールの多言語対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3859)

- メールの文面を各言語版に置き換えました。
  - アカウント情報消去完了通知 [U-111] 
  - ユーザー一括登録 受付通知 [U-120] 
  - ユーザー一括登録 完了通知 [U-121] 
  - ユーザー一括登録 失敗通知 [U-122] 
  - パートナーアカウント情報消去完了通知 [U-123] 

## レビューポイント
- 対応メールは適切でしょうか?
- メール文面は適切でしょうか?
- 反映内容は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ローカル確認、マージ後にdevelop動作確認
2024-04-19 02:14:05 +00:00
makabe
9ee29e91ba マイグレーションコマンド修正 2024-04-19 09:10:48 +09:00
SAITO-PC-3\saito.k
b24059b538 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
#	dictation_function/src/test/common/utility.ts
#	dictation_server/src/features/files/test/utility.ts
2024-04-18 09:57:37 +09:00
SAITO-PC-3\saito.k
a7b18d8151 Merge branch 'develop' 2024-04-17 14:02:05 +09:00
makabe.t
23862ad3ac Merged PR 874: 不具合修正
## 概要
[Task4137: 不具合修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4137)

- ファイル名変更時にダイアログを追加

## レビューポイント
- 共有
## UIの変更
- ダイアログを追加

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ローカル確認
2024-04-17 01:49:34 +00:00
saito.k
566da623bf Merged PR 873: dev動作確認不具合修正
## 概要
[Task4131: dev動作確認不具合修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4131)

- メール送信対象のアカウント取得条件を修正
  - 第五階層はDPAの規約に同意することがないため、accepted_dpa_versionは常にNULLとなる
    - 取得条件にaccepted_dpa_versionがNOTNULLを追加するとカラムに値が入る契機がないのでアカウントを取得できなくなってしまっていた。

## レビューポイント
- 特になし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - ほかのテストケースで使用しているユーザーデータをaccepted_dpa_versionはNULLの状態で作成するようにし、テストがすべて通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-04-17 01:01:31 +00:00
makabe.t
69241ed36c Merged PR 871: Function(音声ファイル自動削除)修正
## 概要
[Task4060: Function(音声ファイル自動削除)修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4060)

- 自動ファイル削除で生ファイル名を利用するように修正しUTを追従しました。

## レビューポイント
- 生ファイル名を使う方法は適切でしょうか?
- テストの修正は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- select対象をraw_file_nameに修正

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - テストが通ることを確認
2024-04-17 00:23:08 +00:00
makabe.t
c469f943f1 Merged PR 872: 生ファイル名の初期値を追加するマイグレーションファイル作成
## 概要
[Task4125: 生ファイル名の初期値を追加するマイグレーションファイル作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4125)

- 生ファイル名と表示ファイル名をそれぞれ修正して初期値を入れるスクリプトを追加しました。
  - 生ファイル名:これまでfile_nameカラムに入っていた値
  - file_nameの値から.zipを除いたもの

## レビューポイント
- この値の操作で問題はないか

## UIの変更
- なし
## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - migrate up/downで想定通りになることを確認
2024-04-17 00:18:48 +00:00
makabe.t
0a714f8484 Merged PR 870: ファイル情報ポップアップ画面修正
## 概要
[Task4051: ファイル情報ポップアップ画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4051)

- ファイル情報ポップアップを修正し、ファイル名を変更できるように修正しました。
- 音声ファイル名変更APIのファイル名の文字数を修正しました。
  - フロントの入力欄で64文字にしたので、プラス拡張子で68文字としました。

## レビューポイント
- 画面イメージは認識通りでしょうか?
- 表示では拡張子を取って、APIに渡す際にはつけているのですが処理として不自然ではないでしょうか?

## UIの変更
- [Task4051](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task4051?csf=1&web=1&e=nbYIE0)

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - タスク一覧、ファイル情報ポップアップが正常に見えることを確認
2024-04-16 10:12:44 +00:00
makabe.t
1d2089b0c4 Merged PR 868: 音声ファイル名変更のに伴うAPI修正
## 概要
[Task4053: 音声ファイル名変更のに伴うAPI修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4053)

- 音声ファイル名について生ファイル名を利用するように修正しました。
  - ファイルアップロード完了(タスク登録)
    - パラメータのファイル名で生ファイル名も登録
  - 音声ファイルダウンロード先取得
  - タスク削除
    - blobストレージのファイル名に生ファイル名を利用

## レビューポイント
- 対象APIは認識通りか

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - UTが通ることを確認
    - 各APIで生ファイル名を使って実行できることを確認
2024-04-16 05:24:18 +00:00
saito.k
f975ecf551 Merged PR 869: Functions修正
## 概要
[Task4085: Functions修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4085)

- メール送信対象のアカウント取得条件を修正
  - プライマリー管理者の規約同意用のカラムがNULLであった場合は、そのアカウントにはメール送信しない。
- Entityの`@Column`で設定する型が実際のパラメータの型と異なっていたため修正
  - 文字列のところをDateTimeとしていた

## レビューポイント
- 修正内容の認識あっているか
- テストケースは足りているか

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/4085?csf=1&web=1&e=WRec5O
  - 35行目に変更あり(規約系のカラムがNULLではないという条件を追加)
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 既存のテスト結果に影響なし

## 補足
- 相談、参考資料などがあれば
2024-04-16 05:08:04 +00:00
makabe.t
e6d27d7810 Merged PR 867: 音声ファイル名変更API実装
## 概要
[Task4052: 音声ファイル名変更API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4052)

- ファイル名変更APIとそのUTを実装しました。

## レビューポイント
- リポジトリ実装のチェック内容とその順序は適切でしょうか?
- テスト項目は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - UT実行
    - ローカル実行

## 補足
- 相談、参考資料などがあれば
2024-04-15 06:52:07 +00:00
makabe.t
f209c7359e Merged PR 865: IF実装・修正
## 概要
[Task4049: IF実装・修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4049)

- 音声ファイル名変更APIのIFを実装してリクエストパラメータのテストを実装しました。
- タスク一覧取得APIのレスポンスに生ファイル名を追加しました。
- OpenAPIの更新

## レビューポイント
- ファイル名変更APIのパスは適切でしょうか?
- バリデータのチェックは適切でしょうか?

## UIの変更
- なし

## クエリの変更
- IFなのでなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 既存テストを実施して、タスク一覧についてはレスポンス期待値を修正。
    - タスク一覧画面が正常に見えることを確認
2024-04-12 01:36:49 +00:00
makabe.t
33d4ab3d2f Merged PR 863: (Sprint31対応)一部のentityでMySQL上の型がbigintのものに対してbigintTransformerで変換する処理が入っていない
## 概要
[Task3928: (Sprint31対応)一部のentityでMySQL上の型がbigintのものに対してbigintTransformerで変換する処理が入っていない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3928)

- 現在、bigint型で変換を適用していない部分へのTransformerの適用はうまくいかないため保留。
  - 既存処理については問題ありません
- Functionのテストで日付に依存している箇所があったので修正しています。

## レビューポイント
- 共有

## UIの変更
- なし
## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - テストの修正のみ
2024-04-11 04:29:16 +00:00
makabe.t
07bca1d638 Merged PR 866: DBマイグレーションエラー修正
## 概要
[Task4120: DBマイグレーションエラー修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4120)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-04-10 09:48:40 +00:00
makabe.t
09c21eafa7 Merged PR 864: DBにカラム追加
## 概要
[Task4057: DBにカラム追加](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4057)

- DBマイグレーションファイルとして音声ファイルテーブルに生ファイル名カラムを追加しました。

## レビューポイント
- カラムの要件は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし
## 動作確認状況
- ローカルでmigrate:up/downできることを確認
2024-04-10 06:15:54 +00:00
makabe.t
e6d6e477d9 Merged PR 862: パートナー一覧画面&パートナー編集ポップアップ実装
## 概要
[Task3935: パートナー一覧画面&パートナー編集ポップアップ実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3935)

- パートナー一覧画面からパートナー編集ポップアップを表示して情報を変更できる画面実装をしています。

## レビューポイント
- エラーの表示は適切でしょうか?
- 画面イメージは認識通りでしょうか?

## UIの変更
- [Task3935](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3935?csf=1&web=1&e=FdaUMT)

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 新規機能なので問題なし
2024-04-08 07:41:05 +00:00
maruyama.t
915483c109 Merged PR 860: パートナー情報更新API実装
## 概要
[Task3937: パートナー情報更新API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3937)

- パートナーアカウント情報更新APIとUTを実装しました。

## レビューポイント
- エラーケースの出し分けは適切でしょうか?
- テストケースは過不足ないでしょうか?

## UIの変更
- なし

## クエリの変更
- 新規追加のため変更はなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 新規追加なので問題なし。
2024-04-05 02:37:58 +00:00
SAITO-PC-3\saito.k
c600d9f818 Merge branch 'develop' 2024-04-03 18:51:31 +09:00
saito.k
5147f853ae Merged PR 861: API修正
## 概要
[Task4038: API修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4038)

- タスク作成時のジョブナンバーの採番ルールを修正
  - 常に最新タスクのジョブナンバー+1を採番する
- テストケース追加
- 非同期処理の呼び出しでawaitが抜けている箇所を修正

## レビューポイント
- 修正内容の認識は合っているか
- テストケースに不足はないか
- 修正箇所がほかの機能に影響していないか
  - taskRepositoryのcreateはupload-finished以外では使用されていない

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/Task4038?csf=1&web=1&e=WTIk2l
  - L107辺りが変更箇所

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - 修正したcreateがほかで使用していないことを確認
    - テストケースを追加し確認
    - 既存のテスト含め成功することを確認

## 補足
- 相談、参考資料などがあれば
2024-04-03 05:35:30 +00:00
maruyama.t
0288292058 Merged PR 859: パートナーユーザー取得API実装
## 概要
[Task3936: パートナーユーザー取得API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3936)
- このPull Requestでの対象/対象外
パートナー変更APIの修正は別タスクで対応
- 影響範囲(他の機能にも影響があるか)
新規APIのため他の機能に影響はない

## レビューポイント
- パートナーのアカウントIDからユーザー一覧を取得する際に、Repository層ではEmai認証状態を意識した取得は行わない
 →service層でフィルタリングする実装にしたが
 (アカウントIDからユーザー一覧を取得する処理がいままでなかったので、あったほうがいいかなと思い)

## クエリの変更
新規APIのためクエリの変更はない

## 動作確認状況
- ローカルで確認
UT+POSTMAN
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか
既存機能には手を入れていない
## 補足
- 相談、参考資料などがあれば
2024-04-03 00:50:53 +00:00
maruyama.t
8752448eed Merged PR 858: エラーメッセージ修正
## 概要
[Task4013: エラーメッセージ修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/4013)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)
なし

## レビューポイント
- とくになし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
UIなのでなし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか
メッセージの修正のみなのでデグレなし

## 補足
- 相談、参考資料などがあれば
2024-03-26 11:30:30 +00:00
湯本 開
1d71bef7aa Merged PR 857: テスト追加
## 概要
[Task3977: テスト追加](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3977)

- エラー時にアラーム発報ログが出力されているかを確認するテストを追加
- contextを継承して、ログに吐き出す内容をstring型の配列として溜め込むテスト用のクラスを追加

## レビューポイント
- テストの内容は妥当か

## クエリの変更
- テストのみ追加なので変更なし

## 動作確認状況
- npm run testが通過したこと
2024-03-26 07:05:39 +00:00
maruyama.t
114ded790e Merged PR 855: API IF実装(パートナーを編集したい)
## 概要
[Task3930: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3930)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 新規追加API2本のIFを作成、controllerの返却値は仮実装(別タスクで実装)
- 影響範囲(他の機能にも影響があるか)
 新規追加のみなので影響はなし

## レビューポイント
- 特筆する点はありません

## UIの変更
なし

## クエリの変更
なし

## 動作確認状況
- ローカルで確認
 バリデーションテストとPOSTMANからの起動の確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか
完全新規のIFの実装のみなのでデグレはない想定

## 補足
- 相談、参考資料などがあれば
2024-03-26 06:22:07 +00:00
Kentaro Fukunaga
ae638b16be Merge remote-tracking branch 'origin/develop' 2024-03-25 18:03:40 +09:00
湯本 開
133db833ee Merged PR 850: staging-pipelineのテスト実行修正
## 概要
[Task3948: staging-pipelineのテスト実行修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3948)

- docker-composeを使用したテストは `ubuntu-latest` で実行する必要があったため、pipelineを修正

## レビューポイント
- 問題がありそうな記述はあるか
- 実行順の依存関係等が壊れていないか

## 動作確認状況
- 未動作確認。4月以降、CCB対応時に動かして確認を想定。
2024-03-25 08:09:16 +00:00
Kentaro Fukunaga
1f0cf50166 Merged PR 856: Clientビルド時に埋め込んでいるキーやURLの更新
## 概要
[Task3963: Clientビルド時に埋め込んでいるキーやURLの更新](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3963)

- クライアント本番用のACB2C関連の環境変数を作り直したACB2Cの値に差し替えました。

## レビューポイント
- 共有

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- 未実施
- デプロイ後に確認

## 補足
- 相談、参考資料などがあれば
2024-03-25 07:54:43 +00:00
masaaki
eb6b413adb Merged PR 848: パートナー一覧画面修正
## 概要
[Task3833: パートナー一覧画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3833)

- パートナー一覧画面のパートナーにカーソルを合わせた際に、Delete Partnerリンクが表示される。
押下した場合は、本当に削除するのかを確認するメッセージが表示され、どのPartnerを削除するかメッセージ欄に表示されること。

## レビューポイント
- 特筆する点はなし

## UIの変更
https://ndstokyo.sharepoint.com/sites/Piranha/Shared%20Documents/Forms/AllItems.aspx?csf=1&web=1&e=hzPw9b&cid=e8e0702d%2D3730%2D4295%2Dbb9d%2D40e6b1998906&FolderCTID=0x012000C0DCEE65AC2177479C3C761CD137C9C9&id=%2Fsites%2FPiranha%2FShared%20Documents%2FGeneral%2FOMDS%2F%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%2FTask3833&viewid=786a81cf%2Dd15f%2D4dc2%2D9e55%2Dc7a729fbc72f

## クエリの変更
なし

## 動作確認状況
- ローカルで確認(APIからの返却値を直接指定する方式で確認)
- 行った修正がデグレを発生させていないことを確認できるか
  新規機能追加のみなので問題なし

## 補足
- 相談、参考資料などがあれば
2024-03-22 07:50:58 +00:00
makabe.t
6e93a5be79 Merged PR 846: パートナーアカウント削除API実装
## 概要
[Task3834: パートナーアカウント削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3834)

- パートナーアカウント削除APIとUTを実装しました。

## レビューポイント
- 削除対象データは適切でしょうか?
- テストケースに不足はないでしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
  - テストとローカルで実行確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 既存処理への変更なし
2024-03-22 06:12:47 +00:00
湯本 開
ac3d523c0e Merged PR 826: Azure Functions実装(音声ファイル削除)
## 概要
[Task3880: Azure Functions実装(音声ファイル削除)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3880)

- 自動音声ファイル削除を実装
- 上記のテストを実装
- テストにMySQLを使用する仕組みを導入

## レビューポイント
- テストケースは十分か
- テスト内容は妥当か
- developにデプロイする前の動作確認・ユニットテストとして十分か

## クエリの変更
- 新規処理のため、既存からの変更はなし

## 動作確認状況
- DBが空の状態でローカル環境で実行し、0件削除のログが出ることを確認
  - 削除対象が正しいか等はdevelopでチェック予定
- 行った修正がデグレを発生させていないことを確認できるか
  - 既存処理の変更はなし
2024-03-19 07:36:03 +00:00
Kentaro Fukunaga
75f0a49fc1 Merged PR 831: 親アカウント変更API実装
## 概要
[Task3853: 親アカウント変更API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3853)

- 親アカウント切り替えAPIを実装しました。

## レビューポイント
- Service層の関数の分け方に改善点ないか?
- テストケースで他にあったほうがいいものや観点などあるか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで全テスト通ることを確認
- 行った修正がデグレを発生させていないことを確認できるか
    - 新規APIの実装のため既存実装に変更なし
2024-03-18 05:47:24 +00:00
maruyama.t
cab7a75ec1 Merged PR 849: 有効なアカウント内の削除されたユーザーの割り当て履歴が集計されない
## 概要
[Task3929: 有効なアカウント内の削除されたユーザーの割り当て履歴が集計されない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3929)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-03-15 12:19:16 +00:00
SAITO-PC-3\saito.k
b8af6fb6b2 Merge branch 'develop' 2024-03-15 19:01:42 +09:00
saito.k
13e0793219 Merged PR 847: リテラルの修正
## 概要
[Task3927: リテラルの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3927)

- 翻訳反映
  - Auto Renew → Auto Assign に変更
  - Transcriptionist List→ Transcription List に変更

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - リテラルの反映のみのためほか機能に影響なし

## 補足
- 相談、参考資料などがあれば
2024-03-15 08:21:24 +00:00
masaaki
f80912c617 Merged PR 834: API IF実装
## 概要
[Task3904: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3904)

- 「プロダクト バックログ項目 1242: パートナーを削除したい」のAPI IFを作成しました
- 影響範囲(他の機能にも影響があるか)
  - 新規IFのため影響はなし

## レビューポイント
- controllerの試験実装が初なので、テストケース過不足ないか確認いただきたいです。

## UIの変更
- 無し

## クエリの変更
- 無し

## 動作確認状況
- ユニットテストが通ることを確認、ローカル環境でpostmanで呼び出せることを確認、SWAGGER UI上で追加されていることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ユニットテストが通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-03-15 07:41:56 +00:00
makabe.t
66c643677d Merged PR 829: 音声ファイルバックアップポップアップ修正
## 概要
[Task3882: 音声ファイルバックアップポップアップ修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3882)

- 音声ファイルバックアップ中にファイル削除された場合の処理を追加しました。
  - ダウンロードで対象ファイルが削除されていた場合に特別なエラーとなるようにしています。
  - タスクバックアップで対象ファイルが削除されていた場合でも成功となるようにしています。

## レビューポイント
- 対応するエラーは適切でしょうか?

## UIの変更
- [Task3882](https://ndstokyo.sharepoint.com/:i:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3882/%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E5%89%8A%E9%99%A4%E6%B8%88%E3%81%BF.png?csf=1&web=1&e=1BKVh8)

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - ダウンロード実行中にファイル削除
    - backupAPIからタスク不在エラーを返却して成功するか確認
2024-03-15 06:53:41 +00:00
makabe.t
1451d6f584 Merged PR 835: 本番環境に対する移行データの投入後の修正
## 概要
[Task3580: 本番環境に対する移行データの投入後の修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3580)

- 検証ツールの日時比較について、一度Date型に変換してから比較するように修正しました。
  - 時刻フォーマットで秒が入っていなくても比較できるようにするため

## レビューポイント
- 日時の変換は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- 本番踏み台で確認
- 行った修正がデグレを発生させていないことを確認できるか
  - ツールの変更のみなので影響なし

## 補足
- 相談、参考資料などがあれば
2024-03-15 01:02:33 +00:00
Kentaro Fukunaga
5f4a05044b Merged PR 845: クライアントメッセージ変更(アカウント階層構造変更)
## 概要
[Task3917: クライアントメッセージ変更](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3917)

- メッセージレビューで変更あったため修正して自動生成しました。

## レビューポイント
- 情報共有
2024-03-15 00:53:58 +00:00
saito.k
02a4784e58 Merged PR 836: 翻訳情報の反映
## 概要
[Task3908: 翻訳情報の反映](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3908)

- 翻訳情報の反映
 - 修正したのは、licenseNotAssignedErrorのピリオドの後にスペースを入れた部分です。

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認、develop環境で確認など
- リテラルの修正だけのためほか機能への影響はなし

## 補足
- 相談、参考資料などがあれば
2024-03-15 00:15:01 +00:00
maruyama.t
9256566f89 Merged PR 839: DEV動作確認のバグ対応
## 概要
[Task3918: DEV動作確認のバグ対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3918)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-03-14 15:03:17 +00:00
makabe
9b61443b2f Merge branch 'develop' into ccb 2024-03-14 11:08:35 +09:00
makabe.t
5169092892 Merged PR 833: 本番環境に対する移行データの投入後の修正
## 概要
[Task3580: 本番環境に対する移行データの投入後の修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3580)

- デモライセンスの判定を日時から日付だけ見るように修正しました。
  - 9999/12/31で始まるかを見ています。
- メールアドレス重複のチェックは大文字小文字を区別せずに実行するようにしています。

## レビューポイント
- 対応箇所は適切でしょうか?
- 対応として先頭文字列を見ていますが適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- 本番踏み台で変換できることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - ツールの変更だけなので問題なし
2024-03-14 01:46:57 +00:00
makabe
017276c94a Merge branch 'develop' into ccb 2024-03-14 09:10:06 +09:00
makabe.t
e96e8ea54a Merged PR 832: 本番環境に対する移行データの投入後の修正
## 概要
[Task3580: 本番環境に対する移行データの投入後の修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3580)

- メールアドレスの重複チェックについて、大文字小文字を区別せずに実行するように変換ツールを修正しました。

## レビューポイント
- メールアドレスチェックの対応箇所は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- 本番踏み台で確認
- 行った修正がデグレを発生させていないことを確認できるか
  - ツールのみの変更なので影響なし
2024-03-14 00:08:37 +00:00
maruyama.t
2b68a9f054 Merged PR 824: AzureFunctions実装3(CSVをストレージアカウントに配置する)
## 概要
[Task3846: AzureFunctions実装3(CSVをストレージアカウントに配置する)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3846)

outputDataを追加
→outputAnalysisLicensesDataに変更(アラートルールなどでログを見るので、何の処理か理解できるように)
blobstorageService.tsに以下を追加
 - uploadFileAnalysisLicensesCSV
(ライセンスCSVを配置する)
- createContainerAnalysisを追加
(コンテナーを作成する)
環境変数の追加

## レビューポイント
- 今回追加されたJP-EASTのストレージアカウントのコンテナーが、第一階層のアカウントのものであるかどうかはソース上は特に意識していないが問題ないでしょうか。

## 動作確認状況
- ローカルで確認(モックでソース上処理が通ることのみ確認のみ)
詳細なテストは別タスクで行う。

## 補足
- 相談、参考資料などがあれば
2024-03-13 07:54:10 +00:00
Kentaro Fukunaga
83e297cc9b Merged PR 821: 画面実装(パートナーライセンス一覧画面&階層構造変更ポップアップ)
## 概要
[Task3854: 画面実装(パートナーライセンス一覧画面&階層構造変更ポップアップ)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3854)

- パートナーライセンス一覧に「Change Owner」ボタンを配置し、表示制御およびクリック時にポップアップ表示する処理の実装
- アカウント階層構造変更ポップアップの処理全体実装
- サーバー側のエラーコード定義

## レビューポイント
- 「一括」を表現するためのドロップダウンの構築や処理周りで改善点ないか(to:斎藤くん)
- コンポーネントでの状態管理でお作法に違反しているところないか(to:斎藤くん)
- 修正箇所がほかの機能に影響していないか
    - パートナーライセンス一覧の画面表示に何らか悪影響ないか?(to:ガンさん)

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3854?csf=1&web=1&e=jBGQrR

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認しました
    - 第一階層でログインしてかつ第三または第四視点での一覧を確認しているときにChangeOwnerボタンが表示される
    - ボタン押下すると、仕様通りにポップアップの表示が行われる
    - ポップアップにて入力項目に入力できる&バリデーション効いている
    - ポップアップにて実行ボタン押下するとAPI実行できる&処理結果に応じて仕様通りの挙動をすること
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - パートナーライセンス画面に新規ボタンを配置した&新規ポップアップの実装のみのため、
ポップアップでの処理が正常終了/失敗/何もせず閉じた場合に元の画面の表示が今まで通り動くことを確認済み。
2024-03-13 07:41:25 +00:00
makabe.t
9f5ccabb0c Merged PR 830: アカウント削除修正
## 概要
[Task3896: アカウント削除修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3896)

- アカウント削除を実行すると失敗するので、対応を実施しました。
  - `AccountsRepositoryModule`のインポートに`AccountArchive`を追加しました。

## レビューポイント
- 対応内容は適切でしょうか?

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認
  - npm run test
  - 削除実行
- 行った修正がデグレを発生させていないことを確認できるか
  - リポジトリのインポートへの追加のみなので対象の処理ができることとtestが通ることで確認
2024-03-13 02:01:28 +00:00
maruyama.t
415fd2eb58 Merged PR 825: AzureFunctions実装2(取得したデータをCSV用に変換する)
## 概要
[Task3844: AzureFunctions実装2(取得したデータをCSV用に変換する)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3844)

transferData()の実装
ccbの最新よりマージした状態での引数に変更
各ライセンス数をCSV配列に出力できるところまでを実装
(実際にローカルにCSV出力して中身を確認済み)

![image.png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/825/attachments/image.png)
※UIの変更ではない為、ここにそのまま張り付けさせていただきます。

## レビューポイント
- 関数化の範囲は適切か。
テストコードは最低限の記述になるが、問題ないか
(33行*7項目の突合せをコード上に実装するのは時間的余裕がないためやってない)
詳細な動作確認は、別タスク
https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_sprints/taskboard/OMDSDictation%20%E3%83%81%E3%83%BC%E3%83%A0/OMDSDictation/%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%2029-2?workitem=3861
で行います。
## 補足
- 相談、参考資料などがあれば
2024-03-12 05:43:49 +00:00
masaaki
340aa73bde Merged PR 827: CSV出力が失敗したときに手動で起動するためのFunctionsを作成
## 概要
[Task3860: CSV出力が失敗したときに手動で起動するためのFunctionsを作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3860)

- analysisLicensesについて手動で起動できる処理(analysisLicensesManualRetry)を追加しました
- データベース接続の初期化処理について共通化しました

## レビューポイント
- 特筆する部分はありません

## UIの変更
- 無し

## クエリの変更
- 無し

## 動作確認状況
- ローカルでpostmanからリクエストを行うことで起動できることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - unit testが通ることを確認
    - タイマを暫定的に1分にして、ローカル環境で各タイマ処理が正常終了することを確認(DBアクセスが全処理行われることを確認)

## 補足
- 相談、参考資料などがあれば
2024-03-12 04:55:04 +00:00
湯本 開
43561f237e Merged PR 822: クエリ比較用ログ出力の仕組みを改良
## 概要
[Task3889: クエリ比較用ログ出力の仕組みを改良](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3889)

- SQLクエリを比較可能とするための仕組みを導入
  - UUIDや日付等の実行の度に変更される要素を出力段階で置き換えてしまうロガーを追加
  - テストで上記ロガーを使うよう修正
- テストで使用する環境変数が必要がないのにlocalを指定するようになっていたため、production想定でテストが実施されるようテスト用環境変数ファイルを変更

## レビューポイント
- この仕組みを使ってデグレを防ぐための「クエリの変更内容を確認する」事が問題なく出来そうか

## UIの変更
- なし

## クエリの変更
- なし

## 動作確認状況
- npm run test が正常に実施されることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - testファイルにしか参照されない変更なので、デグレは原理的に発生しないはず
  - testも正常に成功したことを確認したので、デグレは発生していないはず
2024-03-12 03:57:29 +00:00
SAITO-PC-3\saito.k
311eb98236 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
#	dictation_server/src/app.module.ts
2024-03-12 11:48:25 +09:00
SAITO-PC-3\saito.k
8b2613b9f6 Merge branch 'develop' 2024-03-11 19:37:37 +09:00
saito.k
071bd2b85e Merged PR 828: ヘルプページのリンクを差し替える+ヘルプページへの遷移リンクを一つにする
## 概要
[Task3887: ヘルプページのリンクを差し替える+ヘルプページへの遷移リンクを一つにする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3887)

- ヘルプページのリンクを修正
- リンクを一つにまとめる
- リンクの翻訳情報を修正

## レビューポイント
- 修正に漏れはないか

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3887?csf=1&web=1&e=NtVNCO

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - リンクの張替えと翻訳情報の変更のみで他機能に影響はない

## 補足
- 相談、参考資料などがあれば
2024-03-11 10:18:25 +00:00
saito.k
1ef696efe8 Merged PR 814: リンク差し替える(ヘルプページ以外)
## 概要
[Task3874: リンク差し替える(3/8Staging・本番デプロイ分)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3874)

- 以下のリンクを本物に差し替え
  - 利用規約
    - EULA
    - DPA
    - Privacy Notice
  - デスクトップアプリダウンロード

## レビューポイント
- 差し替える対象に漏れはないか

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3874?csf=1&web=1&e=eQE4hq

## クエリの変更
- なし

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - リンクの差し替えのみなのでほか機能に影響はない

## 補足
- ヘルプページのリンクはまだ受領していないのでほかタスクで対応
2024-03-11 07:54:35 +00:00
saito.k
c059a2eabd Merged PR 815: XMLHttpRequestにてリクエストヘッダー「X-Requested-With: XMLHttpRequest」をコメントアウトしていればコメントアウトはずす
## 概要
[Task1806: XMLHttpRequestにてリクエストヘッダー「X-Requested-With: XMLHttpRequest」をコメントアウトしていればコメントアウトはずす](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1806)

- 以下の開発規約に準拠するように修正
  - 可能な限り、JSONの場合(JSONPではない場合)は、XMLHttpRequestにてリクエストヘッダー「X-Requested-With: XMLHttpRequest」を設定し、サーバー側でチェックすること。

- Client
  - APIリクエスト時に、ヘッダーに「X-Requested-With: XMLHttpRequest」を付ける

- server
  - ヘッダーをチェックするミドルウェアを実装
    - /healthは画面からのリクエストではないので除外している
  - ミドルウェアをローカル環境以外で使用するように実装
    - ローカル環境ではサーバーから静的ファイルを配信しているから
       - APIリクエスト以外のリクエストにもmiddlewareが適用されてしまうのでローカル環境は除外している

## レビューポイント
- この修正で開発規約に準拠しているといえるか
- ローカル環境は除外したが、問題ないか
  - ローカルとdev,stg,prodで差異があることで、ローカルだけ発生しない問題が生じる可能性がある(その逆も)
  - 基本的に特定のヘッダーがあるかというチェックを追加しただけなので、大きな問題が発生するとは考えづらい

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認
- 行った修正がデグレを発生させていないことを確認できるか
  - 事前にdev環境でAPI呼び出しができることを確認
  - すべてのAPIの呼び出しを確認したわけではないが、ログイン等の基本的な操作はできることを確認した

## 補足
- 相談、参考資料などがあれば
2024-03-11 07:26:01 +00:00
masaaki
84fc89071a Merged PR 811: AzureFunctions実装1(DBから必要な情報を取得する)
## 概要
[Task3842: AzureFunctions実装1(DBから必要な情報を取得する)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3842)

- ライセンス数推移情報CSV出力機能のAzureFuntion用関数を追加しました
- DBから必要な情報を取得する処理を実装しました

## レビューポイント
- 特にレビューしてほしい箇所
  - DBアクセス時の結合・検索条件はラフスケッチの条件に対して過不足ないか
  - テストケースに不足はないか
- 関数名、構造は分かりづらくないか

## UIの変更
- 無し

## クエリの変更
- 新規のため無し

## 動作確認状況
- ローカルで処理が正常終了することを確認
- ユニットテストが通ることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - Function全体のunittestを実施し通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-03-11 06:04:02 +00:00
makabe.t
ccc03da62d Merged PR 823: 環境変数の追従
## 概要
[Task3891: 環境変数の追従](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3891)

- `server`に追加した環境変数の追従が`.env.local.example`から漏れていたので追加

## レビューポイント
- 共有

## UIの変更
- なし

## クエリの変更
- なし
## 動作確認状況
- ローカルで確認
  - exampleを追加したのみなので動作に影響はなし
2024-03-11 02:52:55 +00:00
湯本 開
ff4cd35ed3 Merged PR 819: [3848]アカウント削除処理修正
## 概要
[Task3847: [3848]アカウント削除処理修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3847)

- AccountArchiveエンティティを追加
- アカウント削除時、Accountをアーカイブする処理を追加
- アーカイブ関連テストを追加

## レビューポイント
- 実装の修正内容は問題なさそうか
- テストケースの修正内容は問題なさそうか
- クエリの変更内容の確認方法は問題なさそうか to 斎藤さん

## レビュー対象外
- テスト用ロガーに以下の比較用前処理を追加するべきだが、別タスクを作って対応予定
- 下記クエリの変更点にて、CommentOut判定に環境変数STAGEを使用している部分にRequestIdが表示されていない部分が存在するが、テスト用環境変数の変更は上記と同じく別タスクを作って対応予定
[タスク 3889: クエリ比較用ログ出力の仕組みを改良](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_workitems/edit/3889)

## クエリの変更
- ロガーを有効にした状態でテストを実行し、ログのUUIDと日付を処理して比較できるよう加工した
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%AF%E3%82%A8%E3%83%AA/3847?csf=1&web=1&e=xlK011

## 動作確認状況
- npm run testで確認
- 行った修正がデグレを発生させていないことを確認できるか
  - アカウント削除テストで発行されるクエリを比較し、AccountArchiveする対象を特定するためのAccountのSELECT、AccountArchiveのINSERTとSELECTのみが追加されている事が確認できたので、デグレはないと判断
2024-03-11 02:08:29 +00:00
Kentaro Fukunaga
f386a8f7e0 Merged PR 817: API IF実装(親アカウント変更API)
## 概要
[Task3852: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3852)

- 親アカウント変更APIのIFを実装し、OpenAPIの生成もしました。
- 影響範囲(他の機能にも影響があるか)
    - なし

## レビューポイント
- controllerのメソッド名にほか良い案ないか?
- validationに過不足や間違いないか?
- ~~controllerのテストは正常系ひとつだけ追加しているが、他にあったほうがいいものあるか?~~
    - ~~個人的には、テスト追加してもnpmライブラリのvalidatorのテストになるだけな気がするため不要では?と思っています。~~
    - 「npmライブラリのvalidatorを正しいパラメータで正しく利用しているか」が目的であるとの認識を得たため異常系も追加しました。

## 動作確認状況
- apigenを実行してOpenAPI生成できることを確認、controllerテスト通ることを確認。
- 行った修正がデグレを発生させていないことを確認できるか
  - 新規APIのため無し
2024-03-11 01:29:55 +00:00
makabe.t
2e6b7c8ab5 Merged PR 818: function修正
## 概要
[Task3879: function修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3879)

- 行番号をExcelの表記通りとなるように修正
- 途中から始められるようにforループを修正

## レビューポイント
- 共有

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-03-08 05:45:53 +00:00
Kentaro Fukunaga
85edd2296e Merged PR 820: DBマイグレーションファイル追加(アカウント階層構造変更PBI)
## 概要
[Task3863: DBマイグレーションファイル追加](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3863)

- licenseOrdersテーブルに、from_account_idとstatus検索のマルチカラムインデックスを追加しました。
- 影響範囲(他の機能にも影響があるか)
    - なし

## レビューポイント
- 気になる点あれば

## 動作確認状況
- ローカルでmigrate:up/downをして、想定通りindex作成/削除されることを確認
- 行った修正がデグレを発生させていないことを確認できるか
  - インデックス追加のみのため無し
2024-03-08 05:21:22 +00:00
湯本 開
869cbd43e0 Merged PR 810: アカウント退避テーブル作成
## 概要
[Task3848: アカウント退避テーブル作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3848)

- アカウントテーブルと同様の構造をしたアカウント退避テーブルを作成
  - ※差分:  company_nameを削除、active_worktype_idの外部キー制約を削除

## レビューポイント
- 上記要素以外はaccountsテーブルの要素と同等であるか
- 張られたインデックスに不足はないか
- 想定と違う構造になっていないか

## 動作確認状況
- npm run migrate:up/downを実施
- EXPLAINでindexが機能していそうな事を確認
```
EXPLAIN SELECT * FROM omds_ccb.accounts_archive where parent_account_id=1;
---------------------------------------------------
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
'1', 'SIMPLE', 'accounts_archive', NULL, 'ref', 'idx_accounts_archive_parent_account_id', 'idx_accounts_archive_parent_account_id', '9', 'const', '1', '100.00', NULL
```

```
EXPLAIN SELECT * FROM omds_ccb.accounts_archive where tier=2;
---------------------------------------------------
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
'1', 'SIMPLE', 'accounts_archive', NULL, 'ref', 'idx_accounts_archive_tier', 'idx_accounts_archive_tier', '4', 'const', '2', '100.00', NULL
```
```
EXPLAIN SELECT * FROM omds_ccb.accounts_archive where parent_account_id=1 AND tier=1;
----------------------------------------------------
# id, select_type, table, partitions, type, possible_keys, key, key_len, ref, rows, filtered, Extra
'1', 'SIMPLE', 'accounts_archive', NULL, 'ref', 'idx_accounts_archive_parent_account_id,idx_accounts_archive_tier', 'idx_accounts_archive_parent_account_id', '9', 'const', '1', '50.00', 'Using where'
```
2024-03-08 05:20:00 +00:00
saito.k
aca9bcf496 Merged PR 782: 翻訳反映+α
## 概要
[Task3781: 翻訳反映+α](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3781)

- 翻訳情報の反映
- タスクのチェックアウト時(/tasks/{audioFileId}/checkout)にライセンスをチェックするようになったため、それに対応するエラーを追加
  - ライセンスが未割当の時とライセンスが有効期限切れの時にそれぞれ専用のエラーメッセージを表示するように修正する

## レビューポイント
- エラーのハンドリングに誤りはないか

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3781?csf=1&web=1&e=dCIGih

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-03-08 05:11:20 +00:00
makabe.t
146b8a6e40 Merged PR 816: function修正
## 概要
[Task3879: function修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3879)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-03-08 01:09:14 +00:00
SAITO-PC-3\saito.k
9dc80b4965 Merge branch 'develop' into ccb 2024-03-07 20:55:44 +09:00
makabe.t
04c726e964 Merged PR 808: function修正
## 概要
[Task3879: function修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3879)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載
- 修正箇所がほかの機能に影響していないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など
- 行った修正がデグレを発生させていないことを確認できるか
  - 具体的にどのような確認をしたか
    - どのケースに対してどのような手段でデグレがないことを担保しているか

## 補足
- 相談、参考資料などがあれば
2024-03-07 11:47:53 +00:00
maruyama.t
f265d0ff45 Merged PR 812: 「oderLicense」や「CardLicense」を開いて閉じるとパンくずリストがおかしくなる
## 概要
[Task3771: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3771)

ポップアップを×で閉じた際に、パンくずリストを保持したまま、`dispatch(getMyAccountAsync()`しており画面を初期化していた。
画面を更新するのかしないのかあいまいな挙動になっていた。

対応としては、他のポップアップと同様、×で閉じた場合は画面は更新しないように修正。
追加でパンくずリストを初期化する処理を作成し、ポップアップ側でAPIを呼び出した場合のみそれを実行する。

## レビューポイント
- 特筆する点はなし

## UIの変更
- スクショ置き場
https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3771?csf=1&web=1&e=USf40m

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-03-07 10:41:30 +00:00
saito.k
e448e8d249 Merged PR 806: メール文面の修正反映
## 概要
[Task3792: メール文面の修正反映](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3792)

- メール文面を修正
  - ドイツ語の部分

## レビューポイント
- タスクに添付しているエクセルの内容と修正した内容を照らし合わせて確認していただきたいです。

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## クエリの変更
- Repositoryを変更し、クエリが変更された場合は変更内容を確認する
- Before/Afterのクエリ
- クエリ置き場

## 動作確認状況
- ローカル環境でメールを確認
- メール文面のみの修正のため、他機能に影響はない

## 補足
- 相談、参考資料などがあれば
2024-03-07 02:26:15 +00:00
SAITO-PC-3\saito.k
eda88aa048 Merge branch 'develop' into ccb 2024-03-06 11:23:50 +09:00
makabe.t
da40e8f09c Merged PR 800: 画面実装(一括追加ボタン&ポップアップ画面)
## 概要
[Task3753: 画面実装(一括追加ボタン&ポップアップ画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3753)

- ユーザー一括登録画面を実装しました。
  - 一括登録ポップアップ
    - テンプレートCSVダウンロード
    - ファイルインポート
      - エラー行表示

## レビューポイント
- 行エラーの条件、内容は認識通りでしょうか?
- 画面の表示内容は認識通りでしょうか?
- CSV変換時にworkerを有効にしているとエラーとなりうまくいかないのでOFFにしてしまいましたが問題ないでしょうか?
  - @<湯本 開> さん

## UIの変更
- [Task3753](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3753?csf=1&web=1&e=x5M6hr)

## 動作確認状況
- ローカルで確認
 -  ファイルチェックするところまで
2024-03-06 01:58:32 +00:00
makabe.t
d6a47932e7 Merged PR 786: Azure Functions実装(一括登録)
## 概要
[Task3756: Azure Functions実装(一括登録)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3756)

- ユーザー一括登録用のAzure Functionを実装しました。

## レビューポイント
- 処理の流れがラフスケッチと認識通りでしょうか?
- JSONファイルの内容はイメージ通りでしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
  - テスト実行

実際の詳細な動作についてはdevelop環境で確認します。
2024-03-06 01:48:02 +00:00
saito.k
7160e0ee2e Merged PR 804: デグレ再発防止のため、修正をチェックするテストを作成
## 概要
[Task3830: デグレ再発防止のため、修正をチェックするテストを作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3830)

- タスクを100件取得できることを確認するテストを追加

## レビューポイント
- テストでかくにんする項目は足りているか

## UIの変更
- 特になし

## クエリの変更
- 特になし

## 動作確認状況
- ローカルでテストが通ることを確認

## 補足
- 相談、参考資料などがあれば
2024-03-06 01:31:10 +00:00
masaaki
2220e2560f Merged PR 799: makepasswordで条件に合致しないパスワードを生成した際無限ループになる
## 概要
[Task3840: makepasswordで条件に合致しないパスワードを生成した際無限ループになる](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3840)

- makepasswordで条件に合致しないパスワードを生成した場合、再度生成するループ処理としているが、初期化が行われていないため常に同じパスワードで条件合致のチェックが行われていました。結果、一度条件に合致しないパスワードを生成した場合無限ループとなっていました。
- ループ内で変数を初期化するよう対応。

## レビューポイント
- 特にありません

## UIの変更
- 無し

## 動作確認状況
- ユニットテストが通ることを確認
- ローカルでユーザー作成を実施し、これまで同様作成できることを確認

## 補足
- 相談、参考資料などがあれば
2024-03-06 01:19:23 +00:00
makabe.t
31de71f743 Merged PR 797: API実装(一括登録)
## 概要
[Task3752: API実装(一括登録)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3752)

- ユーザー一括登録APIとテストを実装しました。
  - メール文面はまだ翻訳が来ていないので日本語のものを使用しています。別タスクで多言語対応します。

## レビューポイント
- ファイル名は認識通りでしょうか?
- ファイルの内容は認識通りでしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-03-05 10:27:50 +00:00
makabe.t
7ff563f644 Merged PR 795: API実装(一括登録完了)
## 概要
[Task3763: API実装(一括登録完了)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3763)

- 一括登録完了APIとテストを実装しました。
  - メール文面は多言語対応がまだですのですべて日本語の文面にしています。

## レビューポイント
- 送信メールの内容は認識通りでしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-03-04 09:15:53 +00:00
masaaki
a47ebaa9df Merged PR 798: [4回目実行][フルデータ]develop環境での移行実施後の修正作業
## 概要
[Task3821: [4回目実行][フルデータ]develop環境での移行実施後の修正作業](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3821)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 移行ツールに対して以下の修正を実施しました。
  - アカウントとユーザ間でAuthorIDが重複する際、通番を付与して重複を避けるようにしました
  - AADB2Cのエラー発生時、リトライ処理を行うように対応しました

## レビューポイント
- 特にありません

## UIの変更
- 無し

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-03-02 02:21:19 +00:00
masaaki
88ce6a2c9e Merged PR 796: [3回目実行][フルデータ]develop環境での移行実施後の修正作業
## 概要
[Task3802: [3回目実行][フルデータ]develop環境での移行実施後の修正作業](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3802)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-03-01 12:00:42 +00:00
maruyama.t
cad3a99f70 Merged PR 794: 登録ツールにログを仕込む
## 概要
[Task3839: 登録ツールにログを仕込む](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3839)

登録ツールが途中で動かなくなってしまう原因調査のために各関数にログを仕込みました。

## レビューポイント
- 特になし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-29 12:50:17 +00:00
maruyama.t
0ebd2ab17e Merged PR 793: accountに名前がないデータが存在する
## 概要
[Task3831: accountに名前がないデータが存在する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3831)

移行元データのアカウントユーザーにfirtst_nameおよびlast_nameが存在しない行が存在しており、
変換ツール側で対応していなかったためAdminNameが空のアカウントユーザーを作成しようとして登録ツール側でエラーになってしまっていた。
→バックログに起票しOMDSさんに確認中

■暫定対応
first_name\last_nameが存在しない場合はユーザーと同様にメールアドレスをAdminNameとするように修正。

## レビューポイント
- とくになし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-29 06:55:34 +00:00
Kentaro Fukunaga
9ca9b7a144 Merged PR 790: AuthorのNotificationフラグを見てタスク完了メールの送信先を変更するよう修正
## 概要
[Task3818: 対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3818)

- AuthorのNotificationフラグがOFFのときには、Authorに対してタスク完了通知メールが送信されないよう修正しました。

## レビューポイント
- 動作確認項目に不足はないか?

## 動作確認状況
- ローカルで確認しました
    - AuthorのNotificationON時にはメール宛先に入っており、OFF時には宛先から外れること
    - TypistはNotificationON/OFF関わらずメール宛先に入っていること
2024-02-29 06:36:23 +00:00
SAITO-PC-3\saito.k
fd3e584fac Merge branch 'develop' 2024-02-29 10:55:41 +09:00
saito.k
ce6e09a7d0 Merged PR 791: タスク一覧画面の取得件数が10件となっているバグの対応
## 概要
[Task3815: タスク一覧画面の取得件数が10件となっているバグの対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3815)

- OptionItemのソート順を変換処理の中で行うように修正
  - OrderByでソートするのはfindメソッドの作り的に無理そうなので
  - 調査にも時間がかかるため

## レビューポイント
- タスクの中にこのバグが発生した原因を記載し、なぜこの修正にしたのか記述したのでそちらを確認していただいて変なところがあれば指摘していただきたいです。
  - 書いている内容がよくわからない場合は、ハドルでの説明をさせてください。

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-29 01:16:15 +00:00
maruyama.t
6d56255a5a Merged PR 792: parent_account_idが正しく設定されない
## 概要
[Task3804: parent_account_idが正しく設定されない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3804)

Map配列からaccountidをキーにdealerAccountIdを取る処理で、検索keyが逆になっていたため修正。

## レビューポイント
- とくになし

## 動作確認状況
- ローカルで確認(階層を付け替えたアカウントの親子階層が正しいことを確認)

## 補足
- 相談、参考資料などがあれば
2024-02-28 09:04:36 +00:00
湯本 開
363f12f86f Merged PR 774: 画面実装(csv読み込み部分切り出し)
## 概要
[タスク 3754: 画面実装(csv読み込み部分切り出し)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3754)

- CSVファイルの内容を入力として、JSONに変換する処理&テストを実装
- 画面実装時の想定としては、以下の流れと想定しており、本Taskの範囲は1のみ
  1. csv->jsonへパースが出来るか?(csvの形式として合っていて読み込み可能か?)
  2. json内の各パラメータは問題ないか?(データの制限や組み合わせは問題ないか?)
- 使い方を示す & 動作確認のために、client側でもテストを実施できるよう修正(※pipelineでは実行されない)

## レビューポイント
- テストケースを見て、使い方は分かるか
- CSVの形式自体が想定とズレていた場合は入力を弾く必要がある想定だが、間違っていないか
- 利用ライブラリはメジャーかつ便利そうなものを選定したが、問題なさそうか

## 動作確認状況
- npm run testを通過
2024-02-28 09:03:27 +00:00
masaaki
0be9c26f09 Merged PR 781: データ検証ツール作成+動作確認
## 概要
[Task3573: データ検証ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3573)

- データ検証ツールを作成しました

## レビューポイント
- 特にレビューしてほしい箇所
 詳細情報の突き合わせについて、ラフスケッチと対応しているか第三者目線でも確認してほしいです
 verification.serviceのcompareCardLicenses、compareLicenses、compareAccountsになります。

## UIの変更
- 無し

## 動作確認状況
- ローカルで確認

## 補足
- 無し
2024-02-28 05:31:13 +00:00
湯本 開
71127a6db9 Merged PR 787: API I/F修正
## 概要
[Task3793: API I/F修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3793)

- OMDS様とのメール文面調整の結果、csvファイル名もAPIで受け渡す必要が出てきたためAPI I/Fを修正する
  - 一括登録依頼API、一括登録完了APIの両方に「ファイル名」を追加
- 増えたプロパティのバリデーションをするテストを追加

## レビューポイント
- プロパティ名は妥当か
- テストの内容は十分か

## 動作確認状況
- npm run testを通過
2024-02-28 05:30:09 +00:00
maruyama.t
f6d39a4c26 Merged PR 788: [2回目実行]実施後の動作確認
## 概要
[Task3577: [2回目実行]実施後の動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3577)

accountがCountryの場合に配下のDistributorの親アカウントIDを付け替える処理について、Typeの付け替えができていなかったのを修正。
ライセンスの有効期限が"9999/12/31 23:59:59.997"でフォーマットチェックをしているが、移行元は"9999/12/31 23:59:59"なので移行元に合わせた。
dealerAccountIdが設定されているが、そのdealerが存在しない場合もデータを作ってしまっている。
→該当レコードはエラーファイルを出力する。

## レビューポイント
- エラーファイルの出力処理だが簡素すぎるか?
JSONで出力する意味はないが、これまでの動作確認で動作担保できているのでJSONで出しています。

## 動作確認状況
- ローカルで確認
正常の場合データ変換が行われることを確認。
dealerAccountIdが設定されているが、そのdealerが存在しない場合もデータでテストした場合、error.jsonが作られることを確認。
## 補足
- 相談、参考資料などがあれば
2024-02-27 23:55:44 +00:00
SAITO-PC-3\saito.k
34d1dd5629 Merge branch 'develop' 2024-02-27 18:53:10 +09:00
maruyama.t
f0d71937e3 Merged PR 780: データ変換ツール(汚いデータ対応版)の作成+動作確認
## 概要
[Task3776: データ変換ツール(汚いデータ対応版)の作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3776)

綺麗なデータ対応版のレビュー指摘も合わせて修正。
一旦OMDS様よりいただいた1万件~のデータを処理できることは確認済みです。
実装コストとバグの入れ込みを懸念し、有効期限が"9999/12/31"のデータは最初にデータを積む段階で除外するようにしました。

## レビューポイント
- メールアドレス重複チェックについて、想定通りの重複対象を検索出来ているか。
- step3の1.アカウントとユーザが同じ場合
adminMainとuserEmailが重複していた場合に、重複していたユーザーは削除し、アカウントのみを残す(accountユーザーのroleとauthorIdは削除したuserに設定されていたものとする)処理は妥当か。
→accountのIFにroleとauthorIdを追加し、register側のcreateAccountで登録するようにしています。

## 動作確認状況
- ローカルで確認(Account_transition_2024.1.19.csvで実施)
4つのJSONファイルができていることを確認。
Countryの場合の付け替えができていることを確認。
adminMainとemailが重複している場合の重複削除ができていることを確認。

## 補足
- 登録ツールと共通のパラメータで動作するようにしました。
例)
POST:
localhost:8280/transfer
Body:
{
    "inputFilePath": "./data/"
}

 変換ツールの使い方としてはAccount_transition.jsonというファイルを見るようにしています。
2024-02-27 06:24:41 +00:00
Kentaro Fukunaga
dd8bddc971 Merged PR 771: 音声ファイルアップロード完了API実装(ストレージ使用量超過チェック)
## 概要
[Task3687: 音声ファイルアップロード完了API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3687)

- 音声ファイルアップロード完了API実行時に、ストレージの使用量チェックを行い、必要ならメール送信をする実装を追加しました。

## レビューポイント
- 使用量チェックメソッドで他にいい関数名ないか?
- なるべく既存実装をいじりたくなかったので自動ルーティング前にチェック機構を配置したが不都合ないか?
- テストケースに過不足ないか
- 自動テストの実行方法や確認方法として適切か?ほかに代替案ないか?

## 動作確認状況
- ローカルでUT通ることを確認。
   - 実際のメール送信はdeveop動作確認でやります。
2024-02-27 02:49:52 +00:00
SAITO-PC-3\saito.k
ddd4d31f25 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_server/src/features/users/users.service.spec.ts
2024-02-27 09:26:46 +09:00
Kentaro Fukunaga
5305984b1a Merged PR 764: 第五階層ライセンス情報画面実装
## 概要
[Task3709: 第五階層ライセンス情報画面実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3709)

- ストレージ使用可否切り替えの画面実装をしました
- 動作確認中に、既存実装でライセンスオーダーするときとカードライセンスアクティベートするときの操作不能化処理に漏れがあったのを修正しました

## レビューポイント
- Redux周りの実装でお作法に違反しているところがないか。もしくは改善点ないか。
- ライセンス情報表示のAPI結果待ち部分のローディング処理で、最低限の改善にしたが現時点ではこれでよいか?(いつ修正するかも未定だけど、実害はないためひとまずこんな感じで。。。)
    - `licenseSummarySlice.ts` のコメント部分が該当箇所です

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3709?csf=1&web=1&e=bJVzss

## 動作確認状況
- ローカルで動作確認しました。
2024-02-27 00:01:02 +00:00
saito.k
0ab6488f58 Merged PR 779: タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる
## 概要
[Task3787: タスク一覧画面のOptionItemがソート条件によって表示順がおかしくなる](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3787)

- タスク一覧取得APIレスポンスにあるOptionItemの順番を固定する(idの昇順)
- テスト修正

## レビューポイント
- 特になし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-26 11:48:02 +00:00
saito.k
5a78a6668f Merged PR 769: /users/relationsレスポンスのWorkTypeIDをID名の昇順にする
## 概要
[Task3783: /users/relationsレスポンスのWorkTypeIDをID名の昇順にする](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3783)

- relationsAPIレスポンスのWorkTypeList内の順番を指定する
- テスト修正

## レビューポイント
- 修正内容に不足はないか

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-26 11:29:22 +00:00
saito.k
d0628caa05 Merged PR 767: パートナー追加成功時に一覧の更新が行われていない
## 概要
[Task3769: パートナー追加成功時に一覧の更新が行われていない](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3769)

- パートナー追加成功時に一覧の更新を行う

## レビューポイント
- 特になし

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3769?csf=1&web=1&e=ajJOBd

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-26 10:42:26 +00:00
masaaki
68d1a1796b Merged PR 783: [1回目実行]実施後の修正実施
## 概要
[Task3790: [1回目実行]実施後の修正実施](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3790)

- 移行データ動作確認(1回目)で発生した不具合の対応を実施しました
 ・バリデータでエラーとなる(コメントアウトして実行したところ、成功)
 ・アカウント管理者のユーザーのメール認証がfalseで登録されるので、
  パスワード変更では認証をできない⇒強制敵にtrueで登録する
 ・(指摘外、検証ツール実装時に内部検出)カードライセンス登録時、カードライセンス発行・ライセンステーブルも登録する

- このPull Requestでの対象/対象外
 上記以外の指摘(下記)はタスク3772にて対応するため本プルリク対象外

 変換ツール
  ・ディーラーアカウントに登録されたアカウントがいないCSVで
   変換するとディーラーがundefined(パラメータがない)状態でJSON出力されてしまう。
  ・重複したメールアドレスの取り込みが未実装
  ・有効期限が9999/~は移行対象外とする
  ・ワークタイプの出力がされない

 登録ツール
  ・ファイルパスの取り扱いが変換ツールと異なる
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- カードライセンス登録時のライセンスのアカウントIDについて、第一階層アカウントのため「AUTO_INCREMENT_START: 853211」を設定しているが問題ないか?
 →移行データ上第一階層アカウントは最初に登場するため問題ない認識

## UIの変更
- 無し

## 動作確認状況
- ローカルで確認済

## 補足
- 相談、参考資料などがあれば
2024-02-26 08:59:37 +00:00
湯本 開
c95fb1e1f6 Merged PR 784: テスト失敗修正(I/F実装)
## 概要
[Task3791: テスト失敗修正(I/F実装)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3791)

- AuthorIDのチェックを行うバリデータが特定リスクエストに対してしか利用できなかったので、もっと単純な同一ロジックのバリデータを追加して利用するよう変更
- 上記ケースに対するテストを追加

## レビューポイント
- 既存実装へは影響がなさそうか

## 動作確認状況
- npm run testを通過
2024-02-26 07:48:08 +00:00
湯本 開
c1f370faaf Merged PR 766: API I/F & system権限Token実装
## 概要
[Task3764: API I/F & system権限Token実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3764)

- システムが発行したトークンの型定義を追加
- AuthGuardと同等の、システムが発行したTokenである `SystemAccessToken` を検証する `SystemAccessGuard` を追加
- API I/Fを実装

## レビューポイント
- バリデーターは適切か
- システムが発行したトークンの型定義は適切か
- API I/Fの型は問題ないか

## 動作確認状況
- ローカルでswagger UI上で確認
2024-02-26 05:13:43 +00:00
SAITO-PC-3\saito.k
13d421c2bc Merge branch 'develop' into ccb 2024-02-23 11:10:38 +09:00
saito.k
b524fd5995 Merged PR 768: U-105メッセージのドイツ語部分が英語になっている
## 概要
[Task3770: U-105メッセージのドイツ語部分が英語になっている](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3770)

- U-105のメール文面を修正
 - ドイツ語の文章であるべき箇所が英語になっていた

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-23 00:42:05 +00:00
makabe
a1b59de44d Merge branch 'develop' into ccb 2024-02-22 20:43:35 +09:00
makabe.t
fbdfeee73c Merged PR 778: 削除ツールの修正
## 概要
[Task3788: 削除ツールの修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3788)

- ADB2Cの削除に失敗するので対応しました。
  - 最後のページの場合に削除より先に抜けていたので、削除処理を先にやるように修正しました。

## レビューポイント
- 共有

## UIの変更
- なし

## 動作確認状況
- developで確認
2024-02-22 11:42:31 +00:00
makabe.t
f03342bc55 Merged PR 777: データ削除ツール作成+動作確認
## 概要
[Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569)

- クライアントに不要なフォルダが残っていたので削除しました。

## レビューポイント
- 共有

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-02-22 08:45:20 +00:00
maruyama.t
a65d6a2774 Merged PR 776: 階層の付け替えを誤っていたのを修正
## 概要
[Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-22 08:40:57 +00:00
maruyama.t
cb68c16eb8 Merged PR 775: 変換ツールのバリデーションチェックを修正
## 概要
[Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-22 08:23:31 +00:00
maruyama.t
c31bb47bb8 Merged PR 773: apiにtransferが存在しなかったのを修正
## 概要
[Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-22 07:52:16 +00:00
makabe.t
dc52ec2022 Merged PR 765: データ削除ツール作成+動作確認
## 概要
[Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569)

- ADB2Cからのユーザー削除が100件ごとにしか削除できていなかったので、修正しました。
  - 取得が100件まででそのユーザーに対して削除処理をしていたので100件までの削除になっていました。
  - 対応として、100件づつの削除をユーザーが全削除されるまで実行するようにしました。

## レビューポイント
- 対応方法として適切でしょうか?
- ループで制限を設けていますが、MAX値として適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで順に実行できることを確認
- 実際の削除は別途develop環境で実施します。
2024-02-22 07:33:55 +00:00
SAITO-PC-3\saito.k
bc87bcd5cf Merge branch 'develop' 2024-02-22 15:45:00 +09:00
maruyama.t
e3ee9412c9 Merged PR 772: フォルダリネーム
## 概要
[Task3570: データ変換ツール(きれいなデータ版)作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570)

- 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず)
- 何をどう変更したか、追加したライブラリなど
- このPull Requestでの対象/対象外
- 影響範囲(他の機能にも影響があるか)

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-22 06:40:52 +00:00
maruyama.t
8110b9cccc Merged PR 753: データ変換ツール作成+動作確認
## 概要
[Task3570: データ変換ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3570)

## レビューポイント
- 特にtransfer配下の処理を見ていただきたいです。
データのバリデーションは十分か。
アカウントIDとユーザーIDのインクリメント(採番)の場所は正しいか。
ユーザー名は正しく設定できているか。
worktypeの重複を除外する処理は正しいか。
COUNTRY_LIST(既存のクライアントから流用)した変換処理には問題ないか。
出力ファイル名は登録ツール側の想定通りか。

## 動作確認状況

動作確認は、以下で行う。
[タスク 3575: [1回目実行]実施後の動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/OMDSDictation/_sprints/taskboard/OMDSDictation%20%E3%83%81%E3%83%BC%E3%83%A0/OMDSDictation/%E3%82%B9%E3%83%97%E3%83%AA%E3%83%B3%E3%83%88%2028-2?workitem=3575)

## 補足
- 相談、参考資料などがあれば
2024-02-22 05:04:48 +00:00
saito.k
ebbf957419 Merged PR 770: AuthorIDを大文字とアンダースコアのみとするバリデーションを入れたことによるデグレの解消
## 概要
[Task3784: AuthorIDを大文字とアンダースコアのみとするバリデーションを入れたことによるデグレの解消](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3784)

- タイトルの通り
  - ロールがAuthorの時のみチェックするように修正

## レビューポイント
- 特になし

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認、develop環境で確認など

## 補足
- 相談、参考資料などがあれば
2024-02-22 02:38:05 +00:00
masaaki
12d168d14c Merged PR 754: データ登録ツール作成+動作確認
## 概要
[Task3571: データ登録ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3571)

- 移行データの登録ツールを作成しました
  - 入力用jsonファイルの読み込み
  - アカウント・ユーザの登録
    - 既存サービスを移植・微修正し呼び出し
    - rate_limit用のsleep実施
  - ワークタイプ・ライセンス・カードライセンスの登録
- 実行についてはpostmanでの実行を考えており、clientは作成しておりません

## レビューポイント
- 既存サービスからの流用が多いですが、メインの処理はfeatures/registerになるため、こちらをメインに見ていただければと思います。

## UIの変更
- 無し

## 動作確認状況
- ローカルで動作確認

## 補足
- 相談、参考資料などがあれば
2024-02-21 01:41:21 +00:00
makabe
439ce7de63 Merge branch 'develop' into ccb 2024-02-21 09:16:05 +09:00
makabe.t
5adf7ed12e Merged PR 763: ツールをexeとしてビルドできるビルドコマンドを整備する
## 概要
[Task3742: ツールをexeとしてビルドできるビルドコマンドを整備する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3742)

- データ移行ツールフォルダの中に`tool`フォルダを掘って、実行ファイル形式で配置されるようにしました。
  - コンテナ内からはサーバー側の`build:exe`コマンドでビルドできるようにしています。
  - コンテナ外からサーバー/クライアント一括でビルドできるスクリプトを配置しています。
※toolフォルダがあればツールとして実行できる想定です。

## レビューポイント
- exe化のイメージは認識通りでしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルでビルドできることを確認
2024-02-21 00:11:19 +00:00
Kentaro Fukunaga
f8183399e2 Merged PR 762: アカウント利用制限更新API実装
## 概要
[Task3710: アカウント利用制限更新API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3710)

- タイトルの通りです。
- 既存実装でログが不足している箇所あったのでちょろ修正もしました。

## レビューポイント
- これと言ってみて欲しいポイントはないので、何か気になる点あれば

## 動作確認状況
- ローカルで全テストが通ることを確認済み
2024-02-20 11:23:04 +00:00
makabe
e44cb3b955 Merge branch 'develop' into ccb 2024-02-20 19:24:42 +09:00
makabe.t
a9aca6e4ff Merged PR 750: データ削除ツール作成+動作確認
## 概要
[Task3569: データ削除ツール作成+動作確認](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3569)

- データ削除ツールを実装しました。
  - Blobストレージからのコンテナ削除
  - ADB2Cからのユーザー削除
  - DBの全削除
  - Auto Incrementの設定

## レビューポイント
- Blobストレージの削除対象の取得に問題はないでしょうか?
  - 3つのリージョン内のすべてのコンテナを取得してから、取得したコンテナを全削除するようにしています。
- ADB2Cの削除対象の取得に問題はないでしょうか?
  - ローカルアカウントなユーザーのみを取得してから、取得したユーザーを全削除するようにしています。
- フォルダ構成に違和感はないでしょうか?

## UIの変更
- [Task3569](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3569?csf=1&web=1&e=wU1st1)

## 動作確認状況
- ローカルで確認
  - DB操作のみ確認しています。Azureリソースの削除についてはdevelop環境で改めて実施します。
2024-02-20 10:09:05 +00:00
SAITO-PC-3\saito.k
562db3def9 Merge branch 'develop' into ccb 2024-02-19 21:25:54 +09:00
Kentaro Fukunaga
ecb28b9328 Merged PR 759: API IF実装
## 概要
[Task3711: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3711)

- アカウント利用制限更新APIのIFを実装し、openapi生成しました。

## レビューポイント
- メソッド名で他に案あれば。

## 動作確認状況
- 特になし
2024-02-19 04:32:59 +00:00
SAITO-PC-3\saito.k
cdef84e269 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_server/src/features/accounts/accounts.service.spec.ts
2024-02-19 12:00:01 +09:00
makabe.t
fc7d271a29 Merged PR 749: 第五階層ライセンス情報画面実装
## 概要
[Task3654: 第五階層ライセンス情報画面実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3654)

- 第五階層ライセンス情報画面でストレージ情報(Storage Available、Storage Used)を表示するように画面を修正しました。
  - 表示する値はGB単位となるようにし、小数点以下第三位までを表示するようにしています。

## レビューポイント
- 表示する値のフォーマットは適切でしょうか?
  - 小数点以下第三位までを固定で表示するようにしていますが問題ないでしょうか?

## UIの変更
- [Task3654](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3654?csf=1&web=1&e=1tCGoN)

## 動作確認状況
- ローカルで確認
2024-02-16 05:15:45 +00:00
Kentaro Fukunaga
aef30c8cbe Merged PR 748: 第五階層ライセンス情報取得API実装
## 概要
[Task3655: 第五階層ライセンス情報取得API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3655)

- 第五階層ライセンス情報取得APIに、ストレージ上限とストレージ使用量を取得する処理を追加しました。
- 既存の「割り当て済みライセンス取得処理」と「再利用可能ライセンス取得処理」に不要な条件があったため削除しました

## レビューポイント
- 上限計算方法、使用量取得条件に仕様との認識齟齬はないか?
    - もしくはテストケースで「これもあったほうがいいのでは?」などないか
- その他気になる点あれば

## 動作確認状況
- ローカルでテストが全部通ることを確認
2024-02-16 02:11:34 +00:00
x.yumoto.k
c0b99203da Merge branch 'develop' into ccb 2024-02-15 15:14:50 +09:00
SAITO-PC-3\saito.k
63191a3b61 OptionItemの作成が重複してしまっていたので片方を削除
タスク削除のテストをMysql使用するように修正
2024-02-14 12:06:19 +09:00
SAITO-PC-3\saito.k
3877a4670d Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/pages/UserListPage/index.tsx
2024-02-14 11:33:11 +09:00
makabe.t
bbbd3e757b Merged PR 743: テンプレートファイル削除画面修正
## 概要
[Task3600: テンプレートファイル削除画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3600)

- テンプレートファイル削除の画面を実装しました。

## レビューポイント
- エラー処理は適切でしょうか?

## UIの変更
- [Task3600](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3600?csf=1&web=1&e=E25Kf7)

## 動作確認状況
- ローカルで確認
2024-02-13 02:32:17 +00:00
makabe.t
83efd97bdf Merged PR 739: テンプレートファイル削除API実装
## 概要
[Task3599: テンプレートファイル削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3599)

- テンプレートファイル削除APIとテストを実装しました。

## レビューポイント
- テンプレートファイル削除できないエラーの条件は適切でしょうか?
- テストケースは適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
2024-02-13 00:22:38 +00:00
makabe
447b0e280c Merge branch 'develop' into ccb 2024-02-09 19:06:45 +09:00
x.yumoto.k
cd277f3f9a Merge branch 'develop' into ccb 2024-02-09 17:37:38 +09:00
makabe.t
270122b135 Merged PR 741: アカウント情報画面修正(保存日数の表示)
## 概要
[Task3691: アカウント情報画面修正(保存日数の表示)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3691)

- アカウント情報画面にファイルの保存日数を表示するよう修正しました。
  - ファイル削除が設定されている場合は日数、されていない場合はハイフン表示となるようにしています。

## レビューポイント
- 画面イメージは想定通りでしょうか?
- 表示する保存日数は想定通りの値でしょうか?

## UIの変更
- [Task3691](https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3691?csf=1&web=1&e=Gthaa8)

## 動作確認状況
- ローカルで確認
2024-02-09 01:05:15 +00:00
masaaki
32d8c6b896 Merged PR 737: ファイル削除設定API作成
## 概要
[Task3546: ファイル削除設定API作成](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3546)

- プロダクト バックログ項目 1199: アカウント設定を変更したい(ファイル削除設定)
- ファイル削除設定API作成を実装しました
- 変数定義時に型指定が不要で、自動的に明示的な型宣言が削除されています。非テストコードについてはeslint-disable-lineによって無視設定を行っています。テストコードについては削除したままとしています。

## レビューポイント
- 特筆するものはありません

## UIの変更
- 無し

## 動作確認状況
- ローカル及びユニットテストで確認

## 補足
- 無し
2024-02-07 02:47:41 +00:00
Kentaro Fukunaga
91c27b7684 Merged PR 736: テンプレートファイル削除API IF実装
## 概要
[Task3598: テンプレートファイル削除API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3598)

- 掲題のとおり、IF実装しました。

## レビューポイント
- 認可処理に認識違いないか?
- パラメータに不足ないか?

## 動作確認状況
- ローカルでAPI実行できることを確認

## 補足
- とくになし
2024-02-06 23:53:00 +00:00
Kentaro Fukunaga
548c7a05e9 Merged PR 729: 画面実装(ファイル削除設定ポップアップ画面)
## 概要
[Task3547: 画面実装(ファイル削除設定ポップアップ画面)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3547)

- ファイル削除設定ポップアップの新規実装をしました。

## レビューポイント
- reduxとの連携部分の設計でNGな部分ないか?
- 処理の抜け漏れないか?

## UIの変更
- https://ndstokyo.sharepoint.com/:f:/r/sites/Piranha/Shared%20Documents/General/OMDS/%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%BC%E3%83%B3%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88/Task3547?csf=1&web=1&e=0pu92Q

## 動作確認状況
- ローカルで一通りの動作確認しました。

## 補足
- とくになし
2024-02-06 12:58:55 +00:00
makabe.t
eaf1b3c8b8 Merged PR 738: develop動作確認不具合対応
## 概要
[Task3645: develop動作確認不具合対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3645)

- 削除完了メールのタイトルを修正しました。

## レビューポイント
- 共有

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-02-06 10:26:42 +00:00
makabe.t
19b544540e Merged PR 724: API実装
## 概要
[Task3535: API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3535)

- タイピストグループ削除APIとテストを実装しました。

accountのテストがうまくいっていないようなので別途見直します。
※タイピストグループ削除のテストはうまくいっています

## レビューポイント
- エラーケースと出力されるコードは適切でしょうか?
- テストケースは適切でしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
2024-02-06 07:46:57 +00:00
湯本 開
feeec9d1f5 Merged PR 714: API実装(ユーザー削除|Repository以外)
## 概要
[Task3594: API実装(ユーザー削除|Repository以外)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3594)

ユーザー削除API実装
ユニットテスト実装

## レビューポイント
- `'E014008', // ユーザー削除エラー(削除しようとしたユーザーが自分自身だった)`が用意されているが、
`'E014002', // ユーザー削除エラー(削除しようとしたユーザーが管理者だった)`とわけて実装する必要あるか。
管理者でしか削除処理は行えない&管理者ユーザは削除できない。

- `ExistsCheckoutPermissionDeleteFailedError`
削除対象ユーザーがチェックアウト権限を持っている事が原因の削除失敗エラーは、ユーザ削除エラーの一つとして、`code.ts`にコードを用意してあげる必要があるか?
(引継ぎ時あえて用意していないように見えなくもなかったので)

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-06 07:12:11 +00:00
x.yumoto.k
4442c7cbbe Merge branch 'develop' into ccb 2024-02-06 14:56:39 +09:00
SAITO-PC-3\saito.k
1e8bc39c7f Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
2024-02-05 21:15:24 +09:00
makabe
ce3deecd1b Merge branch 'develop' into ccb 2024-02-05 19:39:37 +09:00
makabe
8e159a1c2a Merge branch 'develop' into ccb 2024-02-05 16:36:09 +09:00
maruyama.t
df74dc358c Merged PR 731: 既存API修正(アカウント情報取得API、アカウント作成API)
## 概要
[Task3555: 既存API修正(アカウント情報取得API、アカウント作成API)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3555)

Accountにauto_file_delete、file_retention_daysを追加
既存のテストでアカウントを作成している部分に項目の値を追加。

## レビューポイント
-entityの

```
@Column({ default: 30 })
```
が必要かどうか。

## UIの変更
なし

## 動作確認状況
- ローカルで確認

## 補足
- 相談、参考資料などがあれば
2024-02-05 02:27:43 +00:00
makabe
186896da15 Merge branch 'ccb' of https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud into ccb 2024-02-05 09:46:49 +09:00
saito.k
4be13e002d Merged PR 710: 画面実装(削除操作)
## 概要
[Task3488: 画面実装(削除操作)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3488)

- ユーザー削除の画面実装
  - 確認ダイアログ
  - 削除API呼び出し
  - エラーハンドリング
  - 成功時のメッセージ
  - 成功時のユーザー一覧更新

## レビューポイント
- 特にレビューしてほしい箇所
- 軽微なものや自明なものは記載不要
- 修正範囲が大きい場合などに記載
- 全体的にや仕様を満たしているか等は本当に必要な時のみ記載

## UIの変更
- Before/Afterのスクショなど
- スクショ置き場

## 動作確認状況
- ローカルで確認

## 補足
- API呼び出しのエラーハンドリング部分はエラーコードが採番されたら追従します
2024-02-05 00:39:53 +00:00
makabe
cf30b97a16 Merge branch 'ccb' of https://dev.azure.com/ODMSCloud/ODMS%20Cloud/_git/ODMS%20Cloud into ccb 2024-02-05 09:37:06 +09:00
Kentaro Fukunaga
44759b1aac Merged PR 725: タイピストグループ削除 画面実装
## 概要
[Task3536: 画面実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3536)

- タイピストグループ画面に削除ボタン配置
- 削除ボタンクリック時に確認ダイアログ表示→OKで削除API実行&ぐるぐる表示
- API実行結果によってメッセージの出し分け&画面リスト更新
- 翻訳情報追加

## レビューポイント
- エラーコードに認識違いないか?
- 画面実装のお作法にそぐわないところがないか?
- なんか抜けてる実装などあれば

## UIの変更
大した変更ではないので、スクショ置き場に置く手間を省いてここに貼り付けます
![image (2).png](https://dev.azure.com/ODMSCloud/6023ff7b-d41c-4fa7-9c6f-f576ba48c07c/_apis/git/repositories/302da463-a2d7-40f9-b2bb-6e8edf324fa9/pullRequests/725/attachments/image%20%282%29.png)

## 動作確認状況
- 期待される動作を一通りローカルで確認しました。
    - API実行中はぐるぐる表示されること
    - API成功または削除済みの場合は成功メッセージ表示され、画面更新されること
    - API失敗時、理由によってエラーメッセージが表示分けされること
2024-02-05 00:31:47 +00:00
makabe
5ab2f02c56 Merge branch 'develop' into ccb 2024-02-05 09:26:55 +09:00
Kentaro Fukunaga
f595cae1b8 Merged PR 728: ファイル削除設定更新API IF実装
## 概要
[Task3557: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3557)

- ファイル削除設定更新APIのIFを実装してOpenAPI生成しました。

## 補足
- 別PRでOKもらってるのでそのままマージします
2024-02-03 06:22:53 +00:00
Kentaro Fukunaga
48a2bddfd9 Merged PR 721: DBマイグレーション
## 概要
[Task3556: DBマイグレーション](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3556)

- アカウントテーブルに対して、以下の2カラムを追加しました。
    - 自動ファイル削除要否
    - 文字起こし完了してからファイル削除するまでのファイル保持日数

## レビューポイント
- カラム名としてほかに適切なものはないか?
- 初期値合ってますよね?

## 動作確認状況
- ローカルでmigrate up と downが出来ることを確認

## 補足
- もしこの値の扱いが、「Finishedになってから○○日後にファイルを削除する」のものであればそのとき修正します。
2024-02-02 04:57:25 +00:00
SAITO-PC-3\saito.k
fbcafd2014 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/pages/DictationPage/index.tsx
2024-02-02 12:01:46 +09:00
makabe.t
9f7c8c99c0 Merged PR 720: API IF実装
## 概要
[Task3534: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3534)

- タイピストグループ削除APIのIFを実装し、OpenAPIを更新しました。

## レビューポイント
- パラメータとバリデータは想定通りでしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-02-02 00:27:08 +00:00
makabe
92193d499a Merge branch 'develop' into ccb 2024-01-31 10:28:39 +09:00
湯本 開
c32b38b783 Merged PR 703: API実装(ユーザー削除)/Repository実装
## 概要
[Task3521: API実装(ユーザー削除)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3521)

- ユーザー削除を行うRepository(DB操作部分)を実装
  - 削除不可条件をチェックして削除できなければエラー
  - 削除可能だった場合、以下の処理を実行
    - ユーザーをアーカイブ
    - ユーザーを削除

## レビューポイント
- 「ライセンス割り当て解除」をせずにユーザーを削除するため、ライセンスがUserテーブルに存在しないIDを指したままになってしまうが問題ないか
  - ラフスケッチ時には、UserArchiveのidには紐づく & UserArchiveに紐づくことによって期限切れのライセンスが誰に割り当たっていたかを把握できるという話だったと思うが、これは"そういう必要がある"という仕様という認識でよいか
- ロック対象の指定は妥当であるか
  - デッドロックは発生しなさそうか
    - User -> UserGroup -> Workflow -> Task -> CheckoutPermission -> Licenseの順番

## 動作確認状況
- 動作確認なし
2024-01-30 07:10:11 +00:00
SAITO-PC-3\saito.k
08a37ff264 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/translation/de.json
#	dictation_client/src/translation/en.json
#	dictation_client/src/translation/es.json
#	dictation_client/src/translation/fr.json
2024-01-29 18:30:34 +09:00
makabe
f00861702a Merge branch 'develop' into ccb 2024-01-29 15:52:55 +09:00
湯本 開
8dfbcea0da Merged PR 702: API IF実装
## 概要
[Task3520: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3520)

- ユーザー削除APIのI/Fを実装

## レビューポイント
- バリデーターは適切に設定されているか
- 不要な処理が混入していないか
- 代行操作による実行を許可しているが、認識は間違っていないか
- マージ先ブランチは間違っていないか

## 動作確認状況
- openapi.jsonの生成成功を確認
2024-01-25 04:00:54 +00:00
makabe.t
8aa45baee8 Merged PR 697: DB関連コード修正
## 概要
[Task3509: DB関連コード修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3509)

- 参照するDBがCCB用のスキーマとなるようにserverの参照DB名を変更しました。
対象:
- server
  - .env
  - app.module.ts
- function
  - .env
  - functions/*

※マージの取り込み分も入ってしまったので他は無視してください。

## レビューポイント
- 対応箇所は適切でしょうか。

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
  - ローカルにスキーマを追加・マイグレーションしてserverを起動できることを確認
2024-01-24 02:52:13 +00:00
SAITO-PC-3\saito.k
f2f8728319 ccbの競合解消漏れを対応 2024-01-24 11:15:27 +09:00
SAITO-PC-3\saito.k
ded446de93 Merge branch 'develop' into ccb
# Conflicts:
#	dictation_client/src/pages/UserListPage/index.tsx
#	dictation_server/package.json
#	dictation_server/src/common/test/init.ts
#	dictation_server/src/features/auth/auth.service.spec.ts
#	dictation_server/src/features/files/files.service.spec.ts
#	dictation_server/src/features/licenses/licenses.service.spec.ts
#	dictation_server/src/features/tasks/tasks.service.spec.ts
#	dictation_server/src/features/terms/terms.service.spec.ts
#	dictation_server/src/features/users/users.service.spec.ts
#	dictation_server/src/features/workflows/workflows.service.spec.ts
2024-01-24 10:46:48 +09:00
makabe.t
1524ec2473 Merged PR 701: パイプラインエラー対応
## 概要
[Task3531: パイプラインエラー対応](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3531)

- パイプラインエラー解消

## レビューポイント
- 共有
2024-01-24 00:48:21 +00:00
makabe.t
a7bb32ec4a Merged PR 699: DBマイグレーションファイルにCCB用の設定を追加する
## 概要
[Task3510: DBマイグレーションファイルにCCB用の設定を追加する](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3510)

- DBマイグレーションファイルにCCB用の設定を追加し、コマンドを修正しました。

## レビューポイント
- 設定内容は認識通りでしょうか?

## UIの変更
- なし

## 動作確認状況
- ローカルで確認
  - CCB用のスキーマに対してmigrate:up/downできるところまで確認
2024-01-23 10:30:14 +00:00
makabe.t
d08c6c99af Merged PR 681: タスク削除API実装
## 概要
[Task3457: タスク削除API実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3457)

- タスク削除APIとUTを実装しました。

## レビューポイント
- テストケースは適切でしょうか?
- リポジトリでの削除処理は適切でしょうか?
- エラー時のコード使い分けは適切でしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-01-16 07:55:38 +00:00
makabe.t
8793606070 Merged PR 682: タスク一覧画面修正
## 概要
[Task3458: タスク一覧画面修正](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3458)

- タスク一覧画面のタスク削除ボタンからタスクを削除する処理を実装しました。
  - タスクがInProgress、ユーザーがTypistの場合にはボタンを非活性となるようにしています。

## レビューポイント
- エラーごとの処理内容は適切でしょうか?
- ボタンの活性制御は適切でしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-01-16 00:17:45 +00:00
makabe.t
81c299dd99 Merged PR 680: タスク削除API IF実装
## 概要
[Task3456: タスク削除API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3456)

- タスク削除APIのIFを実装しopenapi.jsonを更新しました。

## レビューポイント
- パス、バリデータは想定通りでしょうか?

## UIの変更
- なし
## 動作確認状況
- ローカルで確認
2024-01-11 08:47:28 +00:00
502 changed files with 82815 additions and 3097 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
environment_building_tools/logfile.log

View File

@ -0,0 +1,312 @@
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConenctionを作成しておくこと
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
trigger:
branches:
include:
- release-ccb
tags:
include:
- stage-*
jobs:
- job: initialize
displayName: Initialize
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
persistCredentials: true
- script: |
git fetch origin release-ccb:release-ccb
if git merge-base --is-ancestor $(Build.SourceVersion) release-ccb; then
echo "This commit is in the release-ccb branch."
else
echo "This commit is not in the release-ccb branch."
exit 1
fi
displayName: 'タグが付けられたCommitがrelease-ccbブランチに存在するか確認'
- job: backend_test
dependsOn: initialize
condition: succeeded('initialize')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
displayName: Build And Push Backend Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_server
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileServerDictation.dockerfile
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
action: Push an image
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
- job: frontend_build_staging
dependsOn: backend_build
condition: succeeded('backend_build')
displayName: Build Frontend Files(staging)
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_client
verbose: false
- task: Bash@3
displayName: Bash Script
inputs:
targetType: inline
script: cd dictation_client && npm run build:stg
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: dictation_client/build
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
replaceExistingArchive: true
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_test
dependsOn: frontend_build_staging
condition: succeeded('frontend_build_staging')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_function
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileFunctionDictation.dockerfile
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
action: Push an image
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
- job: backend_deploy
dependsOn: function_build
condition: succeeded('function_build')
displayName: Backend Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureWebAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'app-odms-dictation-stg'
deployToSlotOrASE: true
resourceGroupName: 'stg-application-rg'
slotName: 'staging'
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
- job: frontend_deploy
dependsOn: backend_deploy
condition: succeeded('backend_deploy')
displayName: Deploy Frontend Files
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob download \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
- task: Bash@3
displayName: Bash Script
inputs:
targetType: inline
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
- task: AzureStaticWebApp@0
displayName: 'Static Web App: '
inputs:
workingDirectory: '$(Build.SourcesDirectory)'
app_location: '/$(Build.SourceVersion)'
config_file_location: /dictation_client
skip_app_build: true
skip_api_build: true
is_static_export: false
verbose: false
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
- job: function_deploy
dependsOn: frontend_deploy
condition: succeeded('frontend_deploy')
displayName: Function Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureFunctionAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'func-odms-dictation-stg'
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
- job: smoke_test
dependsOn: function_deploy
condition: succeeded('function_deploy')
displayName: 'smoke test'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
# スモークテスト用にjobを確保
- job: swap_slot
dependsOn: smoke_test
condition: succeeded('smoke_test')
displayName: 'Swap Staging and Production'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureAppServiceManage@0
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
inputs:
azureSubscription: 'omds-service-connection-stg'
action: 'Swap Slots'
WebAppName: 'app-odms-dictation-stg'
ResourceGroupName: 'stg-application-rg'
SourceSlot: 'staging'
SwapWithProduction: true
- job: migration
dependsOn: swap_slot
condition: succeeded('swap_slot')
displayName: DB migration
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: kv-odms-secret-stg'
inputs:
ConnectedServiceName: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
- task: CmdLine@2
displayName: migration
inputs:
script: >2
# DB接続情報書き換え
sed -i -e "s/DB_NAME_CCB/$(db-name-ccb)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
sql-migrate --version
cat ./dictation_server/db/dbconfig.yml
# migration実行
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci_ccb

View File

@ -0,0 +1,363 @@
# Pipeline側でKeyVaultやDocker、AppService等に対する操作権限を持ったServiceConnectionを作成しておくこと
# また、環境変数 STATIC_DICTATION_DEPLOYMENT_TOKEN の値として静的WebAppsのデプロイトークンを設定しておくこと
trigger:
branches:
include:
- release-ph1-enhance
tags:
include:
- stage-*
jobs:
- job: initialize
displayName: Initialize
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
persistCredentials: true
- script: |
git fetch origin release-ph1-enhance:release-ph1-enhance
if git merge-base --is-ancestor $(Build.SourceVersion) release-ph1-enhance; then
echo "This commit is in the release-ph1-enhance branch."
else
echo "This commit is not in the release-ph1-enhance branch."
exit 1
fi
displayName: 'タグが付けられたCommitがrelease-ph1-enhanceブランチに存在するか確認'
- job: backend_test
dependsOn: initialize
condition: succeeded('initialize')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
displayName: Build And Push Backend Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_server
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileServerDictation.dockerfile
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
action: Push an image
imageName: odmscloud/staging/dictation:$(Build.SourceVersion)
- job: frontend_build_staging
dependsOn: backend_build
condition: succeeded('backend_build')
displayName: Build Frontend Files(staging)
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_client
verbose: false
- task: Bash@3
displayName: Bash Script
inputs:
targetType: inline
script: cd dictation_client && npm run build:stg
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: dictation_client/build
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
replaceExistingArchive: true
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: frontend_build_production
dependsOn: frontend_build_staging
condition: succeeded('frontend_build_staging')
displayName: Build Frontend Files(production)
variables:
storageAccountName: saomdspipeline
environment: production
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_client
verbose: false
- task: Bash@3
displayName: Bash Script
inputs:
targetType: inline
script: cd dictation_client && npm run build:prod
- task: ArchiveFiles@2
inputs:
rootFolderOrFile: dictation_client/build
includeRootFolder: false
archiveType: 'zip'
archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip'
replaceExistingArchive: true
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob upload \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_test
dependsOn: frontend_build_production
condition: succeeded('frontend_build_production')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Npm@1
displayName: npm ci
inputs:
command: ci
workingDir: dictation_function
verbose: false
- task: Docker@0
displayName: build
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
dockerFile: DockerfileFunctionDictation.dockerfile
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
buildArguments: |
BUILD_VERSION=$(Build.SourceVersion)
- task: Docker@0
displayName: push
inputs:
azureSubscriptionEndpoint: 'omds-service-connection-stg'
azureContainerRegistry: '{"loginServer":"crodmsregistrymaintenance.azurecr.io", "id" : "/subscriptions/108fb131-cdca-4729-a2be-e5bd8c0b3ba7/resourceGroups/maintenance-rg/providers/Microsoft.ContainerRegistry/registries/crOdmsRegistryMaintenance"}'
action: Push an image
imageName: odmscloud/staging/dictation_function:$(Build.SourceVersion)
- job: backend_deploy
dependsOn: function_build
condition: succeeded('function_build')
displayName: Backend Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureWebAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'app-odms-dictation-stg'
deployToSlotOrASE: true
resourceGroupName: 'stg-application-rg'
slotName: 'staging'
containers: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation:$(Build.SourceVersion)'
- job: frontend_deploy
dependsOn: backend_deploy
condition: succeeded('backend_deploy')
displayName: Deploy Frontend Files
variables:
storageAccountName: saomdspipeline
environment: staging
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureCLI@2
inputs:
azureSubscription: 'omds-service-connection-stg'
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az storage blob download \
--auth-mode login \
--account-name $(storageAccountName) \
--container-name $(environment) \
--name $(Build.SourceVersion).zip \
--file $(Build.SourcesDirectory)/$(Build.SourceVersion).zip
- task: Bash@3
displayName: Bash Script
inputs:
targetType: inline
script: unzip $(Build.SourcesDirectory)/$(Build.SourceVersion).zip -d $(Build.SourcesDirectory)/$(Build.SourceVersion)
- task: AzureStaticWebApp@0
displayName: 'Static Web App: '
inputs:
workingDirectory: '$(Build.SourcesDirectory)'
app_location: '/$(Build.SourceVersion)'
config_file_location: /dictation_client
skip_app_build: true
skip_api_build: true
is_static_export: false
verbose: false
azure_static_web_apps_api_token: $(STATIC_DICTATION_DEPLOYMENT_TOKEN)
- job: function_deploy
dependsOn: frontend_deploy
condition: succeeded('frontend_deploy')
displayName: Function Deploy
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureFunctionAppContainer@1
inputs:
azureSubscription: 'omds-service-connection-stg'
appName: 'func-odms-dictation-stg'
imageName: 'crodmsregistrymaintenance.azurecr.io/odmscloud/staging/dictation_function:$(Build.SourceVersion)'
- job: smoke_test
dependsOn: function_deploy
condition: succeeded('function_deploy')
displayName: 'smoke test'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
# スモークテスト用にjobを確保
- job: swap_slot
dependsOn: smoke_test
condition: succeeded('smoke_test')
displayName: 'Swap Staging and Production'
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureAppServiceManage@0
displayName: 'Azure App Service Manage: app-odms-dictation-stg'
inputs:
azureSubscription: 'omds-service-connection-stg'
action: 'Swap Slots'
WebAppName: 'app-odms-dictation-stg'
ResourceGroupName: 'stg-application-rg'
SourceSlot: 'staging'
SwapWithProduction: true
- job: migration
dependsOn: swap_slot
condition: succeeded('swap_slot')
displayName: DB migration
pool:
name: odms-deploy-pipeline
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: kv-odms-secret-stg'
inputs:
ConnectedServiceName: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
- task: CmdLine@2
displayName: migration
inputs:
script: >2
# DB接続情報書き換え
sed -i -e "s/DB_NAME/$(db-name-ph1-enhance)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PASS/$(admin-db-pass)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_USERNAME/$(admin-db-user)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_PORT/$(db-port)/g" ./dictation_server/db/dbconfig.yml
sed -i -e "s/DB_HOST/$(db-host)/g" ./dictation_server/db/dbconfig.yml
sql-migrate --version
cat ./dictation_server/db/dbconfig.yml
# migration実行
sql-migrate up -config=./dictation_server/db/dbconfig.yml -env=ci

View File

@ -43,11 +43,14 @@ jobs:
targetType: inline
workingDirectory: dictation_server/.devcontainer
script: |
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_server sudo npm ci
docker-compose exec -T dictation_server sudo npm run migrate:up:test
docker-compose exec -T dictation_server sudo npm run test
- job: backend_build
dependsOn: backend_test
condition: succeeded('backend_test')
@ -170,9 +173,32 @@ jobs:
--type block \
--overwrite \
--file $(Build.ArtifactStagingDirectory)/$(Build.SourceVersion).zip
- job: function_build
- job: function_test
dependsOn: frontend_build_production
condition: succeeded('frontend_build_production')
displayName: UnitTest
pool:
vmImage: ubuntu-latest
steps:
- checkout: self
clean: true
fetchDepth: 1
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
workingDirectory: dictation_function/.devcontainer
script: |
sudo curl -L "https://github.com/docker/compose/releases/download/v2.20.3/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose --version
docker-compose -f pipeline-docker-compose.yml build
docker-compose -f pipeline-docker-compose.yml up -d
docker-compose exec -T dictation_function sudo npm ci
docker-compose exec -T dictation_function sudo npm run test
- job: function_build
dependsOn: function_test
condition: succeeded('function_test')
displayName: Build And Push Function Image
pool:
name: odms-deploy-pipeline
@ -186,32 +212,6 @@ jobs:
command: ci
workingDir: dictation_function
verbose: false
- task: AzureKeyVault@2
displayName: 'Azure Key Vault: kv-odms-secret-stg'
inputs:
ConnectedServiceName: 'omds-service-connection-stg'
KeyVaultName: kv-odms-secret-stg
SecretsFilter: '*'
- task: Bash@3
displayName: Bash Script (Test)
inputs:
targetType: inline
script: |
cd dictation_function
npm run test
env:
TENANT_NAME: xxxxxxxxxxxx
SIGNIN_FLOW_NAME: xxxxxxxxxxxx
ADB2C_TENANT_ID: $(adb2c-tenant-id)
ADB2C_CLIENT_ID: $(adb2c-client-id)
ADB2C_CLIENT_SECRET: $(adb2c-client-secret)
ADB2C_ORIGIN: xxxxxx
SENDGRID_API_KEY: $(sendgrid-api-key)
MAIL_FROM: xxxxxx
APP_DOMAIN: xxxxxxxxx
REDIS_HOST: xxxxxxxxxxxx
REDIS_PORT: 0
REDIS_PASSWORD: xxxxxxxxxxxx
- task: Docker@0
displayName: build
inputs:

1
data_migration_tools/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/tool*

Binary file not shown.

View File

@ -0,0 +1,8 @@
# 移行ツールをビルドする
# docker ps
$clientContainerName = "client_devcontainer-client-1"
$serverContainerName = "server_devcontainer-server-1"
docker exec -t $clientContainerName sudo npm run build:local
docker exec -t $serverContainerName npm run build:exe

View File

@ -0,0 +1,2 @@
npx openapi-generator-cli version-manager set 7.1.0
npx openapi-generator-cli generate -g typescript-axios -i /app/data_migration_tools/server/src/api/odms/openapi.json -o /app/data_migration_tools/client/src/api/

View File

@ -0,0 +1,7 @@
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.1.0"
}
}

View File

@ -1,8 +1,11 @@
import { Route, Routes } from "react-router-dom";
import TopPage from "./pages/topPage";
import DeletePage from "./pages/deletePage";
const AppRouter: React.FC = () => (
<Routes>
<Route path="/" element={<div />} />
<Route path="/" element={<TopPage />} />
<Route path="/delete" element={<DeletePage />} />
</Routes>
);

View File

@ -0,0 +1,4 @@
wwwroot/*.js
node_modules
typings
dist

View File

@ -0,0 +1 @@
# empty npmignore to ensure all required files (e.g., in the dist folder) are published by npm

View File

@ -0,0 +1,23 @@
# OpenAPI Generator Ignore
# Generated by openapi-generator https://github.com/openapitools/openapi-generator
# Use this file to prevent files from being overwritten by the generator.
# The patterns follow closely to .gitignore or .dockerignore.
# As an example, the C# client generator defines ApiClient.cs.
# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
#ApiClient.cs
# You can match any string of characters against a directory, file or extension with a single asterisk (*):
#foo/*/qux
# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
#foo/**/qux
# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
# You can also negate patterns with an exclamation (!).
# For example, you can ignore all files in a docs folder with the file extension .md:
#docs/*.md
# Then explicitly reverse the ignore rule for a single file:
#!docs/README.md

View File

@ -0,0 +1,9 @@
.gitignore
.npmignore
.openapi-generator-ignore
api.ts
base.ts
common.ts
configuration.ts
git_push.sh
index.ts

View File

@ -0,0 +1 @@
7.1.0

View File

@ -0,0 +1,146 @@
/* tslint:disable */
/* eslint-disable */
/**
* ODMSOpenAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from './configuration';
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
// Some imports not used depending on template conditions
// @ts-ignore
import { DUMMY_BASE_URL, assertParamExists, setApiKeyToObject, setBasicAuthToObject, setBearerAuthToObject, setOAuthToObject, setSearchParams, serializeDataIfNeeded, toPathString, createRequestFunction } from './common';
import type { RequestArgs } from './base';
// @ts-ignore
import { BASE_PATH, COLLECTION_FORMATS, BaseAPI, RequiredError, operationServerMap } from './base';
/**
*
* @export
* @interface ErrorResponse
*/
export interface ErrorResponse {
/**
*
* @type {string}
* @memberof ErrorResponse
*/
'message': string;
/**
*
* @type {string}
* @memberof ErrorResponse
*/
'code': string;
}
/**
* DeleteApi - axios parameter creator
* @export
*/
export const DeleteApiAxiosParamCreator = function (configuration?: Configuration) {
return {
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteData: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
const localVarPath = `/delete`;
// use dummy base URL string because the URL constructor only accepts absolute URLs.
const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL);
let baseOptions;
if (configuration) {
baseOptions = configuration.baseOptions;
}
const localVarRequestOptions = { method: 'POST', ...baseOptions, ...options};
const localVarHeaderParameter = {} as any;
const localVarQueryParameter = {} as any;
setSearchParams(localVarUrlObj, localVarQueryParameter);
let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {};
localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers};
return {
url: toPathString(localVarUrlObj),
options: localVarRequestOptions,
};
},
}
};
/**
* DeleteApi - functional programming interface
* @export
*/
export const DeleteApiFp = function(configuration?: Configuration) {
const localVarAxiosParamCreator = DeleteApiAxiosParamCreator(configuration)
return {
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
async deleteData(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
const localVarAxiosArgs = await localVarAxiosParamCreator.deleteData(options);
const index = configuration?.serverIndex ?? 0;
const operationBasePath = operationServerMap['DeleteApi.deleteData']?.[index]?.url;
return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath);
},
}
};
/**
* DeleteApi - factory interface
* @export
*/
export const DeleteApiFactory = function (configuration?: Configuration, basePath?: string, axios?: AxiosInstance) {
const localVarFp = DeleteApiFp(configuration)
return {
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
*/
deleteData(options?: any): AxiosPromise<object> {
return localVarFp.deleteData(options).then((request) => request(axios, basePath));
},
};
};
/**
* DeleteApi - object-oriented interface
* @export
* @class DeleteApi
* @extends {BaseAPI}
*/
export class DeleteApi extends BaseAPI {
/**
*
* @summary
* @param {*} [options] Override http request option.
* @throws {RequiredError}
* @memberof DeleteApi
*/
public deleteData(options?: AxiosRequestConfig) {
return DeleteApiFp(this.configuration).deleteData(options).then((request) => request(this.axios, this.basePath));
}
}

View File

@ -0,0 +1,86 @@
/* tslint:disable */
/* eslint-disable */
/**
* ODMSOpenAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from './configuration';
// Some imports not used depending on template conditions
// @ts-ignore
import type { AxiosPromise, AxiosInstance, AxiosRequestConfig } from 'axios';
import globalAxios from 'axios';
export const BASE_PATH = "http://localhost".replace(/\/+$/, "");
/**
*
* @export
*/
export const COLLECTION_FORMATS = {
csv: ",",
ssv: " ",
tsv: "\t",
pipes: "|",
};
/**
*
* @export
* @interface RequestArgs
*/
export interface RequestArgs {
url: string;
options: AxiosRequestConfig;
}
/**
*
* @export
* @class BaseAPI
*/
export class BaseAPI {
protected configuration: Configuration | undefined;
constructor(configuration?: Configuration, protected basePath: string = BASE_PATH, protected axios: AxiosInstance = globalAxios) {
if (configuration) {
this.configuration = configuration;
this.basePath = configuration.basePath ?? basePath;
}
}
};
/**
*
* @export
* @class RequiredError
* @extends {Error}
*/
export class RequiredError extends Error {
constructor(public field: string, msg?: string) {
super(msg);
this.name = "RequiredError"
}
}
interface ServerMap {
[key: string]: {
url: string,
description: string,
}[];
}
/**
*
* @export
*/
export const operationServerMap: ServerMap = {
}

View File

@ -0,0 +1,150 @@
/* tslint:disable */
/* eslint-disable */
/**
* ODMSOpenAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
import type { Configuration } from "./configuration";
import type { RequestArgs } from "./base";
import type { AxiosInstance, AxiosResponse } from 'axios';
import { RequiredError } from "./base";
/**
*
* @export
*/
export const DUMMY_BASE_URL = 'https://example.com'
/**
*
* @throws {RequiredError}
* @export
*/
export const assertParamExists = function (functionName: string, paramName: string, paramValue: unknown) {
if (paramValue === null || paramValue === undefined) {
throw new RequiredError(paramName, `Required parameter ${paramName} was null or undefined when calling ${functionName}.`);
}
}
/**
*
* @export
*/
export const setApiKeyToObject = async function (object: any, keyParamName: string, configuration?: Configuration) {
if (configuration && configuration.apiKey) {
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
? await configuration.apiKey(keyParamName)
: await configuration.apiKey;
object[keyParamName] = localVarApiKeyValue;
}
}
/**
*
* @export
*/
export const setBasicAuthToObject = function (object: any, configuration?: Configuration) {
if (configuration && (configuration.username || configuration.password)) {
object["auth"] = { username: configuration.username, password: configuration.password };
}
}
/**
*
* @export
*/
export const setBearerAuthToObject = async function (object: any, configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const accessToken = typeof configuration.accessToken === 'function'
? await configuration.accessToken()
: await configuration.accessToken;
object["Authorization"] = "Bearer " + accessToken;
}
}
/**
*
* @export
*/
export const setOAuthToObject = async function (object: any, name: string, scopes: string[], configuration?: Configuration) {
if (configuration && configuration.accessToken) {
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
? await configuration.accessToken(name, scopes)
: await configuration.accessToken;
object["Authorization"] = "Bearer " + localVarAccessTokenValue;
}
}
function setFlattenedQueryParams(urlSearchParams: URLSearchParams, parameter: any, key: string = ""): void {
if (parameter == null) return;
if (typeof parameter === "object") {
if (Array.isArray(parameter)) {
(parameter as any[]).forEach(item => setFlattenedQueryParams(urlSearchParams, item, key));
}
else {
Object.keys(parameter).forEach(currentKey =>
setFlattenedQueryParams(urlSearchParams, parameter[currentKey], `${key}${key !== '' ? '.' : ''}${currentKey}`)
);
}
}
else {
if (urlSearchParams.has(key)) {
urlSearchParams.append(key, parameter);
}
else {
urlSearchParams.set(key, parameter);
}
}
}
/**
*
* @export
*/
export const setSearchParams = function (url: URL, ...objects: any[]) {
const searchParams = new URLSearchParams(url.search);
setFlattenedQueryParams(searchParams, objects);
url.search = searchParams.toString();
}
/**
*
* @export
*/
export const serializeDataIfNeeded = function (value: any, requestOptions: any, configuration?: Configuration) {
const nonString = typeof value !== 'string';
const needsSerialization = nonString && configuration && configuration.isJsonMime
? configuration.isJsonMime(requestOptions.headers['Content-Type'])
: nonString;
return needsSerialization
? JSON.stringify(value !== undefined ? value : {})
: (value || "");
}
/**
*
* @export
*/
export const toPathString = function (url: URL) {
return url.pathname + url.search + url.hash
}
/**
*
* @export
*/
export const createRequestFunction = function (axiosArgs: RequestArgs, globalAxios: AxiosInstance, BASE_PATH: string, configuration?: Configuration) {
return <T = unknown, R = AxiosResponse<T>>(axios: AxiosInstance = globalAxios, basePath: string = BASE_PATH) => {
const axiosRequestArgs = {...axiosArgs.options, url: (configuration?.basePath || axios.defaults.baseURL || basePath) + axiosArgs.url};
return axios.request<T, R>(axiosRequestArgs);
};
}

View File

@ -0,0 +1,110 @@
/* tslint:disable */
/* eslint-disable */
/**
* ODMSOpenAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export interface ConfigurationParameters {
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
username?: string;
password?: string;
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
basePath?: string;
serverIndex?: number;
baseOptions?: any;
formDataCtor?: new () => any;
}
export class Configuration {
/**
* parameter for apiKey security
* @param name security name
* @memberof Configuration
*/
apiKey?: string | Promise<string> | ((name: string) => string) | ((name: string) => Promise<string>);
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
username?: string;
/**
* parameter for basic security
*
* @type {string}
* @memberof Configuration
*/
password?: string;
/**
* parameter for oauth2 security
* @param name security name
* @param scopes oauth2 scope
* @memberof Configuration
*/
accessToken?: string | Promise<string> | ((name?: string, scopes?: string[]) => string) | ((name?: string, scopes?: string[]) => Promise<string>);
/**
* override base path
*
* @type {string}
* @memberof Configuration
*/
basePath?: string;
/**
* override server index
*
* @type {number}
* @memberof Configuration
*/
serverIndex?: number;
/**
* base options for axios calls
*
* @type {any}
* @memberof Configuration
*/
baseOptions?: any;
/**
* The FormData constructor that will be used to create multipart form data
* requests. You can inject this here so that execution environments that
* do not support the FormData class can still run the generated client.
*
* @type {new () => FormData}
*/
formDataCtor?: new () => any;
constructor(param: ConfigurationParameters = {}) {
this.apiKey = param.apiKey;
this.username = param.username;
this.password = param.password;
this.accessToken = param.accessToken;
this.basePath = param.basePath;
this.serverIndex = param.serverIndex;
this.baseOptions = param.baseOptions;
this.formDataCtor = param.formDataCtor;
}
/**
* Check if the given MIME is a JSON MIME.
* JSON MIME examples:
* application/json
* application/json; charset=UTF8
* APPLICATION/JSON
* application/vnd.company+json
* @param mime - MIME (Multipurpose Internet Mail Extensions)
* @return True if the given MIME is JSON, false otherwise.
*/
public isJsonMime(mime: string): boolean {
const jsonMime: RegExp = new RegExp('^(application\/json|[^;/ \t]+\/[^;/ \t]+[+]json)[ \t]*(;.*)?$', 'i');
return mime !== null && (jsonMime.test(mime) || mime.toLowerCase() === 'application/json-patch+json');
}
}

View File

@ -0,0 +1,57 @@
#!/bin/sh
# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
#
# Usage example: /bin/sh ./git_push.sh wing328 openapi-petstore-perl "minor update" "gitlab.com"
git_user_id=$1
git_repo_id=$2
release_note=$3
git_host=$4
if [ "$git_host" = "" ]; then
git_host="github.com"
echo "[INFO] No command line input provided. Set \$git_host to $git_host"
fi
if [ "$git_user_id" = "" ]; then
git_user_id="GIT_USER_ID"
echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
fi
if [ "$git_repo_id" = "" ]; then
git_repo_id="GIT_REPO_ID"
echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
fi
if [ "$release_note" = "" ]; then
release_note="Minor update"
echo "[INFO] No command line input provided. Set \$release_note to $release_note"
fi
# Initialize the local directory as a Git repository
git init
# Adds the files in the local repository and stages them for commit.
git add .
# Commits the tracked changes and prepares them to be pushed to a remote repository.
git commit -m "$release_note"
# Sets the new remote
git_remote=$(git remote)
if [ "$git_remote" = "" ]; then # git remote not defined
if [ "$GIT_TOKEN" = "" ]; then
echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
else
git remote add origin https://${git_user_id}:"${GIT_TOKEN}"@${git_host}/${git_user_id}/${git_repo_id}.git
fi
fi
git pull origin master
# Pushes (Forces) the changes in the local repository up to the remote repository
echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
git push origin master 2>&1 | grep -v 'To https'

View File

@ -0,0 +1,18 @@
/* tslint:disable */
/* eslint-disable */
/**
* ODMSOpenAPI
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
*
* The version of the OpenAPI document: 1.0.0
*
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/
export * from "./api";
export * from "./configuration";

View File

@ -1,7 +1,11 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
import auth from "features/auth/authSlice";
import ui from "features/ui/uiSlice";
export const store = configureStore({
reducer: {
auth,
ui,
},
});

View File

@ -0,0 +1,17 @@
/*
E+6
- 1~2...
- 3~4DB...
- 5~6
ex)
E00XXXX : システムエラーDB接続失敗など
E01XXXX : 業務エラー
EXX00XX : 内部エラー
EXX01XX : トークンエラー
EXX02XX : DBエラーDB関連
EXX03XX : ADB2CエラーDB関連
*/
export const errorCodes = [
"E009999", // 汎用エラー
] as const;

View File

@ -0,0 +1,3 @@
export * from "./code";
export * from "./types";
export * from "./utils";

View File

@ -0,0 +1,9 @@
import { errorCodes } from "./code";
export type ErrorObject = {
message: string;
code: ErrorCodeType;
statusCode?: number;
};
export type ErrorCodeType = (typeof errorCodes)[number];

View File

@ -0,0 +1,101 @@
import { AxiosError } from "axios";
import { isError } from "lodash";
import { ErrorResponse } from "../../api";
import { errorCodes } from "./code";
import { ErrorCodeType, ErrorObject } from "./types";
export const createErrorObject = (error: unknown): ErrorObject => {
// 最低限通常のエラーかを判定
// Error以外のものがthrowされた場合
// 基本的にないはずだがプログラム上あるので拾う
if (!isError(error)) {
return {
message: "not error type.",
code: "E009999",
};
}
// Axiosエラー 通信してのエラーであるかを判定
if (!isAxiosError(error)) {
return {
message: "not axios error.",
code: "E009999",
};
}
const errorResponse = error.response;
if (!errorResponse) {
return {
message: error.message,
code: "E009999",
statusCode: errorResponse,
};
}
const { data } = errorResponse;
// 想定しているエラーレスポンスの型か判定
if (!isErrorResponse(data)) {
return {
message: error.message,
code: "E009999",
statusCode: errorResponse.status,
};
}
const { message, code } = data;
// 想定しているエラーコードかを判定
if (!isErrorCode(code)) {
return {
message,
code: "E009999",
statusCode: errorResponse.status,
};
}
return {
message,
code,
statusCode: errorResponse.status,
};
};
const isAxiosError = (e: unknown): e is AxiosError => {
const error = e as AxiosError;
return error?.isAxiosError ?? false;
};
const isErrorResponse = (error: unknown): error is ErrorResponse => {
const errorResponse = error as ErrorResponse;
if (
errorResponse === undefined ||
errorResponse.message === undefined ||
errorResponse.code === undefined
) {
return false;
}
return true;
};
const isErrorCode = (errorCode: string): errorCode is ErrorCodeType =>
errorCodes.includes(errorCode as ErrorCodeType);
export const isErrorObject = (
data: unknown
): data is { error: ErrorObject } => {
if (
data &&
typeof data === "object" &&
"error" in data &&
typeof (data as { error: ErrorObject }).error === "object" &&
typeof (data as { error: ErrorObject }).error.message === "string" &&
typeof (data as { error: ErrorObject }).error.code === "string" &&
(typeof (data as { error: ErrorObject }).error.statusCode === "number" ||
(data as { error: ErrorObject }).error.statusCode === undefined)
) {
return true;
}
return false;
};

View File

@ -1,6 +1,6 @@
export const getBasePath = () => {
if (import.meta.env.VITE_STAGE === "local") {
return "http://localhost:8180";
return "http://localhost:8280";
}
return window.location.origin;
};

View File

@ -0,0 +1,15 @@
import { createSlice } from "@reduxjs/toolkit";
import type { AuthState } from "./state";
import { initialConfig } from "./utils";
const initialState: AuthState = {
configuration: initialConfig(),
};
export const authSlice = createSlice({
name: "auth",
initialState,
reducers: {},
});
export default authSlice.reducer;

View File

@ -0,0 +1,3 @@
export * from "./authSlice";
export * from "./state";
export * from "./utils";

View File

@ -0,0 +1,5 @@
import { ConfigurationParameters } from "../../api";
export interface AuthState {
configuration: ConfigurationParameters;
}

View File

@ -0,0 +1,13 @@
import { ConfigurationParameters } from "../../api";
// 初期状態のAPI Config
export const initialConfig = (): ConfigurationParameters => {
const config: ConfigurationParameters = {};
if (import.meta.env.VITE_STAGE === "local") {
config.basePath = "http://localhost:8280";
} else {
config.basePath = `${window.location.origin}/dictation/api`;
}
return config;
};

View File

@ -0,0 +1,25 @@
import { createSlice } from "@reduxjs/toolkit";
import { DeleteState } from "./state";
import { deleteDataAsync } from "./operations";
// eslint-disable-next-line @typescript-eslint/no-empty-function, @typescript-eslint/no-unused-vars
const initialState: DeleteState = {};
export const deleteSlice = createSlice({
name: "detete",
initialState,
reducers: {},
extraReducers: (builder) => {
builder.addCase(deleteDataAsync.pending, () => {
/* Empty Object */
});
builder.addCase(deleteDataAsync.fulfilled, () => {
/* Empty Object */
});
builder.addCase(deleteDataAsync.rejected, () => {
/* Empty Object */
});
},
});
export default deleteSlice.reducer;

View File

@ -0,0 +1,3 @@
export * from "./state";
export * from "./deleteSlice";
export * from "./operations";

View File

@ -0,0 +1,47 @@
import { createAsyncThunk } from "@reduxjs/toolkit";
import { openSnackbar } from "../ui/uiSlice";
import type { RootState } from "../../app/store";
import { ErrorObject, createErrorObject } from "../../common/errors";
import { DeleteApi } from "../../api/api";
import { Configuration } from "../../api/configuration";
export const deleteDataAsync = createAsyncThunk<
{
/* Empty Object */
},
void,
{
// rejectした時の返却値の型
rejectValue: {
error: ErrorObject;
};
}
>("delete/deleteDataAsync", async (args, thunkApi) => {
// apiのConfigurationを取得する
const { getState } = thunkApi;
const state = getState() as RootState;
const { configuration } = state.auth;
const config = new Configuration(configuration);
const deleteApi = new DeleteApi(config);
try {
await deleteApi.deleteData();
thunkApi.dispatch(
openSnackbar({
level: "info",
message: "削除しました。",
})
);
return {};
} catch (e) {
const error = createErrorObject(e);
thunkApi.dispatch(
openSnackbar({
level: "error",
message: "削除に失敗しました。",
})
);
return thunkApi.rejectWithValue({ error });
}
});

View File

@ -0,0 +1,2 @@
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface DeleteState {}

View File

@ -0,0 +1,2 @@
// 標準のスナックバー表示時間(ミリ秒)
export const DEFAULT_SNACKBAR_DURATION = 3000;

View File

@ -0,0 +1,5 @@
export * from "./constants";
export * from "./selectors";
export * from "./state";
export * from "./uiSlice";
export * from "./types";

View File

@ -0,0 +1,15 @@
import type { RootState } from "app/store";
import { SnackbarLevel } from "./types";
export const selectSnackber = (
state: RootState
): {
isOpen: boolean;
level: SnackbarLevel;
message: string;
duration?: number;
} => {
const { isOpen, level, message, duration } = state.ui;
return { isOpen, level, message, duration };
};

View File

@ -0,0 +1,8 @@
import { SnackbarLevel } from "./types";
export interface UIState {
isOpen: boolean;
level: SnackbarLevel;
message: string;
duration?: number;
}

View File

@ -0,0 +1 @@
export type SnackbarLevel = "info" | "error";

View File

@ -0,0 +1,38 @@
import { createSlice, PayloadAction } from "@reduxjs/toolkit";
import { SnackbarLevel } from "./types";
import { UIState } from "./state";
import { DEFAULT_SNACKBAR_DURATION } from "./constants";
const initialState: UIState = {
isOpen: false,
level: "error",
message: "",
};
export const uiSlice = createSlice({
name: "ui",
initialState,
reducers: {
openSnackbar: (
state,
action: PayloadAction<{
level: SnackbarLevel;
message: string;
duration?: number;
}>
) => {
const { level, message, duration } = action.payload;
state.isOpen = true;
state.level = level;
state.message = message;
state.duration =
level === "error" ? undefined : duration ?? DEFAULT_SNACKBAR_DURATION;
},
closeSnackbar: (state) => {
state.isOpen = false;
},
},
});
export const { openSnackbar, closeSnackbar } = uiSlice.actions;
export default uiSlice.reducer;

View File

@ -0,0 +1,32 @@
import { AppDispatch } from "app/store";
import { deleteDataAsync } from "features/delete";
import React, { useCallback } from "react";
import { useDispatch } from "react-redux";
import { Link } from "react-router-dom";
const DeletePage = (): JSX.Element => {
const dispatch: AppDispatch = useDispatch();
const onDelete = useCallback(() => {
if (
/* eslint-disable-next-line no-alert */
!window.confirm("本当に削除しますか?")
) {
return;
}
dispatch(deleteDataAsync());
}, [dispatch]);
return (
<div>
<p></p>
<button type="button" onClick={onDelete}>
</button>
<br />
<Link to="/">return to TopPage</Link>
</div>
);
};
export default DeletePage;

View File

@ -0,0 +1,15 @@
import React from "react";
import { Link } from "react-router-dom";
const TopPage = (): JSX.Element => {
console.log("DeletePage");
return (
<div>
<Link to="/delete"></Link>
<br />
<Link to="/">return to TopPage</Link>
</div>
);
};
export default TopPage;

63
data_migration_tools/package-lock.json generated Normal file
View File

@ -0,0 +1,63 @@
{
"name": "data_migration_tools",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"author": "",
"description": "",
"license": "UNLICENSED",
"private": true,
"packages": {},
"devDependencies": {
"@types/express": "^4.17.17",
"@types/multer": "^1.4.7",
"@types/node": "^20.2.3",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "28.0.3",
"license-checker": "^25.0.1",
"prettier": "^2.3.2",
"source-map-support": "^0.5.20",
"supertest": "^6.1.3",
"swagger-ui-express": "^4.5.0",
"ts-jest": "28.0.1",
"ts-loader": "^9.2.3",
"ts-node": "^10.0.0",
"tsconfig-paths": "4.0.0",
"typescript": "^4.3.5"
},
"jest": {
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testEnvironment": "node",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
}
},
"scripts": {
"build": "nest build && cp -r build dist",
"build:exe": "nest build && cp -r build dist && sh ./buildTool.sh",
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"prebuild": "rimraf dist",
"start": "nest start",
"start:debug": "nest start --debug --watch",
"start:dev": "nest start --watch",
"start:prod": "node dist/main",
"test": "jest",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:watch": "jest --watch"
}
}

View File

@ -14,5 +14,11 @@ services:
- "8280"
environment:
- CHOKIDAR_USEPOLLING=true
networks:
- external
networks:
external:
name: omds_network
external: true
volumes:
data_migration_tools_server_node_modules:

View File

@ -0,0 +1,5 @@
DB_HOST=omds-mysql
DB_PORT=3306
DB_NAME=omds
DB_USERNAME=omdsdbuser
DB_PASSWORD=omdsdbpass

View File

@ -0,0 +1,36 @@
STAGE=local
NO_COLOR=TRUE
CORS=TRUE
PORT=8280
# 開発環境ではADB2Cが別テナントになる都合上、環境変数を分けている
TENANT_NAME=adb2codmsdev
SIGNIN_FLOW_NAME=b2c_1_signin_dev
ADB2C_TENANT_ID=xxxxxxxx
ADB2C_CLIENT_ID=xxxxxxxx
ADB2C_CLIENT_SECRET=xxxxxxxx
ADB2C_ORIGIN=https://zzzzzzzzzz
JWT_PRIVATE_KEY="-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEA5IZZNgDew9eGmuFTezwdHYLSaJvUPPIKYoiOeVLD1paWNI51\n7Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3yCTR6wcWR3PfFJrl9vh5SOo79koZ\noJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbWFJXnDe0DVXYXpJLb4LAlF2XAyYX0\nSYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qSfiL9zWk9dvHoKzSnfSDzDFoFcEoV\nchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//mBNNaDHv83Yuw3mGShT73iJ0JQdk\nTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GOOQIDAQABAoIBADrwp7u097+dK/tw\nWD61n3DIGAqg/lmFt8X4IH8MKLSE/FKr16CS1bqwOEuIM3ZdUtDeXd9Xs7IsyEPE\n5ZwuXK7DSF0M4+Mj8Ip49Q0Aww9aUoLQU9HGfgN/r4599GTrt31clZXA/6Mlighq\ncOZgCcEfdItz8OMu5SQuOIW4CKkCuaWnPOP26UqZocaXNZfpZH0iFLATMMH/TT8x\nay9ToHTQYE17ijdQ/EOLSwoeDV1CU1CIE3P4YfLJjvpKptly5dTevriHEzBi70Jx\n/KEPUn9Jj2gZafrUxRVhmMbm1zkeYxL3gsqRuTzRjEeeILuZhSJyCkQZyUNARxsg\nQY4DZfECgYEA+YLKUtmYTx60FS6DJ4s31TAsXY8kwhq/lB9E3GBZKDd0DPayXEeK\n4UWRQDTT6MI6fedW69FOZJ5sFLp8HQpcssb4Weq9PCpDhNTx8MCbdH3Um5QR3vfW\naKq/1XM8MDUnx5XcNYd87Aw3azvJAvOPr69as8IPnj6sKaRR9uQjbYUCgYEA6nfV\n5j0qmn0EJXZJblk4mvvjLLoWSs17j9YlrZJlJxXMDFRYtgnelv73xMxOMvcGoxn5\nifs7dpaM2x5EmA6jVU5sYaB/beZGEPWqPYGyjIwXPvUGAAv8Gbnvpp+xlSco/Dum\nIq0w+43ry5/xWh6CjfrvKV0J2bDOiJwPEdu/8iUCgYEAnBBSvL+dpN9vhFAzeOh7\nY71eAqcmNsLEUcG9MJqTKbSFwhYMOewF0iHRWHeylEPokhfBJn8kqYrtz4lVWFTC\n5o/Nh3BsLNXCpbMMIapXkeWiti1HgE9ErPMgSkJpwz18RDpYIqM8X+jEQS6D7HSr\nyxfDg+w+GJza0rEVE3hfMIECgYBw+KZ2VfhmEWBjEHhXE+QjQMR3s320MwebCUqE\nNCpKx8TWF/naVC0MwfLtvqbbBY0MHyLN6d//xpA9r3rLbRojqzKrY2KiuDYAS+3n\nzssRzxoQOozWju+8EYu30/ADdqfXyIHG6X3VZs87AGiQzGyJLmP3oR1y5y7MQa09\nJI16hQKBgHK5uwJhGa281Oo5/FwQ3uYLymbNwSGrsOJXiEu2XwJEXwVi2ELOKh4/\n03pBk3Kva3fIwEK+vCzDNnxShIQqBE76/2I1K1whOfoUehhYvKHGaXl2j70Zz9Ks\nrkGW1cx7p+yDqATDrwHBHTHFh5bUTTn8dN40n0e0W/llurpbBkJM\n-----END RSA PRIVATE KEY-----\n"
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n"
SENDGRID_API_KEY=xxxxxxxxxxxxxxxx
MAIL_FROM=xxxxx@xxxxx.xxxx
NOTIFICATION_HUB_NAME=ntf-odms-dev
NOTIFICATION_HUB_CONNECT_STRING=XXXXXXXXXXXXXXXXXX
APP_DOMAIN=http://localhost:8081/
STORAGE_TOKEN_EXPIRE_TIME=2
STORAGE_ACCOUNT_NAME_US=saodmsusdev
STORAGE_ACCOUNT_NAME_AU=saodmsaudev
STORAGE_ACCOUNT_NAME_EU=saodmseudev
STORAGE_ACCOUNT_KEY_US=XXXXXXXXXXXXXXXXXXXXXXX
STORAGE_ACCOUNT_KEY_AU=XXXXXXXXXXXXXXXXXXXXXXX
STORAGE_ACCOUNT_KEY_EU=XXXXXXXXXXXXXXXXXXXXXXX
STORAGE_ACCOUNT_ENDPOINT_US=https://AAAAAAAAAAAAA
STORAGE_ACCOUNT_ENDPOINT_AU=https://AAAAAAAAAAAAA
STORAGE_ACCOUNT_ENDPOINT_EU=https://AAAAAAAAAAAAA
ACCESS_TOKEN_LIFETIME_WEB=7200
REFRESH_TOKEN_LIFETIME_WEB=86400
REFRESH_TOKEN_LIFETIME_DEFAULT=2592000
EMAIL_CONFIRM_LIFETIME=86400
REDIS_HOST=redis-cache
REDIS_PORT=6379
REDIS_PASSWORD=omdsredispass
ADB2C_CACHE_TTL=86400

View File

@ -0,0 +1,12 @@
if [ ! -d "../tool" ]; then
mkdir "../tool"
echo "フォルダが作成されました。"
else
rm -f ../tool/ODMS_DataTool.exe
fi
unzip ../baseNode.zip -d ../tool
ncc build dist/main.js -o dist_single -a
npx -y postject ../tool/ODMS_DataTool.exe NODE_JS_CODE dist_single/index.js --sentinel-fuse NODE_JS_FUSE_fce680ab2cc467b6e072b8b5df1996b2 --overwrite
cp -r build ../tool
cp .env ../tool
cp .env.local.example ../tool

File diff suppressed because it is too large Load Diff

View File

@ -19,10 +19,13 @@
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand"
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"apigen": "ts-node src/api/generate.ts && prettier --write \"src/api/odms/*.json\""
},
"dependencies": {
"@azure/identity": "^4.0.1",
"@azure/storage-blob": "^12.14.0",
"@microsoft/microsoft-graph-client": "^3.0.7",
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.3.9",
@ -30,6 +33,7 @@
"@nestjs/serve-static": "^3.0.1",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.3.9",
"@nestjs/typeorm": "^10.0.2",
"@openapitools/openapi-generator-cli": "^2.5.2",
"@vercel/ncc": "^0.36.1",
"axios": "^1.3.4",
@ -37,9 +41,12 @@
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"multer": "^1.4.5-lts.1",
"mysql2": "^2.3.3",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"swagger-cli": "^4.0.4"
"swagger-cli": "^4.0.4",
"typeorm": "^0.3.20",
"csv": "^6.3.6"
},
"devDependencies": {
"@types/express": "^4.17.17",

View File

@ -0,0 +1,25 @@
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
import { AppModule } from "../app.module";
import { promises as fs } from "fs";
import { NestFactory } from "@nestjs/core";
async function bootstrap(): Promise<void> {
const app = await NestFactory.create(AppModule, {
preview: true,
});
const options = new DocumentBuilder()
.setTitle("ODMSOpenAPI")
.setVersion("1.0.0")
.addBearerAuth({
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
})
.build();
const document = SwaggerModule.createDocument(app, options);
await fs.writeFile(
"src/api/odms/openapi.json",
JSON.stringify(document, null, 0)
);
}
bootstrap();

View File

@ -0,0 +1,56 @@
{
"openapi": "3.0.0",
"paths": {
"/delete": {
"post": {
"operationId": "deleteData",
"summary": "",
"description": "すべてのデータを削除します",
"parameters": [],
"responses": {
"200": {
"description": "成功時のレスポンス",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/DeleteResponse" }
}
}
},
"500": {
"description": "想定外のサーバーエラー",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
}
},
"tags": ["delete"]
}
}
},
"info": {
"title": "ODMSOpenAPI",
"description": "",
"version": "1.0.0",
"contact": {}
},
"tags": [],
"servers": [],
"components": {
"securitySchemes": {
"bearer": { "scheme": "bearer", "bearerFormat": "JWT", "type": "http" }
},
"schemas": {
"DeleteResponse": { "type": "object", "properties": {} },
"ErrorResponse": {
"type": "object",
"properties": {
"message": { "type": "string" },
"code": { "type": "string" }
},
"required": ["message", "code"]
}
}
}
}

View File

@ -1,8 +1,35 @@
import { MiddlewareConsumer, Module } from "@nestjs/common";
import { ServeStaticModule } from "@nestjs/serve-static";
import { ConfigModule } from "@nestjs/config";
import { ConfigModule, ConfigService } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm";
import { join } from "path";
import { LoggerMiddleware } from "./common/loggerMiddleware";
import { AdB2cModule } from "./gateways/adb2c/adb2c.module";
import { BlobstorageModule } from "./gateways/blobstorage/blobstorage.module";
import { RegisterController } from "./features/register/register.controller";
import { RegisterService } from "./features/register/register.service";
import { RegisterModule } from "./features/register/register.module";
import { AccountsRepositoryModule } from "./repositories/accounts/accounts.repository.module";
import { UsersRepositoryModule } from "./repositories/users/users.repository.module";
import { SortCriteriaRepositoryModule } from "./repositories/sort_criteria/sort_criteria.repository.module";
import { LicensesRepositoryModule } from "./repositories/licenses/licenses.repository.module";
import { WorktypesRepositoryModule } from "./repositories/worktypes/worktypes.repository.module";
import { AccountsController } from "./features/accounts/accounts.controller";
import { AccountsService } from "./features/accounts/accounts.service";
import { AccountsModule } from "./features/accounts/accounts.module";
import { UsersController } from "./features/users/users.controller";
import { UsersService } from "./features/users/users.service";
import { UsersModule } from "./features/users/users.module";
import { DeleteModule } from "./features/delete/delete.module";
import { DeleteRepositoryModule } from "./repositories/delete/delete.repository.module";
import { DeleteController } from "./features/delete/delete.controller";
import { DeleteService } from "./features/delete/delete.service";
import { TransferModule } from "./features/transfer/transfer.module";
import { TransferController } from "./features/transfer/transfer.controller";
import { TransferService } from "./features/transfer/transfer.service";
import { VerificationController } from "./features/verification/verification.controller";
import { VerificationService } from "./features/verification/verification.service";
import { VerificationModule } from "./features/verification/verification.module";
@Module({
imports: [
@ -13,9 +40,51 @@ import { LoggerMiddleware } from "./common/loggerMiddleware";
envFilePath: [".env.local", ".env"],
isGlobal: true,
}),
AdB2cModule,
AccountsModule,
UsersModule,
TransferModule,
RegisterModule,
VerificationModule,
AccountsRepositoryModule,
UsersRepositoryModule,
SortCriteriaRepositoryModule,
LicensesRepositoryModule,
WorktypesRepositoryModule,
BlobstorageModule,
DeleteModule,
DeleteRepositoryModule,
TypeOrmModule.forRootAsync({
imports: [ConfigModule],
useFactory: async (configService: ConfigService) => ({
type: "mysql",
host: configService.get("DB_HOST"),
port: configService.get("DB_PORT"),
username: configService.get("DB_USERNAME"),
password: configService.get("DB_PASSWORD"),
database: configService.get("DB_NAME"),
autoLoadEntities: true, // forFeature()で登録されたEntityを自動的にロード
synchronize: false, // trueにすると自動的にmigrationが行われるため注意
}),
inject: [ConfigService],
}),
],
controllers: [
RegisterController,
AccountsController,
UsersController,
DeleteController,
TransferController,
VerificationController,
],
providers: [
RegisterService,
AccountsService,
UsersService,
DeleteService,
TransferService,
VerificationService,
],
controllers: [],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {

View File

@ -0,0 +1,61 @@
import { bigintTransformer } from '.';
describe('bigintTransformer', () => {
describe('to', () => {
it('number型を整数を表す文字列に変換できる', () => {
expect(bigintTransformer.to(0)).toBe('0');
expect(bigintTransformer.to(1)).toBe('1');
expect(bigintTransformer.to(1234567890)).toBe('1234567890');
expect(bigintTransformer.to(9007199254740991)).toBe('9007199254740991');
expect(bigintTransformer.to(-1)).toBe('-1');
});
it('少数点以下がある場合はエラーとなる', () => {
expect(() => bigintTransformer.to(1.1)).toThrowError(
'1.1 is not integer.',
);
});
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
expect(() => bigintTransformer.to(9007199254740992)).toThrowError(
'value is greater than 9007199254740991.',
);
expect(() => bigintTransformer.to(9223372036854775807)).toThrowError(
'value is greater than 9007199254740991.',
);
});
});
describe('from', () => {
it('bigint型の文字列をnumber型に変換できる', () => {
expect(bigintTransformer.from('0')).toBe(0);
expect(bigintTransformer.from('1')).toBe(1);
expect(bigintTransformer.from('1234567890')).toBe(1234567890);
expect(bigintTransformer.from('-1')).toBe(-1);
});
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
expect(() => bigintTransformer.from('9007199254740992')).toThrowError(
'9007199254740992 is greater than 9007199254740991.',
);
expect(() => bigintTransformer.from('9223372036854775807')).toThrowError(
'9223372036854775807 is greater than 9007199254740991.',
);
});
it('number型の場合はそのまま返す', () => {
expect(bigintTransformer.from(0)).toBe(0);
expect(bigintTransformer.from(1)).toBe(1);
expect(bigintTransformer.from(1234567890)).toBe(1234567890);
expect(bigintTransformer.from(-1)).toBe(-1);
});
it('nullの場合はそのまま返す', () => {
expect(bigintTransformer.from(null)).toBe(null);
});
it('number型に変換できない場合はエラーとなる', () => {
expect(() => bigintTransformer.from('a')).toThrowError('a is not int.');
expect(() => bigintTransformer.from('')).toThrowError(' is not int.');
expect(() => bigintTransformer.from(undefined)).toThrowError(
'undefined is not string.',
);
expect(() => bigintTransformer.from({})).toThrowError(
'[object Object] is not string.',
);
});
});
});

View File

@ -0,0 +1,57 @@
import { ValueTransformer } from 'typeorm';
// DBのbigint型をnumber型に変換するためのtransformer
// DBのBigInt型をそのまま扱うと、JSのNumber型の最大値を超えると誤差が発生するため、本来はNumber型に変換すべきではないが、
// 影響範囲を最小限に抑えるため、Number型に変換する。使用するのはAutoIncrementされるIDのみの想定のため、
// Number.MAX_SAFE_INTEGERより大きい値は現実的には発生しない想定で変換する。
export const bigintTransformer: ValueTransformer = {
from: (value: any): number | null => {
// valueがnullであればそのまま返す
if (value === null) {
return value;
}
// valueがnumber型かどうかを判定
// 利用DBによってはbigint型であってもnumber型で返ってくる場合があるため、number型の場合はそのまま返す(sqliteの場合)
if (typeof value === 'number') {
return value;
}
// valueが文字列かどうかを判定
if (typeof value !== 'string') {
throw new Error(`${value} is not string.`);
}
// 数値に変換可能な文字列かどうかを判定
if (Number.isNaN(parseInt(value))) {
throw new Error(`${value} is not int.`);
}
// 文字列ならbigintに変換
// valueが整数でない場合は値が丸められてしまうが、TypeORMのEntityの定義上、整数を表す文字列以外はありえないため、少数点は考慮しない
const bigIntValue = BigInt(value);
// bigIntValueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
if (bigIntValue > Number.MAX_SAFE_INTEGER) {
throw new Error(`${value} is greater than ${Number.MAX_SAFE_INTEGER}.`);
}
// number型で表現できる整数であればnumber型に変換して返す
return Number(bigIntValue);
},
to: (value: any): string | null | undefined => {
// valueがnullまたはundefinedであればそのまま返す
if (value === null || value === undefined) {
return value;
}
// valueがnumber型かどうかを判定
if (typeof value !== 'number') {
throw new Error(`${value} is not number.`);
}
// valueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
if (value > Number.MAX_SAFE_INTEGER) {
throw new Error(`value is greater than ${Number.MAX_SAFE_INTEGER}.`);
}
// valueが整数かどうかを判定
if (!Number.isInteger(value)) {
throw new Error(`${value} is not integer.`);
}
return value.toString();
},
};

View File

@ -0,0 +1,70 @@
/*
E+6
- 1~2...
- 3~4DB...
- 5~6
ex)
E00XXXX : システムエラーDB接続失敗など
E01XXXX : 業務エラー
EXX00XX : 内部エラー
EXX01XX : トークンエラー
EXX02XX : DBエラーDB関連
EXX03XX : ADB2CエラーDB関連
*/
export const ErrorCodes = [
'E009999', // 汎用エラー
'E000101', // トークン形式不正エラー
'E000102', // トークン有効期限切れエラー
'E000103', // トークン非アクティブエラー
'E000104', // トークン署名エラー
'E000105', // トークン発行元エラー
'E000106', // トークンアルゴリズムエラー
'E000107', // トークン不足エラー
'E000108', // トークン権限エラー
'E000301', // ADB2Cへのリクエスト上限超過エラー
'E000401', // IPアドレス未設定エラー
'E000501', // リクエストID未設定エラー
'E010001', // パラメータ形式不正エラー
'E010201', // 未認証ユーザエラー
'E010202', // 認証済ユーザエラー
'E010203', // 管理ユーザ権限エラー
'E010204', // ユーザ不在エラー
'E010205', // DBのRoleが想定外の値エラー
'E010206', // DBのTierが想定外の値エラー
'E010207', // ユーザーのRole変更不可エラー
'E010208', // ユーザーの暗号化パスワード不足エラー
'E010209', // ユーザーの同意済み利用規約バージョンが最新でないエラー
'E010301', // メールアドレス登録済みエラー
'E010302', // authorId重複エラー
'E010401', // PONumber重複エラー
'E010501', // アカウント不在エラー
'E010502', // アカウント情報変更不可エラー
'E010503', // 代行操作不許可エラー
'E010504', // アカウントロックエラー
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
'E010602', // タスク変更権限不足エラー
'E010603', // タスク不在エラー
'E010701', // Blobファイル不在エラー
'E010801', // ライセンス不在エラー
'E010802', // ライセンス取り込み済みエラー
'E010803', // ライセンス発行済みエラー
'E010804', // ライセンス不足エラー
'E010805', // ライセンス有効期限切れエラー
'E010806', // ライセンス割り当て不可エラー
'E010807', // ライセンス割り当て解除済みエラー
'E010808', // ライセンス注文キャンセル不可エラー
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
'E010812', // ライセンス未割当エラー
'E010908', // タイピストグループ不在エラー
'E010909', // タイピストグループ名重複エラー
'E011001', // ワークタイプ重複エラー
'E011002', // ワークタイプ登録上限超過エラー
'E011003', // ワークタイプ不在エラー
'E011004', // ワークタイプ使用中エラー
'E012001', // テンプレートファイル不在エラー
'E013001', // ワークフローのAuthorIDとWorktypeIDのペア重複エラー
'E013002', // ワークフロー不在エラー
] as const;

View File

@ -0,0 +1,10 @@
import { errors } from './message';
import { ErrorCodeType, ErrorResponse } from './types/types';
export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => {
const msg = errors[errorcode];
return {
code: errorcode,
message: msg,
};
};

View File

@ -0,0 +1,59 @@
import { Errors } from './types/types';
// エラーコードとメッセージ対応表
export const errors: Errors = {
E009999: 'Internal Server Error.',
E000101: 'Token invalid format Error.',
E000102: 'Token expired Error.',
E000103: 'Token not before Error',
E000104: 'Token invalid signature Error.',
E000105: 'Token invalid issuer Error.',
E000106: 'Token invalid algorithm Error.',
E000107: 'Token is not exist Error.',
E000108: 'Token authority failed Error.',
E000301: 'ADB2C request limit exceeded Error',
E000401: 'IP address not found Error.',
E000501: 'Request ID not found Error.',
E010001: 'Param invalid format Error.',
E010201: 'Email not verified user Error.',
E010202: 'Email already verified user Error.',
E010203: 'Administrator Permissions Error.',
E010204: 'User not Found Error.',
E010205: 'Role from DB is unexpected value Error.',
E010206: 'Tier from DB is unexpected value Error.',
E010207: 'User role change not allowed Error.',
E010208: 'User encryption password not found Error.',
E010209: 'Accepted term not latest Error.',
E010301: 'This email user already created Error',
E010302: 'This AuthorId already used Error',
E010401: 'This PoNumber already used Error',
E010501: 'Account not Found Error.',
E010502: 'Account information cannot be changed Error.',
E010503: 'Delegation not allowed Error.',
E010504: 'Account is locked Error.',
E010601: 'Task is not Editable Error',
E010602: 'No task edit permissions Error',
E010603: 'Task not found Error.',
E010701: 'File not found in Blob Storage Error.',
E010801: 'License not exist Error',
E010802: 'License already activated Error',
E010803: 'License already issued Error',
E010804: 'License shortage Error',
E010805: 'License is expired Error',
E010806: 'License is unavailable Error',
E010807: 'License is already deallocated Error',
E010808: 'Order cancel failed Error',
E010809: 'Already license order status changed Error',
E010810: 'Cancellation period expired error',
E010811: 'Already license allocated Error',
E010812: 'License not allocated Error',
E010908: 'Typist Group not exist Error',
E010909: 'Typist Group name already exist Error',
E011001: 'This WorkTypeID already used Error',
E011002: 'WorkTypeID create limit exceeded Error',
E011003: 'WorkTypeID not found Error',
E011004: 'WorkTypeID is in use Error',
E012001: 'Template file not found Error',
E013001: 'AuthorId and WorktypeId pair already exists Error',
E013002: 'Workflow not found Error',
};

View File

@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';
import { ErrorCodes } from '../code';
export class ErrorResponse {
@ApiProperty()
message: string;
@ApiProperty()
code: string;
}
export type ErrorCodeType = (typeof ErrorCodes)[number];
export type Errors = {
[P in ErrorCodeType]: string;
};

View File

@ -0,0 +1,17 @@
/*
E+6
- 1~2...
- 3~4DB...
- 5~6
ex)
E00XXXX : システムエラーDB接続失敗など
E01XXXX : 業務エラー
EXX00XX : 内部エラー
EXX01XX : トークンエラー
EXX02XX : DBエラーDB関連
EXX03XX : ADB2CエラーDB関連
*/
export const ErrorCodes = [
"E009999", // 汎用エラー
] as const;

View File

@ -0,0 +1,10 @@
import { errors } from "./message";
import { ErrorCodeType, ErrorResponse } from "./types/types";
export const makeErrorResponse = (errorcode: ErrorCodeType): ErrorResponse => {
const msg = errors[errorcode];
return {
code: errorcode,
message: msg,
};
};

View File

@ -0,0 +1,6 @@
import { Errors } from "./types/types";
// エラーコードとメッセージ対応表
export const errors: Errors = {
E009999: "Internal Server Error.",
};

View File

@ -0,0 +1,15 @@
import { ApiProperty } from "@nestjs/swagger";
import { ErrorCodes } from "../code";
export class ErrorResponse {
@ApiProperty()
message: string;
@ApiProperty()
code: string;
}
export type ErrorCodeType = (typeof ErrorCodes)[number];
export type Errors = {
[P in ErrorCodeType]: string;
};

View File

@ -0,0 +1,32 @@
import { Request } from 'express';
import { Context } from './types';
export const makeContext = (
externalId: string,
requestId: string,
delegationId?: string,
): Context => {
return new Context(externalId, requestId, delegationId);
};
// リクエストヘッダーからrequestIdを取得する
export const retrieveRequestId = (req: Request): string | undefined => {
return req.header('x-request-id');
};
/**
* IPアドレスを取得します
* @param {Request}
* @return {string | undefined}
*/
export const retrieveIp = (req: Request): string | undefined => {
// ローカル環境では直近の送信元IPを取得する
if (process.env.STAGE === 'local') {
return req.ip;
}
const ip = req.header('x-forwarded-for');
if (typeof ip === 'string') {
return ip;
}
return undefined;
};

View File

@ -0,0 +1,4 @@
import { Context } from "./types";
import { makeContext, retrieveRequestId, retrieveIp } from "./context";
export { Context, makeContext, retrieveRequestId, retrieveIp };

View File

@ -0,0 +1,34 @@
export class Context {
/**
* APIの操作ユーザーを追跡するためのID
*/
trackingId: string;
/**
* APIの操作ユーザーのIPアドレス
*/
ip: string;
/**
* ID
*/
requestId: string;
/**
* APIの代行操作ユーザーを追跡するためのID
*/
delegationId?: string | undefined;
constructor(externalId: string, requestId: string, delegationId?: string) {
this.trackingId = externalId;
this.delegationId = delegationId;
this.requestId = requestId;
}
/**
*
*/
getTrackingId(): string {
if (this.delegationId) {
return `${this.requestId}_${this.trackingId} by ${this.delegationId}`;
} else {
return `${this.requestId}_${this.trackingId}`;
}
}
}

View File

@ -0,0 +1,3 @@
import { makePassword } from "./password";
export { makePassword };

View File

@ -0,0 +1,41 @@
export const makePassword = (): string => {
// パスワードの文字数を決定
const passLength = 8;
// パスワードに使用可能な文字を決定(今回はアルファベットの大文字と小文字 数字 symbolsの記号
const lowerCase = "abcdefghijklmnopqrstuvwxyz";
const upperCase = lowerCase.toLocaleUpperCase();
const numbers = "0123456789";
const symbols = "@#$%^&*\\-_+=[]{}|:',.?/`~\"();!";
const chars = lowerCase + upperCase + numbers + symbols;
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!から2種類以上組み合わせ
const charaTypePattern =
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
// autoGeneratedPasswordが以上の条件を満たせばvalidがtrueになる
let valid = false;
let autoGeneratedPassword: string = "";
while (!valid) {
autoGeneratedPassword = "";
// パスワードをランダムに決定+
while (autoGeneratedPassword.length < passLength) {
// 上で決定したcharsの中からランダムに1文字ずつ追加
const index = Math.floor(Math.random() * chars.length);
autoGeneratedPassword += chars[index];
}
// パスワードが上で決定した条件をすべて満たしているかチェック
// 条件を満たすまでループ
valid =
autoGeneratedPassword.length == passLength &&
charaTypePattern.test(autoGeneratedPassword);
if (!valid) {
// autoGeneratedPasswordをログに出す
console.log("Password is not valid");
console.log(autoGeneratedPassword);
}
}
return autoGeneratedPassword;
};

View File

@ -0,0 +1,143 @@
import {
ObjectLiteral,
Repository,
EntityTarget,
UpdateResult,
DeleteResult,
UpdateQueryBuilder,
Brackets,
FindOptionsWhere,
} from 'typeorm';
import { Context } from '../log';
/**
* VS Code上で型解析エラーが発生するためtypeorm内の型定義と同一の型定義をここに記述する
*/
type QueryDeepPartialEntity<T> = _QueryDeepPartialEntity<
ObjectLiteral extends T ? unknown : T
>;
type _QueryDeepPartialEntity<T> = {
[P in keyof T]?:
| (T[P] extends Array<infer U>
? Array<_QueryDeepPartialEntity<U>>
: T[P] extends ReadonlyArray<infer U>
? ReadonlyArray<_QueryDeepPartialEntity<U>>
: _QueryDeepPartialEntity<T[P]>)
| (() => string);
};
interface InsertEntityOptions {
id: number;
}
const insertEntity = async <T extends InsertEntityOptions & ObjectLiteral>(
entity: EntityTarget<T>,
repository: Repository<T>,
value: QueryDeepPartialEntity<T>,
isCommentOut: boolean,
context: Context,
): Promise<T> => {
let query = repository.createQueryBuilder().insert().into(entity);
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
const result = await query.values(value).execute();
// result.identifiers[0].idがnumber型でない場合はエラー
if (typeof result.identifiers[0].id !== 'number') {
throw new Error('Failed to insert entity');
}
const where: FindOptionsWhere<T> = { id: result.identifiers[0].id } as T;
// 結果をもとにセレクトする
const inserted = await repository.findOne({
where,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!inserted) {
throw new Error('Failed to insert entity');
}
return inserted;
};
const insertEntities = async <T extends InsertEntityOptions & ObjectLiteral>(
entity: EntityTarget<T>,
repository: Repository<T>,
values: QueryDeepPartialEntity<T>[],
isCommentOut: boolean,
context: Context,
): Promise<T[]> => {
let query = repository.createQueryBuilder().insert().into(entity);
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
const result = await query.values(values).execute();
// 挿入するレコードが0で、結果も0であれば、からの配列を返す
if (values.length === 0 && result.identifiers.length === 0) {
return [];
}
// 挿入するレコード数と挿入されたレコード数が一致しない場合はエラー
if (result.identifiers.length !== values.length) {
throw new Error('Failed to insert entities');
}
const where: FindOptionsWhere<T>[] = result.identifiers.map((i) => {
// idがnumber型でない場合はエラー
if (typeof i.id !== 'number') {
throw new Error('Failed to insert entities');
}
return { id: i.id } as T;
});
// 結果をもとにセレクトする
const inserted = await repository.find({
where,
comment: `${context.getTrackingId()}_${new Date().toUTCString()}`,
});
if (!inserted) {
throw new Error('Failed to insert entity');
}
return inserted;
};
const updateEntity = async <T extends ObjectLiteral>(
repository: Repository<T>,
criteria:
| string
| ((qb: UpdateQueryBuilder<T>) => string)
| Brackets
| ObjectLiteral
| ObjectLiteral[],
values: QueryDeepPartialEntity<T>,
isCommentOut: boolean,
context: Context,
): Promise<UpdateResult> => {
let query = repository.createQueryBuilder().update();
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
return await query.set(values).where(criteria).execute();
};
const deleteEntity = async <T extends ObjectLiteral>(
repository: Repository<T>,
criteria: string | Brackets | ObjectLiteral | ObjectLiteral[],
isCommentOut: boolean,
context: Context,
): Promise<DeleteResult> => {
let query = repository.createQueryBuilder().delete();
if (isCommentOut) {
query = query.comment(
`${context.getTrackingId()}_${new Date().toUTCString()}`,
);
}
return await query.where(criteria).execute();
};
export { insertEntity, insertEntities, updateEntity, deleteEntity };

View File

@ -0,0 +1,10 @@
import { ADMIN_ROLES, USER_ROLES } from '../../../constants';
/**
* Token.roleに配置されうる文字列リテラル型
*/
export type Roles =
| (typeof ADMIN_ROLES)[keyof typeof ADMIN_ROLES]
| (typeof USER_ROLES)[keyof typeof USER_ROLES];
export type UserRoles = (typeof USER_ROLES)[keyof typeof USER_ROLES];

View File

@ -0,0 +1,27 @@
import {
TASK_LIST_SORTABLE_ATTRIBUTES,
SORT_DIRECTIONS,
} from '../../../constants';
export type TaskListSortableAttribute =
(typeof TASK_LIST_SORTABLE_ATTRIBUTES)[number];
export type SortDirection = (typeof SORT_DIRECTIONS)[number];
export const isTaskListSortableAttribute = (
arg: string,
): arg is TaskListSortableAttribute => {
const param = arg as TaskListSortableAttribute;
if (TASK_LIST_SORTABLE_ATTRIBUTES.includes(param)) {
return true;
}
return false;
};
export const isSortDirection = (arg: string): arg is SortDirection => {
const param = arg as SortDirection;
if (SORT_DIRECTIONS.includes(param)) {
return true;
}
return false;
};

View File

@ -0,0 +1,11 @@
import { SortDirection, TaskListSortableAttribute } from '.';
export const getDirection = (direction: SortDirection): SortDirection => {
return direction;
};
export const getTaskListSortableAttribute = (
TaskListSortableAttribute: TaskListSortableAttribute,
): TaskListSortableAttribute => {
return TaskListSortableAttribute;
};

View File

@ -0,0 +1,287 @@
export class csvInputFile {
type: string;
account_id: string;
parent_id: string;
email: string;
company_name: string;
first_name: string;
last_name: string;
country: string;
state: string;
start_date: string;
expired_date: string;
user_email: string;
author_id: string;
recording_mode: string;
wt1: string;
wt2: string;
wt3: string;
wt4: string;
wt5: string;
wt6: string;
wt7: string;
wt8: string;
wt9: string;
wt10: string;
wt11: string;
wt12: string;
wt13: string;
wt14: string;
wt15: string;
wt16: string;
wt17: string;
wt18: string;
wt19: string;
wt20: string;
}
export class csvInputFileWithRow extends csvInputFile {
row: number;
}
export class AccountsFileType {
accountId: number;
type: string;
companyName: string;
country: string;
dealerAccountId?: number;
adminName: string;
adminMail: string;
userId: number;
role: string;
authorId: string;
}
export class AccountsFile {
accountId: number;
type: number;
companyName: string;
country: string;
dealerAccountId?: number;
adminName: string;
adminMail: string;
userId: number;
role: string;
authorId: string;
}
export class UsersFile {
accountId: number;
userId: number;
name: string;
role: string;
authorId: string;
email: string;
}
export class LicensesFile {
expiry_date: string;
account_id: number;
type: string;
status: string;
allocated_user_id?: number;
}
export class WorktypesFile {
account_id: number;
custom_worktype_id: string;
}
export class CardLicensesFile {
license_id: number;
issue_id: number;
card_license_key: string;
activated_at?: string;
created_at?: string;
created_by?: string;
updated_at?: string;
updated_by?: string;
}
export class AccountsMappingFile {
accountIdText: string;
accountIdNumber: number;
}
export class VerificationResultDetails {
input: string;
inputRow: number;
diffTargetTable: string;
columnName: string;
fileData: string;
databaseData: string;
reason: string;
}
export function isAccountsFileArray(obj: any): obj is AccountsFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsFile(item));
}
export function isAccountsFile(obj: any): obj is AccountsFile {
return (
typeof obj === "object" &&
obj !== null &&
"accountId" in obj &&
typeof obj.accountId === "number" &&
"type" in obj &&
typeof obj.type === "number" &&
"companyName" in obj &&
typeof obj.companyName === "string" &&
"country" in obj &&
typeof obj.country === "string" &&
("dealerAccountId" in obj
? obj.dealerAccountId === null || typeof obj.dealerAccountId === "number"
: true) &&
"adminName" in obj &&
typeof obj.adminName === "string" &&
"adminMail" in obj &&
typeof obj.adminMail === "string" &&
"userId" in obj &&
typeof obj.userId === "number" &&
("role" in obj
? obj.role === null || typeof obj.role === "string"
: true) &&
("authorId" in obj
? obj.authorId === null || typeof obj.authorId === "string"
: true)
);
}
export function isUsersFileArray(obj: any): obj is UsersFile[] {
return Array.isArray(obj) && obj.every((item) => isUsersFile(item));
}
export function isUsersFile(obj: any): obj is UsersFile {
return (
typeof obj === "object" &&
obj !== null &&
"accountId" in obj &&
"userId" in obj &&
"name" in obj &&
"role" in obj &&
"authorId" in obj &&
"email" in obj &&
typeof obj.accountId === "number" &&
typeof obj.userId === "number" &&
typeof obj.name === "string" &&
typeof obj.role === "string" &&
typeof obj.authorId === "string" &&
typeof obj.email === "string"
);
}
export function isLicensesFileArray(obj: any): obj is LicensesFile[] {
return Array.isArray(obj) && obj.every((item) => isLicensesFile(item));
}
export function isLicensesFile(obj: any): obj is LicensesFile {
return (
typeof obj === "object" &&
obj !== null &&
"expiry_date" in obj &&
"account_id" in obj &&
"type" in obj &&
"status" in obj &&
typeof obj.expiry_date === "string" &&
typeof obj.account_id === "number" &&
typeof obj.type === "string" &&
typeof obj.status === "string" &&
(obj.allocated_user_id === null ||
typeof obj.allocated_user_id === "number")
);
}
export function isWorktypesFileArray(obj: any): obj is WorktypesFile[] {
return Array.isArray(obj) && obj.every((item) => isWorktypesFile(item));
}
export function isWorktypesFile(obj: any): obj is WorktypesFile {
return (
typeof obj === "object" &&
obj !== null &&
"account_id" in obj &&
"custom_worktype_id" in obj &&
typeof obj.account_id === "number" &&
typeof obj.custom_worktype_id === "string"
);
}
export function isCardLicensesFileArray(obj: any): obj is CardLicensesFile[] {
return Array.isArray(obj) && obj.every((item) => isCardLicensesFile(item));
}
export function isCardLicensesFile(obj: any): obj is CardLicensesFile {
return (
typeof obj === "object" &&
obj !== null &&
"license_id" in obj &&
"issue_id" in obj &&
"card_license_key" in obj &&
typeof obj.license_id === "number" &&
typeof obj.issue_id === "number" &&
typeof obj.card_license_key === "string" &&
(obj.activated_at === null || typeof obj.activated_at === "string") &&
(obj.created_at === null || typeof obj.created_at === "string") &&
(obj.created_by === null || typeof obj.created_by === "string") &&
(obj.updated_at === null || typeof obj.updated_at === "string") &&
(obj.updated_by === null || typeof obj.updated_by === "string")
);
}
export function isAccountsMappingFileArray(
obj: any
): obj is AccountsMappingFile[] {
return Array.isArray(obj) && obj.every((item) => isAccountsMappingFile(item));
}
export function isAccountsMappingFile(obj: any): obj is AccountsMappingFile {
return (
typeof obj === "object" &&
obj !== null &&
"accountIdText" in obj &&
"accountIdNumber" in obj &&
typeof obj.accountIdText === "string" &&
typeof obj.accountIdNumber === "number"
);
}
export function isCsvInputFileForValidateArray(obj: any): obj is csvInputFile[] {
return (
Array.isArray(obj) && obj.every((item) => isCsvInputFileForValidate(item))
);
}
export function isCsvInputFileForValidate(obj: any): obj is csvInputFile {
return (
typeof obj === "object" &&
"type" in obj &&
"account_id" in obj &&
"parent_id" in obj &&
"email" in obj &&
"company_name" in obj &&
"first_name" in obj &&
"last_name" in obj &&
"country" in obj &&
"state" in obj &&
"start_date" in obj &&
"expired_date" in obj &&
"user_email" in obj &&
"author_id" in obj &&
"recording_mode" in obj &&
"wt1" in obj &&
"wt2" in obj &&
"wt3" in obj &&
"wt4" in obj &&
"wt5" in obj &&
"wt6" in obj &&
"wt7" in obj &&
"wt8" in obj &&
"wt9" in obj &&
"wt10" in obj &&
"wt11" in obj &&
"wt12" in obj &&
"wt13" in obj &&
"wt14" in obj &&
"wt15" in obj &&
"wt16" in obj &&
"wt17" in obj &&
"wt18" in obj &&
"wt19" in obj &&
"wt20" in obj
);
}

View File

@ -0,0 +1,39 @@
import {
registerDecorator,
ValidationOptions,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
@ValidatorConstraint()
export class IsAdminPassword implements ValidatorConstraintInterface {
validate(value: string): boolean {
// 8文字64文字でなければ早期に不合格
const minLength = 8;
const maxLength = 64;
if (value.length < minLength || value.length > maxLength) {
return false;
}
// 英字の大文字、英字の小文字、アラビア数字、記号(@#$%^&*\-_+=[]{}|\:',.?/`~"();!から2種類以上組み合わせ
const charaTypePattern =
/^((?=.*[a-z])(?=.*[A-Z])|(?=.*[a-z])(?=.*[\d])|(?=.*[a-z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[A-Z])(?=.*[\d])|(?=.*[A-Z])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!])|(?=.*[\d])(?=.*[@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]))[a-zA-Z\d@#$%^&*\\\-_+=\[\]{}|:',.?\/`~"();!]/;
return new RegExp(charaTypePattern).test(value);
}
defaultMessage(): string {
return "Admin password rule not satisfied";
}
}
export const IsAdminPasswordvalid = (validationOptions?: ValidationOptions) => {
return (object: any, propertyName: string) => {
registerDecorator({
name: "IsAdminPasswordvalid",
target: object.constructor,
propertyName: propertyName,
constraints: [],
options: validationOptions,
validator: IsAdminPassword,
});
};
};

View File

@ -0,0 +1,406 @@
/**
*
* @const {number}
*/
export const TIERS = {
//OMDS東京
TIER1: 1,
//OMDS現地法人
TIER2: 2,
//代理店
TIER3: 3,
//販売店
TIER4: 4,
//エンドユーザー
TIER5: 5,
} as const;
/**
* East USに保存する国リスト
* @const {number}
*/
export const BLOB_STORAGE_REGION_US = ["CA", "KY", "US"];
/**
* Australia Eastに保存する国リスト
* @const {number}
*/
export const BLOB_STORAGE_REGION_AU = ["AU", "NZ"];
/**
* North Europeに保存する国リスト
* @const {number}
*/
export const BLOB_STORAGE_REGION_EU = [
"AT",
"BE",
"BG",
"HR",
"CY",
"CZ",
"DK",
"EE",
"FI",
"FR",
"DE",
"GR",
"HU",
"IS",
"IE",
"IT",
"LV",
"LI",
"LT",
"LU",
"MT",
"NL",
"NO",
"PL",
"PT",
"RO",
"RS",
"SK",
"SI",
"ZA",
"ES",
"SE",
"CH",
"TR",
"GB",
];
/**
*
* @const {string[]}
*/
export const ADMIN_ROLES = {
ADMIN: "admin",
STANDARD: "standard",
} as const;
/**
*
* @const {string[]}
*/
export const USER_ROLES = {
NONE: "none",
AUTHOR: "author",
TYPIST: "typist",
} as const;
/**
*
* @const {string[]}
*/
export const USER_ROLE_ORDERS = [
USER_ROLES.AUTHOR,
USER_ROLES.TYPIST,
USER_ROLES.NONE,
] as string[];
/**
*
* @const {string[]}
*/
export const LICENSE_ISSUE_STATUS = {
ISSUE_REQUESTING: "Issue Requesting",
ISSUED: "Issued",
CANCELED: "Order Canceled",
};
/**
*
* @const {string[]}
*/
export const LICENSE_TYPE = {
TRIAL: "TRIAL",
NORMAL: "NORMAL",
CARD: "CARD",
} as const;
/**
*
* @const {string[]}
*/
export const LICENSE_ALLOCATED_STATUS = {
UNALLOCATED: "Unallocated",
ALLOCATED: "Allocated",
REUSABLE: "Reusable",
DELETED: "Deleted",
} as const;
/**
*
* @const {string[]}
*/
export const SWITCH_FROM_TYPE = {
NONE: "NONE",
CARD: "CARD",
TRIAL: "TRIAL",
} as const;
/**
*
* @const {number}
*/
export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
/**
*
* @const {number}
*/
export const LICENSE_EXPIRATION_DAYS = 365;
/**
* 8
* @const {number}
*/
export const LICENSE_EXPIRATION_TIME_WITH_TIMEZONE = 8;
/**
*
* @const {number}
*/
export const CARD_LICENSE_LENGTH = 20;
/**
*
* @const {string}
*/
export const OPTION_ITEM_NUM = 10;
/**
*
* @const {string[]}
*/
export const TASK_STATUS = {
UPLOADED: "Uploaded",
PENDING: "Pending",
IN_PROGRESS: "InProgress",
FINISHED: "Finished",
BACKUP: "Backup",
} as const;
/**
*
*/
export const TASK_LIST_SORTABLE_ATTRIBUTES = [
"JOB_NUMBER",
"STATUS",
"ENCRYPTION",
"AUTHOR_ID",
"WORK_TYPE",
"FILE_NAME",
"FILE_LENGTH",
"FILE_SIZE",
"RECORDING_STARTED_DATE",
"RECORDING_FINISHED_DATE",
"UPLOAD_DATE",
"TRANSCRIPTION_STARTED_DATE",
"TRANSCRIPTION_FINISHED_DATE",
] as const;
/**
*
*/
export const SORT_DIRECTIONS = ["ASC", "DESC"] as const;
/**
*
* NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで
* https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions
*/
export const TAG_MAX_COUNT = 20;
/**
*
*/
export const PNS = {
WNS: "wns",
APNS: "apns",
};
/**
*
*/
export const USER_LICENSE_EXPIRY_STATUS = {
NORMAL: "Normal",
NO_LICENSE: "NoLicense",
ALERT: "Alert",
RENEW: "Renew",
};
/**
*
* @const {number}
*/
export const TRIAL_LICENSE_EXPIRATION_DAYS = 30;
/**
*
* @const {number}
*/
export const TRIAL_LICENSE_ISSUE_NUM = 100;
/**
* worktypeの最大登録数
* @const {number}
*/
export const WORKTYPE_MAX_COUNT = 20;
/**
* worktypeのDefault値の取りうる値
**/
export const OPTION_ITEM_VALUE_TYPE = {
DEFAULT: "Default",
BLANK: "Blank",
LAST_INPUT: "LastInput",
} as const;
/**
*
**/
export const OPTION_ITEM_VALUE_TYPE_NUMBER: {
type: string;
value: number;
}[] = [
{
type: OPTION_ITEM_VALUE_TYPE.BLANK,
value: 1,
},
{
type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
value: 2,
},
{
type: OPTION_ITEM_VALUE_TYPE.LAST_INPUT,
value: 3,
},
];
/**
* ADB2Cユーザのidentity.signInType
* @const {string[]}
*/
export const ADB2C_SIGN_IN_TYPE = {
EMAILADDRESS: "emailAddress",
} as const;
/**
* MANUAL_RECOVERY_REQUIRED
* @const {string}
*/
export const MANUAL_RECOVERY_REQUIRED = "[MANUAL_RECOVERY_REQUIRED]";
/**
*
* @const {string[]}
*/
export const TERM_TYPE = {
EULA: "EULA",
DPA: "DPA",
PRIVACY_NOTICE: "PrivacyNotice",
} as const;
/**
*
* @const {string}
*/
export const USER_AUDIO_FORMAT = "DS2(QP)";
/**
* NODE_ENVの値
* @const {string[]}
*/
export const NODE_ENV_TEST = "test";
/**
*
* @const {string[]}
*/
export const USER_LICENSE_STATUS = {
UNALLOCATED: "unallocated",
ALLOCATED: "allocated",
EXPIRED: "expired",
} as const;
/**
* typeの取りうる値CSV)
* @const {string[]}
*/
export const MIGRATION_TYPE = {
ADMINISTRATOR: "Administrator",
BC: "BC",
COUNTRY: "Country",
CUSTOMER: "Customer",
DEALER: "Dealer",
DISTRIBUTOR: "Distributor",
USER: "USER",
} as const;
/**
*
* @const {string[]}
*/
export const COUNTRY_LIST = [
{ value: "CA", label: "Canada" },
{ value: "KY", label: "Cayman Islands" },
{ value: "US", label: "United States" },
{ value: "AU", label: "Australia" },
{ value: "NZ", label: "New Zealand" },
{ value: "AT", label: "Austria" },
{ value: "BE", label: "Belgium" },
{ value: "BG", label: "Bulgaria" },
{ value: "HR", label: "Croatia" },
{ value: "CY", label: "Cyprus" },
{ value: "CZ", label: "Czech" },
{ value: "DK", label: "Denmark" },
{ value: "EE", label: "Estonia" },
{ value: "FI", label: "Finland" },
{ value: "FR", label: "France" },
{ value: "DE", label: "Germany" },
{ value: "GR", label: "Greece" },
{ value: "HU", label: "Hungary" },
{ value: "IS", label: "Iceland" },
{ value: "IE", label: "Ireland" },
{ value: "IT", label: "Italy" },
{ value: "LV", label: "Latvia" },
{ value: "LI", label: "Liechtenstein" },
{ value: "LT", label: "Lithuania" },
{ value: "LU", label: "Luxembourg" },
{ value: "MT", label: "Malta" },
{ value: "NL", label: "Netherlands" },
{ value: "NO", label: "Norway" },
{ value: "PL", label: "Poland" },
{ value: "PT", label: "Portugal" },
{ value: "RO", label: "Romania" },
{ value: "RS", label: "Serbia" },
{ value: "SK", label: "Slovakia" },
{ value: "SI", label: "Slovenia" },
{ value: "ZA", label: "South Africa" },
{ value: "ES", label: "Spain" },
{ value: "SE", label: "Sweden" },
{ value: "CH", label: "Switzerland" },
{ value: "TR", label: "Turkey" },
{ value: "GB", label: "United Kingdom" },
];
/**
* recording_modeの取りうる値CSV)
* @const {string[]}
*/
export const RECORDING_MODE = {
DS2_QP: "DS2 (QP)",
DS2_SP: "DS2 (SP)",
DSS: "DSS",
} as const;
/**
* AutoIncrementの初期値
* @const {number}
*/
export const AUTO_INCREMENT_START = 853211;
/**
* sleep間隔
* @const {number}
*/
export const MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC = 13;

View File

@ -0,0 +1,12 @@
import { Controller, Logger } from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { AccountsService } from "./accounts.service";
@ApiTags("accounts")
@Controller("accounts")
export class AccountsController {
private readonly logger = new Logger(AccountsController.name);
constructor(
private readonly accountService: AccountsService //private readonly cryptoService: CryptoService,
) {}
}

View File

@ -0,0 +1,19 @@
import { Module } from "@nestjs/common";
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
import { AccountsController } from "./accounts.controller";
import { AccountsService } from "./accounts.service";
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
@Module({
imports: [
AccountsRepositoryModule,
UsersRepositoryModule,
AdB2cModule,
BlobstorageModule,
],
controllers: [AccountsController],
providers: [AccountsService],
})
export class AccountsModule {}

View File

@ -0,0 +1,232 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
import {
AdB2cService,
ConflictError,
isConflictError,
} from "../../gateways/adb2c/adb2c.service";
import { Account } from "../../repositories/accounts/entity/account.entity";
import { User } from "../../repositories/users/entity/user.entity";
import { MANUAL_RECOVERY_REQUIRED } from "../../constants";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import { Context } from "../../common/log";
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
@Injectable()
export class AccountsService {
constructor(
private readonly accountRepository: AccountsRepositoryService,
private readonly adB2cService: AdB2cService,
private readonly blobStorageService: BlobstorageService
) {}
private readonly logger = new Logger(AccountsService.name);
/**
* DBに作成する
* @param companyName
* @param country
* @param [dealerAccountId]
* @returns account
*/
async createAccount(
context: Context,
companyName: string,
country: string,
dealerAccountId: number | undefined,
email: string,
password: string,
username: string,
role: string,
authorId: string,
acceptedEulaVersion: string,
acceptedPrivacyNoticeVersion: string,
acceptedDpaVersion: string,
type: number,
accountId: number,
userId: number
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createAccount.name
} | params: { ` +
`dealerAccountId: ${dealerAccountId}, ` +
`role: ${role}, ` +
`acceptedEulaVersion: ${acceptedEulaVersion}, ` +
`acceptedPrivacyNoticeVersion: ${acceptedPrivacyNoticeVersion}, ` +
`acceptedDpaVersion: ${acceptedDpaVersion}, ` +
`type: ${type}, ` +
`accountId: ${accountId}, ` +
`userId: ${userId} };`
);
try {
let externalUser: { sub: string } | ConflictError;
try {
// idpにユーザーを作成
externalUser = await this.adB2cService.createUser(
context,
email,
password,
username
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`
);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
this.logger.log("idpにユーザーを作成成功");
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
this.logger.error(
`[${context.getTrackingId()}] email conflict. externalUser: ${externalUser}`
);
throw new HttpException(
makeErrorResponse("E010301"),
HttpStatus.BAD_REQUEST
);
}
this.logger.log("メールアドレスは重複していません");
let account: Account;
let user: User;
try {
// アカウントと管理者をセットで作成
const { newAccount, adminUser } =
await this.accountRepository.createAccount(
context,
companyName,
country,
dealerAccountId,
type,
externalUser.sub,
role,
authorId,
accountId,
userId,
acceptedEulaVersion,
acceptedPrivacyNoticeVersion,
acceptedDpaVersion
);
account = newAccount;
user = adminUser;
this.logger.log(
`[${context.getTrackingId()}] adminUser.external_id: ${
user.external_id
}`
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] create account failed`);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
// 新規作成アカウント用のBlobコンテナを作成
try {
await this.blobStorageService.createContainer(
context,
account.id,
country
);
this.logger.log("コンテナー作成成功");
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create container failed`
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
// DBのアカウントを削除
await this.deleteAccount(account.id, user.id, context);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
return {
accountId: account.id,
userId: user.id,
externalUserId: user.external_id,
};
} catch (e) {
throw e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createAccount.name}`
);
}
}
// AdB2cのユーザーを削除
// TODO「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
private async deleteAdB2cUser(
externalUserId: string,
context: Context
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createAccount.name
} | params: { ` + `externalUserId: ${externalUserId}};`
);
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.getTrackingId()}] delete externalUser: ${externalUserId} | params: { ` +
`externalUserId: ${externalUserId}, };`
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAdB2cUser.name}`
);
}
}
// DBのアカウントを削除
private async deleteAccount(
accountId: number,
userId: number,
context: Context
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteAccount.name
} | params: { accountId: ${accountId}, userId: ${userId} };`
);
try {
await this.accountRepository.deleteAccount(context, accountId, userId);
this.logger.log(
`[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete account: ${accountId}, user: ${userId}`
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteAccount.name}`
);
}
}
}

View File

@ -0,0 +1,30 @@
import { Test, TestingModule } from "@nestjs/testing";
import { ConfigModule } from "@nestjs/config";
import { DeleteService } from "./delete.service";
import { DeleteController } from "./delete.controller";
describe("DeleteController", () => {
let controller: DeleteController;
const mockTemplatesService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: [".env.test", ".env"],
isGlobal: true,
}),
],
controllers: [DeleteController],
providers: [DeleteService],
})
.overrideProvider(DeleteService)
.useValue(mockTemplatesService)
.compile();
controller = module.get<DeleteController>(DeleteController);
});
it("should be defined", () => {
expect(controller).toBeDefined();
});
});

View File

@ -0,0 +1,42 @@
import {
Controller,
HttpException,
HttpStatus,
Logger,
Post,
Req,
} from "@nestjs/common";
import { ErrorResponse } from "../../common/errors/types/types";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { DeleteService } from "./delete.service";
import { DeleteResponse } from "./types/types";
import { makeContext } from "src/common/log";
@ApiTags("delete")
@Controller("delete")
export class DeleteController {
constructor(private readonly deleteService: DeleteService) {}
@ApiResponse({
status: HttpStatus.OK,
type: DeleteResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
type: ErrorResponse,
})
@ApiOperation({
operationId: "deleteData",
description: "すべてのデータを削除します",
})
@Post()
async deleteData(): Promise<{}> {
const context = makeContext("tool", "delete");
await this.deleteService.deleteData(context);
return {};
}
}

View File

@ -0,0 +1,13 @@
import { Module } from "@nestjs/common";
import { DeleteRepositoryModule } from "../../repositories/delete/delete.repository.module";
import { DeleteController } from "./delete.controller";
import { DeleteService } from "./delete.service";
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
@Module({
imports: [DeleteRepositoryModule, AdB2cModule, BlobstorageModule],
providers: [DeleteService],
controllers: [DeleteController],
})
export class DeleteModule {}

View File

@ -0,0 +1,29 @@
import { DataSource } from "typeorm";
import { ConfigModule } from "@nestjs/config";
import { DeleteService } from "./delete.service";
import { Test, TestingModule } from "@nestjs/testing";
describe("DeleteController", () => {
let service: DeleteService;
const mockTemplatesService = {};
beforeEach(async () => {
const module: TestingModule = await Test.createTestingModule({
imports: [
ConfigModule.forRoot({
envFilePath: [".env.test", ".env"],
isGlobal: true,
}),
],
providers: [DeleteService],
})
.overrideProvider(DeleteService)
.useValue(mockTemplatesService)
.compile();
service = module.get<DeleteService>(DeleteService);
});
it("should be defined", () => {
expect(service).toBeDefined();
});
});

View File

@ -0,0 +1,69 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { DeleteRepositoryService } from "../../repositories/delete/delete.repository.service";
import { makeErrorResponse } from "../../common/errors/makeErrorResponse";
import { AdB2cService } from "../../gateways/adb2c/adb2c.service";
import { BlobstorageService } from "../../gateways/blobstorage/blobstorage.service";
import { Context } from "../../common/log";
@Injectable()
export class DeleteService {
private readonly logger = new Logger(DeleteService.name);
constructor(
private readonly deleteRepositoryService: DeleteRepositoryService,
private readonly blobstorageService: BlobstorageService,
private readonly adB2cService: AdB2cService
) { }
/**
*
* @returns data
*/
async deleteData(context: Context): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.deleteData.name}`
);
try {
// BlobStorageからデータを削除する
await this.blobstorageService.deleteContainers(context);
// 100件ずつのユーザー取得なのですべて削除するまでループする
for (let i = 0; i < 500; i++) {
// ADB2Cからユーザ情報を取得する
const { users, hasNext } = await this.adB2cService.getUsers(context);
const externalIds = users.map((user) => user.id);
await this.adB2cService.deleteUsers(context, externalIds);
// 削除していないユーザーがいない場合はループを抜ける
if (!hasNext) {
break;
}
}
// データベースからデータを削除する
await this.deleteRepositoryService.deleteData();
// AutoIncrementの値をリセットする
await this.deleteRepositoryService.resetAutoIncrement();
// 初期データを挿入する
await this.deleteRepositoryService.insertInitData(context);
} catch (e) {
this.logger.error(`error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
default:
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(`[OUT] ${this.deleteData.name}`);
}
}
}

View File

@ -0,0 +1 @@
import { DataSource } from "typeorm";

View File

@ -0,0 +1 @@
export class DeleteResponse {}

View File

@ -0,0 +1,239 @@
import {
Body,
Controller,
HttpStatus,
Post,
Req,
Logger,
HttpException,
} from "@nestjs/common";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import fs from "fs";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { RegisterRequest, RegisterResponse } from "./types/types";
import { RegisterService } from "./register.service";
import { AccountsService } from "../accounts/accounts.service";
import { UsersService } from "../users/users.service";
import { makeContext } from "../../common/log";
import {
isAccountsFileArray,
isUsersFileArray,
isLicensesFileArray,
isWorktypesFileArray,
isCardLicensesFileArray,
} from "../../common/types/types";
import { makePassword } from "../../common/password/password";
import {
USER_ROLES,
MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC,
} from "../../constants";
@ApiTags("register")
@Controller("register")
export class RegisterController {
private readonly logger = new Logger(RegisterController.name);
constructor(
private readonly registerService: RegisterService,
private readonly accountsService: AccountsService,
private readonly usersService: UsersService
) {}
@Post()
@ApiResponse({
status: HttpStatus.OK,
type: RegisterResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
})
@ApiOperation({ operationId: "dataRegist" })
async dataRegist(
@Body() body: RegisterRequest,
@Req() req: Request
): Promise<RegisterResponse> {
const context = makeContext("iko", "register");
const inputFilePath = body.inputFilePath;
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.dataRegist.name
} | params: { inputFilePath: ${inputFilePath}};`
);
try {
// 読み込みファイルのフルパス
const accouncsFileFullPath = inputFilePath + "accounts.json";
const usersFileFullPath = inputFilePath + "users.json";
const licensesFileFullPath = inputFilePath + "licenses.json";
const worktypesFileFullPath = inputFilePath + "worktypes.json";
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
// ファイル存在チェックと読み込み
// どのファイルがないのかわからないのでそれぞれに存在しない場合はエラーを出す
if (!fs.existsSync(accouncsFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
if (!fs.existsSync(usersFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
if (!fs.existsSync(licensesFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
if (!fs.existsSync(worktypesFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
if (!fs.existsSync(cardLicensesFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
// アカウントの登録用ファイル読み込み
const accountsObject = JSON.parse(
fs.readFileSync(accouncsFileFullPath, "utf8")
);
// 型ガードaccount
if (!isAccountsFileArray(accountsObject)) {
throw new Error("input file is not AccountsFiles");
}
for (const AccountsFile of accountsObject) {
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
// roleの設定
// roleの値がnullなら"none"、null以外ならroleの値、
// また、roleの値が"author"なら"author"を設定
let role: string;
let authorId: string;
if (AccountsFile.role === null) {
role = USER_ROLES.NONE;
authorId = null;
} else if (AccountsFile.role === USER_ROLES.AUTHOR) {
role = USER_ROLES.AUTHOR;
authorId = AccountsFile.authorId;
} else {
// ありえないが、roleの値が"none"または"author"の文字列以外の場合はエラーを返す
throw new Error("Invalid role value");
}
this.logger.log("account生成開始");
await this.accountsService.createAccount(
context,
AccountsFile.companyName,
AccountsFile.country,
AccountsFile.dealerAccountId,
AccountsFile.adminMail,
ramdomPassword,
AccountsFile.adminName,
role,
authorId,
null,
null,
null,
AccountsFile.type,
AccountsFile.accountId,
AccountsFile.userId
);
// ratelimit対応のためsleepを行う
await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC);
}
// const AccountsFiles = accountsObject as AccountsFile[];
// ユーザの登録用ファイル読み込み
const usersObject = JSON.parse(
fs.readFileSync(usersFileFullPath, "utf8")
);
// 型ガードuser
if (!isUsersFileArray(usersObject)) {
throw new Error("input file is not UsersFiles");
}
for (const UsersFile of usersObject) {
this.logger.log(UsersFile.name);
await this.usersService.createUser(
context,
UsersFile.name,
UsersFile.role === USER_ROLES.AUTHOR
? USER_ROLES.AUTHOR
: USER_ROLES.NONE,
UsersFile.email,
true,
true,
UsersFile.accountId,
UsersFile.userId,
UsersFile.authorId,
false,
null,
true
);
// ratelimit対応のためsleepを行う
await sleep(MIGRATION_DATA_REGISTER_INTERVAL_MILLISEC);
}
// ライセンスの登録用ファイル読み込み
const licensesObject = JSON.parse(
fs.readFileSync(licensesFileFullPath, "utf8")
);
// 型ガードlicense
if (!isLicensesFileArray(licensesObject)) {
throw new Error("input file is not LicensesFiles");
}
// ワークタイプの登録用ファイル読み込み
const worktypesObject = JSON.parse(
fs.readFileSync(worktypesFileFullPath, "utf8")
);
// 型ガードWorktypes
if (!isWorktypesFileArray(worktypesObject)) {
throw new Error("input file is not WorktypesFiles");
}
// カードライセンスの登録用ファイル読み込み
const cardLicensesObject = JSON.parse(
fs.readFileSync(cardLicensesFileFullPath, "utf8")
);
// 型ガードcardLicenses
if (!isCardLicensesFileArray(cardLicensesObject)) {
throw new Error("input file is not cardLicensesFiles");
}
// ライセンス・ワークタイプ・カードライセンスの登録
await this.registerService.registLicenseAndWorktypeData(
context,
licensesObject,
worktypesObject,
cardLicensesObject
);
return {};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.dataRegist.name}`
);
}
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -0,0 +1,25 @@
import { Module } from "@nestjs/common";
import { RegisterController } from "./register.controller";
import { RegisterService } from "./register.service";
import { AccountsService } from "../accounts/accounts.service";
import { UsersService } from "../users/users.service";
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
import { WorktypesRepositoryModule } from "../../repositories/worktypes/worktypes.repository.module";
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
import { BlobstorageModule } from "../../gateways/blobstorage/blobstorage.module";
@Module({
imports: [
LicensesRepositoryModule,
WorktypesRepositoryModule,
AccountsRepositoryModule,
UsersRepositoryModule,
AdB2cModule,
BlobstorageModule,
],
controllers: [RegisterController],
providers: [RegisterService, AccountsService, UsersService],
})
export class RegisterModule {}

View File

@ -0,0 +1,65 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { Context } from "../../common/log";
import {
LicensesFile,
WorktypesFile,
CardLicensesFile,
} from "../../common/types/types";
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
import { WorktypesRepositoryService } from "../../repositories/worktypes/worktypes.repository.service";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
@Injectable()
export class RegisterService {
constructor(
private readonly licensesRepository: LicensesRepositoryService,
private readonly worktypesRepository: WorktypesRepositoryService
) {}
private readonly logger = new Logger(RegisterService.name);
/**
* Regist Data
* @param inputFilePath: string
*/
async registLicenseAndWorktypeData(
context: Context,
LicensesFiles: LicensesFile[],
WorktypesFiles: WorktypesFile[],
cardLicensesFiles: CardLicensesFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.registLicenseAndWorktypeData.name
}`
);
try {
this.logger.log("Licenses register start");
await this.licensesRepository.insertLicenses(context, LicensesFiles);
this.logger.log("Licenses register end");
this.logger.log("Worktypes register start");
await this.worktypesRepository.createWorktype(context, WorktypesFiles);
this.logger.log("Worktypes register end");
this.logger.log("CardLicenses register start");
await this.licensesRepository.insertCardLicenses(
context,
cardLicensesFiles
);
this.logger.log("CardLicenses register end");
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${
this.registLicenseAndWorktypeData.name
}`
);
}
}
}

View File

@ -0,0 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
export class RegisterRequest {
@ApiProperty()
inputFilePath: string;
}
export class RegisterResponse {}

View File

@ -0,0 +1,225 @@
import {
Body,
Controller,
HttpStatus,
Post,
Req,
HttpException,
Logger,
} from "@nestjs/common";
import fs from "fs";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { transferRequest, transferResponse } from "./types/types";
import { TransferService } from "./transfer.service";
import { makeContext } from "../../common/log";
import { csvInputFile, AccountsMappingFile } from "../../common/types/types";
import { makeErrorResponse } from "src/common/errors/makeErrorResponse";
import { AUTO_INCREMENT_START } from "../../constants";
@ApiTags("transfer")
@Controller("transfer")
export class TransferController {
private readonly logger = new Logger(TransferController.name);
constructor(private readonly transferService: TransferService) {}
@Post()
@ApiResponse({
status: HttpStatus.OK,
type: transferResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
})
@ApiOperation({ operationId: "dataRegist" })
async dataRegist(
@Body() body: transferRequest,
@Req() req: Request
): Promise<transferResponse> {
const context = makeContext("iko", "transfer");
const inputFilePath = body.inputFilePath;
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.dataRegist.name
} | params: { inputFilePath: ${inputFilePath}};`
);
try {
// 読み込みファイルのフルパス
const accouncsFileFullPath = inputFilePath + "Account_transition.csv";
// ファイル存在チェックと読み込み
if (!fs.existsSync(accouncsFileFullPath)) {
this.logger.error(`file not exists from ${inputFilePath}`);
throw new Error(`file not exists from ${inputFilePath}`);
}
// CSVファイルを全行読み込む
const inputFile = fs.readFileSync(accouncsFileFullPath, "utf-8");
// レコードごとに分割
const csvInputFileLines = inputFile.split("\n");
// ヘッダー行を削除
csvInputFileLines.shift();
// 項目ごとに切り分ける
let csvInputFile: csvInputFile[] = [];
csvInputFileLines.forEach((line) => {
// 項目にカンマが入っている場合を考慮して、ダブルクォーテーションで囲まれた部分を一つの項目として扱う
const regExp = /"[^"]*"/g;
const matchList = line.match(regExp);
if (matchList) {
matchList.forEach((match) => {
// カンマを\に変換
const replaced = match.replace(/,/g, "\\");
line = line.replace(match, replaced);
});
}
const data = line.split(",");
// ダブルクォーテーションを削除
data.forEach((value, index) => {
data[index] = value.replace(/"/g, "");
});
// "\r"を削除
data[data.length - 1] = data[data.length - 1].replace(/\r/g, "");
// dataの要素数が34(csvInputFileの要素数)より多い場合、フォーマット不一致エラー(移行元はworktypeの数が20より多く設定できるので理論上は存在する)
// worktypeの数の確認を促すエラーを出す
if (data.length > 34) {
this.logger.error(
`[${context.getTrackingId()}] format error.please check the number of elements in worktype. data=${data}`
);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.BAD_REQUEST
);
}
// data[1]がundefinedの場合、配列には格納しない
if (data[1] !== undefined) {
// バックスラッシュをカンマに戻す
data.forEach((value, index) => {
data[index] = value.replace(/\\/g, ",");
});
csvInputFile.push({
type: data[0],
account_id: data[1],
parent_id: data[2],
email: data[3],
company_name: data[4],
first_name: data[5],
last_name: data[6],
country: data[7],
state: data[8],
start_date: data[9],
expired_date: data[10],
user_email: data[11],
author_id: data[12],
recording_mode: data[13],
wt1: data[14],
wt2: data[15],
wt3: data[16],
wt4: data[17],
wt5: data[18],
wt6: data[19],
wt7: data[20],
wt8: data[21],
wt9: data[22],
wt10: data[23],
wt11: data[24],
wt12: data[25],
wt13: data[26],
wt14: data[27],
wt15: data[28],
wt16: data[29],
wt17: data[30],
wt18: data[31],
wt19: data[32],
wt20: data[33],
});
}
});
// 各データのバリデーションチェック
await this.transferService.validateInputData(context, csvInputFile);
// account_idを通番に変換し、変換前account_id: 変換後accountId配列を作成する。
const accountIdList = csvInputFile.map((line) => line.account_id);
const accountIdListSet = new Set(accountIdList);
const accountIdListArray = Array.from(accountIdListSet);
const accountIdMap = new Map<string, number>();
accountIdListArray.forEach((accountId, index) => {
accountIdMap.set(accountId, index + AUTO_INCREMENT_START);
});
// アカウントID numberとstring対応表の出力
const accountsMappingFiles: AccountsMappingFile[] = [];
accountIdMap.forEach((value, key) => {
const accountsMappingFile = new AccountsMappingFile();
accountsMappingFile.accountIdNumber = value;
accountsMappingFile.accountIdText = key;
accountsMappingFiles.push(accountsMappingFile);
});
fs.writeFileSync(
`${inputFilePath}account_map.json`,
JSON.stringify(accountsMappingFiles)
);
// CSVファイルの変換
const transferResponseCsv = await this.transferService.transferInputData(
context,
csvInputFile,
accountIdMap
);
// countryを除いた階層の再配置
const AccountsFileTypeLines = transferResponseCsv.accountsFileTypeLines;
const AccountsFile = await this.transferService.relocateHierarchy(
context,
AccountsFileTypeLines
);
const UsersFile = transferResponseCsv.usersFileLines;
const LicensesFile = transferResponseCsv.licensesFileLines;
// メールアドレスの重複を削除
const resultDuplicateEmail =
await this.transferService.removeDuplicateEmail(
context,
AccountsFile,
UsersFile,
LicensesFile
);
// AuthorIDが重複している場合通番を付与する
const transferDuplicateAuthorResultUsers =
await this.transferService.transferDuplicateAuthor(
context,
resultDuplicateEmail.accountsFileLines,
resultDuplicateEmail.usersFileLines
);
// transferResponseCsvをつのJSONファイルの出力する(出力先はinputと同じにする)
const outputFilePath = body.inputFilePath;
const WorktypesFile = transferResponseCsv.worktypesFileLines;
this.transferService.outputJsonFile(
context,
outputFilePath,
resultDuplicateEmail.accountsFileLines,
transferDuplicateAuthorResultUsers,
resultDuplicateEmail.licensesFileLines,
WorktypesFile
);
return {};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.dataRegist.name}`
);
}
}
}

View File

@ -0,0 +1,9 @@
import { Module } from "@nestjs/common";
import { TransferController } from "./transfer.controller";
import { TransferService } from "./transfer.service";
@Module({
imports: [],
controllers: [TransferController],
providers: [TransferService],
})
export class TransferModule {}

View File

@ -0,0 +1,697 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { Context } from "../../common/log";
import {
AccountsFileType,
UsersFile,
LicensesFile,
WorktypesFile,
csvInputFile,
AccountsFile,
} from "../../common/types/types";
import {
COUNTRY_LIST,
MIGRATION_TYPE,
TIERS,
WORKTYPE_MAX_COUNT,
RECORDING_MODE,
LICENSE_ALLOCATED_STATUS,
USER_ROLES,
SWITCH_FROM_TYPE,
} from "src/constants";
import {
registInputDataResponse,
removeDuplicateEmailResponse,
} from "./types/types";
import fs from "fs";
import { makeErrorResponse } from "src/common/error/makeErrorResponse";
@Injectable()
export class TransferService {
constructor() {}
private readonly logger = new Logger(TransferService.name);
/**
* Transfer Input Data
* @param OutputFilePath: string
* @param csvInputFile: csvInputFile[]
*/
async transferInputData(
context: Context,
csvInputFile: csvInputFile[],
accountIdMap: Map<string, number>
): Promise<registInputDataResponse> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.transferInputData.name}`
);
try {
let accountsFileTypeLines: AccountsFileType[] = [];
let usersFileLines: UsersFile[] = [];
let licensesFileLines: LicensesFile[] = [];
let worktypesFileLines: WorktypesFile[] = [];
let errorArray: string[] = [];
let userIdIndex = 0;
// authorIdとuserIdの対応関係を保持するMapを定義
const authorIdToUserIdMap: Map<string, number> = new Map();
// countryのリストを生成
const countryAccounts = csvInputFile.filter(
(item) => item.type === "Country"
);
// csvInputFileを一行読み込みする
csvInputFile.forEach((line) => {
// typeが"USER"以外の場合、アカウントデータの作成を行う
if (line.type !== MIGRATION_TYPE.USER) {
// line.countryの値を読み込みCOUNTRY_LISTのlabelからvalueに変換する
const country = COUNTRY_LIST.find(
(country) => country.label === line.country
)?.value;
// adminNameの変換(last_name + " "+ first_name)
// もしline.last_nameとline.first_nameが存在しない場合、line.admin_mailをnameにする
let adminName = line.email;
if (line.last_name && line.first_name) {
adminName = `${line.last_name} ${line.first_name}`;
// スペースが前後に入っている場合があるのでTrimする
adminName = adminName.trim();
}
// ランダムパスワードの生成(データ登録ツール側で行うのでやらない)
// common/password/password.tsのmakePasswordを使用
// const autoGeneratedPassword = makePassword();
// parentAccountIdの設定
// parent_idが存在する場合、accountIdMapを参照し、accountIdに変換する
let parentAccountId: number | null = null;
if (line.parent_id) {
parentAccountId = accountIdMap.get(line.parent_id);
}
// 万が一parent_idが入力されているのに存在しなかった場合は、エラー配列に追加する
if (parentAccountId === undefined) {
errorArray.push(
`parent_id is invalid. parent_id=${line.parent_id}`
);
}
// userIdIndexをインクリメントする
userIdIndex++;
// AccountsFile配列にPush
accountsFileTypeLines.push({
// accountIdはaccountIdMapから取得する
accountId: accountIdMap.get(line.account_id),
type: line.type,
companyName: line.company_name,
country: country,
dealerAccountId: parentAccountId,
adminName: adminName,
adminMail: line.email,
userId: userIdIndex,
role: null,
authorId: null,
});
} else {
// typeが"USER"の場合、かつcountryのアカウントIDに所属していない場合
if (
line.type == MIGRATION_TYPE.USER &&
!countryAccounts.some(
(countryAccount) => countryAccount.account_id === line.account_id
)
) {
// line.author_idが存在する場合のみユーザーデータを作成する
if (line.author_id) {
// userIdIndexをインクリメントする
userIdIndex++;
// nameの変換
// もしline.last_nameとline.first_nameが存在しない場合、line.emailをnameにする
// 存在する場合は、last_name + " " + first_name
let name = line.user_email;
if (line.last_name && line.first_name) {
name = `${line.last_name} ${line.first_name}`;
}
// UsersFileの作成
usersFileLines.push({
accountId: accountIdMap.get(line.account_id),
userId: userIdIndex,
name: name,
role: USER_ROLES.AUTHOR,
authorId: line.author_id,
email: line.user_email,
});
// authorIdとuserIdの対応関係をマッピング
authorIdToUserIdMap.set(line.author_id, userIdIndex);
}
// ライセンスのデータの作成を行う
// line.expired_dateが"9999/12/31"で始まるデータの場合はデモライセンスなので登録しない
if (!line.expired_date.startsWith("9999/12/31")) {
// authorIdが設定されてる場合、statusは"allocated"、allocated_user_idは対象のユーザID
// されていない場合、statusは"reusable"、allocated_user_idはnull
let status: string;
let allocated_user_id: number | null;
if (line.author_id) {
status = LICENSE_ALLOCATED_STATUS.ALLOCATED;
allocated_user_id =
authorIdToUserIdMap.get(line.author_id) ?? null; // authorIdに対応するuserIdを取得
} else {
status = LICENSE_ALLOCATED_STATUS.REUSABLE;
allocated_user_id = null;
}
// LicensesFileの作成
licensesFileLines.push({
expiry_date: line.expired_date,
account_id: accountIdMap.get(line.account_id),
type: SWITCH_FROM_TYPE.NONE,
status: status,
allocated_user_id: allocated_user_id,
});
}
// WorktypesFileの作成
// wt1~wt20まで読み込み、account単位で作成する
// 作成したWorktypesFileを配列にPush
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
const wt = `wt${i}`;
if (line[wt]) {
// account_idで同一のcustom_worktype_idが存在しない場合は、作成する
if (
!worktypesFileLines.find(
(worktype) =>
worktype.account_id ===
accountIdMap.get(line.account_id) &&
worktype.custom_worktype_id === line[wt]
)
) {
worktypesFileLines.push({
account_id: accountIdMap.get(line.account_id),
custom_worktype_id: line[wt],
});
} else {
continue;
}
}
}
}
}
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
return {
accountsFileTypeLines,
usersFileLines,
licensesFileLines,
worktypesFileLines,
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.transferInputData.name}`
);
}
}
/**
*
* @param accountsFileType: AccountsFileType[]
* @returns AccountsFile[]
*/
async relocateHierarchy(
context: Context,
accountsFileType: AccountsFileType[]
): Promise<AccountsFile[]> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.relocateHierarchy.name}`
);
try {
const relocatedAccounts: AccountsFile[] = [];
const dealerRecords: Map<number, number> = new Map();
const countryAccounts = accountsFileType.filter(
(item) => item.type === MIGRATION_TYPE.COUNTRY
);
const notCountryAccounts = accountsFileType.filter(
(item) => item.type !== MIGRATION_TYPE.COUNTRY
);
notCountryAccounts.forEach((notCountryAccount) => {
let assignDealerAccountId = notCountryAccount.dealerAccountId;
// 親アカウントIDがcountryの場合、countryの親アカウントIDを設定する
for (const countryAccount of countryAccounts) {
if (countryAccount.accountId === notCountryAccount.dealerAccountId) {
assignDealerAccountId = countryAccount.dealerAccountId;
}
}
const assignType = this.getAccountType(notCountryAccount.type);
const newAccount: AccountsFile = {
accountId: notCountryAccount.accountId,
type: assignType,
companyName: notCountryAccount.companyName,
country: notCountryAccount.country,
dealerAccountId: assignDealerAccountId,
adminName: notCountryAccount.adminName,
adminMail: notCountryAccount.adminMail,
userId: notCountryAccount.userId,
role: notCountryAccount.role,
authorId: notCountryAccount.authorId,
};
relocatedAccounts.push(newAccount);
});
return relocatedAccounts;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.relocateHierarchy.name}`
);
}
}
// メソッド: アカウントタイプを数値に変換するヘルパー関数
private getAccountType(type: string): number {
switch (type) {
case MIGRATION_TYPE.ADMINISTRATOR:
return TIERS.TIER1;
case MIGRATION_TYPE.BC:
return TIERS.TIER2;
case MIGRATION_TYPE.DISTRIBUTOR:
return TIERS.TIER3;
case MIGRATION_TYPE.DEALER:
return TIERS.TIER4;
case MIGRATION_TYPE.CUSTOMER:
return TIERS.TIER5;
default:
return 0;
}
}
/**
* JSONファイルの出力
* @param outputFilePath: string
* @param accountsFile: AccountsFile[]
* @param usersFile: UsersFile[]
* @param licensesFile: LicensesFile[]
* @param worktypesFile: WorktypesFile[]
*/
async outputJsonFile(
context: Context,
outputFilePath: string,
accountsFile: AccountsFile[],
usersFile: UsersFile[],
licensesFile: LicensesFile[],
worktypesFile: WorktypesFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.outputJsonFile.name}`
);
try {
// JSONファイルの出力を行う
// AccountsFile配列の出力
const accountsFileJson = JSON.stringify(accountsFile);
fs.writeFileSync(`${outputFilePath}accounts.json`, accountsFileJson);
// UsersFile
const usersFileJson = JSON.stringify(usersFile);
fs.writeFileSync(`${outputFilePath}users.json`, usersFileJson);
// LicensesFile
const licensesFileJson = JSON.stringify(licensesFile);
fs.writeFileSync(`${outputFilePath}licenses.json`, licensesFileJson);
// WorktypesFile
const worktypesFileJson = JSON.stringify(worktypesFile);
fs.writeFileSync(`${outputFilePath}worktypes.json`, worktypesFileJson);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.outputJsonFile.name}`
);
}
}
/**
*
* @param csvInputFile: csvInputFile[]
*/
async validateInputData(
context: Context,
csvInputFile: csvInputFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.validateInputData.name}`
);
try {
// エラー配列を定義
let errorArray: string[] = [];
// アカウントに対するworktypeのMap配列を作成する
const accountWorktypeMap = new Map<string, string[]>();
// csvInputFileのバリデーションチェックを行う
csvInputFile.forEach((line, index) => {
// typeのバリデーションチェック
if (
line.type !== MIGRATION_TYPE.ADMINISTRATOR &&
line.type !== MIGRATION_TYPE.BC &&
line.type !== MIGRATION_TYPE.COUNTRY &&
line.type !== MIGRATION_TYPE.DISTRIBUTOR &&
line.type !== MIGRATION_TYPE.DEALER &&
line.type !== MIGRATION_TYPE.CUSTOMER &&
line.type !== MIGRATION_TYPE.USER
) {
throw new HttpException(
`type is invalid. index=${index} type=${line.type}`,
HttpStatus.BAD_REQUEST
);
}
// typeがUSER以外の場合で、countryがnullの場合エラー配列に格納する
if (line.type !== MIGRATION_TYPE.USER) {
if (!line.country) {
// countryがnullの場合エラー配列に格納する
errorArray.push(`country is null. index=${index}`);
}
}
// countryのバリデーションチェック
if (line.country) {
if (!COUNTRY_LIST.find((country) => country.label === line.country)) {
throw new HttpException(
`country is invalid. index=${index} country=${line.country}`,
HttpStatus.BAD_REQUEST
);
}
}
// mailのバリデーションチェック
// メールアドレスの形式が正しいかどうかのチェック
const mailRegExp =
/^[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]+@[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*\.[a-zA-Z0-9!#$%&'_`/=~+\-?^{|}.]*[a-zA-Z]$/;
if (line.email) {
if (!mailRegExp.test(line.email)) {
throw new HttpException(
`email is invalid. index=${index} email=${line.email}`,
HttpStatus.BAD_REQUEST
);
}
}
if (line.user_email) {
if (!mailRegExp.test(line.user_email)) {
throw new HttpException(
`user_email is invalid. index=${index} user_email=${line.email}`,
HttpStatus.BAD_REQUEST
);
}
}
// recording_modeの値が存在する場合
if (line.recording_mode) {
// recording_modeのバリデーションチェック
if (
line.recording_mode !== RECORDING_MODE.DS2_QP &&
line.recording_mode !== RECORDING_MODE.DS2_SP &&
line.recording_mode !== RECORDING_MODE.DSS
) {
throw new HttpException(
`recording_mode is invalid. index=${index} recording_mode=${line.recording_mode}`,
HttpStatus.BAD_REQUEST
);
}
}
// worktypeの1アカウント20件上限チェック
for (let i = 1; i <= WORKTYPE_MAX_COUNT; i++) {
const wt = `wt${i}`;
if (line[wt]) {
if (accountWorktypeMap.has(line.account_id)) {
const worktypes = accountWorktypeMap.get(line.account_id);
// 重複している場合はPushしない
if (worktypes?.includes(line[wt])) {
continue;
} else {
worktypes?.push(line[wt]);
}
// 20件を超えたらエラー
if (worktypes?.length > WORKTYPE_MAX_COUNT) {
throw new HttpException(
`worktype is over. index=${index} account_id=${line.account_id}`,
HttpStatus.BAD_REQUEST
);
}
} else {
accountWorktypeMap.set(line.account_id, [line[wt]]);
}
}
}
});
// エラー配列に値が存在する場合はエラーファイルを出力する
if (errorArray.length > 0) {
const errorFileJson = JSON.stringify(errorArray);
fs.writeFileSync(`error.json`, errorFileJson);
throw new HttpException(
`errorArray is invalid. errorArray=${errorArray}`,
HttpStatus.BAD_REQUEST
);
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.validateInputData.name}`
);
}
}
/**
* removeDuplicateEmail
* @param accountsFileLines: AccountsFile[]
* @param usersFileLines: UsersFile[]
* @param licensesFileLines: LicensesFile[]
* @returns registInputDataResponse
*/
async removeDuplicateEmail(
context: Context,
accountsFileLines: AccountsFile[],
usersFileLines: UsersFile[],
licensesFileLines: LicensesFile[]
): Promise<removeDuplicateEmailResponse> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}`
);
try {
const newAccountsFileLines: AccountsFile[] = [];
const newUsersFileLines: UsersFile[] = [];
const newLicensesFileLines: LicensesFile[] = [...licensesFileLines]; // licensesFileLinesを新規配列にコピー
// accountsFileLinesの行ループ
accountsFileLines.forEach((account) => {
const duplicateAdminMail = newAccountsFileLines.find(
(a) => a.adminMail.toLowerCase() === account.adminMail.toLowerCase() // メールアドレスは大文字小文字を区別しない
);
if (duplicateAdminMail) {
// 重複がある場合はどちらが取込対象か判断できないのでファイルを出力し、エラーにする
const errorFileJson = JSON.stringify(account);
fs.writeFileSync(`duplicate_error.json`, errorFileJson);
throw new HttpException(
`adminMail is duplicate. adminMail=${account.adminMail}`,
HttpStatus.BAD_REQUEST
);
} else {
// 重複がない場合
newAccountsFileLines.push(account);
}
});
// usersFileLinesの行ループ
usersFileLines.forEach((user) => {
const duplicateUserEmail = newUsersFileLines.find(
(u) => u.email.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
);
if (duplicateUserEmail) {
// 重複がある場合
const index = newLicensesFileLines.findIndex(
(license) =>
license.account_id === user.accountId &&
license.allocated_user_id === duplicateUserEmail.userId
);
if (index !== -1) {
// ライセンスの割り当てを解除
newLicensesFileLines[index].status =
LICENSE_ALLOCATED_STATUS.REUSABLE;
newLicensesFileLines[index].allocated_user_id = null;
}
} else {
// 重複がない場合
newUsersFileLines.push(user);
}
// newAccountsFileLinesとの突合せ
const duplicateAdminUserEmail = newAccountsFileLines.find(
(a) => a.adminMail.toLowerCase() === user.email.toLowerCase() // メールアドレスは大文字小文字を区別しない
);
// 重複がある場合
if (duplicateAdminUserEmail) {
// 同一アカウント内での重複の場合
const isDuplicateInSameAccount =
duplicateAdminUserEmail.accountId === user.accountId;
if (isDuplicateInSameAccount) {
// アカウント管理者にauthorロールを付与する
duplicateAdminUserEmail.role = USER_ROLES.AUTHOR;
duplicateAdminUserEmail.authorId = user.authorId;
// アカウントにライセンスが割り当てられているか確認する
const isAllocatedLicense = newLicensesFileLines.some(
(license) =>
license.account_id === duplicateAdminUserEmail.accountId &&
license.allocated_user_id === duplicateAdminUserEmail.userId
);
// 割り当てられていなければアカウントに割り当てる
if (!isAllocatedLicense) {
const index = newLicensesFileLines.findIndex(
(license) =>
license.account_id === user.accountId &&
license.allocated_user_id === user.userId
);
if (index !== -1) {
newLicensesFileLines[index].allocated_user_id =
duplicateAdminUserEmail.userId;
}
}
}
// ユーザーから割り当て解除する
const index = newLicensesFileLines.findIndex(
(license) =>
license.account_id === user.accountId &&
license.allocated_user_id === user.userId
);
if (index !== -1) {
// ライセンスの割り当てを解除
newLicensesFileLines[index].status =
LICENSE_ALLOCATED_STATUS.REUSABLE;
newLicensesFileLines[index].allocated_user_id = null;
}
// ユーザーの削除
const userIndex = newUsersFileLines.findIndex(
(u) => u.userId === user.userId
);
if (userIndex !== -1) {
newUsersFileLines.splice(userIndex, 1);
}
}
});
return {
accountsFileLines: newAccountsFileLines,
usersFileLines: newUsersFileLines,
licensesFileLines: newLicensesFileLines,
};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.removeDuplicateEmail.name}`
);
}
}
/**
* transferDuplicateAuthor
* @param accountsFileLines: AccountsFile[]
* @param usersFileLines: UsersFile[]
* @returns UsersFile[]
*/
async transferDuplicateAuthor(
context: Context,
accountsFileLines: AccountsFile[],
usersFileLines: UsersFile[]
): Promise<UsersFile[]> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.transferDuplicateAuthor.name}`
);
try {
const newUsersFileLines: UsersFile[] = [];
for (const accountsFileLine of accountsFileLines) {
let duplicateSequence: number = 2;
let authorIdList: String[] = [];
// メールアドレス重複時はアカウントにもAuthorIdが設定されるので重複チェック用のリストに追加しておく
if (accountsFileLine.authorId) {
authorIdList.push(accountsFileLine.authorId);
}
const targetaccountUsers = usersFileLines.filter(
(item) => item.accountId === accountsFileLine.accountId
);
for (const targetaccountUser of targetaccountUsers) {
let assignAuthorId = targetaccountUser.authorId;
if (authorIdList.includes(targetaccountUser.authorId)) {
// 同じauthorIdがいる場合、自分のauthorIdに連番を付与する
assignAuthorId = assignAuthorId + duplicateSequence;
duplicateSequence = duplicateSequence + 1;
}
authorIdList.push(targetaccountUser.authorId);
// 新しいAuthorIdのユーザに詰め替え
const newUser: UsersFile = {
accountId: targetaccountUser.accountId,
userId: targetaccountUser.userId,
name: targetaccountUser.name,
role: targetaccountUser.role,
authorId: assignAuthorId,
email: targetaccountUser.email,
};
newUsersFileLines.push(newUser);
}
}
return newUsersFileLines;
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${
this.transferDuplicateAuthor.name
}`
);
}
}
}

View File

@ -0,0 +1,35 @@
import { ApiProperty } from "@nestjs/swagger";
import {
AccountsFile,
AccountsFileType,
LicensesFile,
UsersFile,
WorktypesFile,
} from "src/common/types/types";
export class transferRequest {
@ApiProperty()
inputFilePath: string;
}
export class transferResponse {}
export class registInputDataResponse {
@ApiProperty()
accountsFileTypeLines: AccountsFileType[];
@ApiProperty()
usersFileLines: UsersFile[];
@ApiProperty()
licensesFileLines: LicensesFile[];
@ApiProperty()
worktypesFileLines: WorktypesFile[];
}
export class removeDuplicateEmailResponse {
@ApiProperty()
accountsFileLines: AccountsFile[];
@ApiProperty()
usersFileLines: UsersFile[];
@ApiProperty()
licensesFileLines: LicensesFile[];
}

View File

@ -0,0 +1,10 @@
import { Controller, Logger } from "@nestjs/common";
import { ApiTags } from "@nestjs/swagger";
import { UsersService } from "./users.service";
@ApiTags("users")
@Controller("users")
export class UsersController {
private readonly logger = new Logger(UsersController.name);
constructor(private readonly usersService: UsersService) {}
}

View File

@ -0,0 +1,12 @@
import { Module } from "@nestjs/common";
import { AdB2cModule } from "../../gateways/adb2c/adb2c.module";
import { UsersRepositoryModule } from "../../repositories/users/users.repository.module";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";
@Module({
imports: [UsersRepositoryModule, AdB2cModule],
controllers: [UsersController],
providers: [UsersService],
})
export class UsersModule {}

View File

@ -0,0 +1,310 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import { makePassword } from "../../common/password/password";
import {
AdB2cService,
ConflictError,
isConflictError,
} from "../../gateways/adb2c/adb2c.service";
import {
User as EntityUser,
newUser,
} from "../../repositories/users/entity/user.entity";
import { UsersRepositoryService } from "../../repositories/users/users.repository.service";
import { MANUAL_RECOVERY_REQUIRED, USER_ROLES } from "../../constants";
import { Context } from "../../common/log";
import { UserRoles } from "../../common/types/role";
@Injectable()
export class UsersService {
private readonly logger = new Logger(UsersService.name);
constructor(
private readonly usersRepository: UsersRepositoryService,
private readonly adB2cService: AdB2cService
) {}
/**
* Creates user
* @param context
* @param name
* @param role
* @param email
* @param autoRenew
* @param notification
* @param accountId
* @param userid
* @param [authorId]
* @param [encryption]
* @param [encryptionPassword]
* @param [prompt]
* @returns user
*/
async createUser(
context: Context,
name: string,
role: UserRoles,
email: string,
autoRenew: boolean,
notification: boolean,
accountId: number,
userid: number,
authorId?: string | undefined,
encryption?: boolean | undefined,
encryptionPassword?: string | undefined,
prompt?: boolean | undefined
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` +
`role: ${role}, ` +
`autoRenew: ${autoRenew}, ` +
`notification: ${notification}, ` +
`accountId: ${accountId}, ` +
`userid: ${userid}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`
);
//authorIdが重複していないかチェックする
if (authorId) {
let isAuthorIdDuplicated = false;
try {
isAuthorIdDuplicated = await this.usersRepository.existsAuthorId(
context,
accountId,
authorId
);
this.logger.log(
`[${context.getTrackingId()}] isAuthorIdDuplicated=${isAuthorIdDuplicated}`
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
if (isAuthorIdDuplicated) {
throw new HttpException(
makeErrorResponse("E010302"),
HttpStatus.BAD_REQUEST
);
}
}
this.logger.log("ランダムパスワード生成開始");
// ランダムなパスワードを生成する
const ramdomPassword = makePassword();
this.logger.log("ランダムパスワード生成完了");
//Azure AD B2Cにユーザーを新規登録する
let externalUser: { sub: string } | ConflictError;
try {
this.logger.log(`name=${name}`);
// idpにユーザーを作成
externalUser = await this.adB2cService.createUser(
context,
email,
ramdomPassword,
name
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`
);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
throw new HttpException(
makeErrorResponse("E010301"),
HttpStatus.BAD_REQUEST
);
}
//Azure AD B2Cに登録したユーザー情報のID(sub)と受け取った情報を使ってDBにユーザーを登録する
let newUser: EntityUser;
try {
//roleに応じてユーザー情報を作成する
const newUserInfo = this.createNewUserInfo(
context,
userid,
role,
accountId,
externalUser.sub,
autoRenew,
notification,
authorId,
encryption,
encryptionPassword,
prompt
);
// ユーザ作成
newUser = await this.usersRepository.createNormalUser(
context,
newUserInfo
);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}]create user failed`);
//リカバリー処理
//Azure AD B2Cに登録したユーザー情報を削除する
await this.deleteB2cUser(externalUser.sub, context);
switch (e.code) {
case "ER_DUP_ENTRY":
//AuthorID重複エラー
throw new HttpException(
makeErrorResponse("E010302"),
HttpStatus.BAD_REQUEST
);
default:
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
}
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`
);
return;
}
// Azure AD B2Cに登録したユーザー情報を削除する
// TODO 「タスク 2452: リトライ処理を入れる箇所を検討し、実装する」の候補
private async deleteB2cUser(externalUserId: string, context: Context) {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteB2cUser.name
} | params: { externalUserId: ${externalUserId} }`
);
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.getTrackingId()}] delete externalUser: ${externalUserId}`
);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteB2cUser.name}`
);
}
}
// DBに登録したユーザー情報を削除する
private async deleteUser(userId: number, context: Context) {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteUser.name
} | params: { userId: ${userId} }`
);
try {
await this.usersRepository.deleteNormalUser(context, userId);
this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`);
} catch (error) {
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}`
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteUser.name}`
);
}
}
// roleを受け取って、roleに応じたnewUserを作成して返却する
private createNewUserInfo(
context: Context,
id: number,
role: UserRoles,
accountId: number,
externalId: string,
autoRenew: boolean,
notification: boolean,
authorId?: string | undefined,
encryption?: boolean | undefined,
encryptionPassword?: string | undefined,
prompt?: boolean | undefined
): newUser {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createNewUserInfo.name
} | params: { ` +
`id: ${id}, ` +
`role: ${role}, ` +
`accountId: ${accountId}, ` +
`authorId: ${authorId}, ` +
`externalId: ${externalId}, ` +
`autoRenew: ${autoRenew}, ` +
`notification: ${notification}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`
);
try {
switch (role) {
case USER_ROLES.NONE:
case USER_ROLES.TYPIST:
return {
id,
account_id: accountId,
external_id: externalId,
auto_renew: autoRenew,
notification,
role,
accepted_dpa_version: null,
accepted_eula_version: null,
accepted_privacy_notice_version: null,
encryption: false,
encryption_password: null,
prompt: false,
author_id: null,
};
case USER_ROLES.AUTHOR:
return {
id,
account_id: accountId,
external_id: externalId,
auto_renew: autoRenew,
notification,
role,
author_id: authorId ?? null,
encryption: encryption ?? false,
encryption_password: encryptionPassword ?? null,
prompt: prompt ?? false,
accepted_dpa_version: null,
accepted_eula_version: null,
accepted_privacy_notice_version: null,
};
default:
//不正なroleが指定された場合はログを出力してエラーを返す
this.logger.error(
`[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}`
);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
}
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
return e;
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createNewUserInfo.name}`
);
}
}
}

View File

@ -0,0 +1,9 @@
import { ApiProperty } from '@nestjs/swagger';
export class VerificationRequest {
@ApiProperty()
inputFilePath: string;
}
export class VerificationResponse {}

View File

@ -0,0 +1,148 @@
import {
Body,
Controller,
HttpStatus,
Post,
Req,
Logger,
HttpException,
} from "@nestjs/common";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import fs from "fs";
import { ApiOperation, ApiResponse, ApiTags } from "@nestjs/swagger";
import { Request } from "express";
import { VerificationRequest, VerificationResponse } from "./types/types";
import { VerificationService } from "./verification.service";
import { makeContext } from "../../common/log";
import {
csvInputFileWithRow,
isAccountsMappingFileArray,
isCardLicensesFileArray,
isCsvInputFileForValidateArray,
} from "../../common/types/types";
import * as csv from "csv";
@ApiTags("verification")
@Controller("verification")
export class VerificationController {
private readonly logger = new Logger(VerificationController.name);
constructor(private readonly verificationService: VerificationService) {}
@Post()
@ApiResponse({
status: HttpStatus.OK,
type: VerificationResponse,
description: "成功時のレスポンス",
})
@ApiResponse({
status: HttpStatus.INTERNAL_SERVER_ERROR,
description: "想定外のサーバーエラー",
})
@ApiOperation({ operationId: "dataVerification" })
async dataVerification(
@Body() body: VerificationRequest,
@Req() req: Request
): Promise<VerificationResponse> {
const context = makeContext("iko", "varification");
const inputFilePath = body.inputFilePath;
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.dataVerification.name
} | params: { inputFilePath: ${inputFilePath}};`
);
try {
// 読み込みファイルのフルパス
const accountTransitionFileFullPath =
inputFilePath + "Account_transition.csv";
const accountMapFileFullPath = inputFilePath + "account_map.json";
const cardLicensesFileFullPath = inputFilePath + "cardLicenses.json";
// ファイル存在チェックと読み込み
if (!fs.existsSync(accountTransitionFileFullPath)) {
this.logger.error(
`file not exists from ${accountTransitionFileFullPath}`
);
throw new Error(
`file not exists from ${accountTransitionFileFullPath}`
);
}
if (!fs.existsSync(accountMapFileFullPath)) {
this.logger.error(`file not exists from ${accountMapFileFullPath}`);
throw new Error(`file not exists from ${accountMapFileFullPath}`);
}
if (!fs.existsSync(cardLicensesFileFullPath)) {
this.logger.error(`file not exists from ${cardLicensesFileFullPath}`);
throw new Error(`file not exists from ${cardLicensesFileFullPath}`);
}
// カードライセンスの登録用ファイル読み込み
const cardLicensesObject = JSON.parse(
fs.readFileSync(cardLicensesFileFullPath, "utf8")
);
// 型ガードcardLicenses
if (!isCardLicensesFileArray(cardLicensesObject)) {
throw new Error("input file is not cardLicensesInputFiles");
}
// アカウントIDマッピング用ファイル読み込み
const accountsMapObject = JSON.parse(
fs.readFileSync(accountMapFileFullPath, "utf8")
);
// 型ガードaccountsMapingFile
if (!isAccountsMappingFileArray(accountsMapObject)) {
throw new Error("input file is not accountsMapingFile");
}
// 移行用csvファイルの読み込みcsv parse
fs.createReadStream(accountTransitionFileFullPath).pipe(
csv.parse({ columns: true, delimiter: "," }, (err, csvInputFiles) => {
// 型ガードcsvInputFile
if (!isCsvInputFileForValidateArray(csvInputFiles)) {
throw new Error("input file is not csvInputFile");
}
const csvInputFileswithRows: csvInputFileWithRow[] = [];
let rowCount = 2; // csvの何行目かを表す変数。ヘッダ行があるので2から開始
for (const csvInputFile of csvInputFiles) {
const csvInputFileswithRow: csvInputFileWithRow = {
...csvInputFile,
row: rowCount
};
csvInputFileswithRows.push(csvInputFileswithRow);
rowCount = rowCount + 1;
}
this.verificationService.varificationData(
context,
inputFilePath,
csvInputFileswithRows,
accountsMapObject,
cardLicensesObject
);
})
);
return {};
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.dataVerification.name}`
);
}
}
}
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
}

View File

@ -0,0 +1,17 @@
import { Module } from "@nestjs/common";
import { VerificationController } from "./verification.controller";
import { VerificationService } from "./verification.service";
import { LicensesRepositoryModule } from "../../repositories/licenses/licenses.repository.module";
import { AccountsRepositoryModule } from "../../repositories/accounts/accounts.repository.module";
import { UsersRepositoryModule } from "../../repositories//users/users.repository.module";
@Module({
imports: [
LicensesRepositoryModule,
AccountsRepositoryModule,
UsersRepositoryModule,
],
controllers: [VerificationController],
providers: [VerificationService],
})
export class VerificationModule {}

View File

@ -0,0 +1,747 @@
import { HttpException, HttpStatus, Injectable, Logger } from "@nestjs/common";
import { Context } from "../../common/log";
import {
AccountsMappingFile,
CardLicensesFile,
csvInputFileWithRow,
VerificationResultDetails,
} from "../../common/types/types";
import {
AUTO_INCREMENT_START,
MIGRATION_TYPE,
COUNTRY_LIST,
} from "../../constants/index";
import { makeErrorResponse } from "../../common/error/makeErrorResponse";
import { LicensesRepositoryService } from "../../repositories/licenses/licenses.repository.service";
import {
License,
CardLicense,
} from "../../repositories/licenses/entity/license.entity";
import { AccountsRepositoryService } from "../../repositories/accounts/accounts.repository.service";
import { UsersRepositoryService } from "../../repositories//users/users.repository.service";
import { Account } from "src/repositories/accounts/entity/account.entity";
import fs from "fs";
@Injectable()
export class VerificationService {
constructor(
private readonly AccountsRepository: AccountsRepositoryService,
private readonly UsersRepository: UsersRepositoryService,
private readonly licensesRepository: LicensesRepositoryService
) {}
private readonly logger = new Logger(VerificationService.name);
/**
* Verification Data
* @param inputFilePath: string
*/
async varificationData(
context: Context,
inputFilePath: string,
csvInputFiles: csvInputFileWithRow[],
accountsMappingInputFiles: AccountsMappingFile[],
cardlicensesInputFiles: CardLicensesFile[]
): Promise<void> {
// パラメータ内容が長大なのでログには出さない
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.varificationData.name}`
);
// this.logger.log(csvInputFiles);
try {
// 件数情報の取得
this.logger.log(`入力ファイルから件数情報を取得する`);
const accountFromFile = csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
);
const accountCountFromFile = accountFromFile.length;
const cardLicensesCountFromFile = cardlicensesInputFiles.length;
const licensesCountFromFile =
csvInputFiles.filter(
(item) =>
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
).length + cardLicensesCountFromFile;
// 管理ユーザ数のカウント
const administratorCountFromFile = accountCountFromFile;
// 一般ユーザ数のカウント
// countryのアカウントに所属するユーザをカウント対象外とする
const countryAccountFromFile = csvInputFiles.filter(
(item) => item.type === "Country"
);
// USER、かつuser_emailが設定なし、かつcountryのアカウントID以外をユーザとする
const normaluserFromFile = csvInputFiles.filter(
(item) =>
item.type === "USER" &&
item.user_email.length !== 0 &&
!countryAccountFromFile.some(
(countryItem) => countryItem.account_id === item.account_id
)
);
const normaluserCountFromFile = normaluserFromFile.length;
// ユーザ重複数のカウント
let mailAdresses: string[] = [];
accountFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.email.length !== 0) {
mailAdresses.push(item.email);
}
});
normaluserFromFile.forEach((item) => {
// メールアドレスの要素を配列に追加
if (item.user_email.length !== 0) {
mailAdresses.push(item.user_email);
}
});
// 重複する要素を抽出
const duplicates: { [key: string]: number } = {};
mailAdresses.forEach((str) => {
duplicates[str.toLowerCase()] =
(duplicates[str.toLowerCase()] || 0) + 1;
});
// 重複する要素と件数を表示
let duplicateCount = 0;
Object.keys(duplicates).forEach((key) => {
const count = duplicates[key];
if (count > 1) {
// 重複件数をカウント
duplicateCount = duplicateCount + (count - 1);
//console.log(`${key}が${count}件`);
}
});
const userCountFromFile =
administratorCountFromFile + normaluserCountFromFile - duplicateCount;
this.logger.log(`accountCountFromFile=${accountCountFromFile}`);
this.logger.log(`cardLicensesCountFromFile=${cardLicensesCountFromFile}`);
this.logger.log(`licensesCountFromFile=${licensesCountFromFile}`);
this.logger.log(`userCountFromFile=${userCountFromFile}`);
// DBから情報を取得する
this.logger.log(`DBの情報を取得する`);
const accounts = await this.AccountsRepository.getAllAccounts(context);
const users = await this.UsersRepository.getAllUsers(context);
const licenses = await this.licensesRepository.getAllLicenses(context);
const cardLicenses = await this.licensesRepository.getAllCardLicense(
context
);
// DB件数のカウント
this.logger.log(`DBの情報から件数を取得する`);
const accountsCountFromDB = accounts.length;
const usersCountFromDB = users.length;
const licensesCountFromDB = licenses.length;
const cardLicensesCountFromDB = cardLicenses.length;
this.logger.log(`accountsCountFromDB=${accountsCountFromDB}`);
this.logger.log(`usersCountFromDB=${usersCountFromDB}`);
this.logger.log(`licensesCountFromDB=${licensesCountFromDB}`);
this.logger.log(`cardLicensesCountFromDB=${cardLicensesCountFromDB}`);
// エラー情報の定義
const VerificationResultDetails: VerificationResultDetails[] = [];
// カードライセンス関連の情報突き合わせ
this.logger.log(`カードライセンス関連の情報突き合わせ`);
const isCardDetailNoError = compareCardLicenses(
VerificationResultDetails,
cardlicensesInputFiles,
cardLicenses,
licenses
);
// ライセンス関連の情報突き合わせ
this.logger.log(`ライセンス関連の情報突き合わせ`);
const isLicensesDetailNoError = compareLicenses(
VerificationResultDetails,
csvInputFiles.filter(
(item) =>
item.type === "USER" && !item.expired_date.startsWith("9999/12/31")
),
licenses.filter((item) => item.expiry_date !== null),
accountsMappingInputFiles
);
// アカウント情報の突き合わせ
this.logger.log(`アカウント関連の情報突き合わせ`);
const isAccountsDetailNoError = compareAccounts(
VerificationResultDetails,
csvInputFiles.filter(
(item) => item.type !== "USER" && item.type !== "Country"
),
csvInputFiles.filter((item) => item.type === "Country"),
accounts,
accountsMappingInputFiles
);
// 結果の判定と出力
this.logger.log(`結果の判定と出力`);
const isAccountCountNoDifference =
accountCountFromFile === accountsCountFromDB;
const isUsersCountNoDifference = userCountFromFile === usersCountFromDB;
const isLicensesCountNoDifference =
licensesCountFromFile === licensesCountFromDB;
const isCardLicensesCountNoDifference =
cardLicensesCountFromFile === cardLicensesCountFromDB;
const isNoDetailError = VerificationResultDetails.length === 0;
const isSummaryNoError =
isAccountCountNoDifference &&
isUsersCountNoDifference &&
isLicensesCountNoDifference &&
isCardLicensesCountNoDifference &&
isNoDetailError;
const summaryString = `
${isSummaryNoError ? "OK" : "NG"}
${
isAccountCountNoDifference ? "OK" : "NG"
}csv件数:${accountCountFromFile}/DB件数:${accountsCountFromDB}
${
isLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${licensesCountFromFile}/DB件数:${licensesCountFromDB}
${
isCardLicensesCountNoDifference ? "OK" : "NG"
}csv件数:${cardLicensesCountFromFile}/DB件数:${cardLicensesCountFromDB}
${
isUsersCountNoDifference ? "OK" : "NG"
}csv件数:${userCountFromFile}/DB件数:${usersCountFromDB}
${isAccountsDetailNoError ? "OK" : "NG"}
${isCardDetailNoError ? "OK" : "NG"}
${isLicensesDetailNoError ? "OK" : "NG"}
`;
// サマリファイルの書き込み
fs.writeFileSync(`${inputFilePath}resultsummary.txt`, summaryString);
// 詳細ファイルの書き込み
// 配列をJSON文字列に変換
const jsonContent = JSON.stringify(VerificationResultDetails, null, 2);
// JSONをファイルに書き込み
fs.writeFileSync(`${inputFilePath}resultdetail.json`, jsonContent);
} catch (e) {
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse("E009999"),
HttpStatus.INTERNAL_SERVER_ERROR
);
} finally {
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.varificationData.name}`
);
}
}
}
// dateを任意のフォーマットに変換する
const getFormattedDate = (
date: Date | null,
format: string,
padHours: boolean = false // trueの場合、hhについてゼロパディングする00→0、01→1、23→23
) => {
if (!date) {
return null;
}
const symbol = {
M: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
m: date.getMinutes(),
s: date.getSeconds(),
};
// hhの値をゼロパディングするかどうかのフラグを確認
const hourSymbol = padHours ? "hh" : "h";
const formatted = format.replace(/(M+|d+|h+|m+|s+)/g, (v) =>
(
(v.length > 1 && v !== hourSymbol ? "0" : "") +
symbol[v.slice(-1) as keyof typeof symbol]
).slice(-2)
);
return formatted.replace(/(y+)/g, (v) =>
date.getFullYear().toString().slice(-v.length)
);
};
// 親の階層がcountryの場合、countryの親を返却する
function transrateCountryHierarchy(
countriesFromFile: csvInputFileWithRow[],
targetParentAccountIdString: string
): string {
for (const countryFromFile of countriesFromFile) {
if (countryFromFile.account_id === targetParentAccountIdString) {
return countryFromFile.parent_id;
}
}
return targetParentAccountIdString;
}
// アカウントIDnumberを対応するアカウントIDstringに変換する
function findAccountIdText(
accountsMappings: AccountsMappingFile[],
targetAccountIdNumber: number
): string {
if (targetAccountIdNumber == null) {
return "";
}
for (const accountsMapping of accountsMappings) {
if (accountsMapping.accountIdNumber === targetAccountIdNumber) {
return accountsMapping.accountIdText;
}
}
return `NO_MATCHED_ACCOUNTID_${targetAccountIdNumber}`; // マッチするものが見つからない場合
}
// 階層numberを対応する階層stringに変換する
function getMigrationTypeByNumber(numberValue: number): string {
switch (numberValue) {
case 1:
return MIGRATION_TYPE.ADMINISTRATOR;
case 2:
return MIGRATION_TYPE.BC;
case 3:
return MIGRATION_TYPE.DISTRIBUTOR;
case 4:
return MIGRATION_TYPE.DEALER;
case 5:
return MIGRATION_TYPE.CUSTOMER;
default:
return `NO_MATCHED_TIER_${numberValue}`;
}
}
// 国(省略版)を対応する国(非省略版)に変換する
function getCountryLabelByValue(value: string): string {
const country = COUNTRY_LIST.find((country) => country.value === value);
return country ? country.label : `NO_MATCHED_COUNTRY_${value}`;
}
// カードライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareCardLicenses(
VerificationResultDetails: VerificationResultDetails[],
cardlicensesInputFiles: CardLicensesFile[],
cardLicenses: CardLicense[],
licenses: License[]
): boolean {
let isNoError = true;
let row = 1; // カードライセンスファイルの行数
for (const cardlicensesInputFile of cardlicensesInputFiles) {
const filterdCardLicenses = cardLicenses.filter(
(cardLicenses) =>
cardLicenses.card_license_key === cardlicensesInputFile.card_license_key
);
if (filterdCardLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "card_license_key",
fileData: cardlicensesInputFile.card_license_key,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
/* issue_id
if (cardlicensesInputFile.issue_id !== filterdCardLicenses[0].issue_id) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
diffTargetTable: "cardLicenses",
columnName: "issue_id",
fileData: cardlicensesInputFile.issue_id.toString(),
databaseData: filterdCardLicenses[0].issue_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
*/
const formattedFileActivated = getFormattedDate(
cardlicensesInputFile.activated_at
? new Date(cardlicensesInputFile.activated_at)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
);
const formattedDbActivated = getFormattedDate(
filterdCardLicenses[0].activated_at,
`yyyy/MM/dd hh:mm:ss`,
true
);
if (formattedFileActivated !== formattedDbActivated) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "cardLicenses",
columnName: "activated_at",
fileData: formattedFileActivated,
databaseData: formattedDbActivated,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
const filterdLicenses = licenses.filter(
(licenses) => licenses.id === filterdCardLicenses[0].license_id
);
if (filterdLicenses.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "id",
fileData: filterdCardLicenses[0].license_id.toString(),
databaseData: "-",
reason: "紐つくライセンスのレコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
if (filterdLicenses[0].expiry_date !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "expiry_date",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].expiry_date,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].account_id !== AUTO_INCREMENT_START) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: AUTO_INCREMENT_START.toString(),
databaseData: filterdLicenses[0].account_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].type !== "CARD") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "type",
fileData: "CARD",
databaseData: filterdLicenses[0].type,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].status !== "Unallocated") {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "status",
fileData: "Unallocated",
databaseData: filterdLicenses[0].status,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].allocated_user_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "allocated_user_id",
fileData: null,
databaseData: filterdLicenses[0].allocated_user_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "order_id",
fileData: null,
databaseData: filterdLicenses[0].order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].deleted_at !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "deleted_at",
fileData: null,
databaseData: getFormattedDate(
filterdLicenses[0].deleted_at,
`yyyy/MM/dd hh:mm:ss`
),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
if (filterdLicenses[0].delete_order_id !== null) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "cardLicenses",
inputRow: row,
diffTargetTable: "licenses",
columnName: "delete_order_id",
fileData: null,
databaseData: filterdLicenses[0].delete_order_id.toString(),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
row = row + 1;
}
return isNoError;
}
// ライセンス情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareLicenses(
VerificationResultDetails: VerificationResultDetails[],
licensesFromFile: csvInputFileWithRow[],
licensesFromDatabase: License[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (let i = 0; i < licensesFromFile.length; i++) {
if (
!licensesFromDatabase[i] ||
licensesFromFile[i].account_id !==
findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "account_id",
fileData: licensesFromFile[i].account_id,
databaseData: licensesFromDatabase[i]
? findAccountIdText(
accountsMappingInputFiles,
licensesFromDatabase[i].account_id
) + `(${licensesFromDatabase[i].account_id})`
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
// expiry_dateについて、時はゼロパディングした値で比較する×0109 ○19
if (
!licensesFromDatabase[i] ||
getFormattedDate(
licensesFromFile[i].expired_date
? new Date(licensesFromFile[i].expired_date)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
) !==
getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: licensesFromFile[i].row,
diffTargetTable: "licenses",
columnName: "expired_date",
fileData: getFormattedDate(
licensesFromFile[i].expired_date
? new Date(licensesFromFile[i].expired_date)
: null,
`yyyy/MM/dd hh:mm:ss`,
true
),
databaseData: licensesFromDatabase[i]
? getFormattedDate(
licensesFromDatabase[i].expiry_date,
`yyyy/MM/dd hh:mm:ss`,
true
)
: "undifined",
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
}
}
return isNoError;
}
// アカウント情報の突き合わせを行い、エラー時はエラー情報配列に情報追加する
function compareAccounts(
VerificationResultDetails: VerificationResultDetails[],
accountsFromFile: csvInputFileWithRow[],
countriesFromFile: csvInputFileWithRow[],
accountsFromDatabase: Account[],
accountsMappingInputFiles: AccountsMappingFile[]
): boolean {
let isNoError = true;
for (const accountFromFile of accountsFromFile) {
// DBレコードの存在チェック
const filterdAccounts = accountsFromDatabase.filter(
(accountsFromDatabase) =>
findAccountIdText(
accountsMappingInputFiles,
accountsFromDatabase.id
) === accountFromFile.account_id
);
if (filterdAccounts.length === 0) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "account_id",
fileData: accountFromFile.account_id,
databaseData: "-",
reason: "レコード無し",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックparent_account_id
const transratedParentId = transrateCountryHierarchy(
countriesFromFile,
accountFromFile.parent_id
);
if (
transratedParentId !==
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "parent_account_id",
fileData:
transratedParentId === accountFromFile.parent_id
? accountFromFile.parent_id
: `${transratedParentId}(${accountFromFile.parent_id})`,
databaseData:
findAccountIdText(
accountsMappingInputFiles,
filterdAccounts[0].parent_account_id
) + `(${filterdAccounts[0].parent_account_id})`,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックtier
if (
accountFromFile.type !== getMigrationTypeByNumber(filterdAccounts[0].tier)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "tier",
fileData: accountFromFile.type,
databaseData: getMigrationTypeByNumber(filterdAccounts[0].tier),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcountry
if (
accountFromFile.country !==
getCountryLabelByValue(filterdAccounts[0].country)
) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "country",
fileData: accountFromFile.country,
databaseData: getCountryLabelByValue(filterdAccounts[0].country),
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
// 項目チェックcompany_name
if (accountFromFile.company_name !== filterdAccounts[0].company_name) {
const VerificationResultDetailsOne: VerificationResultDetails = {
input: "Account_transition",
inputRow: accountFromFile.row,
diffTargetTable: "accounts",
columnName: "company_name",
fileData: accountFromFile.company_name,
databaseData: filterdAccounts[0].company_name,
reason: "内容不一致",
};
VerificationResultDetails.push(VerificationResultDetailsOne);
isNoError = false;
continue;
}
}
return isNoError;
}

Some files were not shown because too many files have changed in this diff Show More