first commit
This commit is contained in:
9
.dockerignore
Normal file
9
.dockerignore
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
myteamwallet_frontend/
|
||||||
|
**/node_modules
|
||||||
|
**/dist
|
||||||
|
**/.angular
|
||||||
|
**/.git
|
||||||
|
**/coverage
|
||||||
|
**/.env
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
43
.env
Normal file
43
.env
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
NODE_ENV=develop
|
||||||
|
APP_PORT=3999
|
||||||
|
APP_NAME="NestJS API"
|
||||||
|
API_PREFIX=api
|
||||||
|
APP_FALLBACK_LANGUAGE=en
|
||||||
|
APP_HEADER_LANGUAGE=x-custom-lang
|
||||||
|
FRONTEND_DOMAIN=http://localhost:3000
|
||||||
|
BACKEND_DOMAIN=http://localhost:3999
|
||||||
|
|
||||||
|
DATABASE_TYPE=mysql
|
||||||
|
DATABASE_HOST=localhost
|
||||||
|
DATABASE_PORT=3306
|
||||||
|
DATABASE_USERNAME=teamwallet
|
||||||
|
DATABASE_PASSWORD=SYdktg804sEpwaBNsfjW8wje70clnju7LVgwGRVn
|
||||||
|
DATABASE_NAME=teamwallet
|
||||||
|
DATABASE_SYNCHRONIZE=true
|
||||||
|
DATABASE_MAX_CONNECTIONS=100
|
||||||
|
DATABASE_SSL_ENABLED=false
|
||||||
|
DATABASE_REJECT_UNAUTHORIZED=false
|
||||||
|
DATABASE_CA=
|
||||||
|
DATABASE_KEY=
|
||||||
|
DATABASE_CERT=
|
||||||
|
|
||||||
|
# Support "local", "s3"
|
||||||
|
FILE_DRIVER=local
|
||||||
|
ACCESS_KEY_ID=
|
||||||
|
SECRET_ACCESS_KEY=
|
||||||
|
AWS_S3_REGION=
|
||||||
|
AWS_DEFAULT_S3_BUCKET=
|
||||||
|
|
||||||
|
MAIL_HOST=smtp.strato.de
|
||||||
|
MAIL_PORT=465
|
||||||
|
MAIL_USER=accountservice@myteamwallet.de
|
||||||
|
MAIL_PASSWORD=HmQnnMZmw9aqwcolX5cSJjazhMEhT3Vve3v6wAxt
|
||||||
|
MAIL_IGNORE_TLS=true
|
||||||
|
MAIL_SECURE=true
|
||||||
|
MAIL_REQUIRE_TLS=false
|
||||||
|
MAIL_DEFAULT_EMAIL=accountservice@myteamwallet.de
|
||||||
|
MAIL_DEFAULT_NAME=accountservice@myteamwallet.de
|
||||||
|
MAIL_CLIENT_PORT=1080
|
||||||
|
|
||||||
|
AUTH_JWT_SECRET=this-is-onE-longer-secret
|
||||||
|
AUTH_JWT_TOKEN_EXPIRES_IN=100d
|
||||||
57
.env.example
Normal file
57
.env.example
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
NODE_ENV=production
|
||||||
|
APP_PORT=3999
|
||||||
|
APP_NAME="NestJS API"
|
||||||
|
API_PREFIX=api
|
||||||
|
APP_FALLBACK_LANGUAGE=en
|
||||||
|
APP_HEADER_LANGUAGE=x-custom-lang
|
||||||
|
FRONTEND_DOMAIN=http://localhost:3999
|
||||||
|
BACKEND_DOMAIN=http://localhost:3999
|
||||||
|
|
||||||
|
# External database - not part of this container
|
||||||
|
DATABASE_TYPE=mysql
|
||||||
|
DATABASE_HOST=change-me
|
||||||
|
DATABASE_PORT=3306
|
||||||
|
DATABASE_USERNAME=change-me
|
||||||
|
DATABASE_PASSWORD=change-me
|
||||||
|
DATABASE_NAME=change-me
|
||||||
|
DATABASE_SYNCHRONIZE=false
|
||||||
|
DATABASE_MAX_CONNECTIONS=100
|
||||||
|
DATABASE_SSL_ENABLED=false
|
||||||
|
DATABASE_REJECT_UNAUTHORIZED=false
|
||||||
|
DATABASE_CA=
|
||||||
|
DATABASE_KEY=
|
||||||
|
DATABASE_CERT=
|
||||||
|
|
||||||
|
# Support "local", "s3"
|
||||||
|
FILE_DRIVER=local
|
||||||
|
ACCESS_KEY_ID=
|
||||||
|
SECRET_ACCESS_KEY=
|
||||||
|
AWS_S3_REGION=
|
||||||
|
AWS_DEFAULT_S3_BUCKET=
|
||||||
|
|
||||||
|
MAIL_HOST=change-me
|
||||||
|
MAIL_PORT=465
|
||||||
|
MAIL_USER=change-me
|
||||||
|
MAIL_PASSWORD=change-me
|
||||||
|
MAIL_IGNORE_TLS=true
|
||||||
|
MAIL_SECURE=true
|
||||||
|
MAIL_REQUIRE_TLS=false
|
||||||
|
MAIL_DEFAULT_EMAIL=noreply@example.com
|
||||||
|
MAIL_DEFAULT_NAME=Api
|
||||||
|
MAIL_CLIENT_PORT=1080
|
||||||
|
|
||||||
|
AUTH_JWT_SECRET=change-me
|
||||||
|
AUTH_JWT_TOKEN_EXPIRES_IN=100d
|
||||||
|
|
||||||
|
FACEBOOK_APP_ID=
|
||||||
|
FACEBOOK_APP_SECRET=
|
||||||
|
|
||||||
|
GOOGLE_CLIENT_ID=
|
||||||
|
GOOGLE_CLIENT_SECRET=
|
||||||
|
|
||||||
|
APPLE_APP_AUDIENCE=[]
|
||||||
|
|
||||||
|
TWITTER_CONSUMER_KEY=
|
||||||
|
TWITTER_CONSUMER_SECRET=
|
||||||
|
|
||||||
|
WORKER_HOST=redis://redis:6379/1
|
||||||
40
Dockerfile
Normal file
40
Dockerfile
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
## Build the new Angular frontend (same-origin /api/v1/ base URL)
|
||||||
|
FROM node:22-alpine AS frontend-build
|
||||||
|
WORKDIR /app/frontend
|
||||||
|
COPY myteamwallet_frontend_modern/package.json myteamwallet_frontend_modern/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY myteamwallet_frontend_modern/ ./
|
||||||
|
RUN npm run build:container
|
||||||
|
|
||||||
|
## Build the NestJS backend
|
||||||
|
FROM node:22-alpine AS backend-build
|
||||||
|
WORKDIR /app
|
||||||
|
ENV CI=true
|
||||||
|
COPY myteamwallet_backend/package.json myteamwallet_backend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
COPY myteamwallet_backend/ ./
|
||||||
|
RUN npm run build
|
||||||
|
|
||||||
|
## Runtime image: backend + frontend static assets, external DB only
|
||||||
|
FROM node:22-alpine
|
||||||
|
WORKDIR /app
|
||||||
|
ENV CI=true
|
||||||
|
|
||||||
|
RUN apk add --no-cache bash
|
||||||
|
|
||||||
|
COPY myteamwallet_backend/package.json myteamwallet_backend/package-lock.json ./
|
||||||
|
RUN npm ci
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY --from=backend-build /app/dist ./dist
|
||||||
|
COPY myteamwallet_backend/src ./src
|
||||||
|
COPY myteamwallet_backend/tsconfig.json ./tsconfig.json
|
||||||
|
COPY myteamwallet_backend/wait-for-it.sh ./wait-for-it.sh
|
||||||
|
COPY myteamwallet_backend/docker-entrypoint.sh ./docker-entrypoint.sh
|
||||||
|
RUN sed -i 's/\r$//' wait-for-it.sh docker-entrypoint.sh \
|
||||||
|
&& chmod +x wait-for-it.sh docker-entrypoint.sh
|
||||||
|
|
||||||
|
COPY --from=frontend-build /app/frontend/dist/myteamwallet_frontend_modern/browser ./client
|
||||||
|
|
||||||
|
EXPOSE 3999
|
||||||
|
|
||||||
|
ENTRYPOINT ["./docker-entrypoint.sh"]
|
||||||
43
env-example
Normal file
43
env-example
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
NODE_ENV=development
|
||||||
|
APP_PORT=3000
|
||||||
|
APP_NAME="NestJS API"
|
||||||
|
API_PREFIX=api
|
||||||
|
APP_FALLBACK_LANGUAGE=en
|
||||||
|
APP_HEADER_LANGUAGE=x-custom-lang
|
||||||
|
FRONTEND_DOMAIN=http://localhost:3000
|
||||||
|
BACKEND_DOMAIN=http://localhost:3000
|
||||||
|
|
||||||
|
DATABASE_TYPE=postgres
|
||||||
|
DATABASE_HOST=postgres
|
||||||
|
DATABASE_PORT=5432
|
||||||
|
DATABASE_USERNAME=root
|
||||||
|
DATABASE_PASSWORD=secret
|
||||||
|
DATABASE_NAME=api
|
||||||
|
DATABASE_SYNCHRONIZE=false
|
||||||
|
DATABASE_MAX_CONNECTIONS=100
|
||||||
|
DATABASE_SSL_ENABLED=false
|
||||||
|
DATABASE_REJECT_UNAUTHORIZED=false
|
||||||
|
DATABASE_CA=
|
||||||
|
DATABASE_KEY=
|
||||||
|
DATABASE_CERT=
|
||||||
|
|
||||||
|
# Support "local", "s3"
|
||||||
|
FILE_DRIVER=local
|
||||||
|
ACCESS_KEY_ID=
|
||||||
|
SECRET_ACCESS_KEY=
|
||||||
|
AWS_S3_REGION=
|
||||||
|
AWS_DEFAULT_S3_BUCKET=
|
||||||
|
|
||||||
|
MAIL_HOST=maildev
|
||||||
|
MAIL_PORT=1025
|
||||||
|
MAIL_USER=
|
||||||
|
MAIL_PASSWORD=
|
||||||
|
MAIL_IGNORE_TLS=true
|
||||||
|
MAIL_SECURE=false
|
||||||
|
MAIL_REQUIRE_TLS=false
|
||||||
|
MAIL_DEFAULT_EMAIL=noreply@example.com
|
||||||
|
MAIL_DEFAULT_NAME=Api
|
||||||
|
MAIL_CLIENT_PORT=1080
|
||||||
|
|
||||||
|
AUTH_JWT_SECRET=secret
|
||||||
|
AUTH_JWT_TOKEN_EXPIRES_IN=1d
|
||||||
30
myteamwallet_backend/.eslintrc.js
Normal file
30
myteamwallet_backend/.eslintrc.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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',
|
||||||
|
'no-unused-vars': 'off',
|
||||||
|
'@typescript-eslint/no-unused-vars': ['error'],
|
||||||
|
'require-await': 'off',
|
||||||
|
'@typescript-eslint/require-await': 'error',
|
||||||
|
'@typescript-eslint/no-floating-promises': 'error',
|
||||||
|
},
|
||||||
|
};
|
||||||
41
myteamwallet_backend/.gitignore
vendored
Normal file
41
myteamwallet_backend/.gitignore
vendored
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
# compiled output
|
||||||
|
/dist
|
||||||
|
/client
|
||||||
|
/node_modules
|
||||||
|
|
||||||
|
# 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
|
||||||
|
|
||||||
|
.data
|
||||||
|
/files
|
||||||
|
.env
|
||||||
|
/ormconfig.json
|
||||||
4
myteamwallet_backend/.husky/pre-commit
Normal file
4
myteamwallet_backend/.husky/pre-commit
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run lint
|
||||||
4
myteamwallet_backend/.prettierrc
Normal file
4
myteamwallet_backend/.prettierrc
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
7
myteamwallet_backend/.vscode/settings.json
vendored
Normal file
7
myteamwallet_backend/.vscode/settings.json
vendored
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"conventionalCommits.scopes": [
|
||||||
|
"files",
|
||||||
|
"deploy",
|
||||||
|
"auth"
|
||||||
|
]
|
||||||
|
}
|
||||||
2
myteamwallet_backend/Procfile
Normal file
2
myteamwallet_backend/Procfile
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
web: npm run start:prod
|
||||||
|
release: echo '' > .env && npm run migration:run && npm run seed:run
|
||||||
141
myteamwallet_backend/README.md
Normal file
141
myteamwallet_backend/README.md
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
# PROD System
|
||||||
|
Datenbank auf Server: teamwallet
|
||||||
|
|
||||||
|
# NestJS REST API boilerplate 🇺🇦
|
||||||
|
`ssh -L 3306:127.0.0.1:3306 bastian@h2979752.stratoserver.net`
|
||||||
|
## Description
|
||||||
|
|
||||||
|
Seeden: npm run seed:run
|
||||||
|
|
||||||
|
## Table of Contents
|
||||||
|
|
||||||
|
- [Features](#features)
|
||||||
|
- [Quick run](#quick-run)
|
||||||
|
- [Comfortable development](#comfortable-development)
|
||||||
|
- [Links](#links)
|
||||||
|
- [Automatic update of dependencies](#automatic-update-of-dependencies)
|
||||||
|
- [Database utils](#database-utils)
|
||||||
|
- [Tests](#tests)
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- [x] Database ([typeorm](https://www.npmjs.com/package/typeorm)).
|
||||||
|
- [x] Seeding.
|
||||||
|
- [x] Config Service ([@nestjs/config](https://www.npmjs.com/package/@nestjs/config)).
|
||||||
|
- [x] Mailing ([nodemailer](https://www.npmjs.com/package/nodemailer), [@nestjs-modules/mailer](https://www.npmjs.com/package/@nestjs-modules/mailer)).
|
||||||
|
- [x] Sign in and sign up via email.
|
||||||
|
- [x] Social sign in (Apple, Facebook, Google, Twitter).
|
||||||
|
- [x] Admin and User roles.
|
||||||
|
- [x] I18N ([nestjs-i18n](https://www.npmjs.com/package/nestjs-i18n)).
|
||||||
|
- [x] File uploads. Support local and Amazon S3 drivers.
|
||||||
|
- [x] Swagger.
|
||||||
|
- [x] E2E and units tests.
|
||||||
|
- [x] Docker.
|
||||||
|
- [x] CI (Github Actions).
|
||||||
|
|
||||||
|
## Quick run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
|
||||||
|
cd my-app/
|
||||||
|
cp env-example .env
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
For check status run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose logs
|
||||||
|
```
|
||||||
|
|
||||||
|
## Comfortable development
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone --depth 1 https://github.com/brocoders/nestjs-boilerplate.git my-app
|
||||||
|
cd my-app/
|
||||||
|
cp env-example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
Change `DATABASE_HOST=postgres` to `DATABASE_HOST=localhost`
|
||||||
|
|
||||||
|
Change `MAIL_HOST=maildev` to `MAIL_HOST=localhost`
|
||||||
|
|
||||||
|
Run additional container:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose up -d postgres adminer maildev redis
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install
|
||||||
|
|
||||||
|
npm run migration:run
|
||||||
|
|
||||||
|
npm run seed:run
|
||||||
|
|
||||||
|
npm run start:dev
|
||||||
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
- Swagger: http://localhost:3000/docs
|
||||||
|
- Adminer (client for DB): http://localhost:8080
|
||||||
|
- Maildev: http://localhost:1080
|
||||||
|
|
||||||
|
## Automatic update of dependencies
|
||||||
|
|
||||||
|
If you want to automatically update dependencies, you can connect [Renovate](https://github.com/marketplace/renovate) for your project.
|
||||||
|
|
||||||
|
## Database utils
|
||||||
|
|
||||||
|
Generate migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migration:generate -- src/database/migrations/CreateNameTable
|
||||||
|
```
|
||||||
|
|
||||||
|
Run migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migration:run
|
||||||
|
```
|
||||||
|
|
||||||
|
Revert migration
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run migration:revert
|
||||||
|
```
|
||||||
|
|
||||||
|
Drop all tables in database
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run schema:drop
|
||||||
|
```
|
||||||
|
|
||||||
|
Run seed
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm run seed:run
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# unit tests
|
||||||
|
npm run test
|
||||||
|
|
||||||
|
# e2e tests
|
||||||
|
npm run test:e2e
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tests in Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker compose -f docker-compose.ci.yaml --env-file env-example -p ci up --build --exit-code-from api && docker compose -p ci rm -svf
|
||||||
|
```
|
||||||
|
|
||||||
|
## Test benchmarking
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker run --rm jordi/ab -n 100 -c 100 -T application/json -H "Authorization: Bearer USER_TOKEN" -v 2 http://<server_ip>:3000/api/v1/users
|
||||||
|
```
|
||||||
14
myteamwallet_backend/docker-entrypoint.sh
Normal file
14
myteamwallet_backend/docker-entrypoint.sh
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
echo "Waiting for database at ${DATABASE_HOST}:${DATABASE_PORT}..."
|
||||||
|
bash ./wait-for-it.sh "${DATABASE_HOST}:${DATABASE_PORT}" -t 60
|
||||||
|
|
||||||
|
echo "Running database migrations..."
|
||||||
|
# env-cmd (used by migration:run) requires an .env file to exist, even though
|
||||||
|
# the real config already arrived as process env vars via `docker run --env-file`.
|
||||||
|
touch .env
|
||||||
|
npm run migration:run
|
||||||
|
|
||||||
|
echo "Starting application..."
|
||||||
|
exec node dist/main
|
||||||
8
myteamwallet_backend/nest-cli.json
Normal file
8
myteamwallet_backend/nest-cli.json
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json.schemastore.org/nest-cli",
|
||||||
|
"collection": "@nestjs/schematics",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"compilerOptions": {
|
||||||
|
"assets": [{ "include": "i18n/**/*", "watchAssets": true }]
|
||||||
|
}
|
||||||
|
}
|
||||||
32362
myteamwallet_backend/package-lock.json
generated
Normal file
32362
myteamwallet_backend/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
124
myteamwallet_backend/package.json
Normal file
124
myteamwallet_backend/package.json
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
{
|
||||||
|
"name": "nestjs-boilerplate",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "",
|
||||||
|
"author": "",
|
||||||
|
"private": true,
|
||||||
|
"license": "UNLICENSED",
|
||||||
|
"scripts": {
|
||||||
|
"postinstall": "is-ci || husky install",
|
||||||
|
"typeorm": "env-cmd ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
|
||||||
|
"migration:generate": "npm run typeorm -- --dataSource=src/database/data-source.ts migration:generate",
|
||||||
|
"migration:create": "npm run typeorm -- migration:create",
|
||||||
|
"migration:run": "npm run typeorm -- --dataSource=src/database/data-source.ts migration:run",
|
||||||
|
"migration:revert": "npm run typeorm -- --dataSource=src/database/data-source.ts migration:revert",
|
||||||
|
"schema:drop": "npm run typeorm -- --dataSource=src/database/data-source.ts schema:drop",
|
||||||
|
"seed:run": "ts-node -r tsconfig-paths/register ./src/database/seeds/run-seed.ts",
|
||||||
|
"prebuild": "rimraf dist",
|
||||||
|
"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": "env-cmd jest --config ./test/jest-e2e.json"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@nestjs-modules/mailer": "1.8.1",
|
||||||
|
"@nestjs/common": "9.1.6",
|
||||||
|
"@nestjs/config": "2.2.0",
|
||||||
|
"@nestjs/core": "9.1.6",
|
||||||
|
"@nestjs/jwt": "9.0.0",
|
||||||
|
"@nestjs/passport": "9.0.0",
|
||||||
|
"@nestjs/platform-express": "9.1.6",
|
||||||
|
"@nestjs/serve-static": "^3.0.0",
|
||||||
|
"@nestjs/swagger": "6.1.3",
|
||||||
|
"@nestjs/typeorm": "9.0.1",
|
||||||
|
"apple-signin-auth": "1.7.4",
|
||||||
|
"bcryptjs": "2.4.3",
|
||||||
|
"class-transformer": "0.5.1",
|
||||||
|
"class-validator": "0.13.2",
|
||||||
|
"fb": "2.0.0",
|
||||||
|
"google-auth-library": "8.7.0",
|
||||||
|
"handlebars": "4.7.7",
|
||||||
|
"multer": "1.4.4",
|
||||||
|
"multer-s3": "2.10.0",
|
||||||
|
"mysql2": "^2.3.3",
|
||||||
|
"nestjs-i18n": "9.2.2",
|
||||||
|
"nodemailer": "6.8.0",
|
||||||
|
"passport": "0.6.0",
|
||||||
|
"passport-anonymous": "1.0.1",
|
||||||
|
"passport-jwt": "4.0.0",
|
||||||
|
"pg": "8.8.0",
|
||||||
|
"reflect-metadata": "0.1.13",
|
||||||
|
"rimraf": "3.0.2",
|
||||||
|
"rxjs": "7.5.7",
|
||||||
|
"source-map-support": "0.5.21",
|
||||||
|
"swagger-ui-express": "4.5.0",
|
||||||
|
"twitter": "1.7.1",
|
||||||
|
"typeorm": "0.3.10"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@faker-js/faker": "^7.6.0",
|
||||||
|
"@nestjs/cli": "9.1.5",
|
||||||
|
"@nestjs/schematics": "9.0.3",
|
||||||
|
"@nestjs/testing": "9.1.6",
|
||||||
|
"@types/bcryptjs": "2.4.2",
|
||||||
|
"@types/express": "4.17.14",
|
||||||
|
"@types/facebook-js-sdk": "3.3.6",
|
||||||
|
"@types/jest": "29.2.3",
|
||||||
|
"@types/multer": "1.4.7",
|
||||||
|
"@types/node": "16.18.3",
|
||||||
|
"@types/passport-anonymous": "1.0.3",
|
||||||
|
"@types/passport-jwt": "3.0.7",
|
||||||
|
"@types/supertest": "2.0.12",
|
||||||
|
"@types/twitter": "1.7.1",
|
||||||
|
"@typescript-eslint/eslint-plugin": "5.43.0",
|
||||||
|
"@typescript-eslint/parser": "5.43.0",
|
||||||
|
"aws-sdk": "2.1243.0",
|
||||||
|
"env-cmd": "10.1.0",
|
||||||
|
"eslint": "8.27.0",
|
||||||
|
"eslint-config-prettier": "8.5.0",
|
||||||
|
"eslint-plugin-import": "2.26.0",
|
||||||
|
"eslint-plugin-prettier": "4.2.1",
|
||||||
|
"husky": "7.0.4",
|
||||||
|
"is-ci": "3.0.1",
|
||||||
|
"jest": "29.3.1",
|
||||||
|
"prettier": "2.7.1",
|
||||||
|
"supertest": "6.3.1",
|
||||||
|
"ts-jest": "29.0.1",
|
||||||
|
"ts-loader": "9.4.1",
|
||||||
|
"ts-node": "10.9.1",
|
||||||
|
"tsconfig-paths": "4.1.0",
|
||||||
|
"tslib": "2.4.1",
|
||||||
|
"typescript": "4.8.4"
|
||||||
|
},
|
||||||
|
"jest": {
|
||||||
|
"moduleFileExtensions": [
|
||||||
|
"js",
|
||||||
|
"json",
|
||||||
|
"ts"
|
||||||
|
],
|
||||||
|
"rootDir": "src",
|
||||||
|
"testRegex": ".*\\.spec\\.ts$",
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(t|j)s$": "ts-jest"
|
||||||
|
},
|
||||||
|
"moduleNameMapper": {
|
||||||
|
"^src/(.*)$": "<rootDir>/$1"
|
||||||
|
},
|
||||||
|
"collectCoverageFrom": [
|
||||||
|
"**/*.(t|j)s"
|
||||||
|
],
|
||||||
|
"coverageDirectory": "../coverage",
|
||||||
|
"testEnvironment": "node"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
myteamwallet_backend/renovate.json
Normal file
5
myteamwallet_backend/renovate.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"extends": [
|
||||||
|
"config:base"
|
||||||
|
]
|
||||||
|
}
|
||||||
86
myteamwallet_backend/src/app.module.ts
Normal file
86
myteamwallet_backend/src/app.module.ts
Normal file
@@ -0,0 +1,86 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { UsersModule } from './users/users.module';
|
||||||
|
import { FilesModule } from './files/files.module';
|
||||||
|
import { AuthModule } from './auth/auth.module';
|
||||||
|
import databaseConfig from './config/database.config';
|
||||||
|
import authConfig from './config/auth.config';
|
||||||
|
import appConfig from './config/app.config';
|
||||||
|
import mailConfig from './config/mail.config';
|
||||||
|
import fileConfig from './config/file.config';
|
||||||
|
import * as path from 'path';
|
||||||
|
import { MailerModule } from '@nestjs-modules/mailer';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { I18nModule } from 'nestjs-i18n/dist/i18n.module';
|
||||||
|
import { HeaderResolver } from 'nestjs-i18n';
|
||||||
|
import { TypeOrmConfigService } from './database/typeorm-config.service';
|
||||||
|
import { MailConfigService } from './mail/mail-config.service';
|
||||||
|
import { ForgotModule } from './forgot/forgot.module';
|
||||||
|
import { MailModule } from './mail/mail.module';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { PlayersModule } from './players/players.module';
|
||||||
|
import { TeamsModule } from './teams/teams.module';
|
||||||
|
import { TransactionsModule } from './transactions/transactions.module';
|
||||||
|
import { TeamSettingsModule } from './team-settings/team-settings.module';
|
||||||
|
import { TeamWalletTransactionsModule } from './team-wallet-transactions/team-wallet-transactions.module';
|
||||||
|
import { ServeStaticModule } from '@nestjs/serve-static';
|
||||||
|
import { join } from 'path';
|
||||||
|
import { LoggingModule } from './database/logging/logging.module';
|
||||||
|
import { TranslateModule } from './translate/translate.module';
|
||||||
|
import { PenaltyModule } from './penalty/penalty.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
load: [databaseConfig, authConfig, appConfig, mailConfig, fileConfig],
|
||||||
|
envFilePath: ['.env'],
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
useClass: TypeOrmConfigService,
|
||||||
|
dataSourceFactory: async (options) => {
|
||||||
|
const dataSource = await new DataSource(options).initialize();
|
||||||
|
return dataSource;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
MailerModule.forRootAsync({
|
||||||
|
useClass: MailConfigService,
|
||||||
|
}),
|
||||||
|
I18nModule.forRootAsync({
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
fallbackLanguage: configService.get('app.fallbackLanguage'),
|
||||||
|
loaderOptions: { path: path.join(__dirname, '/i18n/'), watch: true },
|
||||||
|
}),
|
||||||
|
resolvers: [
|
||||||
|
{
|
||||||
|
use: HeaderResolver,
|
||||||
|
useFactory: (configService: ConfigService) => {
|
||||||
|
return [configService.get('app.headerLanguage')];
|
||||||
|
},
|
||||||
|
inject: [ConfigService],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
}),
|
||||||
|
ServeStaticModule.forRoot({
|
||||||
|
rootPath: join(__dirname, '../client'),
|
||||||
|
exclude: ['*/api*'],
|
||||||
|
}),
|
||||||
|
UsersModule,
|
||||||
|
FilesModule,
|
||||||
|
AuthModule,
|
||||||
|
ForgotModule,
|
||||||
|
MailModule,
|
||||||
|
PlayersModule,
|
||||||
|
TeamsModule,
|
||||||
|
TransactionsModule,
|
||||||
|
TeamSettingsModule,
|
||||||
|
TeamWalletTransactionsModule,
|
||||||
|
LoggingModule,
|
||||||
|
TranslateModule,
|
||||||
|
PenaltyModule,
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
})
|
||||||
|
export class AppModule {}
|
||||||
7
myteamwallet_backend/src/auth/auth-providers.enum.ts
Normal file
7
myteamwallet_backend/src/auth/auth-providers.enum.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export enum AuthProvidersEnum {
|
||||||
|
email = 'email',
|
||||||
|
facebook = 'facebook',
|
||||||
|
google = 'google',
|
||||||
|
twitter = 'twitter',
|
||||||
|
apple = 'apple',
|
||||||
|
}
|
||||||
139
myteamwallet_backend/src/auth/auth.controller.ts
Normal file
139
myteamwallet_backend/src/auth/auth.controller.ts
Normal file
@@ -0,0 +1,139 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
HttpStatus,
|
||||||
|
Request,
|
||||||
|
Post,
|
||||||
|
UseGuards,
|
||||||
|
Patch,
|
||||||
|
Delete,
|
||||||
|
UseInterceptors,
|
||||||
|
ClassSerializerInterceptor,
|
||||||
|
SerializeOptions,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { AuthEmailLoginDto } from './dto/auth-email-login.dto';
|
||||||
|
import { AuthForgotPasswordDto } from './dto/auth-forgot-password.dto';
|
||||||
|
import { AuthConfirmEmailDto } from './dto/auth-confirm-email.dto';
|
||||||
|
import { AuthResetPasswordDto } from './dto/auth-reset-password.dto';
|
||||||
|
import { AuthUpdateDto } from './dto/auth-update.dto';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiBearerAuth,
|
||||||
|
ApiOperation,
|
||||||
|
ApiOkResponse,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
|
import { CreateInviteDTO } from './dto/create-invite.dto';
|
||||||
|
|
||||||
|
@ApiTags('Auth')
|
||||||
|
@Controller({
|
||||||
|
path: 'auth',
|
||||||
|
version: '1',
|
||||||
|
})
|
||||||
|
@UseInterceptors(ClassSerializerInterceptor)
|
||||||
|
export class AuthController {
|
||||||
|
constructor(public service: AuthService) {}
|
||||||
|
|
||||||
|
@Post('email/login')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async login(@Body() loginDto: AuthEmailLoginDto) {
|
||||||
|
return this.service.validateLogin(loginDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('admin/email/login')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async adminLogin(@Body() loginDTO: AuthEmailLoginDto) {
|
||||||
|
return this.service.validateLogin(loginDTO);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('email/register')
|
||||||
|
@HttpCode(HttpStatus.CREATED)
|
||||||
|
async register(@Body() createUserDto: any) {
|
||||||
|
return this.service.register(createUserDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('email/confirm')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
async confirmEmail(@Body() confirmEmailDto: AuthConfirmEmailDto) {
|
||||||
|
return this.service.confirmEmail(confirmEmailDto.hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('forgot/password')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
async forgotPassword(@Body() forgotPasswordDto: AuthForgotPasswordDto) {
|
||||||
|
return this.service.forgotPassword(forgotPasswordDto.email);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('reset/password')
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
async resetPassword(@Body() resetPasswordDto: AuthResetPasswordDto) {
|
||||||
|
return this.service.resetPassword(
|
||||||
|
resetPasswordDto.hash,
|
||||||
|
resetPasswordDto.password,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@SerializeOptions({
|
||||||
|
groups: ['exposeProvider'],
|
||||||
|
})
|
||||||
|
@Get('me')
|
||||||
|
// @UseGuards(AuthGuard('jwt'))
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public me(@Request() request: Request) {
|
||||||
|
return this.service.me(request.headers['authorization']);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Patch('me')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async update(@Request() request, @Body() userDto: AuthUpdateDto) {
|
||||||
|
return this.service.update(request.user, userDto);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Delete('me')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@HttpCode(HttpStatus.OK)
|
||||||
|
public async delete(@Request() request) {
|
||||||
|
return this.service.softDelete(request.user);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Erstellt Registrierungstoken',
|
||||||
|
description:
|
||||||
|
'Encoded den JWT Token für eine Einladung für eine neue Registrierung. Team- und Rollen-Infos müssen da sein. Der Token ist dann 30 Tage gültig.',
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'JWT Token',
|
||||||
|
isArray: false,
|
||||||
|
type: 'string',
|
||||||
|
})
|
||||||
|
@Post('invite')
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
public getInvite(
|
||||||
|
@Body()
|
||||||
|
invite: any,
|
||||||
|
) {
|
||||||
|
return this.service.createTeamInvite(invite);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiOperation({
|
||||||
|
summary: 'Verifiziert Registrierungstoken',
|
||||||
|
description:
|
||||||
|
'Decoded und prüft den JWT Token einer Einladung für die Registrierung',
|
||||||
|
})
|
||||||
|
@ApiOkResponse({
|
||||||
|
description: 'Object mit teamName, teamId, roleName, roleId',
|
||||||
|
isArray: false,
|
||||||
|
type: CreateInviteDTO,
|
||||||
|
})
|
||||||
|
@Post('verify-invite')
|
||||||
|
public verifyInvite(@Body() body: any) {
|
||||||
|
return this.service.getTeamFromInvite(body.token);
|
||||||
|
}
|
||||||
|
}
|
||||||
38
myteamwallet_backend/src/auth/auth.module.ts
Normal file
38
myteamwallet_backend/src/auth/auth.module.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { AuthController } from './auth.controller';
|
||||||
|
import { AuthService } from './auth.service';
|
||||||
|
import { PassportModule } from '@nestjs/passport';
|
||||||
|
import { JwtModule } from '@nestjs/jwt';
|
||||||
|
import { JwtStrategy } from './strategies/jwt.strategy';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { AnonymousStrategy } from './strategies/anonymous.strategy';
|
||||||
|
import { UsersModule } from 'src/users/users.module';
|
||||||
|
import { ForgotModule } from 'src/forgot/forgot.module';
|
||||||
|
import { MailModule } from 'src/mail/mail.module';
|
||||||
|
import { IsExist } from 'src/utils/validators/is-exists.validator';
|
||||||
|
import { IsNotExist } from 'src/utils/validators/is-not-exists.validator';
|
||||||
|
import { LoggingModule } from 'src/database/logging/logging.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
UsersModule,
|
||||||
|
ForgotModule,
|
||||||
|
PassportModule,
|
||||||
|
MailModule,
|
||||||
|
JwtModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => ({
|
||||||
|
secret: configService.get('auth.secret'),
|
||||||
|
signOptions: {
|
||||||
|
expiresIn: configService.get('auth.expires'),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
LoggingModule,
|
||||||
|
],
|
||||||
|
controllers: [AuthController],
|
||||||
|
providers: [IsExist, IsNotExist, AuthService, JwtStrategy, AnonymousStrategy],
|
||||||
|
exports: [AuthService],
|
||||||
|
})
|
||||||
|
export class AuthModule {}
|
||||||
405
myteamwallet_backend/src/auth/auth.service.ts
Normal file
405
myteamwallet_backend/src/auth/auth.service.ts
Normal file
@@ -0,0 +1,405 @@
|
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { User } from '../users/entities/user.entity';
|
||||||
|
import * as bcrypt from 'bcryptjs';
|
||||||
|
import { AuthEmailLoginDto } from './dto/auth-email-login.dto';
|
||||||
|
import { AuthUpdateDto } from './dto/auth-update.dto';
|
||||||
|
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
||||||
|
import { RoleEnum } from 'src/roles/roles.enum';
|
||||||
|
import { StatusEnum } from 'src/statuses/statuses.enum';
|
||||||
|
import * as crypto from 'crypto';
|
||||||
|
import { plainToClass } from 'class-transformer';
|
||||||
|
import { Status } from 'src/statuses/entities/status.entity';
|
||||||
|
import { Role } from 'src/roles/entities/role.entity';
|
||||||
|
import { AuthProvidersEnum } from './auth-providers.enum';
|
||||||
|
import { SocialInterface } from 'src/social/interfaces/social.interface';
|
||||||
|
import { AuthRegisterLoginDto } from './dto/auth-register-login.dto';
|
||||||
|
import { UsersService } from 'src/users/users.service';
|
||||||
|
import { ForgotService } from 'src/forgot/forgot.service';
|
||||||
|
import { MailService } from 'src/mail/mail.service';
|
||||||
|
import { CreateInviteDTO } from './dto/create-invite.dto';
|
||||||
|
import { LoggingService } from 'src/database/logging/logging.service';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AuthService {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private usersService: UsersService,
|
||||||
|
private forgotService: ForgotService,
|
||||||
|
private mailService: MailService,
|
||||||
|
private logger: LoggingService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async validateLogin(
|
||||||
|
loginDto: AuthEmailLoginDto,
|
||||||
|
): Promise<{ token: string; user: User }> {
|
||||||
|
const user = await this.usersService.findOne({
|
||||||
|
email: loginDto.email,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_login_fail',
|
||||||
|
details: `mail not found: ${loginDto.email}`,
|
||||||
|
userId: -1,
|
||||||
|
});
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
email: 'notFound',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user.provider !== AuthProvidersEnum.email) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
email: `needLoginViaProvider:${user.provider}`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const isValidPassword = await bcrypt.compare(
|
||||||
|
loginDto.password,
|
||||||
|
user.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isValidPassword) {
|
||||||
|
const token = await this.jwtService.sign({
|
||||||
|
id: user.id,
|
||||||
|
role: user.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_login_success',
|
||||||
|
details: `logged in: ${loginDto.email}`,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { token, user: user };
|
||||||
|
} else {
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_login_fail',
|
||||||
|
details: `incorrect password for user: ${loginDto.email}`,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
password: 'incorrectPassword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async validateSocialLogin(
|
||||||
|
authProvider: string,
|
||||||
|
socialData: SocialInterface,
|
||||||
|
): Promise<{ token: string; user: User }> {
|
||||||
|
let user: User;
|
||||||
|
const socialEmail = socialData.email?.toLowerCase();
|
||||||
|
|
||||||
|
const userByEmail = await this.usersService.findOne({
|
||||||
|
email: socialEmail,
|
||||||
|
});
|
||||||
|
|
||||||
|
user = await this.usersService.findOne({
|
||||||
|
socialId: socialData.id,
|
||||||
|
provider: authProvider,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user) {
|
||||||
|
if (socialEmail && !userByEmail) {
|
||||||
|
user.email = socialEmail;
|
||||||
|
}
|
||||||
|
await this.usersService.update(user.id, user);
|
||||||
|
} else if (userByEmail) {
|
||||||
|
user = userByEmail;
|
||||||
|
} else {
|
||||||
|
const role = plainToClass(Role, {
|
||||||
|
id: RoleEnum.user,
|
||||||
|
});
|
||||||
|
const status = plainToClass(Status, {
|
||||||
|
id: StatusEnum.active,
|
||||||
|
});
|
||||||
|
|
||||||
|
user = await this.usersService.create({
|
||||||
|
email: socialEmail,
|
||||||
|
firstName: socialData.firstName,
|
||||||
|
lastName: socialData.lastName,
|
||||||
|
socialId: socialData.id,
|
||||||
|
provider: authProvider,
|
||||||
|
role,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
|
||||||
|
user = await this.usersService.findOne({
|
||||||
|
id: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const jwtToken = await this.jwtService.sign({
|
||||||
|
id: user.id,
|
||||||
|
role: user.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
token: jwtToken,
|
||||||
|
user,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async register(dto: AuthRegisterLoginDto): Promise<void> {
|
||||||
|
const hash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(randomStringGenerator())
|
||||||
|
.digest('hex');
|
||||||
|
|
||||||
|
const user = await this.usersService.create({
|
||||||
|
...dto,
|
||||||
|
email: dto.email,
|
||||||
|
role: {
|
||||||
|
id: RoleEnum.user,
|
||||||
|
} as Role,
|
||||||
|
status: {
|
||||||
|
id: StatusEnum.inactive,
|
||||||
|
} as Status,
|
||||||
|
hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (user && dto.linkPlayerId != null) {
|
||||||
|
await this.usersService.linkPlayerToUserId(user, dto.linkPlayerId);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_create',
|
||||||
|
details: `user created with mail: ${dto.email}`,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.mailService.userSignUp({
|
||||||
|
to: user.email,
|
||||||
|
data: {
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmEmail(hash: string): Promise<void> {
|
||||||
|
const user = await this.usersService.findOne({
|
||||||
|
hash,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.NOT_FOUND,
|
||||||
|
error: `notFound`,
|
||||||
|
},
|
||||||
|
HttpStatus.NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
user.hash = null;
|
||||||
|
user.status = plainToClass(Status, {
|
||||||
|
id: StatusEnum.active,
|
||||||
|
});
|
||||||
|
await user.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
async forgotPassword(email: string): Promise<void> {
|
||||||
|
const user = await this.usersService.findOne({
|
||||||
|
email,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
email: 'emailNotExists',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
const hash = crypto
|
||||||
|
.createHash('sha256')
|
||||||
|
.update(randomStringGenerator())
|
||||||
|
.digest('hex');
|
||||||
|
await this.forgotService.create({
|
||||||
|
hash,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.mailService.forgotPassword({
|
||||||
|
to: email,
|
||||||
|
data: {
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async resetPassword(hash: string, password: string): Promise<void> {
|
||||||
|
const forgot = await this.forgotService.findOne({
|
||||||
|
where: {
|
||||||
|
hash,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!forgot) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
hash: `notFound`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const user = forgot.user;
|
||||||
|
user.password = password;
|
||||||
|
await user.save();
|
||||||
|
await this.forgotService.softDelete(forgot.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async me(token: string): Promise<User> {
|
||||||
|
token = token.replace('Bearer ', '');
|
||||||
|
let role: any;
|
||||||
|
try {
|
||||||
|
role = this.jwtService.verify(token);
|
||||||
|
|
||||||
|
const u = await this.usersService.findOne({
|
||||||
|
id: role.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.logger.debug({
|
||||||
|
event: 'user_token_verification_success',
|
||||||
|
details: `Email: ${u.email}`,
|
||||||
|
userId: u.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
return u;
|
||||||
|
} catch (error) {
|
||||||
|
const role = this.jwtService.decode(token);
|
||||||
|
|
||||||
|
const user = await this.usersService.findOne({
|
||||||
|
id: (role as any).id,
|
||||||
|
});
|
||||||
|
const t = await this.jwtService.sign({
|
||||||
|
id: user.id,
|
||||||
|
role: user.role,
|
||||||
|
});
|
||||||
|
|
||||||
|
user['token'] = t;
|
||||||
|
await this.logger.debug({
|
||||||
|
event: 'user_token_verification_success',
|
||||||
|
details: `Email: ${user.email}`,
|
||||||
|
userId: user.id,
|
||||||
|
});
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async update(user: User, userDto: AuthUpdateDto): Promise<User> {
|
||||||
|
if (userDto.password) {
|
||||||
|
if (userDto.oldPassword) {
|
||||||
|
const currentUser = await this.usersService.findOne({
|
||||||
|
id: user.id,
|
||||||
|
});
|
||||||
|
|
||||||
|
const isValidOldPassword = await bcrypt.compare(
|
||||||
|
userDto.oldPassword,
|
||||||
|
currentUser.password,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!isValidOldPassword) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
oldPassword: 'incorrectOldPassword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
oldPassword: 'missingOldPassword',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.usersService.update(user.id, userDto);
|
||||||
|
|
||||||
|
return this.usersService.findOne({
|
||||||
|
id: user.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async softDelete(user: User): Promise<void> {
|
||||||
|
await this.usersService.softDelete(user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTeamInvite(object: CreateInviteDTO) {
|
||||||
|
const token = await this.jwtService.sign(object, {
|
||||||
|
expiresIn: '30d',
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_invite_link_create',
|
||||||
|
details: `invitation created for team: ${object.teamName} , ${object.teamId}`,
|
||||||
|
userId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return { token };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTeamFromInvite(token: string) {
|
||||||
|
try {
|
||||||
|
const teamInfo = this.jwtService.verify(token);
|
||||||
|
delete teamInfo.iat;
|
||||||
|
delete teamInfo.exp;
|
||||||
|
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_invite_link_validate',
|
||||||
|
details: `invitation validated for team: ${teamInfo.teamName} , ${teamInfo.teamId}`,
|
||||||
|
userId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
return teamInfo;
|
||||||
|
} catch {
|
||||||
|
await this.logger.info({
|
||||||
|
event: 'user_invite_link_validate_fail',
|
||||||
|
details: `validation failed for token ${token}`,
|
||||||
|
userId: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
throw new HttpException(
|
||||||
|
'Token not valid',
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class AuthConfirmEmailDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
17
myteamwallet_backend/src/auth/dto/auth-email-login.dto.ts
Normal file
17
myteamwallet_backend/src/auth/dto/auth-email-login.dto.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, Validate } from 'class-validator';
|
||||||
|
import { IsExist } from 'src/utils/validators/is-exists.validator';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
|
||||||
|
export class AuthEmailLoginDto {
|
||||||
|
@ApiProperty({ example: 'test1@example.com' })
|
||||||
|
@Transform(({ value }) => value.toLowerCase().trim())
|
||||||
|
@Validate(IsExist, ['User'], {
|
||||||
|
message: 'emailNotExists',
|
||||||
|
})
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail } from 'class-validator';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
|
||||||
|
export class AuthForgotPasswordDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@Transform(({ value }) => value.toLowerCase().trim())
|
||||||
|
@IsEmail()
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
29
myteamwallet_backend/src/auth/dto/auth-register-login.dto.ts
Normal file
29
myteamwallet_backend/src/auth/dto/auth-register-login.dto.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsEmail, IsNotEmpty, MinLength, Validate } from 'class-validator';
|
||||||
|
import { IsNotExist } from 'src/utils/validators/is-not-exists.validator';
|
||||||
|
import { Transform } from 'class-transformer';
|
||||||
|
|
||||||
|
export class AuthRegisterLoginDto {
|
||||||
|
@ApiProperty({ example: 'test1@example.com' })
|
||||||
|
@Transform(({ value }) => value.toLowerCase().trim())
|
||||||
|
@Validate(IsNotExist, ['User'], {
|
||||||
|
message: 'emailAlreadyExists',
|
||||||
|
})
|
||||||
|
@IsEmail()
|
||||||
|
email: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@MinLength(6)
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'John' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Doe' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 27 })
|
||||||
|
linkPlayerId: number | null;
|
||||||
|
}
|
||||||
12
myteamwallet_backend/src/auth/dto/auth-reset-password.dto.ts
Normal file
12
myteamwallet_backend/src/auth/dto/auth-reset-password.dto.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
|
||||||
|
export class AuthResetPasswordDto {
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
password: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsNotEmpty()
|
||||||
|
hash: string;
|
||||||
|
}
|
||||||
22
myteamwallet_backend/src/auth/dto/auth-social-login.dto.ts
Normal file
22
myteamwallet_backend/src/auth/dto/auth-social-login.dto.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Allow, IsNotEmpty } from 'class-validator';
|
||||||
|
import { Tokens } from 'src/social/tokens';
|
||||||
|
import { AuthProvidersEnum } from '../auth-providers.enum';
|
||||||
|
|
||||||
|
export class AuthSocialLoginDto {
|
||||||
|
@Allow()
|
||||||
|
@ApiProperty({ type: () => Tokens })
|
||||||
|
tokens: Tokens;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: AuthProvidersEnum })
|
||||||
|
@IsNotEmpty()
|
||||||
|
socialType: AuthProvidersEnum;
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
firstName?: string;
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
@ApiProperty({ required: false })
|
||||||
|
lastName?: string;
|
||||||
|
}
|
||||||
34
myteamwallet_backend/src/auth/dto/auth-update.dto.ts
Normal file
34
myteamwallet_backend/src/auth/dto/auth-update.dto.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty, IsOptional, MinLength, Validate } from 'class-validator';
|
||||||
|
import { IsExist } from '../../utils/validators/is-exists.validator';
|
||||||
|
import { FileEntity } from '../../files/entities/file.entity';
|
||||||
|
|
||||||
|
export class AuthUpdateDto {
|
||||||
|
@ApiProperty({ type: () => FileEntity })
|
||||||
|
@IsOptional()
|
||||||
|
@Validate(IsExist, ['FileEntity', 'id'], {
|
||||||
|
message: 'imageNotExists',
|
||||||
|
})
|
||||||
|
photo?: FileEntity;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'John' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: 'mustBeNotEmpty' })
|
||||||
|
firstName?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Doe' })
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: 'mustBeNotEmpty' })
|
||||||
|
lastName?: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty()
|
||||||
|
@MinLength(6)
|
||||||
|
password?: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
@IsOptional()
|
||||||
|
@IsNotEmpty({ message: 'mustBeNotEmpty' })
|
||||||
|
oldPassword: string;
|
||||||
|
}
|
||||||
15
myteamwallet_backend/src/auth/dto/create-invite.dto.ts
Normal file
15
myteamwallet_backend/src/auth/dto/create-invite.dto.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CreateInviteDTO {
|
||||||
|
@ApiProperty()
|
||||||
|
teamId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Development Team' })
|
||||||
|
teamName: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
playerId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Max Mustermann' })
|
||||||
|
playerName: string;
|
||||||
|
}
|
||||||
6
myteamwallet_backend/src/auth/dto/verify-token.dto.ts
Normal file
6
myteamwallet_backend/src/auth/dto/verify-token.dto.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class VerifyTokenDTO {
|
||||||
|
@ApiProperty({ example: 'JWT' })
|
||||||
|
token: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import { Strategy } from 'passport-anonymous';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class AnonymousStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
public validate(payload: unknown, request: unknown): unknown {
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
myteamwallet_backend/src/auth/strategies/jwt.strategy.ts
Normal file
28
myteamwallet_backend/src/auth/strategies/jwt.strategy.ts
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
import { ExtractJwt, Strategy } from 'passport-jwt';
|
||||||
|
import { Injectable, UnauthorizedException } from '@nestjs/common';
|
||||||
|
import { JwtService } from '@nestjs/jwt';
|
||||||
|
import { PassportStrategy } from '@nestjs/passport';
|
||||||
|
import { User } from '../../users/entities/user.entity';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
type JwtPayload = Pick<User, 'id' | 'role'> & { iat: number; exp: number };
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class JwtStrategy extends PassportStrategy(Strategy) {
|
||||||
|
constructor(
|
||||||
|
private jwtService: JwtService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {
|
||||||
|
super({
|
||||||
|
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
|
||||||
|
secretOrKey: configService.get('auth.secret'),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public validate(payload: JwtPayload) {
|
||||||
|
if (!payload.id) {
|
||||||
|
throw new UnauthorizedException();
|
||||||
|
}
|
||||||
|
return payload;
|
||||||
|
}
|
||||||
|
}
|
||||||
13
myteamwallet_backend/src/config/app.config.ts
Normal file
13
myteamwallet_backend/src/config/app.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('app', () => ({
|
||||||
|
nodeEnv: process.env.NODE_ENV,
|
||||||
|
name: process.env.APP_NAME,
|
||||||
|
workingDirectory: process.env.PWD || process.cwd(),
|
||||||
|
frontendDomain: process.env.FRONTEND_DOMAIN,
|
||||||
|
backendDomain: process.env.BACKEND_DOMAIN,
|
||||||
|
port: parseInt(process.env.APP_PORT || process.env.PORT, 10) || 3000,
|
||||||
|
apiPrefix: process.env.API_PREFIX || 'api',
|
||||||
|
fallbackLanguage: process.env.APP_FALLBACK_LANGUAGE || 'en',
|
||||||
|
headerLanguage: process.env.APP_HEADER_LANGUAGE || 'x-custom-lang',
|
||||||
|
}));
|
||||||
6
myteamwallet_backend/src/config/auth.config.ts
Normal file
6
myteamwallet_backend/src/config/auth.config.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('auth', () => ({
|
||||||
|
secret: process.env.AUTH_JWT_SECRET,
|
||||||
|
expires: process.env.AUTH_JWT_TOKEN_EXPIRES_IN,
|
||||||
|
}));
|
||||||
17
myteamwallet_backend/src/config/database.config.ts
Normal file
17
myteamwallet_backend/src/config/database.config.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('database', () => ({
|
||||||
|
url: process.env.DATABASE_URL,
|
||||||
|
type: process.env.DATABASE_TYPE,
|
||||||
|
host: process.env.DATABASE_HOST,
|
||||||
|
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
|
||||||
|
password: process.env.DATABASE_PASSWORD,
|
||||||
|
name: process.env.DATABASE_NAME,
|
||||||
|
username: process.env.DATABASE_USERNAME,
|
||||||
|
synchronize: process.env.DATABASE_SYNCHRONIZE === 'true',
|
||||||
|
sslEnabled: process.env.DATABASE_SSL_ENABLED === 'true',
|
||||||
|
rejectUnauthorized: process.env.DATABASE_REJECT_UNAUTHORIZED === 'true',
|
||||||
|
ca: process.env.DATABASE_CA,
|
||||||
|
key: process.env.DATABASE_KEY,
|
||||||
|
cert: process.env.DATABASE_CERT,
|
||||||
|
}));
|
||||||
11
myteamwallet_backend/src/config/file.config.ts
Normal file
11
myteamwallet_backend/src/config/file.config.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('file', () => ({
|
||||||
|
driver: process.env.FILE_DRIVER,
|
||||||
|
accessKeyId: process.env.ACCESS_KEY_ID,
|
||||||
|
secretAccessKey: process.env.SECRET_ACCESS_KEY,
|
||||||
|
awsDefaultS3Bucket: process.env.AWS_DEFAULT_S3_BUCKET,
|
||||||
|
awsDefaultS3Url: process.env.AWS_DEFAULT_S3_URL,
|
||||||
|
awsS3Region: process.env.AWS_S3_REGION,
|
||||||
|
maxFileSize: 5242880, // 5mb
|
||||||
|
}));
|
||||||
13
myteamwallet_backend/src/config/mail.config.ts
Normal file
13
myteamwallet_backend/src/config/mail.config.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import { registerAs } from '@nestjs/config';
|
||||||
|
|
||||||
|
export default registerAs('mail', () => ({
|
||||||
|
port: parseInt(process.env.MAIL_PORT, 10),
|
||||||
|
host: process.env.MAIL_HOST,
|
||||||
|
user: process.env.MAIL_USER,
|
||||||
|
password: process.env.MAIL_PASSWORD,
|
||||||
|
defaultEmail: process.env.MAIL_DEFAULT_EMAIL,
|
||||||
|
defaultName: process.env.MAIL_DEFAULT_NAME,
|
||||||
|
ignoreTLS: process.env.MAIL_IGNORE_TLS === 'true',
|
||||||
|
secure: process.env.MAIL_SECURE === 'true',
|
||||||
|
requireTLS: process.env.MAIL_REQUIRE_TLS === 'true',
|
||||||
|
}));
|
||||||
23
myteamwallet_backend/src/database/data-source.ts
Normal file
23
myteamwallet_backend/src/database/data-source.ts
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
import 'reflect-metadata';
|
||||||
|
import { DataSource, DataSourceOptions } from 'typeorm';
|
||||||
|
|
||||||
|
export const AppDataSource = new DataSource({
|
||||||
|
type: process.env.DATABASE_TYPE,
|
||||||
|
url: process.env.DATABASE_URL,
|
||||||
|
host: process.env.DATABASE_HOST,
|
||||||
|
port: parseInt(process.env.DATABASE_PORT, 10) || 5432,
|
||||||
|
username: process.env.DATABASE_USERNAME,
|
||||||
|
password: process.env.DATABASE_PASSWORD,
|
||||||
|
database: process.env.DATABASE_NAME,
|
||||||
|
synchronize: process.env.DATABASE_SYNCHRONIZE === 'true',
|
||||||
|
dropSchema: false,
|
||||||
|
keepConnectionAlive: true,
|
||||||
|
logging: process.env.NODE_ENV !== 'production',
|
||||||
|
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||||
|
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
|
||||||
|
cli: {
|
||||||
|
entitiesDir: 'src',
|
||||||
|
migrationsDir: 'src/database/migrations',
|
||||||
|
subscribersDir: 'subscriber',
|
||||||
|
},
|
||||||
|
} as DataSourceOptions);
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { LOGEVENT, LOGLEVEL } from '../model/logging-event.type';
|
||||||
|
|
||||||
|
export class CreateLogDTO {
|
||||||
|
level: LOGLEVEL;
|
||||||
|
|
||||||
|
event: LOGEVENT;
|
||||||
|
|
||||||
|
details: string;
|
||||||
|
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
duration?: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import { LOGEVENT, LOGLEVEL } from '../model/logging-event.type';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class LogEntry extends EntityHelper {
|
||||||
|
@ApiProperty({ example: 1 })
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'error' })
|
||||||
|
@Column()
|
||||||
|
level: LOGLEVEL;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'created new user' })
|
||||||
|
@Column()
|
||||||
|
event: LOGEVENT;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'created new user max mustermann' })
|
||||||
|
@Column()
|
||||||
|
details: string;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 5 })
|
||||||
|
@Column()
|
||||||
|
userId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 5 })
|
||||||
|
@Column({ nullable: true })
|
||||||
|
duration?: number;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
11
myteamwallet_backend/src/database/logging/logging.module.ts
Normal file
11
myteamwallet_backend/src/database/logging/logging.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { LogEntry } from './entities/log-entry.entity';
|
||||||
|
import { LoggingService } from './logging.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([LogEntry])],
|
||||||
|
providers: [LoggingService],
|
||||||
|
exports: [LoggingService],
|
||||||
|
})
|
||||||
|
export class LoggingModule {}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { LoggingService } from './logging.service';
|
||||||
|
|
||||||
|
describe('LoggingService', () => {
|
||||||
|
let service: LoggingService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [LoggingService],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<LoggingService>(LoggingService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
95
myteamwallet_backend/src/database/logging/logging.service.ts
Normal file
95
myteamwallet_backend/src/database/logging/logging.service.ts
Normal file
@@ -0,0 +1,95 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { CreateLogDTO } from './dto/create-log.dto';
|
||||||
|
import { LogEntry } from './entities/log-entry.entity';
|
||||||
|
import { LOGEVENT } from './model/logging-event.type';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class LoggingService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(LogEntry)
|
||||||
|
private repository: Repository<LogEntry>,
|
||||||
|
) {
|
||||||
|
// void this.onStart();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onStart() {
|
||||||
|
await this.repository.save({
|
||||||
|
level: 'DEBUG',
|
||||||
|
event: 'application_start',
|
||||||
|
details: 'application started',
|
||||||
|
userId: -1,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async info({
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
event: LOGEVENT;
|
||||||
|
details: string;
|
||||||
|
userId: number;
|
||||||
|
}) {
|
||||||
|
const e: CreateLogDTO = {
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
level: 'INFO',
|
||||||
|
};
|
||||||
|
await this.repository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
async error({
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
event: LOGEVENT;
|
||||||
|
details: string;
|
||||||
|
userId: number;
|
||||||
|
}) {
|
||||||
|
const e: CreateLogDTO = {
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
level: 'ERROR',
|
||||||
|
};
|
||||||
|
await this.repository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
async warn({
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
}: {
|
||||||
|
event: LOGEVENT;
|
||||||
|
details: string;
|
||||||
|
userId: number;
|
||||||
|
}) {
|
||||||
|
const e: CreateLogDTO = {
|
||||||
|
event,
|
||||||
|
details,
|
||||||
|
userId,
|
||||||
|
level: 'WARN',
|
||||||
|
};
|
||||||
|
await this.repository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
async debug(data: {
|
||||||
|
event: LOGEVENT;
|
||||||
|
details: string;
|
||||||
|
userId: number;
|
||||||
|
duration?: number;
|
||||||
|
}) {
|
||||||
|
const e: CreateLogDTO = {
|
||||||
|
event: data.event,
|
||||||
|
details: data.details,
|
||||||
|
userId: data.userId,
|
||||||
|
level: 'DEBUG',
|
||||||
|
duration: data.duration,
|
||||||
|
};
|
||||||
|
await this.repository.save(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum LogTypeEnum {
|
||||||
|
'error' = 1,
|
||||||
|
'info' = 2,
|
||||||
|
'warn' = 3,
|
||||||
|
}
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
export type LOGEVENT =
|
||||||
|
| 'user_create'
|
||||||
|
| 'application_start'
|
||||||
|
| 'transaction_create'
|
||||||
|
| 'team_transaction_create'
|
||||||
|
| 'team_transaction_get'
|
||||||
|
| 'user_login_success'
|
||||||
|
| 'user_login_fail'
|
||||||
|
| 'user_token_verification_success'
|
||||||
|
| 'user_token_verification_fail'
|
||||||
|
| 'user_invite_link_create'
|
||||||
|
| 'user_invite_link_validate'
|
||||||
|
| 'user_invite_link_validate_fail'
|
||||||
|
| 'transaction_create'
|
||||||
|
| 'transaction_create_fail'
|
||||||
|
| 'transaction_reverse'
|
||||||
|
| 'player_creation'
|
||||||
|
| 'team_create';
|
||||||
|
|
||||||
|
export type LOGLEVEL = 'FATAL' | 'ERROR' | 'WARN' | 'INFO' | 'DEBUG' | 'TRACE';
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class CreateUser1604164774154 implements MigrationInterface {
|
||||||
|
name = 'CreateUser1604164774154';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "file" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "path" character varying NOT NULL, CONSTRAINT "PK_36b46d232307066b3a2c9ea3a1d" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "role" ("id" integer NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_b36bcfe02fc8de3c57a8b2391c2" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "status" ("id" integer NOT NULL, "name" character varying NOT NULL, CONSTRAINT "PK_e12743a7086ec826733f54e1d95" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "user" ("id" SERIAL NOT NULL, "email" character varying, "password" character varying, "provider" character varying NOT NULL DEFAULT 'email', "socialId" character varying, "firstName" character varying, "lastName" character varying, "hash" character varying, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "photoId" uuid, "roleId" integer, "statusId" integer, CONSTRAINT "UQ_e12875dfb3b1d92d7d7c5377e22" UNIQUE ("email"), CONSTRAINT "PK_cace4a159ff9f2512dd42373760" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_9bd2fe7a8e694dedc4ec2f666f" ON "user" ("socialId") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_58e4dbff0e1a32a9bdc861bb29" ON "user" ("firstName") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_f0e1b4ecdca13b177e2e3a0613" ON "user" ("lastName") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_e282acb94d2e3aec10f480e4f6" ON "user" ("hash") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE TABLE "forgot" ("id" SERIAL NOT NULL, "hash" character varying NOT NULL, "createdAt" TIMESTAMP NOT NULL DEFAULT now(), "deletedAt" TIMESTAMP, "userId" integer, CONSTRAINT "PK_087959f5bb89da4ce3d763eab75" PRIMARY KEY ("id"))`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`CREATE INDEX "IDX_df507d27b0fb20cd5f7bef9b9a" ON "forgot" ("hash") `,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" ADD CONSTRAINT "FK_75e2be4ce11d447ef43be0e374f" FOREIGN KEY ("photoId") REFERENCES "file"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" ADD CONSTRAINT "FK_c28e52f758e7bbc53828db92194" FOREIGN KEY ("roleId") REFERENCES "role"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" ADD CONSTRAINT "FK_dc18daa696860586ba4667a9d31" FOREIGN KEY ("statusId") REFERENCES "status"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "forgot" ADD CONSTRAINT "FK_31f3c80de0525250f31e23a9b83" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE NO ACTION ON UPDATE NO ACTION`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "forgot" DROP CONSTRAINT "FK_31f3c80de0525250f31e23a9b83"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" DROP CONSTRAINT "FK_dc18daa696860586ba4667a9d31"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" DROP CONSTRAINT "FK_c28e52f758e7bbc53828db92194"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "user" DROP CONSTRAINT "FK_75e2be4ce11d447ef43be0e374f"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_df507d27b0fb20cd5f7bef9b9a"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "forgot"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_e282acb94d2e3aec10f480e4f6"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_f0e1b4ecdca13b177e2e3a0613"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_58e4dbff0e1a32a9bdc861bb29"`);
|
||||||
|
await queryRunner.query(`DROP INDEX "IDX_9bd2fe7a8e694dedc4ec2f666f"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "user"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "status"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "role"`);
|
||||||
|
await queryRunner.query(`DROP TABLE "file"`);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { MigrationInterface, QueryRunner } from 'typeorm';
|
||||||
|
|
||||||
|
export class AddTeamPublicAccess1785513600000 implements MigrationInterface {
|
||||||
|
name = 'AddTeamPublicAccess1785513600000';
|
||||||
|
|
||||||
|
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" ADD "publicAccessEnabled" boolean NOT NULL DEFAULT false`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" ADD "publicAccessToken" character varying(64)`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" ADD CONSTRAINT "UQ_team_public_access_token" UNIQUE ("publicAccessToken")`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" DROP CONSTRAINT "UQ_team_public_access_token"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" DROP COLUMN "publicAccessToken"`,
|
||||||
|
);
|
||||||
|
await queryRunner.query(
|
||||||
|
`ALTER TABLE "team" DROP COLUMN "publicAccessEnabled"`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { PlayersSeedService } from './player-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Player, Team, User])],
|
||||||
|
providers: [PlayersSeedService],
|
||||||
|
exports: [PlayersSeedService],
|
||||||
|
})
|
||||||
|
export class PlayersSeedModule {}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PlayersSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Player)
|
||||||
|
private repository: Repository<Player>,
|
||||||
|
@InjectRepository(Team)
|
||||||
|
private teamRepository: Repository<Team>,
|
||||||
|
@InjectRepository(User)
|
||||||
|
private userRepository: Repository<User>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const dev = await this.teamRepository.findOne({ where: { id: 1 } });
|
||||||
|
|
||||||
|
const countPlayer = await this.repository.count();
|
||||||
|
|
||||||
|
if (countPlayer === 0) {
|
||||||
|
const players = [];
|
||||||
|
for (let index = 0; index < 20; index++) {
|
||||||
|
const p = this.repository.create({
|
||||||
|
firstName: faker.name.firstName(),
|
||||||
|
lastName: faker.name.lastName(),
|
||||||
|
teamRole: {
|
||||||
|
id: 1,
|
||||||
|
name: 'player',
|
||||||
|
},
|
||||||
|
team: dev,
|
||||||
|
});
|
||||||
|
players.push(p);
|
||||||
|
}
|
||||||
|
|
||||||
|
const p2 = this.repository.create({
|
||||||
|
firstName: 'Player',
|
||||||
|
lastName: 'Kapitän',
|
||||||
|
teamRole: {
|
||||||
|
id: 3,
|
||||||
|
name: 'captain',
|
||||||
|
},
|
||||||
|
team: dev,
|
||||||
|
});
|
||||||
|
|
||||||
|
const u = await this.userRepository.findOne({
|
||||||
|
where: { email: 'mail@bastian-wagner.de' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const p3 = this.repository.create({
|
||||||
|
firstName: 'Player',
|
||||||
|
lastName: 'Kassenwart',
|
||||||
|
teamRole: {
|
||||||
|
id: 4,
|
||||||
|
name: 'treasurer',
|
||||||
|
},
|
||||||
|
team: dev,
|
||||||
|
user: u,
|
||||||
|
});
|
||||||
|
|
||||||
|
await this.repository.save(players);
|
||||||
|
await this.repository.save([p2, p3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Role } from 'src/roles/entities/role.entity';
|
||||||
|
import { RoleSeedService } from './role-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Role])],
|
||||||
|
providers: [RoleSeedService],
|
||||||
|
exports: [RoleSeedService],
|
||||||
|
})
|
||||||
|
export class RoleSeedModule {}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Role } from 'src/roles/entities/role.entity';
|
||||||
|
import { RoleEnum } from 'src/roles/roles.enum';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RoleSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Role)
|
||||||
|
private repository: Repository<Role>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const countPlayer = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: RoleEnum.user,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countPlayer === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: RoleEnum.user,
|
||||||
|
name: 'user',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countAdmins = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: RoleEnum.admin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countAdmins === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: RoleEnum.admin,
|
||||||
|
name: 'admin',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
33
myteamwallet_backend/src/database/seeds/run-seed.ts
Normal file
33
myteamwallet_backend/src/database/seeds/run-seed.ts
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { PlayersSeedService } from './player/player-seed.service';
|
||||||
|
import { RoleSeedService } from './role/role-seed.service';
|
||||||
|
import { SeedModule } from './seed.module';
|
||||||
|
import { StatusSeedService } from './status/status-seed.service';
|
||||||
|
import { TeamRoleSeedService } from './team-role/team-role-seed.service';
|
||||||
|
import { TeamsSettingsSeedService } from './team-settings/team-settings-seed.service';
|
||||||
|
import { TeamWalletTransactionTypeSeedService } from './team-wallet-transactions-type/team-wallet-transaction-type-seed.service';
|
||||||
|
import { TeamWalletTransactionSeedService } from './team-wallet-transactions/team-wallet-transaction-seed.service';
|
||||||
|
import { TeamsSeedService } from './teams/teams-seed.service';
|
||||||
|
import { TransactionsTypeSeedService } from './transaction-type/transactions-type-seed.service';
|
||||||
|
import { TransactionsSeedService } from './transactions/transactions-seed.service';
|
||||||
|
import { UserSeedService } from './user/user-seed.service';
|
||||||
|
|
||||||
|
const runSeed = async () => {
|
||||||
|
const app = await NestFactory.create(SeedModule);
|
||||||
|
|
||||||
|
// run
|
||||||
|
await app.get(RoleSeedService).run();
|
||||||
|
await app.get(StatusSeedService).run();
|
||||||
|
await app.get(UserSeedService).run();
|
||||||
|
await app.get(TeamRoleSeedService).run();
|
||||||
|
await app.get(TeamsSeedService).run();
|
||||||
|
await app.get(PlayersSeedService).run();
|
||||||
|
await app.get(TransactionsTypeSeedService).run();
|
||||||
|
await app.get(TransactionsSeedService).run();
|
||||||
|
await app.get(TeamsSettingsSeedService).run();
|
||||||
|
await app.get(TeamWalletTransactionTypeSeedService).run();
|
||||||
|
await app.get(TeamWalletTransactionSeedService).run();
|
||||||
|
await app.close();
|
||||||
|
};
|
||||||
|
|
||||||
|
void runSeed();
|
||||||
47
myteamwallet_backend/src/database/seeds/seed.module.ts
Normal file
47
myteamwallet_backend/src/database/seeds/seed.module.ts
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import appConfig from 'src/config/app.config';
|
||||||
|
import databaseConfig from 'src/config/database.config';
|
||||||
|
import { DataSource } from 'typeorm';
|
||||||
|
import { TypeOrmConfigService } from '../typeorm-config.service';
|
||||||
|
import { PlayersSeedModule } from './player/player-seed.module';
|
||||||
|
import { RoleSeedModule } from './role/role-seed.module';
|
||||||
|
import { StatusSeedModule } from './status/status-seed.module';
|
||||||
|
import { TeamRolesSeedModule } from './team-role/team-role-seed.module';
|
||||||
|
import { TeamSettingsSeedModule } from './team-settings/team-settings-seed.module';
|
||||||
|
import { TeamWalletTransactionTypeSeedModule } from './team-wallet-transactions-type/team-wallet-transaction-type-seed.module';
|
||||||
|
import { TeamWalletTransactionSeedModule } from './team-wallet-transactions/team-wallet-transaction-seed.module';
|
||||||
|
import { TeamsSeedModule } from './teams/teams-seed.module';
|
||||||
|
import { TransactionsTypeSeedModule } from './transaction-type/transactions-type-seed.module';
|
||||||
|
import { TransactionsSeedModule } from './transactions/transactions-seed.module';
|
||||||
|
import { UserSeedModule } from './user/user-seed.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
ConfigModule.forRoot({
|
||||||
|
isGlobal: true,
|
||||||
|
load: [databaseConfig, appConfig],
|
||||||
|
envFilePath: ['.env'],
|
||||||
|
}),
|
||||||
|
TypeOrmModule.forRootAsync({
|
||||||
|
useClass: TypeOrmConfigService,
|
||||||
|
dataSourceFactory: async (options) => {
|
||||||
|
const dataSource = await new DataSource(options).initialize();
|
||||||
|
return dataSource;
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
RoleSeedModule,
|
||||||
|
StatusSeedModule,
|
||||||
|
UserSeedModule,
|
||||||
|
TeamRolesSeedModule,
|
||||||
|
TeamsSeedModule,
|
||||||
|
PlayersSeedModule,
|
||||||
|
TransactionsTypeSeedModule,
|
||||||
|
TransactionsSeedModule,
|
||||||
|
TeamSettingsSeedModule,
|
||||||
|
TeamWalletTransactionTypeSeedModule,
|
||||||
|
TeamWalletTransactionSeedModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class SeedModule {}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Status } from 'src/statuses/entities/status.entity';
|
||||||
|
import { StatusSeedService } from './status-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Status])],
|
||||||
|
providers: [StatusSeedService],
|
||||||
|
exports: [StatusSeedService],
|
||||||
|
})
|
||||||
|
export class StatusSeedModule {}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Status } from 'src/statuses/entities/status.entity';
|
||||||
|
import { StatusEnum } from 'src/statuses/statuses.enum';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class StatusSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Status)
|
||||||
|
private repository: Repository<Status>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const count = await this.repository.count();
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
await this.repository.save([
|
||||||
|
this.repository.create({
|
||||||
|
id: StatusEnum.active,
|
||||||
|
name: 'Active',
|
||||||
|
}),
|
||||||
|
this.repository.create({
|
||||||
|
id: StatusEnum.inactive,
|
||||||
|
name: 'Inactive',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TeamRole } from 'src/team-roles/entities/team-roles.entity';
|
||||||
|
import { TeamRoleSeedService } from './team-role-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TeamRole])],
|
||||||
|
providers: [TeamRoleSeedService],
|
||||||
|
exports: [TeamRoleSeedService],
|
||||||
|
})
|
||||||
|
export class TeamRolesSeedModule {}
|
||||||
@@ -0,0 +1,90 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { TeamRole } from 'src/team-roles/entities/team-roles.entity';
|
||||||
|
import { TeamRolesEnum } from 'src/team-roles/team-roles.enum';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamRoleSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TeamRole)
|
||||||
|
private repository: Repository<TeamRole>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const countPlayer = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: TeamRolesEnum.player,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countPlayer === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamRolesEnum.player,
|
||||||
|
name: 'player',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countScndTr = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: TeamRolesEnum.scnd_treasurer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countScndTr === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamRolesEnum.scnd_treasurer,
|
||||||
|
name: 'scnd_treasurer',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countCaptain = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: TeamRolesEnum.captain,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countCaptain === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamRolesEnum.captain,
|
||||||
|
name: 'captain',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countTreasurer = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: TeamRolesEnum.treasurer,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countTreasurer === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamRolesEnum.treasurer,
|
||||||
|
name: 'treasurer',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countCoach = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: TeamRolesEnum.coach,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countCoach === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamRolesEnum.coach,
|
||||||
|
name: 'coach',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TeamSetting } from 'src/team-settings/entities/team-setting.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { TeamsSettingsSeedService } from './team-settings-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Team, TeamSetting])],
|
||||||
|
providers: [TeamsSettingsSeedService],
|
||||||
|
exports: [TeamsSettingsSeedService],
|
||||||
|
})
|
||||||
|
export class TeamSettingsSeedModule {}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { TeamSetting } from 'src/team-settings/entities/team-setting.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamsSettingsSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TeamSetting)
|
||||||
|
private repository: Repository<TeamSetting>,
|
||||||
|
@InjectRepository(Team)
|
||||||
|
private teamrepository: Repository<Team>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const countTeamSettings = await this.repository.count();
|
||||||
|
|
||||||
|
if (countTeamSettings === 0) {
|
||||||
|
const team = await this.teamrepository.findOne({ where: { id: 1 } });
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
team,
|
||||||
|
key: 'transaction_create_min_role',
|
||||||
|
value: '2',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TeamWalletTransactionType } from 'src/team-wallet-transactions/entities/team-wallet-transaction-type.entity';
|
||||||
|
import { TeamWalletTransactionTypeSeedService } from './team-wallet-transaction-type-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TeamWalletTransactionType])],
|
||||||
|
providers: [TeamWalletTransactionTypeSeedService],
|
||||||
|
exports: [TeamWalletTransactionTypeSeedService],
|
||||||
|
})
|
||||||
|
export class TeamWalletTransactionTypeSeedModule {}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { TeamWalletTransactionType } from 'src/team-wallet-transactions/entities/team-wallet-transaction-type.entity';
|
||||||
|
import { TeamWalletTransactionEnum } from 'src/team-wallet-transactions/team-wallet-transaction.enum';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamWalletTransactionTypeSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TeamWalletTransactionType)
|
||||||
|
private repository: Repository<TeamWalletTransactionType>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const amount = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (amount === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamWalletTransactionEnum.credit,
|
||||||
|
name: 'credit',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: TeamWalletTransactionEnum.expense,
|
||||||
|
name: 'expense',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TeamWalletTransaction } from 'src/team-wallet-transactions/entities/team-wallet-transaction.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { TeamWalletTransactionSeedService } from './team-wallet-transaction-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TeamWalletTransaction, Team])],
|
||||||
|
providers: [TeamWalletTransactionSeedService],
|
||||||
|
exports: [TeamWalletTransactionSeedService],
|
||||||
|
})
|
||||||
|
export class TeamWalletTransactionSeedModule {}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { TeamWalletTransaction } from 'src/team-wallet-transactions/entities/team-wallet-transaction.entity';
|
||||||
|
import { TeamWalletTransactionEnum } from 'src/team-wallet-transactions/team-wallet-transaction.enum';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamWalletTransactionSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TeamWalletTransaction)
|
||||||
|
private repository: Repository<TeamWalletTransaction>,
|
||||||
|
@InjectRepository(Team)
|
||||||
|
private teamrepository: Repository<Team>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const amount = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: 0,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (amount === 0) {
|
||||||
|
const team = await this.teamrepository.findOneBy({ id: 1 });
|
||||||
|
|
||||||
|
for (let index = 0; index < 20; index++) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
team: team,
|
||||||
|
note: '',
|
||||||
|
date: faker.date.recent(90).toISOString(),
|
||||||
|
amount: Math.random() * 10,
|
||||||
|
type: {
|
||||||
|
id: TeamWalletTransactionEnum.credit,
|
||||||
|
name: 'credit',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { TeamsSeedService } from './teams-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Team])],
|
||||||
|
providers: [TeamsSeedService],
|
||||||
|
exports: [TeamsSeedService],
|
||||||
|
})
|
||||||
|
export class TeamsSeedModule {}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamsSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Team)
|
||||||
|
private repository: Repository<Team>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const countTeams = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
id: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countTeams === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
id: 1,
|
||||||
|
name: 'Development Team',
|
||||||
|
alias: '9999',
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TransactionType } from 'src/transactions/entitites/transaction-type.entity';
|
||||||
|
import { TransactionsTypeSeedService } from './transactions-type-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TransactionType])],
|
||||||
|
providers: [TransactionsTypeSeedService],
|
||||||
|
exports: [TransactionsTypeSeedService],
|
||||||
|
})
|
||||||
|
export class TransactionsTypeSeedModule {}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { TransactionType } from 'src/transactions/entitites/transaction-type.entity';
|
||||||
|
import { TransactionTypeEnum } from 'src/transactions/transaction-type.enum';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsTypeSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(TransactionType)
|
||||||
|
private repository: Repository<TransactionType>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const count = await this.repository.count();
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
await this.repository.save([
|
||||||
|
this.repository.create({
|
||||||
|
id: TransactionTypeEnum.credit,
|
||||||
|
name: 'credit',
|
||||||
|
}),
|
||||||
|
this.repository.create({
|
||||||
|
id: TransactionTypeEnum.fee,
|
||||||
|
name: 'fee',
|
||||||
|
}),
|
||||||
|
this.repository.create({
|
||||||
|
id: TransactionTypeEnum.fine,
|
||||||
|
name: 'fine',
|
||||||
|
}),
|
||||||
|
this.repository.create({
|
||||||
|
id: TransactionTypeEnum.levy,
|
||||||
|
name: 'levy',
|
||||||
|
}),
|
||||||
|
this.repository.create({
|
||||||
|
id: TransactionTypeEnum.payment,
|
||||||
|
name: 'payment',
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
import { TransactionType } from 'src/transactions/entitites/transaction-type.entity';
|
||||||
|
import { Transaction } from 'src/transactions/entitites/transaction.entity';
|
||||||
|
import { TransactionsSeedService } from './transactions-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([TransactionType, Transaction, Player])],
|
||||||
|
providers: [TransactionsSeedService],
|
||||||
|
exports: [TransactionsSeedService],
|
||||||
|
})
|
||||||
|
export class TransactionsSeedModule {}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
import { TransactionType } from 'src/transactions/entitites/transaction-type.entity';
|
||||||
|
import { Transaction } from 'src/transactions/entitites/transaction.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { faker } from '@faker-js/faker';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TransactionsSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Transaction)
|
||||||
|
private repository: Repository<Transaction>,
|
||||||
|
@InjectRepository(Player)
|
||||||
|
private playerRepository: Repository<Player>,
|
||||||
|
@InjectRepository(TransactionType)
|
||||||
|
private transactionTypeRepository: Repository<TransactionType>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const count = await this.repository.count();
|
||||||
|
|
||||||
|
if (count === 0) {
|
||||||
|
const players = await this.playerRepository.find();
|
||||||
|
const transactionTypes = await this.transactionTypeRepository.find();
|
||||||
|
|
||||||
|
const transactions = [];
|
||||||
|
for (let x = 0; x < 500; x++) {
|
||||||
|
const p = Math.floor(Math.random() * players.length);
|
||||||
|
const type = Math.floor(Math.random() * transactionTypes.length);
|
||||||
|
|
||||||
|
const t = this.repository.create({
|
||||||
|
amount: Math.random() * 50,
|
||||||
|
player: players[p],
|
||||||
|
type: transactionTypes[type],
|
||||||
|
note: faker.hacker.phrase(),
|
||||||
|
date: faker.date.recent(90).toISOString(),
|
||||||
|
});
|
||||||
|
transactions.push(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
await this.repository.save(transactions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { UserSeedService } from './user-seed.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([User])],
|
||||||
|
providers: [UserSeedService],
|
||||||
|
exports: [UserSeedService],
|
||||||
|
})
|
||||||
|
export class UserSeedModule {}
|
||||||
@@ -0,0 +1,70 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { RoleEnum } from 'src/roles/roles.enum';
|
||||||
|
import { StatusEnum } from 'src/statuses/statuses.enum';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class UserSeedService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(User)
|
||||||
|
private repository: Repository<User>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async run() {
|
||||||
|
const countAdmin = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
role: {
|
||||||
|
id: RoleEnum.admin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countAdmin === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
firstName: 'Bastian',
|
||||||
|
lastName: 'Wagner',
|
||||||
|
email: 'mail@bastian-wagner.de',
|
||||||
|
password: 'Passwort123',
|
||||||
|
role: {
|
||||||
|
id: RoleEnum.admin,
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
id: StatusEnum.active,
|
||||||
|
name: 'Active',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const countUser = await this.repository.count({
|
||||||
|
where: {
|
||||||
|
role: {
|
||||||
|
id: RoleEnum.user,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (countUser === 0) {
|
||||||
|
await this.repository.save(
|
||||||
|
this.repository.create({
|
||||||
|
firstName: 'John',
|
||||||
|
lastName: 'Doe',
|
||||||
|
email: 'john.doe@example.com',
|
||||||
|
password: 'secret',
|
||||||
|
role: {
|
||||||
|
id: RoleEnum.user,
|
||||||
|
name: 'Admin',
|
||||||
|
},
|
||||||
|
status: {
|
||||||
|
id: StatusEnum.active,
|
||||||
|
name: 'Active',
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
31
myteamwallet_backend/src/database/typeorm-config.service.ts
Normal file
31
myteamwallet_backend/src/database/typeorm-config.service.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { TypeOrmModuleOptions, TypeOrmOptionsFactory } from '@nestjs/typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TypeOrmConfigService implements TypeOrmOptionsFactory {
|
||||||
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
|
createTypeOrmOptions(): TypeOrmModuleOptions {
|
||||||
|
return {
|
||||||
|
type: this.configService.get('database.type'),
|
||||||
|
url: this.configService.get('database.url'),
|
||||||
|
host: this.configService.get('database.host'),
|
||||||
|
port: this.configService.get('database.port'),
|
||||||
|
username: this.configService.get('database.username'),
|
||||||
|
password: this.configService.get('database.password'),
|
||||||
|
database: this.configService.get('database.name'),
|
||||||
|
synchronize: this.configService.get('database.synchronize'),
|
||||||
|
dropSchema: false,
|
||||||
|
keepConnectionAlive: true,
|
||||||
|
logging: this.configService.get('app.nodeEnv') !== 'prod',
|
||||||
|
entities: [__dirname + '/../**/*.entity{.ts,.js}'],
|
||||||
|
migrations: [__dirname + '/migrations/**/*{.ts,.js}'],
|
||||||
|
cli: {
|
||||||
|
entitiesDir: 'src',
|
||||||
|
migrationsDir: 'src/database/migrations',
|
||||||
|
subscribersDir: 'subscriber',
|
||||||
|
},
|
||||||
|
} as TypeOrmModuleOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
myteamwallet_backend/src/files/entities/file.entity.ts
Normal file
30
myteamwallet_backend/src/files/entities/file.entity.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
AfterLoad,
|
||||||
|
AfterInsert,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { Allow } from 'class-validator';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import appConfig from '../../config/app.config';
|
||||||
|
|
||||||
|
@Entity({ name: 'file' })
|
||||||
|
export class FileEntity extends EntityHelper {
|
||||||
|
@ApiProperty({ example: 'cbcfa8b8-3a25-4adb-a9c6-e325f0d0f3ae' })
|
||||||
|
@PrimaryGeneratedColumn('uuid')
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
@Column()
|
||||||
|
path: string;
|
||||||
|
|
||||||
|
@AfterLoad()
|
||||||
|
@AfterInsert()
|
||||||
|
updatePath() {
|
||||||
|
if (this.path.indexOf('/') === 0) {
|
||||||
|
this.path = appConfig().backendDomain + this.path;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
48
myteamwallet_backend/src/files/files.controller.ts
Normal file
48
myteamwallet_backend/src/files/files.controller.ts
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Response,
|
||||||
|
UploadedFile,
|
||||||
|
UseGuards,
|
||||||
|
UseInterceptors,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { ApiBearerAuth, ApiBody, ApiConsumes, ApiTags } from '@nestjs/swagger';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { FilesService } from './files.service';
|
||||||
|
|
||||||
|
@ApiTags('Files')
|
||||||
|
@Controller({
|
||||||
|
path: 'files',
|
||||||
|
version: '1',
|
||||||
|
})
|
||||||
|
export class FilesController {
|
||||||
|
constructor(private readonly filesService: FilesService) {}
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(AuthGuard('jwt'))
|
||||||
|
@Post('upload')
|
||||||
|
@ApiConsumes('multipart/form-data')
|
||||||
|
@ApiBody({
|
||||||
|
schema: {
|
||||||
|
type: 'object',
|
||||||
|
properties: {
|
||||||
|
file: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'binary',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
@UseInterceptors(FileInterceptor('file'))
|
||||||
|
async uploadFile(@UploadedFile() file) {
|
||||||
|
return this.filesService.uploadFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':path')
|
||||||
|
download(@Param('path') path, @Response() response) {
|
||||||
|
return response.sendFile(path, { root: './files' });
|
||||||
|
}
|
||||||
|
}
|
||||||
90
myteamwallet_backend/src/files/files.module.ts
Normal file
90
myteamwallet_backend/src/files/files.module.ts
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
import { HttpException, HttpStatus, Module } from '@nestjs/common';
|
||||||
|
import { FilesController } from './files.controller';
|
||||||
|
import { MulterModule } from '@nestjs/platform-express';
|
||||||
|
import { ConfigModule, ConfigService } from '@nestjs/config';
|
||||||
|
import { diskStorage } from 'multer';
|
||||||
|
import { randomStringGenerator } from '@nestjs/common/utils/random-string-generator.util';
|
||||||
|
import * as AWS from 'aws-sdk';
|
||||||
|
import * as multerS3 from 'multer-s3';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { FileEntity } from './entities/file.entity';
|
||||||
|
import { FilesService } from './files.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([FileEntity]),
|
||||||
|
MulterModule.registerAsync({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
inject: [ConfigService],
|
||||||
|
useFactory: (configService: ConfigService) => {
|
||||||
|
const storages = {
|
||||||
|
local: () =>
|
||||||
|
diskStorage({
|
||||||
|
destination: './files',
|
||||||
|
filename: (request, file, callback) => {
|
||||||
|
callback(
|
||||||
|
null,
|
||||||
|
`${randomStringGenerator()}.${file.originalname
|
||||||
|
.split('.')
|
||||||
|
.pop()
|
||||||
|
.toLowerCase()}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
s3: () => {
|
||||||
|
const s3 = new AWS.S3();
|
||||||
|
AWS.config.update({
|
||||||
|
accessKeyId: configService.get('file.accessKeyId'),
|
||||||
|
secretAccessKey: configService.get('file.secretAccessKey'),
|
||||||
|
region: configService.get('file.awsS3Region'),
|
||||||
|
});
|
||||||
|
|
||||||
|
return multerS3({
|
||||||
|
s3: s3,
|
||||||
|
bucket: configService.get('file.awsDefaultS3Bucket'),
|
||||||
|
acl: 'public-read',
|
||||||
|
contentType: multerS3.AUTO_CONTENT_TYPE,
|
||||||
|
key: (request, file, callback) => {
|
||||||
|
callback(
|
||||||
|
null,
|
||||||
|
`${randomStringGenerator()}.${file.originalname
|
||||||
|
.split('.')
|
||||||
|
.pop()
|
||||||
|
.toLowerCase()}`,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileFilter: (request, file, callback) => {
|
||||||
|
if (!file.originalname.match(/\.(jpg|jpeg|png|gif)$/i)) {
|
||||||
|
return callback(
|
||||||
|
new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
file: `cantUploadFileType`,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
callback(null, true);
|
||||||
|
},
|
||||||
|
storage: storages[configService.get('file.driver')](),
|
||||||
|
limits: {
|
||||||
|
fileSize: configService.get('file.maxFileSize'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
controllers: [FilesController],
|
||||||
|
providers: [ConfigModule, ConfigService, FilesService],
|
||||||
|
})
|
||||||
|
export class FilesModule {}
|
||||||
39
myteamwallet_backend/src/files/files.service.ts
Normal file
39
myteamwallet_backend/src/files/files.service.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { FileEntity } from './entities/file.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class FilesService {
|
||||||
|
constructor(
|
||||||
|
private readonly configService: ConfigService,
|
||||||
|
@InjectRepository(FileEntity)
|
||||||
|
private fileRepository: Repository<FileEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async uploadFile(file): Promise<FileEntity> {
|
||||||
|
if (!file) {
|
||||||
|
throw new HttpException(
|
||||||
|
{
|
||||||
|
status: HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
errors: {
|
||||||
|
file: 'selectFile',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
HttpStatus.UNPROCESSABLE_ENTITY,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const path = {
|
||||||
|
local: `/${this.configService.get('app.apiPrefix')}/v1/${file.path}`,
|
||||||
|
s3: file.location,
|
||||||
|
};
|
||||||
|
|
||||||
|
return this.fileRepository.save(
|
||||||
|
this.fileRepository.create({
|
||||||
|
path: path[this.configService.get('file.driver')],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
35
myteamwallet_backend/src/forgot/entities/forgot.entity.ts
Normal file
35
myteamwallet_backend/src/forgot/entities/forgot.entity.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
Index,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
DeleteDateColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { User } from '../../users/entities/user.entity';
|
||||||
|
import { Allow } from 'class-validator';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Forgot extends EntityHelper {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
@Column()
|
||||||
|
@Index()
|
||||||
|
hash: string;
|
||||||
|
|
||||||
|
@Allow()
|
||||||
|
@ManyToOne(() => User, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
user: User;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
|
||||||
|
@DeleteDateColumn()
|
||||||
|
deletedAt: Date;
|
||||||
|
}
|
||||||
11
myteamwallet_backend/src/forgot/forgot.module.ts
Normal file
11
myteamwallet_backend/src/forgot/forgot.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { Forgot } from './entities/forgot.entity';
|
||||||
|
import { ForgotService } from './forgot.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Forgot])],
|
||||||
|
providers: [ForgotService],
|
||||||
|
exports: [ForgotService],
|
||||||
|
})
|
||||||
|
export class ForgotModule {}
|
||||||
34
myteamwallet_backend/src/forgot/forgot.service.ts
Normal file
34
myteamwallet_backend/src/forgot/forgot.service.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { DeepPartial } from 'src/utils/types/deep-partial.type';
|
||||||
|
import { FindOptions } from 'src/utils/types/find-options.type';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { Forgot } from './entities/forgot.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class ForgotService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Forgot)
|
||||||
|
private forgotRepository: Repository<Forgot>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async findOne(options: FindOptions<Forgot>) {
|
||||||
|
return this.forgotRepository.findOne({
|
||||||
|
where: options.where,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findMany(options: FindOptions<Forgot>) {
|
||||||
|
return this.forgotRepository.find({
|
||||||
|
where: options.where,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async create(data: DeepPartial<Forgot>) {
|
||||||
|
return this.forgotRepository.save(this.forgotRepository.create(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
async softDelete(id: number): Promise<void> {
|
||||||
|
await this.forgotRepository.softDelete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
15
myteamwallet_backend/src/home/home.controller.ts
Normal file
15
myteamwallet_backend/src/home/home.controller.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import { Controller, Get } from '@nestjs/common';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { HomeService } from './home.service';
|
||||||
|
|
||||||
|
@ApiTags('Home')
|
||||||
|
@Controller()
|
||||||
|
export class HomeController {
|
||||||
|
constructor(private service: HomeService) {}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
appInfo() {
|
||||||
|
return this.service.appInfo();
|
||||||
|
}
|
||||||
|
}
|
||||||
11
myteamwallet_backend/src/home/home.module.ts
Normal file
11
myteamwallet_backend/src/home/home.module.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { HomeService } from './home.service';
|
||||||
|
import { HomeController } from './home.controller';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
controllers: [HomeController],
|
||||||
|
providers: [HomeService],
|
||||||
|
})
|
||||||
|
export class HomeModule {}
|
||||||
11
myteamwallet_backend/src/home/home.service.ts
Normal file
11
myteamwallet_backend/src/home/home.service.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class HomeService {
|
||||||
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
|
appInfo() {
|
||||||
|
return { name: this.configService.get('app.name') };
|
||||||
|
}
|
||||||
|
}
|
||||||
4
myteamwallet_backend/src/i18n/en/common.json
Normal file
4
myteamwallet_backend/src/i18n/en/common.json
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
{
|
||||||
|
"confirmEmail": "Confirm email",
|
||||||
|
"resetPassword": "Reset password"
|
||||||
|
}
|
||||||
5
myteamwallet_backend/src/i18n/en/confirm-email.json
Normal file
5
myteamwallet_backend/src/i18n/en/confirm-email.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"text1": "Hey!",
|
||||||
|
"text2": "You’re almost ready to start enjoying",
|
||||||
|
"text3": "Simply click the big green button below to verify your email address."
|
||||||
|
}
|
||||||
6
myteamwallet_backend/src/i18n/en/reset-password.json
Normal file
6
myteamwallet_backend/src/i18n/en/reset-password.json
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"text1": "Trouble signing in?",
|
||||||
|
"text2": "Resetting your password is easy.",
|
||||||
|
"text3": "Just press the button below and follow the instructions. We’ll have you up and running in no time.",
|
||||||
|
"text4": "If you did not make this request then please ignore this email."
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface MailData<T = never> {
|
||||||
|
to: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
43
myteamwallet_backend/src/mail/mail-config.service.ts
Normal file
43
myteamwallet_backend/src/mail/mail-config.service.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import * as path from 'path';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { MailerOptions, MailerOptionsFactory } from '@nestjs-modules/mailer';
|
||||||
|
import { HandlebarsAdapter } from '@nestjs-modules/mailer/dist/adapters/handlebars.adapter';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailConfigService implements MailerOptionsFactory {
|
||||||
|
constructor(private configService: ConfigService) {}
|
||||||
|
|
||||||
|
createMailerOptions(): MailerOptions {
|
||||||
|
return {
|
||||||
|
transport: {
|
||||||
|
host: this.configService.get('mail.host'),
|
||||||
|
port: this.configService.get('mail.port'),
|
||||||
|
ignoreTLS: this.configService.get('mail.ignoreTLS'),
|
||||||
|
secure: this.configService.get('mail.secure'),
|
||||||
|
requireTLS: this.configService.get('mail.requireTLS'),
|
||||||
|
auth: {
|
||||||
|
user: this.configService.get('mail.user'),
|
||||||
|
pass: this.configService.get('mail.password'),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaults: {
|
||||||
|
from: `"${this.configService.get(
|
||||||
|
'mail.defaultName',
|
||||||
|
)}" <${this.configService.get('mail.defaultEmail')}>`,
|
||||||
|
},
|
||||||
|
template: {
|
||||||
|
dir: path.join(
|
||||||
|
this.configService.get('app.workingDirectory'),
|
||||||
|
'src',
|
||||||
|
'mail',
|
||||||
|
'mail-templates',
|
||||||
|
),
|
||||||
|
adapter: new HandlebarsAdapter(),
|
||||||
|
options: {
|
||||||
|
strict: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
} as MailerOptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
33
myteamwallet_backend/src/mail/mail-templates/activation.hbs
Normal file
33
myteamwallet_backend/src/mail/mail-templates/activation.hbs
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=">
|
||||||
|
<title>{{title}}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="margin:0;font-family:arial">
|
||||||
|
<table style="border:0;width:100%">
|
||||||
|
<tr style="background:#eeeeee">
|
||||||
|
<td style="padding:20px;color:#808080;text-align:center;font-size:40px;font-weight:600">
|
||||||
|
{{app_name}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:20px;color:#808080;font-size:16px;font-weight:100">
|
||||||
|
{{text1}}<br>
|
||||||
|
{{text2}} {{app_name}}.<br>
|
||||||
|
{{text3}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center">
|
||||||
|
<a href="{{url}}"
|
||||||
|
style="display:inline-block;padding:20px;background:#00838f;text-decoration:none;color:#ffffff">{{actionTitle}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=">
|
||||||
|
<title>{{title}}</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body style="margin:0;font-family:arial">
|
||||||
|
<table style="border:0;width:100%">
|
||||||
|
<tr style="background:#eeeeee">
|
||||||
|
<td style="padding:20px;color:#808080;text-align:center;font-size:40px;font-weight:600">
|
||||||
|
{{app_name}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:20px;color:#808080;font-size:16px;font-weight:100">
|
||||||
|
{{text1}}<br>
|
||||||
|
{{text2}}<br>
|
||||||
|
{{text3}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="text-align:center">
|
||||||
|
<a href="{{url}}"
|
||||||
|
style="display:inline-block;padding:20px;background:#00838f;text-decoration:none;color:#ffffff">{{actionTitle}}</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td style="padding:20px;color:#808080;font-size:16px;font-weight:100">
|
||||||
|
{{text4}}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
10
myteamwallet_backend/src/mail/mail.module.ts
Normal file
10
myteamwallet_backend/src/mail/mail.module.ts
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
import { MailService } from './mail.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [ConfigModule],
|
||||||
|
providers: [MailService],
|
||||||
|
exports: [MailService],
|
||||||
|
})
|
||||||
|
export class MailModule {}
|
||||||
62
myteamwallet_backend/src/mail/mail.service.ts
Normal file
62
myteamwallet_backend/src/mail/mail.service.ts
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
import { MailerService } from '@nestjs-modules/mailer';
|
||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { I18n, I18nRequestScopeService } from 'nestjs-i18n';
|
||||||
|
import { MailData } from './interfaces/mail-data.interface';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MailService {
|
||||||
|
constructor(
|
||||||
|
@I18n()
|
||||||
|
private i18n: I18nRequestScopeService,
|
||||||
|
private mailerService: MailerService,
|
||||||
|
private configService: ConfigService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async userSignUp(mailData: MailData<{ hash: string }>) {
|
||||||
|
return;
|
||||||
|
await this.mailerService.sendMail({
|
||||||
|
to: mailData.to,
|
||||||
|
subject: await this.i18n.t('common.confirmEmail'),
|
||||||
|
text: `${this.configService.get('app.frontendDomain')}/confirm-email/${
|
||||||
|
mailData.data.hash
|
||||||
|
} ${await this.i18n.t('common.confirmEmail')}`,
|
||||||
|
template: 'activation',
|
||||||
|
context: {
|
||||||
|
title: await this.i18n.t('common.confirmEmail'),
|
||||||
|
url: `${this.configService.get('app.frontendDomain')}/confirm-email/${
|
||||||
|
mailData.data.hash
|
||||||
|
}`,
|
||||||
|
actionTitle: await this.i18n.t('common.confirmEmail'),
|
||||||
|
app_name: this.configService.get('app.name'),
|
||||||
|
text1: await this.i18n.t('confirm-email.text1'),
|
||||||
|
text2: await this.i18n.t('confirm-email.text2'),
|
||||||
|
text3: await this.i18n.t('confirm-email.text3'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async forgotPassword(mailData: MailData<{ hash: string }>) {
|
||||||
|
return;
|
||||||
|
await this.mailerService.sendMail({
|
||||||
|
to: mailData.to,
|
||||||
|
subject: await this.i18n.t('common.resetPassword'),
|
||||||
|
text: `${this.configService.get('app.frontendDomain')}/password-change/${
|
||||||
|
mailData.data.hash
|
||||||
|
} ${await this.i18n.t('common.resetPassword')}`,
|
||||||
|
template: 'reset-password',
|
||||||
|
context: {
|
||||||
|
title: await this.i18n.t('common.resetPassword'),
|
||||||
|
url: `${this.configService.get('app.frontendDomain')}/password-change/${
|
||||||
|
mailData.data.hash
|
||||||
|
}`,
|
||||||
|
actionTitle: await this.i18n.t('common.resetPassword'),
|
||||||
|
app_name: this.configService.get('app.name'),
|
||||||
|
text1: await this.i18n.t('reset-password.text1'),
|
||||||
|
text2: await this.i18n.t('reset-password.text2'),
|
||||||
|
text3: await this.i18n.t('reset-password.text3'),
|
||||||
|
text4: await this.i18n.t('reset-password.text4'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
35
myteamwallet_backend/src/main.ts
Normal file
35
myteamwallet_backend/src/main.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
import { ValidationPipe, VersioningType } from '@nestjs/common';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { useContainer } from 'class-validator';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import validationOptions from './utils/validation-options';
|
||||||
|
|
||||||
|
async function bootstrap() {
|
||||||
|
const app = await NestFactory.create(AppModule, { cors: true });
|
||||||
|
useContainer(app.select(AppModule), { fallbackOnErrors: true });
|
||||||
|
const configService = app.get(ConfigService);
|
||||||
|
|
||||||
|
app.enableShutdownHooks();
|
||||||
|
app.setGlobalPrefix(configService.get('app.apiPrefix'), {
|
||||||
|
exclude: ['/'],
|
||||||
|
});
|
||||||
|
app.enableVersioning({
|
||||||
|
type: VersioningType.URI,
|
||||||
|
});
|
||||||
|
app.useGlobalPipes(new ValidationPipe(validationOptions));
|
||||||
|
|
||||||
|
const options = new DocumentBuilder()
|
||||||
|
.setTitle('API')
|
||||||
|
.setDescription('API docs')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addBearerAuth()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app, options);
|
||||||
|
SwaggerModule.setup('docs', app, document);
|
||||||
|
|
||||||
|
await app.listen(configService.get('app.port'));
|
||||||
|
}
|
||||||
|
void bootstrap();
|
||||||
19
myteamwallet_backend/src/penalty/dto/create-penalty.dto.ts
Normal file
19
myteamwallet_backend/src/penalty/dto/create-penalty.dto.ts
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { IsNotEmpty } from 'class-validator';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
|
||||||
|
export class CreatePenaltyDTO {
|
||||||
|
@ApiProperty({ example: 2342 })
|
||||||
|
@IsNotEmpty()
|
||||||
|
teamId: number;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 1 })
|
||||||
|
@IsNotEmpty()
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
team?: Team;
|
||||||
|
|
||||||
|
@ApiProperty({ example: 'Zu spät kommen' })
|
||||||
|
@IsNotEmpty()
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
29
myteamwallet_backend/src/penalty/entities/penalty.entity.ts
Normal file
29
myteamwallet_backend/src/penalty/entities/penalty.entity.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
Column,
|
||||||
|
CreateDateColumn,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class PenaltyEntity extends EntityHelper {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ManyToOne(() => Team, {
|
||||||
|
eager: false,
|
||||||
|
})
|
||||||
|
team: Team;
|
||||||
|
|
||||||
|
@Column({ default: '' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
amount: number;
|
||||||
|
|
||||||
|
@CreateDateColumn()
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
45
myteamwallet_backend/src/penalty/penalty.controller.ts
Normal file
45
myteamwallet_backend/src/penalty/penalty.controller.ts
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Param,
|
||||||
|
Post,
|
||||||
|
Req,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { AuthGuard } from '@nestjs/passport';
|
||||||
|
import { ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { Roles } from 'src/roles/roles.decorator';
|
||||||
|
import { RolesGuard } from 'src/roles/roles.guard';
|
||||||
|
import { CreatePenaltyDTO } from './dto/create-penalty.dto';
|
||||||
|
import { PenaltyService } from './penalty.service';
|
||||||
|
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller({
|
||||||
|
path: 'penalty',
|
||||||
|
version: '1',
|
||||||
|
})
|
||||||
|
export class PenaltyController {
|
||||||
|
constructor(private service: PenaltyService) {}
|
||||||
|
|
||||||
|
@Roles([])
|
||||||
|
@Get()
|
||||||
|
getIt(@Req() req: any) {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
return this.service.getAll(userId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Roles([])
|
||||||
|
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||||
|
@Get(':id')
|
||||||
|
getTeams(@Param('id') teamId: string) {
|
||||||
|
return this.service.getTeamPenalties(teamId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@UseGuards(AuthGuard('jwt'), RolesGuard)
|
||||||
|
@Post()
|
||||||
|
createPenalty(@Req() req: any, @Body() createPenaltyDto: CreatePenaltyDTO) {
|
||||||
|
const userId = req.user?.id;
|
||||||
|
return this.service.createPenalty(createPenaltyDto, userId);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
myteamwallet_backend/src/penalty/penalty.module.ts
Normal file
30
myteamwallet_backend/src/penalty/penalty.module.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { PenaltyController } from './penalty.controller';
|
||||||
|
import { PenaltyService } from './penalty.service';
|
||||||
|
import { LoggingModule } from 'src/database/logging/logging.module';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { TeamWalletTransactionType } from 'src/team-wallet-transactions/entities/team-wallet-transaction-type.entity';
|
||||||
|
import { TeamWalletTransaction } from 'src/team-wallet-transactions/entities/team-wallet-transaction.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Role } from 'src/roles/entities/role.entity';
|
||||||
|
import { PenaltyEntity } from './entities/penalty.entity';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
controllers: [PenaltyController],
|
||||||
|
providers: [PenaltyService],
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([
|
||||||
|
User,
|
||||||
|
Role,
|
||||||
|
TeamWalletTransaction,
|
||||||
|
TeamWalletTransactionType,
|
||||||
|
Team,
|
||||||
|
PenaltyEntity,
|
||||||
|
Player,
|
||||||
|
]),
|
||||||
|
LoggingModule,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class PenaltyModule {}
|
||||||
61
myteamwallet_backend/src/penalty/penalty.service.ts
Normal file
61
myteamwallet_backend/src/penalty/penalty.service.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { CreatePenaltyDTO } from './dto/create-penalty.dto';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { Repository } from 'typeorm';
|
||||||
|
import { PenaltyEntity } from './entities/penalty.entity';
|
||||||
|
import { Player } from 'src/players/entities/player.entity';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class PenaltyService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Team)
|
||||||
|
private teamRepository: Repository<Team>,
|
||||||
|
@InjectRepository(Player)
|
||||||
|
private playerRepository: Repository<Player>,
|
||||||
|
@InjectRepository(PenaltyEntity)
|
||||||
|
private repository: Repository<PenaltyEntity>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createPenalty(dto: CreatePenaltyDTO, userId: string) {
|
||||||
|
const player = await this.playerRepository.findOne({
|
||||||
|
where: { user: { id: Number(userId) }, team: { id: dto.teamId } },
|
||||||
|
relations: ['team'],
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!player || !player.team) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dto.team = player.team;
|
||||||
|
|
||||||
|
const e = this.repository.create(dto);
|
||||||
|
|
||||||
|
return this.repository.save(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll(userId: string) {
|
||||||
|
const id = Number(userId);
|
||||||
|
|
||||||
|
return this.repository.find({
|
||||||
|
where: {
|
||||||
|
team: { players: { user: { id } } },
|
||||||
|
},
|
||||||
|
relations: ['team'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTeamPenalties(teamId: string | number) {
|
||||||
|
teamId = Number(teamId);
|
||||||
|
|
||||||
|
const res = await this.repository.find({
|
||||||
|
where: {
|
||||||
|
team: { id: teamId },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return res.map((r) => {
|
||||||
|
r.amount = Number(r.amount);
|
||||||
|
return r;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
66
myteamwallet_backend/src/players/entities/player.entity.ts
Normal file
66
myteamwallet_backend/src/players/entities/player.entity.ts
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
import {
|
||||||
|
AfterLoad,
|
||||||
|
BeforeInsert,
|
||||||
|
Column,
|
||||||
|
Entity,
|
||||||
|
ManyToOne,
|
||||||
|
OneToMany,
|
||||||
|
PrimaryGeneratedColumn,
|
||||||
|
} from 'typeorm';
|
||||||
|
import { EntityHelper } from 'src/utils/entity-helper';
|
||||||
|
import { TeamRole } from 'src/team-roles/entities/team-roles.entity';
|
||||||
|
import { Team } from 'src/teams/entities/team.entity';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Transaction } from 'src/transactions/entitites/transaction.entity';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class Player extends EntityHelper {
|
||||||
|
@PrimaryGeneratedColumn()
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
firstName: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
lastName: string;
|
||||||
|
|
||||||
|
@ManyToOne(() => TeamRole, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
teamRole?: TeamRole | null;
|
||||||
|
|
||||||
|
@ManyToOne(() => Team, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
team: Team;
|
||||||
|
|
||||||
|
@Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
|
||||||
|
balance: number;
|
||||||
|
|
||||||
|
@ManyToOne(() => User, (user) => user.players, {
|
||||||
|
eager: true,
|
||||||
|
})
|
||||||
|
user?: User | null;
|
||||||
|
|
||||||
|
@OneToMany(() => Transaction, (transaction) => transaction.player)
|
||||||
|
transactions: Transaction[];
|
||||||
|
|
||||||
|
@AfterLoad()
|
||||||
|
updateValue() {
|
||||||
|
this.balance = Number(this.balance);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Column({ default: true })
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
@BeforeInsert()
|
||||||
|
prepareData() {
|
||||||
|
if (this.balance == null) {
|
||||||
|
this.balance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.transactions == null) {
|
||||||
|
this.transactions = [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
9
myteamwallet_backend/src/players/players.module.ts
Normal file
9
myteamwallet_backend/src/players/players.module.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { User } from 'src/users/entities/user.entity';
|
||||||
|
import { Player } from './entities/player.entity';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [TypeOrmModule.forFeature([Player, User])],
|
||||||
|
})
|
||||||
|
export class PlayersModule {}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user