Merge branch 'develop' into main

This commit is contained in:
maruyama.t 2023-11-17 16:22:01 +09:00
commit 912ea52351
72 changed files with 10605 additions and 3880 deletions

File diff suppressed because it is too large Load Diff

View File

@ -63,7 +63,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231026t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231108t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -71,7 +71,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231027t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231109t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -79,7 +79,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231028t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231110t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -87,7 +87,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231029t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231111t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -95,7 +95,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231030t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231112t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -103,7 +103,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231031t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231113t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -111,7 +111,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_dev_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231101t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/daily-20231114t072100-6cc69487-0eb7-4530-8f35-d1ce130a7df7')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -5332,9 +5332,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_dev_name'), '/slow_query_log_file')]",
"properties": {
"currentValue": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-dev-2023110109.log",
"currentValue": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-dev-2023111409.log",
"source": "user-override",
"value": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-dev-2023110109.log"
"value": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-dev-2023111409.log"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},

View File

@ -8,6 +8,9 @@
"applicationGateways_agw_odms_webapp_dev_name": {
"type": "String"
},
"networkSecurityGroups_nsg_odms_gateway_dev_name": {
"type": "String"
},
"networkSecurityGroups_nsg_odms_private_dev_name": {
"type": "String"
},
@ -1591,6 +1594,78 @@
},
"type": "Microsoft.Network/ApplicationGatewayWebApplicationFirewallPolicies"
},
{
"apiVersion": "2023-05-01",
"location": "japaneast",
"name": "[parameters('networkSecurityGroups_nsg_odms_gateway_dev_name')]",
"properties": {
"securityRules": [
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), 'AllowTagCustom65200-65535Inbound')]",
"name": "AllowTagCustom65200-65535Inbound",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "65200-65535",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 100,
"protocol": "TCP",
"sourceAddressPrefix": "GatewayManager",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), 'DenyAnyHTTPSInbound')]",
"name": "DenyAnyHTTPSInbound",
"properties": {
"access": "Deny",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 1000,
"protocol": "TCP",
"sourceAddressPrefix": "*",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), 'AllowDeveloperInboundYumoto')]",
"name": "AllowDeveloperInboundYumoto",
"properties": {
"access": "Allow",
"description": "開発チーム NDS 湯本の動作確認用",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 101,
"protocol": "TCP",
"sourceAddressPrefix": "180.39.76.100",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
}
]
},
"tags": {
"Environment": "develop",
"Project": "ODMS"
},
"type": "Microsoft.Network/networkSecurityGroups"
},
{
"apiVersion": "2023-05-01",
"location": "japaneast",
@ -1725,7 +1800,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 3,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 2,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1740,7 +1815,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 2,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1755,7 +1830,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 3,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 2,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1770,7 +1845,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 2,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1885,6 +1960,51 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), '/AllowDeveloperInboundYumoto')]",
"properties": {
"access": "Allow",
"description": "開発チーム NDS 湯本の動作確認用",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 101,
"protocol": "TCP",
"sourceAddressPrefix": "180.39.76.100",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), '/AllowTagCustom65200-65535Inbound')]",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "65200-65535",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 100,
"protocol": "TCP",
"sourceAddressPrefix": "GatewayManager",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -1929,6 +2049,28 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'), '/DenyAnyHTTPSInbound')]",
"properties": {
"access": "Deny",
"destinationAddressPrefix": "*",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Inbound",
"priority": 1000,
"protocol": "TCP",
"sourceAddressPrefix": "*",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
@ -8500,6 +8642,22 @@
},
"type": "Microsoft.OperationalInsights/workspaces/tables"
},
{
"apiVersion": "2021-12-01-preview",
"dependsOn": [
"[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaces_log_odms_agw_dev_name'))]"
],
"name": "[concat(parameters('workspaces_log_odms_agw_dev_name'), '/LASummaryLogs')]",
"properties": {
"plan": "Analytics",
"retentionInDays": 30,
"schema": {
"name": "LASummaryLogs"
},
"totalRetentionInDays": 30
},
"type": "Microsoft.OperationalInsights/workspaces/tables"
},
{
"apiVersion": "2021-12-01-preview",
"dependsOn": [
@ -10810,40 +10968,6 @@
},
"type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_odms_network_dev_name'))]",
"[resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name'))]"
],
"name": "[concat(parameters('virtualNetworks_vnet_odms_network_dev_name'), '/snet-odms-gateway-dev')]",
"properties": {
"addressPrefix": "10.1.0.0/24",
"applicationGatewayIPConfigurations": [
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name')), '/gatewayIPConfigurations/appGatewayIpConfig')]"
}
],
"delegations": [],
"privateEndpointNetworkPolicies": "Disabled",
"privateLinkServiceNetworkPolicies": "Enabled",
"serviceEndpoints": [
{
"locations": [
"*"
],
"service": "Microsoft.KeyVault"
},
{
"locations": [
"*"
],
"service": "Microsoft.Web"
}
]
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -11185,12 +11309,51 @@
},
"type": "Microsoft.Network/applicationGateways"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_odms_network_dev_name'))]",
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]",
"[resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name'))]"
],
"name": "[concat(parameters('virtualNetworks_vnet_odms_network_dev_name'), '/snet-odms-gateway-dev')]",
"properties": {
"addressPrefix": "10.1.0.0/24",
"applicationGatewayIPConfigurations": [
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name')), '/gatewayIPConfigurations/appGatewayIpConfig')]"
}
],
"delegations": [],
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]"
},
"privateEndpointNetworkPolicies": "Disabled",
"privateLinkServiceNetworkPolicies": "Enabled",
"serviceEndpoints": [
{
"locations": [
"*"
],
"service": "Microsoft.KeyVault"
},
{
"locations": [
"*"
],
"service": "Microsoft.Web"
}
]
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_public_dev_name'))]",
"[resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name'))]",
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_private_dev_name'))]"
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_private_dev_name'))]",
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]",
"[resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name'))]"
],
"location": "japaneast",
"name": "[parameters('virtualNetworks_vnet_odms_network_dev_name')]",
@ -11243,36 +11406,6 @@
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_dev_name'), 'snet-odms-gateway-dev')]",
"name": "snet-odms-gateway-dev",
"properties": {
"addressPrefix": "10.1.0.0/24",
"applicationGatewayIPConfigurations": [
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name')), '/gatewayIPConfigurations/appGatewayIpConfig')]"
}
],
"delegations": [],
"privateEndpointNetworkPolicies": "Disabled",
"privateLinkServiceNetworkPolicies": "Enabled",
"serviceEndpoints": [
{
"locations": [
"*"
],
"service": "Microsoft.KeyVault"
},
{
"locations": [
"*"
],
"service": "Microsoft.Web"
}
]
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_dev_name'), 'snet-odms-private-dev')]",
"name": "snet-odms-private-dev",
@ -11341,6 +11474,39 @@
]
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_dev_name'), 'snet-odms-gateway-dev')]",
"name": "snet-odms-gateway-dev",
"properties": {
"addressPrefix": "10.1.0.0/24",
"applicationGatewayIPConfigurations": [
{
"id": "[concat(resourceId('Microsoft.Network/applicationGateways', parameters('applicationGateways_agw_odms_webapp_dev_name')), '/gatewayIPConfigurations/appGatewayIpConfig')]"
}
],
"delegations": [],
"networkSecurityGroup": {
"id": "[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_gateway_dev_name'))]"
},
"privateEndpointNetworkPolicies": "Disabled",
"privateLinkServiceNetworkPolicies": "Enabled",
"serviceEndpoints": [
{
"locations": [
"*"
],
"service": "Microsoft.KeyVault"
},
{
"locations": [
"*"
],
"service": "Microsoft.Web"
}
]
},
"type": "Microsoft.Network/virtualNetworks/subnets"
}
],
"virtualNetworkPeerings": [

View File

@ -564,6 +564,23 @@
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmsaudev_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmsaudev_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmsaudev_name'), '/default/account-10')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -1210,23 +1227,6 @@
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmseudev_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmseudev_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmseudev_name'), '/default/account-63')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -1787,6 +1787,57 @@
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmseudev_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmseudev_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmseudev_name'), '/default/account-92')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmsusdev_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmsusdev_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmsusdev_name'), '/default/account-93')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmseudev_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmseudev_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmseudev_name'), '/default/account-94')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
}
],
"variables": {}

View File

@ -11,6 +11,9 @@
"networkInterfaces_vm_odms_maintenance600_name": {
"type": "String"
},
"networkInterfaces_vm_odms_prod_maintenance7_name": {
"type": "String"
},
"networkInterfaces_vm_odms_staging_maintenance158_name": {
"type": "String"
},
@ -26,6 +29,12 @@
"privateDnsZones_privatelink_azurecr_io_name": {
"type": "String"
},
"privateDnsZones_privatelink_blob_core_windows_net_name": {
"type": "String"
},
"privateEndpoints_pep_odms_bastion_maintenance_name": {
"type": "String"
},
"privateEndpoints_pep_odms_registry_maintenance_name": {
"type": "String"
},
@ -41,6 +50,9 @@
"schedules_shutdown_computevm_vm_odms_maintenance_name": {
"type": "String"
},
"schedules_shutdown_computevm_vm_odms_prod_maintenance_name": {
"type": "String"
},
"schedules_shutdown_computevm_vm_odms_staging_maintenance_name": {
"type": "String"
},
@ -50,6 +62,9 @@
"storageAccounts_saodmscloudshell_name": {
"type": "String"
},
"storageAccounts_saomdsbastion_name": {
"type": "String"
},
"storageAccounts_saomdspipeline_name": {
"type": "String"
},
@ -62,6 +77,9 @@
"virtualMachines_vm_odms_maintenance_name": {
"type": "String"
},
"virtualMachines_vm_odms_prod_maintenance_name": {
"type": "String"
},
"virtualMachines_vm_odms_staging_maintenance_name": {
"type": "String"
},
@ -359,7 +377,7 @@
"direction": "Inbound",
"priority": 130,
"protocol": "TCP",
"sourceAddressPrefix": "220.215.171.117",
"sourceAddressPrefix": "220.215.248.23",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
@ -821,6 +839,124 @@
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowToolInstallerStorageOutbound')]",
"name": "AllowToolInstallerStorageOutbound",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "10.0.2.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 1001,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.0/24",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowProdBastionToRedisOutbound')]",
"name": "AllowProdBastionToRedisOutbound",
"properties": {
"access": "Allow",
"description": "本番環境踏み台PCから本番環境Redisへのoutbound",
"destinationAddressPrefix": "10.3.1.4",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 122,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowDevBastionToRedisOutbound')]",
"name": "AllowDevBastionToRedisOutbound",
"properties": {
"access": "Allow",
"description": "DEV環境踏み台PCからDEV環境Redisへのoutbound",
"destinationAddressPrefix": "10.1.1.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 120,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.4",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowStgBastionToRedisOutbound')]",
"name": "AllowStgBastionToRedisOutbound",
"properties": {
"access": "Allow",
"description": "STG環境踏み台PCからSTG環境Redisへのoutbound",
"destinationAddressPrefix": "10.2.1.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 121,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.5",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowProdHTTPSOutbound')]",
"name": "AllowProdHTTPSOutbound",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "AzureActiveDirectory",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 112,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"id": "[resourceId('Microsoft.Network/networkSecurityGroups/securityRules', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), 'AllowProdAppOutbound')]",
"name": "AllowProdAppOutbound",
"properties": {
"access": "Allow",
"description": "PROD踏み台からPROD環境へのアクセスを許可",
"destinationAddressPrefix": "10.3.0.10",
"destinationAddressPrefixes": [],
"destinationPortRange": "4443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 113,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
}
]
},
@ -849,6 +985,21 @@
},
"type": "Microsoft.Network/privateDnsZones"
},
{
"apiVersion": "2018-09-01",
"location": "global",
"name": "[parameters('privateDnsZones_privatelink_blob_core_windows_net_name')]",
"properties": {
"maxNumberOfRecordSets": 25000,
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 2,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
"type": "Microsoft.Network/privateDnsZones"
},
{
"apiVersion": "2023-05-01",
"location": "japaneast",
@ -930,6 +1081,58 @@
},
"type": "Microsoft.Storage/storageAccounts"
},
{
"apiVersion": "2023-01-01",
"kind": "StorageV2",
"location": "japaneast",
"name": "[parameters('storageAccounts_saomdsbastion_name')]",
"properties": {
"accessTier": "Hot",
"allowBlobPublicAccess": false,
"allowCrossTenantReplication": false,
"allowSharedKeyAccess": true,
"defaultToOAuthAuthentication": false,
"dnsEndpointType": "Standard",
"encryption": {
"keySource": "Microsoft.Storage",
"requireInfrastructureEncryption": true,
"services": {
"blob": {
"enabled": true,
"keyType": "Account"
},
"file": {
"enabled": true,
"keyType": "Account"
}
}
},
"minimumTlsVersion": "TLS1_2",
"networkAcls": {
"bypass": "AzureServices",
"defaultAction": "Deny",
"ipRules": [
{
"action": "Allow",
"value": "180.39.76.100"
}
],
"resourceAccessRules": [],
"virtualNetworkRules": []
},
"publicNetworkAccess": "Enabled",
"supportsHttpsTrafficOnly": true
},
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"tags": {
"Environment": "maintenance",
"Project": "ODMS"
},
"type": "Microsoft.Storage/storageAccounts"
},
{
"apiVersion": "2023-01-01",
"kind": "StorageV2",
@ -1136,6 +1339,80 @@
},
"type": "Microsoft.Compute/virtualMachines"
},
{
"apiVersion": "2023-03-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm_odms_prod_maintenance7_name'))]"
],
"location": "japaneast",
"name": "[parameters('virtualMachines_vm_odms_prod_maintenance_name')]",
"properties": {
"diagnosticsProfile": {
"bootDiagnostics": {
"enabled": true
}
},
"hardwareProfile": {
"vmSize": "Standard_B2s"
},
"networkProfile": {
"networkInterfaces": [
{
"id": "[resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm_odms_prod_maintenance7_name'))]"
}
]
},
"osProfile": {
"adminUsername": "odmsAdmin",
"allowExtensionOperations": true,
"computerName": "vm-odms-prod-ma",
"requireGuestProvisionSignal": true,
"secrets": [],
"windowsConfiguration": {
"enableAutomaticUpdates": true,
"enableVMAgentPlatformUpdates": false,
"patchSettings": {
"assessmentMode": "ImageDefault",
"enableHotpatching": false,
"patchMode": "AutomaticByOS"
},
"provisionVMAgent": true
}
},
"securityProfile": {
"securityType": "TrustedLaunch",
"uefiSettings": {
"secureBootEnabled": true,
"vTpmEnabled": true
}
},
"storageProfile": {
"dataDisks": [],
"diskControllerType": "SCSI",
"imageReference": {
"offer": "WindowsServer",
"publisher": "MicrosoftWindowsServer",
"sku": "2022-datacenter-azure-edition",
"version": "latest"
},
"osDisk": {
"caching": "ReadWrite",
"createOption": "FromImage",
"deleteOption": "Delete",
"managedDisk": {
"id": "[resourceId('Microsoft.Compute/disks', concat(parameters('virtualMachines_vm_odms_prod_maintenance_name'), '_OsDisk_1_89b0ffad76d44d57a136152577e01483'))]"
},
"name": "[concat(parameters('virtualMachines_vm_odms_prod_maintenance_name'), '_OsDisk_1_89b0ffad76d44d57a136152577e01483')]",
"osType": "Windows"
}
}
},
"tags": {
"Environment": "production",
"Project": "ODMS"
},
"type": "Microsoft.Compute/virtualMachines"
},
{
"apiVersion": "2023-03-01",
"dependsOn": [
@ -1199,10 +1476,8 @@
"caching": "ReadWrite",
"createOption": "FromImage",
"deleteOption": "Delete",
"diskSizeGB": 127,
"managedDisk": {
"id": "[resourceId('Microsoft.Compute/disks', concat(parameters('virtualMachines_vm_odms_staging_maintenance_name'), '_OsDisk_1_903a7540b5a64475b512aedc10487661'))]",
"storageAccountType": "Premium_LRS"
"id": "[resourceId('Microsoft.Compute/disks', concat(parameters('virtualMachines_vm_odms_staging_maintenance_name'), '_OsDisk_1_903a7540b5a64475b512aedc10487661'))]"
},
"name": "[concat(parameters('virtualMachines_vm_odms_staging_maintenance_name'), '_OsDisk_1_903a7540b5a64475b512aedc10487661')]",
"osType": "Windows"
@ -1402,6 +1677,33 @@
},
"type": "microsoft.devtestlab/schedules"
},
{
"apiVersion": "2018-09-15",
"dependsOn": [
"[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachines_vm_odms_prod_maintenance_name'))]"
],
"location": "japaneast",
"name": "[parameters('schedules_shutdown_computevm_vm_odms_prod_maintenance_name')]",
"properties": {
"dailyRecurrence": {
"time": "1900"
},
"notificationSettings": {
"notificationLocale": "ja",
"status": "Disabled",
"timeInMinutes": 30
},
"status": "Enabled",
"targetResourceId": "[resourceId('Microsoft.Compute/virtualMachines', parameters('virtualMachines_vm_odms_prod_maintenance_name'))]",
"taskType": "ComputeVmShutdownTask",
"timeZoneId": "Tokyo Standard Time"
},
"tags": {
"Environment": "production",
"Project": "ODMS"
},
"type": "microsoft.devtestlab/schedules"
},
{
"apiVersion": "2018-09-15",
"dependsOn": [
@ -1507,7 +1809,7 @@
"enableIPForwarding": false,
"ipConfigurations": [
{
"etag": "W/\"0f027839-4941-4a48-a05c-b0f240513f07\"",
"etag": "W/\"7a9501b8-2c41-4e35-8622-46c24a6baa0d\"",
"id": "[concat(resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm_odms_maintenance600_name')), '/ipConfigurations/ipconfig1')]",
"name": "ipconfig1",
"properties": {
@ -1531,6 +1833,49 @@
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_maintenance_name'), 'snet-odms-vm-maintenance')]"
],
"kind": "Regular",
"location": "japaneast",
"name": "[parameters('networkInterfaces_vm_odms_prod_maintenance7_name')]",
"properties": {
"auxiliaryMode": "None",
"auxiliarySku": "None",
"disableTcpStateTracking": false,
"dnsSettings": {
"dnsServers": []
},
"enableAcceleratedNetworking": false,
"enableIPForwarding": false,
"ipConfigurations": [
{
"etag": "W/\"2e09acfb-738c-4282-80b6-c0d107ba3ac3\"",
"id": "[concat(resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm_odms_prod_maintenance7_name')), '/ipConfigurations/ipconfig1')]",
"name": "ipconfig1",
"properties": {
"primary": true,
"privateIPAddress": "10.0.2.6",
"privateIPAddressVersion": "IPv4",
"privateIPAllocationMethod": "Dynamic",
"provisioningState": "Succeeded",
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_maintenance_name'), 'snet-odms-vm-maintenance')]"
}
},
"type": "Microsoft.Network/networkInterfaces/ipConfigurations"
}
],
"nicType": "Standard"
},
"tags": {
"Environment": "production",
"Project": "ODMS"
},
"type": "Microsoft.Network/networkInterfaces"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -1550,7 +1895,7 @@
"enableIPForwarding": false,
"ipConfigurations": [
{
"etag": "W/\"94c366ab-a94a-4393-b858-aefb939b6fc9\"",
"etag": "W/\"d3adbed2-54d6-431d-b056-a9393f6021c3\"",
"id": "[concat(resourceId('Microsoft.Network/networkInterfaces', parameters('networkInterfaces_vm_odms_staging_maintenance158_name')), '/ipConfigurations/ipconfig1')]",
"name": "ipconfig1",
"properties": {
@ -1733,6 +2078,29 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowDevBastionToRedisOutbound')]",
"properties": {
"access": "Allow",
"description": "DEV環境踏み台PCからDEV環境Redisへのoutbound",
"destinationAddressPrefix": "10.1.1.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 120,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.4",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -1948,7 +2316,7 @@
"direction": "Inbound",
"priority": 130,
"protocol": "TCP",
"sourceAddressPrefix": "220.215.171.117",
"sourceAddressPrefix": "220.215.248.23",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
@ -1977,6 +2345,74 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowProdAppOutbound')]",
"properties": {
"access": "Allow",
"description": "PROD踏み台からPROD環境へのアクセスを許可",
"destinationAddressPrefix": "10.3.0.10",
"destinationAddressPrefixes": [],
"destinationPortRange": "4443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 113,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowProdBastionToRedisOutbound')]",
"properties": {
"access": "Allow",
"description": "本番環境踏み台PCから本番環境Redisへのoutbound",
"destinationAddressPrefix": "10.3.1.4",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 122,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowProdHTTPSOutbound')]",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "AzureActiveDirectory",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 112,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.6",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -2046,6 +2482,29 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowStgBastionToRedisOutbound')]",
"properties": {
"access": "Allow",
"description": "STG環境踏み台PCからSTG環境Redisへのoutbound",
"destinationAddressPrefix": "10.2.1.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "6380",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 121,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.5",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -2137,6 +2596,28 @@
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/networkSecurityGroups', parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'))]"
],
"name": "[concat(parameters('networkSecurityGroups_nsg_odms_vm_maintenance_name'), '/AllowToolInstallerStorageOutbound')]",
"properties": {
"access": "Allow",
"destinationAddressPrefix": "10.0.2.7",
"destinationAddressPrefixes": [],
"destinationPortRange": "443",
"destinationPortRanges": [],
"direction": "Outbound",
"priority": 1001,
"protocol": "TCP",
"sourceAddressPrefix": "10.0.2.0/24",
"sourceAddressPrefixes": [],
"sourcePortRange": "*",
"sourcePortRanges": []
},
"type": "Microsoft.Network/networkSecurityGroups/securityRules"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -2309,6 +2790,22 @@
},
"type": "Microsoft.Network/privateDnsZones/A"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_blob_core_windows_net_name'))]"
],
"name": "[concat(parameters('privateDnsZones_privatelink_blob_core_windows_net_name'), '/saomdsbastion')]",
"properties": {
"aRecords": [
{
"ipv4Address": "10.0.2.7"
}
],
"ttl": 3600
},
"type": "Microsoft.Network/privateDnsZones/A"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
@ -2329,6 +2826,26 @@
},
"type": "Microsoft.Network/privateDnsZones/SOA"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_blob_core_windows_net_name'))]"
],
"name": "[concat(parameters('privateDnsZones_privatelink_blob_core_windows_net_name'), '/@')]",
"properties": {
"soaRecord": {
"email": "azureprivatedns-host.microsoft.com",
"expireTime": 2419200,
"host": "azureprivatedns.net",
"minimumTtl": 10,
"refreshTime": 3600,
"retryTime": 300,
"serialNumber": 1
},
"ttl": 3600
},
"type": "Microsoft.Network/privateDnsZones/SOA"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
@ -2507,6 +3024,39 @@
},
"type": "Microsoft.Storage/storageAccounts/blobServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default')]",
"properties": {
"changeFeed": {
"enabled": false
},
"containerDeleteRetentionPolicy": {
"days": 7,
"enabled": true
},
"cors": {
"corsRules": []
},
"deleteRetentionPolicy": {
"allowPermanentDelete": false,
"days": 7,
"enabled": true
},
"isVersioningEnabled": true,
"restorePolicy": {
"enabled": false
}
},
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"type": "Microsoft.Storage/storageAccounts/blobServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2564,6 +3114,30 @@
},
"type": "Microsoft.Storage/storageAccounts/fileServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default')]",
"properties": {
"cors": {
"corsRules": []
},
"protocolSettings": {
"smb": {}
},
"shareDeleteRetentionPolicy": {
"days": 7,
"enabled": true
}
},
"sku": {
"name": "Standard_LRS",
"tier": "Standard"
},
"type": "Microsoft.Storage/storageAccounts/fileServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2588,6 +3162,23 @@
},
"type": "Microsoft.Storage/storageAccounts/fileServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/', parameters('storageAccounts_saomdsbastion_name'), '.9d0044b4-3ec4-4ef1-9c5f-dee9d7e813fb')]",
"properties": {
"privateEndpoint": {},
"privateLinkServiceConnectionState": {
"actionRequired": "None",
"description": "Auto-Approved",
"status": "Approved"
},
"provisioningState": "Succeeded"
},
"type": "Microsoft.Storage/storageAccounts/privateEndpointConnections"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2601,6 +3192,19 @@
},
"type": "Microsoft.Storage/storageAccounts/queueServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default')]",
"properties": {
"cors": {
"corsRules": []
}
},
"type": "Microsoft.Storage/storageAccounts/queueServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2627,6 +3231,19 @@
},
"type": "Microsoft.Storage/storageAccounts/tableServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default')]",
"properties": {
"cors": {
"corsRules": []
}
},
"type": "Microsoft.Storage/storageAccounts/tableServices"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2715,6 +3332,68 @@
},
"type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_blob_core_windows_net_name'))]",
"[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_odms_network_maintenance_name'))]"
],
"location": "global",
"name": "[concat(parameters('privateDnsZones_privatelink_blob_core_windows_net_name'), '/e3fkm7ajqovu6')]",
"properties": {
"registrationEnabled": false,
"virtualNetwork": {
"id": "[resourceId('Microsoft.Network/virtualNetworks', parameters('virtualNetworks_vnet_odms_network_maintenance_name'))]"
}
},
"type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]",
"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_maintenance_name'), 'snet-odms-vm-maintenance')]"
],
"location": "japaneast",
"name": "[parameters('privateEndpoints_pep_odms_bastion_maintenance_name')]",
"properties": {
"customDnsConfigs": [
{
"fqdn": "saomdsbastion.blob.core.windows.net",
"ipAddresses": [
"10.0.2.7"
]
}
],
"ipConfigurations": [],
"manualPrivateLinkServiceConnections": [],
"privateLinkServiceConnections": [
{
"id": "[concat(resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_pep_odms_bastion_maintenance_name')), concat('/privateLinkServiceConnections/', parameters('privateEndpoints_pep_odms_bastion_maintenance_name'), '_ac9d69cb-cf72-4b78-8163-0d902150b027'))]",
"name": "[concat(parameters('privateEndpoints_pep_odms_bastion_maintenance_name'), '_ac9d69cb-cf72-4b78-8163-0d902150b027')]",
"properties": {
"groupIds": [
"blob"
],
"privateLinkServiceConnectionState": {
"actionsRequired": "None",
"description": "Auto-Approved",
"status": "Approved"
},
"privateLinkServiceId": "[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
}
}
],
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_maintenance_name'), 'snet-odms-vm-maintenance')]"
}
},
"tags": {
"Environment": "maintenance",
"Project": "ODMS"
},
"type": "Microsoft.Network/privateEndpoints"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -2838,6 +3517,40 @@
},
"type": "Microsoft.Network/virtualNetworks/subnets"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saomdsbastion_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default/develop')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saomdsbastion_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saomdsbastion_name'))]"
],
"name": "[concat(parameters('storageAccounts_saomdsbastion_name'), '/default/prod')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [

File diff suppressed because it is too large Load Diff

View File

@ -49,8 +49,8 @@
"version": "8.0.21"
},
"sku": {
"name": "Standard_B1s",
"tier": "Burstable"
"name": "Standard_D2ads_v5",
"tier": "GeneralPurpose"
},
"tags": {
"Environment": "staging",
@ -63,7 +63,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231026t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231107t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -71,7 +71,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231027t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231108t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -79,7 +79,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231028t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231109t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -87,7 +87,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231029t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231110t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -95,7 +95,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231030t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231111t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -103,7 +103,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231031t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231112t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -111,7 +111,7 @@
"dependsOn": [
"[resourceId('Microsoft.DBforMySQL/flexibleServers', parameters('flexibleServers_mysql_odms_db_stg_name'))]"
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231101t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/daily-20231113t173100-c3fd140d-3620-488c-9516-d0c0fb44c636')]",
"type": "Microsoft.DBforMySQL/flexibleServers/backups"
},
{
@ -443,9 +443,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/back_log')]",
"properties": {
"currentValue": "185",
"currentValue": "783",
"source": "system-default",
"value": "185"
"value": "783"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -469,9 +469,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/binlog_cache_size')]",
"properties": {
"currentValue": "131072",
"currentValue": "8388608",
"source": "system-default",
"value": "131072"
"value": "8388608"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -701,9 +701,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/binlog_transaction_dependency_history_size')]",
"properties": {
"currentValue": "1000",
"currentValue": "8000",
"source": "system-default",
"value": "1000"
"value": "8000"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -1479,9 +1479,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_buffer_pool_chunk_size')]",
"properties": {
"currentValue": "33554432",
"currentValue": "134217728",
"source": "system-default",
"value": "33554432"
"value": "134217728"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -1557,9 +1557,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_buffer_pool_instances')]",
"properties": {
"currentValue": "1",
"currentValue": "8",
"source": "system-default",
"value": "1"
"value": "8"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -1609,9 +1609,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_buffer_pool_size')]",
"properties": {
"currentValue": "134217728",
"currentValue": "4294967296",
"source": "system-default",
"value": "134217728"
"value": "4294967296"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -1902,9 +1902,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_fatal_semaphore_wait_threshold')]",
"properties": {
"currentValue": "7201",
"currentValue": "600",
"source": "system-default",
"value": "7201"
"value": "600"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -2993,9 +2993,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_temp_tablespaces_dir')]",
"properties": {
"currentValue": "/app/work/temp",
"currentValue": "/mnt/temp",
"source": "system-default",
"value": "/app/work/temp"
"value": "/mnt/temp"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -3032,9 +3032,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/innodb_tmpdir')]",
"properties": {
"currentValue": "/app/work/temp",
"currentValue": "/mnt/temp",
"source": "system-default",
"value": "/app/work/temp"
"value": "/mnt/temp"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -3559,9 +3559,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/max_allowed_packet')]",
"properties": {
"currentValue": "16777216",
"currentValue": "536870912",
"source": "system-default",
"value": "16777216"
"value": "536870912"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -3624,9 +3624,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/max_connections')]",
"properties": {
"currentValue": "85",
"currentValue": "683",
"source": "system-default",
"value": "85"
"value": "683"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -4736,9 +4736,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/relay_log')]",
"properties": {
"currentValue": "/app/work/relaylogs/relay_bin",
"currentValue": "/mnt/relaylogs/relay_bin",
"source": "system-default",
"value": "/app/work/relaylogs/relay_bin"
"value": "/mnt/relaylogs/relay_bin"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -4749,9 +4749,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/relay_log_index')]",
"properties": {
"currentValue": "/app/work/relaylogs/relay_bin.index",
"currentValue": "/mnt/relaylogs/relay_bin.index",
"source": "system-default",
"value": "/app/work/relaylogs/relay_bin.index"
"value": "/mnt/relaylogs/relay_bin.index"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -4801,9 +4801,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/relay_log_space_limit')]",
"properties": {
"currentValue": "1073741824",
"currentValue": "4294967296",
"source": "system-default",
"value": "1073741824"
"value": "4294967296"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5152,9 +5152,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/slave_load_tmpdir')]",
"properties": {
"currentValue": "/app/work/temp",
"currentValue": "/mnt/temp",
"source": "system-default",
"value": "/app/work/temp"
"value": "/mnt/temp"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5204,9 +5204,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/slave_parallel_workers')]",
"properties": {
"currentValue": "0",
"currentValue": "8",
"source": "system-default",
"value": "0"
"value": "8"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5217,9 +5217,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/slave_pending_jobs_size_max')]",
"properties": {
"currentValue": "16777216",
"currentValue": "67108864",
"source": "system-default",
"value": "16777216"
"value": "67108864"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5332,9 +5332,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/slow_query_log_file')]",
"properties": {
"currentValue": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-stg-2023110201.log",
"currentValue": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-stg-2023111401.log",
"source": "user-override",
"value": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-stg-2023110201.log"
"value": "/app/serverlogs/slowlogs/mysql-slow-mysql-odms-db-stg-2023111401.log"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5345,9 +5345,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/sort_buffer_size')]",
"properties": {
"currentValue": "262144",
"currentValue": "524288",
"source": "system-default",
"value": "262144"
"value": "524288"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5501,9 +5501,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/table_definition_cache')]",
"properties": {
"currentValue": "300",
"currentValue": "2400",
"source": "system-default",
"value": "300"
"value": "2400"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5527,9 +5527,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/table_open_cache')]",
"properties": {
"currentValue": "300",
"currentValue": "2400",
"source": "system-default",
"value": "300"
"value": "2400"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5592,9 +5592,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/thread_cache_size')]",
"properties": {
"currentValue": "8",
"currentValue": "14",
"source": "system-default",
"value": "8"
"value": "14"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5670,9 +5670,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/thread_pool_size')]",
"properties": {
"currentValue": "1",
"currentValue": "2",
"source": "system-default",
"value": "1"
"value": "2"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},
@ -5761,9 +5761,9 @@
],
"name": "[concat(parameters('flexibleServers_mysql_odms_db_stg_name'), '/tmpdir')]",
"properties": {
"currentValue": "/app/work/temp",
"currentValue": "/mnt/temp",
"source": "system-default",
"value": "/app/work/temp"
"value": "/mnt/temp"
},
"type": "Microsoft.DBforMySQL/flexibleServers/configurations"
},

View File

@ -32,6 +32,9 @@
"privateDnsZones_privatelink_redis_cache_windows_net_name": {
"type": "String"
},
"privateEndpoints_pep_odms_app_staging_stg_name": {
"type": "String"
},
"privateEndpoints_pep_odms_app_stg_name": {
"type": "String"
},
@ -1387,7 +1390,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 3,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 2,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1402,7 +1405,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 3,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 2,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1421,7 +1424,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 2,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1435,8 +1438,8 @@
"maxNumberOfRecordSets": 25000,
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 3,
"numberOfVirtualNetworkLinks": 0,
"numberOfRecordSets": 5,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -1454,7 +1457,7 @@
"maxNumberOfVirtualNetworkLinks": 1000,
"maxNumberOfVirtualNetworkLinksWithRegistration": 100,
"numberOfRecordSets": 2,
"numberOfVirtualNetworkLinks": 0,
"numberOfVirtualNetworkLinks": 1,
"numberOfVirtualNetworkLinksWithRegistration": 0,
"provisioningState": "Succeeded"
},
@ -2411,6 +2414,44 @@
},
"type": "Microsoft.Network/privateDnsZones/A"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_azurewebsites_net_name'))]"
],
"name": "[concat(parameters('privateDnsZones_privatelink_azurewebsites_net_name'), '/app-odms-dictation-stg-staging')]",
"properties": {
"aRecords": [
{
"ipv4Address": "10.2.1.8"
}
],
"metadata": {
"creator": "created by private endpoint pep-odms-app-staging-stg with resource guid 8d258be6-1804-4cc3-8172-46075366effa"
},
"ttl": 10
},
"type": "Microsoft.Network/privateDnsZones/A"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_azurewebsites_net_name'))]"
],
"name": "[concat(parameters('privateDnsZones_privatelink_azurewebsites_net_name'), '/app-odms-dictation-stg-staging.scm')]",
"properties": {
"aRecords": [
{
"ipv4Address": "10.2.1.8"
}
],
"metadata": {
"creator": "created by private endpoint pep-odms-app-staging-stg with resource guid 8d258be6-1804-4cc3-8172-46075366effa"
},
"ttl": 10
},
"type": "Microsoft.Network/privateDnsZones/A"
},
{
"apiVersion": "2018-09-01",
"dependsOn": [
@ -2611,6 +2652,39 @@
},
"type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_stg_name'), 'snet-odms-public-stg')]"
],
"location": "japaneast",
"name": "[parameters('privateEndpoints_pep_odms_app_staging_stg_name')]",
"properties": {
"customDnsConfigs": [],
"ipConfigurations": [],
"manualPrivateLinkServiceConnections": [],
"privateLinkServiceConnections": [
{
"id": "[concat(resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_pep_odms_app_staging_stg_name')), concat('/privateLinkServiceConnections/', parameters('privateEndpoints_pep_odms_app_staging_stg_name'), '-9794'))]",
"name": "[concat(parameters('privateEndpoints_pep_odms_app_staging_stg_name'), '-9794')]",
"properties": {
"groupIds": [
"sites-staging"
],
"privateLinkServiceConnectionState": {
"actionsRequired": "None",
"status": "Approved"
},
"privateLinkServiceId": "[parameters('sites_app_odms_dictation_stg_externalid')]"
}
}
],
"subnet": {
"id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', parameters('virtualNetworks_vnet_odms_network_stg_name'), 'snet-odms-public-stg')]"
}
},
"type": "Microsoft.Network/privateEndpoints"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
@ -8969,6 +9043,22 @@
},
"type": "Microsoft.OperationalInsights/workspaces/tables"
},
{
"apiVersion": "2021-12-01-preview",
"dependsOn": [
"[resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspaces_log_odms_agw_stg_name'))]"
],
"name": "[concat(parameters('workspaces_log_odms_agw_stg_name'), '/LASummaryLogs')]",
"properties": {
"plan": "Analytics",
"retentionInDays": 30,
"schema": {
"name": "LASummaryLogs"
},
"totalRetentionInDays": 30
},
"type": "Microsoft.OperationalInsights/workspaces/tables"
},
{
"apiVersion": "2021-12-01-preview",
"dependsOn": [
@ -11257,6 +11347,25 @@
},
"type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [
"[resourceId('Microsoft.Network/privateEndpoints', parameters('privateEndpoints_pep_odms_app_staging_stg_name'))]",
"[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_azurewebsites_net_name'))]"
],
"name": "[concat(parameters('privateEndpoints_pep_odms_app_staging_stg_name'), '/default')]",
"properties": {
"privateDnsZoneConfigs": [
{
"name": "privatelink-azurewebsites-net",
"properties": {
"privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', parameters('privateDnsZones_privatelink_azurewebsites_net_name'))]"
}
}
]
},
"type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups"
},
{
"apiVersion": "2023-05-01",
"dependsOn": [

View File

@ -2066,6 +2066,23 @@
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmsusstg_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmsusstg_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmsusstg_name'), '/default/account-189')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
@ -2083,6 +2100,40 @@
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmsusstg_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmsusstg_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmsusstg_name'), '/default/account-191')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [
"[resourceId('Microsoft.Storage/storageAccounts/blobServices', parameters('storageAccounts_saodmsaustg_name'), 'default')]",
"[resourceId('Microsoft.Storage/storageAccounts', parameters('storageAccounts_saodmsaustg_name'))]"
],
"name": "[concat(parameters('storageAccounts_saodmsaustg_name'), '/default/account-192')]",
"properties": {
"defaultEncryptionScope": "$account-encryption-key",
"denyEncryptionScopeOverride": false,
"immutableStorageWithVersioning": {
"enabled": false
},
"publicAccess": "None"
},
"type": "Microsoft.Storage/storageAccounts/blobServices/containers"
},
{
"apiVersion": "2023-01-01",
"dependsOn": [

View File

@ -0,0 +1,12 @@
# To enable ssh & remote debugging on app service change the base image to the one below
# FROM mcr.microsoft.com/azure-functions/node:4-node18-appservice
FROM mcr.microsoft.com/azure-functions/node:4-node18
ENV AzureWebJobsScriptRoot=/home/site/wwwroot \
AzureFunctionsJobHost__Logging__Console__IsEnabled=true
COPY . /home/site/wwwroot
RUN cd /home/site/wwwroot && \
npm install && \
npm run build

View File

@ -6663,6 +6663,77 @@
"node": ">=6.9.0"
}
},
"node_modules/slash/node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/slash/node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/slash/node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/slash/node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/slash/node_modules/escape-string-regexp": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
"dev": true,
"engines": {
"node": ">=0.8.0"
}
},
"node_modules/slash/node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/slash/node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/smart-buffer": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",

View File

@ -14,9 +14,11 @@ import {
DateWithZeroTime,
ExpirationThresholdDate,
} from "../common/types/types";
import { getMailFrom } from "../common/getEnv/getEnv";
import { createMailContentOfLicenseShortage } from "../sendgrid/mailContents/U103ShortageAlert";
import { createMailContentOfLicenseExpiringSoon } from "../sendgrid/mailContents/U104ExpiringSoonAlert";
import { AdB2cService } from "../adb2c/adb2c.service";
import { SendGridService } from "../sendgrid/sendgrid.service";
import { getMailFrom } from "../common/getEnv/getEnv";
export async function licenseAlertProcessing(
context: InvocationContext,
@ -194,7 +196,11 @@ export async function licenseAlertProcessing(
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseShortage();
await createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
@ -218,7 +224,11 @@ export async function licenseAlertProcessing(
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseShortage();
await createMailContentOfLicenseShortage(
targetAccount.companyName,
targetAccount.shortage,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
@ -243,7 +253,11 @@ export async function licenseAlertProcessing(
// ライセンス失効警告メール
if (targetAccount.userCountOfLicenseExpiringSoon !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseExpiringSoon();
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
@ -267,7 +281,11 @@ export async function licenseAlertProcessing(
// ライセンス不足メール
if (targetAccount.shortage !== 0) {
const { subject, text, html } =
await sendgrid.createMailContentOfLicenseExpiringSoon();
await createMailContentOfLicenseExpiringSoon(
targetAccount.companyName,
targetAccount.userCountOfLicenseExpiringSoon,
targetAccount.parentCompanyName
);
// メールを送信
try {
await sendgrid.sendMail(
@ -330,7 +348,7 @@ export async function licenseAlert(
}
app.timer("licenseAlert", {
schedule: "0 */1 * * * *",
schedule: "0 0 1 * * *",
handler: licenseAlert,
});

View File

@ -0,0 +1,179 @@
/**
* ()
* @param companyName
* @param shortage
* @param dealer
* @returns
*/
export async function createMailContentOfLicenseShortage(
companyName: string,
shortage: number,
dealer: string | undefined
): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
// subject ////////////////////////////////////////////////////////////////////
subject: "License Shortage Notification [U-103]",
// text ////////////////////////////////////////////////////////////////////
text: `<English>
Dear ${companyName},
One or more of your assigned ODMS Cloud licenses will expire within 14 days. There is insufficient amount of unassigned licenses in your inventory to issue to users with expiring licenses.
Insufficient license count: ${shortage}
Please order additional annual licenses from ${dealer} to ensure you have sufficient inventory.
You can either automatically or manually assign licenses to users. Users with the Auto-assign option enabled (default) will have their license automatically assigned from your license inventory on the expiration date. If you disable the Auto-assign option, you must manually assign licenses.
Please log in to ODMS Cloud to configure your user setting and verify the license expiration date.
URL: https://odmscloud.omsystem.com/
If you need support regarding ODMS Cloud, please contact ${dealer}.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) ${companyName},
Eine oder mehrere Ihrer zugewiesenen ODMS Cloud-Lizenzen laufen innerhalb von 14 Tagen ab. In Ihrem Bestand ist nicht genügend Anzahl nicht zugewiesener Lizenzen Inventar, um diese an Benutzer mit ablaufenden Lizenzen auszugeben.
Unzureichende Lizenzanzahl: ${shortage}
Bitte bestellen Sie zusätzliche Jahreslizenzen bei Ihrem ${dealer} um sicherzustellen, dass Sie über ausreichend Lagerbestände Inventar.
Sie können Benutzern entweder automatisch oder manuell Lizenzen zuweisen. Benutzern mit aktivierter Option Automatische Zuweisung (Standard) wird die Lizenz am Ablaufdatum automatisch aus Ihrem Lizenzbestand zugewiesen. Wenn Sie die Option Automatisch zuweisen deaktivieren, müssen Sie Lizenzen manuell zuweisen.
Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.
URL: https://odmscloud.omsystem.com/
Wenn Sie Unterstützung bezüglich ODMS benötigen, wenden Sie sich bitte an ${dealer}.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Español>
Estimado(a) ${companyName},
Una o más de sus licencias de ODMS Cloud asignadas caducarán en un plazo de 14 días. No hay una cantidad suficiente de licencias no asignadas en su inventario para emitirlas a usuarios con licencias vencidas.
Recuento de licencias insuficiente: ${shortage}
Solicite licencias anuales adicionales a su ${dealer} para asegurarse de tener suficiente inventario.
Puede asignar licencias a los usuarios de forma automática o manual. A los usuarios con la opción Asignación automática habilitada (predeterminada) se les asignará su licencia automáticamente desde su inventario de licencias en la fecha de vencimiento. Si desactiva la opción Asignar automáticamente, deberá asignar licencias manualmente.
Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.
URL: https://odmscloud.omsystem.com/
Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.
Si recibió este correo electrónico por error, elimínelo de su sistema.
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.
<Français>
Chère/Cher ${companyName},
Une ou plusieurs de vos licences ODMS Cloud attribuées expireront dans les 14 jours. Le nombre de licences non attribuées dans votre inventaire est insuffisant pour être délivré aux utilisateurs dont les licences arrivent à expiration.
Nombre de licences insuffisant: ${shortage}
Veuillez commander des licences annuelles supplémentaires auprès de votre ${dealer} pour vous assurer que vous disposez d'un inventaire suffisant.
Vous pouvez attribuer automatiquement ou manuellement des licences aux utilisateurs. Les utilisateurs dont l'option d'attribution automatique est activée (par défaut) verront leur licence automatiquement attribuée à partir de votre inventaire de licences à la date d'expiration. Si vous désactivez l'option Attribution automatique, vous devez attribuer manuellement des licences.
Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence.
URL: https://odmscloud.omsystem.com/
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter ${dealer}.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.`,
// html ////////////////////////////////////////////////////////////////////
html: `<h3>&lt;English&gt;</h3>
<p>Dear ${companyName},<p>
<p>One or more of your assigned ODMS Cloud licenses will expire within 14 days. There is insufficient amount of unassigned licenses in your inventory to issue to users with expiring licenses.</p>
<p>Insufficient license count: ${shortage}</p>
<p>Please order additional annual licenses from ${dealer} to ensure you have sufficient inventory. </p>
<p>You can either automatically or manually assign licenses to users. Users with the Auto-assign option enabled (default) will have their license automatically assigned from your license inventory on the expiration date. If you disable the Auto-assign option, you must manually assign licenses.</p>
<p>Please log in to ODMS Cloud to configure your user setting and verify the license expiration date.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>If you need support regarding ODMS Cloud, please contact ${dealer}.</p>
<p>If you have received this e-mail in error, please delete this e-mail from your system.<br>
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.</p>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) ${companyName},</p>
<p>Eine oder mehrere Ihrer zugewiesenen ODMS Cloud-Lizenzen laufen innerhalb von 14 Tagen ab. In Ihrem Bestand ist nicht genügend Anzahl nicht zugewiesener Lizenzen Inventar, um diese an Benutzer mit ablaufenden Lizenzen auszugeben.</p>
<p>Unzureichende Lizenzanzahl: ${shortage}</p>
<p>Bitte bestellen Sie zusätzliche Jahreslizenzen bei Ihrem ${dealer} um sicherzustellen, dass Sie über ausreichend Lagerbestände Inventar.</p>
<p>Sie können Benutzern entweder automatisch oder manuell Lizenzen zuweisen. Benutzern mit aktivierter Option Automatische Zuweisung (Standard) wird die Lizenz am Ablaufdatum automatisch aus Ihrem Lizenzbestand zugewiesen. Wenn Sie die Option Automatisch zuweisen deaktivieren, müssen Sie Lizenzen manuell zuweisen.</p>
<p>Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Wenn Sie Unterstützung bezüglich ODMS benötigen, wenden Sie sich bitte an ${dealer}.</p>
<p>Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.<br>
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p>
<h3>&lt;Español&gt;</h3>
<p>Estimado(a) ${companyName},</p>
<p>Una o más de sus licencias de ODMS Cloud asignadas caducarán en un plazo de 14 días. No hay una cantidad suficiente de licencias no asignadas en su inventario para emitirlas a usuarios con licencias vencidas.</p>
<p>Recuento de licencias insuficiente: ${shortage}</p>
<p>Solicite licencias anuales adicionales a su ${dealer} para asegurarse de tener suficiente inventario.</p>
<p>Puede asignar licencias a los usuarios de forma automática o manual. A los usuarios con la opción Asignación automática habilitada (predeterminada) se les asignará su licencia automáticamente desde su inventario de licencias en la fecha de vencimiento. Si desactiva la opción Asignar automáticamente, deberá asignar licencias manualmente.</p>
<p>Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.</p>
<p>Si recibió este correo electrónico por error, elimínelo de su sistema.<br>
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.</p>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher ${companyName},</p>
<p>Une ou plusieurs de vos licences ODMS Cloud attribuées expireront dans les 14 jours. Le nombre de licences non attribuées dans votre inventaire est insuffisant pour être délivré aux utilisateurs dont les licences arrivent à expiration.</p>
<p>Nombre de licences insuffisant: ${shortage}</p>
<p>Veuillez commander des licences annuelles supplémentaires auprès de votre ${dealer} pour vous assurer que vous disposez d'un inventaire suffisant.</p>
<p>Vous pouvez attribuer automatiquement ou manuellement des licences aux utilisateurs. Les utilisateurs dont l'option d'attribution automatique est activée (par défaut) verront leur licence automatiquement attribuée à partir de votre inventaire de licences à la date d'expiration. Si vous désactivez l'option Attribution automatique, vous devez attribuer manuellement des licences.</p>
<p>Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter ${dealer}.</p>
<p>Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.<br>
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.</p>`,
};
}

View File

@ -0,0 +1,156 @@
/**
* ()
* @param companyName
* @param ExpiringSoonUserCount
* @param dealer
* @returns
*/
export async function createMailContentOfLicenseExpiringSoon(
companyName: string,
ExpiringSoonUserCount: number,
dealer: string | undefined
): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
// subject ////////////////////////////////////////////////////////////////////
subject: "License Expiration Warning [U-104]",
// text ////////////////////////////////////////////////////////////////////
text: `<English>
Dear ${companyName},
One or more of your assigned ODMS Cloud licenses will expire today.
Number of licenses expiring: ${ExpiringSoonUserCount}
If you do not have a sufficient number of licenses, you will need to order annual licenses from your ${dealer} and assign them to your users whose licenses are expiring.
Please log in to ODMS Cloud to configure your user setting and verify the license expiration date.
URL: https://odmscloud.omsystem.com/
If you need support regarding ODMS Cloud, please contact ${dealer}.
If you have received this e-mail in error, please delete this e-mail from your system.
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.
<Deutsch>
Sehr geehrte(r) ${companyName},
Eine oder mehrere Ihrer zugewiesenen ODMS Cloud-Lizenzen laufen heute ab.
Anzahl der auslaufenden Lizenzen: ${ExpiringSoonUserCount}
Wenn Sie nicht über genügend Lizenzen verfügen, müssen Sie Jahreslizenzen bei Ihrem ${dealer} bestellen und diese Ihren Benutzern zuweisen, deren Lizenzen ablaufen.
Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.
URL: https://odmscloud.omsystem.com/
Wenn Sie Unterstützung bezüglich ODMS benötigen, wenden Sie sich bitte an ${dealer}.
Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.
<Español>
Estimado(a) ${companyName},
Una o más de sus licencias de ODMS Cloud asignadas caducarán hoy.
Número de licencias que vencen: ${ExpiringSoonUserCount}
Si no tiene una cantidad suficiente de licencias, deberá solicitar licencias anuales a su ${dealer} y asignarlas a los usuarios cuyas licencias están por vencer.
Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.
URL: https://odmscloud.omsystem.com/
Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.
Si recibió este correo electrónico por error, elimínelo de su sistema.
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.
<Français>
Chère/Cher ${companyName},
Une ou plusieurs de vos licences ODMS Cloud attribuées expireront aujourd'hui.
Nombre de licences arrivant à expiration: ${ExpiringSoonUserCount}
Si vous ne disposez pas d'un nombre suffisant de licences, vous devrez commander des licences annuelles auprès de votre ${dealer} et les attribuer à vos utilisateurs dont les licences arrivent à expiration.
Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence.
URL: https://odmscloud.omsystem.com/
Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter ${dealer}.
Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.`,
// html ////////////////////////////////////////////////////////////////////
html: `<h3>&lt;English&gt;</h3>
<p>Dear ${companyName},</p>
<p>One or more of your assigned ODMS Cloud licenses will expire today.<br>
Number of licenses expiring: ${ExpiringSoonUserCount}</p>
<p>If you do not have a sufficient number of licenses, you will need to order annual licenses from your ${dealer} and assign them to your users whose licenses are expiring.</p>
<p>Please log in to ODMS Cloud to configure your user setting and verify the license expiration date.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>If you need support regarding ODMS Cloud, please contact ${dealer}.</p>
<p>If you have received this e-mail in error, please delete this e-mail from your system.<br>
This is an automatically generated e-mail and this mailbox is not monitored. Please do not reply.</p>
<h3>&lt;Deutsch&gt;</h3>
<p>Sehr geehrte(r) ${companyName},</p>
<p>Eine oder mehrere Ihrer zugewiesenen ODMS Cloud-Lizenzen laufen heute ab.<br>
Anzahl der auslaufenden Lizenzen: ${ExpiringSoonUserCount}</p>
<p>Wenn Sie nicht über genügend Lizenzen verfügen, müssen Sie Jahreslizenzen bei Ihrem ${dealer} bestellen und diese Ihren Benutzern zuweisen, deren Lizenzen ablaufen.</p>
<p>Bitte melden Sie sich bei ODMS Cloud an, um Ihre Benutzereinstellungen zu konfigurieren und das Ablaufdatum der Lizenz zu überprüfen.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Wenn Sie Unterstützung bezüglich ODMS benötigen, wenden Sie sich bitte an ${dealer}.</p>
<p>Wenn Sie diese E-Mail fälschlicherweise erhalten haben, löschen Sie diese E-Mail bitte aus Ihrem System.<br>
Dies ist eine automatisch generierte E-Mail und dieses Postfach wird nicht überwacht. Bitte nicht antworten.</p>
<h3>&lt;Español&gt;</h3>
<p>Estimado(a) ${companyName},</p>
<p>Una o más de sus licencias de ODMS Cloud asignadas caducarán hoy.<br>
Número de licencias que vencen: ${ExpiringSoonUserCount}</p>
<p>Si no tiene una cantidad suficiente de licencias, deberá solicitar licencias anuales a su ${dealer} y asignarlas a los usuarios cuyas licencias están por vencer.</p>
<p>Inicie sesión en ODMS Cloud para configurar su configuración de usuario y verificar la fecha de vencimiento de la licencia.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si necesita ayuda con respecto a ODMS Cloud, comuníquese con ${dealer}.</p>
<p>Si recibió este correo electrónico por error, elimínelo de su sistema.<br>
Este es un correo electrónico generado automáticamente y este buzón no está monitoreado. Por favor, no responda.</p>
<h3>&lt;Français&gt;</h3>
<p>Chère/Cher ${companyName},</p>
<p>Une ou plusieurs de vos licences ODMS Cloud attribuées expireront aujourd'hui.<br>
Nombre de licences arrivant à expiration: ${ExpiringSoonUserCount}</p>
<p>Si vous ne disposez pas d'un nombre suffisant de licences, vous devrez commander des licences annuelles auprès de votre ${dealer} et les attribuer à vos utilisateurs dont les licences arrivent à expiration.</p>
<p>Veuillez vous connecter à ODMS Cloud pour configurer vos paramètres utilisateur et vérifier la date d'expiration de la licence.<br>
URL: <a href="https://odmscloud.omsystem.com/">https://odmscloud.omsystem.com/</a></p>
<p>Si vous avez besoin d'assistance concernant ODMS Cloud, veuillez contacter ${dealer}.</p>
<p>Si vous avez reçu cet e-mail par erreur, veuillez supprimer cet e-mail de votre système.<br>
Il s'agit d'un e-mail généré automatiquement et cette boîte aux lettres n'est pas surveillée. Merci de ne pas répondre.</p>
`,
};
}

View File

@ -8,43 +8,6 @@ export class SendGridService {
}
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
}
/**
* ()
* @param accountId ID
* @param userId ID
* @param email
* @returns
*/
async createMailContentOfLicenseShortage(): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
subject: "ライセンス在庫不足通知",
text: `ライセンス在庫不足通知:本文`,
html: `<p>ライセンス在庫不足通知:本文</p>`,
};
}
/**
* ()
* @param accountId ID
* @param userId ID
* @param email
* @returns
*/
async createMailContentOfLicenseExpiringSoon(): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
subject: "ライセンス失効警告 ",
text: `ライセンス失効警告:本文`,
html: `<p>ライセンス失効警告:本文</p>`,
};
}
/**
*

View File

@ -41,14 +41,6 @@ describe("licenseAlert", () => {
const sendgridMock = new SendGridServiceMock() as SendGridService;
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spyShortage = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseShortage"
);
const spyExpirySoon = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseExpiringSoon"
);
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDate = new DateWithZeroTime();
@ -72,8 +64,6 @@ describe("licenseAlert", () => {
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
expect(spyShortage.mock.calls).toHaveLength(1);
expect(spyExpirySoon.mock.calls).toHaveLength(0);
expect(spySend.mock.calls).toHaveLength(1);
});
@ -84,14 +74,6 @@ describe("licenseAlert", () => {
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spyShortage = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseShortage"
);
const spyExpirySoon = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseExpiringSoon"
);
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDate = new DateWithZeroTime();
@ -115,8 +97,6 @@ describe("licenseAlert", () => {
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
expect(spyShortage.mock.calls).toHaveLength(1);
expect(spyExpirySoon.mock.calls).toHaveLength(1);
expect(spySend.mock.calls).toHaveLength(2);
});
@ -127,14 +107,6 @@ describe("licenseAlert", () => {
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spyShortage = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseShortage"
);
const spyExpirySoon = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseExpiringSoon"
);
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDate = new DateWithZeroTime();
@ -171,8 +143,6 @@ describe("licenseAlert", () => {
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
expect(spyShortage.mock.calls).toHaveLength(0);
expect(spyExpirySoon.mock.calls).toHaveLength(0);
expect(spySend.mock.calls).toHaveLength(0);
});
@ -183,14 +153,6 @@ describe("licenseAlert", () => {
const adb2cMock = new AdB2cServiceMock() as AdB2cService;
// 呼び出し回数でテスト成否を判定
const spyShortage = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseShortage"
);
const spyExpirySoon = jest.spyOn(
sendgridMock,
"createMailContentOfLicenseExpiringSoon"
);
const spySend = jest.spyOn(sendgridMock, "sendMail");
const currentDate = new DateWithZeroTime();
@ -214,52 +176,12 @@ describe("licenseAlert", () => {
);
await licenseAlertProcessing(context, source, sendgridMock, adb2cMock);
expect(spyShortage.mock.calls).toHaveLength(1);
expect(spyExpirySoon.mock.calls).toHaveLength(0);
expect(spySend.mock.calls).toHaveLength(1);
});
});
// テスト用sendgrid
export class SendGridServiceMock {
/**
* ()
* @param accountId ID
* @param userId ID
* @param email
* @returns
*/
async createMailContentOfLicenseShortage(): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
subject: "ライセンス在庫不足通知",
text: `ライセンス在庫不足通知:本文`,
html: `<p>ライセンス在庫不足通知:本文</p>`,
};
}
/**
* ()
* @param accountId ID
* @param userId ID
* @param email
* @returns
*/
async createMailContentOfLicenseExpiringSoon(): Promise<{
subject: string;
text: string;
html: string;
}> {
return {
subject: "ライセンス失効警告",
text: `ライセンス失効警告:本文`,
html: `<p>ライセンス失効警告:本文</p>`,
};
}
/**
*
* @param to

View File

@ -11,7 +11,7 @@
"dependencies": {
"@azure/identity": "^3.1.3",
"@azure/keyvault-secrets": "^4.6.0",
"@azure/notification-hubs": "^1.0.2",
"@azure/notification-hubs": "^1.0.3",
"@azure/storage-blob": "^12.14.0",
"@microsoft/microsoft-graph-client": "^3.0.5",
"@nestjs/axios": "^0.1.0",
@ -341,6 +341,22 @@
"openapi-types": ">=7"
}
},
"node_modules/@azure-rest/core-client": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@azure-rest/core-client/-/core-client-1.1.4.tgz",
"integrity": "sha512-RUIQOA8T0WcbNlddr8hjl2MuC5GVRqmMwPXqBVsgvdKesLy+eg3y/6nf3qe2fvcJMI1gF6VtgU5U4hRaR4w4ag==",
"dependencies": {
"@azure/abort-controller": "^1.1.0",
"@azure/core-auth": "^1.3.0",
"@azure/core-rest-pipeline": "^1.5.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/@azure/abort-controller": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@azure/abort-controller/-/abort-controller-1.1.0.tgz",
@ -353,15 +369,16 @@
}
},
"node_modules/@azure/core-auth": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.4.0.tgz",
"integrity": "sha512-HFrcTgmuSuukRf/EdPmqBrc5l6Q5Uu+2TbuhaKbgaCpP2TfAeiNaQPAadxO+CYBRHGUzIDteMAjFspFLDLnKVQ==",
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/@azure/core-auth/-/core-auth-1.5.0.tgz",
"integrity": "sha512-udzoBuYG1VBoHVohDTrvKjyzel34zt77Bhp7dQntVGGD0ehVq48owENbBG8fIgkHRNUBQH5k1r0hpoMu5L8+kw==",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-util": "^1.1.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=12.0.0"
"node": ">=14.0.0"
}
},
"node_modules/@azure/core-client": {
@ -443,11 +460,12 @@
}
},
"node_modules/@azure/core-lro": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.1.tgz",
"integrity": "sha512-JHQy/bA3NOz2WuzOi5zEk6n/TJdAropupxUT521JIJvW7EXV2YN2SFYZrf/2RHeD28QAClGdynYadZsbmP+nyQ==",
"version": "2.5.4",
"resolved": "https://registry.npmjs.org/@azure/core-lro/-/core-lro-2.5.4.tgz",
"integrity": "sha512-3GJiMVH7/10bulzOKGrrLeG/uCBH/9VtxqaMcB9lIqAeamI/xYQSHJL/KcsLDuH+yTjYpro/u6D/MuRe4dN70Q==",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-util": "^1.2.0",
"@azure/logger": "^1.0.0",
"tslib": "^2.2.0"
},
@ -467,23 +485,22 @@
}
},
"node_modules/@azure/core-rest-pipeline": {
"version": "1.10.2",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.10.2.tgz",
"integrity": "sha512-e3WzAsRKLor5EgK2bQqR1OY5D7VBqzORHtlqtygZZQGCYOIBsynqrZBa8MFD1Ue9r8TPtofOLditalnlQHS45Q==",
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/@azure/core-rest-pipeline/-/core-rest-pipeline-1.12.2.tgz",
"integrity": "sha512-wLLJQdL4v1yoqYtEtjKNjf8pJ/G/BqVomAWxcKOR1KbZJyCEnCv04yks7Y1NhJ3JzxbDs307W67uX0JzklFdCg==",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"@azure/core-auth": "^1.4.0",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.0.0",
"@azure/core-util": "^1.3.0",
"@azure/logger": "^1.0.0",
"form-data": "^4.0.0",
"http-proxy-agent": "^5.0.0",
"https-proxy-agent": "^5.0.0",
"tslib": "^2.2.0",
"uuid": "^8.3.0"
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
"node": ">=16.0.0"
}
},
"node_modules/@azure/core-tracing": {
@ -498,23 +515,23 @@
}
},
"node_modules/@azure/core-util": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.3.2.tgz",
"integrity": "sha512-2bECOUh88RvL1pMZTcc6OzfobBeWDBf5oBbhjIhT1MV9otMVWCzpOJkkiKtrnO88y5GGBelgY8At73KGAdbkeQ==",
"version": "1.6.1",
"resolved": "https://registry.npmjs.org/@azure/core-util/-/core-util-1.6.1.tgz",
"integrity": "sha512-h5taHeySlsV9qxuK64KZxy4iln1BtMYlNt5jbuEFN3UFSAd1EwKg/Gjl5a6tZ/W8t6li3xPnutOx7zbDyXnPmQ==",
"dependencies": {
"@azure/abort-controller": "^1.0.0",
"tslib": "^2.2.0"
},
"engines": {
"node": ">=14.0.0"
"node": ">=16.0.0"
}
},
"node_modules/@azure/core-xml": {
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.3.3.tgz",
"integrity": "sha512-Go/xGz7nGqVINsD9O7gOfe8uiR1S+IFcw9WTUPJHSzoFT6F5ZWjXIIlSikLZm77TtmxzXGnQYjjiZIoIZ4x14A==",
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/@azure/core-xml/-/core-xml-1.3.4.tgz",
"integrity": "sha512-B1xI79Ur/u+KR69fGTcsMNj8KDjBSqAy0Ys6Byy4Qm1CqoUy7gCT5A7Pej0EBWRskuH6bpCwrAnosfmQEalkcg==",
"dependencies": {
"fast-xml-parser": "^4.0.8",
"fast-xml-parser": "^4.2.4",
"tslib": "^2.2.0"
},
"engines": {
@ -628,25 +645,31 @@
}
},
"node_modules/@azure/notification-hubs": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.2.tgz",
"integrity": "sha512-INtgq8uFQpncwbKm4It8M0GkKIePNDNybhuXs4cQPf5H0i9CbfFEt2c6LtT1AdEzbWfUhjsmU5y0p3YDmecwwg==",
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@azure/notification-hubs/-/notification-hubs-1.0.3.tgz",
"integrity": "sha512-IWzIO2nKwM+9CWgnTsMyZhXxR7q3n3/aXpJiqUn1U3xHXTtHVE+joZ/AL9fCZTEBVB1PQvbrjp8VZvwEptqJ/Q==",
"dependencies": {
"@azure-rest/core-client": "^1.1.4",
"@azure/abort-controller": "^1.1.0",
"@azure/core-client": "^1.6.1",
"@azure/core-lro": "^2.4.0",
"@azure/core-paging": "^1.3.0",
"@azure/core-rest-pipeline": "^1.8.1",
"@azure/core-auth": "^1.5.0",
"@azure/core-lro": "^2.5.4",
"@azure/core-paging": "^1.5.0",
"@azure/core-rest-pipeline": "^1.12.2",
"@azure/core-tracing": "^1.0.1",
"@azure/core-util": "^1.3.0",
"@azure/core-xml": "^1.3.1",
"@azure/logger": "^1.0.3",
"tslib": "^2.4.0"
"@azure/core-util": "^1.6.1",
"@azure/core-xml": "^1.3.4",
"@azure/logger": "^1.0.4",
"tslib": "^2.6.2"
},
"engines": {
"node": ">=14.0.0"
"node": ">=18.0.0"
}
},
"node_modules/@azure/notification-hubs/node_modules/tslib": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz",
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q=="
},
"node_modules/@azure/storage-blob": {
"version": "12.14.0",
"resolved": "https://registry.npmjs.org/@azure/storage-blob/-/storage-blob-12.14.0.tgz",
@ -5899,17 +5922,17 @@
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"node_modules/fast-xml-parser": {
"version": "4.2.6",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.2.6.tgz",
"integrity": "sha512-Xo1qV++h/Y3Ng8dphjahnYe+rGHaaNdsYOBWL9Y9GCPKpNKilJtilvWkLcI9f9X2DoKTLsZsGYAls5+JL5jfLA==",
"version": "4.3.2",
"resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz",
"integrity": "sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg==",
"funding": [
{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
},
{
"type": "github",
"url": "https://github.com/sponsors/NaturalIntelligence"
},
{
"type": "paypal",
"url": "https://paypal.me/naturalintelligence"
}
],
"dependencies": {

View File

@ -31,7 +31,7 @@
"dependencies": {
"@azure/identity": "^3.1.3",
"@azure/keyvault-secrets": "^4.6.0",
"@azure/notification-hubs": "^1.0.2",
"@azure/notification-hubs": "^1.0.3",
"@azure/storage-blob": "^12.14.0",
"@microsoft/microsoft-graph-client": "^3.0.5",
"@nestjs/axios": "^0.1.0",

View File

@ -2077,6 +2077,14 @@
}
}
},
"400": {
"description": "不正なパラメータ",
"content": {
"application/json": {
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
}
}
},
"401": {
"description": "認証エラー",
"content": {
@ -4180,7 +4188,7 @@
"properties": {
"authorId": {
"type": "string",
"description": "ログインしたユーザーのAuthorIDAuthorでない場合は空文字"
"description": "ログインしたユーザーのAuthorIDAuthorでない場合はundefined"
},
"authorIdList": {
"description": "属しているアカウントのAuthorID List(全て)",
@ -4215,7 +4223,6 @@
}
},
"required": [
"authorId",
"authorIdList",
"workTypeList",
"isEncrypted",

View File

@ -39,6 +39,7 @@ export const ErrorCodes = [
'E010501', // アカウント不在エラー
'E010502', // アカウント情報変更不可エラー
'E010503', // 代行操作不許可エラー
'E010504', // アカウントロックエラー
'E010601', // タスク変更不可エラー(タスクが変更できる状態でない、またはタスクが存在しない)
'E010602', // タスク変更権限不足エラー
'E010603', // タスク不在エラー
@ -54,6 +55,7 @@ export const ErrorCodes = [
'E010809', // ライセンス発行キャンセル不可エラー(ステータスが変えられている場合)
'E010810', // ライセンス発行キャンセル不可エラー(発行から一定期間経過した場合)
'E010811', // ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
'E010812', // ライセンス未割当エラー
'E010908', // タイピストグループ不在エラー
'E011001', // ワークタイプ重複エラー
'E011002', // ワークタイプ登録上限超過エラー

View File

@ -28,6 +28,7 @@ export const errors: Errors = {
E010501: 'Account not Found Error.',
E010502: 'Account information cannot be changed Error.',
E010503: 'Delegation not allowed Error.',
E010504: 'Account is locked Error.',
E010601: 'Task is not Editable Error',
E010602: 'No task edit permissions Error',
E010603: 'Task not found Error.',
@ -43,6 +44,7 @@ export const errors: Errors = {
E010809: 'Already license order status changed Error',
E010810: 'Cancellation period expired error',
E010811: 'Already license allocated Error',
E010812: 'License not allocated Error',
E010908: 'Typist Group not exist Error',
E011001: 'This WorkTypeID already used Error',
E011002: 'WorkTypeID create limit exceeded Error',

View File

@ -4,8 +4,5 @@ export const makeContext = (
externalId: string,
delegationId?: string,
): Context => {
return {
trackingId: externalId,
delegationId: delegationId,
};
return new Context(externalId, delegationId);
};

View File

@ -7,4 +7,19 @@ export class Context {
* APIの代行操作ユーザーを追跡するためのID
*/
delegationId?: string | undefined;
constructor(externalId: string, delegationId?: string) {
this.trackingId = externalId;
this.delegationId = delegationId;
}
/**
*
*/
getTrackingId(): string {
if (this.delegationId) {
return `${this.trackingId} by ${this.delegationId}`;
} else {
return this.trackingId;
}
}
}

View File

@ -1,13 +0,0 @@
/*
M+6
- 1~2...
- 3~4...
- 5~6
ex)
E00XXXX : 正常系メッセージ
E01XXXX : 以上系メッセージ
EXX00XX : 全般
EXX01XX : タスク関連
*/
export const NotifyMessageCodes = ['M000101'] as const;

View File

@ -1,7 +0,0 @@
import { notifyMessages } from './message';
import { NotifyMessageCodeType } from './types/types';
export const makeNotifyMessage = (code: NotifyMessageCodeType): string => {
const msg = notifyMessages[code];
return msg;
};

View File

@ -1,6 +0,0 @@
import { NotifyMessages } from './types/types';
// エラーコードとメッセージ対応表
export const notifyMessages: NotifyMessages = {
M000101: 'You are assigned to Typist.',
};

View File

@ -1,7 +1,6 @@
import { NotifyMessageCodes } from '../code';
export type NotifyMessageCodeType = (typeof NotifyMessageCodes)[number];
export type NotifyMessages = {
[P in NotifyMessageCodeType]: string;
export type NotificationBody = {
filename: string;
authorId: string;
priority: string;
uploadedAt: string;
};

View File

@ -185,6 +185,11 @@ export const overrideBlobstorageService = <TService>(
accountId: number,
country: string,
) => Promise<boolean>;
publishUploadSas?: (
context: Context,
accountId: number,
country: string,
) => Promise<string>;
publishTemplateUploadSas?: (
context: Context,
accountId: number,
@ -212,6 +217,12 @@ export const overrideBlobstorageService = <TService>(
writable: true,
});
}
if (overrides.publishUploadSas) {
Object.defineProperty(obj, obj.publishUploadSas.name, {
value: overrides.publishUploadSas,
writable: true,
});
}
if (overrides.publishTemplateUploadSas) {
Object.defineProperty(obj, obj.publishTemplateUploadSas.name, {
value: overrides.publishTemplateUploadSas,

View File

@ -239,6 +239,27 @@ export const OPTION_ITEM_VALUE_TYPE = {
LAST_INPUT: 'LastInput',
} as const;
/**
*
**/
export const OPTION_ITEM_VALUE_TYPE_NUMBER: {
type: string;
value: number;
}[] = [
{
type: OPTION_ITEM_VALUE_TYPE.BLANK,
value: 1,
},
{
type: OPTION_ITEM_VALUE_TYPE.DEFAULT,
value: 2,
},
{
type: OPTION_ITEM_VALUE_TYPE.LAST_INPUT,
value: 3,
},
];
/**
* ADB2Cユーザのidentity.signInType
* @const {string[]}
@ -261,3 +282,9 @@ export const TERM_TYPE = {
EULA: 'EULA',
DPA: 'DPA',
} as const;
/**
*
* @const {string}
*/
export const USER_AUDIO_FORMAT = 'DS2(QP)';

View File

@ -76,9 +76,9 @@ import { retrieveAuthorizationToken } from '../../common/http/helper';
import { AccessToken } from '../../common/token';
import jwt from 'jsonwebtoken';
import { makeContext } from '../../common/log';
import { v4 as uuidv4 } from 'uuid';
import { AuthService } from '../auth/auth.service';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { v4 as uuidv4 } from 'uuid';
@ApiTags('accounts')
@Controller('accounts')
@ -167,7 +167,24 @@ export class AccountsController {
@Req() req: Request,
@Body() body: GetLicenseSummaryRequest,
): Promise<GetLicenseSummaryResponse> {
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const response = await this.accountService.getLicenseSummary(
context,
body.accountId,
);
return response;
@ -317,8 +334,9 @@ export class AccountsController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const typists = await this.accountService.getTypists(userId);
const typists = await this.accountService.getTypists(context, userId);
return { typists };
}
@ -363,8 +381,12 @@ export class AccountsController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const typistGroups = await this.accountService.getTypistGroups(userId);
const typistGroups = await this.accountService.getTypistGroups(
context,
userId,
);
return { typistGroups };
}
@ -666,8 +688,31 @@ export class AccountsController {
): Promise<GetPartnerLicensesResponse> {
const { limit, offset, accountId } = body;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const getPartnerLicensesResponse =
await this.accountService.getPartnerLicenses(limit, offset, accountId);
await this.accountService.getPartnerLicenses(
context,
limit,
offset,
accountId,
);
return getPartnerLicensesResponse;
}
@ -703,8 +748,31 @@ export class AccountsController {
): Promise<GetOrderHistoriesResponse> {
const { limit, offset, accountId } = body;
const accessToken = retrieveAuthorizationToken(req);
if (!accessToken) {
throw new HttpException(
makeErrorResponse('E000107'),
HttpStatus.UNAUTHORIZED,
);
}
const decodedAccessToken = jwt.decode(accessToken, { json: true });
if (!decodedAccessToken) {
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const getOrderHistoriesResponse =
await this.accountService.getOrderHistories(limit, offset, accountId);
await this.accountService.getOrderHistories(
context,
limit,
offset,
accountId,
);
return getOrderHistoriesResponse;
}
@ -786,7 +854,8 @@ export class AccountsController {
})
@ApiOperation({ operationId: 'getDealers' })
async getDealers(): Promise<GetDealersResponse> {
return await this.accountService.getDealers();
const context = makeContext(uuidv4());
return await this.accountService.getDealers(context);
}
@Post('/issue/cancel')
@ -1460,9 +1529,14 @@ export class AccountsController {
async getAccountInfoMinimalAccess(
@Body() body: GetAccountInfoMinimalAccessRequest,
): Promise<GetAccountInfoMinimalAccessResponse> {
const context = makeContext(uuidv4());
// IDトークンの検証
const idToken = await this.authService.getVerifiedIdToken(body.idToken);
const isVerified = await this.authService.isVerifiedUser(idToken);
const idToken = await this.authService.getVerifiedIdToken(
context,
body.idToken,
);
const isVerified = await this.authService.isVerifiedUser(context, idToken);
if (!isVerified) {
throw new HttpException(
makeErrorResponse('E010201'),
@ -1470,8 +1544,6 @@ export class AccountsController {
);
}
const context = makeContext(idToken.sub);
const tier = await this.accountService.getAccountInfoMinimalAccess(
context,
idToken.sub,

View File

@ -1587,7 +1587,8 @@ describe('AccountsService', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
expect(await service.getLicenseSummary(accountId)).toEqual(
const context = makeContext(`uuidv4`);
expect(await service.getLicenseSummary(context, accountId)).toEqual(
expectedAccountLisenceCounts,
);
});
@ -1619,7 +1620,8 @@ describe('AccountsService', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
await expect(service.getLicenseSummary(accountId)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getLicenseSummary(context, accountId)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1653,7 +1655,8 @@ describe('AccountsService', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
expect(await service.getTypists(externalId)).toEqual([
const context = makeContext(`uuidv4`);
expect(await service.getTypists(context, externalId)).toEqual([
{ id: 1, name: 'Typist1' },
{ id: 2, name: 'Typist2' },
{ id: 3, name: 'Typist3' },
@ -1686,7 +1689,8 @@ describe('AccountsService', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
await expect(service.getTypists(externalId)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getTypists(context, externalId)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1720,7 +1724,8 @@ describe('AccountsService', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
await expect(service.getTypists(externalId)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getTypists(context, externalId)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1755,7 +1760,8 @@ describe('AccountsService', () => {
worktypesRepositoryMockValue,
);
expect(await service.getTypistGroups(externalId)).toEqual([
const context = makeContext(`uuidv4`);
expect(await service.getTypistGroups(context, externalId)).toEqual([
{ id: 1, name: 'GroupA' },
{ id: 2, name: 'GroupB' },
]);
@ -1788,7 +1794,8 @@ describe('AccountsService', () => {
worktypesRepositoryMockValue,
);
await expect(service.getTypistGroups(externalId)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getTypistGroups(context, externalId)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1823,7 +1830,8 @@ describe('AccountsService', () => {
worktypesRepositoryMockValue,
);
await expect(service.getTypistGroups(externalId)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getTypistGroups(context, externalId)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -2005,7 +2013,13 @@ describe('getPartnerAccount', () => {
const offset = 0;
const limit = 20;
const response = await service.getPartnerLicenses(limit, offset, accountId);
const context = makeContext(`uuidv4`);
const response = await service.getPartnerLicenses(
context,
limit,
offset,
accountId,
);
expect(response.total).toBe(2);
@ -2146,7 +2160,13 @@ describe('getPartnerAccount', () => {
const offset = 0;
const limit = 20;
const response = await service.getPartnerLicenses(limit, offset, accountId);
const context = makeContext(`uuidv4`);
const response = await service.getPartnerLicenses(
context,
limit,
offset,
accountId,
);
// 有効期限間近5件 有効期限間近でない3件'Unallocated', 'Allocated', 'Reusable' 有効期限未設定1件  9件
expect(response.childrenPartnerLicenses[0].stockLicense).toBe(9);
@ -2239,7 +2259,13 @@ describe('getOrderHistories', () => {
const offset = 1;
const limit = 2;
const response = await service.getOrderHistories(limit, offset, accountId);
const context = makeContext(`uuidv4`);
const response = await service.getOrderHistories(
context,
limit,
offset,
accountId,
);
expect(response.total).toBe(5);
@ -2278,8 +2304,9 @@ describe('getOrderHistories', () => {
licensesRepositoryMockValue,
worktypesRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
await expect(
service.getOrderHistories(limit, offset, accountId),
service.getOrderHistories(context, limit, offset, accountId),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -2640,8 +2667,9 @@ describe('getDealers', () => {
})
).account;
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(`uuidv4`);
expect(await service.getDealers()).toEqual({
expect(await service.getDealers(context)).toEqual({
dealers: [
{
country: 'JP',
@ -2668,7 +2696,8 @@ describe('getDealers', () => {
const service = module.get<AccountsService>(AccountsService);
expect(await service.getDealers()).toEqual({
const context = makeContext(`uuidv4`);
expect(await service.getDealers(context)).toEqual({
dealers: [],
});
});
@ -2752,7 +2781,6 @@ describe('createTypistGroup', () => {
expect(typistGroupUsers.map((user) => user.user_id)).toEqual(userIds);
}
});
it('typistIdsにRole:typist以外のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -2859,6 +2887,58 @@ describe('createTypistGroup', () => {
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
});
it('typistIdsにメール未認証のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const adminExternalId = 'admin-external-id';
// 第五階層のアカウント作成
const { id: accountId } = (
await makeTestAccount(
source,
{ tier: 5 },
{ external_id: adminExternalId },
)
).account;
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const user = await makeTestUser(source, {
account_id: accountId,
external_id: typiptUserExternalId,
role: 'typist',
email_verified: false, // メール未認証のユーザーを追加
});
userIds.push(user?.id ?? 0);
}
//作成したデータを確認
{
const accounts = await getAccounts(source);
expect(accounts.length).toBe(1);
expect(accounts[0].id).toBe(accountId);
const users = await getUsers(source);
expect(users.length).toBe(4);
}
const service = module.get<AccountsService>(AccountsService);
const typistGroupName = 'typist-group-name';
const typistUserIds = [...userIds];
const context = makeContext(adminExternalId);
await expect(
service.createTypistGroup(
context,
adminExternalId,
typistGroupName,
typistUserIds,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
});
it('DBアクセスに失敗した場合、500エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -3338,6 +3418,69 @@ describe('updateTypistGroup', () => {
}
}
});
it('typistIdsにメール未認証のユーザーが含まれていた場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
// 作成したアカウントにユーザーを3名追加する
const typiptUserExternalIds = [
'typist-user-external-id1',
'typist-user-external-id2',
'typist-user-external-id3',
];
const userIds: number[] = [];
for (const typiptUserExternalId of typiptUserExternalIds) {
const user = await makeTestUser(source, {
account_id: account.id,
external_id: typiptUserExternalId,
role: USER_ROLES.TYPIST,
email_verified: typiptUserExternalId !== 'typist-user-external-id3', //typist-user-external-id3のみメール未認証
});
userIds.push(user?.id ?? 0);
}
const typistGroupName = 'typist-group-name';
const service = module.get<AccountsService>(AccountsService);
const typistUserIds = [...userIds];
const context = makeContext(admin.external_id);
await service.createTypistGroup(
context,
admin.external_id,
typistGroupName,
[userIds[0]],
);
//作成したデータを確認
const group = await getTypistGroup(source, account.id);
{
expect(group.length).toBe(1);
expect(group[0].name).toBe(typistGroupName);
const groupUsers = await getTypistGroupMember(source, group[0].id);
expect(groupUsers.length).toBe(1);
expect(groupUsers[0].user_group_id).toEqual(group[0].id);
expect(groupUsers[0].user_id).toEqual(userIds[0]);
}
const updateTypistGroupName = 'typist-group-name-update';
try {
await service.updateTypistGroup(
context,
admin.external_id,
group[0].id,
updateTypistGroupName,
typistUserIds,
);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('タイピストグループが存在しない場合、400エラーを返却する', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -5754,6 +5897,44 @@ describe('getAuthors', () => {
expect(authors[1].authorId).toBe('AUTHOR_ID_2');
}
});
it('アカウント内のAuthorユーザーの一覧を取得できる未認証ユーザー以外', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const userId1 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_ID_1',
});
const userId2 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_ID_2',
email_verified: false,
});
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(3);
expect(users[1].id).toBe(userId1.id);
expect(users[2].id).toBe(userId2.id);
}
const service = module.get<AccountsService>(AccountsService);
const context = makeContext(admin.external_id);
const authors = await service.getAuthors(context, admin.external_id);
//実行結果を確認
{
expect(authors.length).toBe(1);
expect(authors[0].id).toBe(userId1.id);
expect(authors[0].authorId).toBe('AUTHOR_ID_1');
}
});
it('アカウント内のAuthorユーザーの一覧を取得できる0件', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -5805,6 +5986,189 @@ describe('getAuthors', () => {
}
});
});
describe('getTypists', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('アカウント内のTypistユーザーの一覧を取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const userId1 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
external_id: 'typist1',
});
const userId2 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
external_id: 'typist2',
});
const userId3 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_ID_1',
external_id: 'author1',
});
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(4);
expect(users[1].id).toBe(userId1.id);
expect(users[2].id).toBe(userId2.id);
expect(users[3].id).toBe(userId3.id);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [
{ id: admin.external_id, displayName: '' },
{ id: userId1.external_id, displayName: '' },
{ id: userId2.external_id, displayName: '' },
{ id: userId3.external_id, displayName: '' },
],
});
const context = makeContext(`uuidv4`);
const typists = await service.getTypists(context, admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(2);
expect(typists[0].id).toBe(userId1.id);
expect(typists[1].id).toBe(userId2.id);
}
});
it('アカウント内のTypistユーザーの一覧を取得できる0件', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(1);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [{ id: admin.external_id, displayName: '' }],
});
const context = makeContext(`uuidv4`);
const typists = await service.getTypists(context, admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(0);
}
});
it('アカウント内のTypistユーザーの一覧を取得できるメール認証済みユーザーのみ', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const userId1 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const userId2 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const userId3 = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
email_verified: false,
});
// 作成したデータを確認
{
const users = await getUsers(source);
expect(users.length).toBe(4);
expect(users[1].id).toBe(userId1.id);
expect(users[2].id).toBe(userId2.id);
expect(users[3].id).toBe(userId3.id);
}
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [
{ id: admin.external_id, displayName: '' },
{ id: userId1.external_id, displayName: '' },
{ id: userId2.external_id, displayName: '' },
{ id: userId3.external_id, displayName: '' },
],
});
const context = makeContext(`uuidv4`);
const typists = await service.getTypists(context, admin.external_id);
//実行結果を確認
{
expect(typists.length).toBe(2);
expect(typists[0].id).toBe(userId1.id);
expect(typists[1].id).toBe(userId2.id);
}
});
it('DBアクセスに失敗した場合、500エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5 });
const service = module.get<AccountsService>(AccountsService);
overrideAdB2cService(service, {
getUsers: async () => [{ id: admin.external_id, displayName: '' }],
});
//DBアクセスに失敗するようにする
const usersService = module.get<UsersRepositoryService>(
UsersRepositoryService,
);
usersService.findTypistUsers = jest.fn().mockRejectedValue('DB failed');
const context = makeContext(`uuidv4`);
//実行結果を確認
try {
await service.getTypists(context, admin.external_id);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});
describe('deleteAccountAndData', () => {
let source: DataSource | null = null;
beforeEach(async () => {

View File

@ -93,9 +93,14 @@ export class AccountsService {
* @returns LicenseSummary
*/
async getLicenseSummary(
context: Context,
accountId: number,
): Promise<GetLicenseSummaryResponse> {
this.logger.log(`[IN] ${this.getLicenseSummary.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getLicenseSummary.name
} | params: { ` + `accountId: ${accountId}, };`,
);
try {
const currentDate = new DateWithZeroTime();
@ -139,8 +144,10 @@ export class AccountsService {
};
return licenseSummaryResponse;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('get licenseSummary failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] get licenseSummary failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -167,8 +174,9 @@ export class AccountsService {
acceptedDpaVersion: string,
): Promise<{ accountId: number; userId: number; externalUserId: string }> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createAccount.name} | params: { ` +
`country: ${country}, ` +
`[IN] [${context.getTrackingId()}] ${
this.createAccount.name
} | params: { ` +
`dealerAccountId: ${dealerAccountId}, ` +
`role: ${role}, ` +
`acceptedEulaVersion: ${acceptedEulaVersion} }, ` +
@ -185,8 +193,10 @@ export class AccountsService {
username,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create externalUser failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
@ -196,7 +206,9 @@ export class AccountsService {
// メールアドレス重複エラー
if (isConflictError(externalUser)) {
this.logger.error(`email conflict. externalUser: ${externalUser}`);
this.logger.error(
`[${context.getTrackingId()}] email conflict. externalUser: ${externalUser}`,
);
throw new HttpException(
makeErrorResponse('E010301'),
HttpStatus.BAD_REQUEST,
@ -221,11 +233,13 @@ export class AccountsService {
account = newAccount;
user = adminUser;
this.logger.log(
`[${context.trackingId}] adminUser.external_id: ${user.external_id}`,
`[${context.getTrackingId()}] adminUser.external_id: ${
user.external_id
}`,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create account failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] create account failed`);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -244,8 +258,10 @@ export class AccountsService {
country,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create container failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create container failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -279,8 +295,8 @@ export class AccountsService {
html,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('send E-mail failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] send E-mail failed`);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -306,7 +322,7 @@ export class AccountsService {
throw e;
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createAccount.name}`,
`[OUT] [${context.getTrackingId()}] ${this.createAccount.name}`,
);
}
}
@ -320,12 +336,13 @@ export class AccountsService {
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.trackingId}] delete externalUser: ${externalUserId}`,
`[${context.getTrackingId()}] delete externalUser: ${externalUserId} | params: { ` +
`externalUserId: ${externalUserId}, };`,
);
} catch (error) {
this.logger.error(`error=${error}`);
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.trackingId}] Failed to delete externalUser: ${externalUserId}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`,
);
}
}
@ -336,15 +353,20 @@ export class AccountsService {
userId: number,
context: Context,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteAccount.name
} | params: { accountId: ${accountId}, userId: ${userId} };`,
);
try {
await this.accountRepository.deleteAccount(accountId, userId);
this.logger.log(
`[${context.trackingId}] delete account: ${accountId}, user: ${userId}`,
`[${context.getTrackingId()}] delete account: ${accountId}, user: ${userId}`,
);
} catch (error) {
this.logger.error(`error=${error}`);
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.trackingId}] Failed to delete account: ${accountId}, user: ${userId}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete account: ${accountId}, user: ${userId}`,
);
}
}
@ -356,6 +378,11 @@ export class AccountsService {
country: string,
context: Context,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.deleteBlobContainer.name
} | params: { accountId: ${accountId} };`,
);
try {
await this.blobStorageService.deleteContainer(
context,
@ -363,11 +390,11 @@ export class AccountsService {
country,
);
this.logger.log(
`[${context.trackingId}] delete container: ${accountId}, country: ${country}`,
`[${context.getTrackingId()}] delete container: ${accountId}, country: ${country}`,
);
} catch (error) {
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.trackingId}] Failed to delete container: ${accountId}, country: ${country}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete container: ${accountId}, country: ${country}`,
);
}
}
@ -382,8 +409,9 @@ export class AccountsService {
externalId: string,
): Promise<GetMyAccountResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getAccountInfo.name} | params: { ` +
`name: ${externalId}, };`,
`[IN] [${context.getTrackingId()}] ${
this.getAccountInfo.name
} | params: { ` + `externalId: ${externalId}, };`,
);
try {
let userInfo: User;
@ -415,7 +443,7 @@ export class AccountsService {
},
};
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -435,13 +463,20 @@ export class AccountsService {
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getAccountInfo.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getAccountInfo.name}`,
);
}
}
async getTypistGroups(externalId: string): Promise<TypistGroup[]> {
this.logger.log(`[IN] ${this.getTypistGroups.name}`);
async getTypistGroups(
context: Context,
externalId: string,
): Promise<TypistGroup[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getTypistGroups.name
} | params: { externalId: ${externalId} };`,
);
// TypistGroup取得
try {
@ -452,13 +487,15 @@ export class AccountsService {
return userGroups.map((x) => ({ id: x.id, name: x.name }));
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getTypistGroups.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getTypistGroups.name}`,
);
}
}
/**
@ -474,7 +511,9 @@ export class AccountsService {
typistGroupId: number,
): Promise<GetTypistGroupResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getTypistGroup.name} | params: { externalId: ${externalId}, typistGroupId: ${typistGroupId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getTypistGroup.name
} | params: { externalId: ${externalId}, typistGroupId: ${typistGroupId} };`,
);
try {
@ -497,7 +536,7 @@ export class AccountsService {
typistIds: userGroupMembers.map((x) => x.user_id),
};
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TypistGroupNotExistError:
@ -518,7 +557,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getTypistGroup.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getTypistGroup.name}`,
);
}
}
@ -528,8 +567,12 @@ export class AccountsService {
* @param externalId
* @returns typists
*/
async getTypists(externalId: string): Promise<Typist[]> {
this.logger.log(`[IN] ${this.getTypists.name}`);
async getTypists(context: Context, externalId: string): Promise<Typist[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getTypists.name
} | params: { externalId: ${externalId} };`,
);
// Typist取得
try {
@ -539,9 +582,9 @@ export class AccountsService {
const externalIds = typistUsers.map((x) => x.external_id);
// B2Cからユーザー名を取得する
const trackingId = new Context(context.trackingId);
const adb2cUsers = await this.adB2cService.getUsers(
// TODO: 外部連携以外のログ強化時に、ContollerからContextを取得するように修正する
{ trackingId: 'dummy' },
trackingId,
externalIds,
);
@ -560,13 +603,15 @@ export class AccountsService {
return typists;
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getTypists.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getTypists.name}`,
);
}
}
@ -578,7 +623,9 @@ export class AccountsService {
*/
async getAuthors(context: Context, externalId: string): Promise<Author[]> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getAuthors.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getAuthors.name
} | params: { externalId: ${externalId} };`,
);
try {
@ -609,7 +656,7 @@ export class AccountsService {
});
return authors;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
@ -629,7 +676,9 @@ export class AccountsService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getAuthors.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getAuthors.name}`,
);
}
}
@ -652,7 +701,9 @@ export class AccountsService {
creatorAccountTier: number,
): Promise<{ accountId: number }> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createPartnerAccount.name} | params: { creatorUserId: ${creatorUserId}, creatorAccountTier: ${creatorAccountTier} };`,
`[IN] [${context.getTrackingId()}] ${
this.createPartnerAccount.name
} | params: { creatorUserId: ${creatorUserId}, creatorAccountTier: ${creatorAccountTier} };`,
);
try {
@ -664,7 +715,7 @@ export class AccountsService {
await this.usersRepository.findUserByExternalId(creatorUserId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof UserNotFoundError) {
throw new HttpException(
makeErrorResponse('E010204'),
@ -691,7 +742,7 @@ export class AccountsService {
adminName,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -723,8 +774,10 @@ export class AccountsService {
account = newAccount;
user = adminUser;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner account failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner account failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -743,8 +796,10 @@ export class AccountsService {
country,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner container failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner container failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -761,6 +816,7 @@ export class AccountsService {
try {
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
context,
account.id,
user.id,
email,
@ -775,8 +831,10 @@ export class AccountsService {
);
return { accountId: account.id };
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create partner account send mail failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create partner account send mail failed`,
);
//リカバリ処理
// idpのユーザーを削除
await this.deleteAdB2cUser(externalUser.sub, context);
@ -796,7 +854,7 @@ export class AccountsService {
throw e;
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createPartnerAccount.name}`,
`[OUT] [${context.getTrackingId()}] ${this.createPartnerAccount.name}`,
);
}
}
@ -809,11 +867,16 @@ export class AccountsService {
* @returns getPartnerLicensesResponse
*/
async getPartnerLicenses(
context: Context,
limit: number,
offset: number,
accountId: number,
): Promise<GetPartnerLicensesResponse> {
this.logger.log(`[IN] ${this.getPartnerLicenses.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getPartnerLicenses.name
} | params: { limit: ${limit}, offset: ${offset}, accountId: ${accountId} };`,
);
try {
const currentDate = new DateWithZeroTime();
@ -891,13 +954,15 @@ export class AccountsService {
return getPartnerLicensesResponse;
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getPartnerLicenses.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getPartnerLicenses.name}`,
);
}
}
/**
@ -908,11 +973,16 @@ export class AccountsService {
* @returns getOrderHistoriesResponse
*/
async getOrderHistories(
context: Context,
limit: number,
offset: number,
accountId: number,
): Promise<GetOrderHistoriesResponse> {
this.logger.log(`[IN] ${this.getOrderHistories.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getOrderHistories.name
} | params: { limit: ${limit}, offset: ${offset}, accountId: ${accountId} };`,
);
try {
const licenseHistoryInfo =
@ -949,13 +1019,15 @@ export class AccountsService {
};
return getOrderHistoriesResponse;
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getOrderHistories.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getOrderHistories.name}`,
);
}
}
@ -975,7 +1047,9 @@ export class AccountsService {
poNumber: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.issueLicense.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.issueLicense.name
} | params: { ` +
`orderedAccountId: ${orderedAccountId}, ` +
`userId: ${userId}, ` +
`tier: ${tier}, ` +
@ -993,7 +1067,7 @@ export class AccountsService {
poNumber,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case OrderNotFoundError:
@ -1020,14 +1094,16 @@ export class AccountsService {
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.issueLicense.name}`,
`[OUT] [${context.getTrackingId()}] ${this.issueLicense.name}`,
);
}
}
// dealersのアカウント情報を取得する
async getDealers(): Promise<GetDealersResponse> {
this.logger.log(`[IN] ${this.getDealers.name}`);
async getDealers(context: Context): Promise<GetDealersResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getDealers.name}`,
);
try {
const dealerAccounts = await this.accountRepository.findDealerAccounts();
@ -1044,13 +1120,15 @@ export class AccountsService {
return dealers;
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getDealers.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getDealers.name}`,
);
}
}
/**
@ -1068,7 +1146,9 @@ export class AccountsService {
typistIds: number[],
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createTypistGroup.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.createTypistGroup.name
} | params: { ` +
`externalId: ${externalId}, ` +
`typistGroupName: ${typistGroupName}, ` +
`typistIds: ${typistIds} };`,
@ -1085,7 +1165,7 @@ export class AccountsService {
account_id,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TypistIdInvalidError:
@ -1124,7 +1204,13 @@ export class AccountsService {
typistIds: number[],
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateTypistGroup.name} | params: { typistGroupId: ${typistGroupId}, typistGroupName: ${typistGroupName}, typistIds: ${typistIds} };`,
`[IN] [${context.getTrackingId()}] ${
this.updateTypistGroup.name
} | params: { ` +
`externalId: ${externalId}, ` +
`typistGroupId: ${typistGroupId}, ` +
`typistGroupName: ${typistGroupName}, ` +
`typistIds: ${typistIds} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
@ -1140,7 +1226,7 @@ export class AccountsService {
typistIds,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// タイピストIDが存在しない場合は400エラーを返す
@ -1168,7 +1254,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateTypistGroup.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateTypistGroup.name}`,
);
}
}
@ -1187,7 +1273,9 @@ export class AccountsService {
orderedAccountId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.cancelIssue.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.cancelIssue.name
} | params: { ` +
`extarnalId: ${extarnalId}, ` +
`poNumber: ${poNumber}, ` +
`orderedAccountId: ${orderedAccountId}, };`,
@ -1200,7 +1288,7 @@ export class AccountsService {
await this.usersRepository.findUserByExternalId(extarnalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
default:
throw new HttpException(
@ -1209,7 +1297,9 @@ export class AccountsService {
);
}
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
}
// 注文元アカウントIDの親世代を取得
@ -1218,7 +1308,9 @@ export class AccountsService {
);
// 自身が存在しない場合、エラー
if (!parentAccountIds.includes(myAccountId)) {
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
throw new HttpException(
makeErrorResponse('E000108'),
HttpStatus.UNAUTHORIZED,
@ -1229,7 +1321,7 @@ export class AccountsService {
// 発行キャンセル処理
await this.accountRepository.cancelIssue(orderedAccountId, poNumber);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case AlreadyLicenseStatusChangedError:
throw new HttpException(
@ -1253,7 +1345,9 @@ export class AccountsService {
);
}
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelIssue.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelIssue.name}`,
);
}
}
@ -1267,7 +1361,11 @@ export class AccountsService {
context: Context,
externalId: string,
): Promise<GetWorktypesResponse> {
this.logger.log(`[IN] [${context.trackingId}] ${this.getWorktypes.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getWorktypes.name
} | params: { externalId: ${externalId} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
const { account_id: accountId } =
@ -1286,7 +1384,7 @@ export class AccountsService {
active: active_worktype_id,
};
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
@ -1307,7 +1405,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getWorktypes.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getWorktypes.name}`,
);
}
}
@ -1325,7 +1423,11 @@ export class AccountsService {
worktypeId: string,
description?: string,
): Promise<void> {
this.logger.log(`[IN] [${context.trackingId}] ${this.createWorktype.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createWorktype.name
} | params: { externalId: ${externalId}, worktypeId: ${worktypeId}, description: ${description} };`,
);
try {
// 外部IDをもとにユーザー情報を取得する
@ -1338,7 +1440,7 @@ export class AccountsService {
description,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// WorktypeIDが既に存在する場合は400エラーを返す
@ -1366,7 +1468,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createWorktype.name}`,
`[OUT] [${context.getTrackingId()}] ${this.createWorktype.name}`,
);
}
}
@ -1388,7 +1490,9 @@ export class AccountsService {
description?: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateWorktype.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id}, ` +
`worktypeId: ${worktypeId}, ` +
@ -1408,7 +1512,7 @@ export class AccountsService {
description,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// ユーザーが設定したWorktypeIDが既存WorktypeのWorktypeIDと重複する場合は400エラーを返す
@ -1436,7 +1540,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateWorktype.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateWorktype.name}`,
);
}
}
@ -1454,7 +1558,9 @@ export class AccountsService {
id: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteWorktype.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.deleteWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
@ -1473,7 +1579,7 @@ export class AccountsService {
// ワークタイプを削除する
await this.worktypesRepository.deleteWorktype(accountId, id);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -1511,7 +1617,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deleteWorktype.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deleteWorktype.name}`,
);
}
}
@ -1529,7 +1635,9 @@ export class AccountsService {
id: number,
): Promise<GetOptionItemsResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getOptionItems.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.getOptionItems.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
@ -1553,7 +1661,7 @@ export class AccountsService {
})),
};
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
@ -1575,7 +1683,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getOptionItems.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getOptionItems.name}`,
);
}
}
@ -1595,7 +1703,9 @@ export class AccountsService {
optionItems: PostWorktypeOptionItem[],
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateOptionItems.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateOptionItems.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id}, ` +
`optionItems: ${JSON.stringify(optionItems)} };`,
@ -1620,7 +1730,7 @@ export class AccountsService {
})),
);
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
@ -1642,7 +1752,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateOptionItems.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateOptionItems.name}`,
);
}
}
@ -1660,7 +1770,9 @@ export class AccountsService {
id: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateActiveWorktype.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateActiveWorktype.name
} | params: { ` +
`externalId: ${externalId}, ` +
`id: ${id} };`,
);
@ -1672,7 +1784,7 @@ export class AccountsService {
// ActiveWorktypeを更新する
await this.accountRepository.updateActiveWorktypeId(accountId, id);
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
// 内部IDで指定されたWorktypeが存在しない場合は400エラーを返す
@ -1694,7 +1806,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateActiveWorktype.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateActiveWorktype.name}`,
);
}
}
@ -1714,7 +1826,9 @@ export class AccountsService {
offset: number,
): Promise<GetPartnersResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getPartners.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.getPartners.name
} | params: { ` +
`externalId: ${externalId}, ` +
`limit: ${limit}, ` +
`offset: ${offset}, };`,
@ -1774,13 +1888,15 @@ export class AccountsService {
partners: partners,
};
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getPartners.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getPartners.name}`,
);
}
}
@ -1805,8 +1921,11 @@ export class AccountsService {
secondryAdminUserId?: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateAccountInfo.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateAccountInfo.name
} | params: { ` +
`externalId: ${externalId}, ` +
`tier: ${tier}, ` +
`delegationPermission: ${delegationPermission}, ` +
`primaryAdminUserId: ${primaryAdminUserId}, ` +
`parentAccountId: ${parentAccountId}, ` +
@ -1825,7 +1944,7 @@ export class AccountsService {
secondryAdminUserId,
);
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case DealerAccountNotFoundError:
@ -1847,7 +1966,7 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateAccountInfo.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateAccountInfo.name}`,
);
}
}
@ -1864,7 +1983,9 @@ export class AccountsService {
accountId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteAccountAndData.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.deleteAccountAndData.name
} | params: { ` +
`externalId: ${externalId}, ` +
`accountId: ${accountId}, };`,
);
@ -1889,13 +2010,15 @@ export class AccountsService {
dbUsers = await this.accountRepository.deleteAccountAndInsertArchives(
accountId,
);
this.logger.log(`[${context.trackingId}] delete account: ${accountId}`);
this.logger.log(
`[${context.getTrackingId()}] delete account: ${accountId}`,
);
country = targetAccount.country;
} catch (e) {
// アカウントの削除に失敗した場合はエラーを返す
this.logger.log(`[${context.trackingId}] ${e}`);
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deleteAccountAndData.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
@ -1910,19 +2033,15 @@ export class AccountsService {
context,
);
this.logger.log(
`[${
context.trackingId
}] delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
`[${context.getTrackingId()}] delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
(x) => x.external_id,
)}`,
);
} catch (e) {
// ADB2Cユーザーの削除失敗時は、MANUAL_RECOVERY_REQUIREDを出して処理続行
this.logger.log(`[${context.trackingId}] ${e}`);
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`${MANUAL_RECOVERY_REQUIRED} [${
context.trackingId
}] Failed to delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete ADB2C users: ${accountId}, users_id: ${dbUsers.map(
(x) => x.external_id,
)}`,
);
@ -1932,18 +2051,18 @@ export class AccountsService {
// blobstorageコンテナを削除する
await this.deleteBlobContainer(accountId, country, context);
this.logger.log(
`[${context.trackingId}] delete blob container: ${accountId}-${country}`,
`[${context.getTrackingId()}] delete blob container: ${accountId}-${country}`,
);
} catch (e) {
// blobstorageコンテナを削除で失敗した場合は、MANUAL_RECOVERY_REQUIRED出して正常終了
this.logger.log(`[${context.trackingId}] ${e}`);
this.logger.log(`[${context.getTrackingId()}] ${e}`);
this.logger.log(
`${MANUAL_RECOVERY_REQUIRED}[${context.trackingId}] Failed to delete blob container: ${accountId}, country: ${country}`,
`${MANUAL_RECOVERY_REQUIRED}[${context.getTrackingId()}] Failed to delete blob container: ${accountId}, country: ${country}`,
);
}
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deleteAccountAndData.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deleteAccountAndData.name}`,
);
}
@ -1958,7 +2077,9 @@ export class AccountsService {
externalId: string,
): Promise<number> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getAccountInfoMinimalAccess.name
} | params: { externalId: ${externalId} };`,
);
try {
@ -1973,7 +2094,7 @@ export class AccountsService {
return account.tier;
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -1999,7 +2120,9 @@ export class AccountsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getAccountInfoMinimalAccess.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.getAccountInfoMinimalAccess.name
}`,
);
}
}

View File

@ -40,8 +40,6 @@ import { RedisService } from '../../gateways/redis/redis.service';
@Controller('auth')
export class AuthController {
constructor(
// TODO「タスク 1828: IDトークンを一度しか使えないようにする」で使用する予定
// private readonly redisService: RedisService,
private readonly authService: AuthService,
private readonly redisService: RedisService,
) {}
@ -68,9 +66,13 @@ export class AuthController {
operationId: 'token',
})
async token(@Body() body: TokenRequest): Promise<TokenResponse> {
const idToken = await this.authService.getVerifiedIdToken(body.idToken);
const context = makeContext(uuidv4());
const idToken = await this.authService.getVerifiedIdToken(
context,
body.idToken,
);
const isVerified = await this.authService.isVerifiedUser(idToken);
const isVerified = await this.authService.isVerifiedUser(context, idToken);
if (!isVerified) {
throw new HttpException(
makeErrorResponse('E010201'),
@ -78,20 +80,16 @@ export class AuthController {
);
}
const context = makeContext(uuidv4());
const key = makeIDTokenKey(body.idToken);
const isTokenExists = await this.redisService.get<boolean>(key);
if (!isTokenExists) {
// IDトークンがキャッシュに存在しない場合(idTokenの有効期限をADB2Cの有効期限と合わせる(300秒))
await this.redisService.set(key, true, 300);
} else {
const isTokenExists = await this.redisService.get<boolean>(context, key);
if (isTokenExists) {
// IDトークンがキャッシュに存在する場合エラー
throw new HttpException(
makeErrorResponse('E000106'),
HttpStatus.UNAUTHORIZED,
);
}
// 同意済み利用規約バージョンが最新かチェック
const isAcceptedLatestVersion =
await this.authService.isAcceptedLatestVersion(context, idToken);
@ -104,6 +102,9 @@ export class AuthController {
);
}
// IDトークンをキャッシュに保存(idTokenの有効期限をADB2Cの有効期限と合わせる(300秒))
await this.redisService.set(context, key, true, 300);
const refreshToken = await this.authService.generateRefreshToken(
context,
idToken,

View File

@ -31,7 +31,10 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
expect(await service.getVerifiedIdToken(token)).toEqual(idTokenPayload);
const context = makeContext(`uuidv4`);
expect(await service.getVerifiedIdToken(context, token)).toEqual(
idTokenPayload,
);
});
it('IDトークンの形式が不正な場合、形式不正エラーとなる。', async () => {
@ -40,7 +43,8 @@ describe('AuthService', () => {
const service = await makeAuthServiceMock(adb2cParam, configMockValue);
const token = 'invalid.id.token';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.UNAUTHORIZED),
);
});
@ -54,7 +58,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjEwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.r9x61Mf1S2qFgU_QDKB6tRFBmTQXyOEtpoacOlL_bQzFz1t3GsxMy6SJIvQQ-LtDgylQ1UCdMFiRuy4V8nyLuME0fR-9IkKsboGvwllHB_Isai3XFoja0jpDHMVby1m0B3Z9xOTb7YsaQGyEH-qs1TtnRm6Ny98h4Po80nK8HGefQZHBOlfQN_B1LiHwI3nLXV18NL-4olKXj2NloNRYtnWM0PaqDQcGvZFaSNvtrSYpo9ddD906QWDGVOQ7WvGSUgdNCoxX8Lb3r2-VSj6n84jpb-Y1Fz-GhLluNglAsBhasnJfUIvCIO3iG5pRyTYjHFAVHmzjr8xMOmhS3s41Jw';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000102'), HttpStatus.UNAUTHORIZED),
);
});
@ -68,7 +73,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6OTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.fX2Gbd7fDPNE3Lw-xbum_5CVqQYqEmMhv_v5u8A-U81pmPD2P5rsJEJx66ns1taFLVaE3j9_OzotxrqjqqQqbACkagGcN5wvA3_ZIxyqmhrKYFJc53ZcO7d0pFWiQlluNBI_pnFNDlSMB2Ut8Th5aiPy2uamBM9wC99bcjo7HkHvTKBf6ljU6rPKoD51qGDWqNxjoH-hdSJ29wprvyxyk_yX6dp-cxXUj5DIgXYQuIZF71rdiPtGlAiyTBns8rS2QlEEXapZVlvYrK4mkpUXVDA7ifD8q6gAC2BStqHeys7CGp2MbV4ZwKCVbAUbMs6Tboh8rADZvQhuTEq7qlhZ-w';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000103'), HttpStatus.UNAUTHORIZED),
);
});
@ -80,7 +86,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdXNlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.sign';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000104'), HttpStatus.UNAUTHORIZED),
);
});
@ -94,7 +101,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaW52bGlkX2lzc3VlciIsInN1YiI6InN1YiIsImF1ZCI6ImF1ZCIsIm5vbmNlIjoiZGVmYXVsdE5vbmNlIiwiaWF0IjoxMDAwMDAwMDAwLCJhdXRoX3RpbWUiOjEwMDAwMDAwMDAsImVtYWlscyI6WyJ4eHhAeHguY29tIl0sInRmcCI6InNpZ25pbl91c2VyZmxvdyJ9.0bp3e1mDG78PX3lo8zgOLXGenIqG_Vi6kw7CbwauAQM-cnUZ_aVCoJ_dAv_QmPElOQKcCkRrAvAZ91FwuHDlBGuuDqx8OwqN0VaD-4NPouoAswj-9HNvBm8gUn-pGaXkvWt_72UdCJavZJjDj_RHur8y8kFt5Qeab3mUP2x-uNcV2Q2x3M_IIfcRiIZkRZm_azKfiVIy7tzoUFLDss97y938aR8imMVxazoSQvj7RWIWylgeRr9yVt7qYl18cnEVL0IGtslFbqhfNsiEmRCMsttm5kXs7E9B0bhhUe_xbJW9VumQ6G7dgMrswevp_jRgbpWJoZsgErtqIRl9Tc9ikA';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000105'), HttpStatus.UNAUTHORIZED),
);
});
@ -107,7 +115,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -122,7 +131,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -140,7 +150,8 @@ describe('AuthService', () => {
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImtpZCJ9.eyJleHAiOjkwMDAwMDAwMDAsIm5iZiI6MTAwMDAwMDAwMCwidmVyIjoiMS4wIiwiaXNzIjoiaXNzdWVyIiwic3ViIjoic3ViIiwiYXVkIjoiYXVkIiwibm9uY2UiOiJkZWZhdWx0Tm9uY2UiLCJpYXQiOjEwMDAwMDAwMDAsImF1dGhfdGltZSI6MTAwMDAwMDAwMCwiZW1haWxzIjpbInh4eEB4eC5jb20iXSwidGZwIjoic2lnbmluX3VzZXJmbG93In0.RyieW-VHsHPQOjXbbhRc307AYJOc1sq2hrcu4SW1-K0pvLlkplepxvx02a3vCwQrnBYrIP5w6HExG-S_JgW5nYyWr6DeY11mA484n9KA8GeAcAXV37StH1gfWUJvfGb4C8BaMbMM9Ix4Z9NGwKA9vjNwevfmBZnz9lQUePgv6BJNmyvCt8ElJ01O-1WODbZuojJ4xXymA1OqluzfbphPOsqWTSNmTn0emkLjjnlMQf1iwM4C_kvvr8dUCFg0_UGDfQVJnzPEKB38UqnhLnC5WacrddDwQ0kBuGKZgZ_63Q_7fOvqAZivqLK7BPmbPxi6mx3R1S9Eq2ugzpY1LfJOjA';
await expect(service.getVerifiedIdToken(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.getVerifiedIdToken(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,

View File

@ -55,21 +55,25 @@ export class AuthService {
* @param idToken AzureAD B2Cにより発行されたIDトークン
* @returns verified user
*/
async isVerifiedUser(idToken: IDToken): Promise<boolean> {
this.logger.log(`[IN] ${this.isVerifiedUser.name}`);
async isVerifiedUser(context: Context, idToken: IDToken): Promise<boolean> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.isVerifiedUser.name}`,
);
try {
// IDトークンのユーザーがDBに登録されていてメール認証が完了しているユーザーか検証
const user = await this.usersRepository.findVerifiedUser(idToken.sub);
return user !== undefined;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.isVerifiedUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.isVerifiedUser.name}`,
);
}
}
@ -85,7 +89,9 @@ export class AuthService {
type: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.generateRefreshToken.name}`,
`[IN] [${context.getTrackingId()}] ${
this.generateRefreshToken.name
} | params: { type: ${type} };`,
);
// ユーザー情報とユーザーが属しているアカウント情報を取得
@ -133,7 +139,7 @@ export class AuthService {
return token;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TierUnexpectedError:
@ -156,7 +162,7 @@ export class AuthService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateRefreshToken.name}`,
`[OUT] [${context.getTrackingId()}] ${this.generateRefreshToken.name}`,
);
}
}
@ -171,7 +177,7 @@ export class AuthService {
refreshToken: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.generateAccessToken.name}`,
`[IN] [${context.getTrackingId()}] ${this.generateAccessToken.name}`,
);
const privateKey = getPrivateKey(this.configService);
@ -179,9 +185,11 @@ export class AuthService {
const token = verify<RefreshToken>(refreshToken, pubkey);
if (isVerifyError(token)) {
this.logger.error(`${token.reason} | ${token.message}`);
this.logger.error(
`[${context.getTrackingId()}] ${token.reason} | ${token.message}`,
);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateAccessToken.name}`,
`[OUT] [${context.getTrackingId()}] ${this.generateAccessToken.name}`,
);
throw new HttpException(
makeErrorResponse('E000101'),
@ -200,7 +208,7 @@ export class AuthService {
);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateAccessToken.name}`,
`[OUT] [${context.getTrackingId()}] ${this.generateAccessToken.name}`,
);
return accessToken;
}
@ -218,7 +226,9 @@ export class AuthService {
originAccountId: number,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.generateDelegationRefreshToken.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.generateDelegationRefreshToken.name
} | params: { ` +
`delegateUserExternalId: ${delegateUserExternalId}, ` +
`originAccountId: ${originAccountId}, };`,
);
@ -255,7 +265,7 @@ export class AuthService {
return token;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case AccountNotFoundError:
@ -284,7 +294,9 @@ export class AuthService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateDelegationRefreshToken.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.generateDelegationRefreshToken.name
}`,
);
}
}
@ -300,7 +312,9 @@ export class AuthService {
refreshToken: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
`[IN] [${context.getTrackingId()}] ${
this.generateDelegationAccessToken.name
}`,
);
const privateKey = getPrivateKey(this.configService);
@ -308,9 +322,13 @@ export class AuthService {
const token = verify<RefreshToken>(refreshToken, pubkey);
if (isVerifyError(token)) {
this.logger.error(`${token.reason} | ${token.message}`);
this.logger.error(
`[${context.getTrackingId()}] ${token.reason} | ${token.message}`,
);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.generateDelegationAccessToken.name
}`,
);
throw new HttpException(
makeErrorResponse('E000101'),
@ -330,7 +348,9 @@ export class AuthService {
);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.generateDelegationAccessToken.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.generateDelegationAccessToken.name
}`,
);
return accessToken;
}
@ -350,7 +370,9 @@ export class AuthService {
refreshToken: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateDelegationAccessToken.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateDelegationAccessToken.name
} | params: { ` +
`delegateUserExternalId: ${delegateUserExternalId}, ` +
`originUserExternalId: ${originUserExternalId}, };`,
);
@ -407,7 +429,7 @@ export class AuthService {
return accessToken;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof HttpException) {
throw e;
}
@ -440,7 +462,9 @@ export class AuthService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateDelegationAccessToken.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.updateDelegationAccessToken.name
}`,
);
}
}
@ -450,8 +474,10 @@ export class AuthService {
* @param token
* @returns id token
*/
async getVerifiedIdToken(token: string): Promise<IDToken> {
this.logger.log(`[IN] ${this.getVerifiedIdToken.name}`);
async getVerifiedIdToken(context: Context, token: string): Promise<IDToken> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getVerifiedIdToken.name}`,
);
let kid: string | undefined = '';
try {
@ -462,7 +488,7 @@ export class AuthService {
throw new Error('kid not found');
}
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E000101'),
HttpStatus.UNAUTHORIZED,
@ -471,8 +497,8 @@ export class AuthService {
let issuer = '';
try {
const metadata = await this.adB2cService.getMetaData();
const keySets = await this.adB2cService.getSignKeySets();
const metadata = await this.adB2cService.getMetaData(context);
const keySets = await this.adB2cService.getSignKeySets(context);
issuer = metadata.issuer;
@ -482,7 +508,7 @@ export class AuthService {
throw new Error('Public Key Not Found.');
}
const publicKey = this.getPublicKeyFromJwk(jwkKey);
const publicKey = this.getPublicKeyFromJwk(context, jwkKey);
const verifiedToken = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
@ -496,7 +522,9 @@ export class AuthService {
} catch (e) {
if (e instanceof Error) {
const { name, message } = e;
this.logger.error(`error=${name}: ${message}`);
this.logger.error(
`[${context.getTrackingId()}] error=${name}: ${message}`,
);
switch (e.constructor) {
case jwt.TokenExpiredError:
@ -519,18 +547,20 @@ export class AuthService {
break;
}
} else {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getVerifiedIdToken.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getVerifiedIdToken.name}`,
);
}
}
getPublicKeyFromJwk(jwkKey: JwkSignKey): string {
getPublicKeyFromJwk(context: Context, jwkKey: JwkSignKey): string {
try {
// JWK形式のJSONなのでJWTの公開鍵として使えるようにPEM形式に変換
const publicKey = jwkToPem({
@ -541,10 +571,12 @@ export class AuthService {
return publicKey;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] ${this.getPublicKeyFromJwk.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getPublicKeyFromJwk.name}`,
);
}
}
@ -613,8 +645,9 @@ export class AuthService {
idToken: IDToken,
): Promise<boolean> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.isAcceptedLatestVersion.name} | params: { ` +
`idToken.sub: ${idToken.sub}, };`,
`[IN] [${context.getTrackingId()}] ${
this.isAcceptedLatestVersion.name
} | params: { ` + `idToken.sub: ${idToken.sub}, };`,
);
try {
@ -646,14 +679,16 @@ export class AuthService {
return eulaAccepted && dpaAccepted;
}
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.isAcceptedLatestVersion.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.isAcceptedLatestVersion.name
}`,
);
}
}

View File

@ -4,6 +4,7 @@ import { JwkSignKey, B2cMetadata } from '../../../common/token';
import { AuthService } from '../auth.service';
import { ConfigService } from '@nestjs/config';
import { UsersRepositoryService } from '../../../repositories/users/users.repository.service';
import { Context } from '../../../common/log';
export type AdB2cMockValue = {
getMetaData: B2cMetadata | Error;
@ -71,7 +72,10 @@ export const makeAdB2cServiceMock = (value: AdB2cMockValue) => {
};
// eslint-disable-next-line @typescript-eslint/no-unused-vars
export const makeDefaultGetPublicKeyFromJwk = (jwkKey: JwkSignKey): string => {
export const makeDefaultGetPublicKeyFromJwk = (
context: Context,
jwkKey: JwkSignKey,
): string => {
return [
'-----BEGIN PUBLIC KEY-----',
'MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5IZZNgDew9eGmuFTezwd',

View File

@ -140,6 +140,11 @@ export class FilesController {
type: AudioUploadLocationResponse,
description: '成功時のレスポンス',
})
@ApiResponse({
status: HttpStatus.BAD_REQUEST,
description: '不正なパラメータ',
type: ErrorResponse,
})
@ApiResponse({
status: HttpStatus.UNAUTHORIZED,
description: '認証エラー',

View File

@ -1,13 +1,9 @@
import { HttpException, HttpStatus } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import {
makeBlobstorageServiceMockValue,
makeDefaultTasksRepositoryMockValue,
makeDefaultUsersRepositoryMockValue,
makeFilesServiceMock,
} from './test/files.service.mock';
import { makeBlobstorageServiceMockValue } from './test/files.service.mock';
import { DataSource } from 'typeorm';
import {
createLicense,
createTask,
createUserGroupAndMember,
getTaskFromJobNumber,
@ -16,6 +12,7 @@ import {
import { FilesService } from './files.service';
import { makeContext } from '../../common/log';
import {
makeHierarchicalAccounts,
makeTestAccount,
makeTestSimpleAccount,
makeTestUser,
@ -35,91 +32,244 @@ import {
import { createWorktype } from '../accounts/test/utility';
import { TasksRepositoryService } from '../../repositories/tasks/tasks.repository.service';
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
import { getCheckoutPermissions, getTask } from '../tasks/test/utility';
import { DateWithZeroTime } from '../licenses/types/types';
import { LICENSE_ALLOCATED_STATUS, LICENSE_TYPE } from '../../constants';
describe('音声ファイルアップロードURL取得', () => {
it('アップロードSASトークンが乗っているURLを返却する', async () => {
const blobParam = makeBlobstorageServiceMockValue();
const userRepoParam = makeDefaultUsersRepositoryMockValue();
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
const service = await makeFilesServiceMock(
blobParam,
userRepoParam,
taskRepoParam,
);
expect(
await service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).toEqual('https://blob-storage?sas-token');
describe('publishUploadSas', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true, // trueにすると自動的にmigrationが行われるため注意
});
return source.initialize();
});
it('アカウント専用コンテナが無い場合でも、コンテナ作成しURLを返却する', async () => {
const blobParam = makeBlobstorageServiceMockValue();
const userRepoParam = makeDefaultUsersRepositoryMockValue();
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
blobParam.containerExists = false;
const service = await makeFilesServiceMock(
blobParam,
userRepoParam,
taskRepoParam,
);
expect(
await service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
).toEqual('https://blob-storage?sas-token');
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ユーザー情報の取得に失敗した場合、例外エラーを返却する', async () => {
const blobParam = makeBlobstorageServiceMockValue();
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
it('音声アップロードSASトークンが乗っているURLを取得できる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { account: account } = await makeTestAccount(source, { tier: 5 });
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 本日の日付を作成
let today = new Date();
today.setDate(today.getDate());
today = new DateWithZeroTime(today);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
today,
account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const context = makeContext(externalId);
const baseUrl = `https://saodmsusdev.blob.core.windows.net/account-${account.id}/${userId}`;
const service = await makeFilesServiceMock(
blobParam,
{
findUserByExternalId: new Error(''),
//SASトークンを返却する
overrideBlobstorageService(service, {
containerExists: async () => true,
publishUploadSas: async () => `${baseUrl}?sas-token`,
});
const url = await service.publishUploadSas(context, externalId);
expect(url).toBe(`${baseUrl}?sas-token`);
});
it('blobストレージにコンテナが存在しない場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第四階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 4 });
const context = makeContext(admin.external_id);
//Blobコンテナ存在チェックに失敗するようにする
overrideBlobstorageService(service, {
containerExists: async () => false,
publishUploadSas: async () => '',
});
try {
await service.publishUploadSas(context, admin.external_id);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
it('SASトークンの取得に失敗した場合はエラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第四階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 4 });
const context = makeContext(admin.external_id);
//BlobのSASトークン生成に失敗するようにする
overrideBlobstorageService(service, {
containerExists: async () => true,
publishUploadSas: async () => {
throw new Error('blob failed');
},
taskRepoParam,
});
try {
await service.publishUploadSas(context, admin.external_id);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
it('アカウントがロックされている場合、エラーとなる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
// 第五階層のアカウント作成
const { admin } = await makeTestAccount(source, { tier: 5, locked: true });
const context = makeContext(admin.external_id);
try {
await service.publishUploadSas(context, admin.external_id);
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010504'));
} else {
fail();
}
}
});
it('アップロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const { external_id: externalId, id: userId } = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishUploadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
service.publishUploadSas(makeContext('trackingId'), externalId),
).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('コンテナ作成に失敗した場合、例外エラーを返却する', async () => {
const blobParam = makeBlobstorageServiceMockValue();
const taskRepoParam = makeDefaultTasksRepositoryMockValue();
const service = await makeFilesServiceMock(
blobParam,
{
findUserByExternalId: new Error(''),
},
taskRepoParam,
it('アップロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
blobParam.publishUploadSas = new Error('Azure service down');
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishUploadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishUploadSas(
makeContext('trackingId'),
'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxx',
),
service.publishUploadSas(makeContext('trackingId'), externalId),
).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.UNAUTHORIZED),
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
@ -220,7 +370,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
@ -316,7 +471,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
@ -434,7 +594,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'AUTHOR_ID',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
@ -551,7 +716,12 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'XXXXXXXXXX',
filename: 'file',
priority: 'High',
uploadedAt: '2023-05-26T11:22:33.444',
},
);
// 作成したタスクを取得
const resultTask = await getTaskFromJobNumber(source, result.jobNumber);
@ -565,7 +735,7 @@ describe('タスク作成から自動ルーティング(DB使用)', () => {
// タスクのチェックアウト権限が想定通りワークフローで設定されているのユーザーIDで作成されているか確認
expect(resultCheckoutPermission.length).toEqual(1);
expect(resultCheckoutPermission[0].user_group_id).toEqual(userGroupId);
}, 1000000);
});
it('ワークフローが見つからない場合、タスク作成時に、自動ルーティングを行うことができない', async () => {
if (!source) fail();
@ -879,7 +1049,76 @@ describe('音声ファイルダウンロードURL取得', () => {
),
).toEqual(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 本日の日付を作成
let today = new Date();
today.setDate(today.getDate());
today = new DateWithZeroTime(today);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
today,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = true;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
await service.publishAudioFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1109,6 +1348,133 @@ describe('音声ファイルダウンロードURL取得', () => {
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishAudioFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('テンプレートファイルダウンロードURL取得', () => {
@ -1175,7 +1541,76 @@ describe('テンプレートファイルダウンロードURL取得', () => {
),
).toEqual(`${url}?sas-token`);
});
it('ダウンロードSASトークンが乗っているURLを取得できる第五階層の場合ライセンスのチェックを行う', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 本日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate());
yesterday = new DateWithZeroTime(yesterday);
// 有効期限内のライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = true;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
expect(
await service.publishTemplateFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).toEqual(`${url}?sas-token`);
});
it('Typistの場合、タスクのステータスが[Inprogress,Pending]以外でエラー', async () => {
if (!source) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
@ -1394,6 +1829,133 @@ describe('テンプレートファイルダウンロードURL取得', () => {
new HttpException(makeErrorResponse('E010701'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーにライセンスが未割当の場合エラーとなる(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する(ライセンスは作成しない)
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010812'), HttpStatus.BAD_REQUEST),
);
});
it('ダウンロード時にユーザーに割り当てられたライセンスが有効期限切れの場合エラー(第五階層限定)', async () => {
if (!source) fail();
// 第五階層のアカウントまで作成し、そのアカウントに紐づくユーザーを作成する
const { tier4Accounts: tier4Accounts } = await makeHierarchicalAccounts(
source,
);
const tier5Accounts = await makeTestAccount(source, {
parent_account_id: tier4Accounts[0].account.id,
tier: 5,
});
const {
external_id: externalId,
id: userId,
author_id: authorId,
} = await makeTestUser(source, {
account_id: tier5Accounts.account.id,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'AUTHOR_ID',
});
// 昨日の日付を作成
let yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
yesterday = new DateWithZeroTime(yesterday);
// 期限切れのライセンスを作成して紐づける
await createLicense(
source,
1,
yesterday,
tier5Accounts.account.id,
LICENSE_TYPE.NORMAL,
LICENSE_ALLOCATED_STATUS.ALLOCATED,
userId,
null,
null,
null,
);
const url = `https://saodmsusdev.blob.core.windows.net/account-${tier5Accounts.account.id}/${userId}`;
const { audioFileId } = await createTask(
source,
tier5Accounts.account.id,
url,
'test.zip',
'InProgress',
undefined,
authorId ?? '',
);
const blobParam = makeBlobstorageServiceMockValue();
blobParam.publishDownloadSas = `${url}?sas-token`;
blobParam.fileExists = false;
const notificationParam = makeDefaultNotificationhubServiceMockValue();
const module = await makeTestingModuleWithBlobAndNotification(
source,
blobParam,
notificationParam,
);
if (!module) fail();
const service = module.get<FilesService>(FilesService);
await expect(
service.publishTemplateFileDownloadSas(
makeContext('trackingId'),
externalId,
audioFileId,
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010805'), HttpStatus.BAD_REQUEST),
);
});
});
describe('publishTemplateFileUploadSas', () => {

View File

@ -7,6 +7,7 @@ import { AudioOptionItem, AudioUploadFinishedResponse } from './types/types';
import {
OPTION_ITEM_NUM,
TASK_STATUS,
TIERS,
USER_ROLES,
} from '../../constants/index';
import { User } from '../../repositories/users/entity/user.entity';
@ -23,11 +24,18 @@ import {
} from '../../repositories/tasks/errors/types';
import { Context } from '../../common/log';
import { TemplateFilesRepositoryService } from '../../repositories/template_files/template_files.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
import {
AccountNotFoundError,
AccountLockedError,
} from '../../repositories/accounts/errors/types';
import { Task } from '../../repositories/tasks/entity/task.entity';
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import {
LicenseExpiredError,
LicenseNotAllocatedError,
} from '../../repositories/licenses/errors/types';
import { DateWithZeroTime } from '../licenses/types/types';
@Injectable()
export class FilesService {
@ -79,7 +87,10 @@ export class FilesService {
isEncrypted: boolean,
): Promise<AudioUploadFinishedResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.uploadFinished.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.uploadFinished.name
} | params: { ` +
`userId: ${userId}, ` +
`url: ${url}, ` +
`authorId: ${authorId}, ` +
`fileName: ${fileName}, ` +
@ -112,21 +123,21 @@ export class FilesService {
) {
if (isInvalidCreatedDate) {
this.logger.error(
`param createdDate is invalid format:[createdDate=${createdDate}]`,
`[${context.getTrackingId()}] param createdDate is invalid format:[createdDate=${createdDate}]`,
);
}
if (isInvalidFinishedDate) {
this.logger.error(
`param finishedDate is invalid format:[finishedDate=${finishedDate}]`,
`[${context.getTrackingId()}] param finishedDate is invalid format:[finishedDate=${finishedDate}]`,
);
}
if (isInvalidUploadedDate) {
this.logger.error(
`param uploadedDate is invalid format:[uploadedDate=${uploadedDate}]`,
`[${context.getTrackingId()}] param uploadedDate is invalid format:[uploadedDate=${uploadedDate}]`,
);
}
this.logger.log(
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
`[OUT] [${context.getTrackingId()}] ${this.uploadFinished.name}`,
);
throw new HttpException(
@ -138,10 +149,12 @@ export class FilesService {
// オプションアイテムが10個ない場合はパラメータ不正
if (optionItemList.length !== OPTION_ITEM_NUM) {
this.logger.error(
`param optionItemList expects ${OPTION_ITEM_NUM} items, but has ${optionItemList.length} items`,
`[${context.getTrackingId()}] param optionItemList expects ${OPTION_ITEM_NUM} items, but has ${
optionItemList.length
} items`,
);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
`[OUT] [${context.getTrackingId()}] ${this.uploadFinished.name}`,
);
throw new HttpException(
makeErrorResponse('E010001'),
@ -154,9 +167,9 @@ export class FilesService {
// ユーザー取得
user = await this.usersRepository.findUserByExternalId(userId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
`[OUT] [${context.getTrackingId()}] ${this.uploadFinished.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
@ -169,7 +182,9 @@ export class FilesService {
const urlObj = new URL(url);
urlObj.search = '';
const fileUrl = urlObj.toString();
this.logger.log(`Request URL: ${url}, Without param URL${fileUrl}`);
this.logger.log(
`[${context.getTrackingId()}] Request URL: ${url}, Without param URL${fileUrl}`,
);
// 文字起こしタスク追加(音声ファイルとオプションアイテムも同時に追加)
// 追加時に末尾のJOBナンバーにインクリメントする
@ -192,7 +207,7 @@ export class FilesService {
optionItemList,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -219,31 +234,34 @@ export class FilesService {
// 割り当てられたユーザーがいない場合は通知不要
if (distinctUserIds.length === 0) {
this.logger.log('No user assigned.');
this.logger.log(`[${context.getTrackingId()}] No user assigned.`);
return { jobNumber: task.job_number };
}
// タグを生成
const tags = distinctUserIds.map((x) => `user_${x}`);
this.logger.log(`tags: ${tags}`);
this.logger.log(`[${context.getTrackingId()}] tags: ${tags}`);
// タグ対象に通知送信
await this.notificationhubService.notify(
context,
tags,
makeNotifyMessage('M000101'),
);
await this.notificationhubService.notify(context, tags, {
authorId: authorId,
filename: fileName.replace('.zip', ''),
priority: priority === '00' ? 'Normal' : 'High',
uploadedAt: uploadedDate,
});
// 追加したタスクのJOBナンバーを返却
return { jobNumber: task.job_number };
} catch (error) {
// 処理の本筋はタスク生成のため自動ルーティングに失敗してもエラーにしない
this.logger.error(`Automatic routing or notification failed.`);
this.logger.error(`error=${error}`);
this.logger.error(
`[${context.getTrackingId()}] Automatic routing or notification failed.`,
);
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
return { jobNumber: task.job_number };
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.uploadFinished.name}`,
`[OUT] [${context.getTrackingId()}] ${this.uploadFinished.name}`,
);
}
}
@ -258,7 +276,9 @@ export class FilesService {
externalId: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishUploadSas.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.publishUploadSas.name
} | params: { externalId: ${externalId} };`,
);
//DBから国情報とアカウントIDを取得する
@ -269,7 +289,21 @@ export class FilesService {
}
const accountId = user.account_id;
const country = user.account.country;
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// アカウントがロックされている場合、エラー
if (user.account.locked) {
throw new AccountLockedError('account is locked.');
}
// ライセンスの有効性をチェック
const { licenseError } = await this.checkLicenseValidityByUserId(
context,
user.id,
);
if (licenseError) {
throw licenseError;
}
}
// 国に応じたリージョンのBlobストレージにコンテナが存在するか確認
await this.blobStorageService.containerExists(
context,
@ -285,14 +319,32 @@ export class FilesService {
);
return url;
} catch (e) {
this.logger.error(`error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case AccountLockedError:
throw new HttpException(
makeErrorResponse('E010504'),
HttpStatus.BAD_REQUEST,
);
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${this.publishUploadSas.name}`,
);
}
}
@ -309,7 +361,9 @@ export class FilesService {
audioFileId: number,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishAudioFileDownloadSas.name} | params: { externalId: ${externalId}, audioFileId: ${audioFileId} };`,
`[IN] [${context.getTrackingId()}] ${
this.publishAudioFileDownloadSas.name
} | params: { externalId: ${externalId}, audioFileId: ${audioFileId} };`,
);
//DBから国情報とアカウントID,ユーザーIDを取得する
@ -323,21 +377,46 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスの有効性をチェック
const { licenseError } = await this.checkLicenseValidityByUserId(
context,
user.id,
);
if (licenseError) {
throw licenseError;
}
}
accountId = user.account.id;
userId = user.id;
country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id ?? undefined;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishAudioFileDownloadSas.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
`[OUT] [${context.getTrackingId()}] ${
this.publishAudioFileDownloadSas.name
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
try {
@ -384,7 +463,7 @@ export class FilesService {
);
if (!isFileExist) {
this.logger.log(`filePath:${filePath}`);
this.logger.log(`[${context.getTrackingId()}] filePath:${filePath}`);
throw new AudioFileNotFoundError(
`Audio file is not exists in blob storage. audio_file_id:${audioFileId}, url:${file.url}, fileName:${file.file_name}`,
);
@ -399,7 +478,7 @@ export class FilesService {
);
return url;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -429,7 +508,9 @@ export class FilesService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishAudioFileDownloadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.publishAudioFileDownloadSas.name
}`,
);
}
}
@ -446,7 +527,9 @@ export class FilesService {
audioFileId: number,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishTemplateFileDownloadSas.name} | params: { externalId: ${externalId}, audioFileId: ${audioFileId} };`,
`[IN] [${context.getTrackingId()}] ${
this.publishTemplateFileDownloadSas.name
} | params: { externalId: ${externalId}, audioFileId: ${audioFileId} };`,
);
//DBから国情報とアカウントID,ユーザーIDを取得する
@ -460,20 +543,46 @@ export class FilesService {
if (!user.account) {
throw new AccountNotFoundError('account not found.');
}
// 第五階層のみチェック
if (user.account.tier === TIERS.TIER5) {
// ライセンスの有効性をチェック
const { licenseError } = await this.checkLicenseValidityByUserId(
context,
user.id,
);
if (licenseError) {
throw licenseError;
}
}
accountId = user.account_id;
userId = user.id;
country = user.account.country;
isTypist = user.role === USER_ROLES.TYPIST;
authorId = user.author_id ?? undefined;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishTemplateFileDownloadSas.name}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
`[OUT] [${context.getTrackingId()}] ${
this.publishTemplateFileDownloadSas.name
}`,
);
switch (e.constructor) {
case LicenseExpiredError:
throw new HttpException(
makeErrorResponse('E010805'),
HttpStatus.BAD_REQUEST,
);
case LicenseNotAllocatedError:
throw new HttpException(
makeErrorResponse('E010812'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
try {
@ -544,7 +653,7 @@ export class FilesService {
);
return url;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -562,6 +671,7 @@ export class FilesService {
makeErrorResponse('E010701'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
@ -575,7 +685,9 @@ export class FilesService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishTemplateFileDownloadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.publishTemplateFileDownloadSas.name
}`,
);
}
}
@ -591,7 +703,9 @@ export class FilesService {
externalId: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.publishTemplateFileUploadSas.name
} | params: { externalId: ${externalId} };`,
);
try {
const { account } = await this.usersRepository.findUserByExternalId(
@ -620,14 +734,16 @@ export class FilesService {
return url;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishTemplateFileUploadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.publishTemplateFileUploadSas.name
}`,
);
}
}
@ -647,7 +763,9 @@ export class FilesService {
fileName: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.templateUploadFinished.name} | params: { externalId: ${externalId}, url: ${url}, fileName: ${fileName} };`,
`[IN] [${context.getTrackingId()}] ${
this.templateUploadFinished.name
} | params: { externalId: ${externalId}, url: ${url}, fileName: ${fileName} };`,
);
try {
@ -659,7 +777,9 @@ export class FilesService {
const urlObj = new URL(url);
urlObj.search = '';
const fileUrl = urlObj.toString();
this.logger.log(`Request URL: ${url}, Without param URL${fileUrl}`);
this.logger.log(
`[${context.getTrackingId()}] Request URL: ${url}, Without param URL${fileUrl}`,
);
// テンプレートファイル情報をDBに登録
await this.templateFilesRepository.upsertTemplateFile(
@ -668,15 +788,63 @@ export class FilesService {
fileUrl,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.templateUploadFinished.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.templateUploadFinished.name
}`,
);
}
}
/**
*
*
* @param userId
* @returns licenseError?
*/
// TODO: TASK3084で共通部品化する
private async checkLicenseValidityByUserId(
context: Context,
userId: number,
): Promise<{ licenseError?: Error }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.checkLicenseValidityByUserId.name
} | params: { userId: ${userId} };`,
);
try {
const allocatedLicense = await this.usersRepository.findLicenseByUserId(
userId,
);
if (!allocatedLicense) {
return {
licenseError: new LicenseNotAllocatedError(
'license is not allocated.',
),
};
} else {
const currentDate = new DateWithZeroTime();
if (
allocatedLicense.expiry_date &&
allocatedLicense.expiry_date < currentDate
) {
return {
licenseError: new LicenseExpiredError('license is expired.'),
};
}
}
return {}; // エラーがない場合は空のオブジェクトを返す
} catch (e) {
// リポジトリ層のエラーやその他の例外をハンドリング
return e;
}
}
}

View File

@ -19,6 +19,7 @@ export type BlobstorageServiceMockValue = {
export type UsersRepositoryMockValue = {
findUserByExternalId: User | Error;
isUserLicenseValid: boolean | Error;
};
export type TasksRepositoryMockValue = {
@ -91,13 +92,17 @@ export const makeBlobstorageServiceMock = (
};
export const makeUsersRepositoryMock = (value: UsersRepositoryMockValue) => {
const { findUserByExternalId } = value;
const { findUserByExternalId, isUserLicenseValid } = value;
return {
findUserByExternalId:
findUserByExternalId instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(findUserByExternalId)
: jest.fn<Promise<User>, []>().mockResolvedValue(findUserByExternalId),
isUserLicenseValid:
isUserLicenseValid instanceof Error
? jest.fn<Promise<void>, []>().mockRejectedValue(isUserLicenseValid)
: jest.fn<Promise<boolean>, []>().mockResolvedValue(isUserLicenseValid),
};
};
@ -169,6 +174,7 @@ export const makeDefaultUsersRepositoryMockValue =
user: null,
},
},
isUserLicenseValid: true,
};
};

View File

@ -43,6 +43,7 @@ import {
} from '../../tasks/test/tasks.service.mock';
import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity';
import { UserGroupMember } from '../../../repositories/user_groups/entity/user_group_member.entity';
import { License } from '../../../repositories/licenses/entity/license.entity';
export const createTask = async (
datasource: DataSource,
@ -205,3 +206,33 @@ export const makeTestingModuleWithBlobAndNotification = async (
console.log(e);
}
};
export const createLicense = async (
datasource: DataSource,
licenseId: number,
expiry_date: Date | null,
accountId: number,
type: string,
status: string,
allocated_user_id: number | null,
order_id: number | null,
deleted_at: Date | null,
delete_order_id: number | null,
): Promise<void> => {
const { identifiers } = await datasource.getRepository(License).insert({
id: licenseId,
expiry_date: expiry_date,
account_id: accountId,
type: type,
status: status,
allocated_user_id: allocated_user_id,
order_id: order_id,
deleted_at: deleted_at,
delete_order_id: delete_order_id,
created_by: 'test_runner',
created_at: new Date(),
updated_by: 'updater',
updated_at: new Date(),
});
identifiers.pop() as License;
};

View File

@ -91,9 +91,11 @@ export class LicensesController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
// ライセンス注文処理
await this.licensesService.licenseOrders(
context,
userId,
body.poNumber,
body.orderCount,
@ -142,8 +144,10 @@ export class LicensesController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const cardLicenseKeys = await this.licensesService.issueCardLicenseKeys(
context,
userId,
body.createCount,
);
@ -202,8 +206,10 @@ export class LicensesController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
await this.licensesService.activateCardLicenseKey(
context,
userId,
body.cardLicenseKey,
);

View File

@ -59,8 +59,14 @@ describe('LicensesService', () => {
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
expect(
await service.licenseOrders(userId, body.poNumber, body.orderCount),
await service.licenseOrders(
context,
userId,
body.poNumber,
body.orderCount,
),
).toEqual(undefined);
});
it('ユーザID取得できなかった場合、エラーとなる', async () => {
@ -81,8 +87,9 @@ describe('LicensesService', () => {
const userId = '';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(userId, body.poNumber, body.orderCount),
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -108,8 +115,9 @@ describe('LicensesService', () => {
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(userId, body.poNumber, body.orderCount),
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -133,8 +141,9 @@ describe('LicensesService', () => {
const userId = '0001';
body.orderCount = 1000;
body.poNumber = '1';
const context = makeContext(`uuidv4`);
await expect(
service.licenseOrders(userId, body.poNumber, body.orderCount),
service.licenseOrders(context, userId, body.poNumber, body.orderCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E010401'),
@ -170,8 +179,9 @@ describe('LicensesService', () => {
'AEJWRFFSWRQYQQJ6WVLV',
],
};
const context = makeContext(`uuidv4`);
expect(
await service.issueCardLicenseKeys(userId, body.createCount),
await service.issueCardLicenseKeys(context, userId, body.createCount),
).toEqual(issueCardLicensesResponse);
});
it('カードライセンス発行に失敗した場合、エラーになる', async () => {
@ -189,8 +199,9 @@ describe('LicensesService', () => {
const body = new IssueCardLicensesRequest();
const userId = '0001';
body.createCount = 1000;
const context = makeContext(`uuidv4`);
await expect(
service.issueCardLicenseKeys(userId, body.createCount),
service.issueCardLicenseKeys(context, userId, body.createCount),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -212,8 +223,13 @@ describe('LicensesService', () => {
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
expect(
await service.activateCardLicenseKey(userId, body.cardLicenseKey),
await service.activateCardLicenseKey(
context,
userId,
body.cardLicenseKey,
),
).toEqual(undefined);
});
it('カードライセンス取り込みに失敗した場合、エラーになるDBエラー', async () => {
@ -231,8 +247,9 @@ describe('LicensesService', () => {
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(userId, body.cardLicenseKey),
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -256,8 +273,9 @@ describe('LicensesService', () => {
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(userId, body.cardLicenseKey),
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010801'), HttpStatus.BAD_REQUEST),
);
@ -278,8 +296,9 @@ describe('LicensesService', () => {
const body = new ActivateCardLicensesRequest();
const userId = '0001';
body.cardLicenseKey = 'WZCETXC0Z9PQZ9GKRGGY';
const context = makeContext(`uuidv4`);
await expect(
service.activateCardLicenseKey(userId, body.cardLicenseKey),
service.activateCardLicenseKey(context, userId, body.cardLicenseKey),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010802'), HttpStatus.BAD_REQUEST),
);
@ -320,7 +339,8 @@ describe('DBテスト', () => {
const service = module.get<LicensesService>(LicensesService);
const issueCount = 500;
await service.issueCardLicenseKeys(externalId, issueCount);
const context = makeContext(`uuidv4`);
await service.issueCardLicenseKeys(context, externalId, issueCount);
const dbSelectResult = await selectCardLicensesCount(source);
expect(dbSelectResult.count).toEqual(issueCount);
});
@ -359,8 +379,9 @@ describe('DBテスト', () => {
await createCardLicenseIssue(source, issueId);
const service = module.get<LicensesService>(LicensesService);
const context = makeContext(`uuidv4`);
await service.activateCardLicenseKey(externalId, cardLicenseKey);
await service.activateCardLicenseKey(context, externalId, cardLicenseKey);
const dbSelectResultFromCardLicense = await selectCardLicense(
source,
cardLicenseKey,

View File

@ -1,6 +1,5 @@
import { HttpException, HttpStatus, Injectable, Logger } from '@nestjs/common';
import { makeErrorResponse } from '../../common/error/makeErrorResponse';
import { AccessToken } from '../../common/token';
import { UsersRepositoryService } from '../../repositories/users/users.repository.service';
import { AccountsRepositoryService } from '../../repositories/accounts/accounts.repository.service';
import { AccountNotFoundError } from '../../repositories/accounts/errors/types';
@ -13,6 +12,7 @@ import {
import { LicensesRepositoryService } from '../../repositories/licenses/licenses.repository.service';
import { UserNotFoundError } from '../../repositories/users/errors/types';
import {
DateWithZeroTime,
GetAllocatableLicensesResponse,
IssueCardLicensesResponse,
} from './types/types';
@ -33,12 +33,17 @@ export class LicensesService {
* @param body
*/
async licenseOrders(
context: Context,
externalId: string,
poNumber: string,
orderCount: number,
): Promise<void> {
//アクセストークンからユーザーIDを取得する
this.logger.log(`[IN] ${this.licenseOrders.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.licenseOrders.name
} | params: { externalId: ${externalId}, poNumber: ${poNumber}, orderCount: ${orderCount} };`,
);
let myAccountId: number;
let parentAccountId: number | undefined;
@ -48,7 +53,7 @@ export class LicensesService {
await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -73,7 +78,7 @@ export class LicensesService {
throw new Error('parent account id is undefined');
}
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case AccountNotFoundError:
throw new HttpException(
@ -111,9 +116,15 @@ export class LicensesService {
}
}
async issueCardLicenseKeys(
context: Context,
externalId: string,
createCount: number,
): Promise<IssueCardLicensesResponse> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.issueCardLicenseKeys.name
} | params: { externalId: ${externalId}, createCount: ${createCount} };`,
);
const issueCardLicensesResponse = new IssueCardLicensesResponse();
let myAccountId: number;
@ -123,7 +134,7 @@ export class LicensesService {
await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -145,8 +156,10 @@ export class LicensesService {
issueCardLicensesResponse.cardLicenseKeys = licenseKeys;
return issueCardLicensesResponse;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('get cardlicensekeys failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] get cardlicensekeys failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -160,11 +173,14 @@ export class LicensesService {
* @param cardLicenseKey
*/
async activateCardLicenseKey(
context: Context,
externalId: string,
cardLicenseKey: string,
): Promise<void> {
this.logger.log(
`[IN] ${this.activateCardLicenseKey.name}, argCardLicenseKey: ${cardLicenseKey}`,
`[IN] [${context.getTrackingId()}] ${
this.activateCardLicenseKey.name
} | params: { externalId: ${externalId}, argCardLicenseKey: ${cardLicenseKey} };`,
);
let myAccountId: number;
@ -174,7 +190,7 @@ export class LicensesService {
await this.usersRepository.findUserByExternalId(externalId)
).account_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -196,8 +212,10 @@ export class LicensesService {
cardLicenseKey,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('cardLicenseKey activate failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] cardLicenseKey activate failed`,
);
switch (e.constructor) {
case LicenseNotExistError:
@ -217,7 +235,9 @@ export class LicensesService {
);
}
}
this.logger.log(`[OUT] ${this.activateCardLicenseKey.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.activateCardLicenseKey.name}`,
);
return;
}
@ -232,8 +252,9 @@ export class LicensesService {
userId: string,
): Promise<GetAllocatableLicensesResponse> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getAllocatableLicenses.name} | params: { ` +
`userId: ${userId}, `,
`[IN] [${context.getTrackingId()}] ${
this.getAllocatableLicenses.name
} | params: { ` + `userId: ${userId}, `,
);
// ユーザIDからアカウントIDを取得する
try {
@ -248,15 +269,19 @@ export class LicensesService {
allocatableLicenses,
};
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('get allocatable lisences failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] get allocatable lisences failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getAllocatableLicenses.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.getAllocatableLicenses.name
}`,
);
}
}
@ -273,7 +298,9 @@ export class LicensesService {
poNumber: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.cancelOrder.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.cancelOrder.name
} | params: { ` +
`externalId: ${externalId}, ` +
`poNumber: ${poNumber}, };`,
);
@ -287,7 +314,7 @@ export class LicensesService {
// 注文キャンセル処理
await this.licensesRepository.cancelOrder(myAccountId, poNumber);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -306,7 +333,9 @@ export class LicensesService {
);
}
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancelOrder.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.cancelOrder.name}`,
);
}
return;
}

View File

@ -26,7 +26,9 @@ export class NotificationService {
pnsHandler: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.register.name} | params: { externalId: ${externalId}, pns: ${pns}, pnsHandler: ${pnsHandler} }`,
`[IN] [${context.getTrackingId()}] ${
this.register.name
} | params: { externalId: ${externalId}, pns: ${pns}, pnsHandler: ${pnsHandler} }`,
);
// ユーザIDからアカウントIDを取得する
@ -34,7 +36,7 @@ export class NotificationService {
try {
userId = (await this.usersRepository.findUserByExternalId(externalId)).id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
@ -53,7 +55,7 @@ export class NotificationService {
// TODO: 登録毎に新規登録する想定でUUIDを付与している
// もしデバイスごとに登録を上書きするようであればUUID部分にデバイス識別子を設定
const installationId = `${pns}_${userId}_${uuidv4()}`;
this.logger.log(installationId);
this.logger.log(`[${context.getTrackingId()}] ${installationId}`);
await this.notificationhubService.register(
context,
@ -63,13 +65,15 @@ export class NotificationService {
installationId,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.register.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.register.name}`,
);
}
}
}

View File

@ -318,7 +318,7 @@ export class TasksController {
HttpStatus.UNAUTHORIZED,
);
}
const { userId, role } = decodedAccessToken as AccessToken;
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);

View File

@ -35,7 +35,6 @@ import {
} from '../workflows/test/utility';
import { createTemplateFile } from '../templates/test/utility';
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
import { Roles } from '../../common/types/role';
describe('TasksService', () => {
@ -835,6 +834,9 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, typistUserId_1);
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
const NotificationHubService = module.get<NotificationhubService>(
NotificationhubService,
);
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
@ -850,6 +852,18 @@ describe('changeCheckoutPermission', () => {
user_id: typistUserId_2,
user_group_id: null,
});
const resultTask = await getTask(source, taskId);
// 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId_2}`],
{
authorId: 'MY_AUTHOR_ID',
filename: 'x',
priority: 'High',
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
},
);
});
it('タスクのチェックアウト権限を変更できる。(グループ指定)', async () => {
@ -903,6 +917,9 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, typistUserId_1);
await createCheckoutPermissions(source, taskId, undefined, userGroupId_1);
const service = module.get<TasksService>(TasksService);
const NotificationHubService = module.get<NotificationhubService>(
NotificationhubService,
);
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
@ -918,6 +935,19 @@ describe('changeCheckoutPermission', () => {
user_id: null,
user_group_id: userGroupId_2,
});
const resultTask = await getTask(source, taskId);
// 通知処理が想定通りの引数で呼ばれているか確認
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId_2}`],
{
authorId: 'MY_AUTHOR_ID',
filename: 'x',
priority: 'High',
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
},
);
});
it('タスクのチェックアウト権限を変更できる。(チェックアウト権限を外す)', async () => {
@ -1012,17 +1042,89 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-exist-user', typistUserId: 999 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('ユーザーがメール認証されていない場合、タスクのチェックアウト権限を変更できない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId_1 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id_1',
role: 'typist',
});
const { id: typistUserId_2 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id_2',
role: 'typist',
email_verified: false,
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
'Uploaded',
);
const { userGroupId } = await createUserGroup(
source,
accountId,
'USER_GROUP_A',
[typistUserId_1],
);
await createCheckoutPermissions(source, taskId, typistUserId_1);
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-verified-user', typistUserId: typistUserId_2 }],
'author-user-external-id',
['admin'],
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('ユーザーグループが存在しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1066,17 +1168,23 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'not-exist-user-group', typistGroupId: 999 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010204'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('タスクが存在しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1102,17 +1210,23 @@ describe('changeCheckoutPermission', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('タスクのステータスがUploadedでない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1148,17 +1262,23 @@ describe('changeCheckoutPermission', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがAuthorでタスクのAuthorIDと自身のAuthorIDが一致しない場合、タスクのチェックアウト権限を変更できない', async () => {
@ -1194,17 +1314,23 @@ describe('changeCheckoutPermission', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user', typistUserId: typistUserId }],
'author-user-external-id',
['author'],
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('通知に失敗した場合、エラーとなる', async () => {
@ -1254,20 +1380,23 @@ describe('changeCheckoutPermission', () => {
await createCheckoutPermissions(source, taskId, undefined, userGroupId);
const service = module.get<TasksService>(TasksService);
await expect(
service.changeCheckoutPermission(
try {
await service.changeCheckoutPermission(
makeContext('trackingId'),
1,
[{ typistName: 'typist-user-2', typistUserId: typistUserId_2 }],
'author-user-external-id',
['admin'],
),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
expect(e.getResponse()).toEqual(makeErrorResponse('E009999'));
} else {
fail();
}
}
});
});
@ -1292,12 +1421,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのチェックアウト権限が個人指定である時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1357,12 +1481,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのチェックアウト権限がグループ指定である時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1422,12 +1541,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、タスクのステータスがPendingである時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
@ -1481,12 +1595,7 @@ describe('checkout', () => {
it('ユーザーのRoleがTypistで、対象のタスクのStatus[Uploaded,Inprogress,Pending]以外の時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1513,26 +1622,27 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['typist'],
'typist-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、チェックアウト権限が存在しない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1559,26 +1669,153 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['typist'],
'typist-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、既にチェックアウト中のタスクInProgressがある時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
TASK_STATUS.IN_PROGRESS,
typistUserId,
);
const { taskId, audioFileId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000002',
TASK_STATUS.UPLOADED,
);
await createCheckoutPermissions(source, taskId, typistUserId);
const service = module.get<TasksService>(TasksService);
try {
await service.checkout(
makeContext('trackingId'),
audioFileId,
['typist'],
'typist-user-external-id',
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがTypistで、別ユーザーによってチェックアウト中のタスクInProgressがある時、タスクをチェックアウトできる', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: typistUserId1 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id1',
role: 'typist',
});
const { id: typistUserId2 } = await makeTestUser(source, {
account_id: accountId,
external_id: 'typist-user-external-id2',
role: 'typist',
});
const { id: authorUserId } = await makeTestUser(source, {
account_id: accountId,
external_id: 'author-user-external-id',
role: 'author',
author_id: 'MY_AUTHOR_ID',
});
await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000001',
TASK_STATUS.IN_PROGRESS,
typistUserId1,
);
const { taskId } = await createTask(
source,
accountId,
authorUserId,
'MY_AUTHOR_ID',
'',
'01',
'00000002',
TASK_STATUS.UPLOADED,
);
await createCheckoutPermissions(source, taskId, typistUserId2);
const service = module.get<TasksService>(TasksService);
await service.checkout(
makeContext('trackingId'),
2,
['typist'],
'typist-user-external-id2',
);
const resultTask = await getTask(source, taskId);
const permisions = await getCheckoutPermissions(source, taskId);
expect(resultTask?.status).toEqual(TASK_STATUS.IN_PROGRESS);
expect(resultTask?.typist_user_id).toEqual(typistUserId2);
expect(permisions.length).toBe(1);
expect(permisions[0].task_id).toBe(taskId);
expect(permisions[0].user_id).toBe(typistUserId2);
});
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Uploaded)', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1611,12 +1848,7 @@ describe('checkout', () => {
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクをチェックアウトできる(Finished)', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1637,6 +1869,7 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
expect(
await service.checkout(
makeContext('trackingId'),
@ -1649,12 +1882,7 @@ describe('checkout', () => {
it('ユーザーのRoleがAuthorで、アップロードした音声ファイルに紐づいたタスクが存在しない場合、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1665,26 +1893,27 @@ describe('checkout', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['author'],
'author-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010601'), HttpStatus.BAD_REQUEST),
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.NOT_FOUND);
expect(e.getResponse()).toEqual(makeErrorResponse('E010601'));
} else {
fail();
}
}
});
it('ユーザーのRoleがAuthorで、音声ファイルに紐づいたタスクでユーザーと一致するAuthorIDでない場合、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
const { id: authorUserId } = await makeTestUser(source, {
@ -1705,26 +1934,27 @@ describe('checkout', () => {
);
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['author'],
'author-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
it('ユーザーのRoleに[Typist,author]が設定されていない時、タスクをチェックアウトできない', async () => {
if (!source) fail();
const notificationhubServiceMockValue =
makeDefaultNotificationhubServiceMockValue();
const module = await makeTaskTestingModuleWithNotificaiton(
source,
notificationhubServiceMockValue,
);
const module = await makeTestingModule(source);
if (!module) fail();
const { id: accountId } = await makeTestSimpleAccount(source);
await makeTestUser(source, {
@ -1735,16 +1965,22 @@ describe('checkout', () => {
});
const service = module.get<TasksService>(TasksService);
await expect(
service.checkout(
try {
await service.checkout(
makeContext('trackingId'),
1,
['none'],
'none-user-external-id',
),
).rejects.toEqual(
new HttpException(makeErrorResponse('E010602'), HttpStatus.BAD_REQUEST),
);
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toBe(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010602'));
} else {
fail();
}
}
});
});
@ -2556,9 +2792,14 @@ describe('cancel', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${typistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'AUTHOR_ID',
filename: 'x',
priority: 'High',
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
},
);
}, 1000000);
});
it('API実行者のRoleがAdminの場合、自身が文字起こし実行中のタスクをキャンセルし、そのタスクの自動ルーティングを行うAPI実行者のAuthorIDと音声ファイルに紐づくWorkType', async () => {
if (!source) fail();
@ -2661,7 +2902,12 @@ describe('cancel', () => {
expect(NotificationHubService.notify).toHaveBeenCalledWith(
makeContext('trackingId'),
[`user_${autoRoutingTypistUserId}`],
makeNotifyMessage('M000101'),
{
authorId: 'AUTHOR_ID',
filename: 'x',
priority: 'High',
uploadedAt: resultTask?.file?.uploaded_at.toISOString(),
},
);
});
it('API実行者のRoleがTypistの場合、自身が文字起こし実行中のタスクをキャンセルするが、一致するワークフローがない場合は自動ルーティングを行うことができない', async () => {

View File

@ -19,6 +19,7 @@ import { AdB2cUser } from '../../gateways/adb2c/types/types';
import { CheckoutPermission } from '../../repositories/checkout_permissions/entity/checkout_permission.entity';
import {
AccountNotMatchError,
AlreadyHasInProgressTaskError,
CheckoutPermissionNotFoundError,
StatusNotMatchError,
TaskAuthorIdNotMatchError,
@ -31,7 +32,6 @@ import { Roles } from '../../common/types/role';
import { InvalidRoleError } from './errors/types';
import { NotificationhubService } from '../../gateways/notificationhub/notificationhub.service';
import { UserGroupsRepositoryService } from '../../repositories/user_groups/user_groups.repository.service';
import { makeNotifyMessage } from '../../common/notify/makeNotifyMessage';
import { Context } from '../../common/log';
import { User } from '../../repositories/users/entity/user.entity';
@ -57,7 +57,14 @@ export class TasksService {
direction?: SortDirection,
): Promise<{ tasks: Task[]; total: number }> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getTasks.name} | params: { offset: ${offset}, limit: ${limit}, status: ${status}, paramName: ${paramName}, direction: ${direction} };`,
`[IN] [${context.getTrackingId()}] ${this.getTasks.name} | params: { ` +
`userId: ${userId}, ` +
`roles: ${roles}, ` +
`offset: ${offset},` +
`limit: ${limit}, ` +
`status: ${status}, ` +
`paramName: ${paramName}, ` +
`direction: ${direction} };`,
);
// パラメータが省略された場合のデフォルト値: 保存するソート条件の値の初期値と揃える
@ -141,7 +148,7 @@ export class TasksService {
throw new Error(`invalid roles: ${roles.join(',')}`);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
if (e.constructor === Adb2cTooManyRequestsError) {
throw new HttpException(
@ -155,7 +162,9 @@ export class TasksService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getTasks.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getTasks.name}`,
);
}
}
@ -172,7 +181,9 @@ export class TasksService {
fileId: number,
): Promise<number | undefined> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getNextTask.name} | params: { externalId: ${externalId}, fileId: ${fileId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getNextTask.name
} | params: { externalId: ${externalId}, fileId: ${fileId} };`,
);
try {
@ -211,7 +222,7 @@ export class TasksService {
? undefined
: nextTask.audio_file_id;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -231,7 +242,9 @@ export class TasksService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getNextTask.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getNextTask.name}`,
);
}
}
@ -250,7 +263,9 @@ export class TasksService {
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.trackingId}] ${this.checkout.name} | params: { audioFileId: ${audioFileId}, roles: ${roles}, externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.checkout.name
} | params: { audioFileId: ${audioFileId}, roles: ${roles}, externalId: ${externalId} };`,
);
const { id, account_id, author_id } =
@ -279,7 +294,7 @@ export class TasksService {
throw new InvalidRoleError(`invalid roles: ${roles.join(',')}`);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case CheckoutPermissionNotFoundError:
@ -296,6 +311,7 @@ export class TasksService {
);
case AccountNotMatchError:
case StatusNotMatchError:
case AlreadyHasInProgressTaskError:
throw new HttpException(
makeErrorResponse('E010601'),
HttpStatus.BAD_REQUEST,
@ -312,7 +328,9 @@ export class TasksService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.checkout.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.checkout.name}`,
);
}
}
@ -329,7 +347,9 @@ export class TasksService {
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.trackingId}] ${this.checkin.name} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.checkin.name
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
);
const { id } = await this.usersRepository.findUserByExternalId(
externalId,
@ -341,7 +361,7 @@ export class TasksService {
TASK_STATUS.IN_PROGRESS,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -367,7 +387,9 @@ export class TasksService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.checkin.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.checkin.name}`,
);
}
}
/**
@ -384,15 +406,17 @@ export class TasksService {
role: Roles[],
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.cancel.name} | params: { audioFileId: ${audioFileId}, externalId: ${externalId}, role: ${role} };`,
`[IN] [${context.getTrackingId()}] ${
this.cancel.name
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId}, role: ${role} };`,
);
let user: User;
try {
// ユーザー取得
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancel.name}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.cancel.name}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -408,7 +432,7 @@ export class TasksService {
role.includes(ADMIN_ROLES.ADMIN) ? undefined : user.id,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -444,38 +468,22 @@ export class TasksService {
user.author_id ?? undefined,
);
const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
typistGroupIds,
);
// 重複のない割り当て候補ユーザーID一覧を取得する
const distinctUserIds = [
...new Set([...typistIds, ...groupMembers.map((x) => x.user_id)]),
];
// 割り当てられたユーザーがいない場合は通知不要
if (distinctUserIds.length === 0) {
this.logger.log('No user assigned.');
return;
}
// タグを生成
const tags = distinctUserIds.map((x) => `user_${x}`);
this.logger.log(`tags: ${tags}`);
// タグ対象に通知送信
await this.notificationhubService.notify(
// 通知を送信する
await this.sendNotify(
context,
tags,
makeNotifyMessage('M000101'),
typistIds,
typistGroupIds,
audioFileId,
user.account_id,
);
} catch (e) {
// 処理の本筋はタスクキャンセルのため自動ルーティングに失敗してもエラーにしない
this.logger.error(`Automatic routing or notification failed.`);
this.logger.error(`error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] Automatic routing or notification failed.`,
);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.cancel.name}`);
this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.cancel.name}`);
}
}
@ -492,7 +500,9 @@ export class TasksService {
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.trackingId}] ${this.suspend.name} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.suspend.name
} | params: { audioFileId: ${audioFileId}, externalId: ${externalId} };`,
);
const { id } = await this.usersRepository.findUserByExternalId(
externalId,
@ -504,7 +514,7 @@ export class TasksService {
TASK_STATUS.IN_PROGRESS,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TasksNotFoundError:
@ -530,7 +540,9 @@ export class TasksService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.suspend.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.suspend.name}`,
);
}
}
@ -539,6 +551,11 @@ export class TasksService {
tasks: TaskEntity[],
permissions: CheckoutPermission[],
): Promise<AdB2cUser[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getB2cUsers.name
} | params: { tasks: ${tasks}, permissions: ${permissions} };`,
);
// 割り当て候補の外部IDを列挙
const assigneesExternalIds = permissions.flatMap((permission) =>
permission.user ? [permission.user.external_id] : [],
@ -571,7 +588,9 @@ export class TasksService {
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.trackingId}] ${this.changeCheckoutPermission.name} | params: { audioFileId: ${audioFileId}, assignees: ${assignees}, externalId: ${externalId}, role: ${role} };`,
`[IN] [${context.getTrackingId()}] ${
this.changeCheckoutPermission.name
} | params: { audioFileId: ${audioFileId}, assignees: ${assignees}, externalId: ${externalId}, role: ${role} };`,
);
const { author_id, account_id } =
await this.usersRepository.findUserByExternalId(externalId);
@ -600,38 +619,16 @@ export class TasksService {
.flatMap((assignee) =>
assignee.typistUserId ? [assignee.typistUserId] : [],
);
const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
assigneesGroupIds,
);
// 重複のない割り当て候補ユーザーID一覧を取得する
const distinctUserIds = [
...new Set([
...assigneesUserIds,
...groupMembers.map((x) => x.user_id),
]),
];
// 割り当てられたユーザーがいない場合は通知不要
if (distinctUserIds.length === 0) {
this.logger.log('No user assigned.');
return;
}
// タグを生成
const tags = distinctUserIds.map((x) => `user_${x}`);
this.logger.log(`tags: ${tags}`);
// タグ対象に通知送信
await this.notificationhubService.notify(
// 通知を送信する
await this.sendNotify(
context,
tags,
makeNotifyMessage('M000101'),
assigneesUserIds,
assigneesGroupIds,
audioFileId,
account_id,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case TypistUserNotFoundError:
@ -658,8 +655,64 @@ export class TasksService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.changeCheckoutPermission.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.changeCheckoutPermission.name
}`,
);
}
}
// 通知を送信するプライベートメソッド
private async sendNotify(
context: Context,
typistUserIds: number[],
typistGroupIds: number[],
audioFileId: number,
accountId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.sendNotify.name} | params: { ` +
`typistUserIds: ${typistUserIds}, ` +
`typistGroupIds: ${typistGroupIds}, ` +
`audioFileId: ${audioFileId}, ` +
`accountId: ${accountId} };`,
);
const groupMembers =
await this.userGroupsRepositoryService.getGroupMembersFromGroupIds(
typistGroupIds,
);
// 重複のない割り当て候補ユーザーID一覧を取得する
const distinctUserIds = [
...new Set([...typistUserIds, ...groupMembers.map((x) => x.user_id)]),
];
// 割り当てられたユーザーがいない場合は通知不要
if (distinctUserIds.length === 0) {
this.logger.log(`[${context.getTrackingId()}] No user assigned.`);
return;
}
// タグを生成
const tags = distinctUserIds.map((x) => `user_${x}`);
this.logger.log(`[${context.getTrackingId()}] tags: ${tags}`);
// 通知内容に含む音声ファイル情報を取得
const { file } = await this.taskRepository.getTaskAndAudioFile(
audioFileId,
accountId,
[TASK_STATUS.UPLOADED],
);
if (!file) {
throw new Error('audio file not found');
}
// タグ対象に通知送信
await this.notificationhubService.notify(context, tags, {
authorId: file.author_id,
filename: file.file_name.replace('.zip', ''),
priority: file.priority === '00' ? 'Normal' : 'High',
uploadedAt: file.uploaded_at.toISOString(),
});
}
}

View File

@ -207,6 +207,9 @@ export const getTask = async (
task_id: number,
): Promise<Task | null> => {
const task = await datasource.getRepository(Task).findOne({
relations: {
file: true,
},
where: {
id: task_id,
},

View File

@ -24,7 +24,9 @@ export class TemplatesService {
externalId: string,
): Promise<TemplateFile[]> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getTemplates.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getTemplates.name
} | params: { externalId: ${externalId} };`,
);
try {
@ -42,14 +44,14 @@ export class TemplatesService {
return resTemplates;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getTemplates.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getTemplates.name}`,
);
}
}

View File

@ -3,8 +3,8 @@ import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
import { TermsService } from '../terms/terms.service';
import { ErrorResponse } from '../../common/error/types/types';
import { makeContext } from '../../common/log';
import { GetTermsInfoResponse } from './types/types';
import { v4 as uuidv4 } from 'uuid';
import { GetTermsInfoResponse, TermInfo } from './types/types';
@ApiTags('terms')
@Controller('terms')

View File

@ -15,7 +15,9 @@ export class TermsService {
* return termsInfo
*/
async getTermsInfo(context: Context): Promise<TermInfo[]> {
this.logger.log(`[IN] [${context.trackingId}] ${this.getTermsInfo.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getTermsInfo.name}`,
);
try {
const { eulaVersion, dpaVersion } =
await this.termsRepository.getLatestTermsInfo();
@ -30,14 +32,14 @@ export class TermsService {
},
];
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getTermsInfo.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getTermsInfo.name}`,
);
}
}

View File

@ -144,9 +144,11 @@ export class OptionItemList {
export class GetRelationsResponse {
@ApiProperty({
description: 'ログインしたユーザーのAuthorIDAuthorでない場合は空文字',
required: false,
description:
'ログインしたユーザーのAuthorIDAuthorでない場合はundefined',
})
authorId: string;
authorId?: string;
@ApiProperty({ description: '属しているアカウントのAuthorID List(全て)' })
authorIdList: string[];
@ApiProperty({

View File

@ -81,7 +81,9 @@ export class UsersController {
@ApiOperation({ operationId: 'confirmUser' })
@Post('confirm')
async confirmUser(@Body() body: ConfirmRequest): Promise<ConfirmResponse> {
await this.usersService.confirmUser(body.token);
const context = makeContext(uuidv4());
await this.usersService.confirmUser(context, body.token);
return {};
}
@ -148,8 +150,9 @@ export class UsersController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const users = await this.usersService.getUsers(userId);
const users = await this.usersService.getUsers(context, userId);
return { users };
}
@ -325,6 +328,7 @@ export class UsersController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
//型チェック
if (
@ -336,7 +340,12 @@ export class UsersController {
HttpStatus.BAD_REQUEST,
);
}
await this.usersService.updateSortCriteria(paramName, direction, userId);
await this.usersService.updateSortCriteria(
context,
paramName,
direction,
userId,
);
return {};
}
@ -383,8 +392,10 @@ export class UsersController {
);
}
const { userId } = decodedAccessToken as AccessToken;
const context = makeContext(userId);
const { direction, paramName } = await this.usersService.getSortCriteria(
context,
userId,
);
return { direction, paramName };
@ -618,10 +629,17 @@ export class UsersController {
): Promise<UpdateAcceptedVersionResponse> {
const { idToken, acceptedEULAVersion, acceptedDPAVersion } = body;
const verifiedIdToken = await this.authService.getVerifiedIdToken(idToken);
const context = makeContext(verifiedIdToken.sub);
const context = makeContext(uuidv4());
const isVerified = await this.authService.isVerifiedUser(verifiedIdToken);
const verifiedIdToken = await this.authService.getVerifiedIdToken(
context,
idToken,
);
const isVerified = await this.authService.isVerifiedUser(
context,
verifiedIdToken,
);
if (!isVerified) {
throw new HttpException(
makeErrorResponse('E010201'),

View File

@ -22,6 +22,7 @@ import {
LICENSE_ALLOCATED_STATUS,
LICENSE_EXPIRATION_THRESHOLD_DAYS,
LICENSE_TYPE,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants';
@ -44,6 +45,8 @@ import {
makeTestUser,
} from '../../common/test/utility';
import { v4 as uuidv4 } from 'uuid';
import { createOptionItems, createWorktype } from '../accounts/test/utility';
import { createWorkflow, getWorkflows } from '../workflows/test/utility';
describe('UsersService.confirmUser', () => {
let source: DataSource | null = null;
@ -94,7 +97,8 @@ describe('UsersService.confirmUser', () => {
// account id:1, user id: 2のトークン
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
await service.confirmUser(token);
const context = makeContext(`uuidv4`);
await service.confirmUser(context, token);
//result
const resultUser = await getUser(source, userId);
const resultLicenses = await getLicenses(source, accountId);
@ -137,7 +141,8 @@ describe('UsersService.confirmUser', () => {
if (!module) fail();
const token = 'invalid.id.token';
const service = module.get<UsersService>(UsersService);
await expect(service.confirmUser(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E000101'), HttpStatus.BAD_REQUEST),
);
});
@ -172,7 +177,8 @@ describe('UsersService.confirmUser', () => {
const service = module.get<UsersService>(UsersService);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
await expect(service.confirmUser(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(makeErrorResponse('E010202'), HttpStatus.BAD_REQUEST),
);
});
@ -183,7 +189,8 @@ describe('UsersService.confirmUser', () => {
const service = module.get<UsersService>(UsersService);
const token =
'eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJhY2NvdW50SWQiOjEsInVzZXJJZCI6MiwiZW1haWwiOiJ4eHhAeHh4Lnh4eCIsImlhdCI6MTAwMDAwMDAwMCwiZXhwIjo5MDAwMDAwMDAwfQ.26L6BdNg-3TbyKT62PswlJ6RPMkcTtHzlDXW2Uo9XbMPVSrl2ObcuS6EcXjFFN2DEfNTKbqX_zevIWMpHOAdLNgGhk528nLrBrNvPASqtTjvW9muxMXpjUdjRVkmVbOylBHWW3YpWL9JEbJQ7rAzWDfaIdPhMovdaxumnZt_UwnlnrdaVPLACW7tkH_laEcAU507iSiM4mqxxG8FuTs34t6PEdwRuzZAQPN2IOPYNSvGNdJYryPacSeSNZ_z1xeBYXLOLQfOBZzyTReYDOhXdikhrNUbxjgnZQlSXBCVMlZ9PH42bHfp-LJIeJzW0yqnF6oLklvJP-fo8eW0k5iDOw';
await expect(service.confirmUser(token)).rejects.toEqual(
const context = makeContext(`uuidv4`);
await expect(service.confirmUser(context, token)).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1623,7 +1630,10 @@ describe('UsersService.getUsers', () => {
},
];
expect(await service.getUsers(externalId_author)).toEqual(expectedUsers);
const context = makeContext(`uuidv4`);
expect(await service.getUsers(context, externalId_author)).toEqual(
expectedUsers,
);
});
it('ユーザーの一覧を取得できること(ライセンス割当済み)', async () => {
@ -1739,7 +1749,10 @@ describe('UsersService.getUsers', () => {
},
];
expect(await service.getUsers(external_id1)).toEqual(expectedUsers);
const context = makeContext(`uuidv4`);
expect(await service.getUsers(context, external_id1)).toEqual(
expectedUsers,
);
});
it('DBからのユーザーの取得に失敗した場合、エラーとなる', async () => {
@ -1760,8 +1773,11 @@ describe('UsersService.getUsers', () => {
prompt: false,
});
const context = makeContext(`uuidv4`);
const service = module.get<UsersService>(UsersService);
await expect(service.getUsers('externalId_failed')).rejects.toEqual(
await expect(
service.getUsers(context, 'externalId_failed'),
).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
);
});
@ -1785,8 +1801,9 @@ describe('UsersService.getUsers', () => {
prompt: false,
});
const context = makeContext(`uuidv4`);
const service = module.get<UsersService>(UsersService);
await expect(service.getUsers(externalId_author)).rejects.toEqual(
await expect(service.getUsers(context, externalId_author)).rejects.toEqual(
new HttpException(makeErrorResponse('E009999'), HttpStatus.NOT_FOUND),
);
});
@ -1809,9 +1826,15 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
expect(
await service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
await service.updateSortCriteria(
context,
'AUTHOR_ID',
'ASC',
'external_id',
),
).toEqual(undefined);
});
@ -1834,9 +1857,10 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -1865,9 +1889,10 @@ describe('UsersService.updateSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
await expect(
service.updateSortCriteria('AUTHOR_ID', 'ASC', 'external_id'),
service.updateSortCriteria(context, 'AUTHOR_ID', 'ASC', 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
@ -1894,8 +1919,9 @@ describe('UsersService.getSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
expect(await service.getSortCriteria('external_id')).toEqual({
expect(await service.getSortCriteria(context, 'external_id')).toEqual({
direction: 'ASC',
paramName: 'JOB_NUMBER',
});
@ -1922,8 +1948,11 @@ describe('UsersService.getSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
await expect(
service.getSortCriteria(context, 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -1954,8 +1983,11 @@ describe('UsersService.getSortCriteria', () => {
configMockValue,
sortCriteriaRepositoryMockValue,
);
const context = makeContext(`uuidv4`);
await expect(service.getSortCriteria('external_id')).rejects.toEqual(
await expect(
service.getSortCriteria(context, 'external_id'),
).rejects.toEqual(
new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -2011,10 +2043,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.NONE,
@ -2069,10 +2102,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.TYPIST,
@ -2127,10 +2161,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2185,10 +2220,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.TYPIST,
@ -2243,10 +2279,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2301,10 +2338,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
await expect(
service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.NONE,
@ -2349,10 +2387,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2407,10 +2446,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
expect(
await service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2465,10 +2505,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
await expect(
service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2524,10 +2565,11 @@ describe('UsersService.updateUser', () => {
});
const service = module.get<UsersService>(UsersService);
const context = makeContext(`uuidv4`);
await expect(
service.updateUser(
{ trackingId: 'trackingId' },
context,
external_id,
user1,
USER_ROLES.AUTHOR,
@ -2666,3 +2708,187 @@ describe('UsersService.getUserName', () => {
}
});
});
describe('UsersService.getRelations', () => {
let source: DataSource | null = null;
beforeEach(async () => {
source = new DataSource({
type: 'sqlite',
database: ':memory:',
logging: false,
entities: [__dirname + '/../../**/*.entity{.ts,.js}'],
synchronize: true,
});
return source.initialize();
});
afterEach(async () => {
if (!source) return;
await source.destroy();
source = null;
});
it('ユーザー関連情報を取得できるAuthor', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { account } = await makeTestAccount(source, {
tier: 5,
});
const { id: user1, external_id } = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_1',
encryption: true,
encryption_password: 'password',
prompt: true,
});
const { id: user2 } = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_2',
});
await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_3',
email_verified: false,
});
const worktype1 = await createWorktype(
source,
account.id,
'worktype1',
undefined,
true,
);
await createOptionItems(source, worktype1.id);
const worktype2 = await createWorktype(source, account.id, 'worktype2');
await createOptionItems(source, worktype2.id);
await createWorkflow(source, account.id, user1, worktype1.id);
await createWorkflow(source, account.id, user1, worktype2.id);
await createWorkflow(source, account.id, user1);
await createWorkflow(source, account.id, user2, worktype1.id);
// 作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
expect(workflows.length).toBe(4);
expect(workflows[0].worktype_id).toBe(worktype1.id);
expect(workflows[0].author_id).toBe(user1);
expect(workflows[1].worktype_id).toBe(worktype2.id);
expect(workflows[1].author_id).toBe(user1);
expect(workflows[2].worktype_id).toBe(null);
expect(workflows[2].author_id).toBe(user1);
expect(workflows[3].worktype_id).toBe(worktype1.id);
expect(workflows[3].author_id).toBe(user2);
}
const context = makeContext(external_id);
const service = module.get<UsersService>(UsersService);
const relations = await service.getRelations(context, external_id);
// レスポンスを確認
{
expect(relations.authorId).toBe('AUTHOR_1');
expect(relations.authorIdList.length).toBe(2);
expect(relations.authorIdList[0]).toBe('AUTHOR_1');
expect(relations.authorIdList[1]).toBe('AUTHOR_2');
const workTypeList = relations.workTypeList;
expect(relations.workTypeList.length).toBe(2);
expect(workTypeList[0].workTypeId).toBe(worktype1.custom_worktype_id);
expect(workTypeList[0].optionItemList.length).toBe(10);
expect(workTypeList[0].optionItemList[0].label).toBe('');
expect(workTypeList[0].optionItemList[0].initialValueType).toBe(2);
expect(workTypeList[0].optionItemList[0].defaultValue).toBe('');
expect(workTypeList[1].workTypeId).toBe(worktype2.custom_worktype_id);
expect(workTypeList[1].optionItemList.length).toBe(10);
expect(relations.isEncrypted).toBe(true);
expect(relations.encryptionPassword).toBe('password');
expect(relations.activeWorktype).toBe(worktype1.custom_worktype_id);
expect(relations.audioFormat).toBe(USER_AUDIO_FORMAT);
expect(relations.prompt).toBe(true);
}
});
it('ユーザー関連情報を取得できるAuthor以外', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
const { account } = await makeTestAccount(source, {
tier: 5,
});
const { external_id } = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.TYPIST,
encryption: false,
prompt: false,
});
const { id: user2 } = await makeTestUser(source, {
account_id: account.id,
role: USER_ROLES.AUTHOR,
author_id: 'AUTHOR_2',
});
const worktype1 = await createWorktype(source, account.id, 'worktype1');
await createOptionItems(source, worktype1.id);
await createWorkflow(source, account.id, user2, worktype1.id);
// 作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
expect(workflows.length).toBe(1);
expect(workflows[0].worktype_id).toBe(worktype1.id);
expect(workflows[0].author_id).toBe(user2);
}
const context = makeContext(external_id);
const service = module.get<UsersService>(UsersService);
const relations = await service.getRelations(context, external_id);
// レスポンスを確認
{
expect(relations.authorId).toBe(undefined);
expect(relations.authorIdList.length).toBe(1);
expect(relations.authorIdList[0]).toBe('AUTHOR_2');
expect(relations.workTypeList.length).toBe(0);
expect(relations.isEncrypted).toBe(false);
expect(relations.encryptionPassword).toBe(undefined);
expect(relations.activeWorktype).toBe('');
expect(relations.audioFormat).toBe(USER_AUDIO_FORMAT);
expect(relations.prompt).toBe(false);
}
});
it('ユーザーが存在しない場合は、ユーザー未存在エラー', async () => {
if (!source) fail();
try {
const module = await makeTestingModule(source);
if (!module) fail();
const context = makeContext(uuidv4());
const service = module.get<UsersService>(UsersService);
await service.getRelations(context, 'external_id');
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
});

View File

@ -36,6 +36,8 @@ import {
ADB2C_SIGN_IN_TYPE,
LICENSE_EXPIRATION_THRESHOLD_DAYS,
MANUAL_RECOVERY_REQUIRED,
OPTION_ITEM_VALUE_TYPE_NUMBER,
USER_AUDIO_FORMAT,
USER_LICENSE_STATUS,
USER_ROLES,
} from '../../constants';
@ -70,8 +72,10 @@ export class UsersService {
* Confirms user
* @param token
*/
async confirmUser(token: string): Promise<void> {
this.logger.log(`[IN] ${this.confirmUser.name}`);
async confirmUser(context: Context, token: string): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.confirmUser.name}`,
);
const pubKey = getPublicKey(this.configService);
const decodedToken = verify<{
@ -93,7 +97,7 @@ export class UsersService {
userId,
);
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case EmailAlreadyVerifiedError:
@ -145,14 +149,23 @@ export class UsersService {
encryptionPassword?: string | undefined,
prompt?: boolean | undefined,
): Promise<void> {
this.logger.log(`[IN] [${context.trackingId}] ${this.createUser.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name} | params: { ` +
`externalId: ${externalId}, ` +
`role: ${role}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlert: ${licenseAlert}, ` +
`notification: ${notification}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`,
);
//DBよりアクセス者の所属するアカウントIDを取得する
let adminUser: EntityUser;
try {
adminUser = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -170,7 +183,7 @@ export class UsersService {
authorId,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -198,8 +211,10 @@ export class UsersService {
name,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create externalUser failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(
`[${context.getTrackingId()}] create externalUser failed`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -220,6 +235,7 @@ export class UsersService {
try {
//roleに応じてユーザー情報を作成する
const newUserInfo = this.createNewUserInfo(
context,
role,
accountId,
externalUser.sub,
@ -234,8 +250,8 @@ export class UsersService {
// ユーザ作成
newUser = await this.usersRepository.createNormalUser(newUserInfo);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create user failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}]create user failed`);
//リカバリー処理
//Azure AD B2Cに登録したユーザー情報を削除する
await this.deleteB2cUser(externalUser.sub, context);
@ -260,6 +276,7 @@ export class UsersService {
// メールの内容を構成
const { subject, text, html } =
await this.sendgridService.createMailContentFromEmailConfirmForNormalUser(
context,
accountId,
newUser.id,
email,
@ -275,8 +292,8 @@ export class UsersService {
html,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error('create user failed');
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] create user failed`);
//リカバリー処理
//Azure AD B2Cに登録したユーザー情報を削除する
await this.deleteB2cUser(externalUser.sub, context);
@ -287,7 +304,9 @@ export class UsersService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
this.logger.log(`[OUT] ${this.createUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`,
);
return;
}
@ -297,12 +316,12 @@ export class UsersService {
try {
await this.adB2cService.deleteUser(externalUserId, context);
this.logger.log(
`[${context.trackingId}] delete externalUser: ${externalUserId}`,
`[${context.getTrackingId()}] delete externalUser: ${externalUserId}`,
);
} catch (error) {
this.logger.error(`error=${error}`);
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.trackingId}] Failed to delete externalUser: ${externalUserId}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete externalUser: ${externalUserId}`,
);
}
}
@ -311,17 +330,18 @@ export class UsersService {
private async deleteUser(userId: number, context: Context) {
try {
await this.usersRepository.deleteNormalUser(userId);
this.logger.log(`[${context.trackingId}] delete user: ${userId}`);
this.logger.log(`[${context.getTrackingId()}] delete user: ${userId}`);
} catch (error) {
this.logger.error(`error=${error}`);
this.logger.error(`[${context.getTrackingId()}] error=${error}`);
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED} [${context.trackingId}] Failed to delete user: ${userId}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user: ${userId}`,
);
}
}
// roleを受け取って、roleに応じたnewUserを作成して返却する
private createNewUserInfo(
context: Context,
role: UserRoles,
accountId: number,
externalId: string,
@ -333,6 +353,21 @@ export class UsersService {
encryptionPassword?: string | undefined,
prompt?: boolean | undefined,
): newUser {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createNewUserInfo.name
} | params: { ` +
`role: ${role}, ` +
`accountId: ${accountId}, ` +
`authorId: ${authorId}, ` +
`externalId: ${externalId}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlert: ${licenseAlert}, ` +
`notification: ${notification}, ` +
`authorId: ${authorId}, ` +
`encryption: ${encryption}, ` +
`prompt: ${prompt} };`,
);
switch (role) {
case USER_ROLES.NONE:
case USER_ROLES.TYPIST:
@ -367,7 +402,9 @@ export class UsersService {
};
default:
//不正なroleが指定された場合はログを出力してエラーを返す
this.logger.error(`[NOT IMPLEMENT] [RECOVER] role: ${role}`);
this.logger.error(
`[${context.getTrackingId()}] [NOT IMPLEMENT] [RECOVER] role: ${role}`,
);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
@ -384,7 +421,9 @@ export class UsersService {
token: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.confirmUserAndInitPassword.name}`,
`[IN] [${context.getTrackingId()}] ${
this.confirmUserAndInitPassword.name
}`,
);
const pubKey = getPublicKey(this.configService);
@ -410,7 +449,11 @@ export class UsersService {
const user = await this.usersRepository.findUserById(userId);
const extarnalId = user.external_id;
// パスワードを変更する
await this.adB2cService.changePassword(extarnalId, ramdomPassword);
await this.adB2cService.changePassword(
context,
extarnalId,
ramdomPassword,
);
// ユーザを認証済みにする
await this.usersRepository.updateUserVerified(userId);
// TODO [Task2163] ODMS側が正式にメッセージを決めるまで仮のメール内容とする
@ -428,7 +471,7 @@ export class UsersService {
html,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case EmailAlreadyVerifiedError:
@ -451,8 +494,8 @@ export class UsersService {
* @param accessToken
* @returns users
*/
async getUsers(externalId: string): Promise<User[]> {
this.logger.log(`[IN] ${this.getUsers.name}`);
async getUsers(context: Context, externalId: string): Promise<User[]> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.getUsers.name}`);
try {
// DBから同一アカウントのユーザ一覧を取得する
@ -462,9 +505,10 @@ export class UsersService {
// DBから取得したユーザーの外部IDをもとにADB2Cからユーザーを取得する
const externalIds = dbUsers.map((x) => x.external_id);
const trackingId = new Context(context.trackingId);
const adb2cUsers = await this.adB2cService.getUsers(
// TODO: 外部連携以外のログ強化時に、ContollerからContextを取得するように修正する
{ trackingId: 'dummy' },
trackingId,
externalIds,
);
@ -552,13 +596,15 @@ export class UsersService {
return users;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.NOT_FOUND,
);
} finally {
this.logger.log(`[OUT] ${this.getUsers.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUsers.name}`,
);
}
}
/**
@ -569,17 +615,22 @@ export class UsersService {
* @returns sort criteria
*/
async updateSortCriteria(
context: Context,
paramName: TaskListSortableAttribute,
direction: SortDirection,
externalId: string,
): Promise<void> {
this.logger.log(`[IN] ${this.updateSortCriteria.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.updateSortCriteria.name
} | params: { paramName: ${paramName}, direction: ${direction}, externalId: ${externalId} };`,
);
let user: EntityUser;
try {
// ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
@ -595,13 +646,15 @@ export class UsersService {
direction,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.updateSortCriteria.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateSortCriteria.name}`,
);
}
}
/**
@ -609,17 +662,24 @@ export class UsersService {
* @param token
* @returns sort criteria
*/
async getSortCriteria(externalId: string): Promise<{
async getSortCriteria(
context: Context,
externalId: string,
): Promise<{
paramName: TaskListSortableAttribute;
direction: SortDirection;
}> {
this.logger.log(`[IN] ${this.getSortCriteria.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getSortCriteria.name
} | params: { externalId: ${externalId} };`,
);
let user: EntityUser;
try {
// ユーザー情報を取得
user = await this.usersRepository.findUserByExternalId(externalId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
@ -642,13 +702,15 @@ export class UsersService {
}
return { direction, paramName: parameter };
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] ${this.getSortCriteria.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getSortCriteria.name}`,
);
}
}
@ -661,134 +723,73 @@ export class UsersService {
context: Context,
userId: string,
): Promise<GetRelationsResponse> {
this.logger.log(`[IN] [${context.trackingId}] ${this.getRelations.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getRelations.name
} | params: { userId: ${userId} };`,
);
try {
const user = await this.usersRepository.findUserByExternalId(userId);
const { id } = await this.usersRepository.findUserByExternalId(userId);
// ユーザー関連情報を取得
const { user, authors, worktypes, activeWorktype } =
await this.usersRepository.getUserRelations(id);
// AuthorIDのリストを作成
const authorIds = authors.flatMap((author) =>
author.author_id ? [author.author_id] : [],
);
const workTypeList = worktypes?.map((worktype) => {
return {
workTypeId: worktype.custom_worktype_id,
optionItemList: worktype.option_items.map((optionItem) => {
const initialValueType = OPTION_ITEM_VALUE_TYPE_NUMBER.find(
(x) => x.type === optionItem.default_value_type,
)?.value;
if (!initialValueType) {
throw new Error(
`invalid default_value_type ${optionItem.default_value_type}`,
);
}
return {
label: optionItem.item_label,
initialValueType,
defaultValue: optionItem.initial_value,
};
}),
};
});
// TODO: PBI2105 本実装時に修正すること
return {
authorId: user.author_id ?? '',
authorIdList: [user.author_id ?? '', 'XXX'],
isEncrypted: true,
encryptionPassword: 'abcd@123?dcba',
audioFormat: 'DS2(QP)',
prompt: true,
workTypeList: [
{
workTypeId: 'workType1',
optionItemList: [
{
label: 'optionItem11',
initialValueType: 2,
defaultValue: 'default11',
},
{
label: 'optionItem12',
initialValueType: 2,
defaultValue: 'default12',
},
{
label: 'optionItem13',
initialValueType: 2,
defaultValue: 'default13',
},
{
label: 'optionItem14',
initialValueType: 2,
defaultValue: 'default14',
},
{
label: 'optionItem15',
initialValueType: 2,
defaultValue: 'default15',
},
{
label: 'optionItem16',
initialValueType: 2,
defaultValue: 'default16',
},
{
label: 'optionItem17',
initialValueType: 2,
defaultValue: 'default17',
},
{
label: 'optionItem18',
initialValueType: 2,
defaultValue: 'default18',
},
{
label: 'optionItem19',
initialValueType: 1,
defaultValue: '',
},
{
label: 'optionItem110',
initialValueType: 3,
defaultValue: '',
},
],
},
{
workTypeId: 'workType2',
optionItemList: [
{
label: 'optionItem21',
initialValueType: 2,
defaultValue: 'default21',
},
{
label: 'optionItem22',
initialValueType: 2,
defaultValue: 'default22',
},
{
label: 'optionItem23',
initialValueType: 2,
defaultValue: 'defaul23',
},
{
label: 'optionItem24',
initialValueType: 2,
defaultValue: 'default24',
},
{
label: 'optionItem25',
initialValueType: 2,
defaultValue: 'default25',
},
{
label: 'optionItem26',
initialValueType: 2,
defaultValue: 'default26',
},
{
label: 'optionItem27',
initialValueType: 2,
defaultValue: 'default27',
},
{
label: 'optionItem28',
initialValueType: 2,
defaultValue: 'default28',
},
{
label: 'optionItem29',
initialValueType: 1,
defaultValue: '',
},
{
label: 'optionItem210',
initialValueType: 3,
defaultValue: '',
},
],
},
],
activeWorktype: 'workType1',
authorId: user.author_id ?? undefined,
authorIdList: authorIds,
workTypeList,
isEncrypted: user.encryption,
encryptionPassword: user.encryption_password ?? undefined,
activeWorktype: activeWorktype?.custom_worktype_id ?? '',
audioFormat: USER_AUDIO_FORMAT,
prompt: user.prompt,
};
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
throw new HttpException(
makeErrorResponse('E010204'),
HttpStatus.BAD_REQUEST,
);
default:
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
}
}
throw new HttpException(
makeErrorResponse('E009999'),
@ -796,7 +797,7 @@ export class UsersService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getRelations.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getRelations.name}`,
);
}
}
@ -831,14 +832,17 @@ export class UsersService {
): Promise<void> {
try {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateUser.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateUser.name
} | params: { ` +
`extarnalId: ${extarnalId}, ` +
`id: ${id}, ` +
`role: ${role}, ` +
`authorId: ${authorId}, ` +
`autoRenew: ${autoRenew}, ` +
`licenseAlart: ${licenseAlart}, ` +
`notification: ${notification}, ` +
`encryption: ${encryption}, ` +
`encryptionPassword: ********, ` +
`prompt: ${prompt} }`,
);
@ -861,7 +865,7 @@ export class UsersService {
prompt,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -896,7 +900,9 @@ export class UsersService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.updateUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.updateUser.name}`,
);
}
}
@ -912,7 +918,9 @@ export class UsersService {
newLicenseId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.allocateLicense.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.allocateLicense.name
} | params: { ` +
`userId: ${userId}, ` +
`newLicenseId: ${newLicenseId}, };`,
);
@ -927,7 +935,7 @@ export class UsersService {
accountId,
);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseExpiredError:
@ -949,7 +957,7 @@ export class UsersService {
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.allocateLicense.name}`,
`[OUT] [${context.getTrackingId()}] ${this.allocateLicense.name}`,
);
}
}
@ -961,8 +969,9 @@ export class UsersService {
*/
async deallocateLicense(context: Context, userId: number): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deallocateLicense.name} | params: { ` +
`userId: ${userId}, };`,
`[IN] [${context.getTrackingId()}] ${
this.deallocateLicense.name
} | params: { ` + `userId: ${userId}, };`,
);
try {
@ -971,7 +980,7 @@ export class UsersService {
await this.licensesRepository.deallocateLicense(userId, accountId);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case LicenseAlreadyDeallocatedError:
@ -988,7 +997,7 @@ export class UsersService {
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deallocateLicense.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deallocateLicense.name}`,
);
}
}
@ -1007,7 +1016,9 @@ export class UsersService {
dpaVersion?: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateAcceptedVersion.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateAcceptedVersion.name
} | params: { ` +
`externalId: ${externalId}, ` +
`eulaVersion: ${eulaVersion}, ` +
`dpaVersion: ${dpaVersion}, };`,
@ -1020,7 +1031,7 @@ export class UsersService {
dpaVersion,
);
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -1047,7 +1058,7 @@ export class UsersService {
}
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateAcceptedVersion.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateAcceptedVersion.name}`,
);
}
}
@ -1057,16 +1068,20 @@ export class UsersService {
* @param externalId
*/
async getUserName(context: Context, externalId: string): Promise<string> {
this.logger.log(`[IN] [${context.trackingId}] ${this.getUserName.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getUserName.name
} | params: { externalId: ${externalId} };`,
);
try {
// extarnalIdの存在チェックを行う
await this.usersRepository.findUserByExternalId(externalId);
// ADB2Cからユーザー名を取得する
const adb2cUser = await this.adB2cService.getUser(externalId);
const adb2cUser = await this.adB2cService.getUser(context, externalId);
return adb2cUser.displayName;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -1086,7 +1101,9 @@ export class UsersService {
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getUserName.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUserName.name}`,
);
}
}
}

View File

@ -588,6 +588,56 @@ describe('createWorkflows', () => {
}
});
it('Authorがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {
external_id: 'author1',
author_id: 'AUTHOR1',
account_id: account.id,
role: USER_ROLES.AUTHOR,
email_verified: false,
});
const { id: typistId } = await makeTestUser(source, {
external_id: 'typist1',
account_id: account.id,
role: USER_ROLES.TYPIST,
});
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
const workflowTypists = await getAllWorkflowTypists(source);
expect(workflows.length).toBe(0);
expect(workflowTypists.length).toBe(0);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.createWorkflow(
context,
admin.external_id,
authorId,
[{ typistId: typistId }],
undefined,
undefined,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('DBにAuthorが存在しない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -639,6 +689,7 @@ describe('createWorkflows', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -699,6 +750,7 @@ describe('createWorkflows', () => {
9999,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -758,6 +810,7 @@ describe('createWorkflows', () => {
worktypeId,
9999,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -819,6 +872,75 @@ describe('createWorkflows', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('ルーティング候補ユーザーがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId } = await makeTestUser(source, {
external_id: 'author1',
author_id: 'AUTHOR1',
account_id: account.id,
role: USER_ROLES.AUTHOR,
});
const { id: typistId } = await makeTestUser(source, {
external_id: 'typist1',
account_id: account.id,
role: USER_ROLES.TYPIST,
email_verified: false,
});
const { id: worktypeId } = await createWorktype(
source,
account.id,
'worktype1',
);
const { id: templateId } = await createTemplateFile(
source,
account.id,
'fileName1',
'url1',
);
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
const workflowTypists = await getAllWorkflowTypists(source);
expect(workflows.length).toBe(0);
expect(workflowTypists.length).toBe(0);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.createWorkflow(
context,
admin.external_id,
authorId,
[
{
typistId: typistId,
},
],
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -880,6 +1002,7 @@ describe('createWorkflows', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -950,6 +1073,7 @@ describe('createWorkflows', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1024,6 +1148,7 @@ describe('createWorkflows', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);
@ -1514,6 +1639,78 @@ describe('updateWorkflow', () => {
}
});
it('Authorがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId1 } = await makeTestUser(source, {
external_id: 'author1',
author_id: 'AUTHOR1',
account_id: account.id,
role: USER_ROLES.AUTHOR,
});
const { id: authorId2 } = await makeTestUser(source, {
external_id: 'author2',
author_id: 'AUTHOR2',
account_id: account.id,
role: USER_ROLES.AUTHOR,
email_verified: false,
});
const { id: typistId1 } = await makeTestUser(source, {
external_id: 'typist1',
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const preWorkflow = await createWorkflow(
source,
account.id,
authorId1,
undefined,
undefined,
);
await createWorkflowTypist(source, preWorkflow.id, typistId1);
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
const workflowTypists = await getAllWorkflowTypists(source);
expect(workflows.length).toBe(1);
expect(workflows[0].id).toBe(preWorkflow.id);
expect(workflows[0].account_id).toBe(account.id);
expect(workflows[0].author_id).toBe(authorId1);
expect(workflows[0].worktype_id).toBe(null);
expect(workflows[0].template_id).toBe(null);
expect(workflowTypists.length).toBe(1);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.updateWorkflow(
context,
admin.external_id,
preWorkflow.id,
authorId2,
[{ typistId: typistId1 }],
undefined,
undefined,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('DBにWorkflowが存在しない場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
@ -1550,6 +1747,7 @@ describe('updateWorkflow', () => {
undefined,
undefined,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1623,6 +1821,7 @@ describe('updateWorkflow', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1691,6 +1890,7 @@ describe('updateWorkflow', () => {
9999,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1758,6 +1958,7 @@ describe('updateWorkflow', () => {
worktypeId,
9999,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1832,6 +2033,88 @@ describe('updateWorkflow', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
expect(e.getResponse()).toEqual(makeErrorResponse('E010204'));
} else {
fail();
}
}
});
it('Dルーティング候補ユーザーがメール未認証の場合、400エラーとなること', async () => {
if (!source) fail();
const module = await makeTestingModule(source);
if (!module) fail();
// 第五階層のアカウント作成
const { account, admin } = await makeTestAccount(source, { tier: 5 });
const { id: authorId1 } = await makeTestUser(source, {
external_id: 'author1',
author_id: 'AUTHOR1',
account_id: account.id,
role: USER_ROLES.AUTHOR,
});
const { id: typistId1 } = await makeTestUser(source, {
external_id: 'typist1',
account_id: account.id,
role: USER_ROLES.TYPIST,
});
const { id: typistId2 } = await makeTestUser(source, {
external_id: 'typist2',
account_id: account.id,
role: USER_ROLES.TYPIST,
email_verified: false,
});
const { id: worktypeId } = await createWorktype(
source,
account.id,
'worktype1',
);
const { id: templateId } = await createTemplateFile(
source,
account.id,
'fileName1',
'url1',
);
const preWorkflow = await createWorkflow(
source,
account.id,
authorId1,
undefined,
undefined,
);
await createWorkflowTypist(source, preWorkflow.id, typistId1);
//作成したデータを確認
{
const workflows = await getWorkflows(source, account.id);
expect(workflows.length).toBe(1);
}
const service = module.get<WorkflowsService>(WorkflowsService);
const context = makeContext(admin.external_id);
//実行結果を確認
try {
await service.updateWorkflow(
context,
admin.external_id,
preWorkflow.id,
authorId1,
[
{
typistId: typistId2,
},
],
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1906,6 +2189,7 @@ describe('updateWorkflow', () => {
worktypeId,
templateId,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -1974,6 +2258,7 @@ describe('updateWorkflow', () => {
worktypeId1,
undefined,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.BAD_REQUEST);
@ -2050,6 +2335,7 @@ describe('updateWorkflow', () => {
undefined,
undefined,
);
fail();
} catch (e) {
if (e instanceof HttpException) {
expect(e.getStatus()).toEqual(HttpStatus.INTERNAL_SERVER_ERROR);

View File

@ -35,7 +35,9 @@ export class WorkflowsService {
externalId: string,
): Promise<Workflow[]> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.getWorkflows.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.getWorkflows.name
} | params: { externalId: ${externalId} };`,
);
try {
const { account_id: accountId } =
@ -121,14 +123,14 @@ export class WorkflowsService {
return workflows;
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw new HttpException(
makeErrorResponse('E009999'),
HttpStatus.INTERNAL_SERVER_ERROR,
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.getWorkflows.name}`,
`[OUT] [${context.getTrackingId()}] ${this.getWorkflows.name}`,
);
}
}
@ -152,7 +154,9 @@ export class WorkflowsService {
templateId?: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createWorkflow.name} | | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.createWorkflow.name
} | params: { ` +
`externalId: ${externalId}, ` +
`authorId: ${authorId}, ` +
`worktypeId: ${worktypeId}, ` +
@ -171,7 +175,7 @@ export class WorkflowsService {
templateId,
);
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -212,7 +216,7 @@ export class WorkflowsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createWorkflow.name}`,
`[OUT] [${context.getTrackingId()}] ${this.createWorkflow.name}`,
);
}
}
@ -237,7 +241,9 @@ export class WorkflowsService {
templateId?: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.updateWorkflow.name} | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.updateWorkflow.name
} | params: { ` +
`externalId: ${externalId}, ` +
`workflowId: ${workflowId}, ` +
`authorId: ${authorId}, ` +
@ -258,7 +264,7 @@ export class WorkflowsService {
templateId,
);
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case WorkflowNotFoundError:
@ -304,7 +310,7 @@ export class WorkflowsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.updateWorkflow.name}`,
`[OUT] [${context.getTrackingId()}] ${this.updateWorkflow.name}`,
);
}
}
@ -322,7 +328,9 @@ export class WorkflowsService {
workflowId: number,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteWorkflow.name} | | params: { ` +
`[IN] [${context.getTrackingId()}] ${
this.deleteWorkflow.name
} | params: { ` +
`externalId: ${externalId}, ` +
`workflowId: ${workflowId} };`,
);
@ -339,7 +347,7 @@ export class WorkflowsService {
await this.workflowsRepository.deleteWorkflow(account.id, workflowId);
} catch (e) {
this.logger.error(`[${context.trackingId}] error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e instanceof Error) {
switch (e.constructor) {
case UserNotFoundError:
@ -370,7 +378,7 @@ export class WorkflowsService {
);
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deleteWorkflow.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deleteWorkflow.name}`,
);
}
}

View File

@ -1,7 +1,7 @@
import { ClientSecretCredential } from '@azure/identity';
import { Client } from '@microsoft/microsoft-graph-client';
import { TokenCredentialAuthenticationProvider } from '@microsoft/microsoft-graph-client/authProviders/azureTokenCredentials';
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import axios from 'axios';
import { B2cMetadata, JwkSignKey } from '../../common/token';
@ -71,7 +71,9 @@ export class AdB2cService {
password: string,
username: string,
): Promise<{ sub: string } | ConflictError> {
this.logger.log(`[IN] [${context.trackingId}] ${this.createUser.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.createUser.name}`,
);
try {
// ユーザをADB2Cに登録
const newUser = await this.graphClient.api('users/').post({
@ -92,7 +94,7 @@ export class AdB2cService {
});
return { sub: newUser.id };
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
if (e?.statusCode === 400 && e?.body) {
const error = JSON.parse(e.body);
@ -104,7 +106,9 @@ export class AdB2cService {
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.createUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.createUser.name}`,
);
}
}
@ -112,8 +116,10 @@ export class AdB2cService {
* ADB2Cのメタデータを取得する
* @returns meta data
*/
async getMetaData(): Promise<B2cMetadata> {
this.logger.log(`[IN] ${this.getMetaData.name}`);
async getMetaData(context: Context): Promise<B2cMetadata> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getMetaData.name}`,
);
try {
// Azure AD B2Cのメタデータを取得する。 以下のURLから取得できる。
// https://<テナント名>.b2clogin.com/<テナント名>.onmicrosoft.com/<ユーザーフロー名>/v2.0/.well-known/openid-configuration
@ -125,10 +131,12 @@ export class AdB2cService {
return metaData;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] ${this.getMetaData.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getMetaData.name}`,
);
}
}
@ -136,8 +144,10 @@ export class AdB2cService {
* IDトークンの署名キーセットを取得する
* @returns sign key sets
*/
async getSignKeySets(): Promise<JwkSignKey[]> {
this.logger.log(`[IN] ${this.getSignKeySets.name}`);
async getSignKeySets(context: Context): Promise<JwkSignKey[]> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getSignKeySets.name}`,
);
try {
// 署名キーのキーセット配列を取得する。 以下のURLから取得できる。
const keySets = await axios
@ -148,10 +158,12 @@ export class AdB2cService {
return keySets;
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] ${this.getSignKeySets.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getSignKeySets.name}`,
);
}
}
@ -160,8 +172,16 @@ export class AdB2cService {
* @param externalId
* @param password
*/
async changePassword(externalId: string, password: string): Promise<void> {
this.logger.log(`[IN] ${this.changePassword.name}`);
async changePassword(
context: Context,
externalId: string,
password: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.changePassword.name
} | params: { ` + `externalId: ${externalId}, };`,
);
try {
// ADB2Cのユーザのパスワードを変更する
await this.graphClient.api(`/users/${externalId}`).patch({
@ -171,10 +191,12 @@ export class AdB2cService {
},
});
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] ${this.changePassword.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.changePassword.name}`,
);
}
}
@ -183,16 +205,21 @@ export class AdB2cService {
* @param externalId ID
* @returns
*/
async getUser(externalId: string): Promise<AdB2cUser> {
this.logger.log(`[IN] ${this.getUser.name}`);
async getUser(context: Context, externalId: string): Promise<AdB2cUser> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.getUser.name} | params: { ` +
`externalId: ${externalId} };`,
);
try {
const key = makeADB2CKey(externalId);
// キャッシュ上に存在していれば、キャッシュから取得する
const cachedUser = await this.redisService.get<AdB2cUser>(key);
const cachedUser = await this.redisService.get<AdB2cUser>(context, key);
if (cachedUser) {
this.logger.log(`[CACHE HIT] id: ${externalId}`);
this.logger.log(
`[${context.getTrackingId()}] [CACHE HIT] id: ${externalId}`,
);
return cachedUser;
}
@ -201,15 +228,19 @@ export class AdB2cService {
.api(`users/${externalId}`)
.select(['id', 'displayName', 'identities'])
.get();
await this.redisService.set(key, user, this.ttl);
this.logger.log(`[ADB2C GET] externalId: ${externalId}`);
await this.redisService.set(context, key, user, this.ttl);
this.logger.log(
`[${context.getTrackingId()}] [ADB2C GET] externalId: ${externalId}`,
);
return user;
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] ${this.getUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUser.name}`,
);
}
}
/**
@ -222,19 +253,21 @@ export class AdB2cService {
externalIds: string[],
): Promise<AdB2cUser[]> {
this.logger.log(
`[IN] [${context.trackingId}] ${
`[IN] [${context.getTrackingId()}] ${
this.getUsers.name
} | params: { externalIds:[${externalIds.join(',')}] };`,
);
const keys = externalIds.map((externalId) => makeADB2CKey(externalId));
const cache = await this.redisService.mget<AdB2cUser>(keys);
const cache = await this.redisService.mget<AdB2cUser>(context, keys);
// キャッシュ上に存在していれば、キャッシュから取得する
const cachedUsers = cache.flatMap((x) => (x.value ? [x.value] : []));
if (cachedUsers.length > 0) {
this.logger.log(
`[CACHE HIT] ids: ${cachedUsers.map((x) => x.id).join(',')}`,
`[${context.getTrackingId()}] [CACHE HIT] ids: ${cachedUsers
.map((x) => x.id)
.join(',')}`,
);
}
@ -265,15 +298,17 @@ export class AdB2cService {
value: user,
};
});
await this.redisService.mset(users, this.ttl);
await this.redisService.mset(context, users, this.ttl);
this.logger.log(
`[ADB2C GET] externalIds: ${res.value?.map((x) => x.id).join(',')}`,
`[${context.getTrackingId()}] [ADB2C GET] externalIds: ${res.value
?.map((x) => x.id)
.join(',')}`,
);
}
return [...cachedUsers, ...b2cUsers];
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
const { statusCode } = e;
if (statusCode === 429) {
throw new Adb2cTooManyRequestsError();
@ -281,7 +316,9 @@ export class AdB2cService {
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.getUsers.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.getUsers.name}`,
);
}
}
/**
@ -291,26 +328,32 @@ export class AdB2cService {
*/
async deleteUser(externalId: string, context: Context): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteUser.name} | params: { externalId: ${externalId} };`,
`[IN] [${context.getTrackingId()}] ${
this.deleteUser.name
} | params: { externalId: ${externalId} };`,
);
try {
// https://learn.microsoft.com/en-us/graph/api/user-delete?view=graph-rest-1.0&tabs=javascript#example
await this.graphClient.api(`users/${externalId}`).delete();
this.logger.log(`[ADB2C DELETE] externalId: ${externalId}`);
this.logger.log(
`[${context.getTrackingId()}] [ADB2C DELETE] externalId: ${externalId}`,
);
// キャッシュからも削除する
try {
await this.redisService.del(makeADB2CKey(externalId));
await this.redisService.del(context, makeADB2CKey(externalId));
} catch (e) {
// キャッシュからの削除に失敗しても、ADB2Cからの削除は成功しているため例外はスローしない
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.deleteUser.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteUser.name}`,
);
}
}
@ -321,7 +364,9 @@ export class AdB2cService {
*/
async deleteUsers(externalIds: string[], context: Context): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteUsers.name} | params: { externalIds: ${externalIds} };`,
`[IN] [${context.getTrackingId()}] ${
this.deleteUsers.name
} | params: { externalIds: ${externalIds} };`,
);
try {
@ -329,14 +374,16 @@ export class AdB2cService {
const results = await Promise.allSettled(
externalIds.map(async (externalId) => {
await this.graphClient.api(`users/${externalId}`).delete();
this.logger.log(`[ADB2C DELETE] externalId: ${externalId}`);
this.logger.log(
`[${context.getTrackingId()}] [ADB2C DELETE] externalId: ${externalId}`,
);
// キャッシュからも削除する
try {
await this.redisService.del(makeADB2CKey(externalId));
await this.redisService.del(context, makeADB2CKey(externalId));
} catch (e) {
// キャッシュからの削除に失敗しても、ADB2Cからの削除は成功しているため例外はスローしない
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
}),
);
@ -353,19 +400,21 @@ export class AdB2cService {
const error = result.reason.toString();
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED}[${context.trackingId}] Failed to delete user ${failedId}: ${error}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user ${failedId}: ${error}`,
);
} else {
this.logger.error(
`${MANUAL_RECOVERY_REQUIRED}[${context.trackingId}] Failed to delete user ${failedId}`,
`${MANUAL_RECOVERY_REQUIRED} [${context.getTrackingId()}] Failed to delete user ${failedId}`,
);
}
});
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.deleteUsers.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.deleteUsers.name}`,
);
}
}
}

View File

@ -69,21 +69,27 @@ export class BlobstorageService {
country: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createContainer.name}`,
`[IN] [${context.getTrackingId()}] ${
this.createContainer.name
} | params: { ` + `accountId: ${accountId} };`,
);
// 国に応じたリージョンでコンテナ名を指定してClientを取得
const containerClient = this.getContainerClient(accountId, country);
const containerClient = this.getContainerClient(
context,
accountId,
country,
);
try {
// コンテナ作成
await containerClient.create();
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createContainer.name}`,
`[OUT] [${context.getTrackingId()}] ${this.createContainer.name}`,
);
}
}
@ -100,16 +106,22 @@ export class BlobstorageService {
country: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.deleteContainer.name}`,
`[IN] [${context.getTrackingId()}] ${
this.deleteContainer.name
} | params: { ` + `accountId: ${accountId} };`,
);
try {
// 国に応じたリージョンでコンテナ名を指定してClientを取得
const containerClient = this.getContainerClient(accountId, country);
const containerClient = this.getContainerClient(
context,
accountId,
country,
);
const { succeeded, errorCode, date } =
await containerClient.deleteIfExists();
this.logger.log(
`succeeded: ${succeeded}, errorCode: ${errorCode}, date: ${date}`,
`[${context.getTrackingId()}] succeeded: ${succeeded}, errorCode: ${errorCode}, date: ${date}`,
);
// 失敗時、コンテナが存在しない場合以外はエラーとして例外をスローする
@ -121,11 +133,11 @@ export class BlobstorageService {
);
}
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.deleteContainer.name}`,
`[OUT] [${context.getTrackingId()}] ${this.deleteContainer.name}`,
);
}
}
@ -142,14 +154,20 @@ export class BlobstorageService {
country: string,
): Promise<boolean> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.containerExists.name}`,
`[IN] [${context.getTrackingId()}] ${
this.containerExists.name
} | params: { ` + `accountId: ${accountId} };`,
);
// 国に応じたリージョンでコンテナ名を指定してClientを取得
const containerClient = this.getContainerClient(accountId, country);
const containerClient = this.getContainerClient(
context,
accountId,
country,
);
const exists = await containerClient.exists();
this.logger.log(
`[OUT] [${context.trackingId}] ${this.containerExists.name}`,
`[OUT] [${context.getTrackingId()}] ${this.containerExists.name}`,
);
return exists;
}
@ -169,13 +187,23 @@ export class BlobstorageService {
country: string,
filePath: string,
): Promise<boolean> {
this.logger.log(`[IN] [${context.trackingId}] ${this.fileExists.name}`);
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.fileExists.name} | params: { ` +
`accountId: ${accountId},` +
`filePath: ${filePath} };`,
);
const containerClient = this.getContainerClient(accountId, country);
const containerClient = this.getContainerClient(
context,
accountId,
country,
);
const blob = containerClient.getBlobClient(`${filePath}`);
const exists = await blob.exists();
this.logger.log(`[OUT] [${context.trackingId}] ${this.fileExists.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.fileExists.name}`,
);
return exists;
}
@ -191,19 +219,21 @@ export class BlobstorageService {
country: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishUploadSas.name}`,
`[IN] [${context.getTrackingId()}] ${
this.publishUploadSas.name
} | params: { ` + `accountId: ${accountId} };`,
);
let containerClient: ContainerClient;
let sharedKeyCredential: StorageSharedKeyCredential;
try {
// コンテナ名を指定してClientを取得
containerClient = this.getContainerClient(accountId, country);
containerClient = this.getContainerClient(context, accountId, country);
// 国に対応したリージョンの接続情報を取得する
sharedKeyCredential = this.getSharedKeyCredential(country);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishUploadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${this.publishUploadSas.name}`,
);
throw e;
}
@ -231,7 +261,7 @@ export class BlobstorageService {
url.search = `${sasToken}`;
this.logger.log(
`[OUT] [${context.trackingId}] ${
`[OUT] [${context.getTrackingId()}] ${
this.publishUploadSas.name
} url=${url.toString()}`,
);
@ -250,12 +280,18 @@ export class BlobstorageService {
country: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishTemplateUploadSas.name}`,
`[IN] [${context.getTrackingId()}] ${
this.publishTemplateUploadSas.name
} | params: { ` + `accountId: ${accountId} };`,
);
try {
// コンテナ名を指定してClientを取得
const containerClient = this.getContainerClient(accountId, country);
const containerClient = this.getContainerClient(
context,
accountId,
country,
);
// 国に対応したリージョンの接続情報を取得する
const sharedKeyCredential = this.getSharedKeyCredential(country);
//SASの有効期限を設定
@ -282,11 +318,13 @@ export class BlobstorageService {
return url.toString();
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishTemplateUploadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.publishTemplateUploadSas.name
}`,
);
}
}
@ -306,22 +344,26 @@ export class BlobstorageService {
filePath: string,
): Promise<string> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.publishDownloadSas.name}`,
`[IN] [${context.getTrackingId()}] ${
this.publishDownloadSas.name
}| params: { ` +
`accountId: ${accountId},` +
`filePath: ${filePath} };`,
);
let containerClient: ContainerClient;
let blobClient: BlobClient;
let sharedKeyCredential: StorageSharedKeyCredential;
try {
// コンテナ名を指定してClientを取得
containerClient = this.getContainerClient(accountId, country);
containerClient = this.getContainerClient(context, accountId, country);
// コンテナ内のBlobパス名を指定してClientを取得
blobClient = containerClient.getBlobClient(`${filePath}`);
// 国に対応したリージョンの接続情報を取得する
sharedKeyCredential = this.getSharedKeyCredential(country);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
this.logger.log(
`[OUT] [${context.trackingId}] ${this.publishDownloadSas.name}`,
`[OUT] [${context.getTrackingId()}] ${this.publishDownloadSas.name}`,
);
throw e;
}
@ -350,7 +392,7 @@ export class BlobstorageService {
url.search = `${sasToken}`;
this.logger.log(
`[OUT] [${context.trackingId}] ${
`[OUT] [${context.getTrackingId()}] ${
this.publishDownloadSas.name
}, url=${url.toString()}`,
);
@ -363,9 +405,16 @@ export class BlobstorageService {
* @returns container client
*/
private getContainerClient(
context: Context,
accountId: number,
country: string,
): ContainerClient {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.getContainerClient.name
} | params: { ` + `accountId: ${accountId} };`,
);
const containerName = `account-${accountId}`;
if (BLOB_STORAGE_REGION_US.includes(country)) {
return this.blobServiceClientUS.getContainerClient(containerName);

View File

@ -5,13 +5,15 @@ import {
createAppleNotificationBody,
createAppleNotification,
createTagExpression,
createWindowsToastNotification,
createWindowsInstallation,
createAppleInstallation,
createWindowsRawNotification,
} from '@azure/notification-hubs';
import { TAG_MAX_COUNT } from '../../constants';
import { PNS } from '../../constants';
import { Context } from '../../common/log';
import { NotificationBody } from '../../common/notify/types/types';
@Injectable()
export class NotificationhubService {
private readonly logger = new Logger(NotificationhubService.name);
@ -37,7 +39,9 @@ export class NotificationhubService {
installationId: string,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.register.name} | params: { userId: ${userId}, pns: ${pns}, pnsHandler: ${pnsHandler}, installationId: ${installationId} }`,
`[IN] [${context.getTrackingId()}] ${
this.register.name
} | params: { userId: ${userId}, pns: ${pns}, pnsHandler: ${pnsHandler}, installationId: ${installationId} }`,
);
const tag = `user_${userId}`;
@ -67,26 +71,33 @@ export class NotificationhubService {
throw new Error('invalid pns');
}
} catch (e) {
this.logger.error(`error=${e.message}`);
this.logger.error(`[${context.getTrackingId()}] error=${e.message}`);
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.register.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.register.name}`,
);
}
}
/**
*
* @param context
* @param tags
* @param message
* @param bodyContent
* @returns notify
*/
async notify(
context: Context,
tags: string[],
message: string,
bodyContent: NotificationBody,
): Promise<void> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.notify.name} | params: { tags: ${tags}, message: ${message} }`,
`[IN] [${context.getTrackingId()}] ${
this.notify.name
} | params: { tags: ${tags}, bodyContent: ${JSON.stringify(
bodyContent,
)} }`,
);
try {
@ -99,31 +110,43 @@ export class NotificationhubService {
// Windows
try {
const body = `<toast><visual><binding template="ToastText01"><text id="1">${message}</text></binding></visual></toast>`;
const notification = createWindowsToastNotification({ body });
const body = {
wns: {
alert: '',
},
newDictation: bodyContent,
};
const notification = createWindowsRawNotification({
body: JSON.stringify(body),
});
const result = await this.client.sendNotification(notification, {
tagExpression,
});
this.logger.log(result);
this.logger.log(`[${context.getTrackingId()}] ${result}`);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
// Apple
try {
const body = createAppleNotificationBody({ aps: { alert: message } });
const body = createAppleNotificationBody({
aps: {
alert: '',
},
newDictation: bodyContent,
});
const notification = createAppleNotification({ body });
const result = await this.client.sendNotification(notification, {
tagExpression,
});
this.logger.log(result);
this.logger.log(`[${context.getTrackingId()}] ${result}`);
} catch (e) {
this.logger.error(`error=${e}`);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
}
}
} catch (e) {
throw e;
} finally {
this.logger.log(`[OUT] ${this.notify.name}`);
this.logger.log(`[OUT] [${context.getTrackingId()}] ${this.notify.name}`);
}
}
}

View File

@ -1,11 +1,6 @@
import {
CACHE_MANAGER,
Inject,
Injectable,
InternalServerErrorException,
Logger,
} from '@nestjs/common';
import { CACHE_MANAGER, Inject, Injectable, Logger } from '@nestjs/common';
import { Cache } from 'cache-manager';
import { Context } from '../../common/log';
@Injectable()
export class RedisService {
@ -20,10 +15,17 @@ export class RedisService {
* @param ttl
*/
async set(
context: Context,
key: string,
value: unknown,
ttl?: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.set.name} | params: { ` +
`ttl: ${ttl}
};`,
);
try {
// cache-manager-redis-store がcache-managerのset形式と不一致な値の渡し方を採用しているため、
// @types/cache-managerのset形式を使用すると、redisに値が保存されない。
@ -31,7 +33,7 @@ export class RedisService {
// https://www.npmjs.com/package/cache-manager
await this.cacheManager.set(key, value, { ttl: ttl } as any);
} catch (error) {
this.logger.error(error);
this.logger.error(`[${context.getTrackingId()}] ${error}`);
}
}
@ -42,17 +44,24 @@ export class RedisService {
* @param ttl
*/
async mset<T>(
context: Context,
records: { key: string; value: T }[],
ttl?: number | undefined,
): Promise<void> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${this.mset.name} | params: { ` +
`ttl: ${ttl}
};`,
);
try {
// cache-manager-redis-store のmsetが壊れており、利用できないため、
// 一つずつsetする。
for await (const record of records) {
await this.set(record.key, record.value, ttl);
await this.set(context, record.key, record.value, ttl);
}
} catch (error) {
this.logger.error(error);
this.logger.error(`[${context.getTrackingId()}] ${error}`);
}
}
@ -62,12 +71,14 @@ export class RedisService {
* @param key
* @returns
*/
async get<T>(key: string): Promise<T | undefined> {
async get<T>(context: Context, key: string): Promise<T | undefined> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.get.name};`);
try {
const value = await this.cacheManager.get<T>(key);
return value;
} catch (error) {
this.logger.error(error);
this.logger.error(`[${context.getTrackingId()}] ${error}`);
return undefined;
}
}
@ -78,7 +89,12 @@ export class RedisService {
* @param keys
* @returns
*/
async mget<T>(keys: string[]): Promise<{ key: string; value: T | null }[]> {
async mget<T>(
context: Context,
keys: string[],
): Promise<{ key: string; value: T | null }[]> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.mget.name};`);
if (keys.length === 0) return []; // mget操作は0件の時エラーとなるため、0件は特別扱いする
try {
@ -91,7 +107,7 @@ export class RedisService {
};
});
} catch (error) {
this.logger.error(error);
this.logger.error(`[${context.getTrackingId()}] ${error}`);
return [];
}
}
@ -100,11 +116,13 @@ export class RedisService {
*
* @param key
*/
async del(key: string): Promise<void> {
async del(context: Context, key: string): Promise<void> {
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.del.name};`);
try {
await this.cacheManager.del(key);
} catch (error) {
this.logger.error(error);
this.logger.error(`[${context.getTrackingId()}] ${error}`);
}
}
}

View File

@ -23,7 +23,7 @@ export class SendGridService {
* Email認証用のメールコンテンツを作成する
* @param accountId ID
* @param userId ID
* @param email
* @param email
* @returns
*/
async createMailContentFromEmailConfirm(
@ -33,7 +33,11 @@ export class SendGridService {
email: string,
): Promise<{ subject: string; text: string; html: string }> {
this.logger.log(
`[IN] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`,
`[IN] [${context.getTrackingId()}] ${
this.createMailContentFromEmailConfirm.name
} | params: { ` +
`accountId: ${accountId},` +
`userId: ${userId} };`,
);
const privateKey = getPrivateKey(this.configService);
@ -49,7 +53,9 @@ export class SendGridService {
const path = 'mail-confirm/';
this.logger.log(
`[OUT] [${context.trackingId}] ${this.createMailContentFromEmailConfirm.name}`,
`[OUT] [${context.getTrackingId()}] ${
this.createMailContentFromEmailConfirm.name
}`,
);
return {
subject: 'Verify your new account',
@ -62,15 +68,23 @@ export class SendGridService {
* Email認証用のメールコンテンツを作成する()
* @param accountId ID
* @param userId ID
* @param email
* @param email
* @returns
*/
//TODO [Task2163] 中身が管理ユーザ向けのままなので、修正の必要あり
async createMailContentFromEmailConfirmForNormalUser(
context: Context,
accountId: number,
userId: number,
email: string,
): Promise<{ subject: string; text: string; html: string }> {
this.logger.log(
`[IN] [${context.getTrackingId()}] ${
this.createMailContentFromEmailConfirmForNormalUser.name
} | params: { ` +
`accountId: ${accountId},` +
`userId: ${userId} };`,
);
const privateKey = getPrivateKey(this.configService);
const token = sign<{ accountId: number; userId: number; email: string }>(
@ -109,7 +123,7 @@ export class SendGridService {
text: string,
html: string,
): Promise<void> {
this.logger.log(`[IN] [${context.trackingId}] ${this.sendMail.name}`);
this.logger.log(`[IN] [${context.getTrackingId()}] ${this.sendMail.name}`);
try {
const res = await sendgrid
.send({
@ -125,13 +139,17 @@ export class SendGridService {
})
.then((v) => v[0]);
this.logger.log(
`status code: ${res.statusCode} body: ${JSON.stringify(res.body)}`,
`[${context.getTrackingId()}] status code: ${
res.statusCode
} body: ${JSON.stringify(res.body)}`,
);
} catch (e) {
this.logger.error(e);
this.logger.error(`[${context.getTrackingId()}] error=${e}`);
throw e;
} finally {
this.logger.log(`[OUT] [${context.trackingId}] ${this.sendMail.name}`);
this.logger.log(
`[OUT] [${context.getTrackingId()}] ${this.sendMail.name}`,
);
}
}
}

View File

@ -4,3 +4,5 @@ export class AccountNotFoundError extends Error {}
export class DealerAccountNotFoundError extends Error {}
// 管理者ユーザ未存在エラー
export class AdminUserNotFoundError extends Error {}
// アカウントロックエラー
export class AccountLockedError extends Error {}

View File

@ -33,3 +33,6 @@ export class CancellationPeriodExpiredError extends Error {}
// ライセンス発行キャンセル不可エラー(発行したライセンスが割り当てされている場合)
export class AlreadyLicenseAllocatedError extends Error {}
// ライセンス未割当エラー
export class LicenseNotAllocatedError extends Error {}

View File

@ -14,3 +14,5 @@ export class StatusNotMatchError extends Error {}
export class TypistUserNotMatchError extends Error {}
// Account不一致エラー
export class AccountNotMatchError extends Error {}
// タスクチェックアウト済みエラー
export class AlreadyHasInProgressTaskError extends Error {}

View File

@ -6,6 +6,7 @@ import {
FindOptionsOrderValue,
In,
IsNull,
Not,
Repository,
} from 'typeorm';
import { Task } from './entity/task.entity';
@ -26,6 +27,7 @@ import { UserGroup } from '../user_groups/entity/user_group.entity';
import { User } from '../users/entity/user.entity';
import {
AccountNotMatchError,
AlreadyHasInProgressTaskError,
CheckoutPermissionNotFoundError,
StatusNotMatchError,
TaskAuthorIdNotMatchError,
@ -163,6 +165,24 @@ export class TasksRepositoryService {
`task not found. audio_file_id:${audio_file_id}`,
);
}
// 指定したタスク以外に実行ユーザー担当のInprogressのタスクが存在する場合はエラー
const tasks = await taskRepo.find({
where: {
audio_file_id: Not(audio_file_id),
status: TASK_STATUS.IN_PROGRESS,
typist_user_id: user_id,
},
});
if (tasks.length > 0) {
throw new AlreadyHasInProgressTaskError(
`checkout task already exists. task:${tasks.map(
(x) => x.audio_file_id,
)}`,
);
}
// アカウントチェック
if (task.account_id !== account_id) {
throw new AccountNotMatchError(
@ -804,13 +824,14 @@ export class TasksRepositoryService {
id: In(typistUserIds),
account_id: account_id,
role: USER_ROLES.TYPIST,
email_verified: true,
deleted_at: IsNull(),
},
});
// idはユニークであるため取得件数の一致でユーザーの存在を確認
if (typistUserIds.length !== userRecords.length) {
throw new TypistUserNotFoundError(
`User not exists Error. reqUserId:${typistUserIds}; resUserId:${userRecords.map(
`User not exists or email not verified Error. reqUserId:${typistUserIds}; resUserId:${userRecords.map(
(x) => x.id,
)}`,
);

View File

@ -102,11 +102,12 @@ export class UserGroupsRepositoryService {
id: In(typistIds),
account_id: accountId,
role: USER_ROLES.TYPIST,
email_verified: true,
},
});
if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError(
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
`Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
(x) => x.id,
)}`,
);
@ -153,11 +154,12 @@ export class UserGroupsRepositoryService {
id: In(typistIds),
account_id: accountId,
role: USER_ROLES.TYPIST,
email_verified: true,
},
});
if (userRecords.length !== typistIds.length) {
throw new TypistIdInvalidError(
`Typist user not exists Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
`Typist user not exists or email not verified Error. typistIds:${typistIds}; typistIds(DB):${userRecords.map(
(x) => x.id,
)}`,
);

View File

@ -33,6 +33,8 @@ import {
AdminUserNotFoundError,
} from '../accounts/errors/types';
import { Account } from '../accounts/entity/account.entity';
import { Workflow } from '../workflows/entity/workflow.entity';
import { Worktype } from '../worktypes/entity/worktype.entity';
@Injectable()
export class UsersRepositoryService {
@ -358,13 +360,13 @@ export class UsersRepositoryService {
},
where: { account_id: accountId },
});
return dbUsers;
});
}
/**
*
*
* @param sub
* @returns typist users
*/
@ -387,6 +389,7 @@ export class UsersRepositoryService {
where: {
account_id: user.account_id,
role: USER_ROLES.TYPIST,
email_verified: true,
deleted_at: IsNull(),
},
});
@ -396,7 +399,7 @@ export class UsersRepositoryService {
}
/**
* Authorユーザーを取得する
* Email認証済みのAuthorユーザーを取得する
* @param accountId
* @returns author users
*/
@ -407,6 +410,7 @@ export class UsersRepositoryService {
where: {
account_id: accountId,
role: USER_ROLES.AUTHOR,
email_verified: true,
deleted_at: IsNull(),
},
});
@ -644,4 +648,100 @@ export class UsersRepositoryService {
return originAccount.delegation_permission;
});
}
/**
*
* @param userId ID
* @returns License
*/
async findLicenseByUserId(userId: number): Promise<License | null> {
const allocatedLicense = await this.dataSource
.getRepository(License)
.findOne({
where: {
allocated_user_id: userId,
status: LICENSE_ALLOCATED_STATUS.ALLOCATED,
},
});
return allocatedLicense;
}
/**
*
* @param userId
* @returns user relations
*/
async getUserRelations(userId: number): Promise<{
user: User;
authors: User[];
worktypes: Worktype[];
activeWorktype: Worktype | undefined;
}> {
return await this.dataSource.transaction(async (entityManager) => {
const userRepo = entityManager.getRepository(User);
const user = await userRepo.findOne({
where: { id: userId },
relations: { account: true },
});
if (!user) {
throw new UserNotFoundError(`User is Not Found. id: ${userId}`);
}
// 運用上、アカウントがいないことはあり得ないが、プログラム上発生しうるのでエラーとして処理
if (!user.account) {
throw new AccountNotFoundError(
`Account is Not Found. user.id: ${userId}`,
);
}
// ユーザーの所属するアカウント内のすべてのメール認証済みAuthorユーザーを取得する
const authors = await userRepo.find({
where: {
account_id: user.account_id,
role: USER_ROLES.AUTHOR,
email_verified: true,
},
});
// ユーザーの所属するアカウント内のアクティブワークタイプを取得する
const worktypeRepo = entityManager.getRepository(Worktype);
let activeWorktype: Worktype | undefined = undefined;
const activeWorktypeId = user.account.active_worktype_id;
if (activeWorktypeId !== null) {
activeWorktype =
(await worktypeRepo.findOne({
where: {
account_id: user.account_id,
id: activeWorktypeId,
},
})) ?? undefined;
}
let worktypes: Worktype[] = [];
// ユーザーのロールがAuthorの場合はルーティングルールに紐づいたワークタイプを取得する
if (user.role === USER_ROLES.AUTHOR) {
const workflowRepo = entityManager.getRepository(Workflow);
const workflows = await workflowRepo.find({
where: {
account_id: user.account_id,
author_id: user.id,
worktype_id: Not(IsNull()),
},
relations: {
worktype: {
option_items: true,
},
},
});
worktypes = workflows.flatMap((workflow) =>
workflow.worktype ? [workflow.worktype] : [],
);
}
return { user, authors, worktypes, activeWorktype };
});
}
}

View File

@ -69,10 +69,12 @@ export class WorkflowsRepositoryService {
// authorの存在確認
const userRepo = entityManager.getRepository(User);
const author = await userRepo.findOne({
where: { account_id: accountId, id: authorId },
where: { account_id: accountId, id: authorId, email_verified: true },
});
if (!author) {
throw new UserNotFoundError(`author not found. id: ${authorId}`);
throw new UserNotFoundError(
`author not found or email not verified. id: ${authorId}`,
);
}
// worktypeの存在確認
@ -104,10 +106,16 @@ export class WorkflowsRepositoryService {
typist.typistId ? [typist.typistId] : [],
);
const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) },
where: {
account_id: accountId,
id: In(typistIds),
email_verified: true,
},
});
if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError(`typist not found. ids: ${typistIds}`);
throw new UserNotFoundError(
`typist not found or email not verified. ids: ${typistIds}`,
);
}
// ルーティング候補ユーザーグループの存在確認
@ -198,10 +206,12 @@ export class WorkflowsRepositoryService {
// authorの存在確認
const userRepo = entityManager.getRepository(User);
const author = await userRepo.findOne({
where: { account_id: accountId, id: authorId },
where: { account_id: accountId, id: authorId, email_verified: true },
});
if (!author) {
throw new UserNotFoundError(`author not found. id: ${authorId}`);
throw new UserNotFoundError(
`author not found or email not verified. id: ${authorId}`,
);
}
// worktypeの存在確認
@ -235,10 +245,16 @@ export class WorkflowsRepositoryService {
typist.typistId ? [typist.typistId] : [],
);
const typistUsers = await userRepo.find({
where: { account_id: accountId, id: In(typistIds) },
where: {
account_id: accountId,
id: In(typistIds),
email_verified: true,
},
});
if (typistUsers.length !== typistIds.length) {
throw new UserNotFoundError(`typist not found. ids: ${typistIds}`);
throw new UserNotFoundError(
`typist not found or email not verified. ids: ${typistIds}`,
);
}
// ルーティング候補ユーザーグループの存在確認

View File

@ -4,7 +4,10 @@ import {
PrimaryGeneratedColumn,
UpdateDateColumn,
CreateDateColumn,
ManyToOne,
JoinColumn,
} from 'typeorm';
import { Worktype } from './worktype.entity';
@Entity({ name: 'option_items' })
export class OptionItem {
@ -32,4 +35,8 @@ export class OptionItem {
type: 'datetime',
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date | null;
@ManyToOne(() => Worktype, (worktype) => worktype.id)
@JoinColumn({ name: 'worktype_id' })
worktype: Worktype;
}

View File

@ -5,7 +5,9 @@ import {
PrimaryGeneratedColumn,
CreateDateColumn,
UpdateDateColumn,
OneToMany,
} from 'typeorm';
import { OptionItem } from './option_item.entity';
@Entity({ name: 'worktypes' })
export class Worktype {
@ -41,4 +43,7 @@ export class Worktype {
type: 'datetime',
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
updated_at: Date;
@OneToMany(() => OptionItem, (optionItem) => optionItem.worktype)
option_items: OptionItem[];
}