Merge branch 'develop'

This commit is contained in:
SAITO-PC-3\saito.k 2024-02-19 20:02:53 +09:00
commit 701ee2e6c8
68 changed files with 31432 additions and 118 deletions

View File

@ -0,0 +1,32 @@
FROM node:18.13.0-buster
RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
echo "Asia/Tokyo" > /etc/timezone
# Options for setup script
ARG INSTALL_ZSH="true"
ARG UPGRADE_PACKAGES="false"
ARG USERNAME=vscode
# 1000 はnodeで使われているためずらす
ARG USER_UID=1001
ARG USER_GID=$USER_UID
# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
COPY library-scripts/common-debian.sh /tmp/library-scripts/
RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
&& apt-get install default-jre -y \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# Install mob
RUN curl -sL install.mob.sh | sh
# 以下 ユーザー権限で実施
USER $USERNAME
# copy init-script
COPY --chown=$USERNAME:$USERNAME init.sh /home/${USERNAME}/
RUN chmod +x /home/${USERNAME}/init.sh
# 初期化を行う
# node imageのデフォルトENTRYPOINTが邪魔するため上書き
ENTRYPOINT /home/vscode/init.sh

View File

@ -0,0 +1,43 @@
// For format details, see https://aka.ms/vscode-remote/devcontainer.json or this file's README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.137.0/containers/go
{
"name": "data_migration_tools client",
"dockerComposeFile": ["./docker-compose.yml"],
"service": "client",
//
"shutdownAction": "none",
"workspaceFolder": "/app/data_migration_tools/client",
"runArgs": ["--cap-add=SYS_PTRACE", "--security-opt", "seccomp=unconfined"],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true, // eslint
"source.fixAll.stylelint": true // Stylelint
},
// formatter
"editor.defaultFormatter": "esbenp.prettier-vscode", // Prettier
"editor.formatOnSave": true,
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.renderWhitespace": "all",
"editor.insertSpaces": false,
"editor.renderLineHighlight": "all"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint",
"salbert.comment-ts",
"gruntfuggly.todo-tree",
"esbenp.prettier-vscode",
"ms-vsliveshare.vsliveshare",
"albymor.increment-selection",
"eamodio.gitlens",
"wmaurer.change-case"
],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
"postCreateCommand": "sudo npm install @openapitools/openapi-generator-cli -g && sudo chown -R vscode:vscode /app/data_migration_tools/client",
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@ -0,0 +1,20 @@
version: "3"
services:
client:
env_file: ../.env
build: .
working_dir: /app/data_migration_tools/client
ports:
- "127.0.0.1:3100:3100"
volumes:
- ../../../:/app
- data_migration_tools_client_node_modules:/app/data_migration_tools/client/node_modules
expose:
- "3100"
environment:
- CHOKIDAR_USEPOLLING=true
# Data Volume として永続化する
volumes:
data_migration_tools_client_node_modules:

View File

@ -0,0 +1,22 @@
#!/bin/bash
#
# Init Script for client Container
#
echo [init.sh] client initialize.
# /app の権限がデフォルトでは node ユーザーになっているため、
# 権限確認し、vscode ユーザでない場合付け替える
ls -ld /app | grep vscode
if [ $? -ne 0 ]; then
echo [init.sh] change /app owner to vscode.
sudo chown -R vscode:vscode /app
fi
cd /app/data_migration_tools/client
echo [init.sh] \"npm ci\" start.
npm ci
echo [init.sh] initialize completed!
sleep infinity

View File

@ -0,0 +1,190 @@
#!/usr/bin/env bash
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag]
INSTALL_ZSH=${1:-"true"}
USERNAME=${2:-"vscode"}
USER_UID=${3:-1000}
USER_GID=${4:-1000}
UPGRADE_PACKAGES=${5:-"true"}
set -e
if [ "$(id -u)" -ne 0 ]; then
echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi
# Treat a user name of "none" as root
if [ "${USERNAME}" = "none" ] || [ "${USERNAME}" = "root" ]; then
USERNAME=root
USER_UID=0
USER_GID=0
fi
# Load markers to see which steps have already run
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
if [ -f "${MARKER_FILE}" ]; then
echo "Marker file found:"
cat "${MARKER_FILE}"
source "${MARKER_FILE}"
fi
# Ensure apt is in non-interactive to avoid prompts
export DEBIAN_FRONTEND=noninteractive
# Function to call apt-get if needed
apt-get-update-if-needed()
{
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update
else
echo "Skipping apt-get update."
fi
}
# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
apt-get-update-if-needed
PACKAGE_LIST="apt-utils \
git \
openssh-client \
less \
iproute2 \
procps \
curl \
wget \
unzip \
zip \
nano \
jq \
lsb-release \
ca-certificates \
apt-transport-https \
dialog \
gnupg2 \
libc6 \
libgcc1 \
libgssapi-krb5-2 \
libicu[0-9][0-9] \
liblttng-ust0 \
libstdc++6 \
zlib1g \
locales \
sudo"
# Install libssl1.1 if available
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
PACKAGE_LIST="${PACKAGE_LIST} libssl1.1"
fi
# Install appropriate version of libssl1.0.x if available
LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
# Debian 9
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2"
elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
# Ubuntu 18.04, 16.04, earlier
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0"
fi
fi
echo "Packages to verify are installed: ${PACKAGE_LIST}"
apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
PACKAGES_ALREADY_INSTALLED="true"
fi
# Get to latest versions of all packages
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
apt-get-update-if-needed
apt-get -y upgrade --no-install-recommends
apt-get autoremove -y
fi
# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
# Common need for both applications and things like the agnoster ZSH theme.
if [ "${LOCALE_ALREADY_SET}" != "true" ]; then
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
LOCALE_ALREADY_SET="true"
fi
# Create or update a non-root user to match UID/GID - see https://aka.ms/vscode-remote/containers/non-root-user.
if id -u $USERNAME > /dev/null 2>&1; then
# User exists, update if needed
if [ "$USER_GID" != "$(id -G $USERNAME)" ]; then
groupmod --gid $USER_GID $USERNAME
usermod --gid $USER_GID $USERNAME
fi
if [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
usermod --uid $USER_UID $USERNAME
fi
else
# Create user
groupadd --gid $USER_GID $USERNAME
useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
fi
# Add add sudo support for non-root user
if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
chmod 0440 /etc/sudoers.d/$USERNAME
EXISTING_NON_ROOT_USER="${USERNAME}"
fi
# .bashrc/.zshrc snippet
RC_SNIPPET="$(cat << EOF
export USER=\$(whoami)
export PATH=\$PATH:\$HOME/.local/bin
if [[ \$(which code-insiders 2>&1) && ! \$(which code 2>&1) ]]; then
alias code=code-insiders
fi
EOF
)"
# Ensure ~/.local/bin is in the PATH for root and non-root users for bash. (zsh is later)
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
echo "${RC_SNIPPET}" | tee -a /root/.bashrc >> /etc/skel/.bashrc
if [ "${USERNAME}" != "root" ]; then
echo "${RC_SNIPPET}" >> /home/$USERNAME/.bashrc
chown $USER_UID:$USER_GID /home/$USERNAME/.bashrc
fi
RC_SNIPPET_ALREADY_ADDED="true"
fi
# Optionally install and configure zsh
if [ "${INSTALL_ZSH}" = "true" ] && [ ! -d "/root/.oh-my-zsh" ] && [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
apt-get-update-if-needed
apt-get install -y zsh
curl -fsSLo- https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash 2>&1
echo -e "${RC_SNIPPET}\nDEFAULT_USER=\$USER\nprompt_context(){}" >> /root/.zshrc
cp -fR /root/.oh-my-zsh /etc/skel
cp -f /root/.zshrc /etc/skel
sed -i -e "s/\/root\/.oh-my-zsh/\/home\/\$(whoami)\/.oh-my-zsh/g" /etc/skel/.zshrc
if [ "${USERNAME}" != "root" ]; then
cp -fR /etc/skel/.oh-my-zsh /etc/skel/.zshrc /home/$USERNAME
chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc
fi
ZSH_ALREADY_INSTALLED="true"
fi
# Write marker file
mkdir -p "$(dirname "${MARKER_FILE}")"
echo -e "\
PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\
ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
echo "Done!"

View File

View File

@ -0,0 +1 @@
VITE_STAGE=local

View File

@ -0,0 +1,6 @@
node_modules/
build/
.eslintrc.js
jest.config.js
vite.config.ts
.env.local

View File

@ -0,0 +1,149 @@
module.exports = {
env: {
browser: true,
},
extends: [
"airbnb",
"plugin:@typescript-eslint/recommended",
"plugin:react/recommended",
"prettier",
],
parser: "@typescript-eslint/parser",
parserOptions: {
project: "./tsconfig.json",
ecmaFeatures: {
jsx: true,
},
ecmaVersion: 12,
sourceType: "module",
},
plugins: [
"react",
"@typescript-eslint",
"prettier",
"react-hooks",
"prefer-arrow",
],
rules: {
"react/jsx-uses-react": "off",
"react/react-in-jsx-scope": "off",
"react/function-component-definition": [
"error",
{
namedComponents: "arrow-function",
unnamedComponents: "arrow-function",
},
],
"react/jsx-filename-extension": [
"error",
{
extensions: ["jsx", "tsx"],
},
],
"import/extensions": [
"error",
"ignorePackages",
{
js: "never",
jsx: "never",
ts: "never",
tsx: "never",
},
],
"import/no-unresolved": "off",
"import/prefer-default-export": "off",
"no-use-before-define": 0,
"prettier/prettier": "error",
"no-param-reassign": 0,
"@typescript-eslint/no-use-before-define": "off",
"@typescript-eslint/prefer-interface": "off",
"@typescript-eslint/indent": "off",
"react-hooks/rules-of-hooks": "error",
// Checks rules of Hooks
"react-hooks/exhaustive-deps": "warn",
// Checks effect dependencies
"import/no-extraneous-dependencies": [
"error",
{
devDependencies: [
"**/*.test.js",
"**/*.spec.js",
"**/*.test.ts",
"**/*.spec.ts",
"**/*.test.tsx",
"**/*.spec.tsx",
"**/setupTests.ts",
],
},
],
camelcase: "off",
"prefer-arrow/prefer-arrow-functions": [
"error",
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
"func-style": [
"error",
"expression",
{
allowArrowFunctions: true,
},
],
"react/no-multi-comp": ["error"],
"react/jsx-pascal-case": ["error"],
"@typescript-eslint/naming-convention": [
"error", // default config
{
selector: "default",
format: ["camelCase"],
leadingUnderscore: "allow",
trailingUnderscore: "allow",
},
{
selector: "variable",
format: ["camelCase", "UPPER_CASE"],
leadingUnderscore: "allow",
trailingUnderscore: "allow",
},
{
selector: "typeLike",
format: ["PascalCase"],
}, // custom config
{
selector: ["property"],
format: ["camelCase", "PascalCase"],
},
{
selector: ["variable"],
types: ["function"],
format: ["camelCase", "PascalCase"],
},
{
selector: "interface",
format: ["PascalCase"],
custom: {
regex: "^I[A-Z]",
match: false,
},
},
],
"max-lines": ["error", 3000],
},
settings: {
"import/parsers": {
"@typescript-eslint/parser": [".ts", ".tsx"],
},
"import/resolver": {
node: {
extensions: [".js", "jsx", ".ts", ".tsx"],
paths: ["src"],
},
},
react: {
version: "detect",
},
},
};

24
data_migration_tools/client/.gitignore vendored Normal file
View File

@ -0,0 +1,24 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.env.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.eslintcache
# credentials
credentials

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/src/favicon.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite App</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>

View File

@ -0,0 +1 @@
cp -r build /app/data_migration_tools/server

14043
data_migration_tools/client/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,93 @@
{
"name": "client",
"private": true,
"version": "0.1.0",
"engines": {
"node": ">=18.0.0"
},
"scripts": {
"start": "vite",
"build": "tsc && vite build",
"build:prod": "tsc && vite build",
"build:local": "tsc && vite build && sh localdeploy.sh",
"preview": "vite preview",
"typecheck": "tsc --noEmit",
"codegen": "sh codegen.sh",
"lint": "eslint --cache . --ext .js,.ts,.tsx",
"lint:fix": "npm run lint -- --fix"
},
"dependencies": {
"@azure/storage-blob": "^12.14.0",
"@reduxjs/toolkit": "^1.8.3",
"@testing-library/jest-dom": "^5.16.4",
"@testing-library/react": "^13.3.0",
"@testing-library/user-event": "^14.2.1",
"@types/jest": "^27.5.2",
"@types/node": "^17.0.45",
"@types/react": "^18.0.14",
"@types/react-dom": "^18.0.6",
"@types/react-router-dom": "^5.3.3",
"@types/redux-mock-store": "^1.0.3",
"axios": "^0.27.2",
"eslint-plugin-prefer-arrow": "^1.2.3",
"lodash": "^4.17.21",
"luxon": "^3.3.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-google-recaptcha-v3": "^1.10.0",
"react-paginate": "^8.1.3",
"react-redux": "^8.0.2",
"react-router-dom": "^6.4.1",
"redux-mock-store": "^1.5.4",
"redux-thunk": "^2.4.1",
"styled-components": "^5.3.5",
"typescript": "^4.7.4",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@babel/core": "^7.18.6",
"@mdx-js/react": "^2.1.2",
"@openapitools/openapi-generator-cli": "^2.5.2",
"@types/lodash": "^4.14.191",
"@types/luxon": "^3.2.0",
"@types/react": "^18.0.0",
"@types/react-dom": "^18.0.0",
"@types/redux-mock-store": "^1.0.3",
"@types/styled-components": "^5.1.25",
"@typescript-eslint/eslint-plugin": "^5.30.5",
"@typescript-eslint/parser": "^5.30.5",
"@vitejs/plugin-react": "^1.3.2",
"babel-loader": "^8.2.5",
"eslint": "^8.19.0",
"eslint-config-airbnb": "^19.0.4",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-jsx-a11y": "^6.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.30.1",
"eslint-plugin-react-hooks": "^4.6.0",
"license-checker": "^25.0.1",
"prettier": "^2.7.1",
"redux-mock-store": "^1.5.4",
"typescript": "^4.7.4",
"vite": "^2.9.9",
"vite-plugin-env-compatible": "^1.1.1",
"vite-tsconfig-paths": "^3.5.0"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
],
"overrides": [
{
"files": [
"**/*.stories.*"
],
"rules": {
"import/no-anonymous-default-export": "off"
}
}
]
}
}

View File

@ -0,0 +1,11 @@
import AppRouter from "AppRouter";
import { BrowserRouter } from "react-router-dom";
import "./styles/GlobalStyle.css";
const App = (): JSX.Element => (
<BrowserRouter>
<AppRouter />
</BrowserRouter>
);
export default App;

View File

@ -0,0 +1,9 @@
import { Route, Routes } from "react-router-dom";
const AppRouter: React.FC = () => (
<Routes>
<Route path="/" element={<div />} />
</Routes>
);
export default AppRouter;

View File

@ -0,0 +1,16 @@
import { configureStore, ThunkAction, Action } from "@reduxjs/toolkit";
export const store = configureStore({
reducer: {
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppThunk<ReturnType = void> = ThunkAction<
ReturnType,
RootState,
unknown,
Action<string>
>;
export type AppDispatch = typeof store.dispatch;

View File

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

View File

@ -0,0 +1,3 @@
export interface ErrorObject {
message: string;
}

View File

@ -0,0 +1,33 @@
.snackbar {
position: fixed;
top: -100px; /* 上から現れるための初期位置 */
left: 50%;
transform: translateX(-50%);
background-color: #323232;
color: #ffffff;
border-radius: 5px;
padding: 16px;
display: flex;
justify-content: space-between;
align-items: center;
min-width: 300px;
max-width: 80%;
z-index: 1000;
transition: top 0.5s ease-in-out; /* アニメーション効果の追加 */
}
.snackbar.show {
top: 20px; /* 最終的な表示位置 */
}
.snackbar button {
background: transparent;
border: none;
color: #ffffff;
cursor: pointer;
}
.snackbar p {
margin: 0;
margin-right: 16px;
}

View File

@ -0,0 +1,19 @@
import React, { FC } from "react";
import "./SnackBar.css";
interface SnackBarProps {
message: string;
show: boolean;
closeSnackBar: (e: React.MouseEvent<HTMLButtonElement>) => void;
}
const SnackBar: FC<SnackBarProps> = ({ message, show, closeSnackBar }) => (
<div className={`snackbar${show ? " show" : ""}`}>
<p>{message}</p>
<button type="button" onClick={closeSnackBar}>
</button>
</div>
);
export default SnackBar;

View File

@ -0,0 +1,15 @@
<svg width="410" height="404" viewBox="0 0 410 404" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M399.641 59.5246L215.643 388.545C211.844 395.338 202.084 395.378 198.228 388.618L10.5817 59.5563C6.38087 52.1896 12.6802 43.2665 21.0281 44.7586L205.223 77.6824C206.398 77.8924 207.601 77.8904 208.776 77.6763L389.119 44.8058C397.439 43.2894 403.768 52.1434 399.641 59.5246Z" fill="url(#paint0_linear)"/>
<path d="M292.965 1.5744L156.801 28.2552C154.563 28.6937 152.906 30.5903 152.771 32.8664L144.395 174.33C144.198 177.662 147.258 180.248 150.51 179.498L188.42 170.749C191.967 169.931 195.172 173.055 194.443 176.622L183.18 231.775C182.422 235.487 185.907 238.661 189.532 237.56L212.947 230.446C216.577 229.344 220.065 232.527 219.297 236.242L201.398 322.875C200.278 328.294 207.486 331.249 210.492 326.603L212.5 323.5L323.454 102.072C325.312 98.3645 322.108 94.137 318.036 94.9228L279.014 102.454C275.347 103.161 272.227 99.746 273.262 96.1583L298.731 7.86689C299.767 4.27314 296.636 0.855181 292.965 1.5744Z" fill="url(#paint1_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="6.00017" y1="32.9999" x2="235" y2="344" gradientUnits="userSpaceOnUse">
<stop stop-color="#41D1FF"/>
<stop offset="1" stop-color="#BD34FE"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="194.651" y1="8.81818" x2="236.076" y2="292.989" gradientUnits="userSpaceOnUse">
<stop stop-color="#FFEA83"/>
<stop offset="0.0833333" stop-color="#FFDD35"/>
<stop offset="1" stop-color="#FFA800"/>
</linearGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,23 @@
import { store } from "app/store";
import React from "react";
import { createRoot } from "react-dom/client";
import { Provider } from "react-redux";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
const container = document.getElementById("root");
if (container) {
const root = createRoot(container);
root.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>
);
}
// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.
// Learn more about service workers: https://bit.ly/CRA-PWA
serviceWorker.unregister();

View File

@ -0,0 +1,148 @@
/* eslint-disable */
// This optional code is used to register a service worker.
// register() is not called by default.
// This lets the app load faster on subsequent visits in production, and gives
// it offline capabilities. However, it also means that developers (and users)
// will only see deployed updates on subsequent visits to a page, after all the
// existing tabs open on the page have been closed, since previously cached
// resources are updated in the background.
// To learn more about the benefits of this model and instructions on how to
// opt-in, read https://bit.ly/CRA-PWA
const isLocalhost = Boolean(
window.location.hostname === "localhost" ||
// [::1] is the IPv6 localhost address.
window.location.hostname === "[::1]" ||
// 127.0.0.0/8 are considered localhost for IPv4.
window.location.hostname.match(
/^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
)
);
type Config = {
onSuccess?: (registration: ServiceWorkerRegistration) => void;
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener("load", () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
"This web app is being served cache-first by a service " +
"worker. To learn more, visit https://bit.ly/CRA-PWA"
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === "installed") {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
"New content is available and will be used when all " +
"tabs for this page are closed. See https://bit.ly/CRA-PWA."
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.log("Content is cached for offline use.");
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
})
.catch((error) => {
console.error("Error during service worker registration:", error);
});
}
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { "Service-Worker": "script" },
})
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get("content-type");
if (
response.status === 404 ||
(contentType != null && contentType.indexOf("javascript") === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
});
} else {
// Service worker found. Proceed as normal.
registerValidSW(swUrl, config);
}
})
.catch(() => {
console.log(
"No internet connection found. App is running in offline mode."
);
});
}
export function unregister() {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.ready
.then((registration) => {
registration.unregister();
})
.catch((error) => {
console.error(error.message);
});
}
}

View File

@ -0,0 +1,37 @@
/**
* bring from react-scripts/lib/react-app.d.ts
*/
declare namespace NodeJS {
interface ProcessEnv {
/* eslint-disable @typescript-eslint/naming-convention */
// local環境はdevelopment、staging環境は production になる
readonly NODE_ENV: "development" | "develop" | "production" | "test";
readonly PUBLIC_URL: string;
/* eslint-enable @typescript-eslint/naming-convention */
}
}
declare module "*.gif" {
const src: string;
export default src;
}
declare module "*.jpg" {
const src: string;
export default src;
}
declare module "*.jpeg" {
const src: string;
export default src;
}
declare module "*.png" {
const src: string;
export default src;
}
declare module "*.svg" {
const src: string;
export default src;
}

View File

@ -0,0 +1,11 @@
/* eslint-disable @typescript-eslint/naming-convention */
// 環境変数のコード補完
/// <reference types="vite/client" />
interface ImportMetaEnv {
readonly VITE_STAGE: string;
readonly VITE_ODMS_CLOUD_BASE_PATH: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"target": "es2019",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"baseUrl": "src",
"plugins": [{ "name": "typescript-styled-plugin" }]
},
"include": ["src"]
}

View File

@ -0,0 +1,18 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import tsconfigPaths from "vite-tsconfig-paths";
import env from "vite-plugin-env-compatible";
export default defineConfig({
server: {
host: true,
port: 3100,
open: true,
},
build: {
outDir: "build",
sourcemap: true,
minify: false,
},
plugins: [env(), tsconfigPaths(), react()],
});

View File

@ -0,0 +1,31 @@
FROM node:18.13.0-buster
RUN /bin/cp /usr/share/zoneinfo/Asia/Tokyo /etc/localtime && \
echo "Asia/Tokyo" > /etc/timezone
# Options for setup script
ARG INSTALL_ZSH="true"
ARG UPGRADE_PACKAGES="false"
ARG USERNAME=vscode
# 1000 はnodeで使われているためずらす
ARG USER_UID=1001
ARG USER_GID=$USER_UID
# Install needed packages and setup non-root user. Use a separate RUN statement to add your own dependencies.
COPY library-scripts/common-debian.sh /tmp/library-scripts/
RUN bash /tmp/library-scripts/common-debian.sh "${INSTALL_ZSH}" "${USERNAME}" "${USER_UID}" "${USER_GID}" "${UPGRADE_PACKAGES}" \
&& apt-get install default-jre -y \
&& apt-get clean -y && rm -rf /var/lib/apt/lists/* /tmp/library-scripts
# Install NestJS
RUN npm i -g @nestjs/cli
# 以下 ユーザー権限で実施
USER $USERNAME
# copy init-script
COPY --chown=$USERNAME:$USERNAME init.sh /home/${USERNAME}/
RUN chmod +x /home/${USERNAME}/init.sh
# 初期化を行う
# node imageのデフォルトENTRYPOINTが邪魔するため上書き
ENTRYPOINT /home/vscode/init.sh

View File

@ -0,0 +1,56 @@
// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
// https://github.com/microsoft/vscode-dev-containers/tree/v0.209.6/containers/javascript-node
{
"name": "data_migration_tools server",
"dockerComposeFile": [
"./docker-compose.yml"
],
"service": "server",
//
"shutdownAction": "none",
"workspaceFolder": "/app/data_migration_tools/server",
"runArgs": [
"--cap-add=SYS_PTRACE",
"--security-opt",
"seccomp=unconfined"
],
// Set *default* container specific settings.json values on container create.
"settings": {
"terminal.integrated.shell.linux": "/bin/bash",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
},
"eslint.format.enable": false,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
// formatter
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.renderWhitespace": "all",
"editor.insertSpaces": false,
"editor.renderLineHighlight": "all"
},
// Add the IDs of extensions you want installed when the container is created.
"extensions": [
"dbaeumer.vscode-eslint",
"salbert.comment-ts",
"gruntfuggly.todo-tree",
"esbenp.prettier-vscode",
"ms-vsliveshare.vsliveshare",
"albymor.increment-selection",
"eamodio.gitlens",
"wmaurer.change-case"
],
// Use 'postCreateCommand' to run commands after the container is created.
// "postCreateCommand": "yarn install",
"postCreateCommand": "sudo chown -R vscode:vscode /app/data_migration_tools/server",
// Uncomment to connect as a non-root user. See https://aka.ms/vscode-remote/containers/non-root.
"remoteUser": "vscode"
}

View File

@ -0,0 +1,18 @@
version: "3"
services:
server:
env_file: ../.env
build: .
working_dir: /app/data_migration_tools/server
ports:
- "127.0.0.1:8280:8280"
volumes:
- ../../../:/app
- data_migration_tools_server_node_modules:/app/data_migration_tools/server/node_modules
expose:
- "8280"
environment:
- CHOKIDAR_USEPOLLING=true
volumes:
data_migration_tools_server_node_modules:

View File

@ -0,0 +1,20 @@
#!/bin/bash
#
# Init Script for server Container
#
echo [init.sh] server initialize.
# /app の権限がデフォルトでは node ユーザーになっているため、
# 権限確認し、vscode ユーザでない場合付け替える
ls -ld /app | grep vscode
if [ $? -ne 0 ]; then
echo [init.sh] change /app owner to vscode.
sudo chown -R vscode:vscode /app
fi
cd /app/server
echo [init.sh] initialize completed!
sleep infinity

View File

@ -0,0 +1,190 @@
#!/usr/bin/env bash
#-------------------------------------------------------------------------------------------------------------
# Copyright (c) Microsoft Corporation. All rights reserved.
# Licensed under the MIT License. See https://go.microsoft.com/fwlink/?linkid=2090316 for license information.
#-------------------------------------------------------------------------------------------------------------
# Syntax: ./common-debian.sh [install zsh flag] [username] [user UID] [user GID] [upgrade packages flag]
INSTALL_ZSH=${1:-"true"}
USERNAME=${2:-"vscode"}
USER_UID=${3:-1000}
USER_GID=${4:-1000}
UPGRADE_PACKAGES=${5:-"true"}
set -e
if [ "$(id -u)" -ne 0 ]; then
echo -e 'Script must be run a root. Use sudo, su, or add "USER root" to your Dockerfile before running this script.'
exit 1
fi
# Treat a user name of "none" as root
if [ "${USERNAME}" = "none" ] || [ "${USERNAME}" = "root" ]; then
USERNAME=root
USER_UID=0
USER_GID=0
fi
# Load markers to see which steps have already run
MARKER_FILE="/usr/local/etc/vscode-dev-containers/common"
if [ -f "${MARKER_FILE}" ]; then
echo "Marker file found:"
cat "${MARKER_FILE}"
source "${MARKER_FILE}"
fi
# Ensure apt is in non-interactive to avoid prompts
export DEBIAN_FRONTEND=noninteractive
# Function to call apt-get if needed
apt-get-update-if-needed()
{
if [ ! -d "/var/lib/apt/lists" ] || [ "$(ls /var/lib/apt/lists/ | wc -l)" = "0" ]; then
echo "Running apt-get update..."
apt-get update
else
echo "Skipping apt-get update."
fi
}
# Run install apt-utils to avoid debconf warning then verify presence of other common developer tools and dependencies
if [ "${PACKAGES_ALREADY_INSTALLED}" != "true" ]; then
apt-get-update-if-needed
PACKAGE_LIST="apt-utils \
git \
openssh-client \
less \
iproute2 \
procps \
curl \
wget \
unzip \
zip \
nano \
jq \
lsb-release \
ca-certificates \
apt-transport-https \
dialog \
gnupg2 \
libc6 \
libgcc1 \
libgssapi-krb5-2 \
libicu[0-9][0-9] \
liblttng-ust0 \
libstdc++6 \
zlib1g \
locales \
sudo"
# Install libssl1.1 if available
if [[ ! -z $(apt-cache --names-only search ^libssl1.1$) ]]; then
PACKAGE_LIST="${PACKAGE_LIST} libssl1.1"
fi
# Install appropriate version of libssl1.0.x if available
LIBSSL=$(dpkg-query -f '${db:Status-Abbrev}\t${binary:Package}\n' -W 'libssl1\.0\.?' 2>&1 || echo '')
if [ "$(echo "$LIBSSL" | grep -o 'libssl1\.0\.[0-9]:' | uniq | sort | wc -l)" -eq 0 ]; then
if [[ ! -z $(apt-cache --names-only search ^libssl1.0.2$) ]]; then
# Debian 9
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.2"
elif [[ ! -z $(apt-cache --names-only search ^libssl1.0.0$) ]]; then
# Ubuntu 18.04, 16.04, earlier
PACKAGE_LIST="${PACKAGE_LIST} libssl1.0.0"
fi
fi
echo "Packages to verify are installed: ${PACKAGE_LIST}"
apt-get -y install --no-install-recommends ${PACKAGE_LIST} 2> >( grep -v 'debconf: delaying package configuration, since apt-utils is not installed' >&2 )
PACKAGES_ALREADY_INSTALLED="true"
fi
# Get to latest versions of all packages
if [ "${UPGRADE_PACKAGES}" = "true" ]; then
apt-get-update-if-needed
apt-get -y upgrade --no-install-recommends
apt-get autoremove -y
fi
# Ensure at least the en_US.UTF-8 UTF-8 locale is available.
# Common need for both applications and things like the agnoster ZSH theme.
if [ "${LOCALE_ALREADY_SET}" != "true" ]; then
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen
locale-gen
LOCALE_ALREADY_SET="true"
fi
# Create or update a non-root user to match UID/GID - see https://aka.ms/vscode-remote/containers/non-root-user.
if id -u $USERNAME > /dev/null 2>&1; then
# User exists, update if needed
if [ "$USER_GID" != "$(id -G $USERNAME)" ]; then
groupmod --gid $USER_GID $USERNAME
usermod --gid $USER_GID $USERNAME
fi
if [ "$USER_UID" != "$(id -u $USERNAME)" ]; then
usermod --uid $USER_UID $USERNAME
fi
else
# Create user
groupadd --gid $USER_GID $USERNAME
useradd -s /bin/bash --uid $USER_UID --gid $USER_GID -m $USERNAME
fi
# Add add sudo support for non-root user
if [ "${USERNAME}" != "root" ] && [ "${EXISTING_NON_ROOT_USER}" != "${USERNAME}" ]; then
echo $USERNAME ALL=\(root\) NOPASSWD:ALL > /etc/sudoers.d/$USERNAME
chmod 0440 /etc/sudoers.d/$USERNAME
EXISTING_NON_ROOT_USER="${USERNAME}"
fi
# .bashrc/.zshrc snippet
RC_SNIPPET="$(cat << EOF
export USER=\$(whoami)
export PATH=\$PATH:\$HOME/.local/bin
if [[ \$(which code-insiders 2>&1) && ! \$(which code 2>&1) ]]; then
alias code=code-insiders
fi
EOF
)"
# Ensure ~/.local/bin is in the PATH for root and non-root users for bash. (zsh is later)
if [ "${RC_SNIPPET_ALREADY_ADDED}" != "true" ]; then
echo "${RC_SNIPPET}" | tee -a /root/.bashrc >> /etc/skel/.bashrc
if [ "${USERNAME}" != "root" ]; then
echo "${RC_SNIPPET}" >> /home/$USERNAME/.bashrc
chown $USER_UID:$USER_GID /home/$USERNAME/.bashrc
fi
RC_SNIPPET_ALREADY_ADDED="true"
fi
# Optionally install and configure zsh
if [ "${INSTALL_ZSH}" = "true" ] && [ ! -d "/root/.oh-my-zsh" ] && [ "${ZSH_ALREADY_INSTALLED}" != "true" ]; then
apt-get-update-if-needed
apt-get install -y zsh
curl -fsSLo- https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh | bash 2>&1
echo -e "${RC_SNIPPET}\nDEFAULT_USER=\$USER\nprompt_context(){}" >> /root/.zshrc
cp -fR /root/.oh-my-zsh /etc/skel
cp -f /root/.zshrc /etc/skel
sed -i -e "s/\/root\/.oh-my-zsh/\/home\/\$(whoami)\/.oh-my-zsh/g" /etc/skel/.zshrc
if [ "${USERNAME}" != "root" ]; then
cp -fR /etc/skel/.oh-my-zsh /etc/skel/.zshrc /home/$USERNAME
chown -R $USER_UID:$USER_GID /home/$USERNAME/.oh-my-zsh /home/$USERNAME/.zshrc
fi
ZSH_ALREADY_INSTALLED="true"
fi
# Write marker file
mkdir -p "$(dirname "${MARKER_FILE}")"
echo -e "\
PACKAGES_ALREADY_INSTALLED=${PACKAGES_ALREADY_INSTALLED}\n\
LOCALE_ALREADY_SET=${LOCALE_ALREADY_SET}\n\
EXISTING_NON_ROOT_USER=${EXISTING_NON_ROOT_USER}\n\
RC_SNIPPET_ALREADY_ADDED=${RC_SNIPPET_ALREADY_ADDED}\n\
ZSH_ALREADY_INSTALLED=${ZSH_ALREADY_INSTALLED}" > "${MARKER_FILE}"
echo "Done!"

View File

View File

@ -0,0 +1,25 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir : __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
},
};

View File

@ -0,0 +1,8 @@
/dist*
/node_modules
/dump.rdb
/build
/openapi/build
.env.local

View File

@ -0,0 +1,14 @@
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"runtimeExecutable": "npm",
"runtimeArgs": ["run", "start:debug"],
"envFile": "${workspaceFolder}/.env",
"console": "integratedTerminal"
}
]
}

View File

@ -0,0 +1,21 @@
{
"terminal.integrated.shell.linux": "/bin/bash",
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.format.enable": false,
"[javascript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"editor.formatOnPaste": true,
"editor.formatOnType": true,
"editor.renderWhitespace": "all",
"editor.insertSpaces": false,
"editor.renderLineHighlight": "all"
}

View File

@ -0,0 +1,5 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src"
}

15586
data_migration_tools/server/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,80 @@
{
"name": "data_migration_tool",
"version": "0.0.1",
"description": "",
"author": "",
"private": true,
"license": "UNLICENSED",
"scripts": {
"prebuild": "rimraf dist",
"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\"",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\"",
"lint:fix": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
"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"
},
"dependencies": {
"@azure/storage-blob": "^12.14.0",
"@nestjs/common": "^9.3.9",
"@nestjs/config": "^2.3.1",
"@nestjs/core": "^9.3.9",
"@nestjs/platform-express": "^9.4.1",
"@nestjs/serve-static": "^3.0.1",
"@nestjs/swagger": "^6.2.1",
"@nestjs/testing": "^9.3.9",
"@openapitools/openapi-generator-cli": "^2.5.2",
"@vercel/ncc": "^0.36.1",
"axios": "^1.3.4",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.0",
"cookie-parser": "^1.4.6",
"multer": "^1.4.5-lts.1",
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.0",
"swagger-cli": "^4.0.4"
},
"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": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

View File

@ -0,0 +1,24 @@
import { MiddlewareConsumer, Module } from "@nestjs/common";
import { ServeStaticModule } from "@nestjs/serve-static";
import { ConfigModule } from "@nestjs/config";
import { join } from "path";
import { LoggerMiddleware } from "./common/loggerMiddleware";
@Module({
imports: [
ServeStaticModule.forRoot({
rootPath: join(__dirname, ".", "build"),
}),
ConfigModule.forRoot({
envFilePath: [".env.local", ".env"],
isGlobal: true,
}),
],
controllers: [],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer.apply(LoggerMiddleware).forRoutes("");
}
}

View File

@ -0,0 +1,6 @@
import { AxiosError } from "axios";
export const isAxiosError = (e: unknown): e is AxiosError => {
const error = e as AxiosError;
return error?.isAxiosError ?? false;
};

View File

@ -0,0 +1,28 @@
import { Injectable, Logger, NestMiddleware } from "@nestjs/common";
import { Request, Response } from "express";
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
private readonly logger = new Logger(LoggerMiddleware.name);
use(req: Request, res: Response, next: () => void): void {
this.logger.log(this.createReqMsg(req));
res.on("close", () => {
this.logger.log(this.createResMsg(res));
});
next();
}
private createReqMsg(req: Request): string {
const message = `Request [url=${req.url}, method=${req.method}]`;
return message;
}
private createResMsg(res: Response): string {
const message = `Response [statusCode=${res.statusCode}, message=${res.statusMessage}]`;
return message;
}
}

View File

@ -0,0 +1,35 @@
import { NestFactory } from "@nestjs/core";
import { SwaggerModule, DocumentBuilder } from "@nestjs/swagger";
import { AppModule } from "./app.module";
import { ValidationPipe } from "@nestjs/common";
import { LoggerMiddleware } from "./common/loggerMiddleware";
import cookieParser from "cookie-parser";
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
cors: process.env.CORS === "TRUE",
});
app.use(new LoggerMiddleware(), cookieParser());
// バリデーター(+型の自動変換機能)を適用
app.useGlobalPipes(
new ValidationPipe({ transform: true, forbidUnknownValues: false })
);
if (process.env.STAGE === "local") {
const options = new DocumentBuilder()
.setTitle("data_migration_toolsOpenAPI")
.setVersion("1.0.0")
.addBearerAuth({
type: "http",
scheme: "bearer",
bearerFormat: "JWT",
})
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup("api", app, document);
}
await app.listen(process.env.PORT || 8180);
}
bootstrap();

View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

View File

@ -0,0 +1,22 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "es2022",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false,
"esModuleInterop": true
}
}

View File

@ -43,14 +43,18 @@ const App = (): JSX.Element => {
// Language読み取り
useLayoutEffect(() => {
const language = document.cookie
.trim()
.split(";")
.map((x) => x.split("="))
.find((x) => x.length === 2 && x[0] === "language");
const { cookie } = document;
if (language) {
i18n.changeLanguage(language[1]);
if (cookie) {
const cookieArray = cookie.split(";");
const language = cookieArray.find((x) =>
// 先頭の空白を削除してから判定
x.trim().startsWith("language=")
);
if (language) {
i18n.changeLanguage(language.split("=")[1]);
}
}
}, [i18n]);

View File

@ -1,10 +1,14 @@
import React from "react";
import { useTranslation } from "react-i18next";
import styles from "styles/app.module.scss";
import { getTranslationID } from "translation";
const Footer: React.FC = () => (
<footer className={`${styles.footer}`}>
<div>&copy; OM Digital Solutions 2023</div>
</footer>
);
const Footer: React.FC = () => {
const [t] = useTranslation();
return (
<footer className={`${styles.footer}`}>
<div>{t(getTranslationID("common.label.copyRight"))}</div>
</footer>
);
};
export default Footer;

View File

@ -1,3 +1,5 @@
import type { RoleType } from "./types";
// LicenseStatusTypeの値を定数オブジェクトにする
export const LICENSE_STATUS = {
NORMAL: "Normal",
@ -17,3 +19,10 @@ export const NO_LICENSE = "No License" as const;
// ライセンスが割り当てられている場合の表示
export const LICENSE_NORMAL = "License Assigned" as const;
// Roleの表示名
export const ROLE_DISPLAY_NAME: Record<RoleType, string> = {
author: "Author",
typist: "Transcriptionist",
none: "None",
} as const;

View File

@ -9,7 +9,11 @@ import {
isLicenseStatusType,
isRoleType,
} from "./types";
import { LICENSE_STATUS, LICENSE_ALLOCATE_STATUS } from "./constants";
import {
LICENSE_STATUS,
LICENSE_ALLOCATE_STATUS,
ROLE_DISPLAY_NAME,
} from "./constants";
export const selectInputValidationErrors = (state: RootState) => {
const { name, email, role, authorId, encryption, encryptionPassword } =
@ -176,8 +180,8 @@ export const selectUserViews = (state: RootState): UserView[] => {
prompt: convertedValues.prompt,
encryption: convertedValues.encryption,
authorId: convertedValues.authorId,
// roleの一文字目を大文字に変換する
role: role.charAt(0).toUpperCase() + role.slice(1),
// roleに応じて表示名を変更する
role: ROLE_DISPLAY_NAME[role],
licenseStatus: convertedLicenseStatus,
expiration: convertedExpiration,
remaining: convertedRemaining,

View File

@ -45,7 +45,7 @@ export const worktypeSlice = createSlice({
action: PayloadAction<{ worktypeId: string }>
) => {
const { worktypeId } = action.payload;
state.apps.worktypeId = worktypeId;
state.apps.worktypeId = worktypeId.toUpperCase();
},
changeDescription: (
state,
@ -65,7 +65,12 @@ export const worktypeSlice = createSlice({
optionItem.defaultValueType !== OPTION_ITEMS_DEFAULT_VALUE_TYPE.DEFAULT
) {
optionItem.initialValue = "";
} else {
// defaultValueTypeがDefaultの場合はinitialValueを大文字にする
optionItem.initialValue = optionItem.initialValue.toUpperCase();
}
// itemLabelを大文字にする
optionItem.itemLabel = optionItem.itemLabel.toUpperCase();
// idが一致するoptionItemを削除して、新しいoptionItemを追加する。一致するidがない場合は何もしない
const optionItems = state.apps.optionItems?.filter(

View File

@ -383,15 +383,6 @@ const AccountPage: React.FC = (): JSX.Element => {
className={styles.icLoading}
alt="Loading"
/>
{isTier5 && (
<p className={styles.formComment}>
{t(
getTranslationID(
"accountPage.text.dealerManagementAnnotation"
)
)}
</p>
)}
</div>
</div>
{isTier5 && (

View File

@ -50,16 +50,6 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
<dd>{selectedFileTask?.fileSize ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.fileLength"))}</dt>
<dd>{selectedFileTask?.audioDuration ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.authorId"))}</dt>
<dd>{selectedFileTask?.authorId ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.workType"))}</dt>
<dd>{selectedFileTask?.workType ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.priority"))}</dt>
<dd>
{selectedFileTask?.priority === "01"
? PRIORITY.HIGH
: PRIORITY.NORMAL}
</dd>
<dt>
{t(getTranslationID("dictationPage.label.recordingStartedDate"))}
</dt>
@ -68,8 +58,6 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
{t(getTranslationID("dictationPage.label.recordingFinishedDate"))}
</dt>
<dd>{selectedFileTask?.audioFinishedDate ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.uploadDate"))}</dt>
<dd>{selectedFileTask?.audioUploadedDate ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.encryption"))}</dt>
<dd>
{selectedFileTask?.isEncrypted ? (
@ -78,6 +66,16 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
""
)}
</dd>
<dt>{t(getTranslationID("dictationPage.label.priority"))}</dt>
<dd>
{selectedFileTask?.priority === "01"
? PRIORITY.HIGH
: PRIORITY.NORMAL}
</dd>
<dt>{t(getTranslationID("dictationPage.label.authorId"))}</dt>
<dd>{selectedFileTask?.authorId ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.workType"))}</dt>
<dd>{selectedFileTask?.workType ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.optionItem1"))}</dt>
<dd>{selectedFileTask?.optionItemList[0].optionItemValue}</dd>
<dt>{t(getTranslationID("dictationPage.label.optionItem2"))}</dt>
@ -107,6 +105,10 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
<dd>{selectedFileTask?.jobNumber ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.status"))}</dt>
<dd>{selectedFileTask?.status ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.uploadDate"))}</dt>
<dd>{selectedFileTask?.audioUploadedDate ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.transcriptionist"))}</dt>
<dd>{selectedFileTask?.typist?.name ?? ""}</dd>
<dt>
{t(
getTranslationID("dictationPage.label.transcriptionStartedDate")
@ -119,8 +121,6 @@ export const FilePropertyPopup: React.FC<FilePropertyPopupProps> = (props) => {
)}
</dt>
<dd>{selectedFileTask?.transcriptionFinishedDate ?? ""}</dd>
<dt>{t(getTranslationID("dictationPage.label.transcriptionist"))}</dt>
<dd>{selectedFileTask?.typist?.name ?? ""}</dd>
<dd className={`${styles.full} ${styles.alignRight}`}>
{/* eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions */}
<a onClick={closePopup} className={`${styles.buttonText}`}>

View File

@ -136,17 +136,22 @@ export const EditOptionItemsPopup: React.FC<EditOptionItemsPopupProps> = (
getTranslationID("worktypeIdSetting.label.itemLabel")
)}
</th>
{
// https://so-net.backlog.jp/board/OMDS_IS?selectedIssueKey=OMDS_IS-289
// initialValueとdefaultValueの項目名を入れ替え
// その結果 initialValueの項目名がdefaultValueになり、defaultValueの項目名がinitialValueになる
}
<th className={styles.noLine}>
{t(
getTranslationID(
"worktypeIdSetting.label.defaultValue"
"worktypeIdSetting.label.initialValue"
)
)}
</th>
<th>
{t(
getTranslationID(
"worktypeIdSetting.label.initialValue"
"worktypeIdSetting.label.defaultValue"
)
)}
</th>

View File

@ -129,13 +129,13 @@
"roleChangeError": "Die Benutzerrolle kann nicht geändert werden. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.",
"encryptionPasswordCorrectError": "Das Verschlüsselungskennwort entspricht nicht den Regeln.",
"alreadyLicenseDeallocatedError": "Die zugewiesene Lizenz wurde bereits storniert. Die angezeigten Informationen sind möglicherweise veraltet. Aktualisieren Sie daher bitte den Bildschirm, um den neuesten Status anzuzeigen.",
"UserDeletionLicenseActiveError": "(de)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"TypistDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"AdminUserDeletionError": "(de)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"TypistUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"AuthorUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"TypistUserDeletionTranscriptionistGroupError": "(de)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"AuthorDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
"userDeletionLicenseActiveError": "(de)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"typistDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"adminUserDeletionError": "(de)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"typistUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"authorUserDeletionTranscriptionTaskError": "(de)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"typistUserDeletionTranscriptionistGroupError": "(de)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"authorDeletionRoutingRuleError": "(de)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
},
"label": {
"title": "Benutzer",
@ -220,7 +220,7 @@
},
"label": {
"title": "Diktate",
"displayInfomation": "Informations sur l'affichage",
"displayInfomation": "Informationen anzeigen",
"jobNumber": "Aufgabennummer",
"status": "Status",
"priority": "Priorität",
@ -258,7 +258,7 @@
"changeTranscriptionist": "Transkriptionist ändern",
"deleteDictation": "Diktat löschen",
"selectedTranscriptionist": "Ausgewählter transkriptionist",
"poolTranscriptionist": "Transkriptionsliste",
"poolTranscriptionist": "Liste der Transkriptionisten",
"fileBackup": "Dateisicherung",
"downloadForBackup": "Zur Sicherung herunterladen",
"applications": "Desktopanwendung",
@ -397,7 +397,7 @@
"templateOptional": "Vorlage (Optional)",
"editRule": "Regel bearbeiten",
"selected": "Ausgewählter transkriptionist",
"pool": "Transkriptionsliste",
"pool": "Liste der Transkriptionisten",
"selectAuthor": "Autoren-ID auswählen",
"selectWorktypeId": "Aufgabentypkennung auswählen",
"selectTemplate": "Vorlage auswählen"
@ -411,13 +411,13 @@
},
"typistGroupSetting": {
"label": {
"title": "Transkriptionistengruppe",
"title": "Gruppeneinstellung für Transkriptionisten",
"addGroup": "Gruppe hinzufügen",
"groupName": "Gruppenname",
"addTypistGroup": "Transkriptionist Gruppe hinzufügen",
"transcriptionist": "Transkriptionist",
"selected": "Ausgewählter transkriptionist",
"pool": "Transkriptionsliste",
"pool": "Liste der Transkriptionisten",
"add": "Hinzufügen",
"remove": "Entfernen",
"editTypistGroup": "Transkriptionistengruppe bearbeiten"
@ -467,7 +467,7 @@
},
"templateFilePage": {
"label": {
"title": "Vorlagenliste",
"title": "Vorlageneinstellung",
"addTemplate": "Vorlage hinzufügen",
"fileName": "Dateiname",
"chooseFile": "Datei aussuchen",
@ -475,6 +475,10 @@
"fileSizeTerms": "Die maximale Dateigröße, die gespeichert werden kann, beträgt 5 MB.",
"fileSizeError": "Die ausgewählte Dateigröße ist zu groß. Bitte wählen Sie eine Datei mit einer Größe von 5 MB oder weniger aus.",
"fileEmptyError": "Dateiauswahl ist erforderlich. Bitte wählen Sie eine Datei aus."
},
"message": {
"deleteFailedWorkflowAssigned": "(de)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。",
"deleteFailedTaskAssigned": "(de)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。"
}
},
"partnerPage": {
@ -515,7 +519,8 @@
"emailAddress": "E-Mail-Addresse",
"selectSecondaryAdministrator": "Sekundäradministrator auswählen",
"saveChanges": "Änderungen speichern",
"deleteAccount": "Konto löschen"
"deleteAccount": "Konto löschen",
"fileRetentionDays": "(de)自動ファイル削除までの保持日数"
},
"message": {
"updateAccountFailedError": "Kontoinformationen konnten nicht gespeichert werden. Bitte aktualisieren Sie den Bildschirm und versuchen Sie es erneut."
@ -570,5 +575,15 @@
"job": "Aufgabe",
"close": "Schließen"
}
},
"fileDeleteSettingPopup": {
"label": {
"title": "(de)Auto File Delete Setting",
"autoFileDeleteCheck": "(de)Auto file delete",
"daysAnnotation": "(de)Number of days from transcription finished to delete the files.",
"days": "(de)Days",
"saveButton": "(de)Save Settings",
"daysValidationError": "(de)Daysには1999の数字を入力してください。"
}
}
}
}

View File

@ -129,13 +129,13 @@
"roleChangeError": "Unable to change the User Role. The displayed information may be outdated, so please refresh the screen to see the latest status.",
"encryptionPasswordCorrectError": "Encryption password does not meet the rules.",
"alreadyLicenseDeallocatedError": "Assigned license has already been canceled. The displayed information may be outdated, so please refresh the screen to see the latest status.",
"UserDeletionLicenseActiveError": "ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"TypistDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"AdminUserDeletionError": "ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"TypistUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"AuthorUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"TypistUserDeletionTranscriptionistGroupError": "ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"AuthorDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
"userDeletionLicenseActiveError": "ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"typistDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"adminUserDeletionError": "ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"typistUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"authorUserDeletionTranscriptionTaskError": "ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"typistUserDeletionTranscriptionistGroupError": "ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"authorDeletionRoutingRuleError": "ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
},
"label": {
"title": "User",
@ -258,7 +258,7 @@
"changeTranscriptionist": "Change Transcriptionist",
"deleteDictation": "Delete Dictation",
"selectedTranscriptionist": "Selected Transcriptionist",
"poolTranscriptionist": "Transcription List",
"poolTranscriptionist": "Transcriptionist List",
"fileBackup": "File Backup",
"downloadForBackup": "Download for backup",
"applications": "Desktop Application",
@ -397,7 +397,7 @@
"templateOptional": "Template (Optional)",
"editRule": "Edit Rule",
"selected": "Selected Transcriptionist",
"pool": "Transcription List",
"pool": "Transcriptionist List",
"selectAuthor": "Select Author ID",
"selectWorktypeId": "Select Worktype ID",
"selectTemplate": "Select Template"
@ -411,13 +411,13 @@
},
"typistGroupSetting": {
"label": {
"title": "Transcriptionist Group",
"title": "Transcriptionist Group Setting",
"addGroup": "Add Group",
"groupName": "Group Name",
"addTypistGroup": "Add Transcriptionist Group",
"transcriptionist": "Transcriptionist",
"selected": "Selected Transcriptionist",
"pool": "Transcription List",
"pool": "Transcriptionist List",
"add": "Add",
"remove": "Remove",
"editTypistGroup": "Edit Transcriptionist Group"
@ -467,7 +467,7 @@
},
"templateFilePage": {
"label": {
"title": "Template List",
"title": "Template Setting",
"addTemplate": "Add Template",
"fileName": "File Name",
"chooseFile": "Select file",
@ -475,6 +475,10 @@
"fileSizeTerms": "The maximum file size that can be saved is 5MB.",
"fileSizeError": "The selected file size is too large. Please select a file that is 5MB or less in size.",
"fileEmptyError": "File selection is required. Please select a file."
},
"message": {
"deleteFailedWorkflowAssigned": "テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。",
"deleteFailedTaskAssigned": "テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。"
}
},
"partnerPage": {
@ -515,7 +519,8 @@
"emailAddress": "Email Address",
"selectSecondaryAdministrator": "Select Secondary Administrator",
"saveChanges": "Save Changes",
"deleteAccount": "Delete Account"
"deleteAccount": "Delete Account",
"fileRetentionDays": "自動ファイル削除までの保持日数"
},
"message": {
"updateAccountFailedError": "Failed to save account information. Please refresh the screen and try again."
@ -570,5 +575,15 @@
"job": "Job",
"close": "Close"
}
},
"fileDeleteSettingPopup": {
"label": {
"title": "Auto File Delete Setting",
"autoFileDeleteCheck": "Auto file delete",
"daysAnnotation": "Number of days from transcription finished to delete the files.",
"days": "Days",
"saveButton": "Save Settings",
"daysValidationError": "Daysには1999の数字を入力してください。"
}
}
}
}

View File

@ -129,13 +129,13 @@
"roleChangeError": "No se puede cambiar la función de usuario. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente.",
"encryptionPasswordCorrectError": "La contraseña de cifrado no cumple con las reglas.",
"alreadyLicenseDeallocatedError": "La licencia asignada ya ha sido cancelada. La información mostrada puede estar desactualizada, así que actualice la pantalla para ver el estado más reciente.",
"UserDeletionLicenseActiveError": "(es)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"TypistDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"AdminUserDeletionError": "(es)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"TypistUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"AuthorUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"TypistUserDeletionTranscriptionistGroupError": "(es)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"AuthorDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
"userDeletionLicenseActiveError": "(es)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"typistDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"adminUserDeletionError": "(es)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"typistUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"authorUserDeletionTranscriptionTaskError": "(es)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"typistUserDeletionTranscriptionistGroupError": "(es)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"authorDeletionRoutingRuleError": "(es)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
},
"label": {
"title": "Usuario",
@ -220,7 +220,7 @@
},
"label": {
"title": "Dictado",
"displayInfomation": "Informationen anzeigen",
"displayInfomation": "Mostrar información",
"jobNumber": "Número de trabajo",
"status": "Estado",
"priority": "Prioridad",
@ -258,7 +258,7 @@
"changeTranscriptionist": "Cambiar transcriptor",
"deleteDictation": "Borrar dictado",
"selectedTranscriptionist": "Transcriptor seleccionado",
"poolTranscriptionist": "Lista de transcriptor",
"poolTranscriptionist": "Lista de transcriptores",
"fileBackup": "Copia de seguridad de archivos",
"downloadForBackup": "Descargar para respaldo",
"applications": "Aplicación de escritorio",
@ -397,7 +397,7 @@
"templateOptional": "Plantilla (Opcional)",
"editRule": "Editar regla",
"selected": "Transcriptor seleccionado",
"pool": "Lista de transcriptor",
"pool": "Lista de transcriptores",
"selectAuthor": "Seleccionar ID de autor",
"selectWorktypeId": "Seleccionar ID de tipo de trabajo",
"selectTemplate": "Seleccionar Plantilla"
@ -411,13 +411,13 @@
},
"typistGroupSetting": {
"label": {
"title": "Grupo de transcriptor",
"title": "Configuración del grupo transcriptor",
"addGroup": "Agregar grupo",
"groupName": "Nombre del grupo",
"addTypistGroup": "Agregar grupo transcriptor",
"transcriptionist": "Transcriptor",
"selected": "Transcriptor seleccionado",
"pool": "Lista de transcriptor",
"pool": "Lista de transcriptores",
"add": "Añadir",
"remove": "Eliminar",
"editTypistGroup": "Editar grupo transcriptor"
@ -467,7 +467,7 @@
},
"templateFilePage": {
"label": {
"title": "Lista de plantillas",
"title": "Configuración de plantilla",
"addTemplate": "Agregar plantilla",
"fileName": "Nombre del archivo",
"chooseFile": "Seleccione Archivo",
@ -475,6 +475,10 @@
"fileSizeTerms": "El tamaño máximo de archivo que se puede guardar es de 5 MB.",
"fileSizeError": "El tamaño del archivo seleccionado es demasiado grande. Seleccione un archivo que tenga un tamaño de 5 MB o menos.",
"fileEmptyError": "Se requiere selección de archivos. Por favor seleccione un archivo."
},
"message": {
"deleteFailedWorkflowAssigned": "(es)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。",
"deleteFailedTaskAssigned": "(es)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。"
}
},
"partnerPage": {
@ -515,7 +519,8 @@
"emailAddress": "Dirección de correo electrónico",
"selectSecondaryAdministrator": "Seleccionar administrador secundario",
"saveChanges": "Guardar cambios",
"deleteAccount": "Borrar cuenta"
"deleteAccount": "Borrar cuenta",
"fileRetentionDays": "(es)自動ファイル削除までの保持日数"
},
"message": {
"updateAccountFailedError": "No se pudo guardar la información de la cuenta. Actualice la pantalla e inténtelo de nuevo."
@ -570,5 +575,15 @@
"job": "Trabajo",
"close": "Cerrar"
}
},
"fileDeleteSettingPopup": {
"label": {
"title": "(es)Auto File Delete Setting",
"autoFileDeleteCheck": "(es)Auto file delete",
"daysAnnotation": "(es)Number of days from transcription finished to delete the files.",
"days": "(es)Days",
"saveButton": "(es)Save Settings",
"daysValidationError": "(es)Daysには1999の数字を入力してください。"
}
}
}
}

View File

@ -129,13 +129,13 @@
"roleChangeError": "Impossible de modifier le rôle de l'utilisateur. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.",
"encryptionPasswordCorrectError": "Le mot de passe de cryptage n'est pas conforme aux règles.",
"alreadyLicenseDeallocatedError": "La licence attribuée a déjà été annulée. Les informations affichées peuvent être obsolètes, veuillez donc actualiser l'écran pour voir le dernier statut.",
"UserDeletionLicenseActiveError": "(fr)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"TypistDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"AdminUserDeletionError": "(fr)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"TypistUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"AuthorUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"TypistUserDeletionTranscriptionistGroupError": "(fr)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"AuthorDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
"userDeletionLicenseActiveError": "(fr)ユーザーの削除に失敗しました。対象ユーザーのライセンス割り当てを解除してください。",
"typistDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象Transcriptionistを外してください。",
"adminUserDeletionError": "(fr)ユーザーの削除に失敗しました。アカウント画面で対象ユーザーをPrimary/Secondary Administratorから外してください。",
"typistUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面でタスクのルーティングから対象Transcriptionistを外してください。",
"authorUserDeletionTranscriptionTaskError": "(fr)ユーザーの削除に失敗しました。Dictation画面で対象AuthorのAuthorIDが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。",
"typistUserDeletionTranscriptionistGroupError": "(fr)ユーザーの削除に失敗しました。Workflow画面でTranscriptionistGroupから対象Transcriptionistを外してください。",
"authorDeletionRoutingRuleError": "(fr)ユーザーの削除に失敗しました。Workflow画面でルーティングルールから対象AuthorのAuthorIDを外してください。"
},
"label": {
"title": "Utilisateur",
@ -258,7 +258,7 @@
"changeTranscriptionist": "Changer de transcriptionniste ",
"deleteDictation": "Supprimer la dictée",
"selectedTranscriptionist": "Transcriptionniste sélectionné",
"poolTranscriptionist": "Liste de transcriptionniste",
"poolTranscriptionist": "Liste des transcripteurs",
"fileBackup": "Sauvegarde de fichiers",
"downloadForBackup": "Télécharger pour sauvegarde",
"applications": "Application de bureau",
@ -397,7 +397,7 @@
"templateOptional": "Masque (Facultatif)",
"editRule": "Modifier la règle",
"selected": "Transcriptionniste sélectionné",
"pool": "Liste de transcriptionniste",
"pool": "Liste des transcripteurs",
"selectAuthor": "Sélectionner le Identifiant Auteur",
"selectWorktypeId": "Sélectionner le Identifiant du Type de travail",
"selectTemplate": "Sélectionner le Masque"
@ -411,13 +411,13 @@
},
"typistGroupSetting": {
"label": {
"title": "Groupe de transcriptionniste",
"title": "Paramètre de groupe de transcriptionniste",
"addGroup": "Ajouter groupe",
"groupName": "Nom de groupe",
"addTypistGroup": "Ajouter un groupe de transcripteurs",
"transcriptionist": "Transcriptionniste",
"selected": "Transcriptionniste sélectionné",
"pool": "Liste de transcriptionniste",
"pool": "Liste des transcripteurs",
"add": "Ajouter",
"remove": "Supprimer",
"editTypistGroup": "Modifier le groupe de transcripteurs"
@ -467,7 +467,7 @@
},
"templateFilePage": {
"label": {
"title": "Liste des modèles",
"title": "Paramètre de modèle",
"addTemplate": "Ajouter un modèle",
"fileName": "Nom de fichier",
"chooseFile": "Choisir le fichier",
@ -475,6 +475,10 @@
"fileSizeTerms": "La taille maximale du fichier pouvant être enregistré est de 5 Mo.",
"fileSizeError": "La taille du fichier sélectionné est trop grande. Veuillez sélectionner un fichier d'une taille maximale de 5 Mo.",
"fileEmptyError": "La sélection de fichiers est requise. Veuillez sélectionner un fichier."
},
"message": {
"deleteFailedWorkflowAssigned": "(fr)テンプレートファイルの削除に失敗しました。Workflow画面でルーティングルールから対象テンプレートファイルを外してください。",
"deleteFailedTaskAssigned": "(fr)テンプレートファイルの削除に失敗しました。Dictation画面で対象テンプレートファイルが設定されているタスクの中で、文字起こしが未完了のタスクを削除またはFinishedにしてください。"
}
},
"partnerPage": {
@ -515,7 +519,8 @@
"emailAddress": "Adresse e-mail",
"selectSecondaryAdministrator": "Sélectionner le administrateur secondaire",
"saveChanges": "Sauvegarder les modifications",
"deleteAccount": "Supprimer le compte"
"deleteAccount": "Supprimer le compte",
"fileRetentionDays": "(fr)自動ファイル削除までの保持日数"
},
"message": {
"updateAccountFailedError": "Échec de l'enregistrement des informations du compte. Veuillez actualiser l'écran et réessayer."
@ -570,5 +575,15 @@
"job": "Tâches",
"close": "Fermer"
}
},
"fileDeleteSettingPopup": {
"label": {
"title": "(fr)Auto File Delete Setting",
"autoFileDeleteCheck": "(fr)Auto file delete",
"daysAnnotation": "(fr)Number of days from transcription finished to delete the files.",
"days": "(fr)Days",
"saveButton": "(fr)Save Settings",
"daysValidationError": "(fr)Daysには1999の数字を入力してください。"
}
}
}
}

View File

@ -0,0 +1,36 @@
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments,
ValidationOptions,
registerDecorator,
} from 'class-validator';
// 大文字英数字とアンダースコアのみを許可するバリデータ
@ValidatorConstraint({ name: 'IsAuthorId', async: false })
export class IsAuthorId implements ValidatorConstraintInterface {
validate(value: any, args: ValidationArguments) {
return /^[A-Z0-9_]*$/.test(value);
}
defaultMessage(args: ValidationArguments) {
return `${args.property} should be uppercase alphanumeric and underscore only`;
}
}
/**
*
* @param [validationOptions]
* @returns
*/
export function IsAuthorIdValid(validationOptions?: ValidationOptions) {
return function (object: object, propertyName: string) {
registerDecorator({
name: 'IsAuthorId',
target: object.constructor,
propertyName: propertyName,
constraints: [],
options: validationOptions,
validator: IsAuthorId,
});
};
}

View File

@ -19,7 +19,7 @@ export class IsRecorderAllowedCharacters
}
// 正規表現でWorktypeIDのチェックを行う
// 以下の禁則文字を除く半角英数記号
// 以下の禁則文字を除く大文字英数記号
// \ (backslash)
// / (forward slash)
// : (colon)
@ -31,7 +31,7 @@ export class IsRecorderAllowedCharacters
// | (vertical bar)
// . (period)
const regex =
/^(?!.*\\)(?!.*\/)(?!.*:)(?!.*\*)(?!.*\?)(?!.*")(?!.*<)(?!.*>)(?!.*\|)(?!.*\.)[ -~]+$/;
/^(?!.*\\)(?!.*\/)(?!.*:)(?!.*\*)(?!.*\?)(?!.*")(?!.*<)(?!.*>)(?!.*\|)(?!.*\.)[A-Z0-9 !#$%&'()+,\-;=@\[\]^_`{}~]*$/;
return regex.test(value);
}

View File

@ -134,8 +134,8 @@ describe('createAccount', () => {
},
});
let _subject: string = "";
let _url: string | undefined = "";
let _subject: string = '';
let _url: string | undefined = '';
overrideSendgridService(service, {
sendMail: async (
context: Context,
@ -197,7 +197,9 @@ describe('createAccount', () => {
// 想定通りのメールが送られているか確認
expect(_subject).toBe('User Registration Notification [U-102]');
expect(_url?.startsWith('http://localhost:8081/mail-confirm?verify=')).toBeTruthy();
expect(
_url?.startsWith('http://localhost:8081/mail-confirm?verify='),
).toBeTruthy();
});
it('アカウントを作成がAzure AD B2Cへの通信失敗によって失敗すると500エラーが発生する', async () => {
@ -5727,8 +5729,8 @@ describe('アカウント情報更新', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<AccountsService>(AccountsService);
let _subject: string = "";
let _url: string | undefined = "";
let _subject: string = '';
let _url: string | undefined = '';
overrideSendgridService(service, {
sendMail: async (
context: Context,

View File

@ -18,14 +18,21 @@ import {
} from './test/utility';
import { UsersService } from '../users/users.service';
import { Context, makeContext } from '../../common/log';
import { ADB2C_SIGN_IN_TYPE, LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
import {
ADB2C_SIGN_IN_TYPE,
LICENSE_ALLOCATED_STATUS,
LICENSE_TYPE,
} from '../../constants';
import {
makeHierarchicalAccounts,
makeTestSimpleAccount,
makeTestUser,
} from '../../common/test/utility';
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { overrideAdB2cService, overrideSendgridService } from '../../common/test/overrides';
import {
overrideAdB2cService,
overrideSendgridService,
} from '../../common/test/overrides';
import { truncateAllTable } from '../../common/test/init';
describe('ライセンス注文', () => {
@ -672,7 +679,10 @@ describe('ライセンス割り当て', () => {
const module = await makeTestingModule(source);
if (!module) fail();
const { id: dealerId } = await makeTestSimpleAccount(source, { company_name: "DEALER_COMPANY", tier: 4 });
const { id: dealerId } = await makeTestSimpleAccount(source, {
company_name: 'DEALER_COMPANY',
tier: 4,
});
const { id: dealerAdminId } = await makeTestUser(source, {
account_id: dealerId,
external_id: 'userId_admin',
@ -682,7 +692,7 @@ describe('ライセンス割り当て', () => {
const { id: accountId } = await makeTestSimpleAccount(source, {
parent_account_id: dealerId,
tier: 5
tier: 5,
});
const { id: userId } = await makeTestUser(source, {
account_id: accountId,
@ -740,7 +750,7 @@ describe('ライセンス割り当て', () => {
},
],
}));
}
},
});
overrideSendgridService(service, {

View File

@ -138,9 +138,8 @@ export const createTask = async (
label: `label${i}:audio_file_id${audioFileIdentifiers[0].id}`,
value: `value${i}:audio_file_id${audioFileIdentifiers[0].id}`,
};
}
);
});
await datasource.getRepository(AudioOptionItem).insert(audioOptionItems);
const audioFile = audioFileIdentifiers.pop() as AudioFile;
@ -161,7 +160,7 @@ export const createTask = async (
return { taskId: task.id, audioFileId: audioFile.id };
};
export const createAudioFile = async(
export const createAudioFile = async (
datasource: DataSource,
account_id: number,
owner_user_id: number,
@ -189,7 +188,7 @@ export const createAudioFile = async(
});
const audioFile = audioFileIdentifiers.pop() as AudioFile;
return { audioFileId: audioFile.id };
}
};
/**
*

View File

@ -18,6 +18,7 @@ import {
} from '../../../common/validators/encryptionPassword.validator';
import { IsRoleAuthorDataValid } from '../../../common/validators/roleAuthor.validator';
import { Type } from 'class-transformer';
import { IsAuthorIdValid } from '../../../common/validators/authorId.validator';
export class ConfirmRequest {
@ApiProperty()
@ -90,6 +91,7 @@ export class SignupRequest {
@ApiProperty({ required: false })
@IsRoleAuthorDataValid()
@IsAuthorIdValid()
authorId?: string;
@ApiProperty()
@ -225,6 +227,7 @@ export class PostUpdateUserRequest {
@ApiProperty({ required: false })
@IsRoleAuthorDataValid()
@IsAuthorIdValid()
authorId?: string;
@ApiProperty()

View File

@ -18,4 +18,4 @@ export class TypistGroupNameAlreadyExistError extends Error {
super(message);
this.name = 'TypistGroupNameAlreadyExistError';
}
}
}