Compare commits

...

9 Commits

Author SHA1 Message Date
louiscklaw
66b9bf5bdd "Update docker compose files to expose service ports as per REQ0180" 2025-06-13 18:56:56 +08:00
louiscklaw
35b1cd1eba update, 2025-06-13 18:35:58 +08:00
louiscklaw
4de7a564e3 fix production environment no zsh, 2025-06-13 18:26:01 +08:00
louiscklaw
08d6727dca update prod script, 2025-06-13 18:11:57 +08:00
louiscklaw
d767108fcf fix broken build, 2025-06-13 18:11:12 +08:00
louiscklaw
5ff3393f54 add missing lib for mobile, 2025-06-13 13:33:20 +08:00
louiscklaw
5a531e1288 update add lib, 2025-06-13 13:19:15 +08:00
louiscklaw
5b7cf25753 delete test directory for production, 2025-06-13 13:08:08 +08:00
louiscklaw
19b2357771 "Update .gitignore to exclude _del and _test directories" 2025-06-13 13:06:01 +08:00
4052 changed files with 399 additions and 446176 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,6 @@
_del
_test
04_poc
**/*del
**/*bak

View File

@@ -1,4 +0,0 @@
node_modules
.git
.gitignore
.DS_Store

View File

@@ -1,9 +0,0 @@
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

View File

@@ -1,25 +0,0 @@
# Port number
PORT=3000
# Postgres URL
DATABASE_URL="postgresql://postgres:secret@localhost:5432/mydb?schema=public"
# JWT
# JWT secret key
JWT_SECRET=thisisasamplesecret
# Number of minutes after which an access token expires
JWT_ACCESS_EXPIRATION_MINUTES=30
# Number of days after which a refresh token expires
JWT_REFRESH_EXPIRATION_DAYS=30
# Number of minutes after which a reset password token expires
JWT_RESET_PASSWORD_EXPIRATION_MINUTES=10
# Number of minutes after which a verify email token expires
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES=10
# SMTP configuration options for the email service
# For testing, you can use a fake SMTP service like Ethereal: https://ethereal.email/create
SMTP_HOST=email-server
SMTP_PORT=587
SMTP_USERNAME=email-server-username
SMTP_PASSWORD=email-server-password
EMAIL_FROM=support@yourapp.com

View File

@@ -1,2 +0,0 @@
node_modules
bin

View File

@@ -1,14 +0,0 @@
{
"parser": "@typescript-eslint/parser",
"extends": [
"plugin:@typescript-eslint/recommended",
"plugin:prettier/recommended"
],
"parserOptions": {
"ecmaVersion": 2018,
"sourceType": "module"
},
"rules": {
"@typescript-eslint/no-explicit-any": "off"
}
}

View File

@@ -1,21 +0,0 @@
# Dependencies
node_modules
# yarn error logs
yarn-error.log
# Environment varibales
.env*
!.env*.example
# Code coverage
coverage
# misc
.DS_Store
# Dev db data
db
# TypeScript
build

View File

@@ -1,3 +0,0 @@
{
"*.ts": "eslint"
}

View File

@@ -1,7 +0,0 @@
{
"semi": true,
"tabWidth": 2,
"printWidth": 100,
"singleQuote": true,
"trailingComma": "none"
}

View File

@@ -1,5 +0,0 @@
{
"recommendations": [
"prisma.prisma"
]
}

View File

@@ -1,3 +0,0 @@
{
"python.formatting.provider": "yapf"
}

View File

@@ -1,61 +0,0 @@
# Contributing to prisma-express-typescript-boilerplate
Thank you for your interest in contributing to the prisma-express-typescript-boilerplate project! Your contributions are valuable in making this project better for everyone. Before you start contributing, please take a moment to review the guidelines below.
## Getting Started
1. **Fork the Repository**: Start by forking the prisma-express-typescript-boilerplate repository to your own GitHub account. You can do this by clicking the "Fork" button on the repository page.
2. **Clone the Repository**: Clone your forked repository to your local machine using the following command:
```git clone https://github.com/your-username/prisma-express-typescript-boilerplate.git ```
3. **Install Dependencies**: Navigate to the cloned repository directory and install the project dependencies by running:
```npm install```
## Making Changes
1. **Create a Branch**: Create a new branch for your changes. Use a descriptive name that reflects the nature of your contribution. For example:
```git checkout -b my-contribution```
2. **Implement Your Changes**: Make the necessary modifications and improvements to the project. This could involve bug fixes, feature enhancements, documentation updates, or any other relevant changes.
3. **Test Your Changes**: Ensure that your modifications work as intended and don't introduce any regressions. Run the existing tests using:
```npm test```
If you're adding new features, consider writing tests to cover the new functionality.
4. **Commit Your Changes**: Once you're satisfied with your changes, commit them with a clear and descriptive commit message:
```git add .```
```git commit -m "Add my contribution```
5. **Push Your Changes**: Push your changes to your forked repository:
## Submitting a Pull Request
1. **Open a Pull Request**: Go to the main page of your forked repository on GitHub and switch to the branch you just pushed. Click on the "Compare & pull request" button next to the branch name.
2. **Provide a Description**: In the pull request description, clearly explain the purpose and details of your contribution. Reference any relevant issues or feature requests if applicable.
3. **Review Process**: The project maintainers will review your pull request, providing feedback or suggesting changes if necessary. Be open to discussion and iterate on your changes based on the feedback received.
4. **Merge Your Pull Request**: Once your pull request has been approved and passes the review process, it will be merged into the main repository. Congratulations on your successful contribution!
## Code of Conduct
Please note that the prisma-express-typescript-boilerplate project follows a Code of Conduct. Respectful and inclusive behavior is expected from all contributors. Familiarize yourself with the project's Code of Conduct to ensure a positive and welcoming environment for everyone.
## Getting Help
If you have any questions or need assistance during the contribution process, don't hesitate to reach out to the project maintainers by opening an issue on the repository. They will be happy to help you.
Thank you for your interest in contributing to the prisma-express-typescript-boilerplate project. Your contributions make a difference!

View File

@@ -1,15 +0,0 @@
FROM node:18-alpine3.16
RUN mkdir -p /usr/src/node-app && chown -R node:node /usr/src/node-app
WORKDIR /usr/src/node-app
COPY package.json yarn.lock ./
USER node
RUN yarn install --pure-lockfile
COPY --chown=node:node . .
EXPOSE 3000

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2022 Antonio Lázaro
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,370 +0,0 @@
# RESTful API Node Server Boilerplate
A boilerplate/starter project for quickly building RESTful APIs using [Node.js](https://nodejs.org), [TypeScript](https://www.typescriptlang.org), [Express](https://expressjs.com), and [Prisma](https://www.prisma.io).
This project is an adaptation of the project [RESTful API Node Server Boilerplate](https://github.com/hagopj13/node-express-boilerplate) using a [PostgreSQL](https://www.postgresql.org) database with [Prisma](https://www.prisma.io) ORM. Many of the files are just an adaptation to [TypeScript](https://www.typescriptlang.org) from the files of the previously mentioned project.
## Quick Start
Clone the repo:
```bash
git clone --depth 1 https://github.com/antonio-lazaro/prisma-express-typescript-boilerplate.git
cd prisma-express-typescript-boilerplate
npx rimraf ./.git
```
Install the dependencies:
```bash
yarn install
```
Set the environment variables:
```bash
cp .env.example .env
# open .env and modify the environment variables (if needed)
```
## Table of Contents
- [RESTful API Node Server Boilerplate](#restful-api-node-server-boilerplate)
- [Quick Start](#quick-start)
- [Table of Contents](#table-of-contents)
- [Features](#features)
- [Commands](#commands)
- [Environment Variables](#environment-variables)
- [Project Structure](#project-structure)
- [API Documentation](#api-documentation)
- [API Endpoints](#api-endpoints)
- [Error Handling](#error-handling)
- [Validation](#validation)
- [Authentication](#authentication)
- [Authorization](#authorization)
- [Logging](#logging)
- [Linting](#linting)
- [Contributing](#contributing)
- [Inspirations](#inspirations)
- [License](#license)
## Features
- **SQL database**: [PostgreSQL](https://www.postgresql.org) object data modeling using [Prisma](https://www.prisma.io) ORM
- **Authentication and authorization**: using [passport](http://www.passportjs.org)
- **Validation**: request data validation using [Joi](https://joi.dev)
- **Logging**: using [winston](https://github.com/winstonjs/winston) and [morgan](https://github.com/expressjs/morgan)
- `future` **Testing**: unit and integration tests using [Jest](https://jestjs.io)
- **Error handling**: centralized error handling mechanism
- **API documentation**: with [swagger-jsdoc](https://github.com/Surnet/swagger-jsdoc) and [swagger-ui-express](https://github.com/scottie1984/swagger-ui-express)
- **Process management**: advanced production process management using [PM2](https://pm2.keymetrics.io)
- **Dependency management**: with [Yarn](https://yarnpkg.com)
- **Environment variables**: using [dotenv](https://github.com/motdotla/dotenv) and [cross-env](https://github.com/kentcdodds/cross-env#readme)
- **Security**: set security HTTP headers using [helmet](https://helmetjs.github.io)
- **Santizing**: sanitize request data against xss and query injection
- **CORS**: Cross-Origin Resource-Sharing enabled using [cors](https://github.com/expressjs/cors)
- **Compression**: gzip compression with [compression](https://github.com/expressjs/compression)
- **Docker support**
- **Code coverage**: using [coveralls](https://coveralls.io)
- **Code quality**: with [Codacy](https://www.codacy.com)
- **Git hooks**: with [Husky](https://github.com/typicode/husky) and [lint-staged](https://github.com/okonet/lint-staged)
- **Linting**: with [ESLint](https://eslint.org) and [Prettier](https://prettier.io)
- **Editor config**: consistent editor configuration using [EditorConfig](https://editorconfig.org)
## Commands
Running locally:
```bash
yarn dev
```
Running in production:
```bash
yarn start
```
Testing:
```bash
# run all tests
yarn test
# run all tests in watch mode
yarn test:watch
# run test coverage
yarn coverage
```
Database:
```bash
# push changes to db
yarn db:push
# start prisma studio
yarn db:studio
```
Docker:
```bash
# run docker container in development mode
yarn docker:dev
# run docker container in production mode
yarn docker:prod
# run all tests in a docker container
yarn docker:test
# run docker container with PostgreSQL db
yarn docker:dev-db:start
# stop docker container with PostgreSQL db
yarn docker:dev-db:stop
```
Linting:
```bash
# run ESLint
yarn lint
# fix ESLint errors
yarn lint:fix
# run prettier
yarn prettier
# fix prettier errors
yarn prettier:fix
```
## Environment Variables
The environment variables can be found and modified in the `.env` file. They come with these default values:
```bash
# Port number
PORT=3000
# URL of the PostgreSQL database
DATABASE_URL=postgresql://postgres:secret@localhost:5432/mydb?schema=public
# JWT
# JWT secret key
JWT_SECRET=thisisasamplesecret
# Number of minutes after which an access token expires
JWT_ACCESS_EXPIRATION_MINUTES=30
# Number of days after which a refresh token expires
JWT_REFRESH_EXPIRATION_DAYS=30
# SMTP configuration options for the email service
# For testing, you can use a fake SMTP service like Ethereal: https://ethereal.email/create
SMTP_HOST=email-server
SMTP_PORT=587
SMTP_USERNAME=email-server-username
SMTP_PASSWORD=email-server-password
EMAIL_FROM=support@yourapp.com
```
## Project Structure
```
src\
|--config\ # Environment variables and configuration related things
|--controllers\ # Route controllers (controller layer)
|--docs\ # Swagger files
|--middlewares\ # Custom express middlewares
|--routes\ # Routes
|--services\ # Business logic (service layer)
|--utils\ # Utility classes and functions
|--validations\ # Request data validation schemas
|--app.js # Express app
|--index.js # App entry point
```
## API Documentation
To view the list of available APIs and their specifications, run the server and go to `http://localhost:3000/v1/docs` in your browser. This documentation page is automatically generated using the [swagger](https://swagger.io/) definitions written as comments in the route files.
### API Endpoints
List of available routes:
**Auth routes**:\
`POST /v1/auth/register` - register\
`POST /v1/auth/login` - login\
`POST /v1/auth/refresh-tokens` - refresh auth tokens\
`POST /v1/auth/forgot-password` - send reset password email\
`POST /v1/auth/reset-password` - reset password\
`POST /v1/auth/send-verification-email` - send verification email\
`POST /v1/auth/verify-email` - verify email
**User routes**:\
`POST /v1/users` - create a user\
`GET /v1/users` - get all users\
`GET /v1/users/:userId` - get user\
`PATCH /v1/users/:userId` - update user\
`DELETE /v1/users/:userId` - delete user
## Error Handling
The app has a centralized error handling mechanism.
Controllers should try to catch the errors and forward them to the error handling middleware (by calling `next(error)`). For convenience, you can also wrap the controller inside the catchAsync utility wrapper, which forwards the error.
```javascript
const catchAsync = require('../utils/catchAsync');
const controller = catchAsync(async (req, res) => {
// this error will be forwarded to the error handling middleware
throw new Error('Something wrong happened');
});
```
The error handling middleware sends an error response, which has the following format:
```json
{
"code": 404,
"message": "Not found"
}
```
When running in development mode, the error response also contains the error stack.
The app has a utility ApiError class to which you can attach a response code and a message, and then throw it from anywhere (catchAsync will catch it).
For example, if you are trying to get a user from the DB who is not found, and you want to send a 404 error, the code should look something like:
```javascript
const httpStatus = require('http-status');
const ApiError = require('../utils/ApiError');
const User = require('../models/User');
const getUser = async (userId) => {
const user = await User.findById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
};
```
## Validation
Request data is validated using [Joi](https://joi.dev/). Check the [documentation](https://joi.dev/api/) for more details on how to write Joi validation schemas.
The validation schemas are defined in the `src/validations` directory and are used in the routes by providing them as parameters to the `validate` middleware.
```javascript
const express = require('express');
const validate = require('../../middlewares/validate');
const userValidation = require('../../validations/user.validation');
const userController = require('../../controllers/user.controller');
const router = express.Router();
router.post('/users', validate(userValidation.createUser), userController.createUser);
```
## Authentication
To require authentication for certain routes, you can use the `auth` middleware.
```javascript
const express = require('express');
const auth = require('../../middlewares/auth');
const userController = require('../../controllers/user.controller');
const router = express.Router();
router.post('/users', auth(), userController.createUser);
```
These routes require a valid JWT access token in the Authorization request header using the Bearer schema. If the request does not contain a valid access token, an Unauthorized (401) error is thrown.
**Generating Access Tokens**:
An access token can be generated by making a successful call to the register (`POST /v1/auth/register`) or login (`POST /v1/auth/login`) endpoints. The response of these endpoints also contains refresh tokens (explained below).
An access token is valid for 30 minutes. You can modify this expiration time by changing the `JWT_ACCESS_EXPIRATION_MINUTES` environment variable in the .env file.
**Refreshing Access Tokens**:
After the access token expires, a new access token can be generated, by making a call to the refresh token endpoint (`POST /v1/auth/refresh-tokens`) and sending along a valid refresh token in the request body. This call returns a new access token and a new refresh token.
A refresh token is valid for 30 days. You can modify this expiration time by changing the `JWT_REFRESH_EXPIRATION_DAYS` environment variable in the .env file.
## Authorization
The `auth` middleware can also be used to require certain rights/permissions to access a route.
```javascript
const express = require('express');
const auth = require('../../middlewares/auth');
const userController = require('../../controllers/user.controller');
const router = express.Router();
router.post('/users', auth('manageUsers'), userController.createUser);
```
In the example above, an authenticated user can access this route only if that user has the `manageUsers` permission.
The permissions are role-based. You can view the permissions/rights of each role in the `src/config/roles.js` file.
If the user making the request does not have the required permissions to access this route, a Forbidden (403) error is thrown.
## Logging
Import the logger from `src/config/logger.js`. It is using the [Winston](https://github.com/winstonjs/winston) logging library.
Logging should be done according to the following severity levels (ascending order from most important to least important):
```javascript
const logger = require('<path to src>/config/logger');
logger.error('message'); // level 0
logger.warn('message'); // level 1
logger.info('message'); // level 2
logger.http('message'); // level 3
logger.verbose('message'); // level 4
logger.debug('message'); // level 5
```
In development mode, log messages of all severity levels will be printed to the console.
In production mode, only `info`, `warn`, and `error` logs will be printed to the console.\
It is up to the server (or process manager) to actually read them from the console and store them in log files.\
This app uses pm2 in production mode, which is already configured to store the logs in log files.
Note: API request information (request url, response code, timestamp, etc.) are also automatically logged (using [morgan](https://github.com/expressjs/morgan)).
## Linting
Linting is done using [ESLint](https://eslint.org/) and [Prettier](https://prettier.io).
In this app, ESLint is configured to follow the [Airbnb JavaScript style guide](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb-base) with some modifications. It also extends [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier) to turn off all rules that are unnecessary or might conflict with Prettier.
To modify the ESLint configuration, update the `.eslintrc.json` file. To modify the Prettier configuration, update the `.prettierrc.json` file.
To prevent a certain file or directory from being linted, add it to `.eslintignore` and `.prettierignore`.
To maintain a consistent coding style across different IDEs, the project contains `.editorconfig`
## Contributing
Contributions are more than welcome! Please check out the [contributing guide](CONTRIBUTING.md).
## Inspirations
- [RESTful API Node Server Boilerplate](https://github.com/hagopj13/node-express-boilerplate)
## License
[MIT](LICENSE)

View File

@@ -1,8 +0,0 @@
services:
node-app:
container_name: node-app-dev
# command: sleep infinity
command: yarn dev -L
ports:
- '3000:3000'
- '5555:5555'

View File

@@ -1,15 +0,0 @@
services:
db:
image: postgres
restart: always
container_name: postgresdb-util-dev
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=secret
ports:
- '5432:5432'
volumes:
- db:/var/lib/postgresql/data
volumes:
db:
driver: local

View File

@@ -1,15 +0,0 @@
services:
db:
image: postgres
restart: always
container_name: postgresdb-util-test
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=secret
ports:
- '5432:5432'
volumes:
- db:/var/lib/postgresql/data
volumes:
db:
driver: local

View File

@@ -1,5 +0,0 @@
services:
node-app:
container_name: node-app-prod
command: yarn start
restart: always

View File

@@ -1,4 +0,0 @@
services:
node-app:
container_name: node-app-test
command: yarn test

View File

@@ -1,46 +0,0 @@
volumes:
dbdata:
driver: local
networks:
node-network:
driver: bridge
services:
postgresdb:
image: postgres
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=secret
ports:
- '5432:5432'
volumes:
- dbdata:/var/lib/postgresql/data
networks:
- node-network
mailpit:
image: axllent/mailpit
ports:
- '1025:1025' # SMTP
- '8025:8025' # Web UI
environment:
MP_UI_AUTH: 'admin:password123' # Optional auth
MP_SMTP_AUTH: 'admin:password123'
MP_SMTP_AUTH_ALLOW_INSECURE: 'true'
networks:
- node-network
node-app:
build: .
image: node-app
ports:
- '3000:3000'
depends_on:
- postgresdb
- mailpit
volumes:
- .:/usr/src/node-app
networks:
- node-network

View File

@@ -1,15 +0,0 @@
{
"apps": [
{
"name": "app",
"script": "build/src/index.js",
"instances": 1,
"autorestart": true,
"watch": false,
"time": true,
"env": {
"NODE_ENV": "production"
}
}
]
}

View File

@@ -1,10 +0,0 @@
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
testEnvironmentOptions: {
NODE_ENV: 'test'
},
restoreMocks: true,
coveragePathIgnorePatterns: ['node_modules', 'src/config', 'src/app.ts', 'tests'],
coverageReporters: ['text', 'lcov', 'clover', 'html']
};

View File

@@ -1,115 +0,0 @@
{
"name": "prisma-express-typescript-boilerplate",
"version": "1.0.0",
"description": "REST API Boilerplate with Node JS, TypeScript, Express and Prisma",
"main": "src/index.ts",
"repository": "https://github.com/antonio-lazaro/prisma-express-typescript-boilerplate.git",
"scripts": {
"start": "yarn build && pm2 start ecosystem.config.json --no-daemon",
"dev": "cross-env NODE_ENV=development nodemon src/index.ts",
"test": "docker-compose -f docker-compose.only-db-test.yml up -d && yarn db:push && jest -i --colors --verbose --detectOpenHandles && docker-compose -f docker-compose.only-db-test.yml down",
"test:watch": "docker-compose -f docker-compose.only-db-test.yml up -d && yarn db:push && jest -i --watchAll && docker-compose -f docker-compose.only-db-test.yml down",
"lint": "eslint .",
"lint:fix": "eslint . --fix",
"prettier": "prettier --check **/*.ts",
"prettier:fix": "prettier --write **/*.ts",
"db:generate": "prisma generate",
"db:push": "prisma db push --force-reset",
"db:push:w": "npx nodemon --delay 5 --ext \"ts,tsx,prisma\" --exec \"yarn db:push && yarn seed && yarn db:studio\"",
"db:studio": "prisma studio",
"docker:prod": "docker-compose -f docker-compose.yml -f docker-compose.prod.yml up",
"docker:build": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml build",
"docker:dev": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml up -d",
"docker:bash": "docker-compose -f docker-compose.yml -f docker-compose.dev.yml exec -it node-app sh",
"docker:test": "docker-compose -f docker-compose.yml -f docker-compose.test.yml up",
"docker:dev-db:start": "docker-compose -f docker-compose.only-db-dev.yml up -d",
"docker:dev-db:stop": "docker-compose -f docker-compose.only-db-dev.yml down",
"prepare": "husky install",
"build": "rimraf build && tsc -p tsconfig.json",
"seed": "ts-node prisma/seed.ts",
"seed:w": "yarn nodemon --ext ts,tsx --exec \"yarn run seed\""
},
"keywords": [
"node",
"node.js",
"typescript",
"boilerplate",
"express",
"rest",
"api",
"prisma",
"postgresql",
"es6",
"es7",
"es8",
"es9",
"docker",
"passport",
"joi",
"eslint",
"prettier"
],
"author": "Antonio Lázaro",
"license": "ISC",
"devDependencies": {
"@faker-js/faker": "^7.6.0",
"@jest/globals": "^29.3.1",
"@types/compression": "^1.7.2",
"@types/cors": "^2.8.13",
"@types/express": "^4.17.14",
"@types/jest": "^29.2.5",
"@types/morgan": "^1.9.3",
"@types/node": "^18.11.13",
"@types/passport": "^1.0.11",
"@types/passport-jwt": "^3.0.7",
"@types/supertest": "^2.0.12",
"@types/swagger-jsdoc": "^6.0.1",
"@types/swagger-ui-express": "^4.1.3",
"@types/xss-filters": "^0.0.27",
"@typescript-eslint/eslint-plugin": "^5.46.1",
"@typescript-eslint/parser": "^5.46.1",
"cross-env": "^7.0.3",
"eslint": "^8.29.0",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-prettier": "^4.2.1",
"husky": "^8.0.2",
"jest": "^29.3.1",
"lint-staged": "^13.1.0",
"node-mocks-http": "^1.12.1",
"nodemon": "^2.0.20",
"prettier": "^2.8.1",
"prisma": "^4.10.1",
"supertest": "^6.3.3",
"swagger-jsdoc": "^6.2.5",
"swagger-ui-express": "^4.6.0",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "^4.9.4"
},
"dependencies": {
"@prisma/client": "^4.10.1",
"@types/bcryptjs": "^2.4.2",
"@types/nodemailer": "^6.4.7",
"bcryptjs": "^2.4.3",
"compression": "^1.7.4",
"cors": "^2.8.5",
"date-fns": "^4.1.0",
"dotenv": "^16.0.3",
"express": "^4.18.2",
"express-rate-limit": "^6.7.0",
"helmet": "^6.0.1",
"http-status": "^1.5.3",
"joi": "^17.7.0",
"moment": "^2.29.4",
"morgan": "^1.10.0",
"nodemailer": "^6.8.0",
"passport": "^0.6.0",
"passport-jwt": "^4.0.0",
"pm2": "^5.2.2",
"winston": "^3.8.2",
"xss-filters": "^1.2.7"
},
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,25 +0,0 @@
import { Event } from './seeds/Event';
import { Member } from './seeds/Member';
import { Order } from './seeds/Order';
import { superuserSeed } from './seeds/superuser';
import { userSeed } from './seeds/user';
import { ProductReview } from './seeds/productReview';
import { ProductItem } from './seeds/productItem';
import { Blog } from './seeds/blog';
import { Mail } from './seeds/mail';
// import { File } from './seeds/_files';
// import { Chat } from './seeds/chat';
(async () => {
await userSeed;
await superuserSeed;
await Member;
await Event;
await Order;
await ProductReview;
await ProductItem;
await Blog;
await Mail;
// await File;
// await Chat;
})();

View File

@@ -1,39 +0,0 @@
import { PrismaClient } from '@prisma/client';
import { format, parseISO } from 'date-fns';
const prisma = new PrismaClient();
async function event() {
for (let i = 0; i < 5; i++) {
const helloworldEvent = await prisma.event.upsert({
where: { id: i },
update: {},
create: {
eventDate: new Date(),
joinMembers: undefined,
title: 'event ' + i,
price: 123 + i,
currency: 'HKD',
duration_m: 480 - i,
ageBottom: 12 + i,
ageTop: 48 - i,
location: 'Hong Kong Island',
avatar: 'https://www.ionics.io/img/ionic-logo.png'
}
});
}
console.log('seed event done');
}
const Event = event()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Event };

View File

@@ -1,46 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function member() {
for (let i = 0; i < 100; i++) {
const john = await prisma.member.upsert({
where: { email: `member${i}@example.com` },
update: {},
create: {
email: `member${i}@example.com`,
name: `member_${i}`,
age: 20 + i,
rank: i % 2 ? 'VIP' : 'NON_VIP',
verified: i % 3 ? 'NOT_VERIFIED' : 'VERIFIED',
hobbies: ['fishing', 'basketball', 'piano'],
distance: '40km',
location_area: 'Sai Kung',
greetings: 'Hi, I am ',
gender: 'man',
tall_cm: 172,
weight_kg: 60,
occupation: 'doctor',
language: ['English', 'French', 'Chinese'],
education: ['Degree of Computer'],
self_introduction: 'Get me know me before you love me. Get me know me before you love me.',
music: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
pets: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic'],
character: ['Classic', 'Classic', 'Classic', 'Classic', 'Classic', 'Classic']
}
});
}
console.log('seed member done');
}
const Member = member()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Member };

View File

@@ -1,31 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function order() {
for (let i = 0; i < 3; i++) {
const temp = await prisma.order.upsert({
where: { id: i },
update: {},
create: {
title: 'Single Party with Dating',
order_time: new Date(),
last_payment_date: new Date(),
status: 'Pending'
}
});
}
console.log('seed order done');
}
const Order = order()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Order };

View File

@@ -1,49 +0,0 @@
// src/_mock/_files.ts
//
import { _mock } from './_mock';
import { _fileNames } from './assets';
// ----------------------------------------------------------------------
const GB = 1000000000 * 24;
const URLS = [
_mock.image.cover(2),
'https://www.cloud.com/s/c218bo6kjuqyv66/design_suriname_2015.mp3',
'https://www.cloud.com/s/c218bo6kjuqyv66/expertise_2015_conakry_sao-tome-and-principe_gender.mp4',
'https://www.cloud.com/s/c218bo6kjuqyv66/money-popup-crack.pdf',
_mock.image.cover(3),
_mock.image.cover(5),
'https://www.cloud.com/s/c218bo6kjuqyv66/large_news.txt',
'https://www.cloud.com/s/c218bo6kjuqyv66/nauru-6015-small-fighter-left-gender.psd',
'https://www.cloud.com/s/c218bo6kjuqyv66/tv-xs.doc',
'https://www.cloud.com/s/c218bo6kjuqyv66/gustavia-entertainment-productivity.docx',
'https://www.cloud.com/s/c218bo6kjuqyv66/vintage_bahrain_saipan.xls',
'https://www.cloud.com/s/c218bo6kjuqyv66/indonesia-quito-nancy-grace-left-glad.xlsx',
'https://www.cloud.com/s/c218bo6kjuqyv66/legislation-grain.zip',
'https://www.cloud.com/s/c218bo6kjuqyv66/large_energy_dry_philippines.rar',
'https://www.cloud.com/s/c218bo6kjuqyv66/footer-243-ecuador.iso',
'https://www.cloud.com/s/c218bo6kjuqyv66/kyrgyzstan-04795009-picabo-street-guide-style.ai',
'https://www.cloud.com/s/c218bo6kjuqyv66/india-data-large-gk-chesterton-mother.esp',
'https://www.cloud.com/s/c218bo6kjuqyv66/footer-barbados-celine-dion.ppt',
'https://www.cloud.com/s/c218bo6kjuqyv66/socio_respectively_366996.pptx',
'https://www.cloud.com/s/c218bo6kjuqyv66/socio_ahead_531437_sweden_popup.wav',
'https://www.cloud.com/s/c218bo6kjuqyv66/trinidad_samuel-morse_bring.m4v',
_mock.image.cover(11),
_mock.image.cover(17),
'https://www.cloud.com/s/c218bo6kjuqyv66/xl_david-blaine_component_tanzania_books.pdf'
];
// ----------------------------------------------------------------------
export const _files = () =>
_fileNames.map((name, index) => ({
id: _mock.id(index),
name,
path: URLS[index],
preview: URLS[index],
size: GB / ((index + 1) * 500),
createdAt: _mock.time(index),
modifiedAt: _mock.time(index),
type: `${name.split('.').pop()}`
}));

View File

@@ -1,88 +0,0 @@
import { fSub } from './utils/set-date';
import { CONFIG } from './global-config';
import {
_id,
_ages,
_roles,
_prices,
_emails,
_ratings,
_nativeS,
_nativeM,
_nativeL,
_percents,
_booleans,
_sentences,
_lastNames,
_fullNames,
_tourNames,
_jobTitles,
_taskNames,
_fileNames,
_postTitles,
_firstNames,
_eventNames,
_courseNames,
_fullAddress,
_companyNames,
_productNames,
_descriptions,
_phoneNumbers,
_countryNames
} from './assets';
// ----------------------------------------------------------------------
export const _mock = {
id: (index: number) => _id[index],
// time: (index: number) => `2024-06-${(index + 1).toString().padStart(2, '0')}T23:00:00.000Z`,
time: (index: number) => fSub({ days: index, hours: index }),
boolean: (index: number) => _booleans[index],
role: (index: number) => _roles[index],
// Text
courseNames: (index: number) => _courseNames[index],
fileNames: (index: number) => _fileNames[index],
eventNames: (index: number) => _eventNames[index],
taskNames: (index: number) => _taskNames[index],
postTitle: (index: number) => _postTitles[index],
jobTitle: (index: number) => _jobTitles[index],
tourName: (index: number) => _tourNames[index],
productName: (index: number) => _productNames[index],
sentence: (index: number) => _sentences[index],
description: (index: number) => _descriptions[index],
// Contact
email: (index: number) => _emails[index],
phoneNumber: (index: number) => _phoneNumbers[index],
fullAddress: (index: number) => _fullAddress[index],
// Name
firstName: (index: number) => _firstNames[index],
lastName: (index: number) => _lastNames[index],
fullName: (index: number) => _fullNames[index],
companyNames: (index: number) => _companyNames[index],
countryNames: (index: number) => _countryNames[index],
// Number
number: {
percent: (index: number) => _percents[index],
rating: (index: number) => _ratings[index],
age: (index: number) => _ages[index],
price: (index: number) => _prices[index],
nativeS: (index: number) => _nativeS[index],
nativeM: (index: number) => _nativeM[index],
nativeL: (index: number) => _nativeL[index]
},
// Image
image: {
cover: (index: number) => `${CONFIG.basePath}/assets/images/cover/cover-${index + 1}.webp`,
avatar: (index: number) => `${CONFIG.basePath}/assets/images/avatar/avatar-${index + 1}.webp`,
travel: (index: number) => `${CONFIG.basePath}/assets/images/travel/travel-${index + 1}.webp`,
course: (index: number) => `${CONFIG.basePath}/assets/images/course/course-${index + 1}.webp`,
company: (index: number) =>
`${CONFIG.basePath}/assets/images/company/company-${index + 1}.webp`,
product: (index: number) =>
`${CONFIG.basePath}/assets/images/m-product/product-${index + 1}.webp`,
portrait: (index: number) =>
`${CONFIG.basePath}/assets/images/portrait/portrait-${index + 1}.webp`
}
};

View File

@@ -1,674 +0,0 @@
// ----------------------------------------------------------------------
export const _id = Array.from(
{ length: 40 },
(_, index) => `e99f09a7-dd88-49d5-b1c8-1daf80c2d7b${index + 1}`
);
// ----------------------------------------------------------------------
export const _booleans = [
true,
true,
true,
false,
false,
true,
false,
false,
false,
false,
true,
true,
true,
false,
false,
false,
true,
false,
false,
false,
true,
false,
false,
true,
];
// ----------------------------------------------------------------------
export const _prices = [
83.74, 97.14, 68.71, 85.21, 52.17, 25.18, 43.84, 60.98, 98.42, 53.37, 72.75, 56.61, 64.55, 77.32,
60.62, 79.81, 93.68, 47.44, 76.24, 92.87, 72.91, 20.54, 94.25, 37.51,
];
export const _ratings = [
4.2, 3.7, 4.5, 3.5, 0.5, 3.0, 2.5, 2.8, 4.9, 3.6, 2.5, 1.7, 3.9, 2.8, 4.1, 4.5, 2.2, 3.2, 0.6,
1.3, 3.8, 3.8, 3.8, 2.0,
];
export const _ages = [
30, 26, 59, 47, 29, 46, 18, 56, 39, 19, 45, 18, 46, 56, 38, 41, 44, 48, 32, 45, 42, 60, 33, 57,
];
export const _percents = [
10.1, 13.6, 28.2, 42.1, 37.2, 18.5, 40.1, 94.8, 91.4, 53.0, 25.4, 62.9, 86.6, 62.4, 35.4, 17.6,
52.0, 6.8, 95.3, 26.6, 69.9, 92.1, 46.2, 85.6,
];
export const _nativeS = [
11, 10, 7, 10, 12, 5, 10, 1, 8, 8, 10, 11, 12, 8, 4, 11, 8, 9, 4, 9, 2, 6, 3, 7,
];
export const _nativeM = [
497, 763, 684, 451, 433, 463, 951, 194, 425, 435, 807, 521, 538, 839, 394, 269, 453, 821, 364,
849, 804, 776, 263, 239,
];
export const _nativeL = [
9911, 1947, 9124, 6984, 8488, 2034, 3364, 8401, 8996, 5271, 8478, 1139, 8061, 3035, 6733, 3952,
2405, 3127, 6843, 4672, 6995, 6053, 5192, 9686,
];
export const _fullAddress = [
`19034 Verna Unions Apt. 164 - Honolulu, RI / 87535`,
`1147 Rohan Drive Suite 819 - Burlington, VT / 82021`,
`18605 Thompson Circle Apt. 086 - Idaho Falls, WV / 50337`,
`110 Lamar Station Apt. 730 - Hagerstown, OK / 49808`,
`36901 Elmer Spurs Apt. 762 - Miramar, DE / 92836`,
`2089 Runolfsson Harbors Suite 886 - Chapel Hill, TX / 32827`,
`279 Karolann Ports Apt. 774 - Prescott Valley, WV / 53905`,
`96607 Claire Square Suite 591 - St. Louis Park, HI / 40802`,
`9388 Auer Station Suite 573 - Honolulu, AK / 98024`,
`47665 Adaline Squares Suite 510 - Blacksburg, NE / 53515`,
`989 Vernice Flats Apt. 183 - Billings, NV / 04147`,
`91020 Wehner Locks Apt. 673 - Albany, WY / 68763`,
`585 Candelario Pass Suite 090 - Columbus, LA / 25376`,
`80988 Renner Crest Apt. 000 - Fargo, VA / 24266`,
`28307 Shayne Pike Suite 523 - North Las Vegas, AZ / 28550`,
`205 Farrell Highway Suite 333 - Rock Hill, OK / 63421`,
`253 Kara Motorway Suite 821 - Manchester, SD / 09331`,
`13663 Kiara Oval Suite 606 - Missoula, AR / 44478`,
`8110 Claire Port Apt. 703 - Anchorage, TN / 01753`,
`4642 Demetris Lane Suite 407 - Edmond, AZ / 60888`,
`74794 Asha Flat Suite 890 - Lancaster, OR / 13466`,
`8135 Keeling Pines Apt. 326 - Alexandria, MA / 89442`,
`441 Gibson Shores Suite 247 - Pasco, NM / 60678`,
`4373 Emelia Valley Suite 596 - Columbia, NM / 42586`,
];
// ----------------------------------------------------------------------
export const _emails = [
`nannie.abernathy70@yahoo.com`,
`ashlynn.ohara62@gmail.com`,
`milo.farrell@hotmail.com`,
`violet.ratke86@yahoo.com`,
`letha.lubowitz24@yahoo.com`,
`aditya.greenfelder31@gmail.com`,
`lenna.bergnaum27@hotmail.com`,
`luella.ryan33@gmail.com`,
`joana.simonis84@gmail.com`,
`marjolaine.white94@gmail.com`,
`vergie.block82@hotmail.com`,
`vito.hudson@hotmail.com`,
`tyrel.greenholt@gmail.com`,
`dwight.block85@yahoo.com`,
`mireya13@hotmail.com`,
`dasia.jenkins@hotmail.com`,
`benny89@yahoo.com`,
`dawn.goyette@gmail.com`,
`zella.hickle4@yahoo.com`,
`avery43@hotmail.com`,
`olen.legros@gmail.com`,
`jimmie.gerhold73@hotmail.com`,
`genevieve.powlowski@hotmail.com`,
`louie.kuphal39@gmail.com`,
];
// ----------------------------------------------------------------------
export const _fullNames = [
`Jayvion Simon`,
`Lucian Obrien`,
`Deja Brady`,
`Harrison Stein`,
`Reece Chung`,
`Lainey Davidson`,
`Cristopher Cardenas`,
`Melanie Noble`,
`Chase Day`,
`Shawn Manning`,
`Soren Durham`,
`Cortez Herring`,
`Brycen Jimenez`,
`Giana Brandt`,
`Aspen Schmitt`,
`Colten Aguilar`,
`Angelique Morse`,
`Selina Boyer`,
`Lawson Bass`,
`Ariana Lang`,
`Amiah Pruitt`,
`Harold Mcgrath`,
`Esperanza Mcintyre`,
`Mireya Conner`,
];
export const _firstNames = [
`Mossie`,
`David`,
`Ebba`,
`Chester`,
`Eula`,
`Jaren`,
`Boyd`,
`Brady`,
`Aida`,
`Anastasia`,
`Gregoria`,
`Julianne`,
`Ila`,
`Elyssa`,
`Lucio`,
`Lewis`,
`Jacinthe`,
`Molly`,
`Brown`,
`Fritz`,
`Keon`,
`Ella`,
`Ken`,
`Whitney`,
];
export const _lastNames = [
`Carroll`,
`Simonis`,
`Yost`,
`Hand`,
`Emmerich`,
`Wilderman`,
`Howell`,
`Sporer`,
`Boehm`,
`Morar`,
`Koch`,
`Reynolds`,
`Padberg`,
`Watsica`,
`Upton`,
`Yundt`,
`Pfeffer`,
`Parker`,
`Zulauf`,
`Treutel`,
`McDermott`,
`McDermott`,
`Cruickshank`,
`Parisian`,
];
// ----------------------------------------------------------------------
export const _phoneNumbers = [
'+1 202-555-0143',
'+1 416-555-0198',
'+44 20 7946 0958',
'+61 2 9876 5432',
'+91 22 1234 5678',
'+49 30 123456',
'+33 1 23456789',
'+81 3 1234 5678',
'+86 10 1234 5678',
'+55 11 2345-6789',
'+27 11 123 4567',
'+7 495 123-4567',
'+52 55 1234 5678',
'+39 06 123 4567',
'+34 91 123 4567',
'+31 20 123 4567',
'+46 8 123 456',
'+41 22 123 45 67',
'+82 2 123 4567',
'+54 11 1234-5678',
'+64 9 123 4567',
'+65 1234 5678',
'+60 3-1234 5678',
'+66 2 123 4567',
'+62 21 123 4567',
'+63 2 123 4567',
'+90 212 123 45 67',
'+966 11 123 4567',
'+971 2 123 4567',
'+20 2 12345678',
'+234 1 123 4567',
'+254 20 123 4567',
'+972 3-123-4567',
'+30 21 1234 5678',
'+353 1 123 4567',
'+351 21 123 4567',
'+47 21 23 45 67',
'+45 32 12 34 56',
'+358 9 123 4567',
'+48 22 123 45 67',
];
// ----------------------------------------------------------------------
export const _countryNames = [
'United States',
'Canada',
'United Kingdom',
'Australia',
'India',
'Germany',
'France',
'Japan',
'China',
'Brazil',
'South Africa',
'Russia',
'Mexico',
'Italy',
'Spain',
'Netherlands',
'Sweden',
'Switzerland',
'South Korea',
'Argentina',
'New Zealand',
'Singapore',
'Malaysia',
'Thailand',
'Indonesia',
'Philippines',
'Turkey',
'Saudi Arabia',
'United Arab Emirates',
'Egypt',
'Nigeria',
'Kenya',
'Israel',
'Greece',
'Ireland',
'Portugal',
'Norway',
'Denmark',
'Finland',
'Poland',
];
// ----------------------------------------------------------------------
export const _roles = [
`CEO`,
`CTO`,
`Project Coordinator`,
`Team Leader`,
`Software Developer`,
`Marketing Strategist`,
`Data Analyst`,
`Product Owner`,
`Graphic Designer`,
`Operations Manager`,
`Customer Support Specialist`,
`Sales Manager`,
`HR Recruiter`,
`Business Consultant`,
`Financial Planner`,
`Network Engineer`,
`Content Creator`,
`Quality Assurance Tester`,
`Public Relations Officer`,
`IT Administrator`,
`Compliance Officer`,
`Event Planner`,
`Legal Counsel`,
`Training Coordinator`,
];
// ----------------------------------------------------------------------
export const _postTitles = [
`The Future of Renewable Energy: Innovations and Challenges Ahead`,
`Exploring the Impact of Artificial Intelligence on Modern Healthcare`,
`Climate Change and Its Effects on Global Food Security`,
`The Rise of Remote Work: Benefits, Challenges, and Future Trends`,
`Understanding Blockchain Technology: Beyond Cryptocurrency`,
`Mental Health in the Digital Age: Navigating Social Media and Well-being`,
`Sustainable Fashion: How the Industry is Going Green`,
`Space Exploration: New Frontiers and the Quest for Extraterrestrial Life`,
`The Evolution of E-Commerce: Trends Shaping the Online Retail Landscape`,
`Cybersecurity in the 21st Century: Protecting Data in a Digital World`,
`The Role of Big Data in Transforming Business Strategies`,
`Genetic Engineering: Ethical Considerations and Future Prospects`,
`Urban Farming: A Solution to Food Deserts and Urban Sustainability`,
`The Psychology of Consumer Behavior: What Drives Our Purchasing Decisions?`,
`Renewable Energy vs. Fossil Fuels: Which is the Future?`,
`Artificial Intelligence in Education: Enhancing Learning Experiences`,
`The Impact of Climate Change on Global Migration Patterns`,
`5G Technology: Revolutionizing Connectivity and Communication`,
`The Gig Economy: Opportunities, Risks, and the Future of Work`,
`Smart Cities: Integrating Technology for Sustainable Urban Living`,
`The Influence of Pop Culture on Modern Society`,
`Innovations in Medicine: From Telehealth to Personalized Treatment`,
`The Environmental Cost of Fast Fashion: What Can Consumers Do?`,
`The Intersection of Art and Technology: Digital Art in the 21st Century`,
];
// ----------------------------------------------------------------------
export const _productNames = [
`Urban Explorer Sneakers`,
`Classic Leather Loafers`,
`Mountain Trekking Boots`,
`Elegance Stiletto Heels`,
`Comfy Running Shoes`,
`Chic Ballet Flats`,
`Vintage Oxford Shoes`,
`Waterproof Hiking Boots`,
`Casual Slip-On Sneakers`,
`Premium Dress Shoes`,
`Sporty Trail Runners`,
`Sophisticated Brogues`,
`Beach Sandals`,
`Stylish Wedge Heels`,
`Lightweight Training Shoes`,
`Luxurious Moccasins`,
`Durable Work Boots`,
`Trendy Platform Sneakers`,
`Cozy Winter Boots`,
`Fashion Ankle Boots`,
`Breathable Tennis Shoes`,
`Elegant Evening Pumps`,
`Modern Skate Shoes`,
`Comfortable Walking Shoes`,
];
// ----------------------------------------------------------------------
export const _tourNames = [
`Majestic Mountain Adventures`,
`Island Hopping Extravaganza`,
`Cultural Wonders of Europe`,
`Safari Expedition in Africa`,
`Grand Canyon Explorer`,
`Historic Cities of Asia`,
`Tropical Paradise Getaway`,
`Alaskan Wilderness Tour`,
`Mediterranean Cruise Voyage`,
`Enchanting Eastern Europe`,
`Scenic Coastal Road Trip`,
`Ancient Ruins Discovery`,
`Australian Outback Adventure`,
`Northern Lights Experience`,
`Wildlife Wonders of South America`,
`Royal Castles and Palaces`,
`Ultimate Beach Retreat`,
`National Parks Exploration`,
`Gastronomic Tour of Italy`,
`Hiking Trails of New Zealand`,
`Art and History of France`,
`Exotic Temples of India`,
`Canadian Rockies Journey`,
`Caribbean Sun and Fun`,
];
// ----------------------------------------------------------------------
export const _jobTitles = [
`Software Engineer`,
`Marketing Manager`,
`Data Scientist`,
`Graphic Designer`,
`Financial Analyst`,
`Human Resources Specialist`,
`Project Manager`,
`Sales Executive`,
`Content Writer`,
`Network Administrator`,
`Customer Service Representative`,
`Product Manager`,
`Business Analyst`,
`Mechanical Engineer`,
`Operations Manager`,
`UX/UI Designer`,
`Accountant`,
`Social Media Manager`,
`Research Scientist`,
`Legal Advisor`,
`Public Relations Specialist`,
`Health and Safety Officer`,
`IT Support Specialist`,
`Environmental Consultant`,
];
// ----------------------------------------------------------------------
export const _companyNames = [
`Lueilwitz and Sons`,
`Gleichner, Mueller and Tromp`,
`Nikolaus - Leuschke`,
`Hegmann, Kreiger and Bayer`,
`Grimes Inc`,
`Durgan - Murazik`,
`Altenwerth, Medhurst and Roberts`,
`Raynor Group`,
`Mraz, Donnelly and Collins`,
`Padberg - Bailey`,
`Heidenreich, Stokes and Parker`,
`Pagac and Sons`,
`Rempel, Hand and Herzog`,
`Dare - Treutel`,
`Kihn, Marquardt and Crist`,
`Nolan - Kunde`,
`Wuckert Inc`,
`Dibbert Inc`,
`Goyette and Sons`,
`Feest Group`,
`Bosco and Sons`,
`Bartell - Kovacek`,
`Schimmel - Raynor`,
`Tremblay LLC`,
];
// ----------------------------------------------------------------------
export const _tags = [
`Technology`,
`Health and Wellness`,
`Travel`,
`Finance`,
`Education`,
`Food and Beverage`,
`Fashion`,
`Home and Garden`,
`Sports`,
`Entertainment`,
`Business`,
`Science`,
`Automotive`,
`Beauty`,
`Fitness`,
`Lifestyle`,
`Real Estate`,
`Parenting`,
`Pet Care`,
`Environmental`,
`DIY and Crafts`,
`Gaming`,
`Photography`,
`Music`,
];
// ----------------------------------------------------------------------
export const _taskNames = [
`Prepare Monthly Financial Report`,
`Design New Marketing Campaign`,
`Analyze Customer Feedback`,
`Update Website Content`,
`Conduct Market Research`,
`Develop Software Application`,
`Organize Team Meeting`,
`Create Social Media Posts`,
`Review Project Plan`,
`Implement Security Protocols`,
`Write Technical Documentation`,
`Test New Product Features`,
`Manage Client Inquiries`,
`Train New Employees`,
`Coordinate Logistics`,
`Monitor Network Performance`,
`Develop Training Materials`,
`Draft Press Release`,
`Prepare Budget Proposal`,
`Evaluate Vendor Proposals`,
`Perform Data Analysis`,
`Conduct Quality Assurance`,
`Plan Event Logistics`,
`Optimize SEO Strategies`,
];
// ----------------------------------------------------------------------
export const _courseNames = [
`Introduction to Python Programming`,
`Digital Marketing Fundamentals`,
`Data Science with R`,
`Graphic Design Essentials`,
`Financial Planning for Beginners`,
`Human Resource Management Basics`,
`Project Management Fundamentals`,
`Sales Techniques and Strategies`,
`Content Writing Mastery`,
`Network Security Fundamentals`,
`Customer Service Excellence`,
`Product Management Essentials`,
`Business Analytics with Excel`,
`Mechanical Engineering Principles`,
`Leadership and Team Management`,
`User Experience (UX) Design Basics`,
`Accounting Fundamentals`,
`Social Media Marketing Mastery`,
`Biotechnology Essentials`,
`Legal Studies for Non-Lawyers`,
`Public Speaking Confidence`,
`Health and Wellness Coaching`,
`Web Development Bootcamp`,
`Photography Masterclass`,
];
// ----------------------------------------------------------------------
export const _fileNames = [
'cover-2.jpg',
'design-suriname-2015.mp3',
'expertise-2015-conakry-sao-tome-and-principe-gender.mp4',
'money-popup-crack.pdf',
'cover-4.jpg',
'cover-6.jpg',
'large-news.txt',
'nauru-6015-small-fighter-left-gender.psd',
'tv-xs.doc',
'gustavia-entertainment-productivity.docx',
'vintage-bahrain-saipan.xls',
'indonesia-quito-nancy-grace-left-glad.xlsx',
'legislation-grain.zip',
'large-energy-dry-philippines.rar',
'footer-243-ecuador.iso',
'kyrgyzstan-04795009-picabo-street-guide-style.ai',
'india-data-large-gk-chesterton-mother.esp',
'footer-barbados-celine-dion.ppt',
'socio-respectively-366996.pptx',
'socio-ahead-531437-sweden-popup.wav',
'trinidad-samuel-morse-bring.m4v',
'cover-12.jpg',
'cover-18.jpg',
'xl-david-blaine-component-tanzania-books.pdf',
];
export const _eventNames = [
`Annual General Meeting`,
`Summer Music Festival`,
`Tech Innovators Conference`,
`Charity Gala Dinner`,
`Spring Art Exhibition`,
`Corporate Training Workshop`,
`Community Health Fair`,
`Startup Pitch Night`,
`Regional Sports Tournament`,
`Book Launch Event`,
`Film Premiere Screening`,
`Industry Networking Mixer`,
`Holiday Craft Fair`,
`Environmental Awareness Week`,
`New Year's Eve Party`,
`Product Release Showcase`,
`Cultural Heritage Festival`,
`Science and Technology Expo`,
`Annual Awards Ceremony`,
`Fashion Week Runway Show`,
`Food and Wine Tasting`,
`Outdoor Adventure Camp`,
`Leadership Summit`,
`Wedding Expo`,
];
// ----------------------------------------------------------------------
export const _sentences = [
`The sun slowly set over the horizon, painting the sky in vibrant hues of orange and pink.`,
`She eagerly opened the gift, her eyes sparkling with excitement.`,
`The old oak tree stood tall and majestic, its branches swaying gently in the breeze.`,
`The aroma of freshly brewed coffee filled the air, awakening my senses.`,
`The children giggled with joy as they ran through the sprinklers on a hot summer day.`,
`He carefully crafted a beautiful sculpture out of clay, his hands skillfully shaping the intricate details.`,
`The concert was a mesmerizing experience, with the music filling the venue and the crowd cheering in delight.`,
`The waves crashed against the shore, creating a soothing symphony of sound.`,
`The scent of blooming flowers wafted through the garden, creating a fragrant paradise.`,
`She gazed up at the night sky, marveling at the twinkling stars that dotted the darkness.`,
`The professor delivered a captivating lecture, engaging the students with thought-provoking ideas.`,
`The hiker trekked through the dense forest, guided by the soft glow of sunlight filtering through the trees.`,
`The delicate butterfly gracefully fluttered from flower to flower, sipping nectar with its slender proboscis.`,
`The aroma of freshly baked cookies filled the kitchen, tempting everyone with its irresistible scent.`,
'The majestic waterfall cascaded down the rocks, creating a breathtaking display of nature`s power.',
`The actor delivered a powerful performance, moving the audience to tears with his emotional portrayal.`,
`The book transported me to a magical world, where imagination knew no bounds.`,
`The scent of rain filled the air as dark clouds gathered overhead, promising a refreshing downpour.`,
`The chef skillfully plated the dish, turning simple ingredients into a work of culinary art.`,
`The newborn baby let out a tiny cry, announcing its arrival to the world.`,
`The athlete sprinted across the finish line, arms raised in victory as the crowd erupted in applause.`,
`The ancient ruins stood as a testament to a civilization long gone, their grandeur still awe-inspiring.`,
`The artist dipped the brush into vibrant paint, bringing the canvas to life with bold strokes and vivid colors.`,
`The laughter of children echoed through the playground, filling the atmosphere with pure joy.`,
];
// ----------------------------------------------------------------------
export const _descriptions = [
`Occaecati est et illo quibusdam accusamus qui. Incidunt aut et molestiae ut facere aut. Est quidem iusto praesentium excepturi harum nihil tenetur facilis. Ut omnis voluptates nihil accusantium doloribus eaque debitis.`,
`Atque eaque ducimus minima distinctio velit. Laborum et veniam officiis. Delectus ex saepe hic id laboriosam officia. Odit nostrum qui illum saepe debitis ullam. Laudantium beatae modi fugit ut. Dolores consequatur beatae nihil voluptates rem maiores.`,
`Rerum eius velit dolores. Explicabo ad nemo quibusdam. Voluptatem eum suscipit et ipsum et consequatur aperiam quia. Rerum nulla sequi recusandae illum velit quia quas. Et error laborum maiores cupiditate occaecati.`,
`Et non omnis qui. Qui sunt deserunt dolorem aut velit cumque adipisci aut enim. Nihil quis quisquam nesciunt dicta nobis ab aperiam dolorem repellat. Voluptates non blanditiis. Error et tenetur iste soluta cupiditate ratione perspiciatis et. Quibusdam aliquid nam sunt et quisquam non esse.`,
`Nihil ea sunt facilis praesentium atque. Ab animi alias sequi molestias aut velit ea. Sed possimus eos. Et est aliquid est voluptatem.`,
`Non rerum modi. Accusamus voluptatem odit nihil in. Quidem et iusto numquam veniam culpa aperiam odio aut enim. Quae vel dolores. Pariatur est culpa veritatis aut dolorem.`,
`Est enim et sit non impedit aperiam cumque animi. Aut eius impedit saepe blanditiis. Totam molestias magnam minima fugiat.`,
`Unde a inventore et. Sed esse ut. Atque ducimus quibusdam fuga quas id qui fuga.`,
`Eaque natus adipisci soluta nostrum dolorem. Nesciunt ipsum molestias ut aliquid natus ut omnis qui fugiat. Dolor et rem. Ut neque voluptatem blanditiis quasi ullam deleniti.`,
`Nam et error exercitationem qui voluptate optio. Officia omnis qui accusantium ipsam qui. Quia sequi nulla perspiciatis optio vero omnis maxime omnis ipsum. Perspiciatis consequuntur asperiores veniam dolores.`,
`Perspiciatis nulla ut ut ut voluptates totam consectetur eligendi qui. Optio ut cum. Dolorum sapiente qui laborum. Impedit temporibus totam delectus nihil. Voluptatem corrupti rem.`,
`Distinctio omnis similique omnis eos. Repellat cumque rerum nisi. Reiciendis soluta non ut veniam temporibus. Accusantium et dolorem voluptas harum. Nemo eius voluptate dicta et hic nemo. Dolorem assumenda et beatae molestias sit quo mollitia quis consequatur.`,
`Sed ut mollitia tempore ipsam et illum doloribus ut. Occaecati ratione veritatis explicabo. Omnis nam omnis sunt placeat tempore accusantium placeat distinctio velit.`,
`Eum illo dicta et perspiciatis ut blanditiis eos sequi. Ea veritatis aut et voluptas aut. Laborum eos quia tempore a culpa.`,
`Aut quos quae dolores repudiandae similique perferendis perferendis earum laudantium. Facere placeat natus nobis. Eius vitae ullam dolorem.`,
`Vero dolorem et voluptatem fugit tempore a quam iure. Fuga consequatur corrupti sunt asperiores vitae. Libero totam repellendus animi debitis illum et sunt officia.`,
`Cupiditate illum officiis id molestiae. Numquam non molestiae aliquid et natus sed hic. Alias quia explicabo sed corrupti sint. Natus in et odio qui unde facilis quia. Est sit eius laboriosam aliquid non aperiam quia quo corporis.`,
`Et a ab. Optio aspernatur minus tempora amet vitae consectetur inventore cumque. Sed et omnis. Aspernatur a magnam.`,
`Ipsum omnis et. Quia ea et autem tempore consequuntur veniam dolorem officiis. Ipsa dicta et ut quidem quia doloremque. Sequi vitae doloremque temporibus. Deserunt incidunt id aperiam itaque natus. Earum sit eaque quas incidunt nihil.`,
`Quae consequatur reiciendis. Consequatur non optio. Eaque id placeat. Commodi quo officia aut repudiandae reiciendis tempore voluptatem et. Ut accusamus qui itaque maxime aliquam. Fugit ut animi molestiae porro maiores.`,
`Modi hic asperiores ab cumque quam est aut. Voluptas atque quos molestias. Ut excepturi distinctio ipsam aspernatur sit.`,
`Sunt totam facilis. Quam commodi voluptatem veniam. Tempora deleniti itaque fugit nihil voluptas.`,
`Ipsam aliquam velit nobis repellendus officiis aut deserunt id et. Nihil sunt aut dolores aut. Dolores est ipsa quia et laborum quidem laborum accusamus id. Facilis odit quod hic laudantium saepe omnis nisi in sint. Sed cupiditate possimus id.`,
`Magnam non eveniet optio optio ut aliquid atque. Velit libero aspernatur quis laborum consequatur laudantium. Tempora facere optio fugit accusantium ut. Omnis aspernatur reprehenderit autem esse ut ut enim voluptatibus.`,
];

View File

@@ -1,222 +0,0 @@
// src/_mock/_blog.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import { uuidv4 } from './utils/uuidv4';
import { _mock } from './_mock';
import { CONFIG } from './global-config';
import { _tags } from './assets';
const codeBlock = `
<pre class="nml__editor__content__code__block"><code class="language-javascript">for (var i=1; i &lt;= 20; i++) {\n if (i % 15 == 0)\n return "FizzBuzz"\n else if (i % 3 == 0)\n return "Fizz"\n else if (i % 5 == 0)\n return "Buzz"\n else\n return i\n }</code></pre>
`;
// Made with Tiptap editor
const CONTENT = `
<h1 class="nml__editor__content__heading" style="text-align: start">Heading H1</h1>
<h2 class="nml__editor__content__heading" style="text-align: start">Heading H2</h2>
<h3 class="nml__editor__content__heading" style="text-align: start">Heading H3</h3>
<h4 class="nml__editor__content__heading" style="text-align: start">Heading H4</h4>
<h5 class="nml__editor__content__heading" style="text-align: start">Heading H5</h5>
<h6 class="nml__editor__content__heading" style="text-align: start">Heading H6</h6>
<hr class="nml__editor__content__hr">
<h4 class="nml__editor__content__heading" style="text-align: start">Paragraph</h4>
<p style="text-align: start">What is MTAweb Directory?</p>
<p style="text-align: start">So you have heard about this site or you have been to it, but you cannot figure out what it is or what it can do. MTA web directory is the simplest way in which one can bid on a link, or a few links if they wish to do so. The link directory on MTA displays all of the links it currently has, and does so in alphabetical order, which makes it much easier for someone to find what they are looking for if it is something specific and they do not want to go through all the other sites and links as well. It allows you to start your bid at the bottom and slowly work your way to the top of the list.</p>
<p style="text-align: start">With a very low costing starting bid of just $1, you are guaranteed to have a spot in MTAs successful directory list. When you would like to increase your bid to one of the top positions, you have to know that this would be a wise decision to make as it will not only get your link to be at a higher point in the directory but it will also give you a chance to have your site advertised with the rest of the top ten on the home page of the website. This means that when visitors come to <a target="_blank" rel="noopener noreferrer nofollow" class="nml__editor__content__link" href="http://MTAweb.com">MTAweb.com</a>, your site will be one of the first things they see. In other words, you stand a great chance at getting a comeback to your site sooner than you thought.</p>
<p style="text-align: start"><strong>This is strong text.</strong></p>
<p style="text-align: start"><em>This is italic text</em></p>
<p style="text-align: start">This is underline text</p>
<h4 class="nml__editor__content__heading" style="text-align: start">Unordered list</h4>
<ul class="nml__editor__content__bullet__list">
<li class="nml__editor__content__listItem">
<p>Implements <a target="_blank" rel="noopener noreferrer nofollow" class="nml__editor__content__link" href="https://docs-minimals.vercel.app/introduction">This is an external link</a></p>
</li>
<li class="nml__editor__content__listItem">
<p>Implements <a target="_blank" rel="noopener noreferrer nofollow" class="nml__editor__content__link" href="https://codebeautify.org/dashboard/blog">This is an inside link</a></p>
</li>
<li class="nml__editor__content__listItem">
<p>Renders actual, "native" React DOM elements</p>
</li>
<li class="nml__editor__content__listItem">
<p>Allows you to escape or skip HTML (try toggling the checkboxes above)</p>
</li>
<li class="nml__editor__content__listItem">
<p>If you escape or skip the HTML, no dangerouslySetInnerHTML is used! Yay!</p>
</li>
</ul>
<h4 class="nml__editor__content__heading" style="text-align: start">Ordered list</h4>
<ol class="nml__editor__content__ordered__list">
<li class="nml__editor__content__listItem">
<p>Analysis</p>
</li>
<li class="nml__editor__content__listItem">
<p>Design</p>
</li>
<li class="nml__editor__content__listItem">
<p>Implementation</p>
</li>
</ol>
<h4 class="nml__editor__content__heading" style="text-align: start">Blockquote</h4>
<blockquote class="nml__editor__content__blockquote">
<p>Life is short, Smile while you still have teeth!&nbsp;</p>
</blockquote>
<h4 class="nml__editor__content__heading" style="text-align: start"><br>Block code</h4>
${codeBlock}
<p>Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.</p>
<h5 class="nml__editor__content__heading" style="text-align: start">Why do we use it?</h5>
<p style="text-align: start">It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. The point of using Lorem Ipsum is that it has a more-or-less normal distribution of letters, as opposed to using 'Content here, content here', making it look like readable English. Many desktop publishing packages and web page editors now use Lorem Ipsum as their default model text, and a search for 'lorem ipsum' will uncover many web sites still in their infancy. Various versions have evolved over the years, sometimes by accident, sometimes on purpose (injected humour and the like).</p>
<img class="nml__editor__content__image" src="${CONFIG.basePath}/assets/images/cover/cover-5.webp">
<p>It is important that you buy links because the links are what get you the results that you want. The popularity of the links that are listed in the MTA directory is in fact one of the most important factors in the performance of the search engine. Links are important and this is why you have to purchase a link in order to bid on something and the best part is that a link will only cost you $1, which is nothing compared to what you would pay if you decided to do it through any other company or website.</p>
<img class="nml__editor__content__image" src="${CONFIG.basePath}/assets/images/cover/cover-14.webp">
`;
const generateComments = () => {
const userList = Array.from({ length: 12 }, (_, index) => ({
id: _mock.id(index),
name: _mock.fullName(index),
avatarUrl: _mock.image.avatar(index)
}));
return [
{
// id: uuidv4(),
name: userList[0].name,
avatarUrl: userList[0].avatarUrl,
message: _mock.sentence(1),
postedAt: _mock.time(1),
users: [userList[0], userList[1], userList[2]],
replyComment: [
{
id: uuidv4(),
userId: userList[1].id,
message: _mock.sentence(2),
postedAt: _mock.time(2)
},
{
id: uuidv4(),
userId: userList[0].id,
message: _mock.sentence(3),
tagUser: userList[1].name,
postedAt: _mock.time(3)
},
{
id: uuidv4(),
userId: userList[2].id,
message: _mock.sentence(4),
postedAt: _mock.time(4)
}
]
},
{
// id: uuidv4(),
name: userList[4].name,
avatarUrl: userList[4].avatarUrl,
message: _mock.sentence(5),
postedAt: _mock.time(5),
users: [userList[5], userList[6], userList[7]],
replyComment: [
{
id: uuidv4(),
userId: userList[5].id,
message: _mock.sentence(6),
postedAt: _mock.time(6)
},
{
id: uuidv4(),
userId: userList[6].id,
message: _mock.sentence(7),
postedAt: _mock.time(7)
},
{
id: uuidv4(),
userId: userList[7].id,
message: _mock.sentence(8),
postedAt: _mock.time(8)
}
]
},
{
// id: uuidv4(),
name: userList[8].name,
avatarUrl: userList[8].avatarUrl,
message: _mock.sentence(9),
postedAt: _mock.time(9),
users: [],
replyComment: []
},
{
// id: uuidv4(),
name: userList[9].name,
avatarUrl: userList[9].avatarUrl,
message: _mock.sentence(10),
postedAt: _mock.time(10),
users: [],
replyComment: []
}
];
};
const _posts = () =>
Array.from({ length: 19 }, (_, index) => {
const comments = generateComments();
const publish = index % 3 ? 'published' : 'draft';
const metaKeywords = _tags.slice(8, 11);
return {
// id: _mock.id(index),
publish,
metaKeywords,
content: CONTENT,
tags: _tags.slice(0, 5),
metaTitle: 'Minimal UI Kit',
createdAt: _mock.time(index),
title: _mock.postTitle(index),
coverUrl: _mock.image.cover(index),
totalViews: _mock.number.nativeL(index),
totalShares: _mock.number.nativeL(index + 2),
totalComments: _mock.number.nativeL(index + 1),
totalFavorites: _mock.number.nativeL(index + 3),
metaDescription: 'The starting point for your next project with Minimal UI Kit',
description: `Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. `,
author: {
name: _mock.fullName(index),
avatarUrl: _mock.image.avatar(index)
},
favoritePerson: Array.from({ length: 20 }, (__, personIndex) => ({
name: _mock.fullName(personIndex),
avatarUrl: _mock.image.avatar(personIndex)
})),
//
comments: {
create: comments
}
};
});
const temp_posts = _posts();
async function blog() {
for (let i = 0; i < temp_posts.length; i++) {
await prisma.postItem.upsert({
where: { id: i },
update: {},
create: temp_posts[i]
});
}
console.log('generate blog-postitem done');
}
const Blog = blog()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Blog };

View File

@@ -1,592 +0,0 @@
// src/_mock/_blog.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import { uuidv4 } from './utils/uuidv4';
import { _mock } from './_mock';
import { CONFIG } from './global-config';
import { _tags } from './assets';
import { fSub } from './utils/set-date';
import { fileList } from './_files.ts';
export const _contacts = () =>
Array.from({ length: 20 }, (_, index) => ({
id: _mock.id(index),
role: _mock.role(index),
email: _mock.email(index),
name: _mock.fullName(index),
lastActivity: _mock.time(index),
address: _mock.fullAddress(index),
avatarUrl: _mock.image.avatar(index),
phoneNumber: _mock.phoneNumber(index),
status:
([0, 1, 6, 12].includes(index) && 'online') ||
([3, 8, 14].includes(index) && 'offline') ||
([4, 10, 16].includes(index) && 'busy') ||
'always'
}));
export const _conversations = () => {
const myContact = {
id: '8864c717-587d-472a-929a-8e5f298024da-0',
role: 'admin',
status: 'online',
name: 'Jaydon Frankie',
email: 'demo@minimals.cc',
phoneNumber: '+40 777666555',
address: '90210 Broadway Blvd',
avatarUrl: _mock.image.avatar(24),
lastActivity: fSub({ minutes: 1 })
};
const files = fileList();
const otherContacts = _contacts();
return [
{
id: otherContacts[1].id,
participants: [myContact, otherContacts[1]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: otherContacts[1].id,
body: _mock.sentence(1),
contentType: 'text',
attachments: files.slice(0, 1),
createdAt: fSub({ hours: 5 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(2),
contentType: 'text',
attachments: files.slice(1, 2),
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: otherContacts[1].id,
body: _mock.sentence(3),
contentType: 'text',
attachments: files.slice(2, 3),
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(4),
contentType: 'text',
attachments: files.slice(3, 6),
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: otherContacts[1].id,
body: _mock.sentence(5),
contentType: 'text',
attachments: files.slice(6, 10),
createdAt: fSub({ hours: 1 })
},
{
id: uuidv4(),
senderId: otherContacts[1].id,
attachments: [],
contentType: 'image',
body: _mock.image.cover(4),
createdAt: fSub({ minutes: 15 })
},
{
id: uuidv4(),
senderId: myContact.id,
contentType: 'text',
attachments: [],
body: _mock.sentence(6),
createdAt: fSub({ minutes: 1 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ minutes: 0 })
}
]
},
{
id: otherContacts[2].id,
participants: [myContact, otherContacts[2]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: otherContacts[2].id,
body: _mock.sentence(2),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 6 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(3),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[2].id,
body: _mock.sentence(4),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(5),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 7 })
},
{
id: uuidv4(),
senderId: otherContacts[2].id,
body: _mock.sentence(6),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: otherContacts[2].id,
body: _mock.image.cover(7),
attachments: [],
contentType: 'image',
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(8),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 1 })
}
]
},
{
id: otherContacts[3].id,
participants: [myContact, otherContacts[3]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.sentence(3),
contentType: 'text',
attachments: files.slice(0, 1),
createdAt: fSub({ hours: 8 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(4),
contentType: 'text',
attachments: files.slice(1, 2),
createdAt: fSub({ hours: 7 })
},
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.sentence(5),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 6 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(6),
contentType: 'text',
attachments: files.slice(2, 4),
createdAt: fSub({ hours: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.image.cover(8),
contentType: 'image',
attachments: [],
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.image.cover(9),
contentType: 'image',
attachments: [],
createdAt: fSub({ hours: 2 })
}
]
},
{
id: otherContacts[4].id,
participants: [myContact, otherContacts[4]],
type: 'ONE_TO_ONE',
unreadCount: 8,
messages: [
{
id: uuidv4(),
senderId: otherContacts[4].id,
body: _mock.sentence(4),
contentType: 'text',
attachments: files.slice(2, 4),
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(5),
contentType: 'text',
attachments: files.slice(4, 6),
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: otherContacts[4].id,
body: _mock.sentence(6),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 1 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(8),
contentType: 'text',
attachments: files.slice(6, 10),
createdAt: fSub({ minutes: 45 })
},
{
id: uuidv4(),
senderId: otherContacts[4].id,
body: _mock.sentence(9),
contentType: 'text',
attachments: [],
createdAt: fSub({ minutes: 5 })
}
]
},
{
id: otherContacts[5].id,
participants: [myContact, otherContacts[5]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(5),
contentType: 'text',
attachments: [],
createdAt: fSub({ minutes: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[5].id,
body: _mock.sentence(6),
contentType: 'text',
attachments: [],
createdAt: fSub({ seconds: 30 })
}
]
},
{
id: otherContacts[6].id,
participants: [myContact, otherContacts[6]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(6),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: otherContacts[6].id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 1 })
}
]
},
{
id: `${_mock.id(1)}gr`,
participants: [myContact, ...otherContacts.slice(6, 11)],
type: 'GROUP',
unreadCount: 2,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(6),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[9].id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: otherContacts[10].id,
body: _mock.sentence(8),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: otherContacts[8].id,
body: _mock.sentence(9),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: myContact.id,
attachments: [],
body: _mock.sentence(10),
contentType: 'text',
createdAt: fSub({ hours: 1 })
},
{
id: uuidv4(),
senderId: otherContacts[6].id,
body: _mock.sentence(11),
contentType: 'text',
attachments: [],
createdAt: fSub({ minutes: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[7].id,
body: _mock.sentence(12),
contentType: 'text',
attachments: [],
createdAt: fSub({ seconds: 30 })
}
]
},
{
id: otherContacts[7].id,
participants: [myContact, otherContacts[7]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(7),
contentType: 'text',
attachments: [],
createdAt: fSub({ days: 4 })
},
{
id: uuidv4(),
senderId: otherContacts[7].id,
body: _mock.sentence(8),
contentType: 'text',
attachments: [],
createdAt: fSub({ days: 3 })
}
]
},
{
id: otherContacts[8].id,
participants: [myContact, otherContacts[8]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(8),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 9 })
},
{
id: uuidv4(),
senderId: otherContacts[8].id,
body: _mock.sentence(9),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 8 })
}
]
},
{
id: otherContacts[9].id,
participants: [myContact, otherContacts[9]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(9),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 5 })
},
{
id: uuidv4(),
senderId: otherContacts[9].id,
body: _mock.sentence(10),
contentType: 'text',
attachments: [],
createdAt: fSub({ hours: 3 })
}
]
},
{
id: `${_mock.id(2)}gr`,
participants: [myContact, ...otherContacts.slice(1, 5)],
type: 'GROUP',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(1),
contentType: 'text',
attachments: files.slice(0, 5),
createdAt: fSub({ hours: 4 })
},
{
id: uuidv4(),
senderId: otherContacts[1].id,
body: _mock.sentence(2),
contentType: 'text',
attachments: files.slice(5, 6),
createdAt: fSub({ hours: 3 })
},
{
id: uuidv4(),
senderId: otherContacts[2].id,
body: _mock.sentence(3),
contentType: 'text',
attachments: files.slice(6, 7),
createdAt: fSub({ hours: 2 })
},
{
id: uuidv4(),
senderId: otherContacts[4].id,
body: _mock.sentence(4),
contentType: 'text',
attachments: files.slice(7, 8),
createdAt: fSub({ hours: 1 })
},
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(5),
contentType: 'text',
attachments: files.slice(8, 9),
createdAt: fSub({ minutes: 30 })
},
{
id: uuidv4(),
senderId: otherContacts[3].id,
body: _mock.sentence(6),
contentType: 'text',
attachments: files.slice(9, 10),
createdAt: fSub({ minutes: 10 })
}
]
},
{
id: otherContacts[10].id,
participants: [myContact, otherContacts[10]],
type: 'ONE_TO_ONE',
unreadCount: 0,
messages: [
{
id: uuidv4(),
senderId: myContact.id,
body: _mock.sentence(10),
contentType: 'text',
attachments: [],
createdAt: fSub({ days: 11 })
},
{
id: uuidv4(),
senderId: otherContacts[10].id,
body: _mock.sentence(11),
contentType: 'text',
attachments: [],
createdAt: fSub({ days: 10 })
}
]
}
];
};
const temp_conversations = _conversations();
async function chat() {
for (let i = 0; i < temp_conversations.length; i++) {
await prisma.chatMessage.upsert({
where: { id: i },
update: {},
create: temp_conversations[i]
});
}
console.log('generate blog-postitem done');
}
const Chat = chat()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Chat };

View File

@@ -1,66 +0,0 @@
{
"primary": {
"lighter": "#C8FAD6",
"light": "#5BE49B",
"main": "#00A76F",
"dark": "#007867",
"darker": "#004B50",
"contrastText": "#FFFFFF"
},
"secondary": {
"lighter": "#EFD6FF",
"light": "#C684FF",
"main": "#8E33FF",
"dark": "#5119B7",
"darker": "#27097A",
"contrastText": "#FFFFFF"
},
"info": {
"lighter": "#CAFDF5",
"light": "#61F3F3",
"main": "#00B8D9",
"dark": "#006C9C",
"darker": "#003768",
"contrastText": "#FFFFFF"
},
"success": {
"lighter": "#D3FCD2",
"light": "#77ED8B",
"main": "#22C55E",
"dark": "#118D57",
"darker": "#065E49",
"contrastText": "#ffffff"
},
"warning": {
"lighter": "#FFF5CC",
"light": "#FFD666",
"main": "#FFAB00",
"dark": "#B76E00",
"darker": "#7A4100",
"contrastText": "#1C252E"
},
"error": {
"lighter": "#FFE9D5",
"light": "#FFAC82",
"main": "#FF5630",
"dark": "#B71D18",
"darker": "#7A0916",
"contrastText": "#FFFFFF"
},
"grey": {
"50": "#FCFDFD",
"100": "#F9FAFB",
"200": "#F4F6F8",
"300": "#DFE3E8",
"400": "#C4CDD5",
"500": "#919EAB",
"600": "#637381",
"700": "#454F5B",
"800": "#1C252E",
"900": "#141A21"
},
"common": {
"black": "#000000",
"white": "#FFFFFF"
}
}

View File

@@ -1,76 +0,0 @@
// src/_mock/_files.ts
//
import { _mock } from './_mock';
import { _fileNames } from './assets';
//
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
//
const GB = 1000000000 * 24;
const URLS = [
_mock.image.cover(2),
'https://www.cloud.com/s/c218bo6kjuqyv66/design_suriname_2015.mp3',
'https://www.cloud.com/s/c218bo6kjuqyv66/expertise_2015_conakry_sao-tome-and-principe_gender.mp4',
'https://www.cloud.com/s/c218bo6kjuqyv66/money-popup-crack.pdf',
_mock.image.cover(3),
_mock.image.cover(5),
'https://www.cloud.com/s/c218bo6kjuqyv66/large_news.txt',
'https://www.cloud.com/s/c218bo6kjuqyv66/nauru-6015-small-fighter-left-gender.psd',
'https://www.cloud.com/s/c218bo6kjuqyv66/tv-xs.doc',
'https://www.cloud.com/s/c218bo6kjuqyv66/gustavia-entertainment-productivity.docx',
'https://www.cloud.com/s/c218bo6kjuqyv66/vintage_bahrain_saipan.xls',
'https://www.cloud.com/s/c218bo6kjuqyv66/indonesia-quito-nancy-grace-left-glad.xlsx',
'https://www.cloud.com/s/c218bo6kjuqyv66/legislation-grain.zip',
'https://www.cloud.com/s/c218bo6kjuqyv66/large_energy_dry_philippines.rar',
'https://www.cloud.com/s/c218bo6kjuqyv66/footer-243-ecuador.iso',
'https://www.cloud.com/s/c218bo6kjuqyv66/kyrgyzstan-04795009-picabo-street-guide-style.ai',
'https://www.cloud.com/s/c218bo6kjuqyv66/india-data-large-gk-chesterton-mother.esp',
'https://www.cloud.com/s/c218bo6kjuqyv66/footer-barbados-celine-dion.ppt',
'https://www.cloud.com/s/c218bo6kjuqyv66/socio_respectively_366996.pptx',
'https://www.cloud.com/s/c218bo6kjuqyv66/socio_ahead_531437_sweden_popup.wav',
'https://www.cloud.com/s/c218bo6kjuqyv66/trinidad_samuel-morse_bring.m4v',
_mock.image.cover(11),
_mock.image.cover(17),
'https://www.cloud.com/s/c218bo6kjuqyv66/xl_david-blaine_component_tanzania_books.pdf'
];
// ----------------------------------------------------------------------
export const genFileList = () =>
_fileNames.map((name, index) => ({
// id: _mock.id(index),
// createdAt: _mock.time(index),
//
name,
path: URLS[index],
preview: URLS[index],
size: GB / ((index + 1) * 500),
modifiedAt: _mock.time(index),
type: `${name.split('.').pop()}`
}));
const temp_file_list = genFileList();
async function fileList() {
for (let i = 0; i < temp_file_list.length; i++) {
await prisma.fileStore.upsert({
where: { id: i },
update: {},
create: temp_file_list[i]
});
}
}
const File = fileList()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { File };

View File

@@ -1,24 +0,0 @@
import packageJson from '../../package.json';
type ConfigType = {
basePath?: string;
appVersion: string;
cors: {
allowedOrigins: string[];
methods: string[];
};
};
export const CONFIG: ConfigType = {
appVersion: packageJson.version,
basePath:
process.env.NODE_ENV === 'production' ? process.env.PRODUCTION_API : process.env.DEV_API,
cors: {
/**
* [] = allow all origins
* ['http://localhost:8081', 'http://localhost:8082'] = allow only these origins
*/
allowedOrigins: [],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS']
}
};

View File

@@ -1,120 +0,0 @@
// src/_mock/_kanban.ts
// no idea how to do
//
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
import { uuidv4 } from './utils/uuidv4';
import { _mock } from './_mock';
import { CONFIG } from './global-config';
import { _tags } from './assets';
import { fAdd, fSub } from './utils/set-date';
const generateAttachments = () =>
Array.from({ length: 20 }, (_, index) => _mock.image.cover(index));
const generateAssignees = () =>
Array.from({ length: 20 }, (_, index) => ({
id: _mock.id(index),
name: _mock.fullName(index),
avatarUrl: _mock.image.avatar(index)
}));
const generateComments = () =>
Array.from({ length: 8 }, (_, index) => ({
id: uuidv4(),
name: _mock.fullName(index),
avatarUrl: _mock.image.avatar(index),
createdAt: fSub({ minutes: 20 - index }),
messageType: [1, 2].includes(index) ? 'image' : 'text',
message: [1, 2].includes(index) ? _mock.image.cover(index + 5) : _mock.sentence(index)
}));
const COLUMN_NAMES = {
name1: 'To do',
name2: 'In progress',
name3: 'Ready to test',
name4: 'Done'
};
const COLUMN_IDS = {
id1: `${1}-column-${_mock.id(1)}`,
id2: `${2}-column-${_mock.id(2)}`,
id3: `${3}-column-${_mock.id(3)}`,
id4: `${4}-column-${_mock.id(4)}`
};
const PRIORITY_LEVEL = {
low: 'low',
medium: 'medium',
hight: 'hight'
};
const createTask = (index: number, status: string) => {
const commentList = generateComments();
const assignedUser = generateAssignees();
const attachmentList = generateAttachments();
const reporter = {
id: _mock.id(16),
name: _mock.fullName(16),
avatarUrl: _mock.image.avatar(16)
};
return {
id: `${index}-task-${_mock.id(index)}`,
reporter,
name: _mock.taskNames(index),
labels: _tags.slice(0, index),
comments: commentList.slice(0, index),
assignee: assignedUser.slice(0, index),
description: _mock.description(index),
due: [fAdd({ days: index + 1 }), fAdd({ days: index + 2 })],
priority:
([1, 3].includes(index) && PRIORITY_LEVEL.hight) ||
([2, 4].includes(index) && PRIORITY_LEVEL.medium) ||
PRIORITY_LEVEL.low,
attachments:
(index === 1 && attachmentList.slice(11, 15)) ||
(index === 5 && attachmentList.slice(4, 9)) ||
[],
status
};
};
const tasks = () => ({
[COLUMN_IDS.id1]: [
createTask(1, COLUMN_NAMES.name1),
createTask(2, COLUMN_NAMES.name1),
createTask(3, COLUMN_NAMES.name1)
],
[COLUMN_IDS.id2]: [createTask(4, COLUMN_NAMES.name2), createTask(5, COLUMN_NAMES.name2)],
[COLUMN_IDS.id3]: [],
[COLUMN_IDS.id4]: [createTask(6, COLUMN_NAMES.name4)]
});
const columns = () => [
{ id: COLUMN_IDS.id1, name: COLUMN_NAMES.name1 },
{ id: COLUMN_IDS.id2, name: COLUMN_NAMES.name2 },
{ id: COLUMN_IDS.id3, name: COLUMN_NAMES.name3 },
{ id: COLUMN_IDS.id4, name: COLUMN_NAMES.name4 }
];
// const temp_kanban = _kanban();
async function kanban() {
for (let i = 0; i < 10; i++) {
console.log({ i });
}
}
const Kanban = kanban()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Kanban };

View File

@@ -1,101 +0,0 @@
// src/_mock/_mail.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
//
import { uuidv4 } from './utils/uuidv4';
import { _mock } from './_mock';
import { CONFIG } from './global-config';
import { _tags } from './assets';
//
import { _files } from './_files';
import COLORS from './colors.json';
export const _labels = () => [
{ id: 'all', type: 'system', name: 'all', unreadCount: 3 },
{ id: 'inbox', type: 'system', name: 'inbox', unreadCount: 1 },
{ id: 'sent', type: 'system', name: 'sent', unreadCount: 0 },
{ id: 'drafts', type: 'system', name: 'drafts', unreadCount: 0 },
{ id: 'trash', type: 'system', name: 'trash', unreadCount: 0 },
{ id: 'spam', type: 'system', name: 'spam', unreadCount: 1 },
{ id: 'important', type: 'system', name: 'important', unreadCount: 1 },
{ id: 'starred', type: 'system', name: 'starred', unreadCount: 1 },
{ id: 'social', type: 'custom', name: 'social', unreadCount: 0, color: COLORS.primary.main },
{
id: 'promotions',
type: 'custom',
name: 'promotions',
unreadCount: 2,
color: COLORS.warning.main
},
{ id: 'forums', type: 'custom', name: 'forums', unreadCount: 1, color: COLORS.error.main }
];
const _mails = () =>
Array.from({ length: 9 }, (_, index) => {
const files = _files();
const attachments =
(index === 1 && files.slice(0, 2)) ||
(index === 2 && files.slice(0, 4)) ||
(index === 5 && files.slice(4, 10)) ||
[];
const folder =
([1, 2].includes(index) && 'spam') || ([3, 4].includes(index) && 'sent') || 'inbox';
const labelIds =
(index === 1 && ['promotions', 'forums']) ||
(index === 2 && ['forums']) ||
(index === 5 && ['social']) ||
[];
const from = {
name: _mock.fullName(index),
email: _mock.email(index),
avatarUrl: [1, 2, 6].includes(index) ? null : _mock.image.avatar(index)
};
const to = [
{ name: 'Jaydon Frankie', email: 'demo@minimals.cc', avatarUrl: null },
{ name: _mock.fullName(12), email: _mock.email(12), avatarUrl: _mock.image.avatar(12) }
];
return {
// id: _mock.id(index),
// to: { create: to },
// from: { create: from },
// attachments,
folder,
labelIds,
subject: _mock.postTitle(index),
isUnread: [1, 3].includes(index),
isImportant: _mock.boolean(index),
message: _mock.description(index),
isStarred: _mock.boolean(index + 2)
};
});
const temp_mails = _mails();
async function mail() {
for (let i = 0; i < temp_mails.length; i++) {
await prisma.mail.upsert({
where: { id: i },
update: {},
create: temp_mails[i]
});
}
console.log('generate mail done');
}
const Mail = mail()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { Mail };

View File

@@ -1,243 +0,0 @@
import { _mock } from './_mock';
import { _tags } from './assets';
import { PrismaClient, ProductReview } from '@prisma/client';
import { _productsReview } from './productReview';
const prisma = new PrismaClient();
const COLORS = [
'#FF4842',
'#1890FF',
'#FFC0CB',
'#00AB55',
'#FFC107',
'#7F00FF',
'#000000',
'#FFFFFF'
];
const DESCRIPTION = `
<h6>Specifications</h6>
<table>
<tbody>
<tr>
<td>Category</td>
<td>Mobile</td>
</tr>
<tr>
<td>Manufacturer</td>
<td>Apple</td>
</tr>
<tr>
<td>Warranty</td>
<td>12 Months</td>
</tr>
<tr>
<td>Serial number</td>
<td>358607726380311</td>
</tr>
<tr>
<td>Ships from</td>
<td>United States</td>
</tr>
</tbody>
</table>
<h6>Product details</h6>
<ul>
<li>
<p>The foam sockliner feels soft and comfortable</p>
</li>
<li>
<p>Pull tab</p>
</li>
<li>
<p>Not intended for use as Personal Protective Equipment</p>
</li>
<li>
<p>Colour Shown: White/Black/Oxygen Purple/Action Grape</p>
</li>
<li>
<p>Style: 921826-109</p>
</li>
<li>
<p>Country/Region of Origin: China</p>
</li>
</ul>
<h6>Benefits</h6>
<ul>
<li>
<p>Mesh and synthetic materials on the upper keep the fluid look of the OG while adding comfort</p>
and durability.
</li>
<li>
<p>Originally designed for performance running, the full-length Max Air unit adds soft, comfortable cushio</p>
ning underfoot.
</li>
<li>
<p>The foam midsole feels springy and soft.</p>
</li>
<li>
<p>The rubber outsole adds traction and durability.</p>
</li>
</ul>
<h6>Delivery and returns</h6>
<p>Your order of $200 or more gets free standard delivery.</p>
<ul>
<li>
<p>Standard delivered 4-5 Business Days</p>
</li>
<li>
<p>Express delivered 2-4 Business Days</p>
</li>
</ul>
<p>Orders are processed and delivered Monday-Friday (excluding public holidays)</p>
`;
const getColorSliceForIndex = (index: number) => {
if (index === 0) return COLORS.slice(0, 2);
if (index === 1) return COLORS.slice(1, 3);
if (index === 2) return COLORS.slice(2, 4);
if (index === 3) return COLORS.slice(3, 6);
if (index === 4 || index === 16 || index === 19) return COLORS.slice(4, 6);
if (index === 5 || index === 17) return COLORS.slice(5, 6);
if (index === 6 || index === 18) return COLORS.slice(0, 2);
if (index === 7) return COLORS.slice(4, 6);
if (index === 8) return COLORS.slice(2, 4);
if (index === 9 || index === 11) return COLORS.slice(2, 6);
if (index === 10) return COLORS.slice(3, 6);
if (index === 12) return COLORS.slice(2, 7);
if (index === 13) return COLORS.slice(4, 7);
if (index === 14) return COLORS.slice(0, 2);
if (index === 15) return COLORS.slice(5, 8);
return COLORS.slice(2, 6); // Default case
};
const generateAttachments = () =>
Array.from({ length: 20 }, (_, index) => _mock.image.product(index));
const generateReviews = () => {
const attachments = generateAttachments();
};
const generateRatings = () =>
Array.from({ length: 5 }, (_, index) => ({
name: `${index + 1} Star`,
starCount: _mock.number.nativeL(index),
reviewCount: _mock.number.nativeL(index + 1)
}));
const generateImages = () => Array.from({ length: 8 }, (_, index) => _mock.image.product(index));
const _products = () =>
Array.from({ length: 20 }, (_, index) => {
const reviews = generateReviews();
const images = generateImages();
const ratings = generateRatings();
//
const publish = index % 3 ? 'published' : 'draft';
const category = (index % 2 && 'Shose') || (index % 3 && 'Apparel') || 'Accessories';
const gender = (index % 2 && ['Men']) || (index % 3 && ['Women', 'Kids']) || ['Kids'];
const available = (index % 2 && 72) || (index % 3 && 10) || 0;
const inventoryType = (index % 2 && 'in stock') || (index % 3 && 'low stock') || 'out of stock';
const priceSale = index % 3 ? undefined : _mock.number.price(index);
return {
id: _mock.id(index),
sku: `WW75K521${index}YW/SV`,
name: _mock.productName(index),
gender,
images,
reviews,
publish,
ratings,
category,
available,
priceSale,
taxes: 10,
quantity: 80,
inventoryType,
tags: _tags.slice(0, 5),
code: `38BEE27${index}`,
description: DESCRIPTION,
createdAt: _mock.time(index),
price: _mock.number.price(index),
coverUrl: _mock.image.product(index),
colors: getColorSliceForIndex(index),
totalRatings: _mock.number.rating(index),
totalSold: _mock.number.nativeM(index + 1),
totalReviews: _mock.number.nativeL(index + 1),
newLabel: { enabled: [1, 2, 3].includes(index), content: 'NEW' },
saleLabel: { enabled: [4, 5].includes(index), content: 'SALE' },
sizes: ['6', '7', '8', '8.5', '9', '9.5', '10', '10.5', '11', '11.5', '12', '13'],
subDescription:
'Featuring the original ripple design inspired by Japanese bullet trains, the Nike Air Max 97 lets you push your style full-speed ahead.'
};
});
async function productItem() {
const temp_products = _products();
for (let i = 0; i < temp_products.length; i++) {
// console.log(i);
const temp_pr = _productsReview();
const temp = await prisma.productItem.upsert({
where: { id: i },
update: {},
create: {
name: temp_products[i].name,
code: temp_products[i].code,
price: temp_products[i].price,
taxes: temp_products[i].taxes,
tags: temp_products[i].tags,
sizes: temp_products[i].sizes,
publish: temp_products[i].publish,
gender: temp_products[i].gender,
coverUrl: temp_products[i].coverUrl,
images: temp_products[i].images,
colors: temp_products[i].colors,
quantity: temp_products[i].quantity,
category: temp_products[i].category,
available: temp_products[i].available,
totalSold: temp_products[i].totalSold,
description: temp_products[i].description,
totalRatings: temp_products[i].totalRatings,
totalReviews: temp_products[i].totalReviews,
inventoryType: temp_products[i].inventoryType,
subDescription: temp_products[i].subDescription,
priceSale: temp_products[i].priceSale,
newLabel: temp_products[i].newLabel,
saleLabel: temp_products[i].saleLabel,
ratings: temp_products[i].ratings,
// review: { create: temp_pr },
reviews: { create: temp_pr },
testing: {
create: [{ hello: 'world' }]
},
sku: temp_products[i].sku
}
});
}
console.log('seed productItem done');
}
const ProductItem = productItem()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { ProductItem };

View File

@@ -1,184 +0,0 @@
import { _mock } from './_mock';
import { _tags } from './assets';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const COLORS = [
'#FF4842',
'#1890FF',
'#FFC0CB',
'#00AB55',
'#FFC107',
'#7F00FF',
'#000000',
'#FFFFFF'
];
const DESCRIPTION = `
<h6>Specifications</h6>
<table>
<tbody>
<tr>
<td>Category</td>
<td>Mobile</td>
</tr>
<tr>
<td>Manufacturer</td>
<td>Apple</td>
</tr>
<tr>
<td>Warranty</td>
<td>12 Months</td>
</tr>
<tr>
<td>Serial number</td>
<td>358607726380311</td>
</tr>
<tr>
<td>Ships from</td>
<td>United States</td>
</tr>
</tbody>
</table>
<h6>Product details</h6>
<ul>
<li>
<p>The foam sockliner feels soft and comfortable</p>
</li>
<li>
<p>Pull tab</p>
</li>
<li>
<p>Not intended for use as Personal Protective Equipment</p>
</li>
<li>
<p>Colour Shown: White/Black/Oxygen Purple/Action Grape</p>
</li>
<li>
<p>Style: 921826-109</p>
</li>
<li>
<p>Country/Region of Origin: China</p>
</li>
</ul>
<h6>Benefits</h6>
<ul>
<li>
<p>Mesh and synthetic materials on the upper keep the fluid look of the OG while adding comfort</p>
and durability.
</li>
<li>
<p>Originally designed for performance running, the full-length Max Air unit adds soft, comfortable cushio</p>
ning underfoot.
</li>
<li>
<p>The foam midsole feels springy and soft.</p>
</li>
<li>
<p>The rubber outsole adds traction and durability.</p>
</li>
</ul>
<h6>Delivery and returns</h6>
<p>Your order of $200 or more gets free standard delivery.</p>
<ul>
<li>
<p>Standard delivered 4-5 Business Days</p>
</li>
<li>
<p>Express delivered 2-4 Business Days</p>
</li>
</ul>
<p>Orders are processed and delivered Monday-Friday (excluding public holidays)</p>
`;
const getColorSliceForIndex = (index: number) => {
if (index === 0) return COLORS.slice(0, 2);
if (index === 1) return COLORS.slice(1, 3);
if (index === 2) return COLORS.slice(2, 4);
if (index === 3) return COLORS.slice(3, 6);
if (index === 4 || index === 16 || index === 19) return COLORS.slice(4, 6);
if (index === 5 || index === 17) return COLORS.slice(5, 6);
if (index === 6 || index === 18) return COLORS.slice(0, 2);
if (index === 7) return COLORS.slice(4, 6);
if (index === 8) return COLORS.slice(2, 4);
if (index === 9 || index === 11) return COLORS.slice(2, 6);
if (index === 10) return COLORS.slice(3, 6);
if (index === 12) return COLORS.slice(2, 7);
if (index === 13) return COLORS.slice(4, 7);
if (index === 14) return COLORS.slice(0, 2);
if (index === 15) return COLORS.slice(5, 8);
return COLORS.slice(2, 6); // Default case
};
const generateAttachments = () =>
Array.from({ length: 20 }, (_, index) => _mock.image.product(index));
const generateReviews = () => {
const attachments = generateAttachments();
return Array.from({ length: 8 }, (_, index) => ({
// id: _mock.id(index),
name: _mock.fullName(index),
postedAt: _mock.time(index),
comment: _mock.sentence(index),
isPurchased: _mock.boolean(index),
rating: _mock.number.rating(index),
avatarUrl: _mock.image.avatar(index),
helpful: _mock.number.nativeL(index),
attachments:
(index === 1 && attachments.slice(0, 1)) ||
(index === 3 && attachments.slice(2, 4)) ||
(index === 5 && attachments.slice(5, 8)) ||
[]
}));
};
const generateRatings = () =>
Array.from({ length: 5 }, (_, index) => ({
name: `${index + 1} Star`,
starCount: _mock.number.nativeL(index),
reviewCount: _mock.number.nativeL(index + 1)
}));
const generateImages = () => Array.from({ length: 8 }, (_, index) => _mock.image.product(index));
export const _productsReview = () => {
return generateReviews();
};
async function productReview() {
const temp_pr = _productsReview();
for (let i = 0; i < temp_pr.length; i++) {
const temp = await prisma.productReview.upsert({
where: { id: i },
update: {},
create: {
name: temp_pr[i].name,
rating: temp_pr[i].rating,
comment: temp_pr[i].comment,
helpful: temp_pr[i].helpful,
avatarUrl: temp_pr[i].avatarUrl,
isPurchased: temp_pr[i].isPurchased,
attachments: temp_pr[i].attachments,
postedAt: temp_pr[i].postedAt
}
});
}
console.log('seed productReview done');
}
const ProductReview = productReview()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { ProductReview };

View File

@@ -1,39 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function superuser() {
const admin1 = await prisma.user.upsert({
where: { email: 'admin1@123.com' },
update: {},
create: {
email: 'admin1@123.com',
name: 'Admin1',
password: 'Aa12345678'
}
});
// swagger test
const swaggerUser = await prisma.user.upsert({
where: { email: 'fake@example.com' },
update: {},
create: {
email: 'fake@example.com',
name: 'swagger user',
password: 'password1'
}
});
console.log('seed superuser done');
}
const superuserSeed = superuser()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { superuserSeed };

View File

@@ -1,37 +0,0 @@
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function user() {
const alice = await prisma.user.upsert({
where: { email: 'alice@prisma.io' },
update: {},
create: {
email: 'alice@prisma.io',
name: 'Alice',
password: 'Aa12345678'
}
});
const bob = await prisma.user.upsert({
where: { email: 'bob@prisma.io' },
update: {},
create: {
email: 'bob@prisma.io',
name: 'Bob',
password: 'Aa12345678'
}
});
console.log('seed user done');
}
const userSeed = user()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
export { userSeed };

View File

@@ -1,91 +0,0 @@
import dayjs from 'dayjs';
import duration from 'dayjs/plugin/duration';
// ----------------------------------------------------------------------
dayjs.extend(duration);
export function setDate(now: Date, options: { days?: number; hours?: number; minutes?: number }) {
const month = now.getMonth() + 1;
const year = now.getFullYear();
const today = now.getDate();
const { days, hours = 0, minutes = 0 } = options;
return new Date(`${year}-${month}-${days ?? today} ${hours}:${minutes}`).toJSON();
}
export const subHours = (
value: number,
option: 'years' | 'months' | 'days' | 'hours' | 'minutes' | 'seconds' | 'milliseconds'
) => dayjs().subtract(value, option).format();
// years,
// months,
// days,
// hours,
// minutes,
// seconds,
// milliseconds,
export type DurationProps = {
years?: number;
months?: number;
days?: number;
hours?: number;
minutes?: number;
seconds?: number;
milliseconds?: number;
};
export function fSub({
years = 0,
months = 0,
days = 0,
hours = 0,
minutes = 0,
seconds = 0,
milliseconds = 0
}: DurationProps) {
const result = dayjs()
.subtract(
dayjs.duration({
years,
months,
days,
hours,
minutes,
seconds,
milliseconds
})
)
.format();
return result;
}
export function fAdd({
years = 0,
months = 0,
days = 0,
hours = 0,
minutes = 0,
seconds = 0,
milliseconds = 0
}: DurationProps) {
const result = dayjs()
.add(
dayjs.duration({
years,
months,
days,
hours,
minutes,
seconds,
milliseconds
})
)
.format();
return result;
}

View File

@@ -1,10 +0,0 @@
/* eslint-disable no-bitwise */
// ----------------------------------------------------------------------
export function uuidv4() {
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
const r = (Math.random() * 16) | 0;
const v = c === 'x' ? r : (r & 0x3) | 0x8;
return v.toString(16);
});
}

View File

@@ -1,66 +0,0 @@
import express from 'express';
import helmet from 'helmet';
import compression from 'compression';
import cors from 'cors';
import passport from 'passport';
import httpStatus from 'http-status';
import config from './config/config';
import morgan from './config/morgan';
import xss from './middlewares/xss';
import { jwtStrategy } from './config/passport';
import { authLimiter } from './middlewares/rateLimiter';
import routes from './routes/v1';
import { errorConverter, errorHandler } from './middlewares/error';
import ApiError from './utils/ApiError';
const app = express();
if (config.env !== 'test') {
app.use(morgan.successHandler);
app.use(morgan.errorHandler);
}
// set security HTTP headers
app.use(helmet());
// parse json request body
app.use(express.json());
// parse urlencoded request body
app.use(express.urlencoded({ extended: true }));
// sanitize request data
app.use(xss());
// gzip compression
app.use(compression());
// enable cors
app.use(cors());
app.options('*', cors());
// jwt authentication
app.use(passport.initialize());
passport.use('jwt', jwtStrategy);
// limit repeated failed requests to auth endpoints
if (config.env === 'production') {
app.use('/v1/auth', authLimiter);
}
// v1 api routes
app.use('/v1', routes);
// send back a 404 error for any unknown api request
app.use((req, res, next) => {
console.log(req.url);
next(new ApiError(httpStatus.NOT_FOUND, 'Not found'));
});
// convert error to ApiError, if needed
app.use(errorConverter);
// handle error
app.use(errorHandler);
export default app;

View File

@@ -1,16 +0,0 @@
import { PrismaClient } from '@prisma/client';
import config from './config/config';
// add prisma to the NodeJS global type
interface CustomNodeJsGlobal extends Global {
prisma: PrismaClient;
}
// Prevent multiple instances of Prisma Client in development
declare const global: CustomNodeJsGlobal;
const prisma = global.prisma || new PrismaClient();
if (config.env === 'development') global.prisma = prisma;
export default prisma;

View File

@@ -1,61 +0,0 @@
import dotenv from 'dotenv';
import path from 'path';
import Joi from 'joi';
dotenv.config({ path: path.join(process.cwd(), '.env') });
const envVarsSchema = Joi.object()
.keys({
NODE_ENV: Joi.string().valid('production', 'development', 'test').required(),
PORT: Joi.number().default(3000),
JWT_SECRET: Joi.string().required().description('JWT secret key'),
JWT_ACCESS_EXPIRATION_MINUTES: Joi.number()
.default(30)
.description('minutes after which access tokens expire'),
JWT_REFRESH_EXPIRATION_DAYS: Joi.number()
.default(30)
.description('days after which refresh tokens expire'),
JWT_RESET_PASSWORD_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which reset password token expires'),
JWT_VERIFY_EMAIL_EXPIRATION_MINUTES: Joi.number()
.default(10)
.description('minutes after which verify email token expires'),
SMTP_HOST: Joi.string().description('server that will send the emails'),
SMTP_PORT: Joi.number().description('port to connect to the email server'),
SMTP_USERNAME: Joi.string().description('username for email server'),
SMTP_PASSWORD: Joi.string().description('password for email server'),
EMAIL_FROM: Joi.string().description('the from field in the emails sent by the app')
})
.unknown();
const { value: envVars, error } = envVarsSchema
.prefs({ errors: { label: 'key' } })
.validate(process.env);
if (error) {
throw new Error(`Config validation error: ${error.message}`);
}
export default {
env: envVars.NODE_ENV,
port: envVars.PORT,
jwt: {
secret: envVars.JWT_SECRET,
accessExpirationMinutes: envVars.JWT_ACCESS_EXPIRATION_MINUTES,
refreshExpirationDays: envVars.JWT_REFRESH_EXPIRATION_DAYS,
resetPasswordExpirationMinutes: envVars.JWT_RESET_PASSWORD_EXPIRATION_MINUTES,
verifyEmailExpirationMinutes: envVars.JWT_VERIFY_EMAIL_EXPIRATION_MINUTES
},
email: {
smtp: {
host: envVars.SMTP_HOST,
port: envVars.SMTP_PORT,
auth: {
user: envVars.SMTP_USERNAME,
pass: envVars.SMTP_PASSWORD
}
},
from: envVars.EMAIL_FROM
}
};

View File

@@ -1,26 +0,0 @@
import winston from 'winston';
import config from './config';
const enumerateErrorFormat = winston.format((info) => {
if (info instanceof Error) {
Object.assign(info, { message: info.stack });
}
return info;
});
const logger = winston.createLogger({
level: config.env === 'development' ? 'debug' : 'info',
format: winston.format.combine(
enumerateErrorFormat(),
config.env === 'development' ? winston.format.colorize() : winston.format.uncolorize(),
winston.format.splat(),
winston.format.printf(({ level, message }) => `${level}: ${message}`)
),
transports: [
new winston.transports.Console({
stderrLevels: ['error']
})
]
});
export default logger;

View File

@@ -1,25 +0,0 @@
import { Response } from 'express';
import morgan from 'morgan';
import config from './config';
import logger from './logger';
morgan.token('message', (req, res: Response) => res.locals.errorMessage || '');
const getIpFormat = () => (config.env === 'production' ? ':remote-addr - ' : '');
const successResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms`;
const errorResponseFormat = `${getIpFormat()}:method :url :status - :response-time ms - message: :message`;
export const successHandler = morgan(successResponseFormat, {
skip: (req, res) => res.statusCode >= 400,
stream: { write: (message) => logger.info(message.trim()) }
});
export const errorHandler = morgan(errorResponseFormat, {
skip: (req, res) => res.statusCode < 400,
stream: { write: (message) => logger.error(message.trim()) }
});
export default {
successHandler,
errorHandler
};

View File

@@ -1,33 +0,0 @@
import prisma from '../client';
import { Strategy as JwtStrategy, ExtractJwt, VerifyCallback } from 'passport-jwt';
import config from './config';
import { TokenType } from '@prisma/client';
const jwtOptions = {
secretOrKey: config.jwt.secret,
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken()
};
const jwtVerify: VerifyCallback = async (payload, done) => {
try {
if (payload.type !== TokenType.ACCESS) {
throw new Error('Invalid token type');
}
const user = await prisma.user.findUnique({
select: {
id: true,
email: true,
name: true
},
where: { id: payload.sub }
});
if (!user) {
return done(null, false);
}
done(null, user);
} catch (error) {
done(error, false);
}
};
export const jwtStrategy = new JwtStrategy(jwtOptions, jwtVerify);

View File

@@ -1,9 +0,0 @@
import { Role } from '@prisma/client';
const allRoles = {
[Role.USER]: [],
[Role.ADMIN]: ['getUsers', 'manageUsers']
};
export const roles = Object.keys(allRoles);
export const roleRights = new Map(Object.entries(allRoles));

View File

@@ -1,64 +0,0 @@
import httpStatus from 'http-status';
import catchAsync from '../utils/catchAsync';
import { authService, userService, tokenService, emailService } from '../services';
import exclude from '../utils/exclude';
import { User } from '@prisma/client';
const register = catchAsync(async (req, res) => {
const { email, password } = req.body;
const user = await userService.createUser(email, password);
const userWithoutPassword = exclude(user, ['password', 'createdAt', 'updatedAt']);
const tokens = await tokenService.generateAuthTokens(user);
res.status(httpStatus.CREATED).send({ user: userWithoutPassword, tokens });
});
const login = catchAsync(async (req, res) => {
const { email, password } = req.body;
const user = await authService.loginUserWithEmailAndPassword(email, password);
const tokens = await tokenService.generateAuthTokens(user);
res.send({ user, tokens });
});
const logout = catchAsync(async (req, res) => {
await authService.logout(req.body.refreshToken);
res.status(httpStatus.NO_CONTENT).send();
});
const refreshTokens = catchAsync(async (req, res) => {
const tokens = await authService.refreshAuth(req.body.refreshToken);
res.send({ ...tokens });
});
const forgotPassword = catchAsync(async (req, res) => {
const resetPasswordToken = await tokenService.generateResetPasswordToken(req.body.email);
await emailService.sendResetPasswordEmail(req.body.email, resetPasswordToken);
res.status(httpStatus.NO_CONTENT).send();
});
const resetPassword = catchAsync(async (req, res) => {
await authService.resetPassword(req.query.token as string, req.body.password);
res.status(httpStatus.NO_CONTENT).send();
});
const sendVerificationEmail = catchAsync(async (req, res) => {
const user = req.user as User;
const verifyEmailToken = await tokenService.generateVerifyEmailToken(user);
await emailService.sendVerificationEmail(user.email, verifyEmailToken);
res.status(httpStatus.NO_CONTENT).send();
});
const verifyEmail = catchAsync(async (req, res) => {
await authService.verifyEmail(req.query.token as string);
res.status(httpStatus.NO_CONTENT).send();
});
export default {
register,
login,
logout,
refreshTokens,
forgotPassword,
resetPassword,
sendVerificationEmail,
verifyEmail
};

View File

@@ -1,54 +0,0 @@
import httpStatus from 'http-status';
import pick from '../utils/pick';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService } from '../services';
import eventService from '../services/event.service';
// const createUser = catchAsync(async (req, res) => {
// const { email, password, name, role } = req.body;
// const user = await userService.createUser(email, password, name, role);
// res.status(httpStatus.CREATED).send(user);
// });
// const getUsers = catchAsync(async (req, res) => {
// const filter = pick(req.query, ['name', 'role']);
// const options = pick(req.query, ['sortBy', 'limit', 'page']);
// const result = await userService.queryUsers(filter, options);
// res.send(result);
// });
const getEvents = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await eventService.queryEvents(filter, options);
res.send(result);
});
const getEvent = catchAsync(async (req, res) => {
const eventId = parseInt(req.params.eventId);
const event = await eventService.getEventById(eventId);
if (!event) {
throw new ApiError(httpStatus.NOT_FOUND, 'Event not found');
}
res.send(event);
});
// const updateUser = catchAsync(async (req, res) => {
// const user = await userService.updateUserById(req.params.userId, req.body);
// res.send(user);
// });
// const deleteUser = catchAsync(async (req, res) => {
// await userService.deleteUserById(req.params.userId);
// res.status(httpStatus.NO_CONTENT).send();
// });
export default {
// createUser,
getEvents,
getEvent
// getUser
// updateUser,
// deleteUser
};

View File

@@ -1,9 +0,0 @@
import catchAsync from '../utils/catchAsync';
const getHelloworld = catchAsync(async (req, res) => {
res.send({ hello: 'world' });
});
export default {
getHelloworld
};

View File

@@ -1,2 +0,0 @@
export { default as authController } from './auth.controller';
export { default as userController } from './user.controller';

View File

@@ -1,55 +0,0 @@
import httpStatus from 'http-status';
import pick from '../utils/pick';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService } from '../services';
import eventService from '../services/event.service';
import memberService from '../services/member.service';
// const createUser = catchAsync(async (req, res) => {
// const { email, password, name, role } = req.body;
// const user = await userService.createUser(email, password, name, role);
// res.status(httpStatus.CREATED).send(user);
// });
// const getUsers = catchAsync(async (req, res) => {
// const filter = pick(req.query, ['name', 'role']);
// const options = pick(req.query, ['sortBy', 'limit', 'page']);
// const result = await userService.queryUsers(filter, options);
// res.send(result);
// });
const getMembers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await memberService.queryMembers(filter, options);
res.send(result);
});
const getMember = catchAsync(async (req, res) => {
const memberId = parseInt(req.params.memberId);
const member = await memberService.getMemberById(memberId);
if (!member) {
throw new ApiError(httpStatus.NOT_FOUND, 'Member not found');
}
res.send(member);
});
// const updateUser = catchAsync(async (req, res) => {
// const user = await userService.updateUserById(req.params.userId, req.body);
// res.send(user);
// });
// const deleteUser = catchAsync(async (req, res) => {
// await userService.deleteUserById(req.params.userId);
// res.status(httpStatus.NO_CONTENT).send();
// });
export default {
// createUser,
getMembers,
getMember
// getUser
// updateUser,
// deleteUser
};

View File

@@ -1,63 +0,0 @@
// REQ0047/order-page
//
// PURPOSE:
// - provide api access to backend db for orders
//
// RULES:
// - T.B.A.
//
import httpStatus from 'http-status';
import pick from '../utils/pick';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService } from '../services';
import eventService from '../services/event.service';
import orderService from '../services/order.service';
// const createUser = catchAsync(async (req, res) => {
// const { email, password, name, role } = req.body;
// const user = await userService.createUser(email, password, name, role);
// res.status(httpStatus.CREATED).send(user);
// });
// const getUsers = catchAsync(async (req, res) => {
// const filter = pick(req.query, ['name', 'role']);
// const options = pick(req.query, ['sortBy', 'limit', 'page']);
// const result = await userService.queryUsers(filter, options);
// res.send(result);
// });
const getOrders = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await orderService.queryOrders(filter, options);
res.send(result);
});
const getOrder = catchAsync(async (req, res) => {
const eventId = parseInt(req.params.eventId);
const event = await orderService.getOrderById(eventId);
if (!event) {
throw new ApiError(httpStatus.NOT_FOUND, 'Event not found');
}
res.send(event);
});
// const updateUser = catchAsync(async (req, res) => {
// const user = await userService.updateUserById(req.params.userId, req.body);
// res.send(user);
// });
// const deleteUser = catchAsync(async (req, res) => {
// await userService.deleteUserById(req.params.userId);
// res.status(httpStatus.NO_CONTENT).send();
// });
export default {
// createUser,
getOrders,
getOrder
// getUser
// updateUser,
// deleteUser
};

View File

@@ -1,56 +0,0 @@
import httpStatus from 'http-status';
import pick from '../utils/pick';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService } from '../services';
import eventService from '../services/event.service';
import memberService from '../services/member.service';
import profileService from '../services/profile.service';
// const createUser = catchAsync(async (req, res) => {
// const { email, password, name, role } = req.body;
// const user = await userService.createUser(email, password, name, role);
// res.status(httpStatus.CREATED).send(user);
// });
// const getUsers = catchAsync(async (req, res) => {
// const filter = pick(req.query, ['name', 'role']);
// const options = pick(req.query, ['sortBy', 'limit', 'page']);
// const result = await userService.queryUsers(filter, options);
// res.send(result);
// });
const getMembers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await memberService.queryMembers(filter, options);
res.send(result);
});
const getProfile = catchAsync(async (req, res) => {
const profileId = parseInt(req.params.profileId);
const profile = await profileService.getProfileById(profileId);
if (!profile) {
throw new ApiError(httpStatus.NOT_FOUND, 'Profile not found');
}
res.send(profile);
});
// const updateUser = catchAsync(async (req, res) => {
// const user = await userService.updateUserById(req.params.userId, req.body);
// res.send(user);
// });
// const deleteUser = catchAsync(async (req, res) => {
// await userService.deleteUserById(req.params.userId);
// res.status(httpStatus.NO_CONTENT).send();
// });
export default {
// createUser,
getMembers,
getProfile
// getUser
// updateUser,
// deleteUser
};

View File

@@ -1,44 +0,0 @@
import httpStatus from 'http-status';
import pick from '../utils/pick';
import ApiError from '../utils/ApiError';
import catchAsync from '../utils/catchAsync';
import { userService } from '../services';
const createUser = catchAsync(async (req, res) => {
const { email, password, name, role } = req.body;
const user = await userService.createUser(email, password, name, role);
res.status(httpStatus.CREATED).send(user);
});
const getUsers = catchAsync(async (req, res) => {
const filter = pick(req.query, ['name', 'role']);
const options = pick(req.query, ['sortBy', 'limit', 'page']);
const result = await userService.queryUsers(filter, options);
res.send(result);
});
const getUser = catchAsync(async (req, res) => {
const user = await userService.getUserById(req.params.userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
res.send(user);
});
const updateUser = catchAsync(async (req, res) => {
const user = await userService.updateUserById(req.params.userId, req.body);
res.send(user);
});
const deleteUser = catchAsync(async (req, res) => {
await userService.deleteUserById(req.params.userId);
res.status(httpStatus.NO_CONTENT).send();
});
export default {
createUser,
getUsers,
getUser,
updateUser,
deleteUser
};

View File

@@ -1,92 +0,0 @@
components:
schemas:
User:
type: object
properties:
id:
type: string
email:
type: string
format: email
name:
type: string
role:
type: string
enum: [USER, ADMIN]
example:
id: 5ebac534954b54139806c112
email: fake@example.com
name: fake name
role: USER
Token:
type: object
properties:
token:
type: string
expires:
type: string
format: date-time
example:
token: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
expires: 2020-05-12T16:18:04.793Z
AuthTokens:
type: object
properties:
access:
$ref: '#/components/schemas/Token'
refresh:
$ref: '#/components/schemas/Token'
Error:
type: object
properties:
code:
type: number
message:
type: string
responses:
DuplicateEmail:
description: Email already taken
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 400
message: Email already taken
Unauthorized:
description: Unauthorized
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 401
message: Please authenticate
Forbidden:
description: Forbidden
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 403
message: Forbidden
NotFound:
description: Not found
content:
application/json:
schema:
$ref: '#/components/schemas/Error'
example:
code: 404
message: Not found
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT

View File

@@ -1,21 +0,0 @@
import { name, version, repository } from '../../package.json';
import config from '../config/config';
const swaggerDef = {
openapi: '3.0.0',
info: {
title: `${name} API documentation`,
version,
license: {
name: 'MIT',
url: repository
}
},
servers: [
{
url: `http://localhost:${config.port}/v1`
}
]
};
export default swaggerDef;

View File

@@ -1,39 +0,0 @@
import { Server } from 'http';
import app from './app';
import prisma from './client';
import config from './config/config';
import logger from './config/logger';
let server: Server;
prisma.$connect().then(() => {
logger.info('Connected to SQL Database');
server = app.listen(config.port, () => {
logger.info(`Listening to port ${config.port}`);
});
});
const exitHandler = () => {
if (server) {
server.close(() => {
logger.info('Server closed');
process.exit(1);
});
} else {
process.exit(1);
}
};
const unexpectedErrorHandler = (error: unknown) => {
logger.error(error);
exitHandler();
};
process.on('uncaughtException', unexpectedErrorHandler);
process.on('unhandledRejection', unexpectedErrorHandler);
process.on('SIGTERM', () => {
logger.info('SIGTERM received');
if (server) {
server.close();
}
});

View File

@@ -1,48 +0,0 @@
import passport from 'passport';
import httpStatus from 'http-status';
import ApiError from '../utils/ApiError';
import { roleRights } from '../config/roles';
import { NextFunction, Request, Response } from 'express';
import { User } from '@prisma/client';
const verifyCallback =
(
req: any,
resolve: (value?: unknown) => void,
reject: (reason?: unknown) => void,
requiredRights: string[]
) =>
async (err: unknown, user: User | false, info: unknown) => {
if (err || info || !user) {
return reject(new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate'));
}
req.user = user;
if (requiredRights.length) {
const userRights = roleRights.get(user.role) ?? [];
const hasRequiredRights = requiredRights.every((requiredRight) =>
userRights.includes(requiredRight)
);
if (!hasRequiredRights && req.params.userId !== user.id) {
return reject(new ApiError(httpStatus.FORBIDDEN, 'Forbidden'));
}
}
resolve();
};
const auth =
(...requiredRights: string[]) =>
async (req: Request, res: Response, next: NextFunction) => {
return new Promise((resolve, reject) => {
passport.authenticate(
'jwt',
{ session: false },
verifyCallback(req, resolve, reject, requiredRights)
)(req, res, next);
})
.then(() => next())
.catch((err) => next(err));
};
export default auth;

View File

@@ -1,42 +0,0 @@
import { ErrorRequestHandler } from 'express';
import { Prisma } from '@prisma/client';
import httpStatus from 'http-status';
import config from '../config/config';
import logger from '../config/logger';
import ApiError from '../utils/ApiError';
export const errorConverter: ErrorRequestHandler = (err, req, res, next) => {
let error = err;
if (!(error instanceof ApiError)) {
const statusCode =
error.statusCode || error instanceof Prisma.PrismaClientKnownRequestError
? httpStatus.BAD_REQUEST
: httpStatus.INTERNAL_SERVER_ERROR;
const message = error.message || httpStatus[statusCode];
error = new ApiError(statusCode, message, false, err.stack);
}
next(error);
};
// eslint-disable-next-line no-unused-vars, @typescript-eslint/no-unused-vars
export const errorHandler: ErrorRequestHandler = (err, req, res, next) => {
let { statusCode, message } = err;
if (config.env === 'production' && !err.isOperational) {
statusCode = httpStatus.INTERNAL_SERVER_ERROR;
message = httpStatus[httpStatus.INTERNAL_SERVER_ERROR];
}
res.locals.errorMessage = err.message;
const response = {
code: statusCode,
message,
...(config.env === 'development' && { stack: err.stack })
};
if (config.env === 'development') {
logger.error(err);
}
res.status(statusCode).send(response);
};

View File

@@ -1,7 +0,0 @@
import rateLimit from 'express-rate-limit';
export const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 20,
skipSuccessfulRequests: true
});

View File

@@ -1,21 +0,0 @@
import httpStatus from 'http-status';
import ApiError from '../utils/ApiError';
import { NextFunction, Request, Response } from 'express';
import pick from '../utils/pick';
import Joi from 'joi';
const validate = (schema: object) => (req: Request, res: Response, next: NextFunction) => {
const validSchema = pick(schema, ['params', 'query', 'body']);
const obj = pick(req, Object.keys(validSchema));
const { value, error } = Joi.compile(validSchema)
.prefs({ errors: { label: 'key' }, abortEarly: false })
.validate(obj);
if (error) {
const errorMessage = error.details.map((details) => details.message).join(', ');
return next(new ApiError(httpStatus.BAD_REQUEST, errorMessage));
}
Object.assign(req, value);
return next();
};
export default validate;

View File

@@ -1,31 +0,0 @@
import { NextFunction, Request, Response } from 'express';
import { inHTMLData } from 'xss-filters';
/**
* Clean for xss.
* @param {string/object} data - The value to sanitize
* @return {string/object} The sanitized value
*/
export const clean = <T>(data: T | string = ''): T => {
let isObject = false;
if (typeof data === 'object') {
data = JSON.stringify(data);
isObject = true;
}
data = inHTMLData(data as string).trim();
if (isObject) data = JSON.parse(data);
return data as T;
};
const middleware = () => {
return (req: Request, res: Response, next: NextFunction) => {
if (req.body) req.body = clean(req.body);
if (req.query) req.query = clean(req.query);
if (req.params) req.params = clean(req.params);
next();
};
};
export default middleware;

View File

@@ -1,303 +0,0 @@
import express from 'express';
import validate from '../../middlewares/validate';
import authValidation from '../../validations/auth.validation';
import { authController } from '../../controllers';
import auth from '../../middlewares/auth';
const router = express.Router();
router.post('/register', validate(authValidation.register), authController.register);
router.post('/login', validate(authValidation.login), authController.login);
router.post('/logout', validate(authValidation.logout), authController.logout);
router.post(
'/refresh-tokens',
validate(authValidation.refreshTokens),
authController.refreshTokens
);
router.post(
'/forgot-password',
validate(authValidation.forgotPassword),
authController.forgotPassword
);
router.post(
'/reset-password',
validate(authValidation.resetPassword),
authController.resetPassword
);
router.post('/send-verification-email', auth(), authController.sendVerificationEmail);
router.post('/verify-email', validate(authValidation.verifyEmail), authController.verifyEmail);
export default router;
/**
* @swagger
* tags:
* name: Auth
* description: Authentication
*/
/**
* @swagger
* /auth/register:
* post:
* summary: Register as user
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* tokens:
* $ref: '#/components/schemas/AuthTokens'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
*/
/**
* @swagger
* /auth/login:
* post:
* summary: Login
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* - password
* properties:
* email:
* type: string
* format: email
* password:
* type: string
* format: password
* example:
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* user:
* $ref: '#/components/schemas/User'
* tokens:
* $ref: '#/components/schemas/AuthTokens'
* "401":
* description: Invalid email or password
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: Invalid email or password
*/
/**
* @swagger
* /auth/logout:
* post:
* summary: Logout
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - refreshToken
* properties:
* refreshToken:
* type: string
* example:
* refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
* responses:
* "204":
* description: No content
* "404":
* $ref: '#/components/responses/NotFound'
*/
/**
* @swagger
* /auth/refresh-tokens:
* post:
* summary: Refresh auth tokens
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - refreshToken
* properties:
* refreshToken:
* type: string
* example:
* refreshToken: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1ZWJhYzUzNDk1NGI1NDEzOTgwNmMxMTIiLCJpYXQiOjE1ODkyOTg0ODQsImV4cCI6MTU4OTMwMDI4NH0.m1U63blB0MLej_WfB7yC2FTMnCziif9X8yzwDEfJXAg
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/AuthTokens'
* "401":
* $ref: '#/components/responses/Unauthorized'
*/
/**
* @swagger
* /auth/forgot-password:
* post:
* summary: Forgot password
* description: An email will be sent to reset password.
* tags: [Auth]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - email
* properties:
* email:
* type: string
* format: email
* example:
* email: fake@example.com
* responses:
* "204":
* description: No content
* "404":
* $ref: '#/components/responses/NotFound'
*/
/**
* @swagger
* /auth/reset-password:
* post:
* summary: Reset password
* tags: [Auth]
* parameters:
* - in: query
* name: token
* required: true
* schema:
* type: string
* description: The reset password token
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - password
* properties:
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* password: password1
* responses:
* "204":
* description: No content
* "401":
* description: Password reset failed
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: Password reset failed
*/
/**
* @swagger
* /auth/send-verification-email:
* post:
* summary: Send verification email
* description: An email will be sent to verify email.
* tags: [Auth]
* security:
* - bearerAuth: []
* responses:
* "204":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
*/
/**
* @swagger
* /auth/verify-email:
* post:
* summary: verify email
* tags: [Auth]
* parameters:
* - in: query
* name: token
* required: true
* schema:
* type: string
* description: The verify email token
* responses:
* "204":
* description: No content
* "401":
* description: verify email failed
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/Error'
* example:
* code: 401
* message: verify email failed
*/

View File

@@ -1,21 +0,0 @@
import express from 'express';
import swaggerJsdoc from 'swagger-jsdoc';
import swaggerUi from 'swagger-ui-express';
import swaggerDefinition from '../../docs/swaggerDef';
const router = express.Router();
const specs = swaggerJsdoc({
swaggerDefinition,
apis: ['src/docs/*.yml', 'src/routes/v1/*.ts']
});
router.use('/', swaggerUi.serve);
router.get(
'/',
swaggerUi.setup(specs, {
explorer: true
})
);
export default router;

View File

@@ -1,255 +0,0 @@
import express from 'express';
// import auth from '../../middlewares/auth';
// import validate from '../../middlewares/validate';
// import { userValidation } from '../../validations';
// import { userController } from '../../controllers';
import eventController from '../../controllers/event.controller';
import helloworldController from '../../controllers/helloworld.controller';
const router = express.Router();
router.route('/helloworld').get(helloworldController.getHelloworld);
router
.route('/')
//
.get(eventController.getEvents);
// .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
router.route('/:eventId').get(eventController.getEvent);
// .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
// .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
export default router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,38 +0,0 @@
import express from 'express';
import helloworldController from '../../controllers/helloworld.controller';
const router = express.Router();
router.route('/').get(helloworldController.getHelloworld);
export default router;
/**
* @swagger
* tags:
* name: Helloworld
* description: Simple hello world endpoint
*/
/**
* @swagger
* /helloworld:
* get:
* summary: Get hello world message
* description: Returns a simple hello world message
* tags: [Helloworld]
* responses:
* "200":
* description: Successful response
* content:
* application/json:
* schema:
* type: object
* properties:
* message:
* type: string
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,47 +0,0 @@
import express from 'express';
import authRoute from './auth.route';
import userRoute from './user.route';
import eventRoute from './event.route';
import memberRoute from './member.route';
import orderRoute from './order.route';
import profileRoute from './profile.route';
//
import helloworldRoute from './helloworld.route';
import docsRoute from './docs.route';
import config from '../../config/config';
const router = express.Router();
const defaultRoutes = [
{ path: '/auth', route: authRoute },
{ path: '/users', route: userRoute },
//
{ path: '/events', route: eventRoute },
{ path: '/members', route: memberRoute },
{ path: '/orders', route: orderRoute },
{ path: '/profile', route: profileRoute },
//
{ path: '/helloworld', route: helloworldRoute }
];
const devRoutes = [
// routes available only in development mode
{
path: '/docs',
route: docsRoute
}
];
defaultRoutes.forEach((route) => {
router.use(route.path, route.route);
});
/* istanbul ignore next */
if (config.env === 'development') {
devRoutes.forEach((route) => {
router.use(route.path, route.route);
});
}
export default router;

View File

@@ -1,255 +0,0 @@
import express from 'express';
// import auth from '../../middlewares/auth';
// import validate from '../../middlewares/validate';
// import { userValidation } from '../../validations';
// import { userController } from '../../controllers';
import memberController from '../../controllers/member.controller';
import helloworldController from '../../controllers/helloworld.controller';
const router = express.Router();
router.route('/helloworld').get(helloworldController.getHelloworld);
router
.route('/')
//
.get(memberController.getMembers);
// .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
router.route('/:memberId').get(memberController.getMember);
// .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
// .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
export default router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,264 +0,0 @@
// REQ0047/order-page
//
// PURPOSE:
// - provide api access to backend db for orders
//
// RULES:
// - T.B.A.
//
import express from 'express';
// import auth from '../../middlewares/auth';
// import validate from '../../middlewares/validate';
// import { userValidation } from '../../validations';
// import { userController } from '../../controllers';
import eventController from '../../controllers/event.controller';
import orderController from '../../controllers/order.controller';
import helloworldController from '../../controllers/helloworld.controller';
const router = express.Router();
router.route('/helloworld').get(helloworldController.getHelloworld);
router
.route('/')
//
.get(orderController.getOrders);
// .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
router.route('/:eventId').get(orderController.getOrder);
// .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
// .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
export default router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,256 +0,0 @@
import express from 'express';
// import auth from '../../middlewares/auth';
// import validate from '../../middlewares/validate';
// import { userValidation } from '../../validations';
// import { userController } from '../../controllers';
import memberController from '../../controllers/member.controller';
import profileController from '../../controllers/profile.controller';
import helloworldController from '../../controllers/helloworld.controller';
const router = express.Router();
router.route('/helloworld').get(helloworldController.getHelloworld);
router
.route('/')
//
.get(profileController.getMembers);
// .post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
router.route('/:profileId').get(profileController.getProfile);
// .patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
// .delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
export default router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,252 +0,0 @@
import express from 'express';
import auth from '../../middlewares/auth';
import validate from '../../middlewares/validate';
import { userValidation } from '../../validations';
import { userController } from '../../controllers';
const router = express.Router();
router
.route('/')
.post(auth('manageUsers'), validate(userValidation.createUser), userController.createUser)
.get(auth('getUsers'), validate(userValidation.getUsers), userController.getUsers);
router
.route('/:userId')
.get(auth('getUsers'), validate(userValidation.getUser), userController.getUser)
.patch(auth('manageUsers'), validate(userValidation.updateUser), userController.updateUser)
.delete(auth('manageUsers'), validate(userValidation.deleteUser), userController.deleteUser);
export default router;
/**
* @swagger
* tags:
* name: Users
* description: User management and retrieval
*/
/**
* @swagger
* /users:
* post:
* summary: Create a user
* description: Only admins can create other users.
* tags: [Users]
* security:
* - bearerAuth: []
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* required:
* - name
* - email
* - password
* - role
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* role:
* type: string
* enum: [user, admin]
* example:
* name: fake name
* email: fake@example.com
* password: password1
* role: user
* responses:
* "201":
* description: Created
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*
* get:
* summary: Get all users
* description: Only admins can retrieve all users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: query
* name: name
* schema:
* type: string
* description: User name
* - in: query
* name: role
* schema:
* type: string
* description: User role
* - in: query
* name: sortBy
* schema:
* type: string
* description: sort by query in the form of field:desc/asc (ex. name:asc)
* - in: query
* name: limit
* schema:
* type: integer
* minimum: 1
* default: 10
* description: Maximum number of users
* - in: query
* name: page
* schema:
* type: integer
* minimum: 1
* default: 1
* description: Page number
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* type: object
* properties:
* results:
* type: array
* items:
* $ref: '#/components/schemas/User'
* page:
* type: integer
* example: 1
* limit:
* type: integer
* example: 10
* totalPages:
* type: integer
* example: 1
* totalResults:
* type: integer
* example: 1
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
*/
/**
* @swagger
* /users/{id}:
* get:
* summary: Get a user
* description: Logged in users can fetch only their own user information. Only admins can fetch other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* patch:
* summary: Update a user
* description: Logged in users can only update their own information. Only admins can update other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* name:
* type: string
* email:
* type: string
* format: email
* description: must be unique
* password:
* type: string
* format: password
* minLength: 8
* description: At least one number and one letter
* example:
* name: fake name
* email: fake@example.com
* password: password1
* responses:
* "200":
* description: OK
* content:
* application/json:
* schema:
* $ref: '#/components/schemas/User'
* "400":
* $ref: '#/components/responses/DuplicateEmail'
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*
* delete:
* summary: Delete a user
* description: Logged in users can delete only themselves. Only admins can delete other users.
* tags: [Users]
* security:
* - bearerAuth: []
* parameters:
* - in: path
* name: id
* required: true
* schema:
* type: string
* description: User id
* responses:
* "200":
* description: No content
* "401":
* $ref: '#/components/responses/Unauthorized'
* "403":
* $ref: '#/components/responses/Forbidden'
* "404":
* $ref: '#/components/responses/NotFound'
*/

View File

@@ -1,124 +0,0 @@
import httpStatus from 'http-status';
import tokenService from './token.service';
import userService from './user.service';
import ApiError from '../utils/ApiError';
import { TokenType, User } from '@prisma/client';
import prisma from '../client';
import { encryptPassword, isPasswordMatch } from '../utils/encryption';
import { AuthTokensResponse } from '../types/response';
import exclude from '../utils/exclude';
/**
* Login with username and password
* @param {string} email
* @param {string} password
* @returns {Promise<Omit<User, 'password'>>}
*/
const loginUserWithEmailAndPassword = async (
email: string,
password: string
): Promise<Omit<User, 'password'>> => {
const user = await userService.getUserByEmail(email, [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
]);
if (!user || !(await isPasswordMatch(password, user.password as string))) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Incorrect email or password');
}
return exclude(user, ['password']);
};
/**
* Logout
* @param {string} refreshToken
* @returns {Promise<void>}
*/
const logout = async (refreshToken: string): Promise<void> => {
const refreshTokenData = await prisma.token.findFirst({
where: {
token: refreshToken,
type: TokenType.REFRESH,
blacklisted: false
}
});
if (!refreshTokenData) {
throw new ApiError(httpStatus.NOT_FOUND, 'Not found');
}
await prisma.token.delete({ where: { id: refreshTokenData.id } });
};
/**
* Refresh auth tokens
* @param {string} refreshToken
* @returns {Promise<AuthTokensResponse>}
*/
const refreshAuth = async (refreshToken: string): Promise<AuthTokensResponse> => {
try {
const refreshTokenData = await tokenService.verifyToken(refreshToken, TokenType.REFRESH);
const { userId } = refreshTokenData;
await prisma.token.delete({ where: { id: refreshTokenData.id } });
return tokenService.generateAuthTokens({ id: userId });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Please authenticate');
}
};
/**
* Reset password
* @param {string} resetPasswordToken
* @param {string} newPassword
* @returns {Promise<void>}
*/
const resetPassword = async (resetPasswordToken: string, newPassword: string): Promise<void> => {
try {
const resetPasswordTokenData = await tokenService.verifyToken(
resetPasswordToken,
TokenType.RESET_PASSWORD
);
const user = await userService.getUserById(resetPasswordTokenData.userId);
if (!user) {
throw new Error();
}
const encryptedPassword = await encryptPassword(newPassword);
await userService.updateUserById(user.id, { password: encryptedPassword });
await prisma.token.deleteMany({ where: { userId: user.id, type: TokenType.RESET_PASSWORD } });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Password reset failed');
}
};
/**
* Verify email
* @param {string} verifyEmailToken
* @returns {Promise<void>}
*/
const verifyEmail = async (verifyEmailToken: string): Promise<void> => {
try {
const verifyEmailTokenData = await tokenService.verifyToken(
verifyEmailToken,
TokenType.VERIFY_EMAIL
);
await prisma.token.deleteMany({
where: { userId: verifyEmailTokenData.userId, type: TokenType.VERIFY_EMAIL }
});
await userService.updateUserById(verifyEmailTokenData.userId, { isEmailVerified: true });
} catch (error) {
throw new ApiError(httpStatus.UNAUTHORIZED, 'Email verification failed');
}
};
export default {
loginUserWithEmailAndPassword,
isPasswordMatch,
encryptPassword,
logout,
refreshAuth,
resetPassword,
verifyEmail
};

View File

@@ -1,66 +0,0 @@
import nodemailer from 'nodemailer';
import config from '../config/config';
import logger from '../config/logger';
const transport = nodemailer.createTransport(config.email.smtp);
/* istanbul ignore next */
if (config.env !== 'test') {
transport
.verify()
.then(() => logger.info('Connected to email server'))
.catch(() =>
logger.warn(
'Unable to connect to email server. Make sure you have configured the SMTP options in .env'
)
);
}
/**
* Send an email
* @param {string} to
* @param {string} subject
* @param {string} text
* @returns {Promise}
*/
const sendEmail = async (to: string, subject: string, text: string) => {
const msg = { from: config.email.from, to, subject, text };
await transport.sendMail(msg);
};
/**
* Send reset password email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendResetPasswordEmail = async (to: string, token: string) => {
const subject = 'Reset password';
// replace this url with the link to the reset password page of your front-end app
const resetPasswordUrl = `http://link-to-app/reset-password?token=${token}`;
const text = `Dear user,
To reset your password, click on this link: ${resetPasswordUrl}
If you did not request any password resets, then ignore this email.`;
await sendEmail(to, subject, text);
};
/**
* Send verification email
* @param {string} to
* @param {string} token
* @returns {Promise}
*/
const sendVerificationEmail = async (to: string, token: string) => {
const subject = 'Email Verification';
// replace this url with the link to the email verification page of your front-end app
const verificationEmailUrl = `http://link-to-app/verify-email?token=${token}`;
const text = `Dear user,
To verify your email, click on this link: ${verificationEmailUrl}`;
await sendEmail(to, subject, text);
};
export default {
transport,
sendEmail,
sendResetPasswordEmail,
sendVerificationEmail
};

View File

@@ -1,156 +0,0 @@
import { User, Role, Prisma, Event } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryEvents = async <Key extends keyof Event>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<Event, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const events = await prisma.event.findMany();
return events as Pick<Event, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getEventById = async <Key extends keyof Event>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Event, Key> | null> => {
return prisma.event.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Event, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryEvents,
getEventById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -1,5 +0,0 @@
export { default as authService } from './auth.service';
export { default as userService } from './user.service';
export { default as tokenService } from './token.service';
export { default as emailService } from './email.service';
export { default as eventService } from './event.service';

View File

@@ -1,147 +0,0 @@
import { User, Role, Prisma, Event, Member } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryMembers = async <Key extends keyof Member>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const members = await prisma.member.findMany();
return members as Pick<Member, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getMemberById = async <Key extends keyof Member>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key> | null> => {
return prisma.member.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Member, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryMembers,
getMemberById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -1,164 +0,0 @@
// REQ0047/order-page
//
// PURPOSE:
// - provide api access to backend db for orders
//
// RULES:
// - T.B.A.
//
import { User, Role, Prisma, Event, Order } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryOrders = async <Key extends keyof Order>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<Order, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const events = await prisma.order.findMany();
return events as Pick<Order, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getOrderById = async <Key extends keyof Order>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Order, Key> | null> => {
return prisma.order.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Order, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryOrders,
getOrderById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -1,147 +0,0 @@
import { User, Role, Prisma, Event, Member } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
// const createUser = async (
// email: string,
// password: string,
// name?: string,
// role: Role = Role.USER
// ): Promise<User> => {
// if (await getEventByEmail(email)) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// return prisma.user.create({
// data: {
// email,
// name,
// password: await encryptPassword(password),
// role
// }
// });
// };
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryMembers = async <Key extends keyof Member>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key>[]> => {
// const page = options.page ?? 1;
// const limit = options.limit ?? 10;
// const sortBy = options.sortBy;
// const sortType = options.sortType ?? 'desc';
const members = await prisma.member.findMany();
return members as Pick<Member, Key>[];
};
/**
* Get event by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<Event, Key> | null>}
*/
const getProfileById = async <Key extends keyof Member>(
id: number,
keys: Key[] = ['id'] as Key[]
): Promise<Pick<Member, Key> | null> => {
return prisma.member.findUnique({
where: { id }
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<Member, Key> | null>;
};
/**
* Get event by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
// const getEventByEmail = async <Key extends keyof User>(
// email: string,
// keys: Key[] = [
// 'id',
// 'email',
// 'name',
// 'password',
// 'role',
// 'isEmailVerified',
// 'createdAt',
// 'updatedAt'
// ] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// return prisma.user.findUnique({
// where: { email },
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// }) as Promise<Pick<User, Key> | null>;
// };
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
// const updateUserById = async <Key extends keyof User>(
// userId: number,
// updateBody: Prisma.UserUpdateInput,
// keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
// ): Promise<Pick<User, Key> | null> => {
// const user = await getEventById(userId, ['id', 'email', 'name']);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// if (updateBody.email && (await getEventByEmail(updateBody.email as string))) {
// throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
// }
// const updatedUser = await prisma.user.update({
// where: { id: user.id },
// data: updateBody,
// select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
// });
// return updatedUser as Pick<User, Key> | null;
// };
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
// const deleteUserById = async (userId: number): Promise<User> => {
// const user = await getEventById(userId);
// if (!user) {
// throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
// }
// await prisma.user.delete({ where: { id: user.id } });
// return user;
// };
export default {
// createUser,
queryMembers,
getProfileById
// getEventById,
// getEventByEmail,
// updateUserById,
// deleteUserById
};

View File

@@ -1,140 +0,0 @@
import jwt from 'jsonwebtoken';
import moment, { Moment } from 'moment';
import httpStatus from 'http-status';
import config from '../config/config';
import userService from './user.service';
import ApiError from '../utils/ApiError';
import { Token, TokenType } from '@prisma/client';
import prisma from '../client';
import { AuthTokensResponse } from '../types/response';
/**
* Generate token
* @param {number} userId
* @param {Moment} expires
* @param {string} type
* @param {string} [secret]
* @returns {string}
*/
const generateToken = (
userId: number,
expires: Moment,
type: TokenType,
secret = config.jwt.secret
): string => {
const payload = {
sub: userId,
iat: moment().unix(),
exp: expires.unix(),
type
};
return jwt.sign(payload, secret);
};
/**
* Save a token
* @param {string} token
* @param {number} userId
* @param {Moment} expires
* @param {string} type
* @param {boolean} [blacklisted]
* @returns {Promise<Token>}
*/
const saveToken = async (
token: string,
userId: number,
expires: Moment,
type: TokenType,
blacklisted = false
): Promise<Token> => {
const createdToken = prisma.token.create({
data: {
token,
userId: userId,
expires: expires.toDate(),
type,
blacklisted
}
});
return createdToken;
};
/**
* Verify token and return token doc (or throw an error if it is not valid)
* @param {string} token
* @param {string} type
* @returns {Promise<Token>}
*/
const verifyToken = async (token: string, type: TokenType): Promise<Token> => {
const payload = jwt.verify(token, config.jwt.secret);
const userId = Number(payload.sub);
const tokenData = await prisma.token.findFirst({
where: { token, type, userId, blacklisted: false }
});
if (!tokenData) {
throw new Error('Token not found');
}
return tokenData;
};
/**
* Generate auth tokens
* @param {User} user
* @returns {Promise<AuthTokensResponse>}
*/
const generateAuthTokens = async (user: { id: number }): Promise<AuthTokensResponse> => {
const accessTokenExpires = moment().add(config.jwt.accessExpirationMinutes, 'minutes');
const accessToken = generateToken(user.id, accessTokenExpires, TokenType.ACCESS);
const refreshTokenExpires = moment().add(config.jwt.refreshExpirationDays, 'days');
const refreshToken = generateToken(user.id, refreshTokenExpires, TokenType.REFRESH);
await saveToken(refreshToken, user.id, refreshTokenExpires, TokenType.REFRESH);
return {
access: {
token: accessToken,
expires: accessTokenExpires.toDate()
},
refresh: {
token: refreshToken,
expires: refreshTokenExpires.toDate()
}
};
};
/**
* Generate reset password token
* @param {string} email
* @returns {Promise<string>}
*/
const generateResetPasswordToken = async (email: string): Promise<string> => {
const user = await userService.getUserByEmail(email);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'No users found with this email');
}
const expires = moment().add(config.jwt.resetPasswordExpirationMinutes, 'minutes');
const resetPasswordToken = generateToken(user.id as number, expires, TokenType.RESET_PASSWORD);
await saveToken(resetPasswordToken, user.id as number, expires, TokenType.RESET_PASSWORD);
return resetPasswordToken;
};
/**
* Generate verify email token
* @param {User} user
* @returns {Promise<string>}
*/
const generateVerifyEmailToken = async (user: { id: number }): Promise<string> => {
const expires = moment().add(config.jwt.verifyEmailExpirationMinutes, 'minutes');
const verifyEmailToken = generateToken(user.id, expires, TokenType.VERIFY_EMAIL);
await saveToken(verifyEmailToken, user.id, expires, TokenType.VERIFY_EMAIL);
return verifyEmailToken;
};
export default {
generateToken,
saveToken,
verifyToken,
generateAuthTokens,
generateResetPasswordToken,
generateVerifyEmailToken
};

View File

@@ -1,170 +0,0 @@
import { User, Role, Prisma } from '@prisma/client';
import httpStatus from 'http-status';
import prisma from '../client';
import ApiError from '../utils/ApiError';
import { encryptPassword } from '../utils/encryption';
/**
* Create a user
* @param {Object} userBody
* @returns {Promise<User>}
*/
const createUser = async (
email: string,
password: string,
name?: string,
role: Role = Role.USER
): Promise<User> => {
if (await getUserByEmail(email)) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
return prisma.user.create({
data: {
email,
name,
password: await encryptPassword(password),
role
}
});
};
/**
* Query for users
* @param {Object} filter - Prisma filter
* @param {Object} options - Query options
* @param {string} [options.sortBy] - Sort option in the format: sortField:(desc|asc)
* @param {number} [options.limit] - Maximum number of results per page (default = 10)
* @param {number} [options.page] - Current page (default = 1)
* @returns {Promise<QueryResult>}
*/
const queryUsers = async <Key extends keyof User>(
filter: object,
options: {
limit?: number;
page?: number;
sortBy?: string;
sortType?: 'asc' | 'desc';
},
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key>[]> => {
const page = options.page ?? 1;
const limit = options.limit ?? 10;
const sortBy = options.sortBy;
const sortType = options.sortType ?? 'desc';
const users = await prisma.user.findMany({
where: filter,
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {}),
skip: page * limit,
take: limit,
orderBy: sortBy ? { [sortBy]: sortType } : undefined
});
return users as Pick<User, Key>[];
};
/**
* Get user by id
* @param {ObjectId} id
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
const getUserById = async <Key extends keyof User>(
id: number,
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key> | null> => {
return prisma.user.findUnique({
where: { id },
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<User, Key> | null>;
};
/**
* Get user by email
* @param {string} email
* @param {Array<Key>} keys
* @returns {Promise<Pick<User, Key> | null>}
*/
const getUserByEmail = async <Key extends keyof User>(
email: string,
keys: Key[] = [
'id',
'email',
'name',
'password',
'role',
'isEmailVerified',
'createdAt',
'updatedAt'
] as Key[]
): Promise<Pick<User, Key> | null> => {
return prisma.user.findUnique({
where: { email },
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
}) as Promise<Pick<User, Key> | null>;
};
/**
* Update user by id
* @param {ObjectId} userId
* @param {Object} updateBody
* @returns {Promise<User>}
*/
const updateUserById = async <Key extends keyof User>(
userId: number,
updateBody: Prisma.UserUpdateInput,
keys: Key[] = ['id', 'email', 'name', 'role'] as Key[]
): Promise<Pick<User, Key> | null> => {
const user = await getUserById(userId, ['id', 'email', 'name']);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
if (updateBody.email && (await getUserByEmail(updateBody.email as string))) {
throw new ApiError(httpStatus.BAD_REQUEST, 'Email already taken');
}
const updatedUser = await prisma.user.update({
where: { id: user.id },
data: updateBody,
select: keys.reduce((obj, k) => ({ ...obj, [k]: true }), {})
});
return updatedUser as Pick<User, Key> | null;
};
/**
* Delete user by id
* @param {ObjectId} userId
* @returns {Promise<User>}
*/
const deleteUserById = async (userId: number): Promise<User> => {
const user = await getUserById(userId);
if (!user) {
throw new ApiError(httpStatus.NOT_FOUND, 'User not found');
}
await prisma.user.delete({ where: { id: user.id } });
return user;
};
export default {
createUser,
queryUsers,
getUserById,
getUserByEmail,
updateUserById,
deleteUserById
};

View File

@@ -1,9 +0,0 @@
export interface TokenResponse {
token: string;
expires: Date;
}
export interface AuthTokensResponse {
access: TokenResponse;
refresh?: TokenResponse;
}

View File

@@ -1,17 +0,0 @@
class ApiError extends Error {
statusCode: number;
isOperational: boolean;
constructor(statusCode: number, message: string | undefined, isOperational = true, stack = '') {
super(message);
this.statusCode = statusCode;
this.isOperational = isOperational;
if (stack) {
this.stack = stack;
} else {
Error.captureStackTrace(this, this.constructor);
}
}
}
export default ApiError;

View File

@@ -1,18 +0,0 @@
import { RequestHandler } from 'express';
import { Request, Response, NextFunction } from 'express-serve-static-core';
export interface CustomParamsDictionary {
[key: string]: any;
}
const catchAsync =
(fn: RequestHandler<CustomParamsDictionary, any, any, qs.ParsedQs, Record<string, any>>) =>
(
req: Request<CustomParamsDictionary, any, any, any, Record<string, any>>,
res: Response<any, Record<string, any>, number>,
next: NextFunction
) => {
Promise.resolve(fn(req, res, next)).catch((err) => next(err));
};
export default catchAsync;

View File

@@ -1,10 +0,0 @@
import bcrypt from 'bcryptjs';
export const encryptPassword = async (password: string) => {
const encryptedPassword = await bcrypt.hash(password, 8);
return encryptedPassword;
};
export const isPasswordMatch = async (password: string, userPassword: string) => {
return bcrypt.compare(password, userPassword);
};

Some files were not shown because too many files have changed in this diff Show More