testing all features

This commit is contained in:
Kevsl
2024-06-06 16:31:31 +02:00
commit 9f5c23c7c9
74 changed files with 18541 additions and 0 deletions

View File

@@ -0,0 +1,22 @@
import { Test, TestingModule } from '@nestjs/testing';
import { AppController } from './app.controller';
import { AppService } from './app.service';
describe('AppController', () => {
let appController: AppController;
beforeEach(async () => {
const app: TestingModule = await Test.createTestingModule({
controllers: [AppController],
providers: [AppService],
}).compile();
appController = app.get<AppController>(AppController);
});
describe('root', () => {
it('should return "Hello World!"', () => {
expect(appController.getHello()).toBe('Hello World!');
});
});
});

12
src/app.controller.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
}

29
src/app.module.ts Normal file
View File

@@ -0,0 +1,29 @@
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
import { PrismaModule } from './prisma/prisma.module';
import { RoleModule } from './role/role.module';
import { PromoCodeModule } from './promoCode/promoCode.module';
import { CryptoModule } from './crypto/crypto.module';
import { TradeModule } from './trade/trade.module';
import { OfferModule } from './offer/offer.module';
@Module({
imports: [
ConfigModule.forRoot({
isGlobal: true,
}),
AuthModule,
PrismaModule,
RoleModule,
PromoCodeModule,
CryptoModule,
TradeModule,
OfferModule,
],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}

8
src/app.service.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Injectable } from '@nestjs/common';
@Injectable()
export class AppService {
getHello(): string {
return 'Hello World!';
}
}

View File

@@ -0,0 +1,21 @@
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthLoginDto, AuthRegisterDto } from './dto';
import { ApiTags } from '@nestjs/swagger';
@ApiTags('auth')
@Controller('auth')
export class AuthController {
constructor(private authService: AuthService) {}
@Post('signup')
signup(@Body() dto: AuthRegisterDto) {
return this.authService.signup(dto);
}
@HttpCode(HttpStatus.OK)
@Post('signin')
signin(@Body() dto: AuthLoginDto) {
return this.authService.signin(dto);
}
}

12
src/auth/auth.module.ts Normal file
View File

@@ -0,0 +1,12 @@
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { JwtStrategy } from './strategy';
@Module({
imports: [JwtModule.register({})],
controllers: [AuthController],
providers: [AuthService, JwtStrategy],
})
export class AuthModule {}

92
src/auth/auth.service.ts Normal file
View File

@@ -0,0 +1,92 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AuthLoginDto, AuthRegisterDto } from './dto';
import * as argon from 'argon2';
import { Prisma } from '@prisma/client';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwt: JwtService,
private config: ConfigService,
) {}
async signup(dto: AuthRegisterDto) {
const hash = await argon.hash(dto.password);
const promoCode = await this.prisma.promoCode.findFirst({
where: {
name: dto.promoCode,
},
});
const userRole = await this.prisma.role.findFirst({
where: {
name: 'user',
},
});
let balance = 1000;
if (promoCode && promoCode.value) {
balance = promoCode.value;
}
try {
const user = await this.prisma.user.create({
data: {
firstName: dto.firstName,
lastName: dto.lastName,
pseudo: dto.pseudo,
city: dto.city,
email: dto.email,
hash,
roleId: userRole.id,
isActive: true,
dollarAvailables: balance,
},
});
return this.signToken(user.id, user.email);
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
throw new ForbiddenException('Credentials taken');
}
}
throw error;
}
}
async signin(dto: AuthLoginDto) {
const user = await this.prisma.user.findUnique({
where: {
email: dto.email,
},
});
if (!user) throw new ForbiddenException('Credentials incorrect');
const pwMatches = await argon.verify(user.hash, dto.password);
if (!pwMatches) throw new ForbiddenException('Credentials incorrect');
return this.signToken(user.id, user.email);
}
async signToken(
userId: string,
email: string,
): Promise<{ access_token: string }> {
const payload = {
sub: userId,
email: email,
};
const secret = this.config.get('JWT_SECRET');
const token = await this.jwt.signAsync(payload, {
expiresIn: '30d',
secret: secret,
});
return {
access_token: token,
};
}
}

View File

@@ -0,0 +1,11 @@
import { createParamDecorator, ExecutionContext } from '@nestjs/common';
export const GetUser = createParamDecorator(
(data: string | undefined, ctx: ExecutionContext) => {
const request: Express.Request = ctx.switchToHttp().getRequest();
if (data) {
return request.user[data];
}
return request.user;
},
);

View File

@@ -0,0 +1 @@
export * from './get-user.decorator';

View File

@@ -0,0 +1,13 @@
import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';
export class AuthLoginDto {
@IsEmail()
@IsNotEmpty()
@ApiProperty({ type: String, description: 'email' })
email: string;
@ApiProperty({ type: String, description: 'password' })
@IsString()
@IsNotEmpty()
password: string;
}

View File

@@ -0,0 +1,65 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsNotEmpty, IsOptional, IsString } from 'class-validator';
export class AuthRegisterDto {
@ApiProperty({
type: String,
description: 'FirstName',
example: 'Thomas',
})
@IsNotEmpty()
@IsString()
firstName: string;
@ApiProperty({
type: String,
description: 'Last Name',
example: 'Anderson',
})
@IsNotEmpty()
@IsString()
lastName: string;
@ApiProperty({
type: String,
description: 'Pseudo',
example: 'Néo',
})
@IsNotEmpty()
@IsString()
pseudo: string;
@ApiProperty({
type: String,
description: 'User city',
example: 'Aix les bains',
})
@IsNotEmpty()
@IsString()
city: string;
@ApiProperty({
type: String,
description: 'email',
example: 'neo@matrix.fr',
})
@IsEmail()
@IsNotEmpty()
email: string;
@ApiProperty({
type: String,
description: 'password',
example: 'AAaa11&&&&',
})
@IsString()
@IsNotEmpty()
password: string;
@ApiProperty({
type: String,
description: 'promoCode',
example: 'FILOU20',
})
@IsOptional()
promoCode: string;
}

2
src/auth/dto/index.ts Normal file
View File

@@ -0,0 +1,2 @@
export * from './auth.register.dto';
export * from './auth.login.dto';

1
src/auth/guard/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './jwt.guard';

View File

@@ -0,0 +1,7 @@
import { AuthGuard } from '@nestjs/passport';
export class JwtGuard extends AuthGuard('jwt') {
constructor() {
super();
}
}

View File

@@ -0,0 +1 @@
export * from './jwt.strategy';

View File

@@ -0,0 +1,28 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { PrismaService } from '../../prisma/prisma.service';
@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy, 'jwt') {
constructor(
config: ConfigService,
private prisma: PrismaService,
) {
super({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: config.get('JWT_SECRET'),
});
}
async validate(payload: { sub: string; email: string }) {
const user = await this.prisma.user.findUnique({
where: {
id: payload.sub,
},
});
delete user.hash;
return user;
}
}

View File

@@ -0,0 +1,69 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
UseGuards,
} from '@nestjs/common';
import { GetUser } from '../auth/decorator';
import { ApiTags } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { CryptoService } from './crypto.service';
import { CryptoDto } from './dto';
import { JwtGuard } from 'src/auth/guard';
import { BuyCryptoDto } from './dto/buy.crypto.dto';
@UseGuards(JwtGuard)
@ApiTags('crypto')
@Controller('crypto')
export class CryptoController {
constructor(private promoService: CryptoService) {}
@Get('/all')
getAllPromoCodes(@GetUser() user: User) {
return this.promoService.getCryptos(user.id);
}
@Get('/search/:id')
searchCrypto(@GetUser() user: User, @Param('id') cryptoName: string) {
return this.promoService.searchCryptos(user.id, cryptoName);
}
@HttpCode(HttpStatus.CREATED)
@Post('/create')
createPromoCode(
@Body()
dto: CryptoDto,
@GetUser() user: User,
) {
return this.promoService.createCrypto(user.id, dto);
}
@Post('/buy')
buyCrypto(
@Body()
dto: BuyCryptoDto,
@GetUser() user: User,
) {
return this.promoService.buyCrypto(user.id, dto);
}
@HttpCode(HttpStatus.OK)
@Patch('/update/:id')
editPromoCodeById(
@Param('id') cryptoId: string,
@Body() dto: CryptoDto,
@GetUser() user: User,
) {
return this.promoService.editCryptoById(user.id, cryptoId, dto);
}
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/delete/:id')
deletePromoCodeById(@Param('id') cryptoId: string, @GetUser() user: User) {
return this.promoService.deleteCryptoById(user.id, cryptoId);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { CryptoService } from './crypto.service';
import { CryptoController } from './crypto.controller';
@Module({
providers: [CryptoService],
controllers: [CryptoController],
})
export class CryptoModule {}

View File

@@ -0,0 +1,148 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { checkUserHasAccount, checkuserIsAdmin } from 'src/utils/checkUser';
import { CryptoDto } from './dto';
import { BuyCryptoDto } from './dto/buy.crypto.dto';
@Injectable()
export class CryptoService {
constructor(private prisma: PrismaService) {}
async getCryptos(userId: string) {
await checkUserHasAccount(userId);
return this.prisma.crypto.findMany({
orderBy: {
name: 'asc',
},
});
}
async searchCryptos(userId: string, cryptoName: string) {
await checkUserHasAccount(userId);
return this.prisma.crypto.findMany({
where: {
name: {
contains: cryptoName,
mode: 'insensitive',
},
},
orderBy: {
name: 'asc',
},
});
}
async createCrypto(userId: string, dto: CryptoDto) {
await checkuserIsAdmin(userId);
const crypto = await this.prisma.crypto.create({
data: {
name: dto.name,
image: dto.image,
value: dto.value,
},
});
return crypto;
}
async buyCrypto(userId: string, dto: BuyCryptoDto) {
const crypto = await this.prisma.crypto.findFirst({
where: {
id: dto.id_crypto,
},
});
const user = await this.prisma.user.findFirst({
where: {
id: userId,
},
});
const necessaryAmount = crypto.value * dto.amount;
console.log(necessaryAmount, user.dollarAvailables);
if (necessaryAmount > user.dollarAvailables) {
throw new ForbiddenException('Make money first :) ');
} else {
const userAsset = await this.prisma.userHasCrypto.findFirst({
where: {
id_crypto: dto.id_crypto,
id_user: userId,
},
});
const newBalance = user.dollarAvailables - necessaryAmount;
console.log(newBalance);
await this.prisma.user.update({
where: {
id: user.id,
},
data: {
dollarAvailables: newBalance,
},
});
if (userAsset) {
const newBalance = userAsset.amount + dto.amount;
await this.prisma.userHasCrypto.update({
where: {
id: userAsset.id,
},
data: {
amount: newBalance,
},
});
} else {
await this.prisma.userHasCrypto.create({
data: {
id_crypto: dto.id_crypto,
id_user: userId,
amount: dto.amount,
},
});
}
}
return crypto;
}
async editCryptoById(userId: string, cryptoId: string, dto: CryptoDto) {
await checkuserIsAdmin(userId);
const crypto = await this.prisma.crypto.findUnique({
where: {
id: cryptoId,
},
});
if (!crypto || crypto.id !== cryptoId)
throw new ForbiddenException('Access to resources denied');
return this.prisma.crypto.update({
where: {
id: crypto.id,
},
data: {
...dto,
},
});
}
async deleteCryptoById(userId: string, id: string) {
await checkuserIsAdmin(userId);
const crypto = await this.prisma.crypto.findUnique({
where: {
id: id,
},
});
if (!crypto || crypto.id !== id)
throw new ForbiddenException('Access to resources denied');
await this.prisma.crypto.delete({
where: {
id: crypto.id,
},
});
}
}

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
export class BuyCryptoDto {
@ApiProperty({
type: String,
description: 'Cryptocurrency UUID',
example: '12121-DSZD-E221212-2121221',
})
@IsString()
id_crypto: string;
@ApiProperty({
type: Number,
description: 'Amount of token traded',
example: 2,
})
@IsNumber()
amount: number;
}

View File

@@ -0,0 +1,27 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
export class CryptoDto {
@ApiProperty({
type: String,
description: 'Cryptocurrency name',
example: 'BTC',
})
@IsString()
name: string;
@ApiProperty({
type: Number,
description: 'Value for the cryptocurrency in $',
example: 1,
})
@IsNumber()
value: number;
@ApiProperty({
type: String,
description: 'Image for the cryptocurrency in ',
example: 'https://myImage/com',
})
@IsString()
image: string;
}

1
src/crypto/dto/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './crypto.dto';

27
src/main.ts Normal file
View File

@@ -0,0 +1,27 @@
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
const config = new DocumentBuilder()
.setTitle('CryptCoin APi')
.setDescription('The CryptCoin api swagger ')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
// eslint-disable-next-line prettier/prettier
})
);
await app.listen(3000);
}
bootstrap();

1
src/offer/dto/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './offer.dto';

View File

@@ -0,0 +1,20 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
export class OfferDto {
@ApiProperty({
type: String,
description: 'Cryptocurrency UUID',
example: '12121-DSZD-E221212-2121221',
})
@IsString()
id_crypto: string;
@ApiProperty({
type: 'number',
description: 'Amount traded ',
example: 21,
})
@IsNumber()
amount: number;
}

View File

@@ -0,0 +1,59 @@
/* eslint-disable prettier/prettier */
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
UseGuards,
// UseGuards,
} from '@nestjs/common';
import { GetUser } from '../auth/decorator';
// import { JwtGuard } from '../auth/guard';
import { ApiTags } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { JwtGuard } from 'src/auth/guard';
import { OfferService } from './offer.service';
import { OfferDto } from './dto';
@UseGuards(JwtGuard)
@ApiTags('offer')
@Controller('offer')
export class OfferController {
constructor(private offerService: OfferService) {}
@Get('/all')
getAllRoles(@GetUser() user: User) {
return this.offerService.getOffers(user.id);
}
@HttpCode(HttpStatus.CREATED)
@Post('/create')
createRole(
@Body()
dto: OfferDto,
@GetUser() user: User
) {
return this.offerService.createOffer(user.id, dto);
}
@HttpCode(HttpStatus.OK)
@Patch('/update/:id')
editOfferById(
@Param('id') offerId: string,
@Body() dto: OfferDto,
@GetUser() user: User
) {
return this.offerService.editOfferById(user.id, offerId, dto);
}
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/delete/:id')
deleteOfferById(@Param('id') roleId: string, @GetUser() user: User) {
return this.offerService.deleteOfferById(user.id, roleId);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { OfferService } from './offer.service';
import { OfferController } from './offer.controller';
@Module({
providers: [OfferService],
controllers: [OfferController],
})
export class OfferModule {}

View File

@@ -0,0 +1,76 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { checkUserHasAccount, checkuserIsAdmin } from 'src/utils/checkUser';
import { OfferDto } from './dto';
// import { checkRoleLevel, checkUserIsStaff } from 'src/utils/checkUser';
@Injectable()
export class OfferService {
constructor(private prisma: PrismaService) {}
async getOffers(userId: string) {
await checkUserHasAccount(userId);
return this.prisma.offer.findMany({
orderBy: {
created_at: 'desc',
},
select: {
amount: true,
created_at: true,
id_user: true,
Crypto: true,
},
});
}
async createOffer(userId: string, dto: OfferDto) {
await checkUserHasAccount(userId);
const offer = await this.prisma.offer.create({
data: {
id_crypto: dto.id_crypto,
id_user: userId,
amount: dto.amount,
},
});
return offer;
}
async editOfferById(userId: string, offerId: string, dto: OfferDto) {
await checkUserHasAccount(userId);
const offer = await this.prisma.offer.findUnique({
where: {
id: offerId,
},
});
if (!offer || offer.id !== offerId)
throw new ForbiddenException('Access to resources denied');
return this.prisma.role.update({
where: {
id: offerId,
},
data: {
...dto,
},
});
}
async deleteOfferById(userId: string, id: string) {
await checkuserIsAdmin(userId);
const offer = await this.prisma.offer.findUnique({
where: {
id: id,
},
});
if (!offer || offer.id !== id)
throw new ForbiddenException('Access to resources denied');
await this.prisma.offer.delete({
where: {
id: id,
},
});
}
}

View File

@@ -0,0 +1,9 @@
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';
@Global()
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}

View File

@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient {
constructor(config: ConfigService) {
super({
datasources: {
db: {
url: config.get('DATABASE_URL'),
},
},
});
}
// cleanDb() {
// return this.$transaction([
// this.bookmark.deleteMany(),
// this.user.deleteMany(),
// ])
// }
}

View File

@@ -0,0 +1 @@
export * from './promoCode.dto';

View File

@@ -0,0 +1,19 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNumber, IsString } from 'class-validator';
export class PromoCodeDto {
@ApiProperty({
type: String,
description: 'Name of the PromoCOde',
example: 'FILOU10',
})
@IsString()
name: string;
@ApiProperty({
type: Number,
description: 'Dollars given for account creation when promoCode applied',
example: 100,
})
@IsNumber()
value: number;
}

View File

@@ -0,0 +1,57 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
UseGuards,
} from '@nestjs/common';
import { GetUser } from '../auth/decorator';
import { ApiTags } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { PromoCodeDto } from './dto';
import { PromoCodeService } from './promoCode.service';
import { JwtGuard } from 'src/auth/guard';
@UseGuards(JwtGuard)
@ApiTags('promoCode')
@Controller('promoCode')
export class PromoCodeController {
constructor(private promoService: PromoCodeService) {}
@Get('/all')
getAllPromoCodes(@GetUser() user: User) {
return this.promoService.getPromoCodes(user.id);
}
@HttpCode(HttpStatus.CREATED)
@Post('/create')
createPromoCode(
// @GetUser() user: User,
@Body()
dto: PromoCodeDto,
@GetUser() user: User,
) {
return this.promoService.createPromoCode(user.id, dto);
}
@HttpCode(HttpStatus.OK)
@Patch('/update/:id')
editPromoCodeById(
@Param('id') promoCodeId: string,
@Body() dto: PromoCodeDto,
@GetUser() user: User,
) {
return this.promoService.editPromoCodeById(user.id, promoCodeId, dto);
}
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/delete/:id')
deletePromoCodeById(@Param('id') promoCodeId: string, @GetUser() user: User) {
return this.promoService.deletePromoCodeById(user.id, promoCodeId);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { PromoCodeController } from './promoCode.controller';
import { PromoCodeService } from './promoCode.service';
@Module({
providers: [PromoCodeService],
controllers: [PromoCodeController],
})
export class PromoCodeModule {}

View File

@@ -0,0 +1,79 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { PromoCodeDto } from './dto';
import { checkuserIsAdmin } from 'src/utils/checkUser';
@Injectable()
export class PromoCodeService {
constructor(private prisma: PrismaService) {}
async getPromoCodes(userId: string) {
await checkuserIsAdmin(userId);
return this.prisma.promoCode.findMany({
orderBy: {
name: 'asc',
},
select: {
id: true,
name: true,
value: true,
},
});
}
async createPromoCode(userId: string, dto: PromoCodeDto) {
await checkuserIsAdmin(userId);
const promoCode = await this.prisma.promoCode.create({
data: {
name: dto.name,
value: dto.value,
},
});
return promoCode;
}
async editPromoCodeById(
userId: string,
promoCodeId: string,
dto: PromoCodeDto,
) {
await checkuserIsAdmin(userId);
const promoCode = await this.prisma.promoCode.findUnique({
where: {
id: promoCodeId,
},
});
if (!promoCode || promoCode.id !== promoCodeId)
throw new ForbiddenException('Access to resources denied');
return this.prisma.promoCode.update({
where: {
id: promoCode.id,
},
data: {
...dto,
},
});
}
async deletePromoCodeById(userId: string, id: string) {
await checkuserIsAdmin(userId);
const promoCode = await this.prisma.promoCode.findUnique({
where: {
id: id,
},
});
if (!promoCode || promoCode.id !== id)
throw new ForbiddenException('Access to resources denied');
await this.prisma.promoCode.delete({
where: {
id: promoCode.id,
},
});
}
}

1
src/role/dto/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './role.dto';

11
src/role/dto/role.dto.ts Normal file
View File

@@ -0,0 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsString } from 'class-validator';
export class RoleDto {
@ApiProperty({
type: String,
description: 'Role Name',
example: 'user',
})
@IsString()
name: string;
}

View File

@@ -0,0 +1,63 @@
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
UseGuards,
// UseGuards,
} from '@nestjs/common';
import { GetUser } from '../auth/decorator';
// import { JwtGuard } from '../auth/guard';
import { RoleDto } from './dto';
import { RoleService } from './role.service';
import { ApiTags } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { JwtGuard } from 'src/auth/guard';
@UseGuards(JwtGuard)
@ApiTags('role')
@Controller('role')
export class RoleController {
constructor(private roleService: RoleService) {}
@Get('/all')
getAllRoles(@GetUser() user: User) {
return this.roleService.getRolesAdmin(user.id);
}
// @Get('/cm/all')
// getRolesCm(@GetUser() user: User) {
// return this.roleService.getRolesCm(user)
// }
@HttpCode(HttpStatus.CREATED)
@Post('/create')
createRole(
// @GetUser() user: User,
@Body()
dto: RoleDto,
@GetUser() user: User,
) {
return this.roleService.createRole(user.id, dto);
}
@HttpCode(HttpStatus.OK)
@Patch('/update/:id')
editRoleById(
@Param('id') roleId: string,
@Body() dto: RoleDto,
@GetUser() user: User,
) {
return this.roleService.editRoleById(user.id, roleId, dto);
}
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/delete/:id')
deleteRoleById(@Param('id') roleId: string, @GetUser() user: User) {
return this.roleService.deleteRoleById(user.id, roleId);
}
}

8
src/role/role.module.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { RoleController } from './role.controller';
import { RoleService } from './role.service';
@Module({
providers: [RoleService],
controllers: [RoleController],
})
export class RoleModule {}

72
src/role/role.service.ts Normal file
View File

@@ -0,0 +1,72 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { RoleDto } from './dto';
import { checkuserIsAdmin } from 'src/utils/checkUser';
// import { checkRoleLevel, checkUserIsStaff } from 'src/utils/checkUser';
@Injectable()
export class RoleService {
constructor(private prisma: PrismaService) {}
async getRolesAdmin(userId: string) {
await checkuserIsAdmin(userId);
return this.prisma.role.findMany({
orderBy: {
name: 'asc',
},
select: {
id: true,
name: true,
},
});
}
async createRole(userId: string, dto: RoleDto) {
await checkuserIsAdmin(userId);
const role = await this.prisma.role.create({
data: {
name: dto.name,
},
});
return role;
}
async editRoleById(userId: string, roleId: string, dto: RoleDto) {
await checkuserIsAdmin(userId);
const role = await this.prisma.role.findUnique({
where: {
id: roleId,
},
});
if (!role || role.id !== roleId)
throw new ForbiddenException('Access to resources denied');
return this.prisma.role.update({
where: {
id: roleId,
},
data: {
...dto,
},
});
}
async deleteRoleById(userId: string, id: string) {
await checkuserIsAdmin(userId);
const role = await this.prisma.role.findUnique({
where: {
id: id,
},
});
if (!role || role.id !== id)
throw new ForbiddenException('Access to resources denied');
await this.prisma.role.delete({
where: {
id: id,
},
});
}
}

1
src/trade/dto/index.ts Normal file
View File

@@ -0,0 +1 @@
export * from './trade.dto';

View File

@@ -0,0 +1,48 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDecimal, IsNotEmpty, IsString } from 'class-validator';
export class TradeDto {
@ApiProperty({
type: String,
description: 'UUID of the user that hold tokens before trade',
example: '121212-DSDZ1-21212DJDZ-31313',
})
@IsNotEmpty()
@IsString()
id_giver: string;
@ApiProperty({
type: String,
description: 'UUID of the user that will receive tokens after trade',
example: '121212-DSDZ1-21212DJDZ-31313',
})
@IsNotEmpty()
@IsString()
id_receiver: string;
@ApiProperty({
type: String,
description: 'UUID of the crypto traded',
example: '121212-DSDZ1-21212DJDZ-31313',
})
@IsNotEmpty()
@IsString()
id_crypto: string;
@ApiProperty({
type: Number,
description: 'Amount of tokens traded ',
example: 2,
})
@IsNotEmpty()
@IsDecimal()
amount_traded: number;
@ApiProperty({
type: String,
description: 'Offer UUID ',
example: '121212-DSDZ1-21212DJDZ-31313',
})
@IsNotEmpty()
@IsDecimal()
id_offer: string;
}

View File

@@ -0,0 +1,58 @@
/* eslint-disable prettier/prettier */
import {
Body,
Controller,
Delete,
Get,
HttpCode,
HttpStatus,
Param,
Patch,
Post,
UseGuards,
} from '@nestjs/common';
import { GetUser } from '../auth/decorator';
import { ApiTags } from '@nestjs/swagger';
import { User } from '@prisma/client';
import { TradeService } from './trade.service';
import { TradeDto } from './dto';
import { JwtGuard } from 'src/auth/guard';
@UseGuards(JwtGuard)
@ApiTags('trade')
@Controller('trade')
export class TradeController {
constructor(private tradeService: TradeService) {}
@Get('/all')
getAllPromoCodes(@GetUser() user: User) {
return this.tradeService.getTrades(user.id);
}
@HttpCode(HttpStatus.CREATED)
@Post('/create')
createPromoCode(
// @GetUser() user: User,
@Body()
dto: TradeDto,
@GetUser() user: User
) {
return this.tradeService.createTrade(user.id, dto);
}
@HttpCode(HttpStatus.OK)
@Patch('/update/:id')
editPromoCodeById(
@Param('id') promoCodeId: string,
@Body() dto: TradeDto,
@GetUser() user: User
) {
return this.tradeService.editTradeById(user.id, promoCodeId, dto);
}
@HttpCode(HttpStatus.NO_CONTENT)
@Delete('/delete/:id')
deletePromoCodeById(@Param('id') promoCodeId: string, @GetUser() user: User) {
return this.tradeService.deleteTradeById(user.id, promoCodeId);
}
}

View File

@@ -0,0 +1,9 @@
import { Module } from '@nestjs/common';
import { TradeService } from './trade.service';
import { TradeController } from './trade.controller';
@Module({
providers: [TradeService],
controllers: [TradeController],
})
export class TradeModule {}

177
src/trade/trade.service.ts Normal file
View File

@@ -0,0 +1,177 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { checkUserHasAccount, checkuserIsAdmin } from 'src/utils/checkUser';
import { TradeDto } from './dto';
@Injectable()
export class TradeService {
constructor(private prisma: PrismaService) {}
async getTrades(userId: string) {
await checkuserIsAdmin(userId);
return this.prisma.trade.findMany({
orderBy: {
created_at: 'desc',
},
include: {
Giver: true,
Receiver: true,
Crypto: true,
},
});
}
async getLastTrades() {
return this.prisma.trade.findMany({
orderBy: {
created_at: 'desc',
},
select: {
amount_traded: true,
Crypto: true,
},
});
}
async createTrade(userId: string, dto: TradeDto) {
await checkUserHasAccount(userId);
const crypto = await this.prisma.crypto.findFirst({
where: {
id: dto.id_crypto,
},
});
const buyer = await this.prisma.user.findFirst({
where: {
id: dto.id_receiver,
},
});
const price = crypto.value * dto.amount_traded;
if (buyer.dollarAvailables < price) {
throw new ForbiddenException(
// eslint-disable-next-line prettier/prettier
`Acqueror ${buyer.pseudo} doesnt have enough money to make this trade`
);
}
const asset = await this.prisma.userHasCrypto.findFirst({
where: {
id_crypto: dto.id_crypto,
id_user: dto.id_giver,
},
});
if (!asset || asset.amount < dto.amount_traded) {
throw new ForbiddenException(`Seller doesnt have enough ${crypto.name} `);
}
const trade = await this.prisma.trade.create({
data: {
id_giver: dto.id_giver,
id_receiver: dto.id_receiver,
id_crypto: dto.id_crypto,
amount_traded: dto.amount_traded,
id_offer: dto.id_offer,
},
});
const newBalanceGiver = (asset.amount -= dto.amount_traded);
await this.prisma.userHasCrypto.update({
where: {
id: asset.id,
},
data: {
amount: newBalanceGiver,
},
});
const receiverAssets = await this.prisma.userHasCrypto.findFirst({
where: {
id_user: dto.id_receiver,
id_crypto: dto.id_crypto,
},
});
if (!receiverAssets) {
await this.prisma.userHasCrypto.create({
data: {
id_user: dto.id_receiver,
id_crypto: dto.id_crypto,
amount: dto.amount_traded,
createdAt: new Date(),
},
});
} else {
const newBalanceReceiver = receiverAssets.amount + dto.amount_traded;
await this.prisma.userHasCrypto.update({
where: {
id: receiverAssets.id,
},
data: {
amount: newBalanceReceiver,
},
});
}
const newValue = crypto.value * 1.1;
await this.prisma.crypto.update({
where: {
id: crypto.id,
},
data: {
value: newValue,
},
});
const prevAmount = buyer.dollarAvailables;
await this.prisma.user.update({
where: {
id: dto.id_receiver,
},
data: {
dollarAvailables: prevAmount - price,
},
});
return trade;
}
async editTradeById(userId: string, tradeId: string, dto: TradeDto) {
await checkuserIsAdmin(userId);
const trade = await this.prisma.trade.findUnique({
where: {
id: tradeId,
},
});
if (!trade || trade.id !== tradeId)
throw new ForbiddenException('Access to resources denied');
return this.prisma.trade.update({
where: {
id: trade.id,
},
data: {
...dto,
},
});
}
async deleteTradeById(userId: string, id: string) {
await checkuserIsAdmin(userId);
const trade = await this.prisma.trade.findUnique({
where: {
id: id,
},
});
if (!trade || trade.id !== id)
throw new ForbiddenException('Access to resources denied');
await this.prisma.trade.delete({
where: {
id: trade.id,
},
});
}
}

View File

@@ -0,0 +1,31 @@
/* eslint-disable prettier/prettier */
import { Body, Controller, Get, UseGuards } from '@nestjs/common';
import { GetUser } from '../auth/decorator';
import { JwtGuard } from '../auth/guard';
import { ApiTags } from '@nestjs/swagger';
import { UserService } from './user.service';
import { User } from '@prisma/client';
@ApiTags('user')
@UseGuards(JwtGuard)
@Controller('user')
export class UserController {
constructor(private userService: UserService) {}
@Get('/my-assets')
GetMyAssets(
@Body()
@GetUser()
user: User
) {
return this.userService.GetMyAssets(user.id);
}
@Get('/my-trades')
GetMyTrades(
@Body()
@GetUser()
user: User
) {
return this.userService.GetMyTrades(user.id);
}
}

8
src/user/user.module.ts Normal file
View File

@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { UserService } from './user.service';
import { UserController } from './user.controller';
@Module({
providers: [UserService],
controllers: [UserController],
})
export class UserModule {}

31
src/user/user.service.ts Normal file
View File

@@ -0,0 +1,31 @@
import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async GetMyAssets(userId: string) {
const user = await this.prisma.user.findUnique({
where: {
id: userId,
},
include: {
UserHasCrypto: true,
},
});
return user;
}
async GetMyTrades(userId: string) {
const user = await this.prisma.trade.findMany({
where: {
OR: [{ id_giver: userId }, { id_receiver: userId }],
},
include: {
Crypto: true,
},
});
return user;
}
}

86
src/utils/checkUser.ts Normal file
View File

@@ -0,0 +1,86 @@
import { ForbiddenException } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
import { Roles } from './const/const';
const prisma = new PrismaClient();
export async function checkRoleLevel(userId: string, level: string) {
if (!userId || !level) {
throw new ForbiddenException('Access to resources denied');
}
checkRoleExist(level);
const user = await prisma.user.findUnique({
where: {
id: userId,
},
});
if (user && user.roleId) {
const role = await prisma.role.findFirst({
where: {
id: user.roleId,
},
});
if (role && role.id) {
checkRoleExist(role.name);
if (level === Roles.ADMIN && role.name !== Roles.ADMIN) {
throw new ForbiddenException('Access to resources denied');
}
} else {
throw new ForbiddenException('Access to resources denied');
}
} else {
throw new ForbiddenException('Access to resources denied');
}
}
function checkRoleExist(role: string) {
switch (role) {
case Roles.ADMIN:
case Roles.USER:
break;
default:
throw new ForbiddenException('Access to resources denied');
}
}
export async function checkUserHasAccount(jwtId: string) {
if (jwtId) {
const user = await prisma.user.findUnique({
where: {
id: jwtId,
isActive: true,
},
});
if (!user || !user.id) {
throw new ForbiddenException('Access to resources denied');
}
} else {
throw new ForbiddenException('Access to resources denied');
}
}
export async function checkuserIsAdmin(jwtId: string) {
if (jwtId) {
const user = await prisma.user.findUnique({
where: {
id: jwtId,
isActive: true,
},
include: {
Role: true,
},
});
if (!user || !user.id) {
throw new ForbiddenException('Access to resources denied2');
}
if (user.Role.name !== Roles.ADMIN) {
throw new ForbiddenException('Access to resources denied3');
}
} else {
throw new ForbiddenException('Access to resources denied4');
}
}

4
src/utils/const/const.ts Normal file
View File

@@ -0,0 +1,4 @@
export const Roles = {
ADMIN: 'admin',
USER: 'user',
};