Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added i18n to backend #4

Merged
merged 5 commits into from
Sep 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,089 changes: 885 additions & 204 deletions package-lock.json

Large diffs are not rendered by default.

7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -26,12 +26,12 @@
"@fastify/swagger": "^8.10.0",
"@fastify/swagger-ui": "^1.9.3",
"@prisma/client": "^5.3.1",
"ajv-errors": "^3.0.0",
"bcrypt": "^5.1.1",
"dotenv": "^16.3.1",
"fastify": "^4.23.2",
"fastify-plugin": "^4.5.1",
"fastify-zod": "^1.4.0",
"zod": "^3.22.2"
"fastify-i18n": "^1.1.1",
"fastify-plugin": "^4.5.1"
},
"devDependencies": {
"@swc/core": "^1.3.85",
Expand All @@ -45,6 +45,7 @@
"eslint": "^8.49.0",
"eslint-config-prettier": "^9.0.0",
"jest": "^29.7.0",
"json-schema-to-ts": "^2.9.2",
Anders164a marked this conversation as resolved.
Show resolved Hide resolved
"pino-pretty": "^10.2.0",
"prettier": "^3.0.3",
"prisma": "^5.3.1",
Expand Down
6 changes: 6 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ const getLoggerConfig = () => {
export async function build() {
const fastify = Fastify({
logger: getLoggerConfig(),
ajv: {
customOptions: {
allErrors: true,
},
plugins: [require('ajv-errors')],
},
Anders164a marked this conversation as resolved.
Show resolved Hide resolved
});

const startPlugins = performance.now();
Expand Down
27 changes: 27 additions & 0 deletions src/locales/da.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"email": {
"required": "Email er påkrævet",
"format": "Email skal være af korrekt format",
"type": "Email skal være en tekst",
"inUse": "Denne email er allerede i brug"
},
"name": {
"required": "Navn er påkrævet",
"type": "Navn skal være en tekst",
"minLength": "Du skal vælge et navn"
},
"password": {
"required": "Adgangskode er påkrævet",
"type": "Adgangskode skal være en tekst",
"minLength": "Adgangskode skal minimum være 8 karakterer",
"incorrect": "Adgangskode er forkert"
},
"user": {
"notFound": "Brugeren blev ikke fundet",
"emailOrPasswordIncorrect": "Email og/eller adgangskode er forkert"
},
"refreshToken": {
"expired": "Refresh token er udløbet",
"used": "Refresh token er allerede blevet brugt"
}
}
27 changes: 27 additions & 0 deletions src/locales/en.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"email": {
"required": "Email is required",
"format": "Email must be of correct format",
"type": "Email must be a string",
"inUse": "Email is already in use"
},
"name": {
"required": "Name is required",
"type": "Name must be a string",
"minLength": "You must choose a name"
},
"password": {
"required": "Password is required",
"type": "Password must be a string",
"minLength": "Password must be atleast 8 characters",
"incorrect": "Password incorrect"
},
"user": {
"notFound": "User not found",
"emailOrPasswordIncorrect": "Email and/or password incorrect"
},
"refreshToken": {
"expired": "Refresh token has reached absolute expiry",
"used": "Refresh token has already been used"
}
}
36 changes: 31 additions & 5 deletions src/modules/auth/__test__/login.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ describe('POST /api/auth/login', () => {
expect(response.statusCode).toBe(401);
expect(response.json()).toMatchObject({
error: 'Unauthorized',
message: 'email and/or password incorrect',
message: 'Email and/or password incorrect',
statusCode: 401,
});
});
Expand All @@ -75,7 +75,7 @@ describe('POST /api/auth/login', () => {
expect(response.statusCode).toBe(401);
expect(response.json()).toMatchObject({
error: 'Unauthorized',
message: 'email and/or password incorrect',
message: 'Email and/or password incorrect',
statusCode: 401,
});
});
Expand All @@ -90,7 +90,29 @@ describe('POST /api/auth/login', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'email'",
errors: {
_: ['Email is required', 'Password is required'],
},
statusCode: 400,
});
});

it('should return status 400, when no email or password has been provided, in danish', async () => {
const response = await global.fastify.inject({
method: 'POST',
url: '/api/auth/login',
payload: {},
headers: {
'accept-language': 'da',
},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
errors: {
_: ['Email er påkrævet', 'Adgangskode er påkrævet'],
},
statusCode: 400,
});
});
Expand All @@ -107,7 +129,9 @@ describe('POST /api/auth/login', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'email'",
errors: {
_: ['Email is required'],
},
statusCode: 400,
});
});
Expand All @@ -124,7 +148,9 @@ describe('POST /api/auth/login', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'password'",
errors: {
_: ['Password is required'],
},
statusCode: 400,
});
});
Expand Down
69 changes: 61 additions & 8 deletions src/modules/auth/__test__/register.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: 'body/email must match format "email"',
errors: {
email: ['Email must be of correct format'],
},
statusCode: 400,
});
});
Expand All @@ -87,7 +89,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'email'",
errors: {
_: ['Email is required'],
},
statusCode: 400,
});
});
Expand All @@ -106,7 +110,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: 'body/email must NOT have fewer than 1 characters',
errors: {
email: ['Email must be of correct format'],
},
statusCode: 400,
});
});
Expand All @@ -124,7 +130,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'password'",
errors: {
_: ['Password is required'],
},
statusCode: 400,
});
});
Expand All @@ -143,7 +151,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: 'body/password must NOT have fewer than 8 characters',
errors: {
password: ['Password must be atleast 8 characters'],
},
statusCode: 400,
});
});
Expand All @@ -162,7 +172,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: 'body/password must NOT have fewer than 8 characters',
errors: {
password: ['Password must be atleast 8 characters'],
},
statusCode: 400,
});
});
Expand All @@ -180,7 +192,9 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: "body must have required property 'name'",
errors: {
_: ['Name is required'],
},
statusCode: 400,
});
});
Expand All @@ -199,7 +213,46 @@ describe('POST /api/auth/register', () => {
expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
message: 'body/name must NOT have fewer than 1 characters',
errors: {
name: ['You must choose a name'],
},
statusCode: 400,
});
});

it('should return status 400, when name, email and password is not provided', async () => {
const response = await global.fastify.inject({
method: 'POST',
url: '/api/auth/register',
payload: {},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
errors: {
_: ['Email is required', 'Password is required', 'Name is required'],
},
statusCode: 400,
});
});

it('should return status 400, when name, email and password is not provided, in danish', async () => {
const response = await global.fastify.inject({
method: 'POST',
url: '/api/auth/register',
payload: {},
headers: {
'accept-language': 'da',
},
});

expect(response.statusCode).toBe(400);
expect(response.json()).toMatchObject({
error: 'Bad Request',
errors: {
_: ['Email er påkrævet', 'Adgangskode er påkrævet', 'Navn er påkrævet'],
},
statusCode: 400,
});
});
Expand Down
6 changes: 3 additions & 3 deletions src/modules/auth/auth.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default class AuthController {
return reply.code(201).send(user);
} catch (e) {
if (e instanceof Error) {
return reply.badRequest(e.message);
return reply.badRequest(request.i18n.t(e.message));
}

/* istanbul ignore next */
Expand All @@ -46,7 +46,7 @@ export default class AuthController {
const user = await this.userService.getUserByEmail(request.body.email);

if (!this.authService.verifyPassword(user.password, request.body.password)) {
throw new Error('password incorrect');
throw new Error(request.i18n.t('password.incorrect'));
}

const { refreshToken, refreshTokenPayload, accessToken } =
Expand All @@ -65,7 +65,7 @@ export default class AuthController {
accessToken: accessToken,
});
} catch (e) {
return reply.unauthorized('email and/or password incorrect');
return reply.unauthorized(request.i18n.t('user.emailOrPasswordIncorrect'));
}
}

Expand Down
15 changes: 7 additions & 8 deletions src/modules/auth/auth.route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { FastifyInstance } from 'fastify';
import AuthController from './auth.controller';
import { $ref } from './auth.schema';
import AuthService from './auth.service';
import UserService from './user.service';

Expand All @@ -12,9 +11,9 @@ export default async (fastify: FastifyInstance) => {
{
schema: {
tags: ['Auth'],
body: $ref('createUserSchema'),
body: { $ref: 'createUserSchema' },
response: {
201: $ref('createUserResponseSchema'),
201: { $ref: 'createUserResponseSchema' },
},
},
},
Expand All @@ -26,9 +25,9 @@ export default async (fastify: FastifyInstance) => {
{
schema: {
tags: ['Auth'],
body: $ref('loginSchema'),
body: { $ref: 'loginSchema' },
response: {
200: $ref('loginResponseSchema'),
200: { $ref: 'loginResponseSchema' },
},
},
},
Expand All @@ -41,7 +40,7 @@ export default async (fastify: FastifyInstance) => {
schema: {
tags: ['Auth'],
response: {
200: $ref('refreshResponseSchema'),
200: { $ref: 'refreshResponseSchema' },
},
description: 'The `refreshToken` cookie is required',
},
Expand All @@ -55,7 +54,7 @@ export default async (fastify: FastifyInstance) => {
schema: {
tags: ['Auth'],
response: {
200: $ref('logoutResponseSchema'),
200: { $ref: 'logoutResponseSchema' },
},
},
onRequest: [fastify.authenticate],
Expand All @@ -72,7 +71,7 @@ export default async (fastify: FastifyInstance) => {
},
tags: ['Auth'],
response: {
200: $ref('userResponseSchema'),
200: { $ref: 'userResponseSchema' },
},
},
onRequest: [fastify.authenticate],
Expand Down
Loading