Merged PR 440: 画面実装(テンプレートファイルアップロードPopup)
## 概要 [Task2656: 画面実装(テンプレートファイルアップロードPopup)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2656) - テンプレートファイルアップロードのAPI呼び出し周りを実装 - SASトークン付きURL取得 - Blobストレージへファイルアップロード - アップロード完了 - server側 - `helmet`の`connect-src`を修正 - SASトークン付きURLが想定と違っていたため修正 - DBに保存するURLが想定と違っていたため修正 ## レビューポイント - `connect-src`の`self`以外はローカル環境のみの設定でよさそう? - Popupの挙動で不足している箇所はあるか - アップロードファイルでチェックすべき内容等 ## 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/Task2656?csf=1&web=1&e=iU1huG ## 動作確認状況 - ローカルで確認 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
bf4dc1d717
commit
8265ca38c8
1096
dictation_client/package-lock.json
generated
1096
dictation_client/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -19,6 +19,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@azure/msal-browser": "^2.33.0",
|
"@azure/msal-browser": "^2.33.0",
|
||||||
"@azure/msal-react": "^1.5.3",
|
"@azure/msal-react": "^1.5.3",
|
||||||
|
"@azure/storage-blob": "^12.16.0",
|
||||||
"@reduxjs/toolkit": "^1.8.3",
|
"@reduxjs/toolkit": "^1.8.3",
|
||||||
"@testing-library/jest-dom": "^5.16.4",
|
"@testing-library/jest-dom": "^5.16.4",
|
||||||
"@testing-library/react": "^13.3.0",
|
"@testing-library/react": "^13.3.0",
|
||||||
@ -51,6 +52,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/core": "^7.18.6",
|
"@babel/core": "^7.18.6",
|
||||||
|
"@esbuild-plugins/node-modules-polyfill": "^0.2.2",
|
||||||
"@mdx-js/react": "^2.1.2",
|
"@mdx-js/react": "^2.1.2",
|
||||||
"@openapitools/openapi-generator-cli": "^2.5.2",
|
"@openapitools/openapi-generator-cli": "^2.5.2",
|
||||||
"@types/lodash": "^4.14.191",
|
"@types/lodash": "^4.14.191",
|
||||||
|
|||||||
@ -1,2 +1,3 @@
|
|||||||
|
// TODO 仮で5MBにしているが、OMDS様からの回答待ち
|
||||||
// アップロード可能なファイルサイズの上限(MB)
|
// アップロード可能なファイルサイズの上限(MB)
|
||||||
export const UPLOAD_FILE_SIZE_LIMIT: number = 5 * 1024 * 1024;
|
export const UPLOAD_FILE_SIZE_LIMIT: number = 5 * 1024 * 1024;
|
||||||
|
|||||||
@ -1,9 +1,15 @@
|
|||||||
import { createAsyncThunk } from "@reduxjs/toolkit";
|
import { createAsyncThunk } from "@reduxjs/toolkit";
|
||||||
import { Configuration, GetTemplatesResponse, TemplatesApi } from "api";
|
import {
|
||||||
|
Configuration,
|
||||||
|
FilesApi,
|
||||||
|
GetTemplatesResponse,
|
||||||
|
TemplatesApi,
|
||||||
|
} from "api";
|
||||||
import type { RootState } from "app/store";
|
import type { RootState } from "app/store";
|
||||||
import { ErrorObject, createErrorObject } from "common/errors";
|
import { ErrorObject, createErrorObject } from "common/errors";
|
||||||
import { openSnackbar } from "features/ui/uiSlice";
|
import { openSnackbar } from "features/ui/uiSlice";
|
||||||
import { getTranslationID } from "translation";
|
import { getTranslationID } from "translation";
|
||||||
|
import { BlockBlobClient, ContainerClient } from "@azure/storage-blob";
|
||||||
|
|
||||||
export const listTemplateAsync = createAsyncThunk<
|
export const listTemplateAsync = createAsyncThunk<
|
||||||
GetTemplatesResponse,
|
GetTemplatesResponse,
|
||||||
@ -40,3 +46,69 @@ export const listTemplateAsync = createAsyncThunk<
|
|||||||
return thunkApi.rejectWithValue({ error });
|
return thunkApi.rejectWithValue({ error });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const uploadTemplateAsync = createAsyncThunk<
|
||||||
|
{
|
||||||
|
/* Empty Object */
|
||||||
|
},
|
||||||
|
void,
|
||||||
|
{
|
||||||
|
// rejectした時の返却値の型
|
||||||
|
rejectValue: {
|
||||||
|
error: ErrorObject;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
>("workflow/uploadTemplateAsync", async (args, thunkApi) => {
|
||||||
|
// apiのConfigurationを取得する
|
||||||
|
const { getState } = thunkApi;
|
||||||
|
const state = getState() as RootState;
|
||||||
|
const { configuration, accessToken } = state.auth;
|
||||||
|
const { uploadFile } = state.template.apps;
|
||||||
|
const config = new Configuration(configuration);
|
||||||
|
const filesApi = new FilesApi(config);
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (!uploadFile) {
|
||||||
|
throw new Error("uploadFile is not found");
|
||||||
|
}
|
||||||
|
// SAS付きのURLを取得する
|
||||||
|
const { data } = await filesApi.uploadTemplateLocation({
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
});
|
||||||
|
const { url } = data;
|
||||||
|
|
||||||
|
// ファイルをアップロードする
|
||||||
|
const containerClient = new ContainerClient(url);
|
||||||
|
const blockBlobClient: BlockBlobClient = containerClient.getBlockBlobClient(
|
||||||
|
uploadFile.name
|
||||||
|
);
|
||||||
|
await blockBlobClient.uploadData(uploadFile);
|
||||||
|
|
||||||
|
await filesApi.uploadTemplateFinished(
|
||||||
|
{
|
||||||
|
name: uploadFile.name,
|
||||||
|
url,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
headers: { authorization: `Bearer ${accessToken}` },
|
||||||
|
}
|
||||||
|
);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "info",
|
||||||
|
message: getTranslationID("common.message.success"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return {};
|
||||||
|
} catch (e) {
|
||||||
|
// e ⇒ errorObjectに変換"
|
||||||
|
const error = createErrorObject(e);
|
||||||
|
thunkApi.dispatch(
|
||||||
|
openSnackbar({
|
||||||
|
level: "error",
|
||||||
|
message: getTranslationID("common.message.internalServerError"),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
return thunkApi.rejectWithValue({ error });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
@ -7,6 +7,9 @@ export const selectTemplates = (state: RootState) =>
|
|||||||
export const selectIsLoading = (state: RootState) =>
|
export const selectIsLoading = (state: RootState) =>
|
||||||
state.template.apps.isLoading;
|
state.template.apps.isLoading;
|
||||||
|
|
||||||
|
export const selectIsUploading = (state: RootState) =>
|
||||||
|
state.template.apps.isUploading;
|
||||||
|
|
||||||
export const selectUploadFile = (state: RootState) =>
|
export const selectUploadFile = (state: RootState) =>
|
||||||
state.template.apps.uploadFile;
|
state.template.apps.uploadFile;
|
||||||
|
|
||||||
|
|||||||
@ -7,6 +7,7 @@ export interface TemplateState {
|
|||||||
|
|
||||||
export interface Apps {
|
export interface Apps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isUploading: boolean;
|
||||||
uploadFile?: File;
|
uploadFile?: File;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,11 @@
|
|||||||
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
import { PayloadAction, createSlice } from "@reduxjs/toolkit";
|
||||||
import { TemplateState } from "./state";
|
import { TemplateState } from "./state";
|
||||||
import { listTemplateAsync } from "./operations";
|
import { listTemplateAsync, uploadTemplateAsync } from "./operations";
|
||||||
|
|
||||||
const initialState: TemplateState = {
|
const initialState: TemplateState = {
|
||||||
apps: {
|
apps: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
|
isUploading: false,
|
||||||
uploadFile: undefined,
|
uploadFile: undefined,
|
||||||
},
|
},
|
||||||
domain: {},
|
domain: {},
|
||||||
@ -35,6 +36,15 @@ export const templateSlice = createSlice({
|
|||||||
builder.addCase(listTemplateAsync.rejected, (state) => {
|
builder.addCase(listTemplateAsync.rejected, (state) => {
|
||||||
state.apps.isLoading = false;
|
state.apps.isLoading = false;
|
||||||
});
|
});
|
||||||
|
builder.addCase(uploadTemplateAsync.pending, (state) => {
|
||||||
|
state.apps.isUploading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(uploadTemplateAsync.fulfilled, (state) => {
|
||||||
|
state.apps.isUploading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(uploadTemplateAsync.rejected, (state) => {
|
||||||
|
state.apps.isUploading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@ -1,37 +1,53 @@
|
|||||||
import React, { useCallback } from "react";
|
|
||||||
import { useTranslation } from "react-i18next";
|
|
||||||
import { getTranslationID } from "translation";
|
|
||||||
import styles from "styles/app.module.scss";
|
|
||||||
|
|
||||||
import { useDispatch, useSelector } from "react-redux";
|
|
||||||
import { AppDispatch } from "app/store";
|
import { AppDispatch } from "app/store";
|
||||||
import {
|
import {
|
||||||
cleanupTemplate,
|
|
||||||
selectUploadFile,
|
|
||||||
changeUploadFile,
|
changeUploadFile,
|
||||||
|
cleanupTemplate,
|
||||||
|
listTemplateAsync,
|
||||||
|
selectIsUploading,
|
||||||
|
selectUploadFile,
|
||||||
|
selectUploadFileError,
|
||||||
|
uploadTemplateAsync,
|
||||||
} from "features/workflow/template";
|
} from "features/workflow/template";
|
||||||
|
import React, { useCallback, useEffect, useState } from "react";
|
||||||
|
import { useTranslation } from "react-i18next";
|
||||||
|
import { useDispatch, useSelector } from "react-redux";
|
||||||
|
import styles from "styles/app.module.scss";
|
||||||
|
import { getTranslationID } from "translation";
|
||||||
import close from "../../assets/images/close.svg";
|
import close from "../../assets/images/close.svg";
|
||||||
|
import progress_activit from "../../assets/images/progress_activit.svg";
|
||||||
|
|
||||||
interface AddTemplateFilePopupProps {
|
interface AddTemplateFilePopupProps {
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
isOpen: boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
||||||
props: AddTemplateFilePopupProps
|
props: AddTemplateFilePopupProps
|
||||||
) => {
|
) => {
|
||||||
const { onClose, isOpen } = props;
|
const { onClose } = props;
|
||||||
const [t] = useTranslation();
|
const [t] = useTranslation();
|
||||||
const dispatch: AppDispatch = useDispatch();
|
const dispatch: AppDispatch = useDispatch();
|
||||||
|
|
||||||
// 閉じるボタンを押したときの処理
|
// 保存ボタンを押したかどうか
|
||||||
const closePopup = useCallback(() => {
|
const [isPushUploadButton, setIsPushUploadButton] = useState<boolean>(false);
|
||||||
onClose();
|
|
||||||
dispatch(cleanupTemplate());
|
|
||||||
}, [onClose, dispatch]);
|
|
||||||
|
|
||||||
// アップロード対象のファイル情報
|
// アップロード対象のファイル情報
|
||||||
const uploadFile = useSelector(selectUploadFile);
|
const uploadFile = useSelector(selectUploadFile);
|
||||||
|
// ファイルアップロード中かどうか
|
||||||
|
const isUploading = useSelector(selectIsUploading);
|
||||||
|
// ファイルのエラー
|
||||||
|
const { hasErrorFileSize, hasErrorRequired } = useSelector(
|
||||||
|
selectUploadFileError
|
||||||
|
);
|
||||||
|
|
||||||
|
// ブラウザのウィンドウが閉じられようとしている場合に発火するイベントハンドラ
|
||||||
|
const handleBeforeUnload = (e: BeforeUnloadEvent) => {
|
||||||
|
// テンプレートファイルアップロード中に閉じられようとしている場合、ダイアログを表示させる
|
||||||
|
if (isUploading) {
|
||||||
|
e.preventDefault();
|
||||||
|
// ChromeではreturnValueが必要
|
||||||
|
e.returnValue = "";
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// ファイルが選択されたときの処理
|
// ファイルが選択されたときの処理
|
||||||
const handleFileChange = useCallback(
|
const handleFileChange = useCallback(
|
||||||
@ -43,12 +59,48 @@ export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
|||||||
if (file) {
|
if (file) {
|
||||||
dispatch(changeUploadFile({ file }));
|
dispatch(changeUploadFile({ file }));
|
||||||
}
|
}
|
||||||
|
// 同名のファイルを選択した場合、onChangeが発火しないため、valueをクリアする
|
||||||
|
event.target.value = "";
|
||||||
},
|
},
|
||||||
[dispatch]
|
[dispatch]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ファイルアップロード処理
|
||||||
|
const handleUploadFile = useCallback(async () => {
|
||||||
|
setIsPushUploadButton(true);
|
||||||
|
// エラーチェックを実施
|
||||||
|
if (hasErrorFileSize || hasErrorRequired) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// ファイルアップロード処理
|
||||||
|
const { meta } = await dispatch(uploadTemplateAsync());
|
||||||
|
if (meta.requestStatus === "fulfilled") {
|
||||||
|
onClose();
|
||||||
|
dispatch(listTemplateAsync());
|
||||||
|
}
|
||||||
|
}, [dispatch, hasErrorFileSize, hasErrorRequired, onClose]);
|
||||||
|
|
||||||
|
// コンポーネントがマウントされた時にイベントハンドラを登録する
|
||||||
|
useEffect(() => {
|
||||||
|
window.addEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
// コンポーネントがアンマウントされるときにイベントハンドラを解除する
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("beforeunload", handleBeforeUnload);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(
|
||||||
|
() => () => {
|
||||||
|
// useEffectのreturnとしてcleanupAppsを実行することで、ポップアップのアンマウント時に初期化を行う
|
||||||
|
dispatch(cleanupTemplate());
|
||||||
|
setIsPushUploadButton(false);
|
||||||
|
},
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
[]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`${styles.modal} ${isOpen ? styles.isShow : ""}`}>
|
<div className={`${styles.modal} ${styles.isShow}`}>
|
||||||
<div className={styles.modalBox}>
|
<div className={styles.modalBox}>
|
||||||
<p className={styles.modalTitle}>
|
<p className={styles.modalTitle}>
|
||||||
{t(getTranslationID("templateFilePage.label.addTemplate"))}
|
{t(getTranslationID("templateFilePage.label.addTemplate"))}
|
||||||
@ -57,7 +109,7 @@ export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
|||||||
src={close}
|
src={close}
|
||||||
className={styles.modalTitleIcon}
|
className={styles.modalTitleIcon}
|
||||||
alt="close"
|
alt="close"
|
||||||
onClick={closePopup}
|
onClick={onClose}
|
||||||
/>
|
/>
|
||||||
</p>
|
</p>
|
||||||
<form className={styles.form}>
|
<form className={styles.form}>
|
||||||
@ -80,6 +132,16 @@ export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
|||||||
onChange={handleFileChange}
|
onChange={handleFileChange}
|
||||||
/>
|
/>
|
||||||
</label>
|
</label>
|
||||||
|
{isPushUploadButton && hasErrorRequired && (
|
||||||
|
<span className={`${styles.formError} ${styles.alignCenter}`}>
|
||||||
|
{t(getTranslationID("templateFilePage.label.fileEmptyError"))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{isPushUploadButton && hasErrorFileSize && (
|
||||||
|
<span className={`${styles.formError} ${styles.alignCenter}`}>
|
||||||
|
{t(getTranslationID("templateFilePage.label.fileSizeError"))}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
<dd className={`${styles.full} ${styles.alignCenter}`}>
|
||||||
<input
|
<input
|
||||||
@ -88,8 +150,18 @@ export const AddTemplateFilePopup: React.FC<AddTemplateFilePopupProps> = (
|
|||||||
value={t(
|
value={t(
|
||||||
getTranslationID("templateFilePage.label.addTemplate")
|
getTranslationID("templateFilePage.label.addTemplate")
|
||||||
)}
|
)}
|
||||||
className={`${styles.formSubmit} ${styles.marginBtm1} ${styles.isActive}`}
|
onClick={handleUploadFile}
|
||||||
|
className={`${styles.formSubmit} ${styles.marginBtm1} ${
|
||||||
|
!isUploading ? styles.isActive : ""
|
||||||
|
}`}
|
||||||
/>
|
/>
|
||||||
|
{isUploading && (
|
||||||
|
<img
|
||||||
|
src={progress_activit}
|
||||||
|
className={styles.icLoading}
|
||||||
|
alt="Loading"
|
||||||
|
/>
|
||||||
|
)}
|
||||||
</dd>
|
</dd>
|
||||||
</dl>
|
</dl>
|
||||||
</form>
|
</form>
|
||||||
|
|||||||
@ -31,12 +31,13 @@ export const TemplateFilePage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<AddTemplateFilePopup
|
{isShowAddPopup && (
|
||||||
onClose={() => {
|
<AddTemplateFilePopup
|
||||||
setIsShowAddPopup(false);
|
onClose={() => {
|
||||||
}}
|
setIsShowAddPopup(false);
|
||||||
isOpen={isShowAddPopup}
|
}}
|
||||||
/>
|
/>
|
||||||
|
)}
|
||||||
<div className={styles.wrap}>
|
<div className={styles.wrap}>
|
||||||
<Header userName="XXXXXXXX" />
|
<Header userName="XXXXXXXX" />
|
||||||
<UpdateTokenTimer />
|
<UpdateTokenTimer />
|
||||||
|
|||||||
@ -421,7 +421,7 @@
|
|||||||
"chooseFile": "(de)Choose File",
|
"chooseFile": "(de)Choose File",
|
||||||
"notFileChosen": "(de)- Not file chosen -",
|
"notFileChosen": "(de)- Not file chosen -",
|
||||||
"fileSizeTerms": "(de)Flie Name",
|
"fileSizeTerms": "(de)Flie Name",
|
||||||
"fileSizeError": "(de)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
"fileSizeError": "(de)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。",
|
||||||
"fileEmptyError": "(de)ファイル選択は必須です。ファイルを選択してください。"
|
"fileEmptyError": "(de)ファイル選択は必須です。ファイルを選択してください。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -421,7 +421,7 @@
|
|||||||
"chooseFile": "Choose File",
|
"chooseFile": "Choose File",
|
||||||
"notFileChosen": "- Not file chosen -",
|
"notFileChosen": "- Not file chosen -",
|
||||||
"fileSizeTerms": "Flie Name",
|
"fileSizeTerms": "Flie Name",
|
||||||
"fileSizeError": "選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
"fileSizeError": "選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。",
|
||||||
"fileEmptyError": "ファイル選択は必須です。ファイルを選択してください。"
|
"fileEmptyError": "ファイル選択は必須です。ファイルを選択してください。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -421,7 +421,7 @@
|
|||||||
"chooseFile": "(es)Choose File",
|
"chooseFile": "(es)Choose File",
|
||||||
"notFileChosen": "(es)- Not file chosen -",
|
"notFileChosen": "(es)- Not file chosen -",
|
||||||
"fileSizeTerms": "(es)Flie Name",
|
"fileSizeTerms": "(es)Flie Name",
|
||||||
"fileSizeError": "(es)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
"fileSizeError": "(es)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。",
|
||||||
"fileEmptyError": "(es)ファイル選択は必須です。ファイルを選択してください。"
|
"fileEmptyError": "(es)ファイル選択は必須です。ファイルを選択してください。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -421,7 +421,7 @@
|
|||||||
"chooseFile": "(fr)Choose File",
|
"chooseFile": "(fr)Choose File",
|
||||||
"notFileChosen": "(fr)- Not file chosen -",
|
"notFileChosen": "(fr)- Not file chosen -",
|
||||||
"fileSizeTerms": "(fr)Flie Name",
|
"fileSizeTerms": "(fr)Flie Name",
|
||||||
"fileSizeError": "(fr)選択されたファイルのサイズが大きすぎます。サイズが?MB以下のファイルを選択してください。",
|
"fileSizeError": "(fr)選択されたファイルのサイズが大きすぎます。サイズが5MB以下のファイルを選択してください。",
|
||||||
"fileEmptyError": "(fr)ファイル選択は必須です。ファイルを選択してください。"
|
"fileEmptyError": "(fr)ファイル選択は必須です。ファイルを選択してください。"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -16,4 +16,9 @@ export default defineConfig({
|
|||||||
minify: false,
|
minify: false,
|
||||||
},
|
},
|
||||||
plugins: [env(), tsconfigPaths(), react(), sassDts()],
|
plugins: [env(), tsconfigPaths(), react(), sassDts()],
|
||||||
|
resolve: {
|
||||||
|
alias: {
|
||||||
|
os: "rollup-plugin-node-polyfills/polyfills/os",
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -8,6 +8,7 @@ AZURE_CLIENT_SECRET=xxxxxxxx
|
|||||||
ADB2C_TENANT_ID=xxxxxxxx
|
ADB2C_TENANT_ID=xxxxxxxx
|
||||||
ADB2C_CLIENT_ID=xxxxxxxx
|
ADB2C_CLIENT_ID=xxxxxxxx
|
||||||
ADB2C_CLIENT_SECRET=xxxxxxxx
|
ADB2C_CLIENT_SECRET=xxxxxxxx
|
||||||
|
ADB2C_ORIGIN=https://zzzzzzzzzz
|
||||||
KEY_VAULT_NAME=kv-odms-secret-dev
|
KEY_VAULT_NAME=kv-odms-secret-dev
|
||||||
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_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"
|
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd\nHYLSaJvUPPIKYoiOeVLD1paWNI517Vkaoh0ngprcKOdv6T1N07V4igK7mOim2zY3\nyCTR6wcWR3PfFJrl9vh5SOo79koZoJb27YiM4jtxfx2dezzp0T2GoNR5rRolPUbW\nFJXnDe0DVXYXpJLb4LAlF2XAyYX0SYKUVUsJnzm5k4xbXtnwPwVbpm0EdswBE6qS\nfiL9zWk9dvHoKzSnfSDzDFoFcEoVchawzYXf/MM1YR4wo5XyzECc6Q5Ah4z522//\nmBNNaDHv83Yuw3mGShT73iJ0JQdkTturshv2Ecma38r6ftrIwNYXw4VVatJM8+GO\nOQIDAQAB\n-----END PUBLIC KEY-----\n"
|
||||||
|
|||||||
@ -620,7 +620,7 @@ export class FilesService {
|
|||||||
await this.templateFilesRepository.upsertTemplateFile(
|
await this.templateFilesRepository.upsertTemplateFile(
|
||||||
accountId,
|
accountId,
|
||||||
fileName,
|
fileName,
|
||||||
url,
|
fileUrl,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this.logger.error(`error=${e}`);
|
this.logger.error(`error=${e}`);
|
||||||
|
|||||||
@ -281,8 +281,8 @@ export class BlobstorageService {
|
|||||||
},
|
},
|
||||||
sharedKeyCredential,
|
sharedKeyCredential,
|
||||||
);
|
);
|
||||||
|
// baseパスの末尾に/をつけないとパスの最後のセグメントが無視される
|
||||||
const url = new URL('Templates', containerClient.url);
|
const url = new URL('Templates', `${containerClient.url}/`);
|
||||||
url.search = `${sasToken}`;
|
url.search = `${sasToken}`;
|
||||||
|
|
||||||
return url.toString();
|
return url.toString();
|
||||||
|
|||||||
@ -5,11 +5,17 @@ import { AppModule } from './app.module';
|
|||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import helmet from 'helmet';
|
import helmet from 'helmet';
|
||||||
const helmetDirectives = helmet.contentSecurityPolicy.getDefaultDirectives();
|
const helmetDirectives = helmet.contentSecurityPolicy.getDefaultDirectives();
|
||||||
helmetDirectives['connect-src'] = [
|
helmetDirectives['connect-src'] =
|
||||||
"'self'",
|
process.env.STAGE === 'local'
|
||||||
'https://adb2codmsdev.b2clogin.com/adb2codmsdev.onmicrosoft.com/b2c_1_signin_dev/v2.0/.well-known/openid-configuration',
|
? [
|
||||||
'https://adb2codmsdev.b2clogin.com/adb2codmsdev.onmicrosoft.com/b2c_1_signin_dev/oauth2/v2.0/token',
|
"'self'",
|
||||||
];
|
process.env.ADB2C_ORIGIN,
|
||||||
|
process.env.STORAGE_ACCOUNT_ENDPOINT_US,
|
||||||
|
process.env.STORAGE_ACCOUNT_ENDPOINT_AU,
|
||||||
|
process.env.STORAGE_ACCOUNT_ENDPOINT_EU,
|
||||||
|
]
|
||||||
|
: ["'self'"];
|
||||||
|
|
||||||
helmetDirectives['navigate-to'] = ["'self'"];
|
helmetDirectives['navigate-to'] = ["'self'"];
|
||||||
helmetDirectives['style-src'] = ["'self'", 'https:'];
|
helmetDirectives['style-src'] = ["'self'", 'https:'];
|
||||||
helmetDirectives['report-uri'] = ["'self'"];
|
helmetDirectives['report-uri'] = ["'self'"];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user