Merged PR 855: API IF実装(パートナーを編集したい)
## 概要 [Task3930: API IF実装](https://paruru.nds-tyo.co.jp:8443/tfs/ReciproCollection/fa4924a4-d079-4fab-9fb5-a9a11eb205f0/_workitems/edit/3930) - 元PBI or タスクへのリンク(内容・目的などはそちらにあるはず) - 新規追加API2本のIFを作成、controllerの返却値は仮実装(別タスクで実装) - 影響範囲(他の機能にも影響があるか) 新規追加のみなので影響はなし ## レビューポイント - 特筆する点はありません ## UIの変更 なし ## クエリの変更 なし ## 動作確認状況 - ローカルで確認 バリデーションテストとPOSTMANからの起動の確認 - 行った修正がデグレを発生させていないことを確認できるか - 具体的にどのような確認をしたか - どのケースに対してどのような手段でデグレがないことを担保しているか 完全新規のIFの実装のみなのでデグレはない想定 ## 補足 - 相談、参考資料などがあれば
This commit is contained in:
parent
133db833ee
commit
114ded790e
@ -1772,6 +1772,118 @@
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/partner/users": {
|
||||
"post": {
|
||||
"operationId": "getPartnerUsers",
|
||||
"summary": "",
|
||||
"description": "パートナーアカウントのユーザー情報を取得します(開発規約に基づき、他のAPIと合わせてGETではなくPOSTを使用)",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetPartnerUsersRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/GetPartnerUsersResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "パラメータ不正/API実行者と取得対象が親子関係ではない",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/accounts/partner/update": {
|
||||
"post": {
|
||||
"operationId": "updatePartnerInfo",
|
||||
"summary": "",
|
||||
"description": "パートナーアカウントの情報を更新します",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdatePartnerInfoRequest"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"description": "成功時のレスポンス",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/UpdatePartnerInfoResponse"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"400": {
|
||||
"description": "パラメータ不正/API実行者と取得対象が親子関係ではない/アカウントが不在/プライマリ管理者が同一アカウント内にいない",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"401": {
|
||||
"description": "認証エラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
},
|
||||
"500": {
|
||||
"description": "想定外のサーバーエラー",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": { "$ref": "#/components/schemas/ErrorResponse" }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": ["accounts"],
|
||||
"security": [{ "bearer": [] }]
|
||||
}
|
||||
},
|
||||
"/users/confirm": {
|
||||
"post": {
|
||||
"operationId": "confirmUser",
|
||||
@ -4677,6 +4789,55 @@
|
||||
"required": ["targetAccountId"]
|
||||
},
|
||||
"DeletePartnerAccountResponse": { "type": "object", "properties": {} },
|
||||
"GetPartnerUsersRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"targetAccountId": {
|
||||
"type": "number",
|
||||
"description": "取得対象のアカウントID"
|
||||
}
|
||||
},
|
||||
"required": ["targetAccountId"]
|
||||
},
|
||||
"PartnerUser": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": { "type": "number", "description": "ユーザーID" },
|
||||
"name": { "type": "string", "description": "ユーザー名" },
|
||||
"email": { "type": "string", "description": "メールアドレス" },
|
||||
"isPrimaryAdmin": {
|
||||
"type": "boolean",
|
||||
"description": "プライマリ管理者かどうか"
|
||||
}
|
||||
},
|
||||
"required": ["id", "name", "email", "isPrimaryAdmin"]
|
||||
},
|
||||
"GetPartnerUsersResponse": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"users": {
|
||||
"type": "array",
|
||||
"items": { "$ref": "#/components/schemas/PartnerUser" }
|
||||
}
|
||||
},
|
||||
"required": ["users"]
|
||||
},
|
||||
"UpdatePartnerInfoRequest": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"targetAccountId": {
|
||||
"type": "number",
|
||||
"description": "変更対象アカウントID"
|
||||
},
|
||||
"primaryAdminUserId": {
|
||||
"type": "number",
|
||||
"description": "プライマリ管理者ID"
|
||||
},
|
||||
"companyName": { "type": "string", "description": "会社名" }
|
||||
},
|
||||
"required": ["targetAccountId", "primaryAdminUserId", "companyName"]
|
||||
},
|
||||
"UpdatePartnerInfoResponse": { "type": "object", "properties": {} },
|
||||
"ConfirmRequest": {
|
||||
"type": "object",
|
||||
"properties": { "token": { "type": "string" } },
|
||||
|
||||
@ -6,6 +6,8 @@ import { AuthService } from '../auth/auth.service';
|
||||
import {
|
||||
SwitchParentRequest,
|
||||
DeletePartnerAccountRequest,
|
||||
GetPartnerUsersRequest,
|
||||
UpdatePartnerInfoRequest,
|
||||
} from './types/types';
|
||||
import { plainToClass } from 'class-transformer';
|
||||
import { validate } from 'class-validator';
|
||||
@ -129,4 +131,150 @@ describe('AccountsController', () => {
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
});
|
||||
describe('valdation getPartnerUsers', () => {
|
||||
it('最低限の有効なリクエストが成功する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(GetPartnerUsersRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('取得対象アカウントが指定されていない場合、リクエストが失敗する', async () => {
|
||||
const request = {};
|
||||
|
||||
const valdationObject = plainToClass(GetPartnerUsersRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
|
||||
it('取得対象アカウントが0の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
userId: 0,
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(GetPartnerUsersRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
|
||||
it('取得対象アカウントが文字列(数値以外)の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
userId: 'a',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(GetPartnerUsersRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
});
|
||||
describe('valdation updatePartnerInfo', () => {
|
||||
it('最低限の有効なリクエストが成功する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
primaryAdminUserId: 2,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(0);
|
||||
});
|
||||
|
||||
it('更新対象アカウントが指定されていない場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: undefined,
|
||||
primaryAdminUserId: 2,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
|
||||
it('更新対象アカウントが0の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 0,
|
||||
primaryAdminUserId: 2,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
|
||||
it('更新対象アカウントが文字列(数値以外)の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 'a',
|
||||
primaryAdminUserId: 2,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
});
|
||||
// primaryAdminUserIdのテスト
|
||||
it('更新対象アカウントが指定されていない場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
primaryAdminUserId: undefined,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
it('更新対象アカウントが0の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
primaryAdminUserId: 0,
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
it('更新対象アカウントが文字列(数値以外)の場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
primaryAdminUserId: 'a',
|
||||
companyName: 'test',
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
// companyNameのテスト
|
||||
it('更新対象アカウントが文字列以外場合、リクエストが失敗する', async () => {
|
||||
const request = {
|
||||
targetAccountId: 1,
|
||||
primaryAdminUserId: 2,
|
||||
companyName: 1,
|
||||
};
|
||||
|
||||
const valdationObject = plainToClass(UpdatePartnerInfoRequest, request);
|
||||
|
||||
const errors = await validate(valdationObject);
|
||||
expect(errors.length).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@ -81,6 +81,10 @@ import {
|
||||
SwitchParentResponse,
|
||||
DeletePartnerAccountRequest,
|
||||
DeletePartnerAccountResponse,
|
||||
GetPartnerUsersResponse,
|
||||
GetPartnerUsersRequest,
|
||||
UpdatePartnerInfoRequest,
|
||||
UpdatePartnerInfoResponse,
|
||||
} from './types/types';
|
||||
import { USER_ROLES, ADMIN_ROLES, TIERS } from '../../constants';
|
||||
import { AuthGuard } from '../../common/guards/auth/authguards';
|
||||
@ -2479,4 +2483,173 @@ export class AccountsController {
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@Post('partner/users')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: GetPartnerUsersResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description: 'パラメータ不正/API実行者と取得対象が親子関係ではない',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'getPartnerUsers',
|
||||
description:
|
||||
'パートナーアカウントのユーザー情報を取得します(開発規約に基づき、他のAPIと合わせてGETではなくPOSTを使用)',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3],
|
||||
}),
|
||||
)
|
||||
async getPartnerUsers(
|
||||
@Req() req: Request,
|
||||
@Body() body: GetPartnerUsersRequest,
|
||||
): Promise<GetPartnerUsersResponse> {
|
||||
const { targetAccountId } = body;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
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, requestId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
// TODO: 仮実装
|
||||
/*await this.accountService.getPartnerUsers(
|
||||
context,
|
||||
targetAccountId,
|
||||
);
|
||||
*/
|
||||
//仮の返却値
|
||||
return { users: [] };
|
||||
}
|
||||
|
||||
@Post('partner/update')
|
||||
@ApiResponse({
|
||||
status: HttpStatus.OK,
|
||||
type: UpdatePartnerInfoResponse,
|
||||
description: '成功時のレスポンス',
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.UNAUTHORIZED,
|
||||
description: '認証エラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.BAD_REQUEST,
|
||||
description:
|
||||
'パラメータ不正/API実行者と取得対象が親子関係ではない/アカウントが不在/プライマリ管理者が同一アカウント内にいない',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiResponse({
|
||||
status: HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
description: '想定外のサーバーエラー',
|
||||
type: ErrorResponse,
|
||||
})
|
||||
@ApiOperation({
|
||||
operationId: 'updatePartnerInfo',
|
||||
description: 'パートナーアカウントの情報を更新します',
|
||||
})
|
||||
@ApiBearerAuth()
|
||||
@UseGuards(AuthGuard)
|
||||
@UseGuards(
|
||||
RoleGuard.requireds({
|
||||
roles: [ADMIN_ROLES.ADMIN],
|
||||
tiers: [TIERS.TIER1, TIERS.TIER2, TIERS.TIER3],
|
||||
}),
|
||||
)
|
||||
async updatePartnerInfo(
|
||||
@Req() req: Request,
|
||||
@Body() body: UpdatePartnerInfoRequest,
|
||||
): Promise<UpdatePartnerInfoResponse> {
|
||||
const { targetAccountId } = body;
|
||||
|
||||
const accessToken = retrieveAuthorizationToken(req);
|
||||
if (!accessToken) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000107'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const ip = retrieveIp(req);
|
||||
if (!ip) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000401'),
|
||||
HttpStatus.UNAUTHORIZED,
|
||||
);
|
||||
}
|
||||
|
||||
const requestId = retrieveRequestId(req);
|
||||
if (!requestId) {
|
||||
throw new HttpException(
|
||||
makeErrorResponse('E000501'),
|
||||
HttpStatus.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
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, requestId);
|
||||
this.logger.log(`[${context.getTrackingId()}] ip : ${ip}`);
|
||||
|
||||
// TODO: 仮実装
|
||||
/*await this.accountService.updatePartnerAccount(
|
||||
context,
|
||||
targetAccountId,
|
||||
);
|
||||
*/
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
@ -13,6 +13,9 @@ import {
|
||||
ArrayMaxSize,
|
||||
ValidateNested,
|
||||
Max,
|
||||
IsString,
|
||||
IsNotEmpty,
|
||||
IsBoolean,
|
||||
} from 'class-validator';
|
||||
import { IsAdminPasswordvalid } from '../../../common/validators/admin.validator';
|
||||
import { IsUnique } from '../../../common/validators/IsUnique.validator';
|
||||
@ -398,6 +401,31 @@ export class DeletePartnerAccountRequest {
|
||||
targetAccountId: number;
|
||||
}
|
||||
|
||||
export class GetPartnerUsersRequest {
|
||||
@ApiProperty({ description: '取得対象のアカウントID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
targetAccountId: number;
|
||||
}
|
||||
|
||||
export class UpdatePartnerInfoRequest {
|
||||
@ApiProperty({ description: '変更対象アカウントID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
targetAccountId: number;
|
||||
|
||||
@ApiProperty({ description: 'プライマリ管理者ID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@Min(1)
|
||||
primaryAdminUserId: number;
|
||||
|
||||
@ApiProperty({ description: '会社名' })
|
||||
@MaxLength(255)
|
||||
companyName: string;
|
||||
}
|
||||
// ==============================
|
||||
// RESPONSE
|
||||
// ==============================
|
||||
@ -718,7 +746,37 @@ export class UpdateRestrictionStatusResponse {}
|
||||
export class SwitchParentResponse {}
|
||||
|
||||
export class DeletePartnerAccountResponse {}
|
||||
export class PartnerUser {
|
||||
@ApiProperty({ description: 'ユーザーID' })
|
||||
@Type(() => Number)
|
||||
@IsInt()
|
||||
@IsNotEmpty()
|
||||
id: number;
|
||||
|
||||
@ApiProperty({ description: 'ユーザー名' })
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
name: string;
|
||||
|
||||
@ApiProperty({ description: 'メールアドレス' })
|
||||
@IsEmail({ blacklisted_chars: '*' })
|
||||
@IsNotEmpty()
|
||||
email: string;
|
||||
|
||||
@ApiProperty({ description: 'プライマリ管理者かどうか' })
|
||||
@Type(() => Boolean)
|
||||
isPrimaryAdmin: boolean;
|
||||
}
|
||||
|
||||
export class GetPartnerUsersResponse {
|
||||
@ApiProperty({ type: [PartnerUser] })
|
||||
@IsArray()
|
||||
@ValidateNested({ each: true })
|
||||
@Type(() => PartnerUser)
|
||||
users: PartnerUser[];
|
||||
}
|
||||
|
||||
export class UpdatePartnerInfoResponse {}
|
||||
// ==============================
|
||||
// Request/Response外の型
|
||||
// TODO: Request/Response/その他の型を別ファイルに分ける
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user