Skip to content

Commit

Permalink
fix: filename validation error
Browse files Browse the repository at this point in the history
  • Loading branch information
jorgevrgs committed Jun 14, 2024
1 parent f3bc1d2 commit 3c30797
Show file tree
Hide file tree
Showing 22 changed files with 196 additions and 46 deletions.
28 changes: 28 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Next.js: debug server-side",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev"
},
{
"name": "Next.js: debug client-side",
"type": "chrome",
"request": "launch",
"url": "http://localhost:3000"
},
{
"name": "Next.js: debug full stack",
"type": "node-terminal",
"request": "launch",
"command": "pnpm dev",
"serverReadyAction": {
"pattern": "- Local:.+(https?://.+)",
"uriFormat": "%s",
"action": "debugWithChrome"
}
}
]
}
3 changes: 2 additions & 1 deletion apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"license": "MIT",
"private": true,
"scripts": {
"dev": "next dev",
"dev": "next dev | pino-pretty -c -t",
"build": "next build",
"start": "next start",
"lint": "next lint",
Expand Down Expand Up @@ -40,6 +40,7 @@
"jsdom": "20.0.0",
"next-connect": "0.13.0",
"next-transpile-modules": "10.0.1",
"pino-pretty": "11.2.1",
"postcss": "8.4.38",
"react-test-renderer": "18.2.0",
"sass": "1.54.9",
Expand Down
File renamed without changes.
File renamed without changes.
24 changes: 21 additions & 3 deletions libs/backend/src/application/utils/error.util.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { logger } from '@libs/shared';
import { NextApiRequest, NextApiResponse } from 'next';
import { ResponseCode, ResponseStatus } from '../../domain';
import type { ValidationError } from '../../domain/schemas';
import { validationErrorValidator } from '../../domain/schemas';

export function tryCatchAsync<T>(
cb: (req: NextApiRequest, res: NextApiResponse<T>) => Promise<void>
Expand All @@ -8,9 +11,24 @@ export function tryCatchAsync<T>(
cb(req, res).catch((error) => {
logger.error(error);

res.status(400).json({
error: error instanceof Error ? error.message : 'Bad Request',
});
validationErrorValidator
.validate(error)
.then((validationError: ValidationError) => {
res.status(validationError.status).json({
code: ResponseCode.VALIDATION_ERROR,
message: error.message,
status: ResponseStatus.ERROR,
errors: validationError.messages,
});
})
.catch(() => {
res.status(400).json({
code: ResponseCode.UNKNOWN_ERROR,
message: error.message,
status: ResponseStatus.ERROR,
errors: [error],
});
});
});
};
}
1 change: 1 addition & 0 deletions libs/backend/src/domain/constants/collections.constant.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export const CELEBRITIES_COLLECTION = 'celebrities';

export const IMAGES_COLLECTION = 'images';
1 change: 1 addition & 0 deletions libs/backend/src/domain/constants/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './collections.constant';
export * from './regex.constant';
export * from './response.constant';
3 changes: 1 addition & 2 deletions libs/backend/src/domain/constants/regex.constant.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
export const MONGO_ID_REGEX = /^[0-9a-fA-F]{24}$/;

export const FILENAME_REGEX =
/^[a-zA-Z0-9]+(\.png|\.webp|\.jpg|\.jpeg|\.gif|\.bmp)$/;
export const FILENAME_REGEX = /^[a-zA-Z0-9]+\.(png|webp|jpg|jpeg|gif|bmp)$/;
10 changes: 10 additions & 0 deletions libs/backend/src/domain/constants/response.constant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
export enum ResponseStatus {
SUCCESS = 'SUCCESS',
ERROR = 'ERROR',
}

export enum ResponseCode {
VALIDATION_ERROR = 'VALIDATION_ERROR',
SERVER_ERROR = 'SERVER_ERROR',
UNKNOWN_ERROR = 'UNKNOWN_ERROR',
}
1 change: 1 addition & 0 deletions libs/backend/src/domain/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './request.schema';
export * from './response.schema';
export * from './validation-error.schema';
12 changes: 12 additions & 0 deletions libs/backend/src/domain/schemas/request.schema.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { describe, expect, it } from 'vitest';
import { getImageByFilenameParamsValidator } from './request.schema';

describe('request.schema', () => {
it('should validate a filename', async () => {
const actual = await getImageByFilenameParamsValidator.validate({
filename: 'francis.webp',
});

expect(actual).toStrictEqual({ filename: 'francis.webp' });
});
});
6 changes: 2 additions & 4 deletions libs/backend/src/domain/schemas/request.schema.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
import vine from '@vinejs/vine';
import { FILENAME_REGEX, MONGO_ID_REGEX } from '../constants';

const cleanStringSchema = vine.string().trim().escape().toLowerCase();

export const updateVoteBodySchema = vine.object({
vote: vine.enum(['positive', 'negative']),
});

export const updateVoteBodyValidator = vine.compile(updateVoteBodySchema);

export const updateVoteParamsSchema = vine.object({
id: cleanStringSchema.regex(MONGO_ID_REGEX),
celebrityId: vine.string().trim().toLowerCase().regex(MONGO_ID_REGEX),
});

export const updateVoteParamsValidator = vine.compile(updateVoteParamsSchema);

export const getImageByFilenameParamsSchema = vine.object({
filename: cleanStringSchema.regex(FILENAME_REGEX),
filename: vine.string().trim().toLowerCase().regex(FILENAME_REGEX),
});

export const getImageByFilenameParamsValidator = vine.compile(
Expand Down
8 changes: 4 additions & 4 deletions libs/backend/src/domain/schemas/response.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ import vine from '@vinejs/vine';
import { MONGO_ID_REGEX } from '../constants';

export const updateVoteResponseSchema = vine.object({
celebrityId: vine.string().regex(MONGO_ID_REGEX),
celebrityId: vine.string().trim().regex(MONGO_ID_REGEX),
votes: vine.object({
positive: vine.number(),
negative: vine.number(),
}),
name: vine.string(),
picture: vine.string(),
description: vine.string().optional(),
name: vine.string().trim(),
picture: vine.string().trim(),
description: vine.string().trim().optional(),
lastUpdated: vine.date(),
active: vine.boolean(),
});
Expand Down
18 changes: 18 additions & 0 deletions libs/backend/src/domain/schemas/validation-error.schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import vine from '@vinejs/vine';
import { Infer } from '@vinejs/vine/build/src/types';

export const validationErrorSchema = vine.object({
status: vine.number(),
code: vine.string(),
messages: vine.array(
vine.object({
message: vine.string(),
rule: vine.string(),
field: vine.string(),
})
),
});

export const validationErrorValidator = vine.compile(validationErrorSchema);

export type ValidationError = Infer<typeof validationErrorSchema>;
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from '@libs/shared';
import { NextApiRequest, NextApiResponse } from 'next';
import sharp from 'sharp';
import { findOneByNameBucket } from '../../application';
Expand All @@ -7,10 +8,10 @@ export async function getCoverByNameController(
req: NextApiRequest,
res: NextApiResponse
) {
logger.info(req.query, 'getCoverByNameController');

// Validate query params
const [params] = await Promise.all([
getImageByFilenameParamsValidator.validate(req.query),
]);
const params = await getImageByFilenameParamsValidator.validate(req.query);

const { filename } = params;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { logger } from '@libs/shared';
import { NextApiRequest, NextApiResponse } from 'next';
import sharp from 'sharp';
import { findOneByNameBucket } from '../../application';
Expand All @@ -7,10 +8,10 @@ export async function getImageByNameController(
req: NextApiRequest,
res: NextApiResponse
) {
logger.info(req.query, 'getImageByNameController');

// Validate query params
const [params] = await Promise.all([
getImageByFilenameParamsValidator.validate(req.query),
]);
const params = await getImageByFilenameParamsValidator.validate(req.query);

const { filename } = params;

Expand Down
File renamed without changes.
13 changes: 8 additions & 5 deletions libs/frontend/src/infrastructure/components/featured-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,18 @@ export default function FeaturedCard() {
return null;
}

const cover = featuredCelebrity.picture
.replace('/images/', '/cover/')
.replace('.png', '.webp');
const picture = featuredCelebrity.picture.replace('.png', '.webp');

return (
<div className="contents lg:relative lg:w-full lg:max-w-5xl lg:mx-auto relative">
<div className="absolute inset-0 ">
<Image
layout="fill"
className="w-full h-full"
src={featuredCelebrity.picture
.replace('/images/', '/cover/')
.replace('.png', '.webp')}
src={cover}
alt={featuredCelebrity.name}
priority={true}
objectFit="cover"
Expand All @@ -40,7 +43,7 @@ export default function FeaturedCard() {
var(--color-dark-background),
var(--color-dark-background)
),
calc(-50vw + 650px) -6rem/105vw auto no-repeat url(${featuredCelebrity.picture});
calc(-50vw + 650px) -6rem/105vw auto no-repeat url(${picture});
filter: blur(1rem);
}
Expand All @@ -50,7 +53,7 @@ export default function FeaturedCard() {
var(--color-dark-background),
var(--color-dark-background)
),
7vw -6.5rem/115vw auto no-repeat url(${featuredCelebrity.picture});
7vw -6.5rem/115vw auto no-repeat url(${picture});
}
`}</style>

Expand Down
Loading

0 comments on commit 3c30797

Please sign in to comment.