Skip to content

Commit

Permalink
Merge branch 'staging' into feature/update-excute-and-checkout-api
Browse files Browse the repository at this point in the history
  • Loading branch information
quannhg committed Dec 4, 2023
2 parents 0698d0d + 1d35c77 commit 113200c
Show file tree
Hide file tree
Showing 11 changed files with 215 additions and 17 deletions.
5 changes: 3 additions & 2 deletions prisma/seed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const createStudent = async () => {
const students: Student[] = studentUser.map((user) => {
return {
default_coin_per_sem: 100,
remain_coin: 50,
remain_coin: 5000,
id: user.id
};
});
Expand Down Expand Up @@ -213,7 +213,8 @@ const createConfiguration = async () => {
{ name: 'dollar to coin', value: '73', description: 'The amount of coin user gets per dollar' },
{ name: 'coin per sem', value: '100', description: 'The amount of coin a student has free in one semester' },
///100mb = 100 * 1024 * 1024 (byte)
{ name: 'max file size', value: `${100 * 1024 * 1024}`, description: 'The amount of coin a student has free in one semester' }
{ name: 'max file size', value: `${100 * 1024 * 1024}`, description: 'The amount of coin a student has free in one semester' },
{ name: 'service fee', value: `5`, description: 'The amount of coin for a printing request be executed' }
];

const serializedExtensions = JSON.stringify(acceptedExtensions);
Expand Down
1 change: 1 addition & 0 deletions src/constants/PrintingRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export const DEFAULT_ACCEPTED_EXTENSION = ['pdf'];
export const DEFAULT_COIN_PER_PAGE = 2;
export const DEFAULT_COIN_PER_SEM = 100;
export const DEFAULT_MAX_FILE_SIZE = 100 * 1024 * 1024;
export const DEFAULT_SERVICE_FEE = 5;
10 changes: 10 additions & 0 deletions src/dtos/out/configuration/acceptedExtension.dto.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import { Static, Type } from '@sinclair/typebox';

export const AcceptedExtensionDto = Type.Array(Type.String());
export const ServiceFeeDto = Type.Number();
export const CoinPerPageDto = Type.Number();
export const CoinPerSemDto = Type.Number();
export const DollarToCoinDto = Type.Number();
export const MaxFileSizeDto = Type.Number();

export type AcceptedExtensionDto = Static<typeof AcceptedExtensionDto>;
export type ServiceFeeDto = Static<typeof ServiceFeeDto>;
export type CoinPerPageDto = Static<typeof CoinPerPageDto>;
export type CoinPerSemDto = Static<typeof CoinPerSemDto>;
export type DollarToCoinDto = Static<typeof DollarToCoinDto>;
export type MaxFileSizeDto = Static<typeof MaxFileSizeDto>;
3 changes: 2 additions & 1 deletion src/dtos/out/configuration/configuration.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Static, Type } from '@sinclair/typebox';
export const ConfigurationDto = Type.Array(
Type.Object({
name: Type.String(),
value: Type.String()
value: Type.String(),
description: Type.String()
})
);

Expand Down
3 changes: 2 additions & 1 deletion src/dtos/out/printing/printingRequest.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ export const GetPrintingRequestResultDto = Type.Array(
filesName: Type.Array(Type.String({ format: 'fileName' })),
numPages: Type.Number({ format: 'pageNumber' }),
coins: Type.Number({ format: 'coins' }),
paid: Type.String({ format: 'paid' })
paid: Type.String({ format: 'paid' }),
serviceFee: Type.Number()
})
);

Expand Down
60 changes: 58 additions & 2 deletions src/handlers/configuration.handler.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import { updateAcceptedExtensionDto } from '@dtos/in';
import { AcceptedExtensionDto, ConfigurationDto } from '@dtos/out';
import {
AcceptedExtensionDto,
CoinPerPageDto,
CoinPerSemDto,
ConfigurationDto,
DollarToCoinDto,
MaxFileSizeDto,
ServiceFeeDto
} from '@dtos/out';
import { DBConfiguration } from '@handlers';
import { Handler } from '@interfaces';
import { prisma } from '@repositories';
Expand All @@ -23,6 +31,49 @@ const getAcceptedExtension: Handler<AcceptedExtensionDto> = async () => {
}
};

const getServiceFee: Handler<ServiceFeeDto> = async () => {
try {
return await DBConfiguration.serviceFee();
} catch (err) {
logger.error('Error when getting service fee configuration:', err);
throw err;
}
};

const getCoinPerPage: Handler<CoinPerPageDto> = async () => {
try {
return await DBConfiguration.coinPerPage();
} catch (err) {
logger.error('Error when getting coin per page configuration:', err);
throw err;
}
};

const getCoinPerSem: Handler<CoinPerSemDto> = async () => {
try {
return await DBConfiguration.coinPerSem();
} catch (err) {
logger.error('Error when getting coin per semester configuration:', err);
throw err;
}
};
const getDollarToCoin: Handler<DollarToCoinDto> = async () => {
try {
return await DBConfiguration.dollarToCoin();
} catch (err) {
logger.error('Error when getting 1 dollar to coin ratio configuration:', err);
throw err;
}
};
const getMaxFileSizeDto: Handler<MaxFileSizeDto> = async () => {
try {
return await DBConfiguration.maxFileSize();
} catch (err) {
logger.error('Error when getting max file size configuration:', err);
throw err;
}
};

const updateAcceptedExtension: Handler<AcceptedExtensionDto, { Body: updateAcceptedExtensionDto }> = async (req, res) => {
const { acceptedExtensions } = req.body;
if (acceptedExtensions.length === 0) return res.badRequest('Needing at least one extension');
Expand Down Expand Up @@ -50,5 +101,10 @@ const updateAcceptedExtension: Handler<AcceptedExtensionDto, { Body: updateAccep
export const configurationHandler = {
getAcceptedExtension,
getConfigurations,
updateAcceptedExtension
updateAcceptedExtension,
getServiceFee,
getCoinPerPage,
getCoinPerSem,
getDollarToCoin,
getMaxFileSizeDto
};
26 changes: 23 additions & 3 deletions src/handlers/getConfigurationInDb.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ import {
DEFAULT_COIN_PER_PAGE,
DEFAULT_COIN_PER_SEM,
DEFAULT_DOLLAR_TO_COIN,
DEFAULT_MAX_FILE_SIZE
DEFAULT_MAX_FILE_SIZE,
DEFAULT_SERVICE_FEE
} from '@constants';
import { logger } from '@utils';

Expand All @@ -15,11 +16,12 @@ const getAll: () => Promise<
{
name: string;
value: string;
description: string;
}[]
> = async () => {
try {
const configurations = await prisma.configuration.findMany({
select: { name: true, value: true }
select: { name: true, value: true, description: true }
});
return configurations;
} catch (error) {
Expand Down Expand Up @@ -128,4 +130,22 @@ const maxFileSize: () => Promise<number> = async () => {
}
};

export const DBConfiguration = { acceptedExtensions, coinPerPage, coinPerSem, dollarToCoin, getAll, maxFileSize };
const serviceFee: () => Promise<number> = async () => {
try {
const serviceFee = await prisma.configuration.findMany({
select: { value: true },
where: { name: 'service fee' }
});
if (serviceFee.length === 0) {
logger.warn(`No "service fee" configuration found. Using default value: ${DEFAULT_SERVICE_FEE} coins.`);
return DEFAULT_SERVICE_FEE;
}
const coinPerSem = Number(serviceFee[0]?.value);

return coinPerSem;
} catch (error) {
throw new Error('Failed to retrieve "max file size" configuration:', error);
}
};

export const DBConfiguration = { acceptedExtensions, coinPerPage, coinPerSem, dollarToCoin, getAll, maxFileSize, serviceFee };
37 changes: 34 additions & 3 deletions src/handlers/printingRequest.handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ const getAllPrintingRequest: Handler<GetPrintingRequestResultDto> = async (req)
numPages: true,
coins: true,
paid: true,
files: { select: { realName: true } }
files: { select: { realName: true } },
serviceFee: true
},
where: {
userId: {
Expand Down Expand Up @@ -73,10 +74,12 @@ const getAllPrintingRequest: Handler<GetPrintingRequestResultDto> = async (req)
const createPrintingRequest: Handler<CreatePrintingRequestResultDto> = async (req, res) => {
try {
const userId = req.userId;
const serviceFee = await DBConfiguration.serviceFee();

const printingRequestId = await prisma.printingRequest.create({
data: {
userId
userId,
serviceFee: serviceFee
},
select: { id: true }
});
Expand Down Expand Up @@ -331,6 +334,7 @@ const uploadConfigToPrintingRequest: Handler<UploadConfigResultDto, { Params: Up
res
) => {
try {
//TODO: update correct coin
const fileConfig = req.body;
if (!fileConfig) {
return res.badRequest('Missing config data');
Expand Down Expand Up @@ -378,10 +382,37 @@ const getFilesOfPrintingRequest = async (printingRequestId: string) => {

const executePrintingRequest: Handler<PrintingFileResultDto, { Body: PrintingRequestInputDto }> = async (req, res) => {
try {
//TODO: roles is undefined
// if (!req.roles || !req.roles.includes(USER_ROLES.student)) {
// return res.badRequest('This endpoint is only accessible to students.');
// }

const userId = req.userId;
const printingRequestId = req.body.printingRequestId;

const filesOfPrintingRequest = await getFilesOfPrintingRequest(printingRequestId);
const [printingRequest, student] = await Promise.all([
prisma.printingRequest.findFirst({ where: { id: printingRequestId }, select: { coins: true } }),
prisma.student.findFirst({ where: { id: userId }, select: { remain_coin: true } })
]);

if (!printingRequest) {
return res.badRequest('Invalid printing request id');
}

if (!student) {
logger.warn('The logic of the system is incorrect; this user cannot execute the printing request');
return res.badRequest('This endpoint is only accessible to students.');
}

const requireCoins = printingRequest.coins;

if (requireCoins > student.remain_coin) {
return res.badRequest("User doesn't have enough coins to execute the printing request");
}

await prisma.student.update({ where: { id: userId }, data: { remain_coin: { decrement: requireCoins } } });

const filesOfPrintingRequest = await getFilesOfPrintingRequest(printingRequestId);
filesOfPrintingRequest.forEach(async (file) => {
const buffer = await minio.getFileFromMinio(file.minioName);
const config = await getConfigOfFile(file.minioName);
Expand Down
9 changes: 6 additions & 3 deletions src/hooks/auth/checkRole.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
import { envs } from '@configs';
import { PERMISSION_DENIED } from '@constants';
import jwt, { JwtPayload } from 'jsonwebtoken';
import { MUST_LOGIN_FIRST, PERMISSION_DENIED } from '@constants';
import jwt from 'jsonwebtoken';
import { RolesValidation } from 'src/types/auth';

export const checkRoles: RolesValidation = (allowedRoles) => (req, res, done) => {
const token = req.cookies.token;
if (!token) return res.unauthorized(MUST_LOGIN_FIRST);
try {
const decodedPayload = jwt.verify(token || '', envs.JWT_SECRET) as JwtPayload;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const decodedPayload: any = jwt.verify(token, envs.JWT_SECRET);
req.roles = decodedPayload['roles'];

const result = req.roles.map((role) => allowedRoles.includes(role)).find((val) => val === true);
if (!result) return res.forbidden(PERMISSION_DENIED);
done();
Expand Down
3 changes: 2 additions & 1 deletion src/interfaces/handler.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { FastifyRequest, FastifyReply, RouteGenericInterface } from 'fastify';
import { UserRole } from 'src/types/auth';

export type Handler<RS = unknown, RQ extends RouteGenericInterface = Record<string, never>> = (
req: FastifyRequest<RQ> & { userId: string },
req: FastifyRequest<RQ> & { userId: string } & { roles: UserRole[] },
res: FastifyReply
) => Result<RS>;
75 changes: 74 additions & 1 deletion src/routes/apis/configuration.plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,14 @@
import { USER_ROLES } from '@constants';
import { updateAcceptedExtensionDto } from '@dtos/in';
import { AcceptedExtensionDto, ConfigurationDto } from '@dtos/out';
import {
AcceptedExtensionDto,
CoinPerPageDto,
CoinPerSemDto,
ConfigurationDto,
DollarToCoinDto,
MaxFileSizeDto,
ServiceFeeDto
} from '@dtos/out';
import { configurationHandler } from '@handlers';
import { createRoutes } from '@utils';

Expand Down Expand Up @@ -31,6 +39,71 @@ export const configurationPlugin = createRoutes('Configuration', [
},
handler: configurationHandler.getAcceptedExtension
},
{
method: 'GET',
url: '/serviceFee',
roles: ['*'],
schema: {
summary: '',
description: '',
response: {
200: ServiceFeeDto
}
},
handler: configurationHandler.getServiceFee
},
{
method: 'GET',
url: '/coinPerPage',
roles: ['*'],
schema: {
summary: '',
description: '',
response: {
200: CoinPerPageDto
}
},
handler: configurationHandler.getCoinPerPage
},
{
method: 'GET',
url: '/coinPerSem',
roles: ['*'],
schema: {
summary: '',
description: '',
response: {
200: CoinPerSemDto
}
},
handler: configurationHandler.getCoinPerSem
},
{
method: 'GET',
url: '/dollarToCoin',
roles: ['*'],
schema: {
summary: '',
description: '',
response: {
200: DollarToCoinDto
}
},
handler: configurationHandler.getDollarToCoin
},
{
method: 'GET',
url: '/maxFileSize',
roles: ['*'],
schema: {
summary: '',
description: '',
response: {
200: MaxFileSizeDto
}
},
handler: configurationHandler.getMaxFileSizeDto
},
{
method: 'PUT',
url: '/',
Expand Down

0 comments on commit 113200c

Please sign in to comment.