diff --git a/dictation_server/src/common/entity/bigintTransformer.spec.ts b/dictation_server/src/common/entity/bigintTransformer.spec.ts new file mode 100644 index 0000000..48c20d0 --- /dev/null +++ b/dictation_server/src/common/entity/bigintTransformer.spec.ts @@ -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.', + ); + }); + }); +}); diff --git a/dictation_server/src/common/entity/index.ts b/dictation_server/src/common/entity/index.ts new file mode 100644 index 0000000..26f5d5b --- /dev/null +++ b/dictation_server/src/common/entity/index.ts @@ -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(); + }, +}; diff --git a/dictation_server/src/repositories/accounts/entity/account.entity.ts b/dictation_server/src/repositories/accounts/entity/account.entity.ts index 7663f47..3c40a03 100644 --- a/dictation_server/src/repositories/accounts/entity/account.entity.ts +++ b/dictation_server/src/repositories/accounts/entity/account.entity.ts @@ -1,3 +1,4 @@ +import { bigintTransformer } from '../../../common/entity'; import { User } from '../../../repositories/users/entity/user.entity'; import { Entity, @@ -13,7 +14,7 @@ export class Account { @PrimaryGeneratedColumn() id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) parent_account_id: number | null; @Column() @@ -34,13 +35,13 @@ export class Account { @Column({ default: false }) verified: boolean; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) primary_admin_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) secondary_admin_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) active_worktype_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts index 2ff148e..f0b6608 100644 --- a/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts +++ b/dictation_server/src/repositories/checkout_permissions/entity/checkout_permission.entity.ts @@ -1,3 +1,4 @@ +import { bigintTransformer } from '../../../common/entity'; import { Task } from '../../../repositories/tasks/entity/task.entity'; import { UserGroup } from '../../../repositories/user_groups/entity/user_group.entity'; import { User } from '../../../repositories/users/entity/user.entity'; @@ -18,10 +19,10 @@ export class CheckoutPermission { @Column({}) task_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) user_group_id: number | null; @OneToOne(() => User, (user) => user.id) diff --git a/dictation_server/src/repositories/licenses/entity/license.entity.ts b/dictation_server/src/repositories/licenses/entity/license.entity.ts index c3adf94..696cabb 100644 --- a/dictation_server/src/repositories/licenses/entity/license.entity.ts +++ b/dictation_server/src/repositories/licenses/entity/license.entity.ts @@ -10,6 +10,7 @@ import { PrimaryColumn, } from 'typeorm'; import { User } from '../../users/entity/user.entity'; +import { bigintTransformer } from '../../../common/entity'; @Entity({ name: 'license_orders' }) export class LicenseOrder { @@ -79,16 +80,16 @@ export class License { @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) allocated_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) order_id: number | null; @Column({ nullable: true, type: 'datetime' }) deleted_at: Date | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) delete_order_id: number | null; @Column({ nullable: true, type: 'datetime' }) @@ -244,16 +245,16 @@ export class LicenseArchive { @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) allocated_user_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) order_id: number | null; @Column({ nullable: true, type: 'datetime' }) deleted_at: Date | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) delete_order_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/tasks/entity/task.entity.ts b/dictation_server/src/repositories/tasks/entity/task.entity.ts index 27da363..124d4d1 100644 --- a/dictation_server/src/repositories/tasks/entity/task.entity.ts +++ b/dictation_server/src/repositories/tasks/entity/task.entity.ts @@ -11,6 +11,7 @@ import { OneToMany, ManyToOne, } from 'typeorm'; +import { bigintTransformer } from '../../../common/entity'; @Entity({ name: 'tasks' }) export class Task { @@ -26,11 +27,11 @@ export class Task { audio_file_id: number; @Column() status: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) typist_user_id: number | null; @Column() priority: string; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) template_file_id: number | null; @Column({ nullable: true, type: 'datetime' }) started_at: Date | null; diff --git a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts index 52af3e2..a515a1a 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow.entity.ts @@ -12,6 +12,7 @@ import { WorkflowTypist } from './workflow_typists.entity'; import { Worktype } from '../../worktypes/entity/worktype.entity'; import { TemplateFile } from '../../template_files/entity/template_file.entity'; import { User } from '../../users/entity/user.entity'; +import { bigintTransformer } from '../../../common/entity'; @Entity({ name: 'workflows' }) export class Workflow { @@ -24,10 +25,10 @@ export class Workflow { @Column() author_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) worktype_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) template_id: number | null; @Column({ nullable: true, type: 'datetime' }) diff --git a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts index 31f8cbd..a3af367 100644 --- a/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts +++ b/dictation_server/src/repositories/workflows/entity/workflow_typists.entity.ts @@ -10,6 +10,7 @@ import { import { Workflow } from './workflow.entity'; import { User } from '../../users/entity/user.entity'; import { UserGroup } from '../../user_groups/entity/user_group.entity'; +import { bigintTransformer } from '../../../common/entity'; @Entity({ name: 'workflow_typists' }) export class WorkflowTypist { @@ -19,10 +20,10 @@ export class WorkflowTypist { @Column() workflow_id: number; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) typist_id: number | null; - @Column({ nullable: true, type: 'int' }) + @Column({ nullable: true, type: 'bigint', transformer: bigintTransformer }) typist_group_id: number | null; @Column({ nullable: true, type: 'datetime' })