Skip to content

Commit

Permalink
Internationalization
Browse files Browse the repository at this point in the history
  • Loading branch information
JessicaMulein committed Dec 23, 2024
1 parent eedebeb commit 8b03aa3
Show file tree
Hide file tree
Showing 25 changed files with 593 additions and 158 deletions.
3 changes: 2 additions & 1 deletion .devcontainer/devcontainer.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,8 @@
"GitHub.vscode-pull-request-github",
"MermaidChart.vscode-mermaid-chart",
"Graphite.gti-vscode",
"Orta.vscode-jest"
"Orta.vscode-jest",
"Google.geminicodeassist"
],
// Set *default* container specific settings.json values on container create.
"settings": {
Expand Down
3 changes: 2 additions & 1 deletion .vscode/extensions.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"ms-playwright.playwright",
"mermaidchart.vscode-mermaid-chart",
"graphite.gti-vscode",
"codeium.codeium"
"codeium.codeium",
"google.geminicodeassist"
]
}
53 changes: 25 additions & 28 deletions chili-and-cilantro-api/src/controllers/api/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,35 +69,30 @@ export class GameController extends BaseController {
.isString()
.trim()
.notEmpty()
.matches(
constants.GAME_NAME_REGEX,
constants.GAME_NAME_REGEX_ERROR,
),
body('username')
.isString()
.trim()
.notEmpty()
.matches(
constants.USERNAME_REGEX,
translate(StringNames.Validation_UsernameRegexErrorTemplate),
.matches(constants.GAME_NAME_REGEX)
.withMessage(
translate(StringNames.Validation_GameNameRegexErrorTemplate),
),
body('displayname')
.isString()
.trim()
.notEmpty()
.matches(
constants.USER_DISPLAY_NAME_REGEX,
.matches(constants.USER_DISPLAY_NAME_REGEX)
.withMessage(
translate(StringNames.Validation_DisplayNameRegexErrorTemplate),
),
body('password')
.optional()
.isString()
.trim()
.matches(
constants.PASSWORD_REGEX,
.matches(constants.PASSWORD_REGEX)
.withMessage(
translate(StringNames.Validation_PasswordRegexErrorTemplate),
),
body('maxChefs').isInt({ min: 2, max: 8 }),
body('maxChefs').isInt({
min: constants.MIN_CHEFS,
max: constants.MAX_CHEFS,
}),
],
}),
routeConfig<unknown[]>({
Expand All @@ -110,24 +105,24 @@ export class GameController extends BaseController {
.isString()
.trim()
.notEmpty()
.matches(
constants.USERNAME_REGEX,
.matches(constants.USERNAME_REGEX)
.withMessage(
translate(StringNames.Validation_UsernameRegexErrorTemplate),
),
body('password')
.optional()
.isString()
.trim()
.matches(
constants.PASSWORD_REGEX,
.matches(constants.PASSWORD_REGEX)
.withMessage(
translate(StringNames.Validation_PasswordRegexErrorTemplate),
),
body('displayname')
.isString()
.trim()
.notEmpty()
.matches(
constants.USER_DISPLAY_NAME_REGEX,
.matches(constants.USER_DISPLAY_NAME_REGEX)
.withMessage(
translate(StringNames.Validation_DisplayNameRegexErrorTemplate),
),
],
Expand Down Expand Up @@ -180,18 +175,20 @@ export class GameController extends BaseController {
private async createGame(req: Request, res: Response, next: NextFunction) {
try {
const user = await this.validateAndFetchRequestUser(req, res, next);
const { name, username, password, maxChefs } = req.body;
const { name, displayname, password, maxChefs } = req.validatedBody;
const sanitizedName = (name as string)?.trim();
const sanitizedUserName = (username as string)?.trim();
const sanitizedDisplayName = (displayname as string)
?.trim()
.toLowerCase();
const sanitizedPassword = (password as string)?.trim();
const sanitizedMaxChefs = parseInt(maxChefs, 10);

const { game, chef } = await this.gameService.performCreateGameAsync(
user,
sanitizedUserName,
sanitizedDisplayName,
sanitizedName,
sanitizedPassword,
sanitizedMaxChefs,
sanitizedPassword,
);
this.sendApiMessageResponse(
201,
Expand Down Expand Up @@ -252,7 +249,7 @@ export class GameController extends BaseController {
private async sendMessage(req: Request, res: Response, next: NextFunction) {
try {
const user = await this.validateAndFetchRequestUser(req, res, next);
const { message } = req.body;
const { message } = req.validatedBody;
const gameCode = req.params.code;
const sanitizedMessage = (message as string)?.trim();
const messageAction = await this.gameService.performSendMessageAsync(
Expand Down Expand Up @@ -359,7 +356,7 @@ export class GameController extends BaseController {
try {
const user = await this.validateAndFetchRequestUser(req, res, next);
const gameCode = req.params.code;
const { action, ingredient, bid } = req.body;
const { action, ingredient, bid } = req.validatedBody;
const actionArgs = {
...(ingredient ? { ingredient: ingredient as CardType } : {}),
...(bid ? { bid: bid as number } : {}),
Expand Down
2 changes: 1 addition & 1 deletion chili-and-cilantro-api/src/controllers/api/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,7 @@ export class UserController extends BaseController {
next: NextFunction,
): Promise<void> {
try {
const { username, email, password } = req.body;
const { username, email, password } = req.validatedBody;

const userDoc = await this.userService.findUser(
password,
Expand Down
26 changes: 12 additions & 14 deletions chili-and-cilantro-api/src/services/game.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,30 +133,31 @@ export class GameService extends BaseService {
user: IUserDocument,
displayName: string,
gameName: string,
password: string,
maxChefs: number,
password?: string,
): Promise<void> {
if (await this.playerService.userIsInAnyActiveGameAsync(user)) {
throw new ChefAlreadyJoinedError();
}
if (
!validator.matches(displayName, constants.MULTILINGUAL_STRING_REGEX) ||
!validator.matches(displayName, constants.USER_DISPLAY_NAME_REGEX) ||
displayName.length < constants.MIN_USER_DISPLAY_NAME_LENGTH ||
displayName.length > constants.MAX_USER_DISPLAY_NAME_LENGTH
) {
throw new InvalidUserDisplayNameError(displayName);
}
if (
!validator.matches(gameName, constants.MULTILINGUAL_STRING_REGEX) ||
!validator.matches(gameName, constants.GAME_NAME_REGEX) ||
gameName.length < constants.MIN_GAME_NAME_LENGTH ||
gameName.length > constants.MAX_GAME_NAME_LENGTH
) {
throw new InvalidGameNameError();
}
if (
!validator.matches(password, constants.GAME_PASSWORD_REGEX) ||
password.length < constants.MIN_GAME_PASSWORD_LENGTH ||
password.length > constants.MAX_GAME_PASSWORD_LENGTH
password !== undefined &&
(!validator.matches(password, constants.GAME_PASSWORD_REGEX) ||
password.length < constants.MIN_GAME_PASSWORD_LENGTH ||
password.length > constants.MAX_GAME_PASSWORD_LENGTH)
) {
throw new InvalidGamePasswordError();
}
Expand All @@ -183,8 +184,8 @@ export class GameService extends BaseService {
user: IUserDocument,
displayName: string,
gameName: string,
password: string,
maxChefs: number,
password?: string,
gameId: DefaultIdType = new Types.ObjectId(),
masterChefId: DefaultIdType = new Types.ObjectId(),
): Promise<{
Expand Down Expand Up @@ -243,24 +244,24 @@ export class GameService extends BaseService {
user: IUserDocument,
displayName: string,
gameName: string,
password: string,
maxChefs: number,
password?: string,
session?: ClientSession,
): Promise<IGameChef> {
return this.withTransaction<IGameChef>(async () => {
await this.validateCreateGameOrThrowAsync(
user,
displayName,
gameName,
password,
maxChefs,
password,
);
return this.createGameAsync(
user,
displayName,
gameName,
password,
maxChefs,
password,
);
}, session);
}
Expand Down Expand Up @@ -318,10 +319,7 @@ export class GameService extends BaseService {
throw new TooManyChefsError();
}
if (
!validator.matches(
userDisplayName,
constants.MULTILINGUAL_STRING_REGEX,
) ||
!validator.matches(userDisplayName, constants.USER_DISPLAY_NAME_REGEX) ||
userDisplayName.length < constants.MIN_USER_DISPLAY_NAME_LENGTH ||
userDisplayName.length > constants.MAX_USER_DISPLAY_NAME_LENGTH
) {
Expand Down
21 changes: 21 additions & 0 deletions chili-and-cilantro-api/test/fixtures/password.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { randomBytes } from 'crypto';

Check failure on line 1 in chili-and-cilantro-api/test/fixtures/password.ts

View workflow job for this annotation

GitHub Actions / Qodana for JS

ESLint

ESLint: Specify a correct path to the 'eslint' package

export function generateBcryptHash(): string {
const version = ['$2a', '$2b', '$2x', '$2y', '$2'][
Math.floor(Math.random() * 5)
];
const cost = String(Math.floor(Math.random() * 10) + 10).padStart(2, '0');
const saltAndHash =
randomBytes(22) // Generate 22 RANDOM bytes for the salt
.toString('base64') // Encode to base64
.replace(/\+/g, '.') // Replace invalid bcrypt characters
.replace(/\//g, '/')
.substring(0, 22) + // Take only the first 22 base64 characters for salt
randomBytes(31)
.toString('base64') // Generate 31 RANDOM bytes and encode for hash
.replace(/\+/g, '.')
.replace(/\//g, '/')
.substring(0, 31); // 31 base64 characters for hash

return `${version}$${cost}$${saltAndHash}`;
}
28 changes: 14 additions & 14 deletions chili-and-cilantro-api/test/unit/gameService.createGame.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).resolves.not.toThrow();
});
Expand All @@ -106,8 +106,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(ChefAlreadyJoinedError);
});
Expand All @@ -127,8 +127,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidUserDisplayNameError);
});
Expand All @@ -148,8 +148,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidUserDisplayNameError);
});
Expand All @@ -169,8 +169,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidUserDisplayNameError);
});
Expand All @@ -190,8 +190,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidGameNameError);
});
Expand All @@ -211,8 +211,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidGameNameError);
});
Expand All @@ -232,8 +232,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidGameNameError);
});
Expand All @@ -254,8 +254,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidGamePasswordError);
});
Expand All @@ -276,8 +276,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(InvalidGamePasswordError);
});
Expand All @@ -297,8 +297,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(NotEnoughChefsError);
});
Expand All @@ -318,8 +318,8 @@ describe('GameService', () => {
user,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow(TooManyChefsError);
});
Expand Down Expand Up @@ -389,8 +389,8 @@ describe('GameService', () => {
mockUser,
gameUserName,
gameName,
password,
maxChefs,
password,
mockGame._id,
mockChef._id,
);
Expand Down Expand Up @@ -494,8 +494,8 @@ describe('GameService', () => {
mockUser,
displayName,
gameName,
password,
maxChefs,
password,
),
).rejects.toThrow('Validation failed');
});
Expand Down
Loading

0 comments on commit 8b03aa3

Please sign in to comment.