Skip to content

Commit

Permalink
Merge pull request #16 from ribeirogab/feature/validation
Browse files Browse the repository at this point in the history
feat: add validation
  • Loading branch information
ribeirogab authored Sep 22, 2024
2 parents 3b0f3f3 + e651c52 commit e93ffc9
Show file tree
Hide file tree
Showing 15 changed files with 121 additions and 22 deletions.
11 changes: 10 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@
"tsyringe": "^4.8.0",
"uuid": "^10.0.0",
"winston": "^3.14.2",
"zod": "^3.23.8"
"zod": "^3.23.8",
"zod-to-json-schema": "^3.23.3"
},
"devDependencies": {
"@ribeirogab/eslint-config": "^2.0.1",
Expand Down
12 changes: 3 additions & 9 deletions src/middlewares/error-handling.middleware.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { FastifyError, FastifyReply, FastifyRequest } from 'fastify';
import { inject, injectable } from 'tsyringe';
import { ZodError } from 'zod';

import { AppErrorCodeEnum, HttpStatusCodesEnum } from '@/constants';
import { AppError } from '@/errors';
Expand All @@ -26,21 +25,16 @@ export class ErrorHandlingMiddleware implements ErrorMiddleware {
});
}

if (error instanceof ZodError) {
if (error.code === 'FST_ERR_VALIDATION') {
const errorBody = {
status_code: HttpStatusCodesEnum.BAD_REQUEST,
error_code: AppErrorCodeEnum.ValidationError,
message: 'Payload validation error',
details: error.issues,
message: error.message,
};

this.logger.error('Payload validation error', errorBody);

return reply.status(errorBody.status_code).send({
status_code: HttpStatusCodesEnum.BAD_REQUEST,
message: 'Payload validation error',
details: error.issues,
});
return reply.status(errorBody.status_code).send(errorBody);
}

if (
Expand Down
3 changes: 2 additions & 1 deletion src/repositories/session.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ export class SessionRepository implements SessionRepositoryInterface {
private readonly PK = 'session';

constructor(
@inject('DynamoConfig') private readonly dynamoConfig: DynamoConfig,
@inject('DynamoConfig')
private readonly dynamoConfig: DynamoConfig,
) {}

public async upsert(session: Session): Promise<Session> {
Expand Down
28 changes: 25 additions & 3 deletions src/repositories/user.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
} from '@aws-sdk/client-dynamodb';
import { marshall, unmarshall } from '@aws-sdk/util-dynamodb';
import { inject, injectable } from 'tsyringe';
import { z } from 'zod';

import {
type DynamoConfig,
Expand All @@ -30,23 +31,31 @@ import type {
@injectable()
export class UserRepository implements UserRepositoryInterface {
private readonly PK = DynamoPartitionKeysEnum.User;
private readonly schema: z.ZodType<Omit<User, 'id'>> = z.object({
name: z.string().min(2).max(255),
email: z.string().email(),
});

constructor(
@inject('UniqueIdAdapter')
private readonly uniqueIdAdapter: UniqueIdAdapter,

@inject('DynamoConfig') private readonly dynamoConfig: DynamoConfig,
@inject('DynamoConfig')
private readonly dynamoConfig: DynamoConfig,

@inject('LoggerAdapter') private readonly logger: LoggerAdapter,
@inject('LoggerAdapter')
private readonly logger: LoggerAdapter,
) {
this.logger.setPrefix(this.logger, UserRepository.name);
}

public async create(dto: Omit<User, 'id'>): Promise<User> {
try {
const { data: parsedUser } = this.parse(dto);

const user: User = {
id: this.uniqueIdAdapter.generate(),
...dto,
...parsedUser,
created_at: new Date().toISOString(),
};

Expand Down Expand Up @@ -103,4 +112,17 @@ export class UserRepository implements UserRepositoryInterface {
throw error;
}
}

private parse(user: Omit<User, 'id'>): {
data: Omit<User, 'id'>;
success: boolean;
} {
const { success, data, error } = this.schema.safeParse(user);

if (!success || !data) {
throw new Error(error?.message || 'Error parsing user');
}

return { success, data };
}
}
6 changes: 4 additions & 2 deletions src/repositories/verification-code.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ export class VerificationCodeRepository
private readonly PK = DynamoPartitionKeysEnum.VerificationCode;

constructor(
@inject('DynamoConfig') private readonly dynamoConfig: DynamoConfig,
@inject('DynamoConfig')
private readonly dynamoConfig: DynamoConfig,

@inject('LoggerAdapter') private readonly logger: LoggerAdapter,
@inject('LoggerAdapter')
private readonly logger: LoggerAdapter,
) {
this.logger.setPrefix(this.logger, VerificationCodeRepository.name);
}
Expand Down
9 changes: 8 additions & 1 deletion src/routers/auth.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { inject, injectable } from 'tsyringe';
import type { AuthController } from '@/controllers';
import type { Router } from '@/interfaces';
import type { EnsureAuthenticatedMiddleware } from '@/middlewares';
import { loginConfirmSchema, loginRefreshSchema, loginSchema } from '@/schemas';

@injectable()
export class AuthRouter implements Router {
Expand All @@ -20,16 +21,22 @@ export class AuthRouter implements Router {
_?: unknown,
done?: (err?: Error) => void,
) {
app.post('/login', this.authController.login.bind(this.authController));
app.post(
'/login',
{ schema: loginSchema },
this.authController.login.bind(this.authController),
);

app.post(
'/login/confirm',
{ schema: loginConfirmSchema },
this.authController.loginConfirm.bind(this.authController),
);

app.post(
'/login/refresh',
{
schema: loginRefreshSchema,
preHandler: [
this.ensureAuthenticatedMiddleware.middleware.bind(
this.ensureAuthenticatedMiddleware,
Expand Down
3 changes: 3 additions & 0 deletions src/routers/registration.router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { inject, injectable } from 'tsyringe';

import type { RegistrationController } from '@/controllers';
import type { Router } from '@/interfaces';
import { registrationConfirmSchema, registrationSchema } from '@/schemas';

@injectable()
export class RegistrationRouter implements Router {
Expand All @@ -19,6 +20,7 @@ export class RegistrationRouter implements Router {
app.post(
'/',
{
schema: registrationSchema,
config: {
rateLimit: {
timeWindow: 1000 * 60, // 1 minute
Expand All @@ -33,6 +35,7 @@ export class RegistrationRouter implements Router {

app.post(
'/confirm',
{ schema: registrationConfirmSchema },
this.registrationController.registrationConfirm.bind(
this.registrationController,
),
Expand Down
5 changes: 5 additions & 0 deletions src/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export * from './registration-confirm.schema';
export * from './login-confirm.schema';
export * from './login-refresh.schema';
export * from './registration.schema';
export * from './login.schema';
12 changes: 12 additions & 0 deletions src/schemas/login-confirm.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { FastifySchema } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

export const loginConfirmSchema: FastifySchema = {
body: zodToJsonSchema(
z.object({
code: z.string().min(6).max(6),
token: z.string(),
}),
),
};
11 changes: 11 additions & 0 deletions src/schemas/login-refresh.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { FastifySchema } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

export const loginRefreshSchema: FastifySchema = {
body: zodToJsonSchema(
z.object({
refresh_token: z.string(),
}),
),
};
11 changes: 11 additions & 0 deletions src/schemas/login.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import type { FastifySchema } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

export const loginSchema: FastifySchema = {
body: zodToJsonSchema(
z.object({
email: z.string().email(),
}),
),
};
12 changes: 12 additions & 0 deletions src/schemas/registration-confirm.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { FastifySchema } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

export const registrationConfirmSchema: FastifySchema = {
body: zodToJsonSchema(
z.object({
code: z.string().min(6).max(6),
token: z.string(),
}),
),
};
12 changes: 12 additions & 0 deletions src/schemas/registration.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { FastifySchema } from 'fastify';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';

export const registrationSchema: FastifySchema = {
body: zodToJsonSchema(
z.object({
name: z.string().min(2).max(255),
email: z.string().email(),
}),
),
};
5 changes: 1 addition & 4 deletions src/services/registration-confirm.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ export class RegistrationConfirmService
verificationCode,
);

// To do: Validate the user data
const user = userData as Omit<User, 'id'>;

return user;
return userData as Omit<User, 'id'>;
}
}

0 comments on commit e93ffc9

Please sign in to comment.