Merged PR 152: API実装(タスク一覧取得 | typist)

## 概要
[Task1865: API実装(タスク一覧取得 | typist)](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/1865)

- Typist用のRepository呼び出しを追加

## レビュー対象外
- テスト実装(別Taskで対応予定)

## レビューポイント
- SQLの呼び出し回数が増え気味だが問題なさそうか
- Task一覧の取得条件は間違っていなさそうであるか

## 動作確認状況
- ローカルで確認(ユーザーグループ等は各1件程度のデータで確認)
This commit is contained in:
湯本 開 2023-06-15 06:42:57 +00:00
parent 4065268820
commit 2cdcae4924
7 changed files with 194 additions and 12 deletions

View File

@ -87,7 +87,7 @@ export class TasksController {
? body.direction
: undefined;
const { tasks, total } = await this.taskService.getTasksFromAccountId(
const { tasks, total } = await this.taskService.getTasks(
decodedToken,
offset,
limit,

View File

@ -22,7 +22,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
expect(
await service.tasksService.getTasksFromAccountId(
await service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -87,7 +87,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
await expect(
service.tasksService.getTasksFromAccountId(
service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -119,7 +119,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
await expect(
service.tasksService.getTasksFromAccountId(
service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -148,7 +148,7 @@ describe('TasksService', () => {
status: 'Uploaded',
priority: '00',
created_at: new Date('2023-01-01T01:01:01.000'),
option_items: undefined,
option_items: undefined, // 存在しない場合でも空配列であるはずのものがundefined
file: {
id: 1,
account_id: 1,
@ -185,7 +185,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
await expect(
service.tasksService.getTasksFromAccountId(
service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -220,7 +220,7 @@ describe('TasksService', () => {
const status = ['Uploaded,Backup'];
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
const result = await service.tasksService.getTasksFromAccountId(
const result = await service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -292,7 +292,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
await expect(
service.tasksService.getTasksFromAccountId(
service.tasksService.getTasks(
accessToken,
offset,
limit,
@ -323,7 +323,7 @@ describe('TasksService', () => {
const paramName = 'JOB_NUMBER';
const direction = 'ASC';
await expect(
service.tasksService.getTasksFromAccountId(
service.tasksService.getTasks(
accessToken,
offset,
limit,

View File

@ -20,7 +20,7 @@ export class TasksService {
) {}
// TODO: 引数にAccessTokenがあるのは不適切なのでController側で分解したい
async getTasksFromAccountId(
async getTasks(
accessToken: AccessToken,
offset: number,
limit: number,
@ -40,6 +40,9 @@ export class TasksService {
await this.usersRepository.findUserByExternalId(userId);
if (roles.includes(ADMIN_ROLES.ADMIN)) {
const { account_id } = await this.usersRepository.findUserByExternalId(
userId,
);
const result = await this.taskRepository.getTasksFromAccountId(
account_id,
offset,
@ -69,7 +72,18 @@ export class TasksService {
}
if (roles.includes(USER_ROLES.TYPIST)) {
throw new Error(`NOT IMPLEMENTED`);
const result = await this.taskRepository.getTasksFromTypistRelations(
userId,
offset,
limit,
paramName ?? defaultParamName,
direction ?? defaultDirection,
status,
);
const tasks = createTasks(result.tasks, result.permissions);
return { tasks: tasks, total: result.count };
}
throw new Error(`invalid roles: ${roles.join(',')}`);

View File

@ -1,3 +1,4 @@
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';
import {
@ -6,6 +7,7 @@ import {
PrimaryGeneratedColumn,
JoinColumn,
OneToOne,
ManyToOne,
} from 'typeorm';
@Entity({ name: 'checkout_permission' })
@ -29,4 +31,8 @@ export class CheckoutPermission {
@OneToOne(() => UserGroup, (group) => group.id)
@JoinColumn({ name: 'user_group_id' })
user_group?: UserGroup;
@ManyToOne(() => Task, (task) => task.id)
@JoinColumn({ name: 'task_id' })
task?: Task;
}

View File

@ -15,6 +15,7 @@ import {
SortDirection,
TaskListSortableAttribute,
} from '../../common/types/sort';
import { UserGroupMember } from '../user_groups/entity/user_group_member.entity';
@Injectable()
export class TasksRepositoryService {
@ -155,6 +156,127 @@ export class TasksRepositoryService {
return value;
}
async getTasksFromTypistRelations(
external_user_id: string,
offset: number,
limit: number,
sort_criteria: TaskListSortableAttribute,
direction: SortDirection,
status: string[],
): Promise<{
tasks: Task[];
permissions: CheckoutPermission[];
count: number;
}> {
const order = makeOrder(sort_criteria, direction);
const value = await this.dataSource.transaction(async (entityManager) => {
const groupMemberRepo = entityManager.getRepository(UserGroupMember);
// ユーザーの所属するすべてのグループを列挙
const groups = await groupMemberRepo.find({
relations: {
user: true,
},
where: {
user: {
external_id: external_user_id,
},
},
});
// ユーザーの所属するすべてのグループIDを列挙
const groupIds = groups.map((member) => member.user_group_id);
const checkoutRepo = entityManager.getRepository(CheckoutPermission);
// ユーザーに対するチェックアウト権限、またはユーザーの所属するユーザーグループのチェックアウト権限を取得
const related = await checkoutRepo.find({
relations: {
task: true,
},
where: [
{
// ユーザーがチェックアウト可能である
user: {
external_id: external_user_id,
},
},
{
// ユーザーの所属するユーザーグループがチェックアウト可能である
user_group_id: In(groupIds),
},
],
});
// ユーザー本人、またはユーザーが所属するユーザーグループがチェックアウト可能なタスクIDの一覧を作成
const relatedTaskIds = related.map((permission) => permission.task_id);
const taskRepo = entityManager.getRepository(Task);
// [✔] 自分が割り当てられている
// [✔] または 自分が割り当て可能になっている
// [✔] または 自分が所属しているTypistGroupが割り当て可能になっている
// という条件のTask一覧を取得する
// limit/offsetによらず条件に一致するすべてのレコード数を取得
const count = await taskRepo.count({
where: [
{
// Typistが割り当てられているTaskを取得
typist_user: {
external_id: external_user_id,
},
status: In(status),
},
{
// TypistまたはTypistが所属するユーザーグループが割り当て可能になっているTaskを取得
id: In(relatedTaskIds),
status: In(status),
},
],
});
// 条件に該当するTask一覧を取得
const tasks = await taskRepo.find({
relations: {
file: true,
option_items: true,
typist_user: true,
},
where: [
{
// Typistが割り当てられているTaskを取得
typist_user: {
external_id: external_user_id,
},
status: In(status),
},
{
// TypistまたはTypistが所属するユーザーグループが割り当て可能になっているTaskを取得
id: In(relatedTaskIds),
status: In(status),
},
],
order: order, // 引数によってOrderに使用するパラメータを変更
take: limit,
skip: offset,
});
// TODO: Task内にCheckoutPermissionを含める方法が上手くいかなかった複雑になりすぎた 原因未調査)ため、
// 確実に上手くいく方法としてQueryの分割を行ったが、本来はオブジェクトの構築はTypeORMに一任したい
const taskIds = tasks.map((x) => x.id);
const permissions = await checkoutRepo.find({
relations: {
user: true,
user_group: true,
},
where: {
task_id: In(taskIds),
},
});
return { tasks, permissions, count };
});
return value;
}
/**
*
*/

View File

@ -0,0 +1,39 @@
import { User } from '../../../repositories/users/entity/user.entity';
import {
Entity,
Column,
PrimaryGeneratedColumn,
OneToOne,
JoinColumn,
} from 'typeorm';
@Entity({ name: 'user_group_member' })
export class UserGroupMember {
@PrimaryGeneratedColumn()
id: number;
@Column()
user_group_id: number;
@Column()
user_id: string;
@Column({ nullable: true })
deleted_at?: Date;
@Column()
created_by: string;
@Column({ nullable: true })
created_at?: Date;
@Column()
updated_by: string;
@Column({ nullable: true })
updated_at?: Date;
@OneToOne(() => User, (user) => user.id)
@JoinColumn({ name: 'user_id' })
user?: User;
}

View File

@ -2,9 +2,10 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserGroupsRepositoryService } from './user_groups.repository.service';
import { UserGroup } from './entity/user_group.entity';
import { UserGroupMember } from './entity/user_group_member.entity';
@Module({
imports: [TypeOrmModule.forFeature([UserGroup])],
imports: [TypeOrmModule.forFeature([UserGroup, UserGroupMember])],
providers: [UserGroupsRepositoryService],
exports: [UserGroupsRepositoryService],
})