-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[TM-1641] Handle user locale updating in v3.
- Loading branch information
Showing
8 changed files
with
245 additions
and
107 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,12 @@ | ||
/* eslint-disable */ | ||
export default { | ||
displayName: 'user-service', | ||
preset: '../../jest.preset.js', | ||
testEnvironment: 'node', | ||
displayName: "user-service", | ||
preset: "../../jest.preset.js", | ||
testEnvironment: "node", | ||
transform: { | ||
'^.+\\.[tj]s$': ['ts-jest', { tsconfig: '<rootDir>/tsconfig.spec.json' }], | ||
"^.+\\.[tj]s$": ["ts-jest", { tsconfig: "<rootDir>/tsconfig.spec.json" }] | ||
}, | ||
moduleFileExtensions: ['ts', 'js', 'html'], | ||
coverageDirectory: '../../coverage/apps/user-service', | ||
moduleFileExtensions: ["ts", "js", "html"], | ||
coveragePathIgnorePatterns: [".dto.ts"], | ||
coverageDirectory: "../../coverage/apps/user-service" | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { Equals, IsEnum, IsUUID, ValidateNested } from "class-validator"; | ||
import { ApiProperty } from "@nestjs/swagger"; | ||
import { Type } from "class-transformer"; | ||
|
||
const VALID_LOCALES = ["en-US", "es-MX", "fr-FR", "pt-BR"]; | ||
|
||
class UserUpdateAttributes { | ||
@IsEnum(VALID_LOCALES) | ||
@ApiProperty({ description: "New default locale for the given user", nullable: true, enum: VALID_LOCALES }) | ||
locale?: string | null; | ||
} | ||
|
||
class UserUpdate { | ||
@Equals("users") | ||
@ApiProperty({ enum: ["users"] }) | ||
type: string; | ||
|
||
@IsUUID() | ||
@ApiProperty({ format: "uuid" }) | ||
id: string; | ||
|
||
@ValidateNested() | ||
@Type(() => UserUpdateAttributes) | ||
@ApiProperty({ type: () => UserUpdateAttributes }) | ||
attributes: UserUpdateAttributes; | ||
} | ||
|
||
export class UserUpdateBodyDto { | ||
@ValidateNested() | ||
@Type(() => UserUpdate) | ||
@ApiProperty({ type: () => UserUpdate }) | ||
data: UserUpdate; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,76 +1,113 @@ | ||
import { | ||
BadRequestException, | ||
Body, | ||
Controller, | ||
Get, | ||
NotFoundException, | ||
Param, | ||
Patch, | ||
Request, | ||
UnauthorizedException, | ||
} from '@nestjs/common'; | ||
import { User } from '@terramatch-microservices/database/entities'; | ||
import { PolicyService } from '@terramatch-microservices/common'; | ||
import { ApiOperation, ApiParam } from '@nestjs/swagger'; | ||
import { OrganisationDto, UserDto } from '@terramatch-microservices/common/dto'; | ||
import { ApiException } from '@nanogiants/nestjs-swagger-api-exception-decorator'; | ||
import { JsonApiResponse } from '@terramatch-microservices/common/decorators'; | ||
import { | ||
buildJsonApi, | ||
JsonApiDocument, | ||
} from '@terramatch-microservices/common/util'; | ||
UnauthorizedException | ||
} from "@nestjs/common"; | ||
import { User } from "@terramatch-microservices/database/entities"; | ||
import { PolicyService } from "@terramatch-microservices/common"; | ||
import { ApiOperation, ApiParam } from "@nestjs/swagger"; | ||
import { OrganisationDto, UserDto } from "@terramatch-microservices/common/dto"; | ||
import { ApiException } from "@nanogiants/nestjs-swagger-api-exception-decorator"; | ||
import { JsonApiResponse } from "@terramatch-microservices/common/decorators"; | ||
import { buildJsonApi, DocumentBuilder } from "@terramatch-microservices/common/util"; | ||
import { UserUpdateBodyDto } from "./dto/user-update.dto"; | ||
|
||
const USER_RESPONSE_SHAPE = { | ||
data: { | ||
type: UserDto, | ||
relationships: [ | ||
{ | ||
name: "org", | ||
type: OrganisationDto, | ||
meta: { | ||
userStatus: { | ||
type: "string", | ||
enum: ["approved", "requested", "rejected", "na"] | ||
} | ||
} | ||
} | ||
] | ||
}, | ||
included: [{ type: OrganisationDto }] | ||
}; | ||
|
||
@Controller('users/v3/users') | ||
@Controller("users/v3/users") | ||
export class UsersController { | ||
constructor(private readonly policyService: PolicyService) {} | ||
|
||
@Get(':id') | ||
@ApiOperation({ operationId: 'usersFind', description: "Fetch a user by ID, or with the 'me' identifier" }) | ||
@ApiParam({ name: 'id', example: 'me', description: 'A valid user id or "me"' }) | ||
@JsonApiResponse({ | ||
data: { | ||
type: UserDto, | ||
relationships: [ | ||
{ | ||
name: 'org', | ||
type: OrganisationDto, | ||
meta: { | ||
userStatus: { | ||
type: 'string', | ||
enum: ['approved', 'requested', 'rejected', 'na'], | ||
}, | ||
}, | ||
}, | ||
], | ||
}, | ||
included: [{ type: OrganisationDto }], | ||
@Get(":uuid") | ||
@ApiOperation({ operationId: "usersFind", description: "Fetch a user by ID, or with the 'me' identifier" }) | ||
@ApiParam({ name: "uuid", example: "me", description: 'A valid user uuid or "me"' }) | ||
@JsonApiResponse(USER_RESPONSE_SHAPE) | ||
@ApiException(() => UnauthorizedException, { | ||
description: "Authorization failed" | ||
}) | ||
@ApiException(() => NotFoundException, { | ||
description: "User with that ID not found" | ||
}) | ||
async findOne(@Param("uuid") pathId: string, @Request() { authenticatedUserId }) { | ||
const userWhere = pathId === "me" ? { id: authenticatedUserId } : { uuid: pathId }; | ||
const user = await User.findOne({ | ||
include: ["roles", "organisation", "frameworks"], | ||
where: userWhere | ||
}); | ||
if (user == null) throw new NotFoundException(); | ||
|
||
await this.policyService.authorize("read", user); | ||
|
||
return (await this.addUserResource(buildJsonApi(), user)).serialize(); | ||
} | ||
|
||
@Patch(":uuid") | ||
@ApiOperation({ operationId: "userUpdate", description: "Update a user by ID" }) | ||
@ApiParam({ name: "uuid", description: "A valid user uuid" }) | ||
@JsonApiResponse(USER_RESPONSE_SHAPE) | ||
@ApiException(() => UnauthorizedException, { | ||
description: 'Authorization failed', | ||
description: "Authorization failed" | ||
}) | ||
@ApiException(() => NotFoundException, { | ||
description: 'User with that ID not found', | ||
description: "User with that ID not found" | ||
}) | ||
async findOne( | ||
@Param('id') pathId: string, | ||
@Request() { authenticatedUserId } | ||
): Promise<JsonApiDocument> { | ||
const userId = pathId === 'me' ? authenticatedUserId : parseInt(pathId); | ||
@ApiException(() => BadRequestException, { description: "Something is malformed about the request" }) | ||
async update(@Param("uuid") uuid: string, @Body() updatePayload: UserUpdateBodyDto) { | ||
if (uuid !== updatePayload.data.id) { | ||
throw new BadRequestException(`Path uuid and payload id do not match`); | ||
} | ||
|
||
const user = await User.findOne({ | ||
include: ['roles', 'organisation', 'frameworks'], | ||
where: { id: userId }, | ||
include: ["roles", "organisation", "frameworks"], | ||
where: { uuid } | ||
}); | ||
if (user == null) throw new NotFoundException(); | ||
|
||
await this.policyService.authorize('read', user); | ||
await this.policyService.authorize("update", user); | ||
|
||
// The only thing allowed to update for now is the locale | ||
const { locale } = updatePayload.data.attributes; | ||
if (locale != null) { | ||
user.locale = locale; | ||
await user.save(); | ||
} | ||
|
||
return (await this.addUserResource(buildJsonApi(), user)).serialize(); | ||
} | ||
|
||
const document = buildJsonApi(); | ||
private async addUserResource(document: DocumentBuilder, user: User) { | ||
const userResource = document.addData(user.uuid, new UserDto(user, await user.myFrameworks())); | ||
|
||
const org = await user.primaryOrganisation(); | ||
if (org != null) { | ||
const orgResource = document.addIncluded(org.uuid, new OrganisationDto(org)); | ||
const userStatus = org.OrganisationUser?.status ?? 'na'; | ||
userResource.relateTo('org', orgResource, { userStatus }); | ||
const userStatus = org.OrganisationUser?.status ?? "na"; | ||
userResource.relateTo("org", orgResource, { userStatus }); | ||
} | ||
|
||
return document.serialize(); | ||
return document; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.