mirror of
https://github.com/Kevsl/crypto-exchange-api.git
synced 2025-07-08 21:50:13 +02:00
testing all features
This commit is contained in:
commit
9f5c23c7c9
25
.eslintrc.js
Normal file
25
.eslintrc.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
module.exports = {
|
||||||
|
parser: '@typescript-eslint/parser',
|
||||||
|
parserOptions: {
|
||||||
|
project: 'tsconfig.json',
|
||||||
|
tsconfigRootDir: __dirname,
|
||||||
|
sourceType: 'module',
|
||||||
|
},
|
||||||
|
plugins: ['@typescript-eslint/eslint-plugin'],
|
||||||
|
extends: [
|
||||||
|
'plugin:@typescript-eslint/recommended',
|
||||||
|
'plugin:prettier/recommended',
|
||||||
|
],
|
||||||
|
root: true,
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
jest: true,
|
||||||
|
},
|
||||||
|
ignorePatterns: ['.eslintrc.js'],
|
||||||
|
rules: {
|
||||||
|
'@typescript-eslint/interface-name-prefix': 'off',
|
||||||
|
'@typescript-eslint/explicit-function-return-type': 'off',
|
||||||
|
'@typescript-eslint/explicit-module-boundary-types': 'off',
|
||||||
|
'@typescript-eslint/no-explicit-any': 'off',
|
||||||
|
},
|
||||||
|
};
|
56
.gitignore
vendored
Normal file
56
.gitignore
vendored
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
# 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
|
6
.prettierrc
Normal file
6
.prettierrc
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true
|
||||||
|
}
|
73
README.md
Normal file
73
README.md
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
<p align="center">
|
||||||
|
<a href="http://nestjs.com/" target="blank"><img src="https://nestjs.com/img/logo-small.svg" width="200" alt="Nest Logo" /></a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
[circleci-image]: https://img.shields.io/circleci/build/github/nestjs/nest/master?token=abc123def456
|
||||||
|
[circleci-url]: https://circleci.com/gh/nestjs/nest
|
||||||
|
|
||||||
|
<p align="center">A progressive <a href="http://nodejs.org" target="_blank">Node.js</a> framework for building efficient and scalable server-side applications.</p>
|
||||||
|
<p align="center">
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/v/@nestjs/core.svg" alt="NPM Version" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/l/@nestjs/core.svg" alt="Package License" /></a>
|
||||||
|
<a href="https://www.npmjs.com/~nestjscore" target="_blank"><img src="https://img.shields.io/npm/dm/@nestjs/common.svg" alt="NPM Downloads" /></a>
|
||||||
|
<a href="https://circleci.com/gh/nestjs/nest" target="_blank"><img src="https://img.shields.io/circleci/build/github/nestjs/nest/master" alt="CircleCI" /></a>
|
||||||
|
<a href="https://coveralls.io/github/nestjs/nest?branch=master" target="_blank"><img src="https://coveralls.io/repos/github/nestjs/nest/badge.svg?branch=master#9" alt="Coverage" /></a>
|
||||||
|
<a href="https://discord.gg/G7Qnnhy" target="_blank"><img src="https://img.shields.io/badge/discord-online-brightgreen.svg" alt="Discord"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#backer" target="_blank"><img src="https://opencollective.com/nest/backers/badge.svg" alt="Backers on Open Collective" /></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://opencollective.com/nest/sponsors/badge.svg" alt="Sponsors on Open Collective" /></a>
|
||||||
|
<a href="https://paypal.me/kamilmysliwiec" target="_blank"><img src="https://img.shields.io/badge/Donate-PayPal-ff3f59.svg"/></a>
|
||||||
|
<a href="https://opencollective.com/nest#sponsor" target="_blank"><img src="https://img.shields.io/badge/Support%20us-Open%20Collective-41B883.svg" alt="Support us"></a>
|
||||||
|
<a href="https://twitter.com/nestframework" target="_blank"><img src="https://img.shields.io/twitter/follow/nestframework.svg?style=social&label=Follow"></a>
|
||||||
|
</p>
|
||||||
|
<!--[](https://opencollective.com/nest#backer)
|
||||||
|
[](https://opencollective.com/nest#sponsor)-->
|
||||||
|
|
||||||
|
## Description
|
||||||
|
|
||||||
|
[Nest](https://github.com/nestjs/nest) framework TypeScript starter repository.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running the app
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# development
|
||||||
|
$ npm run start
|
||||||
|
|
||||||
|
# watch mode
|
||||||
|
$ npm run start:dev
|
||||||
|
|
||||||
|
# production mode
|
||||||
|
$ npm run start:prod
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
$ npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
$ npm run test:e2e
|
||||||
|
|
||||||
|
# test coverage
|
||||||
|
$ npm run test:cov
|
||||||
|
```
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Nest is an MIT-licensed open source project. It can grow thanks to the sponsors and support by the amazing backers. If you'd like to join them, please [read more here](https://docs.nestjs.com/support).
|
||||||
|
|
||||||
|
## Stay in touch
|
||||||
|
|
||||||
|
- Author - [Kamil Myśliwiec](https://kamilmysliwiec.com)
|
||||||
|
- Website - [https://nestjs.com](https://nestjs.com/)
|
||||||
|
- Twitter - [@nestframework](https://twitter.com/nestframework)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Nest is [MIT licensed](LICENSE).
|
25
docker-compose.yaml
Normal file
25
docker-compose.yaml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
cryptcoin:
|
||||||
|
image: postgres:13
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: uDr4KjGcpEm6N5F3BqhxAT
|
||||||
|
POSTGRES_PASSWORD: 12xt6h4fWf4QL9WiP5syZ887S8Si54giDizdB8L685aBiBkBVaW87MS3bWc22ba3L9YT3
|
||||||
|
POSTGRES_DB: cW76664NDPkmc
|
||||||
|
networks:
|
||||||
|
- CRY
|
||||||
|
test-db:
|
||||||
|
image: postgres:13
|
||||||
|
ports:
|
||||||
|
- 5434:5434
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: uDr4KjGcpEm6N5F3BqhxAT
|
||||||
|
POSTGRES_PASSWORD: 12xt6h4fWf4QL9WiP5syZ887S8Si54giDizdB8L685aBiBkBVaW87MS3bWc22ba3L9YT3
|
||||||
|
POSTGRES_DB: cW76664NDPkmc
|
||||||
|
networks:
|
||||||
|
- CRY
|
||||||
|
networks:
|
||||||
|
CRY:
|
||||||
|
external: true
|
8
nest-cli.json
Normal file
8
nest-cli.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"deleteOutDir": true
|
||||||
|
}
|
||||||
|
}
|
16268
package-lock.json
generated
Normal file
16268
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
96
package.json
Normal file
96
package.json
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
{
|
||||||
|
"name": "cryptcoin",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"build": "nest build",
|
||||||
|
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",
|
||||||
|
"start": "nest start",
|
||||||
|
"start:dev": "nest start --watch",
|
||||||
|
"start:debug": "nest start --debug --watch",
|
||||||
|
"start:prod": "node dist/main",
|
||||||
|
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||||
|
"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-modules/mailer": "^1.11.0",
|
||||||
|
"@nestjs/common": "^10.0.0",
|
||||||
|
"@nestjs/config": "^3.2.0",
|
||||||
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/jwt": "^10.2.0",
|
||||||
|
"@nestjs/passport": "^10.0.3",
|
||||||
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
|
"@nestjs/swagger": "^7.1.17",
|
||||||
|
"@nestjs/throttler": "^5.1.1",
|
||||||
|
"@prisma/client": "^5.8.1",
|
||||||
|
"@prisma/studio": "^0.497.0",
|
||||||
|
"@types/nodemailer": "^6.4.14",
|
||||||
|
"argon2": "^0.31.2",
|
||||||
|
"class-transformer": "^0.5.1",
|
||||||
|
"class-validator": "^0.14.1",
|
||||||
|
"css-inline": "^0.11.2",
|
||||||
|
"ejs": "^3.1.9",
|
||||||
|
"handlebars": "^4.7.8",
|
||||||
|
"helmet": "^7.1.0",
|
||||||
|
"nodemailer": "^6.9.10",
|
||||||
|
"passport": "^0.7.0",
|
||||||
|
"passport-jwt": "^4.0.1",
|
||||||
|
"prisma": "^5.8.0",
|
||||||
|
"pug": "^3.0.2",
|
||||||
|
"reflect-metadata": "^0.2.1",
|
||||||
|
"rimraf": "^5.0.5",
|
||||||
|
"rxjs": "^7.8.1",
|
||||||
|
"save": "^2.9.0",
|
||||||
|
"swagger": "^0.7.5",
|
||||||
|
"swagger-ui-express": "^5.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@nestjs/cli": "^10.0.0",
|
||||||
|
"@nestjs/schematics": "^10.0.0",
|
||||||
|
"@nestjs/testing": "^10.0.0",
|
||||||
|
"@types/express": "^4.17.17",
|
||||||
|
"@types/jest": "^29.5.2",
|
||||||
|
"@types/node": "^20.11.5",
|
||||||
|
"@types/passport-jwt": "^3.0.6",
|
||||||
|
"@types/supertest": "^6.0.0",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^6.0.0",
|
||||||
|
"@typescript-eslint/parser": "^6.0.0",
|
||||||
|
"dotenv-cli": "^4.1.1",
|
||||||
|
"eslint": "^8.42.0",
|
||||||
|
"eslint-config-prettier": "^9.0.0",
|
||||||
|
"eslint-plugin-prettier": "^5.0.0",
|
||||||
|
"jest": "^29.5.0",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"source-map-support": "^0.5.21",
|
||||||
|
"supertest": "^6.3.3",
|
||||||
|
"ts-jest": "^29.1.0",
|
||||||
|
"ts-loader": "^9.4.3",
|
||||||
|
"ts-node": "^10.9.2",
|
||||||
|
"tsconfig-paths": "^4.2.0",
|
||||||
|
"typescript": "^5.3.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"
|
||||||
|
}
|
||||||
|
}
|
88
prisma/migrations/20240605210127_/migration.sql
Normal file
88
prisma/migrations/20240605210127_/migration.sql
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "User" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"firstName" TEXT NOT NULL,
|
||||||
|
"lastName" TEXT NOT NULL,
|
||||||
|
"pseudo" TEXT NOT NULL,
|
||||||
|
"hash" TEXT NOT NULL,
|
||||||
|
"email" TEXT NOT NULL,
|
||||||
|
"roleId" TEXT NOT NULL,
|
||||||
|
"isActive" BOOLEAN NOT NULL,
|
||||||
|
"city" TEXT NOT NULL,
|
||||||
|
"dollarAvailables" DOUBLE PRECISION NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Crypto" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
"value" DOUBLE PRECISION NOT NULL,
|
||||||
|
"image" TEXT NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Crypto_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "UserHasCrypto" (
|
||||||
|
"id_user" TEXT NOT NULL,
|
||||||
|
"id_crypto" TEXT NOT NULL,
|
||||||
|
"amount" DOUBLE PRECISION NOT NULL,
|
||||||
|
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "UserHasCrypto_pkey" PRIMARY KEY ("id_user","id_crypto")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Trade" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"id_giver" TEXT NOT NULL,
|
||||||
|
"id_receiver" TEXT NOT NULL,
|
||||||
|
"id_crypto" TEXT NOT NULL,
|
||||||
|
"value_while_trade" DOUBLE PRECISION NOT NULL,
|
||||||
|
"amount_traded" DOUBLE PRECISION NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Trade_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Role" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "Role_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "PromoCode" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"name" TEXT NOT NULL,
|
||||||
|
|
||||||
|
CONSTRAINT "PromoCode_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "User" ADD CONSTRAINT "User_roleId_fkey" FOREIGN KEY ("roleId") REFERENCES "Role"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserHasCrypto" ADD CONSTRAINT "UserHasCrypto_id_user_fkey" FOREIGN KEY ("id_user") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "UserHasCrypto" ADD CONSTRAINT "UserHasCrypto_id_crypto_fkey" FOREIGN KEY ("id_crypto") REFERENCES "Crypto"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Trade" ADD CONSTRAINT "Trade_id_giver_fkey" FOREIGN KEY ("id_giver") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Trade" ADD CONSTRAINT "Trade_id_receiver_fkey" FOREIGN KEY ("id_receiver") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Trade" ADD CONSTRAINT "Trade_id_crypto_fkey" FOREIGN KEY ("id_crypto") REFERENCES "Crypto"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
8
prisma/migrations/20240605211026_/migration.sql
Normal file
8
prisma/migrations/20240605211026_/migration.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `value` to the `PromoCode` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PromoCode" ADD COLUMN "value" INTEGER NOT NULL;
|
26
prisma/migrations/20240605223302_/migration.sql
Normal file
26
prisma/migrations/20240605223302_/migration.sql
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to alter the column `value` on the `Crypto` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Decimal(65,30)`.
|
||||||
|
- You are about to alter the column `value_while_trade` on the `Trade` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Decimal(65,30)`.
|
||||||
|
- You are about to alter the column `amount_traded` on the `Trade` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Decimal(65,30)`.
|
||||||
|
- You are about to alter the column `dollarAvailables` on the `User` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Decimal(65,30)`.
|
||||||
|
- You are about to alter the column `amount` on the `UserHasCrypto` table. The data in that column could be lost. The data in that column will be cast from `DoublePrecision` to `Decimal(65,30)`.
|
||||||
|
- A unique constraint covering the columns `[email]` on the table `User` will be added. If there are existing duplicate values, this will fail.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Crypto" ALTER COLUMN "value" SET DATA TYPE DECIMAL(65,30);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" ALTER COLUMN "value_while_trade" SET DATA TYPE DECIMAL(65,30),
|
||||||
|
ALTER COLUMN "amount_traded" SET DATA TYPE DECIMAL(65,30);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ALTER COLUMN "dollarAvailables" SET DATA TYPE DECIMAL(65,30);
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserHasCrypto" ALTER COLUMN "amount" SET DATA TYPE DECIMAL(65,30);
|
||||||
|
|
||||||
|
-- CreateIndex
|
||||||
|
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
|
8
prisma/migrations/20240605225102_/migration.sql
Normal file
8
prisma/migrations/20240605225102_/migration.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to drop the column `value_while_trade` on the `Trade` table. All the data in the column will be lost.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" DROP COLUMN "value_while_trade";
|
16
prisma/migrations/20240605230717_/migration.sql
Normal file
16
prisma/migrations/20240605230717_/migration.sql
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to alter the column `value` on the `Crypto` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Integer`.
|
||||||
|
- You are about to alter the column `amount_traded` on the `Trade` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Integer`.
|
||||||
|
- You are about to alter the column `amount` on the `UserHasCrypto` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `Integer`.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Crypto" ALTER COLUMN "value" SET DATA TYPE INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" ALTER COLUMN "amount_traded" SET DATA TYPE INTEGER;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserHasCrypto" ALTER COLUMN "amount" SET DATA TYPE INTEGER;
|
11
prisma/migrations/20240605231422_/migration.sql
Normal file
11
prisma/migrations/20240605231422_/migration.sql
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- The primary key for the `UserHasCrypto` table will be changed. If it partially fails, the table could be left without primary key constraint.
|
||||||
|
- The required column `id` was added to the `UserHasCrypto` table with a prisma-level default value. This is not possible if the table is not empty. Please add this column as optional, then populate it before making it required.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserHasCrypto" DROP CONSTRAINT "UserHasCrypto_pkey",
|
||||||
|
ADD COLUMN "id" TEXT NOT NULL,
|
||||||
|
ADD CONSTRAINT "UserHasCrypto_pkey" PRIMARY KEY ("id");
|
8
prisma/migrations/20240606080326_/migration.sql
Normal file
8
prisma/migrations/20240606080326_/migration.sql
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- You are about to alter the column `dollarAvailables` on the `User` table. The data in that column could be lost. The data in that column will be cast from `Decimal(65,30)` to `DoublePrecision`.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ALTER COLUMN "dollarAvailables" SET DATA TYPE DOUBLE PRECISION;
|
5
prisma/migrations/20240606081530_/migration.sql
Normal file
5
prisma/migrations/20240606081530_/migration.sql
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Crypto" ALTER COLUMN "value" SET DATA TYPE DOUBLE PRECISION;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" ALTER COLUMN "amount_traded" SET DATA TYPE DOUBLE PRECISION;
|
47
prisma/migrations/20240606130104_/migration.sql
Normal file
47
prisma/migrations/20240606130104_/migration.sql
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
Warnings:
|
||||||
|
|
||||||
|
- Added the required column `id_offer` to the `Trade` table without a default value. This is not possible if the table is not empty.
|
||||||
|
|
||||||
|
*/
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Crypto" ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "PromoCode" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Role" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
ADD COLUMN "updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "Trade" ADD COLUMN "id_offer" TEXT NOT NULL,
|
||||||
|
ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "User" ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- AlterTable
|
||||||
|
ALTER TABLE "UserHasCrypto" ALTER COLUMN "updated_at" SET DEFAULT CURRENT_TIMESTAMP;
|
||||||
|
|
||||||
|
-- CreateTable
|
||||||
|
CREATE TABLE "Offer" (
|
||||||
|
"id" TEXT NOT NULL,
|
||||||
|
"id_crypto" TEXT NOT NULL,
|
||||||
|
"id_user" TEXT NOT NULL,
|
||||||
|
"amount" DOUBLE PRECISION NOT NULL,
|
||||||
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
"updated_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
|
CONSTRAINT "Offer_pkey" PRIMARY KEY ("id")
|
||||||
|
);
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Offer" ADD CONSTRAINT "Offer_id_crypto_fkey" FOREIGN KEY ("id_crypto") REFERENCES "Crypto"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Offer" ADD CONSTRAINT "Offer_id_user_fkey" FOREIGN KEY ("id_user") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
||||||
|
|
||||||
|
-- AddForeignKey
|
||||||
|
ALTER TABLE "Trade" ADD CONSTRAINT "Trade_id_offer_fkey" FOREIGN KEY ("id_offer") REFERENCES "Offer"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
|
3
prisma/migrations/migration_lock.toml
Normal file
3
prisma/migrations/migration_lock.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
# Please do not edit this file manually
|
||||||
|
# It should be added in your version-control system (i.e. Git)
|
||||||
|
provider = "postgresql"
|
110
prisma/schema.prisma
Normal file
110
prisma/schema.prisma
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
datasource db {
|
||||||
|
provider = "postgresql"
|
||||||
|
url = env("DATABASE_URL")
|
||||||
|
}
|
||||||
|
|
||||||
|
generator client {
|
||||||
|
provider = "prisma-client-js"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
model Crypto {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
value Float
|
||||||
|
image String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
UserHasCrypto UserHasCrypto[]
|
||||||
|
Trade Trade[]
|
||||||
|
Offer Offer[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
model Offer {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
id_crypto String
|
||||||
|
id_user String
|
||||||
|
amount Float
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
|
||||||
|
Crypto Crypto @relation(fields: [id_crypto], references: [id])
|
||||||
|
User User @relation(fields: [id_user], references: [id])
|
||||||
|
Trade Trade[]
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
model PromoCode {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
value Int
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
model Role {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
name String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
|
||||||
|
User User[]
|
||||||
|
}
|
||||||
|
|
||||||
|
model Trade {
|
||||||
|
id String @id @default(uuid())
|
||||||
|
id_giver String
|
||||||
|
id_receiver String
|
||||||
|
id_crypto String
|
||||||
|
amount_traded Float
|
||||||
|
id_offer String
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
|
||||||
|
Offer Offer @relation(fields: [id_offer], references: [id])
|
||||||
|
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
|
||||||
|
city String
|
||||||
|
dollarAvailables Float
|
||||||
|
created_at DateTime @default(now())
|
||||||
|
updated_at DateTime @updatedAt @default(now())
|
||||||
|
|
||||||
|
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 @updatedAt @default(now())
|
||||||
|
User User @relation(fields: [id_user], references: [id])
|
||||||
|
Crypto Crypto @relation(fields: [id_crypto], references: [id])
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
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 this.appService.getHello();
|
||||||
|
}
|
||||||
|
}
|
29
src/app.module.ts
Normal file
29
src/app.module.ts
Normal 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
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 { 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
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 {}
|
92
src/auth/auth.service.ts
Normal file
92
src/auth/auth.service.ts
Normal 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,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
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 { 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;
|
||||||
|
},
|
||||||
|
);
|
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 { 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;
|
||||||
|
}
|
65
src/auth/dto/auth.register.dto.ts
Normal file
65
src/auth/dto/auth.register.dto.ts
Normal 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
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 { 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;
|
||||||
|
}
|
||||||
|
}
|
69
src/crypto/crypto.controller.ts
Normal file
69
src/crypto/crypto.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
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 { CryptoService } from './crypto.service';
|
||||||
|
import { CryptoController } from './crypto.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [CryptoService],
|
||||||
|
controllers: [CryptoController],
|
||||||
|
})
|
||||||
|
export class CryptoModule {}
|
148
src/crypto/crypto.service.ts
Normal file
148
src/crypto/crypto.service.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
19
src/crypto/dto/buy.crypto.dto.ts
Normal file
19
src/crypto/dto/buy.crypto.dto.ts
Normal 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;
|
||||||
|
}
|
27
src/crypto/dto/crypto.dto.ts
Normal file
27
src/crypto/dto/crypto.dto.ts
Normal 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
1
src/crypto/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './crypto.dto';
|
27
src/main.ts
Normal file
27
src/main.ts
Normal 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
1
src/offer/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './offer.dto';
|
20
src/offer/dto/offer.dto.ts
Normal file
20
src/offer/dto/offer.dto.ts
Normal 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;
|
||||||
|
}
|
59
src/offer/offer.controller.ts
Normal file
59
src/offer/offer.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
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 { OfferService } from './offer.service';
|
||||||
|
import { OfferController } from './offer.controller';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [OfferService],
|
||||||
|
controllers: [OfferController],
|
||||||
|
})
|
||||||
|
export class OfferModule {}
|
76
src/offer/offer.service.ts
Normal file
76
src/offer/offer.service.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
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 {}
|
23
src/prisma/prisma.service.ts
Normal file
23
src/prisma/prisma.service.ts
Normal 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(),
|
||||||
|
// ])
|
||||||
|
// }
|
||||||
|
}
|
1
src/promoCode/dto/index.ts
Normal file
1
src/promoCode/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './promoCode.dto';
|
19
src/promoCode/dto/promoCode.dto.ts
Normal file
19
src/promoCode/dto/promoCode.dto.ts
Normal 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;
|
||||||
|
}
|
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 { 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);
|
||||||
|
}
|
||||||
|
}
|
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 {}
|
79
src/promoCode/promoCode.service.ts
Normal file
79
src/promoCode/promoCode.service.ts
Normal 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
1
src/role/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './role.dto';
|
11
src/role/dto/role.dto.ts
Normal file
11
src/role/dto/role.dto.ts
Normal 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;
|
||||||
|
}
|
63
src/role/role.controller.ts
Normal file
63
src/role/role.controller.ts
Normal 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
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 { 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
1
src/trade/dto/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './trade.dto';
|
48
src/trade/dto/trade.dto.ts
Normal file
48
src/trade/dto/trade.dto.ts
Normal 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;
|
||||||
|
}
|
58
src/trade/trade.controller.ts
Normal file
58
src/trade/trade.controller.ts
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
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 { 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
177
src/trade/trade.service.ts
Normal 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,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
31
src/user/user.controller.ts
Normal file
31
src/user/user.controller.ts
Normal 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
8
src/user/user.module.ts
Normal 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
31
src/user/user.service.ts
Normal 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
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 && 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
4
src/utils/const/const.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const Roles = {
|
||||||
|
ADMIN: 'admin',
|
||||||
|
USER: 'user',
|
||||||
|
};
|
24
test/app.e2e-spec.ts
Normal file
24
test/app.e2e-spec.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
import * as request from 'supertest';
|
||||||
|
import { AppModule } from './../src/app.module';
|
||||||
|
|
||||||
|
describe('AppController (e2e)', () => {
|
||||||
|
let app: INestApplication;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const moduleFixture: TestingModule = await Test.createTestingModule({
|
||||||
|
imports: [AppModule],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
app = moduleFixture.createNestApplication();
|
||||||
|
await app.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('/ (GET)', () => {
|
||||||
|
return request(app.getHttpServer())
|
||||||
|
.get('/')
|
||||||
|
.expect(200)
|
||||||
|
.expect('Hello World!');
|
||||||
|
});
|
||||||
|
});
|
9
test/jest-e2e.json
Normal file
9
test/jest-e2e.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"moduleFileExtensions": ["js", "json", "ts"],
|
||||||
|
"rootDir": ".",
|
||||||
|
"testEnvironment": "node",
|
||||||
|
"testRegex": ".e2e-spec.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
}
|
||||||
|
}
|
4
tsconfig.build.json
Normal file
4
tsconfig.build.json
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||||
|
}
|
21
tsconfig.json
Normal file
21
tsconfig.json
Normal 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
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user