Compare commits

...

9 Commits

Author SHA1 Message Date
d73aecd2ab
Add initial Prisma schema
Set up Prisma models for Crypto, CryptoHistory, Offer, PromoCode, Role, Trade, User, and UserHasCrypto. This includes defining fields, relationships, and default values for each model, and configuring PostgreSQL as the database provider.
2024-11-08 09:35:32 +01:00
cfcc998271
Add AuthService for user registration and login
Implement user signup and signin functionality with password hashing, role assignment, and JWT token generation. Integrate promo code for initial balance adjustment, and handle unique credential errors.
2024-10-31 11:49:03 +01:00
747b4357f9
Remove trailing slash from .gitignore prisma/migrations entry
The trailing slash was removed to accurately match the directory during ignore operations. This ensures that the .gitignore works as expected for the prisma migrations folder.
2024-10-31 11:45:49 +01:00
1f7ed6e03c
Add .gitignore file
Created a .gitignore file to exclude compiled output, logs, OS-specific files, test directories, editor settings, environment variable files, temporary directories, runtime data, and diagnostic reports from version control. This helps maintain a cleaner repository and prevents the inclusion of unnecessary or sensitive files.
2024-10-31 11:44:39 +01:00
5666c16d00
Add main application bootstrap logic
Introduce the main.ts file to set up the NestJS application. This includes enabling CORS, configuring Swagger documentation, and setting up global validation pipes.
2024-10-31 11:43:49 +01:00
272d3ec50a
Add nest-cli.json configuration file
This adds a new configuration file for the Nest CLI to the project. The file specifies the schema, schematics collection, source root directory, and compiler options, including deleting the output directory before building.
2024-10-31 11:43:38 +01:00
57d9845d19
Add pnpm-lock.yaml file
Introduce a new pnpm-lock.yaml file to manage dependency versions and settings. This change ensures consistency across installations by locking the specific versions of dependencies.
2024-10-31 11:43:27 +01:00
80c8662ed1
Add tsconfig and tsconfig.build configuration files
Introduced `tsconfig.json` with TypeScript compiler options tailored for the project. Additionally, created `tsconfig.build.json` to extend the main config for build-specific settings and exclusions. This setup ensures a streamlined compilation process and cleaner build output.
2024-10-31 11:42:56 +01:00
0fb7c3e288
Add docker-compose file for PostgreSQL service
Introduces a docker-compose.yaml file to set up a PostgreSQL container named "neptune-db". Configures the container with environment variables and volumes for persistent data storage.
2024-10-31 11:41:42 +01:00
10 changed files with 6341 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
# compiled output
/dist
/node_modules
/build
# Logs
logs
*.log
npm-debug.log*
pnpm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# OS
.DS_Store
# Tests
/coverage
/.nyc_output
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# temp directory
.temp
.tmp
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
/prisma/migrations

14
docker-compose.yaml Normal file
View File

@ -0,0 +1,14 @@
services:
database:
container_name: "neptune-db"
image: 'postgres:latest'
env_file:
- .env
ports:
- "${POSTGRES_PORT}:5432"
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_DATABASE}
volumes:
- './db-data/:/var/lib/postgresql/data/'

8
nest-cli.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/nest-cli",
"collection": "@nestjs/schematics",
"sourceRoot": "src",
"compilerOptions": {
"deleteOutDir": true
}
}

85
package.json Normal file
View File

@ -0,0 +1,85 @@
{
"name": "neptune-back",
"version": "0.0.1",
"description": "",
"author": "Mathis HERRIOT",
"private": true,
"license": "MIT",
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"scripts": {
"build": "nest build",
"start": "nest start",
"start:dev": "nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/src/main",
"test": "jest",
"test:watch": "jest --watch",
"test:cov": "jest --coverage",
"test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand",
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/common": "^10.4.6",
"@nestjs/config": "^3.3.0",
"@nestjs/core": "^10.4.6",
"@nestjs/jwt": "^10.2.0",
"@nestjs/passport": "^10.0.3",
"@nestjs/platform-express": "^10.4.6",
"@nestjs/swagger": "^7.4.2",
"@nestjs/throttler": "^5.2.0",
"@prisma/client": "^5.21.1",
"@prisma/studio": "^0.497.0",
"@types/nodemailer": "^6.4.16",
"argon2": "^0.31.2",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.1",
"ejs": "^3.1.10",
"handlebars": "^4.7.8",
"helmet": "^7.2.0",
"nodemailer": "^6.9.16",
"passport": "^0.7.0",
"passport-jwt": "^4.0.1",
"prisma": "^5.21.1",
"reflect-metadata": "^0.2.2",
"rimraf": "^5.0.10",
"rxjs": "^7.8.1"
},
"devDependencies": {
"@nestjs/cli": "^10.4.5",
"@nestjs/schematics": "^10.2.3",
"@nestjs/testing": "^10.4.6",
"@types/express": "^4.17.21",
"@types/jest": "^29.5.14",
"@types/node": "^20.17.4",
"@types/passport-jwt": "^3.0.13",
"@types/supertest": "^6.0.2",
"dotenv-cli": "^4.1.1",
"jest": "^29.7.0",
"source-map-support": "^0.5.21",
"supertest": "^6.3.4",
"ts-jest": "^29.2.5",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.6.3"
},
"jest": {
"moduleFileExtensions": [
"js",
"json",
"ts"
],
"rootDir": "src",
"testRegex": ".*\\.spec\\.ts$",
"transform": {
"^.+\\.(t|j)s$": "ts-jest"
},
"collectCoverageFrom": [
"**/*.(t|j)s"
],
"coverageDirectory": "../coverage",
"testEnvironment": "node"
}
}

5911
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

105
prisma/schema.prisma Normal file
View File

@ -0,0 +1,105 @@
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Crypto {
id String @id @default(uuid())
name String @unique
value Float
image String
quantity Float @default(1000)
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
UserHasCrypto UserHasCrypto[]
Trade Trade[]
Offer Offer[]
CryptoHistory CryptoHistory[]
}
model CryptoHistory {
id String @id @default(uuid())
id_crypto String
value Float
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
Crypto Crypto @relation(fields: [id_crypto], references: [id])
}
model Offer {
id String @id @default(uuid())
id_crypto String
id_user String
amount Float
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
Crypto Crypto @relation(fields: [id_crypto], references: [id])
User User @relation(fields: [id_user], references: [id])
}
model PromoCode {
id String @id @default(uuid())
name String
value Int
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
}
model Role {
id String @id @default(uuid())
name String
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
User User[]
}
model Trade {
id String @id @default(uuid())
id_giver String
id_receiver String
id_crypto String
amount_traded Float
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
Giver User @relation("Giver", fields: [id_giver], references: [id])
Receiver User @relation("Receiver", fields: [id_receiver], references: [id])
Crypto Crypto @relation(fields: [id_crypto], references: [id])
}
model User {
id String @id @default(uuid())
firstName String
lastName String
pseudo String
hash String
email String @unique
roleId String
isActive Boolean
dollarAvailables Float
created_at DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
Role Role @relation(fields: [roleId], references: [id])
UserHasCrypto UserHasCrypto[]
TradeGiven Trade[] @relation("Giver")
TradeReceived Trade[] @relation("Receiver")
Offer Offer[]
}
model UserHasCrypto {
id String @id @default(uuid())
id_user String
id_crypto String
amount Int
createdAt DateTime @default(now())
updated_at DateTime @default(now()) @updatedAt
User User @relation(fields: [id_user], references: [id])
Crypto Crypto @relation(fields: [id_crypto], references: [id])
}

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

@ -0,0 +1,110 @@
import { ForbiddenException, Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { AuthLoginDto, AuthRegisterDto } from './dto';
import * as argon from 'argon2';
import { Prisma, User } from '@prisma/client';
import { JwtService } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
@Injectable()
export class AuthService {
private readonly initialBalance = 1000;
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.getPromoCode(dto.promoCode);
const userRole = await this.getUserRole('user');
const balance = this.calculateBalance(promoCode);
try {
const user = await this.createUser(dto, hash, userRole.id, balance);
return this.signToken(user);
} catch (error) {
this.handleSignupError(error);
}
}
private async getPromoCode(promoCode: string) {
return this.prisma.promoCode.findFirst({
where: {name: promoCode},
});
}
private async getUserRole(roleName: string) {
return this.prisma.role.findFirst({
where: {name: roleName},
});
}
private calculateBalance(promoCode: any) {
let balance = this.initialBalance;
if (promoCode && promoCode.value) {
balance += promoCode.value;
}
return balance;
}
private async createUser(dto: AuthRegisterDto, hash: string, roleId: string, balance: number) {
return this.prisma.user.create({
data: {
firstName: dto.firstName,
lastName: dto.lastName,
pseudo: dto.pseudo,
city: dto.city,
email: dto.email,
hash,
age: dto.age,
roleId,
isActive: true,
dollarAvailables: balance,
},
});
}
private handleSignupError(error: any) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
if (error.code === 'P2002') {
throw new ForbiddenException('Credentials taken');
}
}
throw error;
}
async signin(dto: AuthLoginDto) {
const userDatas = await this.prisma.user.findUnique({
where: { email: dto.email },
include: {
UserHasCrypto: { include: { Crypto: true } },
Role: true,
},
});
if (!userDatas) throw new ForbiddenException('Credentials incorrect');
const pwMatches = await argon.verify(userDatas.hash, dto.password);
if (!pwMatches) throw new ForbiddenException('Credentials incorrect');
return this.signToken(userDatas);
}
async signToken(user: any): Promise<{ access_token: string; user: User }> {
const payload = { sub: user.id, email: user.email };
user.hash = null;
const secret = this.config.get('JWT_SECRET');
const token = await this.jwt.signAsync(payload, {
expiresIn: '30d',
secret: secret,
});
return {
access_token: token,
user,
};
}
}

26
src/main.ts Normal file
View File

@ -0,0 +1,26 @@
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('Neptune API')
.setDescription('A fictive app')
.setVersion('1.0')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api', app, document);
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
}),
);
await app.listen(process.env.PORT || 3000);
}
bootstrap();

4
tsconfig.build.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
}

21
tsconfig.json Normal file
View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"module": "commonjs",
"declaration": true,
"removeComments": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"allowSyntheticDefaultImports": true,
"target": "ES2021",
"sourceMap": true,
"outDir": "./dist",
"baseUrl": "./",
"incremental": true,
"skipLibCheck": true,
"strictNullChecks": false,
"noImplicitAny": false,
"strictBindCallApply": false,
"forceConsistentCasingInFileNames": false,
"noFallthroughCasesInSwitch": false
}
}