Compare commits
4 Commits
d73aecd2ab
...
8ea217fe9f
Author | SHA1 | Date | |
---|---|---|---|
8ea217fe9f | |||
2fdc16e003 | |||
fa91630a29 | |||
313a51f8db |
14
.env.example
Normal file
14
.env.example
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
PORT=3333
|
||||||
|
|
||||||
|
POSTGRES_HOST=127.0.0.1
|
||||||
|
POSTGRES_PORT=5434
|
||||||
|
POSTGRES_DATABASE=
|
||||||
|
POSTGRES_USER=
|
||||||
|
POSTGRES_PASSWORD=
|
||||||
|
DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DATABASE}?schema=public&connection_limit=1
|
||||||
|
|
||||||
|
SMTP_PASSWORD=""
|
||||||
|
SMTP_EMAIL=""
|
||||||
|
SMTP_HOST=""
|
||||||
|
|
||||||
|
JWT_SECRET=
|
34
prisma/seed.ts
Normal file
34
prisma/seed.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { PrismaClient } from '@prisma/client';
|
||||||
|
|
||||||
|
const prisma = new PrismaClient();
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await prisma.role.create({
|
||||||
|
data: {
|
||||||
|
name: 'user',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await prisma.role.create({
|
||||||
|
data: {
|
||||||
|
name: 'admin',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await prisma.promoCode.create({
|
||||||
|
data: {
|
||||||
|
name: 'PROMO1000',
|
||||||
|
value: 1000,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
main()
|
||||||
|
.then(async () => {
|
||||||
|
await prisma.$disconnect();
|
||||||
|
})
|
||||||
|
.catch(async (e) => {
|
||||||
|
console.error(e);
|
||||||
|
await prisma.$disconnect();
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
|
22
src/app.controller.spec.ts
Normal file
22
src/app.controller.spec.ts
Normal 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
12
src/app.controller.ts
Normal 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 "Hello";
|
||||||
|
}
|
||||||
|
}
|
38
src/app.module.ts
Normal file
38
src/app.module.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { AppController } from "@/app.controller";
|
||||||
|
import { AppService } from "@/app.service";
|
||||||
|
import { AuthModule } from "@/auth/auth.module";
|
||||||
|
import { CryptoModule } from "@/crypto/crypto.module";
|
||||||
|
import { OfferModule } from "@/offer/offer.module";
|
||||||
|
import { PrismaModule } from "@/prisma/prisma.module";
|
||||||
|
import { PromoCodeModule } from "@/promoCode/promoCode.module";
|
||||||
|
import { RoleModule } from "@/role/role.module";
|
||||||
|
import { TradeModule } from "@/trade/trade.module";
|
||||||
|
import { UserModule } from "@/user/user.module";
|
||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { ConfigModule } from "@nestjs/config";
|
||||||
|
import { ThrottlerModule } from "@nestjs/throttler";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
}),
|
||||||
|
AuthModule,
|
||||||
|
PrismaModule,
|
||||||
|
RoleModule,
|
||||||
|
PromoCodeModule,
|
||||||
|
CryptoModule,
|
||||||
|
TradeModule,
|
||||||
|
OfferModule,
|
||||||
|
UserModule,
|
||||||
|
ThrottlerModule.forRoot([
|
||||||
|
{
|
||||||
|
ttl: 60000,
|
||||||
|
limit: 40,
|
||||||
|
},
|
||||||
|
]),
|
||||||
|
],
|
||||||
|
controllers: [AppController],
|
||||||
|
providers: [AppService],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
8
src/app.service.ts
Normal file
8
src/app.service.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AppService {
|
||||||
|
getHello(): string {
|
||||||
|
return "Hello World!";
|
||||||
|
}
|
||||||
|
}
|
21
src/auth/auth.controller.ts
Normal file
21
src/auth/auth.controller.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Body, Controller, HttpCode, HttpStatus, Post } from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { AuthService } from "./auth.service";
|
||||||
|
import { AuthLoginDto, AuthRegisterDto } from "./dto";
|
||||||
|
|
||||||
|
@ApiTags("auth")
|
||||||
|
@Controller("auth")
|
||||||
|
export class AuthController {
|
||||||
|
constructor(private authService: AuthService) {}
|
||||||
|
|
||||||
|
@Post("sign-up")
|
||||||
|
signUp(@Body() dto: AuthRegisterDto) {
|
||||||
|
return this.authService.signup(dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Post("sign-in")
|
||||||
|
signIn(@Body() dto: AuthLoginDto) {
|
||||||
|
return this.authService.signin(dto);
|
||||||
|
}
|
||||||
|
}
|
12
src/auth/auth.module.ts
Normal file
12
src/auth/auth.module.ts
Normal 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 {}
|
@ -1,110 +1,113 @@
|
|||||||
import { ForbiddenException, Injectable } from '@nestjs/common';
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
import { PrismaService } from '../prisma/prisma.service';
|
import { ConfigService } from "@nestjs/config";
|
||||||
import { AuthLoginDto, AuthRegisterDto } from './dto';
|
import { JwtService } from "@nestjs/jwt";
|
||||||
import * as argon from 'argon2';
|
import { Prisma, User } from "@prisma/client";
|
||||||
import { Prisma, User } from '@prisma/client';
|
import * as argon from "argon2";
|
||||||
import { JwtService } from '@nestjs/jwt';
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { AuthLoginDto, AuthRegisterDto } from "./dto";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class AuthService {
|
export class AuthService {
|
||||||
private readonly initialBalance = 1000;
|
private readonly initialBalance = 1000;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private prisma: PrismaService,
|
private prisma: PrismaService,
|
||||||
private jwt: JwtService,
|
private jwt: JwtService,
|
||||||
private config: ConfigService,
|
private config: ConfigService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async signup(dto: AuthRegisterDto) {
|
async signup(dto: AuthRegisterDto) {
|
||||||
const hash = await argon.hash(dto.password);
|
const hash = await argon.hash(dto.password);
|
||||||
const promoCode = await this.getPromoCode(dto.promoCode);
|
const promoCode = await this.getPromoCode(dto.promoCode);
|
||||||
const userRole = await this.getUserRole('user');
|
const userRole = await this.getUserRole("user");
|
||||||
const balance = this.calculateBalance(promoCode);
|
const balance = this.calculateBalance(promoCode);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const user = await this.createUser(dto, hash, userRole.id, balance);
|
const user = await this.createUser(dto, hash, userRole.id, balance);
|
||||||
return this.signToken(user);
|
return this.signToken(user);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleSignupError(error);
|
this.handleSignupError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getPromoCode(promoCode: string) {
|
private async getPromoCode(promoCode: string) {
|
||||||
return this.prisma.promoCode.findFirst({
|
return this.prisma.promoCode.findFirst({
|
||||||
where: {name: promoCode},
|
where: { name: promoCode },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getUserRole(roleName: string) {
|
private async getUserRole(roleName: string) {
|
||||||
return this.prisma.role.findFirst({
|
return this.prisma.role.findFirst({
|
||||||
where: {name: roleName},
|
where: { name: roleName },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private calculateBalance(promoCode: any) {
|
private calculateBalance(promoCode: any) {
|
||||||
let balance = this.initialBalance;
|
let balance = this.initialBalance;
|
||||||
if (promoCode && promoCode.value) {
|
if (promoCode && promoCode.value) {
|
||||||
balance += promoCode.value;
|
balance += promoCode.value;
|
||||||
}
|
}
|
||||||
return balance;
|
return balance;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async createUser(dto: AuthRegisterDto, hash: string, roleId: string, balance: number) {
|
private async createUser(
|
||||||
return this.prisma.user.create({
|
dto: AuthRegisterDto,
|
||||||
data: {
|
hash: string,
|
||||||
firstName: dto.firstName,
|
roleId: string,
|
||||||
lastName: dto.lastName,
|
balance: number,
|
||||||
pseudo: dto.pseudo,
|
) {
|
||||||
city: dto.city,
|
return this.prisma.user.create({
|
||||||
email: dto.email,
|
data: {
|
||||||
hash,
|
firstName: dto.firstName,
|
||||||
age: dto.age,
|
lastName: dto.lastName,
|
||||||
roleId,
|
pseudo: dto.pseudo,
|
||||||
isActive: true,
|
email: dto.email,
|
||||||
dollarAvailables: balance,
|
hash,
|
||||||
},
|
roleId,
|
||||||
});
|
isActive: true,
|
||||||
}
|
dollarAvailables: balance,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private handleSignupError(error: any) {
|
private handleSignupError(error: any) {
|
||||||
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
if (error instanceof Prisma.PrismaClientKnownRequestError) {
|
||||||
if (error.code === 'P2002') {
|
if (error.code === "P2002") {
|
||||||
throw new ForbiddenException('Credentials taken');
|
throw new ForbiddenException("Credentials taken");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
async signin(dto: AuthLoginDto) {
|
async signin(dto: AuthLoginDto) {
|
||||||
const userDatas = await this.prisma.user.findUnique({
|
const userDatas = await this.prisma.user.findUnique({
|
||||||
where: { email: dto.email },
|
where: { email: dto.email },
|
||||||
include: {
|
include: {
|
||||||
UserHasCrypto: { include: { Crypto: true } },
|
UserHasCrypto: { include: { Crypto: true } },
|
||||||
Role: true,
|
Role: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!userDatas) throw new ForbiddenException('Credentials incorrect');
|
if (!userDatas) throw new ForbiddenException("Credentials incorrect");
|
||||||
|
|
||||||
const pwMatches = await argon.verify(userDatas.hash, dto.password);
|
const pwMatches = await argon.verify(userDatas.hash, dto.password);
|
||||||
if (!pwMatches) throw new ForbiddenException('Credentials incorrect');
|
if (!pwMatches) throw new ForbiddenException("Credentials incorrect");
|
||||||
|
|
||||||
return this.signToken(userDatas);
|
return this.signToken(userDatas);
|
||||||
}
|
}
|
||||||
|
|
||||||
async signToken(user: any): Promise<{ access_token: string; user: User }> {
|
async signToken(user: any): Promise<{ access_token: string; user: User }> {
|
||||||
const payload = { sub: user.id, email: user.email };
|
const payload = { sub: user.id, email: user.email };
|
||||||
user.hash = null;
|
user.hash = null;
|
||||||
const secret = this.config.get('JWT_SECRET');
|
const secret = this.config.get("JWT_SECRET");
|
||||||
const token = await this.jwt.signAsync(payload, {
|
const token = await this.jwt.signAsync(payload, {
|
||||||
expiresIn: '30d',
|
expiresIn: "30d",
|
||||||
secret: secret,
|
secret: secret,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
access_token: token,
|
access_token: token,
|
||||||
user,
|
user,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
11
src/auth/decorator/get-user.decorator.ts
Normal file
11
src/auth/decorator/get-user.decorator.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { ExecutionContext, createParamDecorator } 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;
|
||||||
|
},
|
||||||
|
);
|
1
src/auth/decorator/index.ts
Normal file
1
src/auth/decorator/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./get-user.decorator";
|
13
src/auth/dto/auth.login.dto.ts
Normal file
13
src/auth/dto/auth.login.dto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsEmail, IsNotEmpty, IsString } from "class-validator";
|
||||||
|
export class AuthLoginDto {
|
||||||
|
@IsEmail()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@ApiProperty({ type: String, description: "email" })
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: String, description: "password" })
|
||||||
|
@IsString()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
73
src/auth/dto/auth.register.dto.ts
Normal file
73
src/auth/dto/auth.register.dto.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import {
|
||||||
|
IsEmail,
|
||||||
|
IsInt,
|
||||||
|
IsNotEmpty,
|
||||||
|
IsOptional,
|
||||||
|
IsString,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from "class-validator";
|
||||||
|
export class AuthRegisterDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "FirstName",
|
||||||
|
example: "Thomas",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Last Name",
|
||||||
|
example: "Anderson",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Pseudo",
|
||||||
|
example: "Néo",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
pseudo: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "email",
|
||||||
|
example: "neo@matrix.fr",
|
||||||
|
})
|
||||||
|
@MaxLength(255)
|
||||||
|
@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
2
src/auth/dto/index.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./auth.register.dto";
|
||||||
|
export * from "./auth.login.dto";
|
1
src/auth/guard/index.ts
Normal file
1
src/auth/guard/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./jwt.guard";
|
7
src/auth/guard/jwt.guard.ts
Normal file
7
src/auth/guard/jwt.guard.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { AuthGuard } from "@nestjs/passport";
|
||||||
|
|
||||||
|
export class JwtGuard extends AuthGuard("jwt") {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
1
src/auth/strategy/index.ts
Normal file
1
src/auth/strategy/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./jwt.strategy";
|
28
src/auth/strategy/jwt.strategy.ts
Normal file
28
src/auth/strategy/jwt.strategy.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { PrismaService } from "@/prisma/prisma.service";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { ConfigService } from "@nestjs/config";
|
||||||
|
import { PassportStrategy } from "@nestjs/passport";
|
||||||
|
import { ExtractJwt, Strategy } from "passport-jwt";
|
||||||
|
|
||||||
|
@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;
|
||||||
|
}
|
||||||
|
}
|
73
src/crypto/crypto.controller.ts
Normal file
73
src/crypto/crypto.controller.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { JwtGuard } from "src/auth/guard";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
import { CryptoService } from "./crypto.service";
|
||||||
|
import { CryptoDto } from "./dto";
|
||||||
|
import { BuyCryptoDto } from "./dto/buy.crypto.dto";
|
||||||
|
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
@ApiTags("crypto")
|
||||||
|
@Controller("crypto")
|
||||||
|
export class CryptoController {
|
||||||
|
constructor(private cryptoService: CryptoService) {}
|
||||||
|
|
||||||
|
@Get("/all")
|
||||||
|
getAllPromoCodes(@GetUser() user: User) {
|
||||||
|
return this.cryptoService.getCryptos(user.id);
|
||||||
|
}
|
||||||
|
@Get("/search/:name")
|
||||||
|
searchCrypto(@GetUser() user: User, @Param("name") cryptoName: string) {
|
||||||
|
return this.cryptoService.searchCryptos(user.id, cryptoName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("/history/:id")
|
||||||
|
CryptoHistory(@GetUser() user: User, @Param("id") cryptoId: string) {
|
||||||
|
return this.cryptoService.getCryptoHistory(user.id, cryptoId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.CREATED)
|
||||||
|
@Post("/create")
|
||||||
|
createPromoCode(
|
||||||
|
@Body()
|
||||||
|
dto: CryptoDto,
|
||||||
|
@GetUser() user: User,
|
||||||
|
) {
|
||||||
|
return this.cryptoService.createCrypto(user.id, dto);
|
||||||
|
}
|
||||||
|
@Post("/buy")
|
||||||
|
buyCrypto(
|
||||||
|
@Body()
|
||||||
|
dto: BuyCryptoDto,
|
||||||
|
@GetUser() user: User,
|
||||||
|
) {
|
||||||
|
return this.cryptoService.buyCrypto(user.id, dto);
|
||||||
|
}
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
@Patch("/update/:id")
|
||||||
|
editCryptoById(
|
||||||
|
@Param("id") cryptoId: string,
|
||||||
|
@Body() dto: CryptoDto,
|
||||||
|
@GetUser() user: User,
|
||||||
|
) {
|
||||||
|
return this.cryptoService.editCryptoById(user.id, cryptoId, dto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(HttpStatus.NO_CONTENT)
|
||||||
|
@Delete("/delete/:id")
|
||||||
|
deleteOfferById(@Param("id") roleId: string, @GetUser() user: User) {
|
||||||
|
return this.cryptoService.deleteCryptoById(user.id, roleId);
|
||||||
|
}
|
||||||
|
}
|
9
src/crypto/crypto.module.ts
Normal file
9
src/crypto/crypto.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { CryptoController } from "./crypto.controller";
|
||||||
|
import { CryptoService } from "./crypto.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [CryptoService],
|
||||||
|
controllers: [CryptoController],
|
||||||
|
})
|
||||||
|
export class CryptoModule {}
|
192
src/crypto/crypto.service.ts
Normal file
192
src/crypto/crypto.service.ts
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
|
import { checkUserHasAccount, checkUserIsAdmin } from "src/utils/checkUser";
|
||||||
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
|
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 getCryptoHistory(userId: string, cryptoId: string) {
|
||||||
|
await checkUserHasAccount(userId);
|
||||||
|
|
||||||
|
if (cryptoId) {
|
||||||
|
return this.prisma.crypto.findMany({
|
||||||
|
where: {
|
||||||
|
id: cryptoId,
|
||||||
|
},
|
||||||
|
|
||||||
|
orderBy: {
|
||||||
|
created_at: "desc",
|
||||||
|
},
|
||||||
|
take: 50,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
throw new ForbiddenException("Crypto UUID required");
|
||||||
|
}
|
||||||
|
|
||||||
|
async createCrypto(userId: string, dto: CryptoDto) {
|
||||||
|
await checkUserIsAdmin(userId);
|
||||||
|
|
||||||
|
const existingCryptosWithSameName = await this.prisma.crypto.findMany({
|
||||||
|
where: {
|
||||||
|
name: dto.name,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (existingCryptosWithSameName.length > 0) {
|
||||||
|
throw new ForbiddenException("Name already taken");
|
||||||
|
}
|
||||||
|
const crypto = await this.prisma.crypto.create({
|
||||||
|
data: {
|
||||||
|
name: dto.name,
|
||||||
|
image: dto.image,
|
||||||
|
value: dto.value,
|
||||||
|
quantity: dto.quantity,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (crypto.quantity < dto.amount) {
|
||||||
|
throw new ForbiddenException("No more tokens available");
|
||||||
|
}
|
||||||
|
const necessaryAmount = crypto.value * dto.amount;
|
||||||
|
console.log(necessaryAmount, user.dollarAvailables);
|
||||||
|
|
||||||
|
if (necessaryAmount > user.dollarAvailables) {
|
||||||
|
throw new ForbiddenException("Make money first :) ");
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const newCryptoValue = crypto.value * 1.1;
|
||||||
|
|
||||||
|
await this.prisma.cryptoHistory.create({
|
||||||
|
data: {
|
||||||
|
id_crypto: crypto.id,
|
||||||
|
value: newCryptoValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const newQuantity = (crypto.quantity -= dto.amount);
|
||||||
|
return this.prisma.crypto.update({
|
||||||
|
where: {
|
||||||
|
id: dto.id_crypto,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
value: newCryptoValue,
|
||||||
|
quantity: newQuantity,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
32
src/crypto/dto/buy.crypto.dto.ts
Normal file
32
src/crypto/dto/buy.crypto.dto.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import {
|
||||||
|
IsNumber,
|
||||||
|
IsString,
|
||||||
|
IsUUID,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from "class-validator";
|
||||||
|
export class BuyCryptoDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Cryptocurrency UUID",
|
||||||
|
example: "12121-DSZD-E221212-2121221",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsString()
|
||||||
|
@IsUUID()
|
||||||
|
id_crypto: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: Number,
|
||||||
|
description: "Amount of token traded",
|
||||||
|
example: 2,
|
||||||
|
})
|
||||||
|
@Min(1)
|
||||||
|
@Max(1000)
|
||||||
|
@IsNumber()
|
||||||
|
amount: number;
|
||||||
|
}
|
54
src/crypto/dto/crypto.dto.ts
Normal file
54
src/crypto/dto/crypto.dto.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import {
|
||||||
|
IsNumber,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
IsUrl,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from "class-validator";
|
||||||
|
export class CryptoDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Cryptocurrency name",
|
||||||
|
example: "BTC",
|
||||||
|
})
|
||||||
|
@MaxLength(50)
|
||||||
|
@MinLength(1)
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: Number,
|
||||||
|
description: "Value for the cryptocurrency in $",
|
||||||
|
example: 1,
|
||||||
|
})
|
||||||
|
@Min(1)
|
||||||
|
@Max(10000)
|
||||||
|
@IsPositive()
|
||||||
|
@IsNumber()
|
||||||
|
value: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: Number,
|
||||||
|
description: "Quantity of tokens available on the platform",
|
||||||
|
example: 100,
|
||||||
|
})
|
||||||
|
@Min(1)
|
||||||
|
@Max(10000)
|
||||||
|
@IsPositive()
|
||||||
|
@IsNumber()
|
||||||
|
quantity: number;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Image for the cryptocurrency in ",
|
||||||
|
example: "https://myImage/com",
|
||||||
|
})
|
||||||
|
@MaxLength(255)
|
||||||
|
@IsUrl()
|
||||||
|
@IsString()
|
||||||
|
image: string;
|
||||||
|
}
|
1
src/crypto/dto/index.ts
Normal file
1
src/crypto/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./crypto.dto";
|
38
src/main.ts
38
src/main.ts
@ -1,26 +1,26 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { ValidationPipe } from "@nestjs/common";
|
||||||
import { AppModule } from './app.module';
|
import { NestFactory } from "@nestjs/core";
|
||||||
import { ValidationPipe } from '@nestjs/common';
|
import { DocumentBuilder, SwaggerModule } from "@nestjs/swagger";
|
||||||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
import { AppModule } from "./app.module";
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
app.enableCors();
|
app.enableCors();
|
||||||
|
|
||||||
const config = new DocumentBuilder()
|
const config = new DocumentBuilder()
|
||||||
.setTitle('Neptune API')
|
.setTitle("Neptune API")
|
||||||
.setDescription('A fictive app')
|
.setDescription("A fictive app")
|
||||||
.setVersion('1.0')
|
.setVersion("1.0")
|
||||||
.build();
|
.build();
|
||||||
const document = SwaggerModule.createDocument(app, config);
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
SwaggerModule.setup('api', app, document);
|
SwaggerModule.setup("api", app, document);
|
||||||
|
|
||||||
app.useGlobalPipes(
|
app.useGlobalPipes(
|
||||||
new ValidationPipe({
|
new ValidationPipe({
|
||||||
whitelist: true,
|
whitelist: true,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
|
|
||||||
await app.listen(process.env.PORT || 3000);
|
await app.listen(process.env.PORT || 3000);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
1
src/offer/dto/index.ts
Normal file
1
src/offer/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./offer.dto";
|
32
src/offer/dto/offer.dto.ts
Normal file
32
src/offer/dto/offer.dto.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import {
|
||||||
|
IsNumber,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
IsUUID,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
} from "class-validator";
|
||||||
|
export class OfferDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Cryptocurrency UUID",
|
||||||
|
example: "12121-DSZD-E221212-6227933",
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
@IsUUID()
|
||||||
|
id_crypto: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: "number",
|
||||||
|
|
||||||
|
description: "Amount traded ",
|
||||||
|
example: 21,
|
||||||
|
})
|
||||||
|
@Min(1)
|
||||||
|
@Max(1000)
|
||||||
|
@IsNumber()
|
||||||
|
@IsPositive()
|
||||||
|
amount: number;
|
||||||
|
}
|
58
src/offer/offer.controller.ts
Normal file
58
src/offer/offer.controller.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
// UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
// import { JwtGuard } from '../auth/guard';
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { JwtGuard } from "src/auth/guard";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
import { OfferDto } from "./dto";
|
||||||
|
import { OfferService } from "./offer.service";
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
9
src/offer/offer.module.ts
Normal file
9
src/offer/offer.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { OfferController } from "./offer.controller";
|
||||||
|
import { OfferService } from "./offer.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [OfferService],
|
||||||
|
controllers: [OfferController],
|
||||||
|
})
|
||||||
|
export class OfferModule {}
|
35
src/offer/offer.service.sql
Normal file
35
src/offer/offer.service.sql
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
SELECT
|
||||||
|
o.amount,
|
||||||
|
o.created_at,
|
||||||
|
o.id_user,
|
||||||
|
c.id AS crypto_id,
|
||||||
|
c.name AS crypto_name,
|
||||||
|
c.value AS crypto_value,
|
||||||
|
c.image AS crypto_image,
|
||||||
|
c.quantity AS crypto_quantity
|
||||||
|
FROM
|
||||||
|
"Offer" o
|
||||||
|
JOIN
|
||||||
|
"Crypto" c ON o.id_crypto = c.id
|
||||||
|
ORDER BY
|
||||||
|
o.created_at DESC;
|
||||||
|
|
||||||
|
INSERT INTO "Offer" (id, id_crypto, id_user, amount, created_at, updated_at)
|
||||||
|
VALUES (gen_random_uuid(), 'dto.id_crypto', 'userId', 'dto.amount', NOW(), NOW());
|
||||||
|
|
||||||
|
SELECT * FROM "Offer" WHERE id = 'offerId';
|
||||||
|
|
||||||
|
SELECT * FROM "Crypto" WHERE id = 'dto.id_crypto';
|
||||||
|
|
||||||
|
UPDATE "Offer"
|
||||||
|
SET
|
||||||
|
id_crypto = 'dto.id_crypto',
|
||||||
|
amount = 'dto.amount',
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE
|
||||||
|
id = 'offerId';
|
||||||
|
|
||||||
|
SELECT * FROM "Offer" WHERE id = 'id';
|
||||||
|
|
||||||
|
DELETE FROM "Offer" WHERE id = 'id';
|
||||||
|
|
84
src/offer/offer.service.ts
Normal file
84
src/offer/offer.service.ts
Normal file
@ -0,0 +1,84 @@
|
|||||||
|
import { PrismaService } from "@/prisma/prisma.service";
|
||||||
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
|
import { checkUserHasAccount, checkUserIsAdmin } from "src/utils/checkUser";
|
||||||
|
import { OfferDto } from "./dto";
|
||||||
|
|
||||||
|
@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);
|
||||||
|
return this.prisma.offer.create({
|
||||||
|
data: {
|
||||||
|
id_crypto: dto.id_crypto,
|
||||||
|
id_user: userId,
|
||||||
|
amount: dto.amount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async editOfferById(userId: string, offerId: string, dto: OfferDto) {
|
||||||
|
await checkUserHasAccount(userId);
|
||||||
|
|
||||||
|
const offer = await this.prisma.offer.findUnique({
|
||||||
|
where: {
|
||||||
|
id: offerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const crypto = await this.prisma.crypto.findUnique({
|
||||||
|
where: {
|
||||||
|
id: dto.id_crypto,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!crypto || !crypto.id) {
|
||||||
|
throw new ForbiddenException("Crypto doesnt exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!offer || offer.id !== offerId)
|
||||||
|
throw new ForbiddenException("Offer id mandatory");
|
||||||
|
|
||||||
|
return this.prisma.offer.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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
9
src/prisma/prisma.module.ts
Normal file
9
src/prisma/prisma.module.ts
Normal 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 {}
|
17
src/prisma/prisma.service.ts
Normal file
17
src/prisma/prisma.service.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
// biome-ignore lint/style/useImportType:
|
||||||
|
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"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
1
src/promoCode/dto/index.ts
Normal file
1
src/promoCode/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./promoCode.dto";
|
32
src/promoCode/dto/promoCode.dto.ts
Normal file
32
src/promoCode/dto/promoCode.dto.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import {
|
||||||
|
IsNumber,
|
||||||
|
IsPositive,
|
||||||
|
IsString,
|
||||||
|
Max,
|
||||||
|
MaxLength,
|
||||||
|
Min,
|
||||||
|
MinLength,
|
||||||
|
} from "class-validator";
|
||||||
|
export class PromoCodeDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Name of the PromoCOde",
|
||||||
|
example: "FILOU10",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: Number,
|
||||||
|
description: "Dollars given for account creation when promoCode applied",
|
||||||
|
example: 100,
|
||||||
|
})
|
||||||
|
@IsPositive()
|
||||||
|
@Min(1)
|
||||||
|
@Max(3000)
|
||||||
|
@IsNumber()
|
||||||
|
value: number;
|
||||||
|
}
|
57
src/promoCode/promoCode.controller.ts
Normal file
57
src/promoCode/promoCode.controller.ts
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { JwtGuard } from "src/auth/guard";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
import { PromoCodeDto } from "./dto";
|
||||||
|
import { PromoCodeService } from "./promoCode.service";
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
9
src/promoCode/promoCode.module.ts
Normal file
9
src/promoCode/promoCode.module.ts
Normal 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 {}
|
70
src/promoCode/promoCode.service.spec.ts
Normal file
70
src/promoCode/promoCode.service.spec.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { PrismaService } from "@/prisma/prisma.service";
|
||||||
|
import { checkUserIsAdmin } from "@/utils/checkUser";
|
||||||
|
import { ForbiddenException } from "@nestjs/common";
|
||||||
|
// biome-ignore lint/style/useImportType:
|
||||||
|
import { Test, TestingModule } from "@nestjs/testing";
|
||||||
|
import { PromoCodeService } from "./promoCode.service";
|
||||||
|
|
||||||
|
jest.mock("../utils/checkUser");
|
||||||
|
|
||||||
|
describe("PromoCodeService", () => {
|
||||||
|
let service: PromoCodeService;
|
||||||
|
let prisma: PrismaService;
|
||||||
|
|
||||||
|
const mockPrismaService = {
|
||||||
|
promoCode: {
|
||||||
|
findMany: jest.fn(),
|
||||||
|
create: jest.fn(),
|
||||||
|
findUnique: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
PromoCodeService,
|
||||||
|
{ provide: PrismaService, useValue: mockPrismaService },
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<PromoCodeService>(PromoCodeService);
|
||||||
|
prisma = module.get<PrismaService>(PrismaService);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("getPromoCodes", () => {
|
||||||
|
it("should get promo codes if user is admin", async () => {
|
||||||
|
(checkUserIsAdmin as jest.Mock).mockResolvedValue(true);
|
||||||
|
const mockPromoCodes = [
|
||||||
|
{ id: "1", name: "PROMO10", value: 10 },
|
||||||
|
{ id: "2", name: "PROMO20", value: 20 },
|
||||||
|
];
|
||||||
|
mockPrismaService.promoCode.findMany.mockResolvedValue(mockPromoCodes);
|
||||||
|
|
||||||
|
const result = await service.getPromoCodes("user-id");
|
||||||
|
|
||||||
|
expect(checkUserIsAdmin).toHaveBeenCalledWith("user-id");
|
||||||
|
expect(prisma.promoCode.findMany).toHaveBeenCalledWith({
|
||||||
|
orderBy: { name: "asc" },
|
||||||
|
select: { id: true, name: true, value: true },
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockPromoCodes);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("should throw ForbiddenException if user is not admin", async () => {
|
||||||
|
(checkUserIsAdmin as jest.Mock).mockRejectedValue(
|
||||||
|
new ForbiddenException("Not an admin"),
|
||||||
|
);
|
||||||
|
|
||||||
|
await expect(service.getPromoCodes("user-id")).rejects.toThrow(ForbiddenException);
|
||||||
|
|
||||||
|
expect(checkUserIsAdmin).toHaveBeenCalledWith("user-id");
|
||||||
|
expect(prisma.promoCode.findMany).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
75
src/promoCode/promoCode.service.ts
Normal file
75
src/promoCode/promoCode.service.ts
Normal file
@ -0,0 +1,75 @@
|
|||||||
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
|
import { checkUserIsAdmin } from "../utils/checkUser";
|
||||||
|
import { PromoCodeDto } from "./dto";
|
||||||
|
@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
1
src/role/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./role.dto";
|
13
src/role/dto/role.dto.ts
Normal file
13
src/role/dto/role.dto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsString, MaxLength, MinLength } from "class-validator";
|
||||||
|
export class RoleDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Role Name",
|
||||||
|
example: "user",
|
||||||
|
})
|
||||||
|
@MinLength(1)
|
||||||
|
@MaxLength(50)
|
||||||
|
@IsString()
|
||||||
|
name: string;
|
||||||
|
}
|
59
src/role/role.controller.ts
Normal file
59
src/role/role.controller.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Delete,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Param,
|
||||||
|
Patch,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
// UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { JwtGuard } from "src/auth/guard";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
// import { JwtGuard } from '../auth/guard';
|
||||||
|
import { RoleDto } from "./dto";
|
||||||
|
import { RoleService } from "./role.service";
|
||||||
|
|
||||||
|
@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
8
src/role/role.module.ts
Normal 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
72
src/role/role.service.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
|
import { checkUserIsAdmin } from "src/utils/checkUser";
|
||||||
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
|
import { RoleDto } from "./dto";
|
||||||
|
// 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
1
src/trade/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./trade.dto";
|
13
src/trade/dto/trade.dto.ts
Normal file
13
src/trade/dto/trade.dto.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from "@nestjs/swagger";
|
||||||
|
import { IsNotEmpty, IsString, IsUUID } from "class-validator";
|
||||||
|
export class TradeDto {
|
||||||
|
@ApiProperty({
|
||||||
|
type: String,
|
||||||
|
description: "Offer UUID ",
|
||||||
|
example: "121212-DSDZ1-21212DJDZ-31313",
|
||||||
|
})
|
||||||
|
@IsUUID()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@IsString()
|
||||||
|
id_offer: string;
|
||||||
|
}
|
38
src/trade/trade.controller.ts
Normal file
38
src/trade/trade.controller.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
} from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { JwtGuard } from "src/auth/guard";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
import { TradeDto } from "./dto";
|
||||||
|
import { TradeService } from "./trade.service";
|
||||||
|
|
||||||
|
@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);
|
||||||
|
}
|
||||||
|
}
|
9
src/trade/trade.module.ts
Normal file
9
src/trade/trade.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { TradeController } from "./trade.controller";
|
||||||
|
import { TradeService } from "./trade.service";
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [TradeService],
|
||||||
|
controllers: [TradeController],
|
||||||
|
})
|
||||||
|
export class TradeModule {}
|
173
src/trade/trade.service.ts
Normal file
173
src/trade/trade.service.ts
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
import { ForbiddenException, Injectable } from "@nestjs/common";
|
||||||
|
import { checkUserHasAccount, checkUserIsAdmin } from "src/utils/checkUser";
|
||||||
|
import { PrismaService } from "../prisma/prisma.service";
|
||||||
|
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,
|
||||||
|
// },
|
||||||
|
|
||||||
|
select: {
|
||||||
|
Giver: {
|
||||||
|
select: {
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
pseudo: true,
|
||||||
|
dollarAvailables: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Receiver: {
|
||||||
|
select: {
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
pseudo: true,
|
||||||
|
dollarAvailables: 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 offer = await this.prisma.offer.findUnique({
|
||||||
|
where: {
|
||||||
|
id: dto.id_offer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const crypto = await this.prisma.crypto.findFirst({
|
||||||
|
where: {
|
||||||
|
id: offer.id_crypto,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const buyer = await this.prisma.user.findFirst({
|
||||||
|
where: {
|
||||||
|
id: offer.id_user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const price = crypto.value * offer.amount;
|
||||||
|
if (buyer.dollarAvailables < price) {
|
||||||
|
throw new ForbiddenException(
|
||||||
|
`Acqueror ${buyer.pseudo} doesnt have enough money to make this trade`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const asset = await this.prisma.userHasCrypto.findFirst({
|
||||||
|
where: {
|
||||||
|
id_crypto: offer.id_crypto,
|
||||||
|
id_user: offer.id_user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!asset || asset.amount < offer.amount) {
|
||||||
|
throw new ForbiddenException(`Seller doesnt have enough ${crypto.name} `);
|
||||||
|
}
|
||||||
|
|
||||||
|
const trade = await this.prisma.trade.create({
|
||||||
|
data: {
|
||||||
|
id_giver: offer.id_user,
|
||||||
|
id_receiver: userId,
|
||||||
|
id_crypto: offer.id_crypto,
|
||||||
|
amount_traded: offer.amount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const newBalanceGiver = (asset.amount -= offer.amount);
|
||||||
|
await this.prisma.userHasCrypto.update({
|
||||||
|
where: {
|
||||||
|
id: asset.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: newBalanceGiver,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const receiverAssets = await this.prisma.userHasCrypto.findFirst({
|
||||||
|
where: {
|
||||||
|
id_user: userId,
|
||||||
|
id_crypto: offer.id_crypto,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!receiverAssets) {
|
||||||
|
await this.prisma.userHasCrypto.create({
|
||||||
|
data: {
|
||||||
|
id_user: userId,
|
||||||
|
id_crypto: offer.id_crypto,
|
||||||
|
amount: offer.amount,
|
||||||
|
createdAt: new Date(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const newBalanceReceiver = receiverAssets.amount + offer.amount;
|
||||||
|
await this.prisma.userHasCrypto.update({
|
||||||
|
where: {
|
||||||
|
id: receiverAssets.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
amount: newBalanceReceiver,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const newValue = crypto.value * 1.1;
|
||||||
|
await this.prisma.cryptoHistory.create({
|
||||||
|
data: {
|
||||||
|
id_crypto: crypto.id,
|
||||||
|
value: newValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.prisma.crypto.update({
|
||||||
|
where: {
|
||||||
|
id: crypto.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
value: newValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const prevAmount = buyer.dollarAvailables;
|
||||||
|
|
||||||
|
await this.prisma.user.update({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
dollarAvailables: prevAmount - price,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await this.prisma.offer.delete({
|
||||||
|
where: {
|
||||||
|
id: offer.id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return trade;
|
||||||
|
}
|
||||||
|
}
|
36
src/user/user.controller.ts
Normal file
36
src/user/user.controller.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { Controller, Get, UseGuards } from "@nestjs/common";
|
||||||
|
import { ApiTags } from "@nestjs/swagger";
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
import { GetUser } from "../auth/decorator";
|
||||||
|
import { JwtGuard } from "../auth/guard";
|
||||||
|
import { UserService } from "./user.service";
|
||||||
|
|
||||||
|
@ApiTags("user")
|
||||||
|
@UseGuards(JwtGuard)
|
||||||
|
@Controller("user")
|
||||||
|
export class UserController {
|
||||||
|
constructor(private userService: UserService) {}
|
||||||
|
|
||||||
|
@Get("/my-assets")
|
||||||
|
GetMyAssets(
|
||||||
|
@GetUser()
|
||||||
|
user: User,
|
||||||
|
) {
|
||||||
|
return this.userService.getMyAssets(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get("/users-assets")
|
||||||
|
GetAlLAssets(
|
||||||
|
@GetUser()
|
||||||
|
user: User,
|
||||||
|
) {
|
||||||
|
return this.userService.getUsersAssets(user.id);
|
||||||
|
}
|
||||||
|
@Get("/my-trades")
|
||||||
|
GetMyTrades(
|
||||||
|
@GetUser()
|
||||||
|
user: User,
|
||||||
|
) {
|
||||||
|
return this.userService.getMyTrades(user.id);
|
||||||
|
}
|
||||||
|
}
|
8
src/user/user.module.ts
Normal file
8
src/user/user.module.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Module } from "@nestjs/common";
|
||||||
|
import { UserController } from "./user.controller";
|
||||||
|
import { UserService } from "./user.service";
|
||||||
|
@Module({
|
||||||
|
providers: [UserService],
|
||||||
|
controllers: [UserController],
|
||||||
|
})
|
||||||
|
export class UserModule {}
|
80
src/user/user.service.ts
Normal file
80
src/user/user.service.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
import { PrismaService } from "@/prisma/prisma.service";
|
||||||
|
import { Injectable } from "@nestjs/common";
|
||||||
|
import { checkUserHasAccount, checkUserIsAdmin } from "src/utils/checkUser";
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserService {
|
||||||
|
constructor(private prisma: PrismaService) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the assets of a given user, including their first name, last name, available dollars,
|
||||||
|
* pseudo, and associated cryptocurrencies.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The unique identifier of the user whose assets are being retrieved.
|
||||||
|
* @return A promise that resolves to an object containing the user's assets and associated data.
|
||||||
|
*/
|
||||||
|
async getMyAssets(userId: string) {
|
||||||
|
await checkUserHasAccount(userId);
|
||||||
|
|
||||||
|
return this.prisma.user.findUnique({
|
||||||
|
where: {
|
||||||
|
id: userId,
|
||||||
|
},
|
||||||
|
select: {
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
dollarAvailables: true,
|
||||||
|
pseudo: true,
|
||||||
|
UserHasCrypto: {
|
||||||
|
select: {
|
||||||
|
Crypto: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves the assets of users based on user ID.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The ID of the user requesting the assets.
|
||||||
|
* @return A promise that resolves to an array of user assets.
|
||||||
|
*/
|
||||||
|
async getUsersAssets(userId: string) {
|
||||||
|
await checkUserIsAdmin(userId);
|
||||||
|
|
||||||
|
return this.prisma.user.findMany({
|
||||||
|
select: {
|
||||||
|
firstName: true,
|
||||||
|
lastName: true,
|
||||||
|
pseudo: true,
|
||||||
|
dollarAvailables: true,
|
||||||
|
UserHasCrypto: {
|
||||||
|
select: {
|
||||||
|
Crypto: true,
|
||||||
|
amount: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
take: 20,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetches all trades associated with a given user.
|
||||||
|
*
|
||||||
|
* @param {string} userId - The unique identifier of the user.
|
||||||
|
* @return A promise that resolves to an array of trade objects.
|
||||||
|
*/
|
||||||
|
async getMyTrades(userId: string) {
|
||||||
|
await checkUserHasAccount(userId);
|
||||||
|
return this.prisma.trade.findMany({
|
||||||
|
where: {
|
||||||
|
OR: [{ id_giver: userId }, { id_receiver: userId }],
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
Crypto: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
86
src/utils/checkUser.ts
Normal file
86
src/utils/checkUser.ts
Normal 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?.roleId) {
|
||||||
|
const role = await prisma.role.findFirst({
|
||||||
|
where: {
|
||||||
|
id: user.roleId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (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
4
src/utils/const/const.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const Roles = {
|
||||||
|
ADMIN: "admin",
|
||||||
|
USER: "user",
|
||||||
|
};
|
0
src/utils/styles.ts
Normal file
0
src/utils/styles.ts
Normal file
16
src/utils/tests/user-mock.ts
Normal file
16
src/utils/tests/user-mock.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
// auth/test/mocks.ts
|
||||||
|
import { User } from "@prisma/client";
|
||||||
|
|
||||||
|
export const getMockUser = (): User => ({
|
||||||
|
id: "user-id",
|
||||||
|
email: "test@example.com",
|
||||||
|
firstName: "Test User",
|
||||||
|
lastName: "Test",
|
||||||
|
hash: "test-hash",
|
||||||
|
pseudo: "test-pseudo",
|
||||||
|
isActive: true,
|
||||||
|
created_at: new Date(),
|
||||||
|
updated_at: new Date(),
|
||||||
|
roleId: "user",
|
||||||
|
dollarAvailables: 1000,
|
||||||
|
});
|
0
src/utils/types.ts
Normal file
0
src/utils/types.ts
Normal file
Loading…
x
Reference in New Issue
Block a user