Merged PR 552: dictation_serverからソースコードを複製
## 概要 [Task2977: dictation_serverからソースコードを複製](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/2977) 新たに構築したdictation_functionで以下のことをできるようにしました。 ・RDBへの接続 ・sendgridでのメール送信 ・jestでのテスト実行 ※とりあえず動くことを目標としているため、DB接続のパラメータやsendgridのAPIキーなどがベタ打ちになっています。機能実装時には外出ししたファイルから読み込めるようにします。 ## レビューポイント ・フォルダ構成は適切か ・RDB、sendgrid、jestに関するもので不足しているものがないか ・今後機能開発を始めるにあたり、他に必要なものがないか ## UIの変更 なし ## 動作確認状況 ローカルでjestによるテストを実施し、以下を確認 ・RDBからデータが取得できる ・メールが送信され、設定したアドレスで受信できる ## 補足 なし
This commit is contained in:
parent
1e4a545bf8
commit
86d17d6729
@ -14,6 +14,12 @@ services:
|
||||
- "8082"
|
||||
environment:
|
||||
- CHOKIDAR_USEPOLLING=true
|
||||
networks:
|
||||
- external
|
||||
networks:
|
||||
external:
|
||||
name: omds_network
|
||||
external: true
|
||||
|
||||
# Data Volume として永続化する
|
||||
volumes:
|
||||
|
||||
5
dictation_function/.env
Normal file
5
dictation_function/.env
Normal file
@ -0,0 +1,5 @@
|
||||
DB_HOST=omds-mysql
|
||||
DB_PORT=3306
|
||||
DB_NAME=omds
|
||||
DB_USERNAME=omdsdbuser
|
||||
DB_PASSWORD=omdsdbpass
|
||||
7
dictation_function/.gitignore
vendored
7
dictation_function/.gitignore
vendored
@ -29,7 +29,6 @@ dist
|
||||
.python_packages/
|
||||
|
||||
# Python Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
@ -45,4 +44,8 @@ __pycache__/
|
||||
# Azurite artifacts
|
||||
__blobstorage__
|
||||
__queuestorage__
|
||||
__azurite_db*__.json
|
||||
__azurite_db*__.json
|
||||
|
||||
# credentials
|
||||
credentials
|
||||
.env.local
|
||||
6429
dictation_function/package-lock.json
generated
6429
dictation_function/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -9,15 +9,41 @@
|
||||
"clean": "rimraf dist",
|
||||
"prestart": "npm run clean && npm run build",
|
||||
"start": "func start",
|
||||
"test": "echo \"No tests yet...\""
|
||||
"test": "jest"
|
||||
},
|
||||
"dependencies": {
|
||||
"@azure/functions": "^4.0.0"
|
||||
"@azure/functions": "^4.0.0",
|
||||
"@sendgrid/mail": "^7.7.0",
|
||||
"dotenv": "^16.0.3",
|
||||
"mysql2": "^2.3.3",
|
||||
"typeorm": "^0.3.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
"azure-functions-core-tools": "^4.x",
|
||||
"@types/jest": "^27.5.0",
|
||||
"@types/node": "18.x",
|
||||
"typescript": "^4.0.0",
|
||||
"rimraf": "^5.0.0"
|
||||
"azure-functions-core-tools": "^4.x",
|
||||
"jest": "^28.0.3",
|
||||
"rimraf": "^5.0.0",
|
||||
"sqlite3": "^5.1.6",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^28.0.1",
|
||||
"typescript": "^4.0.0"
|
||||
},
|
||||
"jest": {
|
||||
"moduleFileExtensions": [
|
||||
"js",
|
||||
"json",
|
||||
"ts"
|
||||
],
|
||||
"rootDir": "src",
|
||||
"testRegex": ".*\\.spec\\.ts$",
|
||||
"transform": {
|
||||
"^.+\\.(t|j)s$": "ts-jest"
|
||||
},
|
||||
"collectCoverageFrom": [
|
||||
"**/*.(t|j)s"
|
||||
],
|
||||
"coverageDirectory": "../coverage",
|
||||
"testEnvironment": "node"
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,61 @@
|
||||
import { bigintTransformer } from '.';
|
||||
|
||||
describe('bigintTransformer', () => {
|
||||
describe('to', () => {
|
||||
it('number型を整数を表す文字列に変換できる', () => {
|
||||
expect(bigintTransformer.to(0)).toBe('0');
|
||||
expect(bigintTransformer.to(1)).toBe('1');
|
||||
expect(bigintTransformer.to(1234567890)).toBe('1234567890');
|
||||
expect(bigintTransformer.to(9007199254740991)).toBe('9007199254740991');
|
||||
expect(bigintTransformer.to(-1)).toBe('-1');
|
||||
});
|
||||
it('少数点以下がある場合はエラーとなる', () => {
|
||||
expect(() => bigintTransformer.to(1.1)).toThrowError(
|
||||
'1.1 is not integer.',
|
||||
);
|
||||
});
|
||||
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
|
||||
expect(() => bigintTransformer.to(9007199254740992)).toThrowError(
|
||||
'value is greater than 9007199254740991.',
|
||||
);
|
||||
expect(() => bigintTransformer.to(9223372036854775807)).toThrowError(
|
||||
'value is greater than 9007199254740991.',
|
||||
);
|
||||
});
|
||||
});
|
||||
describe('from', () => {
|
||||
it('bigint型の文字列をnumber型に変換できる', () => {
|
||||
expect(bigintTransformer.from('0')).toBe(0);
|
||||
expect(bigintTransformer.from('1')).toBe(1);
|
||||
expect(bigintTransformer.from('1234567890')).toBe(1234567890);
|
||||
expect(bigintTransformer.from('-1')).toBe(-1);
|
||||
});
|
||||
it('Number.MAX_SAFE_INTEGERを超える値を変換しようとするとエラーになる', () => {
|
||||
expect(() => bigintTransformer.from('9007199254740992')).toThrowError(
|
||||
'9007199254740992 is greater than 9007199254740991.',
|
||||
);
|
||||
expect(() => bigintTransformer.from('9223372036854775807')).toThrowError(
|
||||
'9223372036854775807 is greater than 9007199254740991.',
|
||||
);
|
||||
});
|
||||
it('number型の場合はそのまま返す', () => {
|
||||
expect(bigintTransformer.from(0)).toBe(0);
|
||||
expect(bigintTransformer.from(1)).toBe(1);
|
||||
expect(bigintTransformer.from(1234567890)).toBe(1234567890);
|
||||
expect(bigintTransformer.from(-1)).toBe(-1);
|
||||
});
|
||||
it('nullの場合はそのまま返す', () => {
|
||||
expect(bigintTransformer.from(null)).toBe(null);
|
||||
});
|
||||
it('number型に変換できない場合はエラーとなる', () => {
|
||||
expect(() => bigintTransformer.from('a')).toThrowError('a is not int.');
|
||||
expect(() => bigintTransformer.from('')).toThrowError(' is not int.');
|
||||
expect(() => bigintTransformer.from(undefined)).toThrowError(
|
||||
'undefined is not string.',
|
||||
);
|
||||
expect(() => bigintTransformer.from({})).toThrowError(
|
||||
'[object Object] is not string.',
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
57
dictation_function/src/common/entity/index.ts
Normal file
57
dictation_function/src/common/entity/index.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { ValueTransformer } from 'typeorm';
|
||||
|
||||
// DBのbigint型をnumber型に変換するためのtransformer
|
||||
// DBのBigInt型をそのまま扱うと、JSのNumber型の最大値を超えると誤差が発生するため、本来はNumber型に変換すべきではないが、
|
||||
// 影響範囲を最小限に抑えるため、Number型に変換する。使用するのはAutoIncrementされるIDのみの想定のため、
|
||||
// Number.MAX_SAFE_INTEGERより大きい値は現実的には発生しない想定で変換する。
|
||||
export const bigintTransformer: ValueTransformer = {
|
||||
from: (value: any): number | null => {
|
||||
// valueがnullであればそのまま返す
|
||||
if (value === null) {
|
||||
return value;
|
||||
}
|
||||
// valueがnumber型かどうかを判定
|
||||
// 利用DBによってはbigint型であってもnumber型で返ってくる場合があるため、number型の場合はそのまま返す(sqliteの場合)
|
||||
if (typeof value === 'number') {
|
||||
return value;
|
||||
}
|
||||
// valueが文字列かどうかを判定
|
||||
if (typeof value !== 'string') {
|
||||
throw new Error(`${value} is not string.`);
|
||||
}
|
||||
// 数値に変換可能な文字列かどうかを判定
|
||||
if (Number.isNaN(parseInt(value))) {
|
||||
throw new Error(`${value} is not int.`);
|
||||
}
|
||||
|
||||
// 文字列ならbigintに変換
|
||||
// valueが整数でない場合は値が丸められてしまうが、TypeORMのEntityの定義上、整数を表す文字列以外はありえないため、少数点は考慮しない
|
||||
const bigIntValue = BigInt(value);
|
||||
// bigIntValueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
|
||||
if (bigIntValue > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error(`${value} is greater than ${Number.MAX_SAFE_INTEGER}.`);
|
||||
}
|
||||
// number型で表現できる整数であればnumber型に変換して返す
|
||||
return Number(bigIntValue);
|
||||
},
|
||||
to: (value: any): string | null | undefined => {
|
||||
// valueがnullまたはundefinedであればそのまま返す
|
||||
if (value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
// valueがnumber型かどうかを判定
|
||||
if (typeof value !== 'number') {
|
||||
throw new Error(`${value} is not number.`);
|
||||
}
|
||||
|
||||
// valueがNumber.MAX_SAFE_INTEGERより大きいかどうかを判定
|
||||
if (value > Number.MAX_SAFE_INTEGER) {
|
||||
throw new Error(`value is greater than ${Number.MAX_SAFE_INTEGER}.`);
|
||||
}
|
||||
// valueが整数かどうかを判定
|
||||
if (!Number.isInteger(value)) {
|
||||
throw new Error(`${value} is not integer.`);
|
||||
}
|
||||
return value.toString();
|
||||
},
|
||||
};
|
||||
71
dictation_function/src/common/test/utility.ts
Normal file
71
dictation_function/src/common/test/utility.ts
Normal file
@ -0,0 +1,71 @@
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../../entity/user.entity";
|
||||
import { Account } from "../../entity/account.entity";
|
||||
import { ADMIN_ROLES, USER_ROLES } from "../../constants";
|
||||
|
||||
type InitialTestDBState = {
|
||||
tier1Accounts: { account: Account; users: User[] }[];
|
||||
tier2Accounts: { account: Account; users: User[] }[];
|
||||
tier3Accounts: { account: Account; users: User[] }[];
|
||||
tier4Accounts: { account: Account; users: User[] }[];
|
||||
tier5Accounts: { account: Account; users: User[] }[];
|
||||
};
|
||||
|
||||
// 上書きされたら困る項目を除外したAccount型
|
||||
type OverrideAccount = Omit<
|
||||
Account,
|
||||
"id" | "primary_admin_user_id" | "secondary_admin_user_id" | "user"
|
||||
>;
|
||||
|
||||
// 上書きされたら困る項目を除外したUser型
|
||||
type OverrideUser = Omit<
|
||||
User,
|
||||
"id" | "account" | "license" | "userGroupMembers"
|
||||
>;
|
||||
|
||||
type AccountDefault = { [K in keyof OverrideAccount]?: OverrideAccount[K] };
|
||||
type UserDefault = { [K in keyof OverrideUser]?: OverrideUser[K] };
|
||||
|
||||
/**
|
||||
* テスト ユーティリティ: 指定したプロパティを上書きしたユーザーを作成する
|
||||
* @param dataSource データソース
|
||||
* @param defaultUserValue User型と同等かつoptionalなプロパティを持つ上書き箇所指定用オブジェクト
|
||||
* @returns 作成したユーザー
|
||||
*/
|
||||
export const makeTestUser = async (
|
||||
datasource: DataSource,
|
||||
defaultUserValue?: UserDefault
|
||||
): Promise<User> => {
|
||||
const d = defaultUserValue;
|
||||
const { identifiers } = await datasource.getRepository(User).insert({
|
||||
account_id: d?.account_id ?? -1,
|
||||
external_id: d?.external_id ?? uuidv4(),
|
||||
role: d?.role ?? `${ADMIN_ROLES.STANDARD} ${USER_ROLES.NONE}`,
|
||||
author_id: d?.author_id,
|
||||
accepted_eula_version: d?.accepted_eula_version ?? "1.0",
|
||||
accepted_dpa_version: d?.accepted_dpa_version ?? "1.0",
|
||||
email_verified: d?.email_verified ?? true,
|
||||
auto_renew: d?.auto_renew ?? true,
|
||||
license_alert: d?.license_alert ?? true,
|
||||
notification: d?.notification ?? true,
|
||||
encryption: d?.encryption ?? true,
|
||||
encryption_password: d?.encryption_password,
|
||||
prompt: d?.prompt ?? true,
|
||||
created_by: d?.created_by ?? "test_runner",
|
||||
created_at: d?.created_at ?? new Date(),
|
||||
updated_by: d?.updated_by ?? "updater",
|
||||
updated_at: d?.updated_at ?? new Date(),
|
||||
});
|
||||
const result = identifiers.pop() as User;
|
||||
|
||||
const user = await datasource.getRepository(User).findOne({
|
||||
where: {
|
||||
id: result.id,
|
||||
},
|
||||
});
|
||||
if (!user) {
|
||||
throw new Error("Unexpected null");
|
||||
}
|
||||
return user;
|
||||
};
|
||||
263
dictation_function/src/constants/index.ts
Normal file
263
dictation_function/src/constants/index.ts
Normal file
@ -0,0 +1,263 @@
|
||||
/**
|
||||
* 階層
|
||||
* @const {number}
|
||||
*/
|
||||
export const TIERS = {
|
||||
//OMDS東京
|
||||
TIER1: 1,
|
||||
//OMDS現地法人
|
||||
TIER2: 2,
|
||||
//代理店
|
||||
TIER3: 3,
|
||||
//販売店
|
||||
TIER4: 4,
|
||||
//エンドユーザー
|
||||
TIER5: 5,
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 音声ファイルをEast USに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_US = ['CA', 'KY', 'US'];
|
||||
|
||||
/**
|
||||
* 音声ファイルをAustralia Eastに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_AU = ['AU', 'NZ'];
|
||||
|
||||
/**
|
||||
* 音声ファイルをNorth Europeに保存する国リスト
|
||||
* @const {number}
|
||||
*/
|
||||
export const BLOB_STORAGE_REGION_EU = [
|
||||
'AT',
|
||||
'BE',
|
||||
'BG',
|
||||
'HR',
|
||||
'CY',
|
||||
'CZ',
|
||||
'DK',
|
||||
'EE',
|
||||
'FI',
|
||||
'FR',
|
||||
'DE',
|
||||
'GR',
|
||||
'HU',
|
||||
'IS',
|
||||
'IE',
|
||||
'IT',
|
||||
'LV',
|
||||
'LI',
|
||||
'LT',
|
||||
'LU',
|
||||
'MT',
|
||||
'NL',
|
||||
'NO',
|
||||
'PL',
|
||||
'PT',
|
||||
'RO',
|
||||
'RS',
|
||||
'SK',
|
||||
'SI',
|
||||
'ZA',
|
||||
'ES',
|
||||
'SE',
|
||||
'CH',
|
||||
'TR',
|
||||
'GB',
|
||||
];
|
||||
|
||||
/**
|
||||
* 管理ロール
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADMIN_ROLES = {
|
||||
ADMIN: 'admin',
|
||||
STANDARD: 'standard',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ロール
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const USER_ROLES = {
|
||||
NONE: 'none',
|
||||
AUTHOR: 'author',
|
||||
TYPIST: 'typist',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ライセンス注文状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ISSUE_STATUS = {
|
||||
ISSUE_REQUESTING: 'Issue Requesting',
|
||||
ISSUED: 'Issued',
|
||||
CANCELED: 'Order Canceled',
|
||||
};
|
||||
|
||||
/**
|
||||
* ライセンス種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_TYPE = {
|
||||
TRIAL: 'TRIAL',
|
||||
NORMAL: 'NORMAL',
|
||||
CARD: 'CARD',
|
||||
} as const;
|
||||
/**
|
||||
* ライセンス状態
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const LICENSE_ALLOCATED_STATUS = {
|
||||
UNALLOCATED: 'Unallocated',
|
||||
ALLOCATED: 'Allocated',
|
||||
REUSABLE: 'Reusable',
|
||||
DELETED: 'Deleted',
|
||||
} as const;
|
||||
/**
|
||||
* 切り替え元種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const SWITCH_FROM_TYPE = {
|
||||
NONE: 'NONE',
|
||||
CARD: 'CARD',
|
||||
TRIAL: 'TRIAL',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ライセンスの期限切れが近いと見なす日数のしきい値
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_THRESHOLD_DAYS = 14;
|
||||
|
||||
/**
|
||||
* ライセンスの有効期間
|
||||
* @const {number}
|
||||
*/
|
||||
export const LICENSE_EXPIRATION_DAYS = 365;
|
||||
|
||||
/**
|
||||
* カードライセンスの桁数
|
||||
* @const {number}
|
||||
*/
|
||||
export const CARD_LICENSE_LENGTH = 20;
|
||||
|
||||
/**
|
||||
* 音声ファイルに紐づくオプションアイテムの数
|
||||
* @const {string}
|
||||
*/
|
||||
export const OPTION_ITEM_NUM = 10;
|
||||
|
||||
/**
|
||||
* 文字起こしタスクのステータス
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TASK_STATUS = {
|
||||
UPLOADED: 'Uploaded',
|
||||
PENDING: 'Pending',
|
||||
IN_PROGRESS: 'InProgress',
|
||||
FINISHED: 'Finished',
|
||||
BACKUP: 'Backup',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* タスク一覧でソート可能な属性の一覧
|
||||
*/
|
||||
export const TASK_LIST_SORTABLE_ATTRIBUTES = [
|
||||
'JOB_NUMBER',
|
||||
'STATUS',
|
||||
'ENCRYPTION',
|
||||
'AUTHOR_ID',
|
||||
'WORK_TYPE',
|
||||
'FILE_NAME',
|
||||
'FILE_LENGTH',
|
||||
'FILE_SIZE',
|
||||
'RECORDING_STARTED_DATE',
|
||||
'RECORDING_FINISHED_DATE',
|
||||
'UPLOAD_DATE',
|
||||
'TRANSCRIPTION_STARTED_DATE',
|
||||
'TRANSCRIPTION_FINISHED_DATE',
|
||||
] as const;
|
||||
|
||||
/**
|
||||
* タスク一覧のソート条件(昇順・降順)
|
||||
*/
|
||||
export const SORT_DIRECTIONS = ['ASC', 'DESC'] as const;
|
||||
|
||||
/**
|
||||
* 通知タグの最大個数
|
||||
* NotificationHubの仕様上タグ式のOR条件で使えるタグは20個まで
|
||||
* https://learn.microsoft.com/ja-jp/azure/notification-hubs/notification-hubs-tags-segment-push-message#tag-expressions
|
||||
*/
|
||||
export const TAG_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* 通知のプラットフォーム種別文字列
|
||||
*/
|
||||
export const PNS = {
|
||||
WNS: 'wns',
|
||||
APNS: 'apns',
|
||||
};
|
||||
|
||||
/**
|
||||
* ユーザーのライセンス状態
|
||||
*/
|
||||
export const USER_LICENSE_STATUS = {
|
||||
NORMAL: 'Normal',
|
||||
NO_LICENSE: 'NoLicense',
|
||||
ALERT: 'Alert',
|
||||
RENEW: 'Renew',
|
||||
};
|
||||
|
||||
/**
|
||||
*トライアルライセンスの有効期限(日数)
|
||||
* @const {number}
|
||||
*/
|
||||
export const TRIAL_LICENSE_EXPIRATION_DAYS = 30;
|
||||
|
||||
/**
|
||||
* ライセンスの発行数
|
||||
* @const {number}
|
||||
*/
|
||||
export const TRIAL_LICENSE_ISSUE_NUM = 100;
|
||||
|
||||
/**
|
||||
* worktypeの最大登録数
|
||||
* @const {number}
|
||||
*/
|
||||
export const WORKTYPE_MAX_COUNT = 20;
|
||||
|
||||
/**
|
||||
* worktypeのDefault値の取りうる値
|
||||
**/
|
||||
export const OPTION_ITEM_VALUE_TYPE = {
|
||||
DEFAULT: 'Default',
|
||||
BLANK: 'Blank',
|
||||
LAST_INPUT: 'LastInput',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* ADB2Cユーザのidentity.signInType
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const ADB2C_SIGN_IN_TYPE = {
|
||||
EMAILADDRESS: 'emailAddress',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* MANUAL_RECOVERY_REQUIRED
|
||||
* @const {string}
|
||||
*/
|
||||
export const MANUAL_RECOVERY_REQUIRED = '[MANUAL_RECOVERY_REQUIRED]';
|
||||
|
||||
/**
|
||||
* 利用規約種別
|
||||
* @const {string[]}
|
||||
*/
|
||||
export const TERM_TYPE = {
|
||||
EULA: 'EULA',
|
||||
DPA: 'DPA',
|
||||
} as const;
|
||||
70
dictation_function/src/entity/account.entity.ts
Normal file
70
dictation_function/src/entity/account.entity.ts
Normal file
@ -0,0 +1,70 @@
|
||||
import { bigintTransformer } from "../common/entity";
|
||||
import { User } from "./user.entity";
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
OneToMany,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity({ name: "accounts" })
|
||||
export class Account {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
parent_account_id: number | null;
|
||||
|
||||
@Column()
|
||||
tier: number;
|
||||
|
||||
@Column()
|
||||
country: string;
|
||||
|
||||
@Column({ default: false })
|
||||
delegation_permission: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
locked: boolean;
|
||||
|
||||
@Column()
|
||||
company_name: string;
|
||||
|
||||
@Column({ default: false })
|
||||
verified: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
primary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
secondary_admin_user_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "bigint", transformer: bigintTransformer })
|
||||
active_worktype_id: number | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
|
||||
@OneToMany(() => User, (user) => user.id)
|
||||
user: User[] | null;
|
||||
}
|
||||
73
dictation_function/src/entity/user.entity.ts
Normal file
73
dictation_function/src/entity/user.entity.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import {
|
||||
Entity,
|
||||
Column,
|
||||
PrimaryGeneratedColumn,
|
||||
CreateDateColumn,
|
||||
UpdateDateColumn,
|
||||
} from "typeorm";
|
||||
|
||||
@Entity({ name: "users" })
|
||||
export class User {
|
||||
@PrimaryGeneratedColumn()
|
||||
id: number;
|
||||
|
||||
@Column()
|
||||
external_id: string;
|
||||
|
||||
@Column()
|
||||
account_id: number;
|
||||
|
||||
@Column()
|
||||
role: string;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
author_id: string | null;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
accepted_eula_version: string | null;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
accepted_dpa_version: string | null;
|
||||
|
||||
@Column({ default: false })
|
||||
email_verified: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
auto_renew: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
license_alert: boolean;
|
||||
|
||||
@Column({ default: true })
|
||||
notification: boolean;
|
||||
|
||||
@Column({ default: false })
|
||||
encryption: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "varchar" })
|
||||
encryption_password: string | null;
|
||||
|
||||
@Column({ default: false })
|
||||
prompt: boolean;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
deleted_at: Date | null;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
created_by: string | null;
|
||||
|
||||
@CreateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
created_at: Date;
|
||||
|
||||
@Column({ nullable: true, type: "datetime" })
|
||||
updated_by: string | null;
|
||||
|
||||
@UpdateDateColumn({
|
||||
default: () => "datetime('now', 'localtime')",
|
||||
type: "datetime",
|
||||
}) // defaultはSQLite用設定値.本番用は別途migrationで設定
|
||||
updated_at: Date;
|
||||
}
|
||||
@ -1,4 +1,8 @@
|
||||
import { app, InvocationContext, Timer } from "@azure/functions";
|
||||
import { DataSource } from "typeorm";
|
||||
import { User } from "../entity/user.entity";
|
||||
import { SendGridService } from "../sendgrid/sendgrid.service";
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
// タイマートリガー処理のサンプルです
|
||||
// TODO:開発が進んだら削除すること
|
||||
@ -7,6 +11,57 @@ export async function timerTriggerExample(
|
||||
context: InvocationContext
|
||||
): Promise<void> {
|
||||
context.log("Timer function processed request.");
|
||||
|
||||
dotenv.config({ path: ".env" });
|
||||
const datasource = new DataSource({
|
||||
type: "mysql",
|
||||
host: process.env.DB_HOST,
|
||||
port: Number(process.env.DB_PORT),
|
||||
username: process.env.DB_USERNAME,
|
||||
password: process.env.DB_PASSWORD,
|
||||
database: process.env.DB_NAME,
|
||||
entities: [User],
|
||||
});
|
||||
|
||||
try {
|
||||
await datasource.initialize();
|
||||
const userRepository = datasource.getRepository(User); // Userエンティティに対応するリポジトリを取得
|
||||
|
||||
// ユーザーを検索
|
||||
const users = await userRepository.find();
|
||||
console.log(users);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
} finally {
|
||||
await datasource.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
// test実行確認用サンプル
|
||||
// TODO:開発が進んだら削除すること
|
||||
export async function testExample(datasource: DataSource): Promise<User[]> {
|
||||
let users: User[];
|
||||
const userRepository = datasource.getRepository(User); // Userエンティティに対応するリポジトリを取得
|
||||
|
||||
// ユーザーを検索
|
||||
users = await userRepository.find();
|
||||
return users;
|
||||
}
|
||||
|
||||
// test実行確認用サンプル
|
||||
// TODO:開発が進んだら削除すること
|
||||
export async function testSendgridExample(): Promise<string> {
|
||||
const sendgrid = new SendGridService();
|
||||
|
||||
// メールを送信
|
||||
await sendgrid.sendMail(
|
||||
"oura.a89@gmail.com",
|
||||
process.env.MAIL_FROM,
|
||||
"testMail",
|
||||
"test!",
|
||||
"html"
|
||||
);
|
||||
return "sucsess";
|
||||
}
|
||||
|
||||
app.timer("timerTriggerExample", {
|
||||
|
||||
60
dictation_function/src/sendgrid/sendgrid.service.ts
Normal file
60
dictation_function/src/sendgrid/sendgrid.service.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import sendgrid from "@sendgrid/mail";
|
||||
|
||||
export class SendGridService {
|
||||
constructor() {
|
||||
sendgrid.setApiKey(process.env.SENDGRID_API_KEY);
|
||||
}
|
||||
/**
|
||||
* メールコンテンツを作成する
|
||||
* @param accountId 認証対象のユーザーが所属するアカウントのID
|
||||
* @param userId 認証対象のユーザーのID
|
||||
* @param email 認証対象のユーザーのメールアドレス
|
||||
* @returns メールのサブジェクトとコンテンツ
|
||||
*/
|
||||
async createMailContent(
|
||||
accountId: number,
|
||||
userId: number,
|
||||
email: string
|
||||
): Promise<{ subject: string; text: string; html: string }> {
|
||||
return {
|
||||
subject: "Verify your new account",
|
||||
text: `The verification URL.`,
|
||||
html: `<p>The verification URL.<p>`,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* メールを送信する
|
||||
* @param to
|
||||
* @param from
|
||||
* @param subject
|
||||
* @param text
|
||||
* @param html
|
||||
* @returns mail
|
||||
*/
|
||||
async sendMail(
|
||||
to: string,
|
||||
from: string,
|
||||
subject: string,
|
||||
text: string,
|
||||
html: string
|
||||
): Promise<void> {
|
||||
try {
|
||||
const res = await sendgrid
|
||||
.send({
|
||||
from: {
|
||||
email: from,
|
||||
},
|
||||
to: {
|
||||
email: to,
|
||||
},
|
||||
subject: subject,
|
||||
text: text,
|
||||
html: html,
|
||||
})
|
||||
.then((v) => v[0]);
|
||||
} catch (e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
42
dictation_function/src/test/timerTriggerExample.spec.ts
Normal file
42
dictation_function/src/test/timerTriggerExample.spec.ts
Normal file
@ -0,0 +1,42 @@
|
||||
import { DataSource } from "typeorm";
|
||||
import {
|
||||
testExample,
|
||||
testSendgridExample,
|
||||
} from "../functions/timerTriggerExample";
|
||||
import { makeTestUser } from "../common/test/utility";
|
||||
import * as dotenv from "dotenv";
|
||||
|
||||
describe("timerTriggerExample", () => {
|
||||
dotenv.config({ path: ".env.local", override: true });
|
||||
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("sample test(DB)", async () => {
|
||||
const count = 5;
|
||||
for (let i = 0; i < count; i++) {
|
||||
await makeTestUser(source);
|
||||
}
|
||||
|
||||
const result = await testExample(source);
|
||||
expect(result.length).toEqual(count);
|
||||
});
|
||||
|
||||
it("sample test(sendgrid)", async () => {
|
||||
await testSendgridExample();
|
||||
});
|
||||
});
|
||||
@ -5,6 +5,9 @@
|
||||
"outDir": "dist",
|
||||
"rootDir": ".",
|
||||
"sourceMap": true,
|
||||
"strict": false
|
||||
"strict": false,
|
||||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user