feat: init project
This commit is contained in:
commit
c30e0da3a3
6
.commitlintrc.json
Normal file
6
.commitlintrc.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"extends": ["@commitlint/config-conventional"],
|
||||||
|
"rules": {
|
||||||
|
"type-enum": [2, "always", ["feat", "fix", "docs", "style", "refactor", "revert", "chore", "build", "ci", "test"]]
|
||||||
|
}
|
||||||
|
}
|
18
.env.example
Normal file
18
.env.example
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# Server Options
|
||||||
|
HTTP_HOST=localhost
|
||||||
|
HTTP_PORT=8080
|
||||||
|
|
||||||
|
# Database Options
|
||||||
|
DB_HOST=127.0.0.1
|
||||||
|
DB_PORT=27017
|
||||||
|
DB_USERNAME=admin
|
||||||
|
DB_PASSWORD=admin
|
||||||
|
DB_NAME=admin
|
||||||
|
|
||||||
|
# Other
|
||||||
|
PUBLIC_ACCESS_SECRET_KEY=`-----BEGIN PUBLIC KEY-----
|
||||||
|
MIGeMA0GCSqGSIb3DQEBAQUAA4GMADCBiAKBgHgnvr7O2tiApjJfid1orFnIGm69
|
||||||
|
80fZp+Lpbjo+NC/0whMFga2Biw5b1G2Q/B2u0tpO1Fs/E8z7Lv1nYfr5jx2S8x6B
|
||||||
|
dA4TS2kB9Kf0wn0+7wSlyikHoKhbtzwXHZl17GsyEi6wHnsqNBSauyIWhpha8i+Y
|
||||||
|
+3GyaOY536H47qyXAgMBAAE=
|
||||||
|
-----END PUBLIC KEY-----`
|
1
.eslintignore
Normal file
1
.eslintignore
Normal file
@ -0,0 +1 @@
|
|||||||
|
jest.config.js
|
145
.eslintrc
Normal file
145
.eslintrc
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
{
|
||||||
|
"root": true,
|
||||||
|
"env": {
|
||||||
|
"browser": true,
|
||||||
|
"es2022": true,
|
||||||
|
"jest": true
|
||||||
|
},
|
||||||
|
"parser": "@typescript-eslint/parser",
|
||||||
|
"plugins": ["@typescript-eslint", "prettier", "import"],
|
||||||
|
"extends": [
|
||||||
|
"prettier",
|
||||||
|
"eslint:recommended",
|
||||||
|
"eslint-config-prettier",
|
||||||
|
"plugin:import/recommended",
|
||||||
|
"plugin:import/typescript",
|
||||||
|
"plugin:prettier/recommended",
|
||||||
|
"plugin:@typescript-eslint/recommended",
|
||||||
|
"plugin:@typescript-eslint/eslint-recommended"
|
||||||
|
],
|
||||||
|
"settings": {
|
||||||
|
"import/resolver": {
|
||||||
|
"node": {
|
||||||
|
"extensions": [".js", ".ts"],
|
||||||
|
"paths": ["./src"]
|
||||||
|
},
|
||||||
|
"typescript": {
|
||||||
|
"alwaysTryTypes": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"import/parsers": {
|
||||||
|
"@typescript-eslint/parser": [".ts"]
|
||||||
|
},
|
||||||
|
"extensions": [".js", ".ts"],
|
||||||
|
"import/ignore": ["node_modules"]
|
||||||
|
},
|
||||||
|
"parserOptions": {
|
||||||
|
"ecmaVersion": 2022,
|
||||||
|
"sourceType": "module",
|
||||||
|
"project": ["./tsconfig.json"],
|
||||||
|
"ecmaFeatures": {
|
||||||
|
"jsx": false,
|
||||||
|
"arrowFunctions": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"rules": {
|
||||||
|
"@typescript-eslint/explicit-function-return-type": "off",
|
||||||
|
"@typescript-eslint/no-var-requires": "error",
|
||||||
|
"@typescript-eslint/no-non-null-assertion": "off",
|
||||||
|
"@typescript-eslint/quotes": ["error", "double"],
|
||||||
|
"@typescript-eslint/no-use-before-define": "off",
|
||||||
|
"@typescript-eslint/no-empty-interface": "error",
|
||||||
|
"@typescript-eslint/no-explicit-any": "warn",
|
||||||
|
"@typescript-eslint/ban-ts-comment": 0,
|
||||||
|
"@typescript-eslint/no-shadow": "error",
|
||||||
|
"@typescript-eslint/no-unused-vars": ["error", { "argsIgnorePattern": "^_" }],
|
||||||
|
"@typescript-eslint/lines-between-class-members": ["error", { "exceptAfterOverload": true }],
|
||||||
|
"@typescript-eslint/naming-convention": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"selector": "default",
|
||||||
|
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "variable",
|
||||||
|
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
"selector": "typeLike",
|
||||||
|
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "enumMember",
|
||||||
|
"format": ["camelCase", "UPPER_CASE", "PascalCase", "snake_case"],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"selector": "objectLiteralProperty",
|
||||||
|
"format": [],
|
||||||
|
"leadingUnderscore": "allow"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-named-default": 0,
|
||||||
|
"import/prefer-default-export": "off",
|
||||||
|
"import/no-dynamic-require": "off",
|
||||||
|
"import/no-duplicates": "off",
|
||||||
|
"import/no-unresolved": "error",
|
||||||
|
"import/no-cycle": "off",
|
||||||
|
"import/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"js": "never",
|
||||||
|
"jsx": "never",
|
||||||
|
"ts": "never",
|
||||||
|
"tsx": "never"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"import/no-extraneous-dependencies": ["error", { "devDependencies": true }],
|
||||||
|
"import/order": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"newlines-between": "always-and-inside-groups",
|
||||||
|
"groups": [["builtin", "external"], ["internal", "sibling"], "type"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"prettier/prettier": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"endOfLine": "auto"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"comma-dangle": "off",
|
||||||
|
"class-methods-use-this": "off",
|
||||||
|
"func-names": "off",
|
||||||
|
"function-paren-newline": "off",
|
||||||
|
"global-require": "off",
|
||||||
|
"implicit-arrow-linebreak": 0,
|
||||||
|
"no-use-before-define": "off",
|
||||||
|
"no-inner-declarations": "off",
|
||||||
|
"no-console": ["warn", { "allow": ["error", "info"] }],
|
||||||
|
"consistent-return": "off",
|
||||||
|
"no-alert": "error",
|
||||||
|
"no-process-exit": "error",
|
||||||
|
"no-shadow": "error",
|
||||||
|
"no-param-reassign": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"props": true,
|
||||||
|
"ignorePropertyModificationsFor": ["accamulator"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"object-shorthand": "off",
|
||||||
|
"quotes": ["error", "double"]
|
||||||
|
},
|
||||||
|
"globals": {
|
||||||
|
"window": false,
|
||||||
|
"FormData": false,
|
||||||
|
"Blob": true,
|
||||||
|
"document": false
|
||||||
|
}
|
||||||
|
}
|
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
/node_modules
|
||||||
|
nohup.out
|
||||||
|
.env
|
||||||
|
/.idea
|
4
.husky/commit-msg
Normal file
4
.husky/commit-msg
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npx --no -- commitlint --edit ${1}
|
4
.husky/pre-commit
Normal file
4
.husky/pre-commit
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
yarn run code:format && yarn run lint && yarn run code:check && git add .
|
12
.prettierrc
Normal file
12
.prettierrc
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"singleQuote": false,
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"useTabs": false,
|
||||||
|
"endOfLine": "auto",
|
||||||
|
"bracketSpacing": true,
|
||||||
|
"arrowParens": "always",
|
||||||
|
"jsxSingleQuote": false
|
||||||
|
}
|
36
Dockerfile
Normal file
36
Dockerfile
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
FROM node:16-alpine as dev
|
||||||
|
|
||||||
|
RUN apk update && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
WORKDIR /usr/app
|
||||||
|
|
||||||
|
# Install node dependencies - done in a separate step so Docker can cache it.
|
||||||
|
COPY yarn.lock .
|
||||||
|
COPY package.json .
|
||||||
|
COPY tsconfig.json .
|
||||||
|
|
||||||
|
RUN yarn install --ignore-scripts --non-interactive --frozen-lockfile && yarn cache clean
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN ls
|
||||||
|
|
||||||
|
RUN yarn build
|
||||||
|
|
||||||
|
FROM node:16-alpine as production
|
||||||
|
|
||||||
|
RUN apk update && rm -rf /var/cache/apk/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=dev /usr/app/dist /app
|
||||||
|
COPY --from=dev /usr/app/package.json /app/
|
||||||
|
COPY --from=dev /usr/app/yarn.lock /app/
|
||||||
|
|
||||||
|
RUN chown -R node: .
|
||||||
|
|
||||||
|
USER node
|
||||||
|
|
||||||
|
RUN yarn install --non-interactive --frozen-lockfile --production && yarn cache clean
|
||||||
|
|
||||||
|
CMD ["node", "index.js"]
|
37
README.md
Normal file
37
README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Hub Admin Panel Backend Service
|
||||||
|
|
||||||
|
## Настройка и запуск
|
||||||
|
|
||||||
|
```
|
||||||
|
1) yarn setup - загрузка всех зависимостей и инициализация husky
|
||||||
|
2) yarn dev - запуск проекта в режиме разработки
|
||||||
|
```
|
||||||
|
|
||||||
|
## Переменные окружения сервиса:
|
||||||
|
|
||||||
|
**Для конфигурации сервера**:
|
||||||
|
|
||||||
|
```
|
||||||
|
ENVIRONMENT - application environment
|
||||||
|
HTTP_HOST - service host
|
||||||
|
HTTP_PORT - service port
|
||||||
|
PUBLIC_ACCESS_SECRET_KEY - secret to verify private access secret key
|
||||||
|
```
|
||||||
|
|
||||||
|
**Для конфигурации базы данных**:
|
||||||
|
|
||||||
|
```
|
||||||
|
DB_HOST - mongo host
|
||||||
|
DB_PORT - mongo port
|
||||||
|
DB_USERNAME - mongo username
|
||||||
|
DB_PASSWORD - mongo password
|
||||||
|
DB_NAME - database name
|
||||||
|
```
|
||||||
|
|
||||||
|
## Среды окружения
|
||||||
|
|
||||||
|
```
|
||||||
|
development - среда для разработки
|
||||||
|
staging - среда для тестирования продукта
|
||||||
|
production - среда продакшена
|
||||||
|
```
|
33
compose.yml
Normal file
33
compose.yml
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
services:
|
||||||
|
auth:
|
||||||
|
build: .
|
||||||
|
ports:
|
||||||
|
- 8090:8080
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
networks:
|
||||||
|
- penahub_ntwrk
|
||||||
|
- default
|
||||||
|
mongo:
|
||||||
|
image: mongo:6
|
||||||
|
hostname: mongo
|
||||||
|
restart: always
|
||||||
|
container_name: mongodb
|
||||||
|
networks:
|
||||||
|
- penahub_ntwrk
|
||||||
|
entrypoint: ["/usr/bin/mongod","--bind_ip_all","--replSet","rs0"]
|
||||||
|
mongosetup:
|
||||||
|
image: mongo:6
|
||||||
|
depends_on:
|
||||||
|
- mongo
|
||||||
|
restart: "no"
|
||||||
|
networks:
|
||||||
|
- penahub_ntwrk
|
||||||
|
entrypoint: [ "bash", "-c", "sleep 5 && mongosh --host mongo:27017 --eval 'rs.initiate()'"]
|
||||||
|
volumes:
|
||||||
|
mongo_state: {}
|
||||||
|
networks:
|
||||||
|
penahub_ntwrk:
|
||||||
|
driver: bridge
|
||||||
|
attachable: true
|
||||||
|
internal: true
|
13
jest.config.ts
Normal file
13
jest.config.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import type { Config } from "jest";
|
||||||
|
|
||||||
|
const config: Config = {
|
||||||
|
preset: "ts-jest",
|
||||||
|
testEnvironment: "node",
|
||||||
|
moduleDirectories: ["node_modules", "<rootDir>"],
|
||||||
|
moduleNameMapper: {
|
||||||
|
"@/(.*)": "<rootDir>/src/$1",
|
||||||
|
},
|
||||||
|
testTimeout: 15000,
|
||||||
|
};
|
||||||
|
|
||||||
|
export default config;
|
9
nodemon.json
Normal file
9
nodemon.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"ignore": ["test", ".git", "node_modules"],
|
||||||
|
"watch": ["src"],
|
||||||
|
"exec": "npm run start:server",
|
||||||
|
"ext": "ts",
|
||||||
|
"env": {
|
||||||
|
"ENVIRONMENT": "development"
|
||||||
|
}
|
||||||
|
}
|
68
package.json
Normal file
68
package.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"name": "auth-server",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"author": "DevMArt",
|
||||||
|
"license": "ISC",
|
||||||
|
"scripts": {
|
||||||
|
"setup": "yarn & husky install",
|
||||||
|
"start": "ts-node ./src/index.ts",
|
||||||
|
"start:server": "node --inspect=0.0.0.0:56745 -r ts-node/register -r dotenv/config src/index.ts",
|
||||||
|
"dev": "nodemon",
|
||||||
|
"test": "jest --coverage",
|
||||||
|
"test:watch": "jest --watch",
|
||||||
|
"build": "tsc -p tsconfig.json --sourceMap --outDir dist && tsc-alias -p tsconfig.json",
|
||||||
|
"code:check": "prettier --check \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
||||||
|
"code:format": "prettier --write \"src/**/*.{ts,tsx,js,css,scss,html}\"",
|
||||||
|
"code:format:specific-file": "prettier --write",
|
||||||
|
"lint": "eslint .",
|
||||||
|
"lint:fix": "eslint --fix ."
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fastify/cookie": "^8.3.0",
|
||||||
|
"@fastify/cors": "^8.2.0",
|
||||||
|
"@fastify/jwt": "^6.3.3",
|
||||||
|
"bcryptjs": "^2.4.3",
|
||||||
|
"dotenv": "^16.0.3",
|
||||||
|
"fastify": "^4.9.2",
|
||||||
|
"fastify-plugin": "^4.3.0",
|
||||||
|
"fastify-print-routes": "^2.0.6",
|
||||||
|
"jsonwebtoken": "^8.5.1",
|
||||||
|
"mongoose": "^6.7.2",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"tsc-alias": "^1.7.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@commitlint/cli": "^17.1.2",
|
||||||
|
"@commitlint/config-conventional": "^17.1.0",
|
||||||
|
"@types/bcryptjs": "^2.4.2",
|
||||||
|
"@types/jest": "^29.2.3",
|
||||||
|
"@types/jsonwebtoken": "^8.5.9",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
|
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
||||||
|
"@typescript-eslint/parser": "^5.18.0",
|
||||||
|
"eslint": "^8.23.0",
|
||||||
|
"eslint-config-prettier": "^8.5.0",
|
||||||
|
"eslint-import-resolver-node": "^0.3.6",
|
||||||
|
"eslint-import-resolver-typescript": "^3.5.0",
|
||||||
|
"eslint-plugin-import": "^2.26.0",
|
||||||
|
"eslint-plugin-prettier": "^4.2.1",
|
||||||
|
"husky": "^7.0.4",
|
||||||
|
"jest": "^29.3.1",
|
||||||
|
"mongodb-memory-server": "^8.10.0",
|
||||||
|
"nodemon": "^2.0.20",
|
||||||
|
"prettier": "^2.7.1",
|
||||||
|
"ts-jest": "^29.0.3",
|
||||||
|
"ts-node": "^10.9.1",
|
||||||
|
"tsconfig-paths": "^4.1.0",
|
||||||
|
"typescript": "^4.9.3",
|
||||||
|
"typescript-transform-paths": "^3.4.4"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"./src/**/*.{ts,js,jsx,tsx}": [
|
||||||
|
"eslint --ignore-path .gitignore --fix",
|
||||||
|
"prettier --ignore-path .gitignore --write"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
8
src/configuration/combine-routes.ts
Normal file
8
src/configuration/combine-routes.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
|
||||||
|
import { setRoleRoutes } from "@/routes/role.routes";
|
||||||
|
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
|
||||||
|
export const combineRoutes = (server: FastifyInstance): void => {
|
||||||
|
server.register(setRoleRoutes, { prefix: "/role" });
|
||||||
|
};
|
36
src/configuration/configure-env.ts
Normal file
36
src/configuration/configure-env.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import path from "path";
|
||||||
|
import dotenv from "dotenv";
|
||||||
|
|
||||||
|
import { DEFAULT } from "@/constants/default";
|
||||||
|
|
||||||
|
import type { Environment } from "@/types/environment";
|
||||||
|
|
||||||
|
const defineEnvironment = (): Environment => {
|
||||||
|
const environments: Environment[] = ["development", "staging", "production"];
|
||||||
|
const environment = process.env.ENVIRONMENT as Environment | undefined;
|
||||||
|
|
||||||
|
if (environment && environments.includes(environment)) {
|
||||||
|
return environment;
|
||||||
|
}
|
||||||
|
|
||||||
|
return DEFAULT.environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const configureENV = () => {
|
||||||
|
const environment = defineEnvironment();
|
||||||
|
|
||||||
|
if (environment === "development") {
|
||||||
|
return dotenv.config({
|
||||||
|
debug: true,
|
||||||
|
path: path.resolve(process.env.PWD || "", ".env.example"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (environment === "staging") {
|
||||||
|
return dotenv.config({ debug: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
return dotenv.config();
|
||||||
|
};
|
||||||
|
|
||||||
|
configureENV();
|
7
src/configuration/constitute-mongo-uri.ts
Normal file
7
src/configuration/constitute-mongo-uri.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import type { DatabaseOptions } from "@/types/configuration/database-options";
|
||||||
|
|
||||||
|
export const constituteMongoURI = ({ username, password, host, port, database }: DatabaseOptions) => {
|
||||||
|
const dbConnection = `mongodb://${username}:${password}@${host}:${port}`;
|
||||||
|
|
||||||
|
return database ? `${dbConnection}/${database}` : dbConnection;
|
||||||
|
};
|
17
src/configuration/register-fastify-plugins.ts
Normal file
17
src/configuration/register-fastify-plugins.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import cors from "@fastify/cors";
|
||||||
|
import cookie from "@fastify/cookie";
|
||||||
|
import jwt from "@fastify/jwt";
|
||||||
|
|
||||||
|
import printRoutes from "@/plugins/print-routes";
|
||||||
|
|
||||||
|
import type { FastifyInstance } from "fastify";
|
||||||
|
import type { PluginsOptions } from "@/types/configuration/plugins-options";
|
||||||
|
|
||||||
|
export const registerFastifyPlugins = (fastify: FastifyInstance, options: PluginsOptions) => {
|
||||||
|
fastify.register(cors, options.cors);
|
||||||
|
fastify.register(cookie, options.cookie);
|
||||||
|
fastify.register(jwt, options.jwt);
|
||||||
|
fastify.register(printRoutes);
|
||||||
|
|
||||||
|
return fastify;
|
||||||
|
};
|
20
src/constants/configuration.ts
Normal file
20
src/constants/configuration.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export const CONFIGURATION = {
|
||||||
|
http: {
|
||||||
|
host: process.env.HTTP_HOST || "",
|
||||||
|
port: Number(process.env.HTTP_PORT) || 8080,
|
||||||
|
},
|
||||||
|
db: {
|
||||||
|
host: process.env.DB_HOST || "",
|
||||||
|
port: Number(process.env.DB_PORT) || 3000,
|
||||||
|
username: process.env.DB_USERNAME || "admin",
|
||||||
|
password: process.env.DB_PASSWORD || "admin",
|
||||||
|
database: process.env.DB_NAME || "database",
|
||||||
|
},
|
||||||
|
service: {
|
||||||
|
privateAccessSecretKey: process.env.PRIVATE_ACCESS_SECRET_KEY || "",
|
||||||
|
publicAccessSecretKey: process.env.PUBLIC_ACCESS_SECRET_KEY || "",
|
||||||
|
privateRefreshSecretKey: process.env.PRIVATE_REFRESH_SECRET_KEY || "",
|
||||||
|
publicRefreshSecretKey: process.env.PUBLIC_REFRESH_SECRET_KEY || "",
|
||||||
|
salt: Number(process.env.SALT) || 10,
|
||||||
|
},
|
||||||
|
} as const;
|
10
src/constants/default.ts
Normal file
10
src/constants/default.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import type { Environment } from "@/types/environment";
|
||||||
|
|
||||||
|
type Default = {
|
||||||
|
readonly [key: string]: string;
|
||||||
|
readonly environment: Environment;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const DEFAULT: Default = {
|
||||||
|
environment: "development",
|
||||||
|
};
|
222
src/handlers/roles/index.ts
Normal file
222
src/handlers/roles/index.ts
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
import { Types } from "mongoose";
|
||||||
|
|
||||||
|
import { RoleModel } from "@/models/role.model";
|
||||||
|
|
||||||
|
import { convertArrayToRecord } from "@/utils/convert-array-to-record";
|
||||||
|
import { validateEmptyFields } from "@/utils/validate-empty-fields";
|
||||||
|
|
||||||
|
import type { FastifyReply } from "fastify";
|
||||||
|
import type { CreateRoleRequest, GetRoleRequest, RemoveRoleRequest, UpdateRoleRequest } from "./types";
|
||||||
|
|
||||||
|
export const getAllRoles = async () => RoleModel.find({ isDeleted: false }).lean();
|
||||||
|
|
||||||
|
export const createRole = async (request: CreateRoleRequest, reply: FastifyReply) => {
|
||||||
|
const [createRoleRequest, error] = validateEmptyFields(request.body, ["name", "permissions"]);
|
||||||
|
|
||||||
|
if (error || !createRoleRequest) {
|
||||||
|
reply.status(400);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isRoleExist = await RoleModel.exists({ name: createRoleRequest.name }).lean();
|
||||||
|
|
||||||
|
if (isRoleExist) {
|
||||||
|
reply.status(409);
|
||||||
|
return new Error("role already exist");
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissionsRecord = convertArrayToRecord(createRoleRequest.permissions, true);
|
||||||
|
|
||||||
|
return RoleModel.create({ name: createRoleRequest.name, permissions: permissionsRecord });
|
||||||
|
};
|
||||||
|
|
||||||
|
export const deleteRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.body || {};
|
||||||
|
|
||||||
|
if (!id || !Types.ObjectId.isValid(id)) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("wrong id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const deletedRole = await RoleModel.findByIdAndDelete(id);
|
||||||
|
|
||||||
|
if (!deletedRole) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return deletedRole.toJSON();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const removeRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.body || {};
|
||||||
|
|
||||||
|
if (!id || !Types.ObjectId.isValid(id)) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("wrong id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await RoleModel.findById(id);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role by id not found");
|
||||||
|
}
|
||||||
|
if (role.isDeleted) {
|
||||||
|
reply.status(409);
|
||||||
|
return new Error("role already deleted");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.updateOne({ $set: { isDeleted: true, deletedAt: Date.now() } });
|
||||||
|
|
||||||
|
return role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getRole = async (request: GetRoleRequest, reply: FastifyReply) => {
|
||||||
|
const { name, id } = request.body || {};
|
||||||
|
const { query } = request.params || {};
|
||||||
|
|
||||||
|
if (!name && !id && !query) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("either name or id must be filled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name || id) {
|
||||||
|
return RoleModel.findOne({
|
||||||
|
$or: [
|
||||||
|
{ name, isDeleted: false },
|
||||||
|
{ id, isDeleted: false },
|
||||||
|
],
|
||||||
|
}).lean();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("query is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Types.ObjectId.isValid(query)) {
|
||||||
|
return RoleModel.findOne({ _id: new Types.ObjectId(query), isDeleted: false }).lean();
|
||||||
|
}
|
||||||
|
|
||||||
|
return RoleModel.findOne({ name: query, isDeleted: false }).lean();
|
||||||
|
};
|
||||||
|
|
||||||
|
export const replaceRole = async (request: UpdateRoleRequest, reply: FastifyReply) => {
|
||||||
|
const [replaceRoleRequest, error] = validateEmptyFields(request.body || {}, ["name", "permissions"]);
|
||||||
|
const { query } = request.params || {};
|
||||||
|
|
||||||
|
if (error || !replaceRoleRequest) {
|
||||||
|
reply.status(400);
|
||||||
|
return error;
|
||||||
|
}
|
||||||
|
if (!query) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("either name or id must be filled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Types.ObjectId.isValid(query)) {
|
||||||
|
const role = RoleModel.findById(query);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role by id not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.replaceOne({
|
||||||
|
name: replaceRoleRequest.name,
|
||||||
|
permissions: convertArrayToRecord(replaceRoleRequest.permissions, true),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await RoleModel.findOne({ name: query });
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.replaceOne({
|
||||||
|
name: replaceRoleRequest.name,
|
||||||
|
permissions: convertArrayToRecord(replaceRoleRequest.permissions, true),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const updateRole = async (request: UpdateRoleRequest, reply: FastifyReply) => {
|
||||||
|
const { query } = request.params;
|
||||||
|
|
||||||
|
if (!query) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("query is empty");
|
||||||
|
}
|
||||||
|
if (!request.body?.name && !request.body?.permissions) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("either name or permissions must be filled");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Types.ObjectId.isValid(query)) {
|
||||||
|
const role = await RoleModel.findById(query);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role by id not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.updateOne({
|
||||||
|
$set: {
|
||||||
|
name: request.body?.name,
|
||||||
|
permissions: request.body?.permissions && convertArrayToRecord(request.body.permissions, true),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return role;
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await RoleModel.findOne({ name: query });
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.updateOne({
|
||||||
|
$set: {
|
||||||
|
name: request.body?.name,
|
||||||
|
permissions: request.body?.permissions && convertArrayToRecord(request.body.permissions, true),
|
||||||
|
updatedAt: Date.now(),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return role;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const restoreRole = async (request: RemoveRoleRequest, reply: FastifyReply) => {
|
||||||
|
const { id } = request.body || {};
|
||||||
|
|
||||||
|
if (!id || !Types.ObjectId.isValid(id)) {
|
||||||
|
reply.status(400);
|
||||||
|
return new Error("wrong id");
|
||||||
|
}
|
||||||
|
|
||||||
|
const role = await RoleModel.findById(id);
|
||||||
|
|
||||||
|
if (!role) {
|
||||||
|
reply.status(404);
|
||||||
|
return new Error("role by id not found");
|
||||||
|
}
|
||||||
|
if (!role.isDeleted) {
|
||||||
|
reply.status(409);
|
||||||
|
return new Error("role not removed");
|
||||||
|
}
|
||||||
|
|
||||||
|
await role.updateOne({ $set: { isDeleted: false } });
|
||||||
|
|
||||||
|
return role;
|
||||||
|
};
|
25
src/handlers/roles/types.ts
Normal file
25
src/handlers/roles/types.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import type { FastifyRequest } from "fastify";
|
||||||
|
|
||||||
|
type RoleRequest = {
|
||||||
|
name?: string;
|
||||||
|
permissions?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type CreateRoleRequest = FastifyRequest<{ Body: RoleRequest }>;
|
||||||
|
export type RemoveRoleRequest = FastifyRequest<{ Body: { id?: string } }>;
|
||||||
|
|
||||||
|
export type GetRoleRequest = FastifyRequest<{
|
||||||
|
Body?: Pick<RoleRequest, "name"> & {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
|
Params: {
|
||||||
|
query?: string;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
|
||||||
|
export type UpdateRoleRequest = FastifyRequest<{
|
||||||
|
Body?: RoleRequest;
|
||||||
|
Params: {
|
||||||
|
query?: string;
|
||||||
|
};
|
||||||
|
}>;
|
24
src/index.ts
Normal file
24
src/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import "./configuration/configure-env";
|
||||||
|
|
||||||
|
import { Server } from "./server";
|
||||||
|
|
||||||
|
import { CONFIGURATION } from "@/constants/configuration";
|
||||||
|
|
||||||
|
const server = new Server({
|
||||||
|
serverOptions: CONFIGURATION.http,
|
||||||
|
databaseOptions: CONFIGURATION.db,
|
||||||
|
pluginsOptions: {
|
||||||
|
cors: {
|
||||||
|
methods: ["GET", "PUT", "POST", "PATCH", "DELETE"],
|
||||||
|
origin: "*",
|
||||||
|
},
|
||||||
|
jwt: {
|
||||||
|
secret: {
|
||||||
|
public: CONFIGURATION.service.publicAccessSecretKey,
|
||||||
|
private: CONFIGURATION.service.privateAccessSecretKey,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
server.start();
|
24
src/models/eloquent-model.schema.ts
Normal file
24
src/models/eloquent-model.schema.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import type { SchemaDefinition } from "mongoose";
|
||||||
|
import type { EloquentModel } from "@/types/models/eloquent-model";
|
||||||
|
|
||||||
|
export const eloquentModelSchema: SchemaDefinition<EloquentModel> = {
|
||||||
|
createdAt: {
|
||||||
|
type: Date,
|
||||||
|
required: true,
|
||||||
|
default: Date.now,
|
||||||
|
},
|
||||||
|
updatedAt: {
|
||||||
|
type: Date,
|
||||||
|
required: true,
|
||||||
|
default: Date.now,
|
||||||
|
},
|
||||||
|
deletedAt: {
|
||||||
|
type: Date,
|
||||||
|
required: false,
|
||||||
|
},
|
||||||
|
isDeleted: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
};
|
27
src/models/role.model.ts
Normal file
27
src/models/role.model.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { Schema, model, SchemaDefinition } from "mongoose";
|
||||||
|
|
||||||
|
import { eloquentModelSchema } from "./eloquent-model.schema";
|
||||||
|
|
||||||
|
import type { Role } from "@/types/models/role.type";
|
||||||
|
|
||||||
|
const schema: SchemaDefinition<Role> = {
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
permissions: {
|
||||||
|
type: Map,
|
||||||
|
of: Boolean,
|
||||||
|
default: {},
|
||||||
|
},
|
||||||
|
...eloquentModelSchema,
|
||||||
|
};
|
||||||
|
|
||||||
|
const schemaSettings = {
|
||||||
|
versionKey: false,
|
||||||
|
collection: "roles",
|
||||||
|
};
|
||||||
|
|
||||||
|
const RoleSchema = new Schema<Role>(schema, schemaSettings);
|
||||||
|
|
||||||
|
export const RoleModel = model("Role", RoleSchema);
|
23
src/models/token.model.ts
Normal file
23
src/models/token.model.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import { Schema, model, SchemaDefinition } from "mongoose";
|
||||||
|
|
||||||
|
import type { Token } from "@/types/models/token.type";
|
||||||
|
|
||||||
|
const schema: SchemaDefinition<Token> = {
|
||||||
|
userId: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
refreshToken: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const schemaSettings = {
|
||||||
|
versionKey: false,
|
||||||
|
collection: "tokens",
|
||||||
|
};
|
||||||
|
|
||||||
|
const TokenSchema = new Schema<Token>(schema, schemaSettings);
|
||||||
|
|
||||||
|
export const TokenModel = model("Token", TokenSchema);
|
42
src/models/user.model.ts
Normal file
42
src/models/user.model.ts
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import { Schema, model, SchemaDefinition } from "mongoose";
|
||||||
|
|
||||||
|
import { eloquentModelSchema } from "./eloquent-model.schema";
|
||||||
|
|
||||||
|
import type { User } from "@/types/models/user.type";
|
||||||
|
|
||||||
|
const schema: SchemaDefinition<User> = {
|
||||||
|
login: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
password: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
phoneNumber: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
avatar: {
|
||||||
|
type: String,
|
||||||
|
default: "/media/img/no-avatar.png",
|
||||||
|
},
|
||||||
|
role: {
|
||||||
|
type: String,
|
||||||
|
default: "user",
|
||||||
|
},
|
||||||
|
...eloquentModelSchema,
|
||||||
|
};
|
||||||
|
|
||||||
|
const schemaSettings = {
|
||||||
|
versionKey: false,
|
||||||
|
collection: "users",
|
||||||
|
};
|
||||||
|
|
||||||
|
const UserSchema = new Schema<User>(schema, schemaSettings);
|
||||||
|
|
||||||
|
export const UserModel = model("User", UserSchema);
|
43
src/plugins/print-routes.ts
Normal file
43
src/plugins/print-routes.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import fastifyPlugin from "fastify-plugin";
|
||||||
|
|
||||||
|
import type { FastifyError, FastifyInstance, FastifyPluginOptions, RouteOptions } from "fastify";
|
||||||
|
|
||||||
|
type PrintRoutesRouteOptions = {
|
||||||
|
hide?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const METHODS_ORDER = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH", "OPTIONS"];
|
||||||
|
|
||||||
|
const printRoutes = (routes: Array<RouteOptions & PrintRoutesRouteOptions> = []) => {
|
||||||
|
if (routes.length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const filteredRoutes = routes.filter((route) => route.hide !== true);
|
||||||
|
const sortedRoutes = [...filteredRoutes].sort((routeA, routeB) => routeA.url.localeCompare(routeB.url));
|
||||||
|
const output = sortedRoutes.reduce<string>((accamulator, route) => {
|
||||||
|
const methods = Array.isArray(route.method) ? route.method : [route.method];
|
||||||
|
const methodsValue = [...methods].sort((a, b) => METHODS_ORDER.indexOf(a) - METHODS_ORDER.indexOf(b)).join(" | ");
|
||||||
|
|
||||||
|
return accamulator + `${methodsValue}\t${route.url}\n`;
|
||||||
|
}, "");
|
||||||
|
|
||||||
|
console.info(`\n\nAvailable routes:\n${output}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default fastifyPlugin(
|
||||||
|
(instance: FastifyInstance, _: FastifyPluginOptions, done: (error?: FastifyError) => void) => {
|
||||||
|
const routes: Array<RouteOptions> = [];
|
||||||
|
|
||||||
|
instance.addHook("onRoute", (route) => {
|
||||||
|
routes.push(route);
|
||||||
|
});
|
||||||
|
|
||||||
|
instance.addHook("onReady", (next) => {
|
||||||
|
printRoutes(routes);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
);
|
25
src/routes/role.routes.ts
Normal file
25
src/routes/role.routes.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
getRole,
|
||||||
|
getAllRoles,
|
||||||
|
createRole,
|
||||||
|
deleteRole,
|
||||||
|
updateRole,
|
||||||
|
replaceRole,
|
||||||
|
removeRole,
|
||||||
|
restoreRole,
|
||||||
|
} from "@/handlers/roles";
|
||||||
|
|
||||||
|
import type { FastifyInstance, FastifyPluginOptions } from "fastify";
|
||||||
|
|
||||||
|
export const setRoleRoutes = <T = FastifyPluginOptions>(server: FastifyInstance, opts: T, done: () => void): void => {
|
||||||
|
server.get("/all", getAllRoles);
|
||||||
|
server.get("/:query", getRole);
|
||||||
|
server.post("/", createRole);
|
||||||
|
server.post("/restore", restoreRole);
|
||||||
|
server.patch("/:query", updateRole);
|
||||||
|
server.put("/:query", replaceRole);
|
||||||
|
server.delete("/", removeRole);
|
||||||
|
server.delete("/delete", deleteRole);
|
||||||
|
|
||||||
|
done();
|
||||||
|
};
|
59
src/server/index.ts
Normal file
59
src/server/index.ts
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import fastify, { FastifyInstance } from "fastify";
|
||||||
|
import { connect as mongoConnect } from "mongoose";
|
||||||
|
|
||||||
|
import { registerFastifyPlugins } from "@/configuration/register-fastify-plugins";
|
||||||
|
import { combineRoutes } from "@/configuration/combine-routes";
|
||||||
|
import { constituteMongoURI } from "@/configuration/constitute-mongo-uri";
|
||||||
|
|
||||||
|
import type { PluginsOptions } from "@/types/configuration/plugins-options";
|
||||||
|
import type { DatabaseOptions } from "@/types/configuration/database-options";
|
||||||
|
|
||||||
|
type ServerOptions = {
|
||||||
|
port?: number;
|
||||||
|
host?: string;
|
||||||
|
backlog?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ServerArgs = {
|
||||||
|
serverOptions?: ServerOptions;
|
||||||
|
pluginsOptions?: PluginsOptions;
|
||||||
|
databaseOptions?: DatabaseOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
export class Server {
|
||||||
|
private fastify: FastifyInstance;
|
||||||
|
private serverOptions?: ServerOptions;
|
||||||
|
private databaseOptions?: DatabaseOptions;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Настройка сервера
|
||||||
|
* @param {ServerOptions} serverOptions объект настроек сервера.
|
||||||
|
* @param {DatabaseOptions} databaseOptions объект настроек подключения к базе данных.
|
||||||
|
* @param {PluginsOptions} pluginsOptions объект настроек плагинов fastify.
|
||||||
|
*/
|
||||||
|
constructor({ serverOptions, databaseOptions, pluginsOptions }: ServerArgs) {
|
||||||
|
this.serverOptions = serverOptions;
|
||||||
|
this.databaseOptions = databaseOptions;
|
||||||
|
this.fastify = fastify({
|
||||||
|
logger: false,
|
||||||
|
bodyLimit: 30 * 1024 * 1024,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (pluginsOptions) {
|
||||||
|
registerFastifyPlugins(this.fastify, pluginsOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
combineRoutes(this.fastify);
|
||||||
|
}
|
||||||
|
|
||||||
|
public start = async () => {
|
||||||
|
const fasticyConnection = this.fastify.listen(this.serverOptions);
|
||||||
|
const databaseConnection = this.databaseOptions ? mongoConnect(constituteMongoURI(this.databaseOptions)) : null;
|
||||||
|
|
||||||
|
await Promise.all([databaseConnection, fasticyConnection])
|
||||||
|
.then(() => console.info(`server started on ${this.serverOptions?.host}:${this.serverOptions?.port}`))
|
||||||
|
.catch((reason) => console.error(reason));
|
||||||
|
};
|
||||||
|
|
||||||
|
public getFastifyInstance = () => this.fastify;
|
||||||
|
}
|
154
src/swagger.json
Normal file
154
src/swagger.json
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
{
|
||||||
|
"openapi": "3.0.3",
|
||||||
|
"info": {
|
||||||
|
"title": "PenaHub Server",
|
||||||
|
"version": "1.0.11",
|
||||||
|
"license": {
|
||||||
|
"name": "Apache 2.0",
|
||||||
|
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"externalDocs": {
|
||||||
|
"description": "Find out more about Swagger",
|
||||||
|
"url": "http://swagger.io"
|
||||||
|
},
|
||||||
|
"servers": [
|
||||||
|
{
|
||||||
|
"url": "http://84.201.155.77:8080"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"paths": {
|
||||||
|
"/": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"/"
|
||||||
|
],
|
||||||
|
"summary": "Список пользователей",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/auth": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Страница регистрации",
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/auth": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"auth"
|
||||||
|
],
|
||||||
|
"summary": "Регистрация нового пользователя",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/urlencoded": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"login": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "valera24"
|
||||||
|
},
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "valera24@mail.com"
|
||||||
|
},
|
||||||
|
"phoneNumber": {
|
||||||
|
"type": "string",
|
||||||
|
"example": 79993337733
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "secret"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/login": {
|
||||||
|
"get": {
|
||||||
|
"tags": [
|
||||||
|
"login"
|
||||||
|
],
|
||||||
|
"summary": "Страница авторизации",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "return",
|
||||||
|
"in": "query",
|
||||||
|
"description": "URL на который будет перенаправлен пользователь после авторизации",
|
||||||
|
"required": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"/api/v1/login": {
|
||||||
|
"post": {
|
||||||
|
"tags": [
|
||||||
|
"login"
|
||||||
|
],
|
||||||
|
"summary": "Авторизация пользователя",
|
||||||
|
"requestBody": {
|
||||||
|
"content": {
|
||||||
|
"application/urlencoded": {
|
||||||
|
"schema": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"email": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "valera24@mail.com"
|
||||||
|
},
|
||||||
|
"password": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "secret"
|
||||||
|
},
|
||||||
|
"goto": {
|
||||||
|
"type": "string",
|
||||||
|
"example": "http://penahub.quiz/signedup"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"responses": {
|
||||||
|
"200": {
|
||||||
|
"description": "Successful (Если не передан goto)"
|
||||||
|
},
|
||||||
|
"304": {
|
||||||
|
"description": "Successful (Если передан goto)"
|
||||||
|
},
|
||||||
|
"401": {
|
||||||
|
"description": "password incorrect"
|
||||||
|
},
|
||||||
|
"404": {
|
||||||
|
"description": "User not found"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/types/configuration/database-options.ts
Normal file
7
src/types/configuration/database-options.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export type DatabaseOptions = {
|
||||||
|
host: string;
|
||||||
|
username: string;
|
||||||
|
password: string;
|
||||||
|
port: number;
|
||||||
|
database?: string;
|
||||||
|
};
|
9
src/types/configuration/plugins-options.ts
Normal file
9
src/types/configuration/plugins-options.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import type { FastifyCorsOptions } from "@fastify/cors";
|
||||||
|
import type { FastifyCookieOptions } from "@fastify/cookie";
|
||||||
|
import type { FastifyJWTOptions } from "@fastify/jwt";
|
||||||
|
|
||||||
|
export type PluginsOptions = {
|
||||||
|
cors?: FastifyCorsOptions;
|
||||||
|
cookie?: FastifyCookieOptions;
|
||||||
|
jwt?: FastifyJWTOptions;
|
||||||
|
};
|
1
src/types/environment.ts
Normal file
1
src/types/environment.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export type Environment = "development" | "staging" | "production";
|
6
src/types/models/eloquent-model.ts
Normal file
6
src/types/models/eloquent-model.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export type EloquentModel = {
|
||||||
|
createdAt: Date;
|
||||||
|
updatedAt: Date;
|
||||||
|
deletedAt: Date;
|
||||||
|
isDeleted: boolean;
|
||||||
|
};
|
6
src/types/models/role.type.ts
Normal file
6
src/types/models/role.type.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
import type { EloquentModel } from "./eloquent-model";
|
||||||
|
|
||||||
|
export type Role = EloquentModel & {
|
||||||
|
name: string;
|
||||||
|
permissions: Record<string, boolean>;
|
||||||
|
};
|
3
src/types/object-with-required-fields.ts
Normal file
3
src/types/object-with-required-fields.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export type ObjectWithRequiredFields<KeyValue extends Record<string, unknown>> = {
|
||||||
|
[Key in keyof KeyValue]-?: KeyValue[Key];
|
||||||
|
};
|
5
src/utils/convert-array-to-record.ts
Normal file
5
src/utils/convert-array-to-record.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export const convertArrayToRecord = <Array extends string[], Value>(
|
||||||
|
array: Array,
|
||||||
|
defaultValue: Value
|
||||||
|
): Record<string, Value> =>
|
||||||
|
array.reduce<Record<string, Value>>((accamulator, value) => ({ ...accamulator, [value]: defaultValue }), {});
|
15
src/utils/validate-empty-fields.ts
Normal file
15
src/utils/validate-empty-fields.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import type { ObjectWithRequiredFields } from "@/types/object-with-required-fields";
|
||||||
|
|
||||||
|
type KeyValue = Record<string, unknown>;
|
||||||
|
type Keys<T> = Array<keyof T>;
|
||||||
|
type ValidateEmptyFields<T extends KeyValue> = [ObjectWithRequiredFields<T> | undefined, Error | null];
|
||||||
|
|
||||||
|
export const validateEmptyFields = <T extends KeyValue>(record: T, keys: Keys<T> = []): ValidateEmptyFields<T> => {
|
||||||
|
for (const key of keys) {
|
||||||
|
if (!record[key]) {
|
||||||
|
return [undefined, new Error(`field <${key as string}> is empty`)];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [record as ObjectWithRequiredFields<T>, null];
|
||||||
|
};
|
33
tsconfig.json
Normal file
33
tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./dist",
|
||||||
|
"target": "es6",
|
||||||
|
"module": "commonjs",
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"emitDecoratorMetadata": true,
|
||||||
|
"strict": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"noImplicitAny": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"typeRoots": ["node_modules/@types"],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["./src/*"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"ts-node": {
|
||||||
|
"transpileOnly": true,
|
||||||
|
"files": true,
|
||||||
|
"require": ["typescript-transform-paths/register", "tsconfig-paths/register"]
|
||||||
|
},
|
||||||
|
"include": ["./src/**/*.ts", "./jest.config.ts"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user