├── src
├── uploads
│ └── .gitkeep
├── commons
│ ├── keys
│ │ ├── test.js
│ │ ├── token.js
│ │ ├── blacklist.js
│ │ ├── user.js
│ │ ├── pub.js
│ │ └── spam.js
│ ├── utils
│ │ ├── httpStatusCode.js
│ │ ├── convert.js
│ │ ├── random.js
│ │ ├── sendEmail.js
│ │ ├── statusCodes.js
│ │ └── reasonPhrases.js
│ ├── helpers
│ │ ├── asyncHandler.js
│ │ ├── errorHandle.js
│ │ └── checkFieldsBuilder.js
│ ├── configs
│ │ ├── multer.config.js
│ │ ├── cloudinary.config.js
│ │ ├── redis.config.js
│ │ ├── gmail.config.js
│ │ └── app.config.js
│ └── constants
│ │ └── index.js
├── views
│ ├── images
│ │ └── logo.png
│ ├── resetThankYou.html
│ ├── changePasswordThankYou.html
│ ├── otpPhone.html
│ └── forgotPassword.html
├── apis
│ └── google.api.js
├── app
│ ├── v3
│ │ ├── models
│ │ │ └── repositories
│ │ │ │ └── telegram.repo.js
│ │ ├── services
│ │ │ └── telegram.service.js
│ │ ├── routes
│ │ │ ├── telegrams
│ │ │ │ └── index.js
│ │ │ └── index.js
│ │ └── controllers
│ │ │ └── telegram.controller.js
│ ├── v2
│ │ ├── routes
│ │ │ ├── index.js
│ │ │ ├── puppeteers
│ │ │ │ └── index.js
│ │ │ ├── users
│ │ │ │ └── index.js
│ │ │ ├── notifications
│ │ │ │ └── index.js
│ │ │ └── images
│ │ │ │ └── index.js
│ │ ├── controllers
│ │ │ ├── puppeteer.controller.js
│ │ │ ├── user.controller.js
│ │ │ ├── image.controller.js
│ │ │ └── notification.controller.js
│ │ ├── models
│ │ │ └── user_otp.model.js
│ │ └── services
│ │ │ ├── puppeteer.service.js
│ │ │ ├── image.service.js
│ │ │ ├── notification.service.js
│ │ │ └── user.service.js
│ └── v1
│ │ ├── routes
│ │ ├── labels
│ │ │ └── index.js
│ │ ├── index.js
│ │ ├── todos
│ │ │ └── index.js
│ │ └── users
│ │ │ └── index.js
│ │ ├── models
│ │ ├── todo_list_label.js
│ │ ├── user.model.v1.js
│ │ ├── label.model.js
│ │ ├── user_verification.js
│ │ ├── todo.model.js
│ │ └── user.model.js
│ │ ├── controllers
│ │ ├── label.controller.js
│ │ ├── todo.controller.js
│ │ └── user.controller.js
│ │ └── services
│ │ ├── label.service.js
│ │ └── todo.service.js
├── crons
│ ├── user_otp.js
│ └── user_verification.js
├── databases
│ ├── init.telegram.js
│ ├── init.pub.js
│ ├── init.firebase.js
│ ├── init.cloudinary.js
│ ├── init.knex.js
│ ├── init.pg.js
│ └── init.redis.js
├── auth
│ ├── auth.token.js
│ ├── auth.password.js
│ ├── auth.cookie.js
│ ├── auth.blacklist.js
│ └── check.auth.js
├── cores
│ ├── success.response.js
│ └── error.response.js
├── loggers
│ ├── winston.log.js
│ └── mylogger.log.js
├── libs
│ └── method.js
└── app.js
├── .gitignore
├── zips
├── react_notification.zip
└── nodejs-child-process-tutorial.zip
├── .vscode
└── settings.json
├── LIBRARY.md
├── BOT.md
├── firebaseConfig.example.json
├── migrations
├── create_user_03012024.sql
├── update_user_03012024 copy.sql
├── create_label_03012023.sql
├── create_todo_list_03012023.sql
├── create_user_otp_07022024.sql
├── create_user_verification_0502024.sql
├── backup
│ ├── demo.sql
│ ├── dump_2024-01-06_17_40_28.sql
│ └── dump_2024-01-04_15_48_56.sql
├── create_todo_list_label_03012024.sql
├── create_user_v1.sql
└── trg_insert_user_04012024.sql
├── newMap.js
├── .dockerignore
├── CERT.md
├── primary.mjs
├── .github
└── FUNDING.yml
├── docker-compose.yml
├── makefile
├── demo.js
├── .env.example
├── package.json
├── server.js
├── README.md
├── NEWMAP.md
├── REDIS.md
└── SQL.md
/src/uploads/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/commons/keys/test.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | Test: "test",
3 | };
4 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | .env
3 | src/logs
4 | yarn.lock
5 | firebaseConfig.json
6 | cert
--------------------------------------------------------------------------------
/src/commons/keys/token.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | RefetchToken: "refresh_token",
3 | };
4 |
--------------------------------------------------------------------------------
/src/commons/keys/blacklist.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | BlacklistTokens: "blacklist_tokens",
3 | };
4 |
--------------------------------------------------------------------------------
/src/commons/keys/user.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | User: "user",
3 | VerifyEmail: "verify_email_${email}",
4 | };
5 |
--------------------------------------------------------------------------------
/src/views/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fdhhhdjd/Class_NodeJs_Systems/HEAD/src/views/images/logo.png
--------------------------------------------------------------------------------
/src/commons/keys/pub.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | User: {
3 | SpamPassword: "user_spam_password",
4 | },
5 | };
6 |
--------------------------------------------------------------------------------
/zips/react_notification.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fdhhhdjd/Class_NodeJs_Systems/HEAD/zips/react_notification.zip
--------------------------------------------------------------------------------
/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "editor.formatOnSave": true,
3 | "cSpell.words": ["cloudinary", "Fullname", "Initalstate"]
4 | }
5 |
--------------------------------------------------------------------------------
/zips/nodejs-child-process-tutorial.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/fdhhhdjd/Class_NodeJs_Systems/HEAD/zips/nodejs-child-process-tutorial.zip
--------------------------------------------------------------------------------
/src/commons/keys/spam.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | SpamForget: "spam_forget",
3 | SpamOTP: "spam_otp",
4 | SpamPassword: "spam_password",
5 | };
6 |
--------------------------------------------------------------------------------
/LIBRARY.md:
--------------------------------------------------------------------------------
1 | ## [IoRedis example](https://github.com/redis/ioredis/blob/HEAD/examples)
2 |
3 | # 1. Check helmet
4 | ```cmd
5 | curl -I http://localhost:5000
6 | ```
--------------------------------------------------------------------------------
/src/commons/utils/httpStatusCode.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | StatusCodes: require("../utils/statusCodes"),
3 | ReasonPhrases: require("../utils/reasonPhrases"),
4 | };
5 |
--------------------------------------------------------------------------------
/src/apis/google.api.js:
--------------------------------------------------------------------------------
1 | const googleApi = {
2 | firebase: {
3 | message: {
4 | sendSingleDevice: "/fcm/send",
5 | },
6 | },
7 | };
8 |
9 | module.exports = googleApi;
10 |
--------------------------------------------------------------------------------
/BOT.md:
--------------------------------------------------------------------------------
1 | # BOT
2 |
3 | # 1. TELEGRAM
4 |
5 | ## Into telegram enter with BotFather
6 | ```cmd
7 | /newbot
8 | class_telegram
9 | class_telegram_bot
10 | click open telegram bot
11 | ```
12 |
--------------------------------------------------------------------------------
/firebaseConfig.example.json:
--------------------------------------------------------------------------------
1 | {
2 | "apiKey": "",
3 | "authDomain": "",
4 | "projectId": "",
5 | "storageBucket": "",
6 | "messagingSenderId": "",
7 | "appId": "",
8 | "measurementId": ""
9 | }
10 |
--------------------------------------------------------------------------------
/migrations/create_user_03012024.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.user (
2 | id SERIAL PRIMARY KEY,
3 | username VARCHAR(255) NOT NULL,
4 | email VARCHAR(255) NOT NULL,
5 | password VARCHAR(255) NOT NULL
6 | );
7 |
--------------------------------------------------------------------------------
/newMap.js:
--------------------------------------------------------------------------------
1 | const anotherMap = new Map();
2 |
3 | anotherMap
4 | .set("fullname", "Nguyen Tien Tai")
5 | .set("age", 24)
6 | .set("job", "developer and teacher");
7 |
8 | const data = anotherMap.get("age");
9 | console.log(data);
10 |
--------------------------------------------------------------------------------
/migrations/update_user_03012024 copy.sql:
--------------------------------------------------------------------------------
1 | ALTER TABLE public.user
2 | ADD COLUMN status SMALLINT DEFAULT 10,
3 | ADD COLUMN created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
4 | ADD COLUMN updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL;
5 |
--------------------------------------------------------------------------------
/src/commons/helpers/asyncHandler.js:
--------------------------------------------------------------------------------
1 | const asyncHandler = (fn) => {
2 | return (req, res, next) => {
3 | Promise.resolve(fn(req, res, next)).catch((error) => {
4 | next(error);
5 | });
6 | };
7 | };
8 |
9 | module.exports = { asyncHandler };
10 |
--------------------------------------------------------------------------------
/src/app/v3/models/repositories/telegram.repo.js:
--------------------------------------------------------------------------------
1 | //* REQUIRED
2 | const { BOT } = require("../../../../databases/init.telegram");
3 |
4 | const sendTelegram = ({ idChat, message }) => BOT.sendMessage(idChat, message);
5 |
6 | module.exports = {
7 | sendTelegram,
8 | };
9 |
--------------------------------------------------------------------------------
/src/crons/user_otp.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const cron = require("node-cron");
3 |
4 | //* IMPORT
5 | const userOtpModel = require("../app/v2/models/user_otp.model");
6 |
7 | cron.schedule("*/60 * * * *", function () {
8 | userOtpModel.deleteExpiredRecords();
9 | });
10 |
--------------------------------------------------------------------------------
/migrations/create_label_03012023.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.label (
2 | id SERIAL PRIMARY KEY,
3 | name VARCHAR(255) NOT NULL,
4 | created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
5 | updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
6 | );
7 |
--------------------------------------------------------------------------------
/src/crons/user_verification.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const cron = require("node-cron");
3 |
4 | //* IMPORT
5 | const userVerificationModel = require("../app/v1/models/user_verification");
6 |
7 | cron.schedule("*/60 * * * *", function () {
8 | userVerificationModel.deleteExpiredRecords();
9 | });
10 |
--------------------------------------------------------------------------------
/migrations/create_todo_list_03012023.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.todo_list (
2 | id SERIAL PRIMARY KEY,
3 | title VARCHAR(255) NOT NULL,
4 | user_id INTEGER REFERENCES public.user(id),
5 | created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
6 | updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
7 | );
8 |
--------------------------------------------------------------------------------
/src/app/v3/services/telegram.service.js:
--------------------------------------------------------------------------------
1 | //* REQUIRED
2 | const { sendTelegram } = require("../models/repositories/telegram.repo");
3 |
4 | class TelegramService {
5 | async sendMessage({ message }) {
6 | sendTelegram({ idChat: 5886856229, message });
7 | return message;
8 | }
9 | }
10 |
11 | module.exports = new TelegramService();
12 |
--------------------------------------------------------------------------------
/migrations/create_user_otp_07022024.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.user_otp (
2 | id SERIAL PRIMARY KEY,
3 | user_id INTEGER REFERENCES public.user(id) NOT NULL,
4 | otp INTEGER NOT NULL,
5 | expires_at TIMESTAMP NOT NULL,
6 | created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
7 | updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
8 | );
9 |
--------------------------------------------------------------------------------
/migrations/create_user_verification_0502024.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.user_verification (
2 | id SERIAL PRIMARY KEY,
3 | user_id INTEGER REFERENCES public.user(id),
4 | unique_string TEXT,
5 | expires_at TIMESTAMP NOT NULL,
6 | created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
7 | updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
8 | );
9 |
--------------------------------------------------------------------------------
/src/app/v2/routes/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* LIB
4 | const express = require("express");
5 |
6 | const router = express.Router();
7 |
8 | // router.use("/images", require("./images"));
9 | router.use("/users", require("./users"));
10 | router.use("/notifications", require("./notifications"));
11 | router.use("/puppeteers", require("./puppeteers"));
12 |
13 | module.exports = router;
14 |
--------------------------------------------------------------------------------
/src/app/v2/routes/puppeteers/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const puppeteerController = require("../../controllers/puppeteer.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.post("/start", asyncHandler(puppeteerController.start));
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/src/app/v3/routes/telegrams/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const telegramController = require("../../controllers/telegram.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.post("/send-message", asyncHandler(telegramController.sendMessage));
11 |
12 | module.exports = router;
13 |
--------------------------------------------------------------------------------
/src/app/v3/controllers/telegram.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse } = require("../../../cores/success.response");
3 | const telegramService = require("../services/telegram.service");
4 |
5 | class TelegramController {
6 | async sendMessage(req, res, ___) {
7 | new SuccessResponse({
8 | metadata: await telegramService.sendMessage(req.body),
9 | }).send(res);
10 | }
11 | }
12 |
13 | module.exports = new TelegramController();
14 |
--------------------------------------------------------------------------------
/src/app/v2/controllers/puppeteer.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse } = require("../../../cores/success.response");
3 | const puppeteerService = require("../services/puppeteer.service");
4 |
5 | class PuppeteerServiceController {
6 | async start(req, res, ___) {
7 | new SuccessResponse({
8 | metadata: await puppeteerService.start(req.body),
9 | }).send(res);
10 | }
11 | }
12 |
13 | module.exports = new PuppeteerServiceController();
14 |
--------------------------------------------------------------------------------
/src/databases/init.telegram.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const TelegramBot = require("node-telegram-bot-api");
3 |
4 | const token = process.env.BOT_TOKEN;
5 | const BOT = new TelegramBot(token, {
6 | polling: true,
7 | request: {
8 | agentOptions: {
9 | keepAlive: true,
10 | family: 4,
11 | },
12 | },
13 | });
14 | BOT.on("polling_error", (error) => {
15 | console.error(`Polling error: ${error.message}`);
16 | });
17 |
18 | module.exports = { BOT };
19 |
--------------------------------------------------------------------------------
/src/app/v3/routes/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* LIB
4 | const express = require("express");
5 | const { BOT } = require("../../../databases/init.telegram");
6 |
7 | const router = express.Router();
8 |
9 | router.use("/telegrams", require("./telegrams"));
10 |
11 | BOT.on("message", (msg) => {
12 | const chatId = msg.chat.id;
13 | const messageText = msg.text;
14 | if (messageText === "/start") {
15 | BOT.sendMessage(chatId, "Welcome to the bot!");
16 | }
17 | });
18 |
19 | module.exports = router;
20 |
--------------------------------------------------------------------------------
/migrations/backup/demo.sql:
--------------------------------------------------------------------------------
1 | INSERT INTO public."user" (id, username, email, password, status, created_at, updated_at)
2 | SELECT
3 | generate_series(10, 10009) AS id,
4 | 'user' || generate_series(10, 10009) AS username,
5 | 'user' || generate_series(10, 10009) || '@example.com' AS email,
6 | 'password' || generate_series(10, 10009) AS password,
7 | floor(random() * 100) AS status,
8 | now() - interval '1 day' * floor(random() * 365) AS created_at,
9 | now() - interval '1 day' * floor(random() * 365) AS updated_at;
10 |
--------------------------------------------------------------------------------
/src/commons/utils/convert.js:
--------------------------------------------------------------------------------
1 | const formatPhone = (phone) => {
2 | const formattedPhone = phone.startsWith("0")
3 | ? `+84${phone.substring(1)}`
4 | : `+${phone}`;
5 | return formattedPhone;
6 | };
7 |
8 | const splitUsername = (email) => {
9 | const username = email.substring(0, email.indexOf("@"));
10 | return username;
11 | };
12 |
13 | const getURIFromTemplate = (template, data) => {
14 | return template.replace(/\${(\w+)}/g, (_, key) => data[key]);
15 | };
16 |
17 | module.exports = { formatPhone, splitUsername, getURIFromTemplate };
18 |
--------------------------------------------------------------------------------
/migrations/create_todo_list_label_03012024.sql:
--------------------------------------------------------------------------------
1 | -- CREATE TABLE public.todo_list_label (
2 | -- todo_list_id INTEGER REFERENCES public.todo_list(id),
3 | -- label_id INTEGER REFERENCES public.label(id),
4 | -- PRIMARY KEY (todo_list_id, label_id),
5 | -- due_date DATE
6 | -- );
7 |
8 | CREATE TABLE public.todo_list_label (
9 | todo_list_id INTEGER REFERENCES public.todo_list(id),
10 | label_id INTEGER UNIQUE REFERENCES public.label(id), -- Thêm UNIQUE ở đây
11 | PRIMARY KEY (todo_list_id, label_id),
12 | due_date DATE
13 | );
14 |
15 |
--------------------------------------------------------------------------------
/src/auth/auth.token.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const JWT = require("jsonwebtoken");
3 |
4 | const createTokenJWT = (user, secret, timeExpire) => {
5 | try {
6 | return JWT.sign(user, secret, {
7 | expiresIn: timeExpire,
8 | });
9 | } catch (error) {
10 | return error.message;
11 | }
12 | };
13 |
14 | const verifyTokenJWT = async (token, secret) => {
15 | try {
16 | return await JWT.verify(token, secret);
17 | } catch (error) {
18 | return error.message;
19 | }
20 | };
21 |
22 | module.exports = {
23 | verifyTokenJWT,
24 | createTokenJWT,
25 | };
26 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | .git
2 | .github
3 | .vscode
4 | coverage
5 | docs
6 | mock-config
7 | *.sh
8 | .editorconfig
9 | .prettierignore
10 | config.yaml
11 | jest*
12 | npm-debug.log
13 | yarn-error.log
14 | **/*.test.ts
15 | src/test
16 | **/*.pem
17 |
18 | # Data
19 | db_data
20 | minio_data
21 |
22 | # Git
23 | .git
24 | .gitignore
25 |
26 | # Docker
27 | Dockerfile*
28 | docker-compose*
29 |
30 | # NPM dependencies
31 | node_modules
32 |
33 |
34 | # Eslint
35 | .eslintignore
36 | .eslintrc.js
37 |
38 | #prettieri
39 | .prettierignore
40 | .prettierrc
41 |
42 | #unittest
43 | test
--------------------------------------------------------------------------------
/src/app/v2/routes/users/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const userController = require("../../controllers/user.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.post("/login/phone", asyncHandler(userController.loginPhone));
11 | router.post("/verify/phone", asyncHandler(userController.verifyOTP));
12 | router.post(
13 | "/login/google/popup",
14 | asyncHandler(userController.loginGooglePopup)
15 | );
16 |
17 | module.exports = router;
18 |
--------------------------------------------------------------------------------
/src/commons/configs/multer.config.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* LIB
4 | const multer = require("multer");
5 |
6 | const uploadMemory = multer({ storage: multer.memoryStorage() });
7 |
8 | const uploadDisk = multer({
9 | storage: multer.diskStorage({
10 | destination: function (req, file, cb) {
11 | cb(null, "./src/uploads");
12 | },
13 | filename: function (req, file, cb) {
14 | const uniqueSuffix = Date.now() + file.originalname;
15 | cb(null, uniqueSuffix);
16 | },
17 | }),
18 | });
19 |
20 | module.exports = {
21 | uploadMemory,
22 | uploadDisk,
23 | };
24 |
--------------------------------------------------------------------------------
/src/databases/init.pub.js:
--------------------------------------------------------------------------------
1 | // //* LIB
2 | // const IOREDIS = require("ioredis");
3 |
4 | // //* IMPORT
5 | // const {
6 | // redis: { host, port, user, password },
7 | // } = require("../commons/configs/redis.config");
8 |
9 | // class RedisPublisher {
10 | // constructor() {
11 | // this.redisClient = new IOREDIS({
12 | // port,
13 | // host,
14 | // user,
15 | // password,
16 | // });
17 | // }
18 |
19 | // publish(channel, message) {
20 | // this.redisClient.publish(channel, JSON.stringify(message));
21 | // }
22 | // }
23 |
24 | // module.exports = new RedisPublisher();
25 |
--------------------------------------------------------------------------------
/migrations/create_user_v1.sql:
--------------------------------------------------------------------------------
1 | CREATE TABLE public.user (
2 | id SERIAL PRIMARY KEY,
3 | username VARCHAR(255) NOT NULL,
4 | email VARCHAR(255) NOT NULL,
5 | password VARCHAR(255) NOT NULL,
6 | refetch_token TEXT,
7 | role SMALLINT DEFAULT 10 NOT NULL,
8 | created_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL,
9 | updated_at TIMESTAMP WITHOUT TIME ZONE DEFAULT NOW() NOT NULL
10 | );
11 |
12 |
13 | ALTER TABLE public.user
14 | ADD COLUMN phone TEXT;
15 |
16 | ALTER TABLE "user" ADD CONSTRAINT unique_email UNIQUE ("email");
17 | ALTER TABLE "user" ALTER COLUMN "password" DROP NOT NULL;
18 |
--------------------------------------------------------------------------------
/src/commons/configs/cloudinary.config.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | require("dotenv").config();
3 |
4 | //* IMPORT
5 | const { NODE_ENV } = require("../constants");
6 |
7 | const dev = {
8 | cloud: {
9 | name: process.env.CLOUD_NAME,
10 | api_key: process.env.CLOUD_API_KEY,
11 | api_secret: process.env.CLOUD_API_SECRET,
12 | },
13 | };
14 | const pro = {
15 | cloud: {
16 | name: process.env.CLOUD_NAME,
17 | api_key: process.env.CLOUD_API_KEY,
18 | api_secret: process.env.CLOUD_API_SECRET,
19 | },
20 | };
21 | const config = { dev, pro };
22 |
23 | const env = process.env.NODE_ENV || NODE_ENV.DEV;
24 |
25 | module.exports = config[env];
26 |
--------------------------------------------------------------------------------
/CERT.md:
--------------------------------------------------------------------------------
1 | # Cert HTTPS
2 | ## 1. Generate a private key
3 | ## 2. Create a CSR ( Certificate signing request) using the private key
4 | ## 3. Generate the SSL certificate from CSR
5 |
6 |
7 | # C1
8 | ```cmd
9 | mkdir cert
10 | cd cert
11 | openssl genrsa -out key.pem
12 | openssl req -new -key key.pem -out csr.pem
13 | VI /""/""/ pickurpage llp /""/""/nguyentientai@gmail.com/ ""
14 | openssl x509 -req -days 365 -in csr.pem -signkey key.pem -out cert.pem
15 | ```
16 |
17 | # C2
18 | ```cmd
19 | sudo apt install mkcert
20 | mkcert -install
21 | mkcert -key-file key.pem -cert-file cert.pem example.local "*.example.local"
22 | ```
23 |
--------------------------------------------------------------------------------
/src/auth/auth.password.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const bcrypt = require("bcrypt");
3 |
4 | //* IMPORT
5 | const { SALT_ROUNDS } = require("../commons/constants");
6 |
7 | const encodePassword = async (password) => {
8 | try {
9 | return await bcrypt.hash(password, SALT_ROUNDS);
10 | } catch (error) {
11 | error.message;
12 | }
13 | };
14 |
15 | const comparePassword = async (passwordText, passwordHash) => {
16 | try {
17 | console.log(passwordText, passwordHash);
18 | return await bcrypt.compare(passwordText, passwordHash);
19 | } catch (error) {
20 | error.message;
21 | }
22 | };
23 |
24 | module.exports = { encodePassword, comparePassword };
25 |
--------------------------------------------------------------------------------
/src/commons/configs/redis.config.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | require("dotenv").config();
3 |
4 | //* IMPORT
5 | const { NODE_ENV } = require("../constants");
6 |
7 | const dev = {
8 | redis: {
9 | host: process.env.REDIS_HOST,
10 | port: process.env.REDIS_PORT,
11 | user: process.env.REDIS_USER,
12 | password: process.env.REDIS_PASSWORD,
13 | },
14 | };
15 | const pro = {
16 | redis: {
17 | host: process.env.REDIS_HOST,
18 | port: process.env.REDIS_PORT,
19 | user: process.env.REDIS_USER,
20 | password: process.env.REDIS_PASSWORD,
21 | },
22 | };
23 | const config = { dev, pro };
24 |
25 | const env = process.env.NODE_ENV || NODE_ENV.DEV;
26 |
27 | module.exports = config[env];
28 |
--------------------------------------------------------------------------------
/src/app/v1/routes/labels/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const labelController = require("../../controllers/label.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.get("/get/all", asyncHandler(labelController.getAll));
11 |
12 | router.get("/get/:labelId", asyncHandler(labelController.getDetail));
13 |
14 | router.post("/create", asyncHandler(labelController.create));
15 |
16 | router.patch("/update/:labelId", asyncHandler(labelController.update));
17 |
18 | router.delete("/delete/:labelId", asyncHandler(labelController.delete));
19 |
20 | module.exports = router;
21 |
--------------------------------------------------------------------------------
/src/commons/helpers/errorHandle.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const logger = require("../../loggers/winston.log");
3 | const { StatusCodes, ReasonPhrases } = require("../utils/httpStatusCode");
4 |
5 | const errorHandler = (error, checkNodeApp) => {
6 | const statusCode = error.status || StatusCodes.INTERNAL_SERVER_ERROR;
7 | const message = error.message || ReasonPhrases.INTERNAL_SERVER_ERROR;
8 |
9 | const response = {
10 | message,
11 | status: statusCode,
12 | };
13 |
14 | if (checkNodeApp) {
15 | Object.assign(response, { stack: error.stack });
16 | }
17 |
18 | logger.error(`${response.status} = ${response.message} = ${response.stack}`);
19 | return { response };
20 | };
21 |
22 | module.exports = { errorHandler };
23 |
--------------------------------------------------------------------------------
/src/databases/init.firebase.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const fs = require("fs");
3 | const firebase = require("firebase/compat/app");
4 | require("firebase/compat/auth");
5 |
6 | const rawData = fs.readFileSync("firebaseConfig.json");
7 | const firebaseConfig = JSON.parse(rawData);
8 |
9 | firebase.initializeApp(firebaseConfig);
10 |
11 | const email = "example@example.com";
12 | const password = "examplePassword";
13 |
14 | firebase
15 | .auth()
16 | .signInWithEmailAndPassword(email, password)
17 | .then((_) => {
18 | console.log("CONNECT FIREBASE:", "OK");
19 | })
20 | .catch((error) => {
21 | console.error("ERROR CONNECT:", error);
22 | });
23 |
24 | const auth = firebase.auth();
25 |
26 | module.exports = { firebase, auth };
27 |
--------------------------------------------------------------------------------
/src/databases/init.cloudinary.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const cloudinary = require("cloudinary");
3 |
4 | //* IMPORT
5 | const {
6 | cloud: { name, api_key, api_secret },
7 | } = require("../commons/configs/cloudinary.config");
8 |
9 | cloudinary.config({
10 | cloud_name: name,
11 | api_key,
12 | api_secret,
13 | });
14 |
15 | checkCloudinaryConnectivity = async () => {
16 | try {
17 | const pingResponse = await cloudinary.api.ping();
18 | console.info(
19 | "CLOUDINARY IS CONNECTED:",
20 | pingResponse?.status.toUpperCase()
21 | );
22 | } catch (error) {
23 | console.error("Error checking Cloudinary connectivity:", error);
24 | }
25 | };
26 |
27 | checkCloudinaryConnectivity();
28 |
29 | module.exports = cloudinary;
30 |
--------------------------------------------------------------------------------
/primary.mjs:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | import cluster from "cluster";
3 | import os from "os";
4 | import path from "path";
5 | import { fileURLToPath } from "url";
6 |
7 | const __dirname = path.dirname(fileURLToPath(import.meta.url));
8 |
9 | const cpuCount = os.cpus().length;
10 |
11 | console.log(`The total number of CPUs: ${cpuCount}`);
12 | console.log(`Primary pid: ${process.pid}`);
13 |
14 | cluster.setupPrimary({
15 | exec: path.join(__dirname, "server.js"),
16 | });
17 |
18 | for (let i = 0; i < cpuCount; i++) {
19 | cluster.fork();
20 | }
21 | cluster.on("exit", (worker, code, signal) => {
22 | console.log(`Worker ${worker.process.pid} has been killed`);
23 | console.log("Starting another worker", code, signal);
24 | cluster.fork();
25 | });
26 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 | github:
3 | - fdhhhdjd
4 | # Up to 4 GitHub Sponsors-enabled usernames, e.g., [user1, user2]
5 | patreon:
6 | user?u=65668237
7 | # A single Patreon username with user ID, e.g., user?u=65668237
8 | open_collective:
9 | # A single Open Collective username
10 | ko_fi:
11 | tientainguyen
12 | # A single Ko-fi username
13 | tidelift:
14 | # A single Tidelift platform-name/package-name, e.g., npm/babel
15 | community_bridge:
16 | # A single Community Bridge project-name, e.g., cloud-foundry
17 | liberapay: nguyentientai
18 |
19 | issuehunt:
20 | # A single IssueHunt username
21 | otechie:
22 | # A single Otechie username
23 | custom: https://profile-forme.com
--------------------------------------------------------------------------------
/src/app/v1/routes/index.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* LIB
4 | const express = require("express");
5 |
6 | //* IMPORT
7 | const {
8 | StatusCodes,
9 | ReasonPhrases,
10 | } = require("../../../commons/utils/httpStatusCode");
11 | const { getRedis } = require("../../../databases/init.redis");
12 |
13 | const router = express.Router();
14 |
15 | router.use("/users", require("./users"));
16 | router.use("/todos", require("./todos"));
17 | router.use("/labels", require("./labels"));
18 |
19 | router.get("/", async (_, res, __) => {
20 | const healthCheck = {
21 | uptime: process.uptime(),
22 | message: ReasonPhrases.OK,
23 | timestamp: Date.now(),
24 | };
25 | return res.status(StatusCodes.OK).json(healthCheck);
26 | });
27 |
28 | module.exports = router;
29 |
--------------------------------------------------------------------------------
/src/app/v2/controllers/user.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse } = require("../../../cores/success.response");
3 | const userService = require("../services/user.service");
4 |
5 | class UserV2Controller {
6 | async loginPhone(req, res, ___) {
7 | new SuccessResponse({
8 | metadata: await userService.loginPhone(req.body),
9 | }).send(res);
10 | }
11 | async verifyOTP(req, res, ___) {
12 | new SuccessResponse({
13 | metadata: await userService.verifyOTP(res, req.body),
14 | }).send(res);
15 | }
16 |
17 | async loginGooglePopup(req, res, ___) {
18 | new SuccessResponse({
19 | metadata: await userService.loginGooglePopup(res, req.body),
20 | }).send(res);
21 | }
22 | }
23 |
24 | module.exports = new UserV2Controller();
25 |
--------------------------------------------------------------------------------
/src/commons/configs/gmail.config.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | require("dotenv").config();
3 |
4 | //* IMPORT
5 | const { NODE_ENV } = require("../constants");
6 |
7 | const dev = {
8 | gmail: {
9 | port: process.env.SMTP_PORT,
10 | host: process.env.SMTP_HOST,
11 | service: process.env.SMTP_SERVICE,
12 | mail: process.env.SMTP_MAIL,
13 | password: process.env.SMTP_PASSWORD,
14 | },
15 | };
16 | const pro = {
17 | gmail: {
18 | port: process.env.SMTP_PORT,
19 | host: process.env.SMTP_HOST,
20 | service: process.env.SMTP_SERVICE,
21 | mail: process.env.SMTP_MAIL,
22 | password: process.env.SMTP_PASSWORD,
23 | },
24 | };
25 | const config = { dev, pro };
26 |
27 | const env = process.env.NODE_ENV || NODE_ENV.DEV;
28 |
29 | module.exports = config[env];
30 |
--------------------------------------------------------------------------------
/src/app/v2/routes/notifications/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const notificationController = require("../../controllers/notification.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.post(
11 | "/single/device",
12 | asyncHandler(notificationController.sendDeviceId)
13 | );
14 |
15 | router.post(
16 | "/multicast/device",
17 | asyncHandler(notificationController.sendMulticast)
18 | );
19 |
20 | router.post("/topic/device", asyncHandler(notificationController.sendTopic));
21 |
22 | router.post(
23 | "/condition/device",
24 | asyncHandler(notificationController.sendTopicCondition)
25 | );
26 |
27 | module.exports = router;
28 |
--------------------------------------------------------------------------------
/src/app/v1/models/todo_list_label.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | upSertTodoWithLabel: async (data) => {
6 | try {
7 | const queryResult = await knexInstance("todo_list_label")
8 | .insert(data)
9 | .onConflict("label_id")
10 | .merge()
11 | .returning(["todo_list_id", "label_id"]);
12 |
13 | return queryResult;
14 | } catch (error) {
15 | throw error;
16 | }
17 | },
18 | deleteTodoAssignLabel: async (query) => {
19 | try {
20 | const result = knexInstance("todo_list_label")
21 | .del()
22 | .where(query)
23 | .returning(["label_id"]);
24 | return result;
25 | } catch (error) {
26 | throw error;
27 | }
28 | },
29 | };
30 |
--------------------------------------------------------------------------------
/src/commons/constants/index.js:
--------------------------------------------------------------------------------
1 | const NODE_ENV = {
2 | DEV: "dev",
3 | PRO: "pro",
4 | };
5 |
6 | const LIMIT_BODY = {
7 | _5_MB: "5mb",
8 | };
9 |
10 | const TIME_TOKEN = {
11 | ACCESS: "15m",
12 | REFETCH: "7d",
13 | };
14 |
15 | const TIME_COOKIE = {
16 | _7_DAY: 7 * 24 * 60 * 60 * 1000,
17 | };
18 |
19 | const TIME = {
20 | _15_SECOND: 15 * 1000,
21 | _1_MINUTE: 60,
22 | _2_MINUTE: 2 * 60,
23 | _3_MINUTE: 3 * 60,
24 | };
25 |
26 | const TOKEN_EXPIRE = "jwt expired";
27 | const INVALID_TOKEN = "invalid token";
28 |
29 | const SALT_ROUNDS = 10;
30 |
31 | const ROLE = {
32 | CUSTOMER: 10,
33 | ADMIN: 20,
34 | };
35 |
36 | module.exports = {
37 | NODE_ENV,
38 | LIMIT_BODY,
39 | TIME_TOKEN,
40 | TIME_COOKIE,
41 | TOKEN_EXPIRE,
42 | INVALID_TOKEN,
43 | SALT_ROUNDS,
44 | ROLE,
45 | TIME,
46 | };
47 |
--------------------------------------------------------------------------------
/src/app/v2/controllers/image.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse } = require("../../../cores/success.response");
3 | const imageService = require("../services/image.service");
4 |
5 | class UploadController {
6 | async upload(req, res, ___) {
7 | new SuccessResponse({
8 | metadata: await imageService.upload(req.userInfo, req.file),
9 | }).send(res);
10 | }
11 |
12 | async uploadMulti(req, res, ___) {
13 | new SuccessResponse({
14 | metadata: await imageService.uploadMulti(req.userInfo, {
15 | files: req.files,
16 | }),
17 | }).send(res);
18 | }
19 |
20 | async removeImage(req, res, ___) {
21 | new SuccessResponse({
22 | metadata: await imageService.removePublicId(req.body),
23 | }).send(res);
24 | }
25 | }
26 |
27 | module.exports = new UploadController();
28 |
--------------------------------------------------------------------------------
/src/commons/utils/random.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const crypto = require("crypto");
3 |
4 | //* IMPORT
5 | const {
6 | app: { radomPassword },
7 | } = require("../../commons/configs/app.config");
8 |
9 | const generateRandomString = (length = 10) => {
10 | const charsetLength = radomPassword.length;
11 |
12 | const password = Array.from({ length }, () =>
13 | radomPassword.charAt(Math.floor(Math.random() * charsetLength))
14 | ).join("");
15 |
16 | return password;
17 | };
18 | const generateRandomLink = (length) => {
19 | return new Promise((resolve, reject) => {
20 | crypto.randomBytes(length, (ex, buf) => {
21 | if (ex) {
22 | reject(ex);
23 | } else {
24 | resolve(buf.toString("hex"));
25 | }
26 | });
27 | });
28 | };
29 |
30 | module.exports = { generateRandomString, generateRandomLink };
31 |
--------------------------------------------------------------------------------
/src/app/v1/models/user.model.v1.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const pool = require("../databases/init.pg");
3 |
4 | module.exports = {
5 | getUserById: async ({ id }, data) => {
6 | try {
7 | const columns = Object.values(data).join(", ");
8 |
9 | const result = await pool.query({
10 | text: `SELECT ${columns} FROM public.user WHERE id = $1`,
11 | values: [id],
12 | });
13 |
14 | return result.rows;
15 | } catch (error) {
16 | throw error;
17 | }
18 | },
19 |
20 | getAllUser: async (data) => {
21 | try {
22 | const columns = Object.values(data).join(", ");
23 |
24 | const result = await pool.query({
25 | text: `SELECT ${columns} FROM public.user ORDER BY created_at DESC`,
26 | });
27 |
28 | return result.rows;
29 | } catch (error) {
30 | throw error;
31 | }
32 | },
33 | };
34 |
--------------------------------------------------------------------------------
/src/app/v1/routes/todos/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const todoController = require("../../controllers/todo.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 |
8 | const router = express.Router();
9 |
10 | router.get("/get/all", asyncHandler(todoController.getAll));
11 |
12 | router.get("/get/:todoId", asyncHandler(todoController.getDetail));
13 |
14 | router.post("/create", asyncHandler(todoController.create));
15 |
16 | router.patch("/update/:todoId", asyncHandler(todoController.update));
17 |
18 | router.post("/upsert", asyncHandler(todoController.upsert));
19 |
20 | router.delete(
21 | "/delete/label",
22 | asyncHandler(todoController.deleteTodoAssignLabel)
23 | );
24 |
25 | router.delete("/delete/:todoId", asyncHandler(todoController.delete));
26 |
27 | module.exports = router;
28 |
--------------------------------------------------------------------------------
/src/app/v2/routes/images/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const imageController = require("../../controllers/image.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 | const { uploadDisk } = require("../../../../commons/configs/multer.config");
8 | const {
9 | checkAuthorizationAccessToken,
10 | } = require("../../../../auth/check.auth");
11 |
12 | const router = express.Router();
13 |
14 | router.use(checkAuthorizationAccessToken);
15 |
16 | router.post(
17 | "/upload",
18 | uploadDisk.single("file", 1),
19 | asyncHandler(imageController.upload)
20 | );
21 |
22 | router.post(
23 | "/upload/multi",
24 | uploadDisk.array("files", 3),
25 | asyncHandler(imageController.uploadMulti)
26 | );
27 |
28 | router.post("/remove", asyncHandler(imageController.removeImage));
29 |
30 | module.exports = router;
31 |
--------------------------------------------------------------------------------
/src/auth/auth.cookie.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const _ = require("lodash");
3 |
4 | //* IMPORT
5 | const {
6 | app: { node },
7 | } = require("../commons/configs/app.config");
8 | const { NODE_ENV, TIME_COOKIE } = require("../commons/constants");
9 | const { BadRequestRequestError } = require("../cores/error.response");
10 |
11 | const createCookie = (res, key, value, options = {}) => {
12 | try {
13 | const isOptionsEmpty = _.isEmpty(options);
14 | if (isOptionsEmpty) {
15 | const isProduction = node === NODE_ENV.PRO;
16 | Object.assign(options, {
17 | httpOnly: isProduction,
18 | sameSite: isProduction,
19 | secure: isProduction,
20 | maxAge: TIME_COOKIE._7_DAY,
21 | });
22 | }
23 |
24 | res.cookie(key, value, options);
25 | } catch (error) {
26 | throw new BadRequestRequestError(error);
27 | }
28 | };
29 |
30 | module.exports = {
31 | createCookie,
32 | };
33 |
--------------------------------------------------------------------------------
/docker-compose.yml:
--------------------------------------------------------------------------------
1 | version: "3.8"
2 |
3 | services:
4 | ###* PG4 ###
5 | postgresql:
6 | container_name: postgresql
7 | image: postgres:latest
8 | restart: unless-stopped
9 | environment:
10 | POSTGRES_DB: "${POSTGRES_DB}"
11 | POSTGRES_USER: "${POSTGRES_USER}"
12 | POSTGRES_PASSWORD: "${POSTGRES_PASSWORD}"
13 | volumes:
14 | - db_data:/var/lib/postgresql/data
15 | ports:
16 | - "${POSTGRES_PORT_EDIT}:${POSTGRES_PORT}"
17 | env_file:
18 | - .env
19 | networks:
20 | - fullstack
21 | healthcheck:
22 | test:
23 | [
24 | "CMD-SHELL",
25 | "sh -c 'pg_isready -U ${POSTGRES_USER} -d ${POSTGRES_DB}'",
26 | ]
27 | interval: 10s
28 | timeout: 3s
29 | retries: 3
30 |
31 | ###* NETWORD GENERAL ###
32 | networks:
33 | fullstack:
34 | driver: bridge
35 | volumes:
36 | db_data:
37 | driver: local
38 |
--------------------------------------------------------------------------------
/makefile:
--------------------------------------------------------------------------------
1 | # Get file .env
2 | include .env
3 | export $(shell sed 's/=.*//' .env)
4 |
5 | # Folder constants
6 | DOCKER_COMPOSE := docker-compose.yml
7 | PGPASSWORD := $(POSTGRES_PASSWORD)
8 | PG_CONTAINER := postgresql
9 | FILE_SQL := ./migrations/backup/dump_2024-01-04_15_48_56.sql
10 | FOLDER_BACKUP := ./migrations/backup/dump_`date +%Y-%m-%d"_"%H_%M_%S`.sql
11 |
12 | # Run auto
13 | default:
14 | docker ps
15 |
16 | run-build:
17 | sudo docker-compose -f $(DOCKER_COMPOSE) up -d --build
18 |
19 | run-down:
20 | docker-compose -f $(DOCKER_COMPOSE) down
21 |
22 | backup-data-pg_dump:
23 | docker exec -i $(PG_CONTAINER) pg_dump -h $(PG_CONTAINER) -U $(POSTGRES_USER) $(POSTGRES_DB) > $(FOLDER_BACKUP)
24 |
25 | backup-data-pg_dumpall:
26 | docker exec -t $(PG_CONTAINER) pg_dumpall -c -U $(POSTGRES_USER) > $(FOLDER_BACKUP)
27 |
28 | restore-data:
29 | cat $(FILE_SQL) | docker exec -i $(PG_CONTAINER) psql -h localhost -U $(POSTGRES_USER) -d $(POSTGRES_DB)
--------------------------------------------------------------------------------
/migrations/trg_insert_user_04012024.sql:
--------------------------------------------------------------------------------
1 | -- Tạo trigger function
2 | CREATE OR REPLACE FUNCTION insert_hello_label_after_user_change()
3 | RETURNS TRIGGER AS
4 | $$
5 | BEGIN
6 | -- Kiểm tra sự kiện là INSERT và có cột 'username'
7 | IF TG_OP = 'INSERT' AND NEW.username IS NOT NULL THEN
8 | -- Chèn một nhãn mới với tên là 'hello create' vào bảng label
9 | INSERT INTO public.label (name) VALUES ('hello create');
10 | END IF;
11 |
12 | -- Kiểm tra sự kiện là UPDATE và cột 'username' thay đổi
13 | IF TG_OP = 'UPDATE' AND NEW.username <> OLD.username THEN
14 | -- Chèn một nhãn mới với tên là 'hello update' vào bảng label
15 | INSERT INTO public.label (name) VALUES ('hello update');
16 | END IF;
17 |
18 | RETURN NEW;
19 | END;
20 | $$
21 | LANGUAGE plpgsql;
22 |
23 | CREATE TRIGGER trg_insert_hello_label_after_user_change
24 | AFTER INSERT OR UPDATE
25 | ON public."user"
26 | FOR EACH ROW
27 | EXECUTE FUNCTION insert_hello_label_after_user_change();
28 |
--------------------------------------------------------------------------------
/src/app/v2/controllers/notification.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse } = require("../../../cores/success.response");
3 | const notificationService = require("../services/notification.service");
4 |
5 | class NotificationV2Controller {
6 | async sendDeviceId(req, res, ___) {
7 | new SuccessResponse({
8 | metadata: await notificationService.sendDeviceId(req.body),
9 | }).send(res);
10 | }
11 |
12 | async sendMulticast(req, res, ___) {
13 | new SuccessResponse({
14 | metadata: await notificationService.sendMulticast(req.body),
15 | }).send(res);
16 | }
17 |
18 | async sendTopic(req, res, ___) {
19 | new SuccessResponse({
20 | metadata: await notificationService.sendTopic(req.body),
21 | }).send(res);
22 | }
23 |
24 | async sendTopicCondition(req, res, ___) {
25 | new SuccessResponse({
26 | metadata: await notificationService.sendTopicCondition(req.body),
27 | }).send(res);
28 | }
29 | }
30 |
31 | module.exports = new NotificationV2Controller();
32 |
--------------------------------------------------------------------------------
/src/databases/init.knex.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const KNEX = require("knex");
3 | const { NODE_ENV } = require("../commons/constants");
4 |
5 | const knexInstance = KNEX({
6 | client: "cockroachdb",
7 | connection: `postgresql://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DB}?${process.env.SECURITY}`,
8 | pool: {
9 | min: 10,
10 | max: 20,
11 | acquireTimeoutMillis: 30000,
12 | createTimeoutMillis: 30000,
13 | idleTimeoutMillis: 30000,
14 | reapIntervalMillis: 1000,
15 | },
16 | debug: process.env.NODE_ENV === NODE_ENV.DEV,
17 | });
18 |
19 | knexInstance
20 | .raw("SELECT 1")
21 | .then((_) => {
22 | console.info("CONNECTED TO POSTGRESQL SUCCESS 🐘 !!");
23 | // const poolConfig = KNEX.client.pool;
24 |
25 | // console.info("Pool configuration:", poolConfig);
26 | })
27 | .catch((err) => {
28 | console.error("Failed to connect to PostgreSQL database", err);
29 | });
30 |
31 | module.exports = knexInstance;
32 |
--------------------------------------------------------------------------------
/src/app/v1/controllers/label.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse, Created } = require("../../../cores/success.response");
3 | const labelService = require("../services/label.service");
4 |
5 | class LabelController {
6 | async getAll(_, res, ___) {
7 | new SuccessResponse({
8 | metadata: await labelService.getAll(),
9 | }).send(res);
10 | }
11 |
12 | async getDetail(req, res, ___) {
13 | new SuccessResponse({
14 | metadata: await labelService.getDetail(req.params),
15 | }).send(res);
16 | }
17 |
18 | async create(req, res, ___) {
19 | new Created({
20 | metadata: await labelService.create(req.body),
21 | }).send(res);
22 | }
23 |
24 | async update(req, res, ___) {
25 | new SuccessResponse({
26 | metadata: await labelService.update(req.body, req.params),
27 | }).send(res);
28 | }
29 |
30 | async delete(req, res, ___) {
31 | new SuccessResponse({
32 | metadata: await labelService.delete(req.params),
33 | }).send(res);
34 | }
35 | }
36 |
37 | module.exports = new LabelController();
38 |
--------------------------------------------------------------------------------
/demo.js:
--------------------------------------------------------------------------------
1 | const bcrypt = require("bcrypt");
2 |
3 | const saltRounds = 10;
4 |
5 | // const handlePassword = async (password) => {
6 | // const salt = "$2b$10$QEbg.e6ig1I4rSu4ljIxDu";
7 | // // const salt = await bcrypt.genSalt(saltRounds);
8 |
9 | // console.log(`salt:: ${salt}`);
10 |
11 | // const hash = await bcrypt.hash(password, salt);
12 | // console.log(`Hash:: ${hash}`);
13 | // };
14 |
15 | const handlePassword = async (password) => {
16 | const saltRounds = 10;
17 | // const salt = await bcrypt.genSalt(saltRounds);
18 | const salt = "$2b$10$U0Lp2zaHtzqC/.61cGCtZe";
19 |
20 | // $2b$10$gqjRx4X/yTzZS5MIzVvlAu tram1234 $2b$10$gqjRx4X/yTzZS5MIzVvlAuqoMhmYdv5ffWWY2yxTO/uWrTxtA0VXy
21 | // $2b$10$.n7NELKxdFWpefx6TKBqIO nam1234 $2b$10$.n7NELKxdFWpefx6TKBqIO.InBI.YBPO5..mbbMs6SppvqX0UnDf.
22 | // $2b$10$U0Lp2zaHtzqC/.61cGCtZe phong $2b$10$U0Lp2zaHtzqC/.61cGCtZeEuB.gyxphozW37NxmfUUNTyeNGmP81q
23 |
24 | console.log(`salt:: ${salt}`);
25 |
26 | const hash = await bcrypt.hash(password, salt);
27 |
28 | console.log(`Hash:: ${hash}`);
29 | };
30 |
31 | handlePassword("phong");
32 |
33 | // $2b$10$0Ihy6yvhvb3ZMOgaaIBH6e
34 |
--------------------------------------------------------------------------------
/.env.example:
--------------------------------------------------------------------------------
1 | # App
2 | NODE_ENV='dev'
3 |
4 | # Posgreql
5 | POSTGRES_DB=
6 | POSTGRES_USER=
7 | POSTGRES_PASSWORD=
8 | POSTGRES_PORT=
9 | POSTGRES_HOST=
10 | SECURITY='sslmode=verify-full'
11 |
12 | # MORGAN
13 | MORGAN='dev'
14 |
15 | # WINSTON
16 | LOG_LEVEL='info'
17 |
18 | # SMTP
19 | SMTP_HOST=smtp.gmail.com
20 | SMTP_PORT=465
21 | SMTP_SERVICE=gmail
22 | SMTP_MAIL=
23 | SMTP_PASSWORD=
24 |
25 |
26 | # CLOUDINARY
27 | CLOUD_NAME=
28 | CLOUD_API_KEY=
29 | CLOUD_API_SECRET=
30 |
31 | ## GOOGLE PUSH NOTIFICATION
32 | GOOGLE_PUSH_NOTIFICATION=https://fcm.googleapis.com
33 |
34 | # FIREBASE MESSSAGE
35 | PRIVATE_KEY_FIREBASE_MESSAGE=
36 |
37 |
38 | # RANDOM PASSWORD
39 | RANDOM_PASSWORD='abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!@#$%^&*()_+{}[]<>?'
40 |
41 |
42 | # REDIS
43 | REDIS_HOST=
44 | REDIS_PORT=
45 | REDIS_USER=
46 | REDIS_PASSWORD=
47 |
48 | # JWT
49 | ACCESS_TOKEN_SECRET='l*pLoT3d)4**-@:'i.vRh(5'S98bgyr.sUH.q=g}G8gz0Xi:=s),l2|BM(9x0.ve{2cT4]a2Z562;Hme!lEO-+X@L-fhXqHDXCg2'
50 | REFRESH_TOKEN_SECRET='q:ih7-;-h7M)*;0X]W6e\EWW='y:-m7:sORv{zJ1vB(O]@'4p}G-/;b2QsM2T-b1-}zPcgk--@YSeQVwmFg?{/QJ(Uu|?La2fFc"
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/src/app/v1/services/label.service.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const labelModel = require("../models/label.model");
3 |
4 | class LabelService {
5 | // Todo 1. Get all
6 | async getAll() {
7 | const data = {
8 | id: "id",
9 | name: "name",
10 | };
11 | const result = await labelModel.getAll(data);
12 | return result;
13 | }
14 |
15 | // Todo 2. Get detail
16 | async getDetail({ labelId }) {
17 | const data = {
18 | id: "id",
19 | name: "name",
20 | };
21 | const result = await labelModel.getById({ id: labelId }, data);
22 | return result;
23 | }
24 |
25 | // Todo 3. Create
26 | async create({ name }) {
27 | const result = await labelModel.create({ name });
28 | return result;
29 | }
30 |
31 | // Todo 4. Update
32 | async update({ name }, { labelId }) {
33 | const result = await labelModel.update({ name }, { id: Number(labelId) });
34 | return result;
35 | }
36 |
37 | // Todo 5. Delete id
38 | async delete({ labelId }) {
39 | const result = await labelModel.deleteId({ id: Number(labelId) });
40 | return result;
41 | }
42 | }
43 |
44 | module.exports = new LabelService();
45 |
--------------------------------------------------------------------------------
/src/cores/success.response.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* IMPORT
4 | const {
5 | StatusCodes,
6 | ReasonPhrases,
7 | } = require("../commons/utils/httpStatusCode");
8 |
9 | class SuccessResponse {
10 | constructor({
11 | message,
12 | statusCode = StatusCodes.OK,
13 | reasonStatusCode = ReasonPhrases.OK,
14 | option = {},
15 | metadata = {},
16 | }) {
17 | this.message = !message ? reasonStatusCode : message;
18 | this.status = statusCode;
19 | this.option = option;
20 | this.metadata = metadata;
21 | }
22 |
23 | send(res, _ = {}) {
24 | return res.status(this.status).json(this);
25 | }
26 | }
27 |
28 | class Ok extends SuccessResponse {
29 | constructor({ message, metadata }) {
30 | super({ message, metadata });
31 | }
32 | }
33 |
34 | class Created extends SuccessResponse {
35 | constructor({
36 | option = {},
37 | message,
38 | statusCode = StatusCodes.CREATED,
39 | reasonStatusCode = ReasonPhrases.CREATED,
40 | metadata = {},
41 | }) {
42 | super({ message, statusCode, reasonStatusCode, metadata });
43 | this.option = option;
44 | }
45 | }
46 | module.exports = {
47 | Ok,
48 | Created,
49 | SuccessResponse,
50 | };
51 |
--------------------------------------------------------------------------------
/src/loggers/winston.log.js:
--------------------------------------------------------------------------------
1 | //* LIb
2 | const winston = require("winston");
3 | const { combine, timestamp, printf } = winston.format;
4 | const DailyRotateFile = require("winston-daily-rotate-file");
5 | const path = require("path");
6 |
7 | //* IMPORT
8 | const {
9 | app: { node, log },
10 | } = require("../commons/configs/app.config");
11 | const { NODE_ENV } = require("../commons/constants");
12 |
13 | const logFormat = printf((info) => {
14 | return `[${info.timestamp}] [${info.level.toUpperCase()}] [${node}] ${
15 | info.message
16 | }`;
17 | });
18 |
19 | const logsDirectory = path.join(__dirname, "../logs");
20 |
21 | const isProduct = node === NODE_ENV.PRO;
22 | const logger = winston.createLogger({
23 | level: isProduct ? log : "debug",
24 | format: combine(
25 | timestamp({
26 | format: "YYYY-MM-DD hh:mm:ss.SSS A",
27 | }),
28 | logFormat
29 | ),
30 | transports: [
31 | new winston.transports.Console(),
32 | new DailyRotateFile({
33 | dirname: logsDirectory,
34 | filename: "app-%DATE%.log",
35 | datePattern: "YYYY-MM-DD",
36 | zippedArchive: true,
37 | maxSize: "20m",
38 | maxFiles: "14d",
39 | }),
40 | ],
41 | });
42 |
43 | module.exports = logger;
44 |
--------------------------------------------------------------------------------
/src/commons/configs/app.config.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | require("dotenv").config();
3 |
4 | //* IMPORT
5 | const { NODE_ENV } = require("../constants");
6 |
7 | const dev = {
8 | app: {
9 | port: process.env.PORT || 5000,
10 | morgan: process.env.MORGAN || "dev",
11 | node: process.env.NODE_ENV || NODE_ENV.DEV,
12 | web_server: process.env.WEB_SERVER,
13 | log: process.env.LOG_LEVEL,
14 | accessKey: process.env.ACCESS_TOKEN_SECRET,
15 | refetchKey: process.env.REFRESH_TOKEN_SECRET,
16 | radomPassword: process.env.RANDOM_PASSWORD,
17 | firebaseMessage: process.env.PRIVATE_KEY_FIREBASE_MESSAGE,
18 | },
19 | };
20 | const pro = {
21 | app: {
22 | port: process.env.PORT || 5000,
23 | morgan: process.env.MORGAN || "combined",
24 | node: process.env.NODE_ENV || NODE_ENV.DEV,
25 | web_server: process.env.WEB_SERVER,
26 | log: process.env.LOG_LEVEL,
27 | accessKey: process.env.ACCESS_TOKEN_SECRET,
28 | refetchKey: process.env.REFRESH_TOKEN_SECRET,
29 | radomPassword: process.env.RANDOM_PASSWORD,
30 | firebaseMessage: process.env.PRIVATE_KEY_FIREBASE_MESSAGE,
31 | },
32 | };
33 | const config = { dev, pro };
34 |
35 | const env = process.env.NODE_ENV || NODE_ENV.DEV;
36 |
37 | module.exports = config[env];
38 |
--------------------------------------------------------------------------------
/src/app/v2/models/user_otp.model.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | createUserOtp: (data) =>
6 | new Promise((resolve, reject) => {
7 | try {
8 | const result = knexInstance("user_otp")
9 | .insert(data)
10 | .onConflict("id")
11 | .merge()
12 | .returning(["id"]);
13 | resolve(result);
14 | } catch (error) {
15 | reject(error);
16 | }
17 | }),
18 |
19 | deleteExpiredRecords: () =>
20 | new Promise((resolve, reject) => {
21 | try {
22 | const currentTime = new Date();
23 |
24 | const result = knexInstance("user_otp")
25 | .del()
26 | .where("expires_at", "<=", currentTime)
27 | .returning(["id"]);
28 | resolve(result);
29 | } catch (error) {
30 | reject(error);
31 | }
32 | }),
33 |
34 | deleteId: async (query) => {
35 | const result = knexInstance("user_otp")
36 | .del()
37 | .where(query)
38 | .returning(["id"]);
39 | return result;
40 | },
41 |
42 | getUserOTPById: async (query, data) => {
43 | const result = await knexInstance("user_otp")
44 | .select(data)
45 | .where(query)
46 | .first();
47 | return result;
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/app/v1/models/label.model.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | create: (data) =>
6 | new Promise((resolve, reject) => {
7 | try {
8 | const result = knexInstance("label")
9 | .insert(data)
10 | .onConflict("id")
11 | .merge()
12 | .returning(["id"]);
13 | resolve(result);
14 | } catch (error) {
15 | reject(error);
16 | }
17 | }),
18 |
19 | update: async (data, query) =>
20 | new Promise((resolve, reject) => {
21 | try {
22 | const result = knexInstance("label")
23 | .update(data)
24 | .where(query)
25 | .returning(["id"]);
26 | resolve(result);
27 | } catch (error) {
28 | reject(error);
29 | }
30 | }),
31 |
32 | deleteId: async (query) => {
33 | const result = knexInstance("label").del().where(query).returning(["id"]);
34 | return result;
35 | },
36 |
37 | getById: async (query, data) => {
38 | const result = await knexInstance("label").select(data).where(query);
39 | return result;
40 | },
41 |
42 | getAll: async (data) => {
43 | const result = await knexInstance("label")
44 | .select(data)
45 | .orderBy("created_at", "desc");
46 | return result;
47 | },
48 | };
49 |
--------------------------------------------------------------------------------
/src/app/v1/models/user_verification.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | createUserVerification: (data) =>
6 | new Promise((resolve, reject) => {
7 | try {
8 | const result = knexInstance("user_verification")
9 | .insert(data)
10 | .onConflict("id")
11 | .merge()
12 | .returning(["id"]);
13 | resolve(result);
14 | } catch (error) {
15 | reject(error);
16 | }
17 | }),
18 |
19 | deleteExpiredRecords: () =>
20 | new Promise((resolve, reject) => {
21 | try {
22 | const currentTime = new Date();
23 |
24 | const result = knexInstance("user_verification")
25 | .del()
26 | .where("expires_at", "<=", currentTime)
27 | .returning(["id"]);
28 | resolve(result);
29 | } catch (error) {
30 | reject(error);
31 | }
32 | }),
33 |
34 | deleteId: async (query) => {
35 | const result = knexInstance("user_verification")
36 | .del()
37 | .where(query)
38 | .returning(["id"]);
39 | return result;
40 | },
41 |
42 | getUserVerificationById: async (query, data) => {
43 | const result = await knexInstance("user_verification")
44 | .select(data)
45 | .where(query)
46 | .first();
47 | return result;
48 | },
49 | };
50 |
--------------------------------------------------------------------------------
/src/app/v1/controllers/todo.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse, Created } = require("../../../cores/success.response");
3 | const todoService = require("../services/todo.service");
4 |
5 | class TodoController {
6 | async getAll(_, res, ___) {
7 | new SuccessResponse({
8 | metadata: await todoService.getAll(),
9 | }).send(res);
10 | }
11 |
12 | async getDetail(req, res, ____) {
13 | new SuccessResponse({
14 | metadata: await todoService.getDetail(req.params),
15 | }).send(res);
16 | }
17 |
18 | async create(req, res, ___) {
19 | new Created({
20 | metadata: await todoService.create(req.body),
21 | }).send(res);
22 | }
23 |
24 | async update(req, res, ___) {
25 | new SuccessResponse({
26 | metadata: await todoService.update(req.body, req.params),
27 | }).send(res);
28 | }
29 |
30 | async upsert(req, res, ___) {
31 | new SuccessResponse({
32 | metadata: await todoService.upsert(req.body),
33 | }).send(res);
34 | }
35 |
36 | async deleteTodoAssignLabel(req, res, ___) {
37 | new SuccessResponse({
38 | metadata: await todoService.deleteTodoAssignLabel(req.body),
39 | }).send(res);
40 | }
41 |
42 | async delete(req, res, ___) {
43 | new SuccessResponse({
44 | metadata: await todoService.delete(req.params),
45 | }).send(res);
46 | }
47 | }
48 |
49 | module.exports = new TodoController();
50 |
--------------------------------------------------------------------------------
/src/views/resetThankYou.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Reset Mật khẩu - Cảm ơn
7 |
44 |
45 |
46 |
47 |
Thank You {{username}} 😍!
48 |
Mật khẩu của bạn đã được thay đổi thành công.
49 |
50 | Cảm ơn đã sử dụng dịch vụ chúng tôi
51 | profile-forme.com !
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/src/views/changePasswordThankYou.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Thay đổi mật khẩu - Cảm ơn
7 |
44 |
45 |
46 |
47 |
Thank You {{username}} 😍!
48 |
Mật khẩu của bạn đã được thay đổi thành công.
49 |
50 | Cảm ơn đã sử dụng dịch vụ chúng tôi
51 | profile-forme.com !
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backend-demo",
3 | "version": "1.0.0",
4 | "description": "",
5 | "main": "index.js",
6 | "scripts": {
7 | "dev": "DEBUG=ioredis:* node --watch server.js",
8 | "cluster": "DEBUG=ioredis:* node --watch primary.mjs",
9 | "tai": "node --watch server.js",
10 | "node": "nodemon server.js",
11 | "test": "echo \"Error: no test specified\" && exit 1"
12 | },
13 | "keywords": [],
14 | "author": "",
15 | "license": "ISC",
16 | "dependencies": {
17 | "axios": "^1.6.7",
18 | "bcrypt": "^5.1.1",
19 | "cloudinary": "^2.0.0",
20 | "compression": "^1.7.4",
21 | "cookie-parser": "^1.4.6",
22 | "cors": "^2.8.5",
23 | "crypto": "^1.0.1",
24 | "dotenv": "^16.3.2",
25 | "express": "^4.18.2",
26 | "express-handlebars": "^7.1.2",
27 | "firebase": "^10.8.0",
28 | "helmet": "^7.1.0",
29 | "ioredis": "^5.3.2",
30 | "jsonwebtoken": "^9.0.2",
31 | "knex": "^3.1.0",
32 | "morgan": "^1.10.0",
33 | "multer": "^1.4.5-lts.1",
34 | "node-cron": "^3.0.3",
35 | "node-telegram-bot-api": "^0.65.1",
36 | "nodemailer": "^6.9.9",
37 | "nodemailer-express-handlebars": "^6.1.0",
38 | "otp-generator": "^4.0.1",
39 | "pg": "^8.11.3",
40 | "puppeteer": "^22.3.0",
41 | "uuid": "^9.0.1",
42 | "validator": "^13.11.0",
43 | "winston": "^3.11.0",
44 | "winston-daily-rotate-file": "^4.7.1"
45 | },
46 | "devDependencies": {
47 | "nodemon": "^3.0.2"
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/src/app/v2/services/puppeteer.service.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const puppeteer = require("puppeteer");
3 |
4 | class PuppeteerService {
5 | async start({ url }) {
6 | const now = new Date();
7 | const formattedDate = now.toISOString().replace(/:/g, "-");
8 | const fileName = `screenshot-${formattedDate}.jpeg`;
9 |
10 | // Run into mode headless default
11 | const browser = await puppeteer.launch({ headless: true });
12 | const page = await browser.newPage();
13 |
14 | // Adjust setting before page when load
15 | await page.setViewport({ width: 1280, height: 800 });
16 |
17 | // Processing load page and screenshot
18 | try {
19 | await page.goto(url, { waitUntil: "networkidle2", timeout: 30000 }); // Chờ cho đến khi trang đã tải xong hoặc không có hoạt động mạng trong 30 giây
20 | const screenshot = await page.screenshot({
21 | path: fileName,
22 | type: "jpeg",
23 | quality: 100,
24 | fullPage: true, // Screen all page
25 | // clip: { x: 0, y: 0, width: 800, height: 600 }, // Nếu muốn chụp phần cụ thể của trang, hãy bỏ comment và chỉnh sửa clip
26 | });
27 | console.log(`Screenshot saved as ${fileName}`);
28 | return screenshot;
29 | } catch (error) {
30 | console.error(`Error occurred while taking screenshot: ${error}`);
31 | } finally {
32 | // Close the browser after when finish
33 | await browser.close();
34 | }
35 | }
36 | }
37 |
38 | module.exports = new PuppeteerService();
39 |
--------------------------------------------------------------------------------
/src/commons/utils/sendEmail.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const nodeMailer = require("nodemailer");
3 | const hbs = require("nodemailer-express-handlebars");
4 | const path = require("path");
5 |
6 | //* IMPORT
7 | const {
8 | gmail: { port, host, service, mail, password },
9 | } = require("../../commons/configs/gmail.config");
10 |
11 | const sendEmail = async (options) => {
12 | try {
13 | const transporter = nodeMailer.createTransport({
14 | host,
15 | port,
16 | secure: true,
17 | service,
18 | auth: {
19 | user: mail,
20 | pass: password,
21 | },
22 | tls: {
23 | rejectUnauthorized: true,
24 | },
25 | });
26 |
27 | const handlebarOptions = {
28 | viewEngine: {
29 | extName: ".html",
30 | partialsDir: path.resolve("./src/views"),
31 | defaultLayout: false,
32 | },
33 | viewPath: path.resolve("./src/views"),
34 | extName: ".html",
35 | };
36 |
37 | transporter.use("compile", hbs(handlebarOptions));
38 |
39 | const mailOptions = {
40 | from: mail,
41 | to: options.to,
42 | subject: options.subject,
43 | attachments: options.attachments,
44 | template: options.template,
45 | context: options.context,
46 | html: options.html,
47 | };
48 | await transporter.sendMail(mailOptions);
49 | } catch (error) {
50 | console.error("Error sending email:", error);
51 | throw error;
52 | }
53 | };
54 |
55 | module.exports = sendEmail;
56 |
--------------------------------------------------------------------------------
/src/libs/method.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const { default: axios } = require("axios");
3 |
4 | //* IMPORT
5 | const { TIME } = require("../commons/constants");
6 | const {
7 | app: { firebaseMessage },
8 | } = require("../commons/configs/app.config");
9 |
10 | class AxiosService {
11 | constructor() {
12 | this.axiosIns = this.createAxiosInstance(
13 | process.env.GOOGLE_PUSH_NOTIFICATION,
14 | TIME._15_SECOND
15 | );
16 |
17 | this.applyAuthInterceptor(this.axiosIns);
18 | }
19 |
20 | createAxiosInstance(baseURL, timeout) {
21 | return axios.create({
22 | baseURL: baseURL,
23 | timeout: timeout,
24 | headers: {
25 | Accept: "application/json",
26 | },
27 | });
28 | }
29 |
30 | applyAuthInterceptor(axiosInstance) {
31 | axiosInstance.interceptors.request.use(
32 | (config) => {
33 | config.headers["Authorization"] = `key=${firebaseMessage}`;
34 |
35 | return config;
36 | },
37 | (error) => {
38 | return Promise.reject(error);
39 | }
40 | );
41 | }
42 |
43 | static getInstance() {
44 | if (!this.instance) {
45 | this.instance = new AxiosService();
46 | }
47 | return this.instance;
48 | }
49 |
50 | async post(url, data, config) {
51 | try {
52 | const response = await this.axiosIns.post(url, data, config);
53 | return response.data;
54 | } catch (error) {
55 | throw error;
56 | }
57 | }
58 | }
59 |
60 | const axiosService = AxiosService.getInstance();
61 |
62 | module.exports = axiosService;
63 |
--------------------------------------------------------------------------------
/src/auth/auth.blacklist.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { TIME } = require("../commons/constants");
3 | const { BlacklistTokens } = require("../commons/keys/blacklist");
4 | const { BadRequestRequestError } = require("../cores/error.response");
5 | const { getRedis } = require("../databases/init.redis");
6 | const { instanceConnect } = getRedis();
7 |
8 | const isTokenBlacklisted = async (refetchToken) => {
9 | try {
10 | const start = 0;
11 | const end = -1;
12 | const result = await instanceConnect.lrange(BlacklistTokens, start, end);
13 |
14 | return result.includes(refetchToken);
15 | } catch (error) {
16 | return error?.message;
17 | }
18 | };
19 |
20 | const checkUserSpam = async ({
21 | key,
22 | blockDuration = TIME._3_MINUTE,
23 | delDuration = TIME._1_MINUTE,
24 | maxRequest = 3,
25 | }) => {
26 | try {
27 | const BLOCK_DURATION_SECONDS = blockDuration;
28 | const DELETE_DURATION_SECONDS = delDuration;
29 | const MAX_REQUESTS = maxRequest;
30 | const numRequests = await instanceConnect.incr(key);
31 | let _ttl;
32 |
33 | if (numRequests === MAX_REQUESTS + 1) {
34 | await instanceConnect.expire(key, BLOCK_DURATION_SECONDS);
35 | _ttl = BLOCK_DURATION_SECONDS;
36 | } else {
37 | _ttl = await instanceConnect.ttl(key);
38 | }
39 |
40 | if (numRequests > MAX_REQUESTS) {
41 | return `You are blocked for ${_ttl}s. Thank you.`;
42 | } else {
43 | instanceConnect.expire(key, DELETE_DURATION_SECONDS);
44 | }
45 | } catch (error) {
46 | return error?.message;
47 | }
48 | };
49 |
50 | module.exports = {
51 | isTokenBlacklisted,
52 | checkUserSpam,
53 | };
54 |
--------------------------------------------------------------------------------
/server.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const https = require("https");
3 | const fs = require("fs");
4 | const path = require("path");
5 |
6 | //* IMPORT
7 | const app = require("./src/app");
8 | const {
9 | app: { port: PORT },
10 | } = require("./src/commons/configs/app.config");
11 | const logger = require("./src/loggers/winston.log");
12 |
13 | const initRedis = require("./src/databases/init.redis");
14 |
15 | // HTTP
16 | const server = app.listen(PORT, () => {
17 | console.info(`💸 Api backend start with http://localhost:${PORT} 🔥`);
18 | });
19 |
20 | // HTTPS
21 | // const sslServer = https.createServer(
22 | // {
23 | // // Todo: C1
24 | // // key: fs.readFileSync(path.join(__dirname, "cert", "key.pem")),
25 | // // cert: fs.readFileSync(path.join(__dirname, "cert", "cert.pem")),
26 | // // Todo: C2
27 | // key: fs.readFileSync(path.join(__dirname, "cert", "key.pem")),
28 | // cert: fs.readFileSync(path.join(__dirname, "cert", "cert.pem")),
29 | // },
30 | // app
31 | // );
32 | // sslServer.listen(PORT, () => {
33 | // console.info(`💸 Api backend start with https://localhost:${PORT} 🔥`);
34 | // });
35 |
36 | const cleanup = () => {
37 | initRedis.closeRedis();
38 | };
39 |
40 | process.on("exit", cleanup);
41 |
42 | process.on("unhandledRejection", (reason, promise) => {
43 | logger.error(`Unhandled Rejection at: ${promise}, reason: ${reason}`);
44 | });
45 |
46 | process.on("uncaughtException", (error) => {
47 | logger.error(`Uncaught Exception thrown: ${error.message}`);
48 | cleanup();
49 | });
50 |
51 | process.on("SIGINT", () => {
52 | server.close(() => console.log(`Exit Server Express`));
53 | cleanup();
54 | });
55 |
--------------------------------------------------------------------------------
/src/auth/check.auth.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const _ = require("lodash");
3 | const { UnauthorizedError, NotFoundError } = require("../cores/error.response");
4 | const { verifyTokenJWT } = require("./auth.token");
5 |
6 | //* IMPORT
7 | const {
8 | app: { accessKey },
9 | } = require("../commons/configs/app.config");
10 | const { TOKEN_EXPIRE, INVALID_TOKEN, ROLE } = require("../commons/constants");
11 | const { isTokenBlacklisted } = require("./auth.blacklist");
12 |
13 | const checkAuthorizationAccessToken = async (req, __, next) => {
14 | try {
15 | const accessToken = req.headers?.authorization?.split(" ")[1];
16 | const refetchToken = req?.cookies?.refresh_token;
17 |
18 | if (_.isEmpty(accessToken) || _.isEmpty(refetchToken)) {
19 | next(new UnauthorizedError());
20 | }
21 |
22 | const infoToken = await verifyTokenJWT(accessToken, accessKey);
23 |
24 | const checkToken = [TOKEN_EXPIRE, INVALID_TOKEN].includes(infoToken);
25 |
26 | if (checkToken) {
27 | next(new UnauthorizedError());
28 | }
29 |
30 | const isBlacklisted = await isTokenBlacklisted(refetchToken);
31 |
32 | if (isBlacklisted) {
33 | next(new UnauthorizedError("Token is not blacklisted"));
34 | }
35 |
36 | req.userInfo = infoToken;
37 | req.accessToken = accessToken;
38 |
39 | next();
40 | } catch (error) {
41 | next(new NotFoundError(error));
42 | }
43 | };
44 |
45 | const checkRoleAdmin = async (req, __, next) => {
46 | if (req.userInfo.role !== ROLE.ADMIN) {
47 | next(new UnauthorizedError());
48 | }
49 | next();
50 | };
51 |
52 | module.exports = {
53 | checkAuthorizationAccessToken,
54 | checkRoleAdmin,
55 | };
56 |
--------------------------------------------------------------------------------
/src/databases/init.pg.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const { Pool } = require("pg");
3 |
4 | const connectionString = `postgresql://${process.env.POSTGRES_USER}:${process.env.POSTGRES_PASSWORD}@${process.env.POSTGRES_HOST}:${process.env.POSTGRES_PORT}/${process.env.POSTGRES_DB}?${process.env.SECURITY}&application_name=class_fullstack`;
5 |
6 | // Tạo một đối tượng pool kết nối
7 | const pool = new Pool({
8 | connectionString: connectionString,
9 | min: 10, // Số kết nối tối thiểu được giữ trong pool.
10 | max: 20, // Số kết nối tối đa mà pool có thể giữ.
11 | acquireTimeoutMillis: 30000, // Thời gian tối đa mà một kết nối mới có thể chờ đợi để được tạo và thêm vào pool.
12 | createTimeoutMillis: 30000, // Thời gian tối đa mà một kết nối mới có thể mất để được tạo.
13 | idleTimeoutMillis: 30000, // Thời gian tối đa mà một kết nối có thể ở trong pool mà không được sử dụng trước khi bị giải phóng.
14 | reapIntervalMillis: 1000, // Thời gian giữa các chu kỳ để giải phóng các kết nối không sử dụng.
15 | log: (...messages) => {
16 | const logMessage = messages.join(" ");
17 |
18 | if (logMessage.includes("Executing (default):")) {
19 | console.log(
20 | "Executed SQL:",
21 | logMessage.split("Executing (default):")[1].trim()
22 | );
23 | }
24 |
25 | console.log(logMessage);
26 | },
27 | });
28 |
29 | pool
30 | .connect()
31 | .then((client) => {
32 | console.info("CONNECTED TO POSTGRESQL SUCCESS !!");
33 | client.release();
34 | })
35 | .catch((err) => {
36 | console.error("Failed to connect to PostgreSQL database", err);
37 | });
38 |
39 | // Export pool để sử dụng nó ở các module khác
40 | module.exports = pool;
41 |
--------------------------------------------------------------------------------
/src/app/v1/routes/users/index.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 |
4 | //* IMPORT
5 | const userController = require("../../controllers/user.controller");
6 | const { asyncHandler } = require("../../../../commons/helpers/asyncHandler");
7 | const {
8 | checkAuthorizationAccessToken,
9 | checkRoleAdmin,
10 | } = require("../../../../auth/check.auth");
11 |
12 | const router = express.Router();
13 |
14 | router.post("/login", asyncHandler(userController.login));
15 |
16 | router.post("/register", asyncHandler(userController.register));
17 |
18 | router.get("/renewToken", asyncHandler(userController.renewToken));
19 |
20 | router.post("/forget", asyncHandler(userController.forgetPassword));
21 |
22 | router.post(
23 | "/reset/password/:uniqueString",
24 | asyncHandler(userController.resetPassword)
25 | );
26 |
27 | router.use(checkAuthorizationAccessToken);
28 |
29 | router.get("/logout", asyncHandler(userController.logout));
30 |
31 | router.get(
32 | "/accept/reset/login",
33 | asyncHandler(userController.acceptResetLogin)
34 | );
35 |
36 | router.get("/get/all", asyncHandler(userController.getAll));
37 |
38 | router.get("/get/:userId", asyncHandler(userController.getDetail));
39 |
40 | router.get("/get/todo/:userId", asyncHandler(userController.getTodoFollowUser));
41 |
42 | router.post("/create", asyncHandler(userController.create));
43 |
44 | router.patch("/update/:userId", asyncHandler(userController.update));
45 |
46 | router.patch("/change/password", asyncHandler(userController.changePassword));
47 |
48 | router.delete("/delete/:userId", asyncHandler(userController.delete));
49 |
50 | router.use(checkRoleAdmin);
51 |
52 | router.post("/block", asyncHandler(userController.blockRefetchToken));
53 |
54 | module.exports = router;
55 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | # Class Online: Learn backend and systems With teacher Tai.
11 |
12 | # Connect
13 |
14 | ## [Database](https://cockroachlabs.cloud/clusters)
15 |
16 | ## [Connect to CockroachDB with Node.js and Knex](https://github.com/cockroachlabs/example-app-node-knex)
17 |
18 | ## [Knex](https://knexjs.org/)
19 |
20 | ## [Github used Pg](https://github.com/dhatGuy/PERN-Store/blob/main/server/db/auth.db.js)
21 |
22 |
23 | ## Team Word: Liên hệ công việc ☎ https://profile-forme.com
24 |
25 | ## 1. Nguyen Tien Tai ( MainTain 🚩).
26 |
27 | ## Tài Khoản Donate li Cf để có động lực code cho anh em tham khảo 😄.
28 |
29 | 
30 |
31 |
32 | ## Mk: NGUYEN TIEN TAI
33 |
34 | ## STK: 1651002972052
35 |
36 | ## Chi Nhánh: NGAN HANG TMCP AN BINH (ABBANK).
37 |
38 | ## SUPORT CONTACT: [https://profile-forme.com](https://profile-forme.com)
39 |
40 | ## Thank You So Much <3.
41 |
--------------------------------------------------------------------------------
/src/app/v1/models/todo.model.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | create: (data) =>
6 | new Promise((resolve, reject) => {
7 | try {
8 | const result = knexInstance("todo_list")
9 | .insert(data)
10 | .onConflict("id")
11 | .merge()
12 | .returning(["id"]);
13 | resolve(result);
14 | } catch (error) {
15 | reject(error);
16 | }
17 | }),
18 |
19 | update: async (data, query) =>
20 | new Promise((resolve, reject) => {
21 | try {
22 | const result = knexInstance("todo_list")
23 | .update(data)
24 | .where(query)
25 | .returning(["id"]);
26 | resolve(result);
27 | } catch (error) {
28 | reject(error);
29 | }
30 | }),
31 |
32 | deleteId: async (query) => {
33 | const result = knexInstance("todo_list")
34 | .del()
35 | .where(query)
36 | .returning(["id"]);
37 | return result;
38 | },
39 |
40 | getById: async (query, data) => {
41 | const result = await knexInstance("todo_list")
42 | .join("user", "todo_list.user_id", "=", "user.id")
43 | .select(data)
44 | .where(query);
45 | return result;
46 | },
47 |
48 | getCount: async () => {
49 | const result = await knexInstance("todo_list").count().first();
50 |
51 | return result.count;
52 | },
53 |
54 | getAll: async (data) => {
55 | const result = await knexInstance("todo_list_label")
56 | .fullOuterJoin(
57 | "todo_list",
58 | "todo_list.id",
59 | "=",
60 | "todo_list_label.todo_list_id"
61 | )
62 | .join("user", "todo_list.user_id", "=", "user.id")
63 | .leftJoin("label", "label.id", "=", "todo_list_label.label_id")
64 | .select(
65 | {
66 | nameLabel: "label.name",
67 | },
68 | data
69 | )
70 | .orderBy("todo_list.created_at", "desc");
71 |
72 | const resultCount = await module.exports.getCount();
73 |
74 | return { count: resultCount, result };
75 | },
76 | };
77 |
--------------------------------------------------------------------------------
/src/app/v1/services/todo.service.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { BadRequestRequestError } = require("../../../cores/error.response");
3 | const todoModel = require("../models/todo.model");
4 | const todoListLabelModel = require("../models/todo_list_label");
5 |
6 | class TodoService {
7 | // Todo 1. Get all
8 | async getAll() {
9 | const data = {
10 | id: "todo_list.id",
11 | title: "todo_list.title",
12 | username: "user.username",
13 | email: "user.email",
14 | };
15 | const result = await todoModel.getAll(data);
16 | return result;
17 | }
18 |
19 | // Todo 2. Get detail
20 | async getDetail({ todoId }) {
21 | if (!todoId) throw new BadRequestRequestError();
22 |
23 | const data = {
24 | id: "todo_list.id",
25 | title: "todo_list.title",
26 | username: "user.username",
27 | email: "user.email",
28 | };
29 | const result = await todoModel.getById({ "todo_list.id": todoId }, data);
30 | return result;
31 | }
32 |
33 | // Todo 3. Create
34 | async create({ title, user_id }) {
35 | const result = await todoModel.create({ title, user_id });
36 | return result;
37 | }
38 |
39 | // Todo 4. Update
40 | async update({ title, user_id }, { todoId }) {
41 | const result = await todoModel.update(
42 | { title, user_id },
43 | { id: Number(todoId) }
44 | );
45 | return result;
46 | }
47 |
48 | // Todo 5. Upsert
49 | async upsert({ todo_list_id, label_id }) {
50 | const result = await todoListLabelModel.upSertTodoWithLabel({
51 | todo_list_id,
52 | label_id,
53 | });
54 | return result;
55 | }
56 |
57 | // Todo 6. Delete todo label
58 | async deleteTodoAssignLabel({ todo_list_id, label_id }) {
59 | const result = await todoListLabelModel.deleteTodoAssignLabel({
60 | todo_list_id: Number(todo_list_id),
61 | label_id: Number(label_id),
62 | });
63 | return result;
64 | }
65 |
66 | // Todo 6. Delete id
67 | async delete({ todoId }) {
68 | console.log(todoId);
69 | const result = await todoModel.deleteId({ id: Number(todoId) });
70 | return result;
71 | }
72 | }
73 |
74 | module.exports = new TodoService();
75 |
--------------------------------------------------------------------------------
/src/views/otpPhone.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Email Template
7 |
54 |
55 |
56 |
57 |
62 |
Xin chào {{username}},
63 |
64 | Cảm ơn bạn đã lựa chọn công ty chúng tôi. Sử dụng mã OTP sau để hoàn tất
65 | các thủ tục Đăng ký của bạn. Mã OTP có hiệu lực trong 5 phút
66 |
67 |
68 |
{{otp}}
69 |
70 |
Trân trọng,
class fullstack
71 |
72 |
73 |
Nguyen Tien Tai
74 |
75 |
76 |
77 |
78 |
--------------------------------------------------------------------------------
/src/app/v2/services/image.service.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const _ = require("lodash");
3 | const fs = require("fs");
4 |
5 | //* IMPORT
6 | const cloudinary = require("../../../databases/init.cloudinary");
7 | const { BadRequestRequestError } = require("../../../cores/error.response");
8 |
9 | class ImageService {
10 | async upload(info, { path, folderName = "class/" }) {
11 | console.log(path, "----");
12 | if (_.isEmpty(info) || _.isEmpty(path)) {
13 | await this.removeTmp(path);
14 | throw new BadRequestRequestError();
15 | }
16 |
17 | const uploadResult = await cloudinary.uploader.upload(
18 | path,
19 | function (res) {
20 | console.log(res);
21 | },
22 | {
23 | public_id: Date.now() + "thumb",
24 | folder: folderName + info.id,
25 | use_filename: true,
26 | }
27 | );
28 |
29 | const thumbUrl = await cloudinary.url(uploadResult.public_id, {
30 | height: 100,
31 | width: 100,
32 | format: "jpg",
33 | });
34 |
35 | return {
36 | public_id: uploadResult.public_id,
37 | image_url: uploadResult.secure_url,
38 | thumb_size: thumbUrl,
39 | info,
40 | };
41 | }
42 | async uploadMulti(info, { files }, folderName = "class/") {
43 | if (_.isEmpty(info) || _.isEmpty(files)) {
44 | throw new BadRequestRequestError();
45 | }
46 |
47 | const uploadTasks = files.map(async (file) => {
48 | const result = await cloudinary.uploader.upload(
49 | file.path,
50 | {},
51 | {
52 | public_id: Date.now() + "thumb",
53 | folder: folderName + info.id,
54 | }
55 | );
56 |
57 | const thumbSize = await cloudinary.url(result.public_id, {
58 | height: 100,
59 | width: 100,
60 | format: "jpg",
61 | });
62 |
63 | return {
64 | image_url: result.secure_url,
65 | shopId: 8409,
66 | thumb_size: thumbSize,
67 | };
68 | });
69 | return await Promise.all(uploadTasks);
70 | }
71 |
72 | async removePublicId({ public_id }) {
73 | if (_.isEmpty(public_id)) {
74 | throw new BadRequestRequestError();
75 | }
76 |
77 | const result = await cloudinary.uploader.destroy(public_id, {});
78 |
79 | return result;
80 | }
81 |
82 | async removeTmp(path) {
83 | return new Promise((resolve, reject) => {
84 | fs.unlink(path, (err) => {
85 | if (err) {
86 | console.error("Error removing temporary file:", err);
87 | reject(err);
88 | } else {
89 | console.log("Temporary file removed successfully.");
90 | resolve();
91 | }
92 | });
93 | });
94 | }
95 | }
96 |
97 | module.exports = new ImageService();
98 |
--------------------------------------------------------------------------------
/src/cores/error.response.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* IMPORT
4 | const {
5 | StatusCodes,
6 | ReasonPhrases,
7 | } = require("../commons/utils/httpStatusCode");
8 | // const logger = require("../loggers/winston.log");
9 | const myLogger = require("../loggers/mylogger.log");
10 |
11 | class ErrorResponse extends Error {
12 | constructor(message, status) {
13 | super(message);
14 | this.status = status;
15 | this.now = Date.now();
16 |
17 | // logger.error(`${this.status} = ${this.message}`);
18 | // myLogger.error(this.message, {
19 | // context: "/path",
20 | // requestId: "uuaaaa",
21 | // message: this.message,
22 | // metadata: {},
23 | // });
24 | // myLogger.error(this.message, [
25 | // "/api/v1/messages",
26 | // "123123123",
27 | // { error: "Bad request" },
28 | // ]);
29 | }
30 | }
31 | class BadRequestRequestError extends ErrorResponse {
32 | constructor(
33 | message = ReasonPhrases.BAD_REQUEST,
34 | statusCode = StatusCodes.BAD_REQUEST
35 | ) {
36 | super(message, statusCode);
37 | }
38 | }
39 |
40 | class NotFoundError extends ErrorResponse {
41 | constructor(
42 | message = ReasonPhrases.NOT_FOUND,
43 | statusCode = StatusCodes.NOT_FOUND
44 | ) {
45 | super(message, statusCode);
46 | }
47 | }
48 |
49 | class UnauthorizedError extends ErrorResponse {
50 | constructor(
51 | message = ReasonPhrases.UNAUTHORIZED,
52 | statusCode = StatusCodes.UNAUTHORIZED
53 | ) {
54 | super(message, statusCode);
55 | }
56 | }
57 |
58 | class GoneError extends ErrorResponse {
59 | constructor(message = ReasonPhrases.GONE, statusCode = StatusCodes.GONE) {
60 | super(message, statusCode);
61 | }
62 | }
63 |
64 | class InternalServerError extends ErrorResponse {
65 | constructor(
66 | message = ReasonPhrases.INTERNAL_SERVER_ERROR,
67 | statusCode = StatusCodes.INTERNAL_SERVER_ERROR
68 | ) {
69 | super(message, statusCode);
70 | }
71 | }
72 |
73 | class TooManyRequestsError extends ErrorResponse {
74 | constructor(
75 | message = ReasonPhrases.TOO_MANY_REQUESTS,
76 | statusCode = StatusCodes.TOO_MANY_REQUESTS
77 | ) {
78 | super(message, statusCode);
79 | }
80 | }
81 |
82 | class RedisError extends ErrorResponse {
83 | constructor(
84 | message = ReasonPhrases.INTERNAL_SERVER_ERROR,
85 | statusCode = StatusCodes.INTERNAL_SERVER_ERROR
86 | ) {
87 | super(message, statusCode);
88 | }
89 | }
90 |
91 | module.exports = {
92 | BadRequestRequestError,
93 | InternalServerError,
94 | NotFoundError,
95 | UnauthorizedError,
96 | GoneError,
97 | TooManyRequestsError,
98 | RedisError,
99 | };
100 |
--------------------------------------------------------------------------------
/src/databases/init.redis.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const IOREDIS = require("ioredis");
3 |
4 | //* IMPORT
5 | const {
6 | redis: { host, port, user, password },
7 | } = require("../commons/configs/redis.config");
8 | const { RedisError } = require("../cores/error.response");
9 |
10 | let client = {},
11 | statusConnectRedis = {
12 | CONNECT: "connect",
13 | END: "end",
14 | RECONNECT: "reconnecting",
15 | ERROR: "error",
16 | },
17 | connectTimeout;
18 |
19 | const REDIS_CONNECT_TIMEOUT = 10000,
20 | REDIS_CONNECT_MESSAGE = {
21 | code: -99,
22 | message: {
23 | vn: "Redis đang lỗi",
24 | enL: "Server connect Error",
25 | },
26 | };
27 |
28 | const handleTimeoutError = () => {
29 | connectTimeout = setTimeout(() => {
30 | throw new RedisError({
31 | statusCode: REDIS_CONNECT_MESSAGE.code,
32 | message: REDIS_CONNECT_MESSAGE.message,
33 | });
34 | }, REDIS_CONNECT_TIMEOUT);
35 | };
36 |
37 | const handleEventConnect = ({ connectionRedis }) => {
38 | connectionRedis.on(statusConnectRedis.CONNECT, () => {
39 | console.log("CONNECTED TO REDIS SUCCESS ✅!!");
40 | clearTimeout(connectTimeout);
41 | });
42 |
43 | connectionRedis.on(statusConnectRedis.END, () => {
44 | console.log(`CONNECTED TO REDIS ${statusConnectRedis.END} 🔚!!`);
45 | handleTimeoutError();
46 | });
47 |
48 | connectionRedis.on(statusConnectRedis.RECONNECT, () => {
49 | console.log(`CONNECTED TO REDIS ${statusConnectRedis.RECONNECT} 🔁!!`);
50 | clearTimeout(connectTimeout);
51 | });
52 |
53 | connectionRedis.on(statusConnectRedis.ERROR, (error) => {
54 | console.error(`ERROR TO REDIS: ${error.message} ⁉️`);
55 | handleTimeoutError();
56 | });
57 | };
58 |
59 | const initRedis = () => {
60 | const InstanceRedis = new IOREDIS({
61 | port,
62 | host,
63 | user,
64 | password,
65 | });
66 | client.instanceConnect = InstanceRedis;
67 | handleEventConnect({ connectionRedis: InstanceRedis });
68 | };
69 |
70 | const getRedis = () => client;
71 |
72 | const closeRedis = () => {
73 | if (client.instanceConnect) {
74 | client.instanceConnect.removeAllListeners();
75 | clearTimeout(connectTimeout);
76 | console.log(client.instanceConnect.status);
77 | if (client.instanceConnect.status !== statusConnectRedis.END) {
78 | client.instanceConnect.quit((err) => {
79 | if (err) {
80 | console.error("Error closing Redis connection:", err);
81 | } else {
82 | console.log("Redis connection closed successfully.");
83 | }
84 | });
85 | } else {
86 | console.warn("Redis connection is already closed.");
87 | }
88 | } else {
89 | console.warn("No active Redis connection to close.");
90 | }
91 | };
92 |
93 | module.exports = { initRedis, closeRedis, getRedis };
94 |
--------------------------------------------------------------------------------
/src/app.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const express = require("express");
3 | const morgan = require("morgan");
4 | const cookieParser = require("cookie-parser");
5 | const cors = require("cors");
6 | const { default: helmet } = require("helmet");
7 | const compression = require("compression");
8 | const dotenv = require("dotenv");
9 | const { v4: uuid } = require("uuid");
10 |
11 | //* IMPORT
12 | const { NODE_ENV, LIMIT_BODY } = require("./commons/constants");
13 | const {
14 | app: { morgan: morganConfig, node },
15 | } = require("./commons/configs/app.config");
16 | const { errorHandler } = require("./commons/helpers/errorHandle");
17 | const myLogger = require("./loggers/mylogger.log");
18 |
19 | const app = express();
20 | dotenv.config();
21 |
22 | app.use(cors());
23 | app.use(express.json());
24 | app.use(morgan(morganConfig));
25 | app.use(cookieParser());
26 | app.use(helmet());
27 | app.use(compression());
28 | app.use(
29 | express.urlencoded({
30 | extended: true,
31 | })
32 | );
33 | app.use(
34 | express.json({
35 | limit: LIMIT_BODY._5_MB,
36 | })
37 | );
38 |
39 | app.use((req, __, next) => {
40 | const requestId = req.headers["x-request-id"];
41 | req.requestId = requestId ? requestId : uuid();
42 |
43 | myLogger.log(`input params::${req.method}`, [
44 | req.path,
45 | { requestId: req.requestId },
46 | req.method === "POST" ? req.body : req.query,
47 | ]);
48 | next();
49 | });
50 |
51 | //* Database & Cache
52 | require("./databases/init.knex");
53 | const initRedis = require("./databases/init.redis");
54 | const { StatusCodes } = require("./commons/utils/httpStatusCode");
55 | const { BadRequestRequestError } = require("./cores/error.response");
56 | initRedis.initRedis();
57 |
58 | // require("./databases/init.cloudinary");
59 | // require("./databases/init.firebase");
60 |
61 | //* CRON
62 | // require("./crons/user_verification");
63 | // require("./crons/user_otp");
64 |
65 | // * V1
66 | app.use("/api/v1", require("./app/v1/routes"));
67 |
68 | //* V2
69 | app.use("/api/v2", require("./app/v2/routes"));
70 |
71 | //* V2
72 | app.use("/api/v3", require("./app/v3/routes"));
73 |
74 | app.use((_, __, next) => {
75 | const ErrorCode = new BadRequestRequestError();
76 | return next(ErrorCode);
77 | });
78 |
79 | app.use((error, req, res, ____) => {
80 | const checkNodeApp = node === NODE_ENV.DEV;
81 |
82 | const reqMessage = `${error.status} - ${
83 | Date.now() - error.now
84 | }ms - Response: ${JSON.stringify(error)}`;
85 | myLogger.error(reqMessage, [
86 | req.path,
87 | { requestId: req.requestId },
88 | req.method === "POST" ? req.body : req.query,
89 | ]);
90 |
91 | const resultError = errorHandler(error, checkNodeApp);
92 |
93 | return res.status(resultError?.response.status).json(resultError?.response);
94 | });
95 |
96 | module.exports = app;
97 |
--------------------------------------------------------------------------------
/src/loggers/mylogger.log.js:
--------------------------------------------------------------------------------
1 | "use strict";
2 |
3 | //* LIB
4 | const { format, createLogger, transports } = require("winston");
5 | const DailyRotateFile = require("winston-daily-rotate-file");
6 | const path = require("path");
7 | const { v4: uuid } = require("uuid");
8 |
9 | const logsDirectory = path.join(__dirname, "../logs");
10 |
11 | class MyLogger {
12 | constructor() {
13 | const formatPrint = format.printf(
14 | ({ level, message, context, requestId, timestamp, metadata }) => {
15 | return `${timestamp} - ${level} - ${context} - ${requestId} - ${message} - ${JSON.stringify(
16 | metadata
17 | )}`;
18 | }
19 | );
20 |
21 | this.logger = createLogger({
22 | format: format.combine(
23 | format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
24 | formatPrint
25 | ),
26 | transports: [
27 | new transports.Console(),
28 | new DailyRotateFile({
29 | dirname: logsDirectory,
30 | filename: "app-%DATE%.info.log",
31 | // datePattern: "YYYY-MM-DD-HH-mm",
32 | datePattern: "YYYY-MM-DD",
33 | zippedArchive: true,
34 | maxSize: "20m",
35 | maxFiles: "14d",
36 | format: format.combine(
37 | format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
38 | formatPrint
39 | ),
40 | level: "info",
41 | }),
42 | new DailyRotateFile({
43 | dirname: logsDirectory,
44 | filename: "app-%DATE%.error.log",
45 | // datePattern: "YYYY-MM-DD-HH-mm",
46 | datePattern: "YYYY-MM-DD",
47 | zippedArchive: true,
48 | maxSize: "20m",
49 | maxFiles: "14d",
50 | format: format.combine(
51 | format.timestamp({ format: "YYYY-MM-DD HH:mm:ss" }),
52 | formatPrint
53 | ),
54 | level: "error",
55 | }),
56 | ],
57 | });
58 | }
59 |
60 | commonParams(params) {
61 | let context, req, metadata;
62 | if (!Array.isArray(params)) {
63 | context = params;
64 | } else {
65 | [context, req, metadata] = params;
66 | }
67 | const requestId = req?.requestId || uuid();
68 | return {
69 | requestId,
70 | context,
71 | metadata,
72 | };
73 | }
74 |
75 | log(message, params) {
76 | const paramLog = this.commonParams(params);
77 | const logObject = Object.assign(
78 | {
79 | message,
80 | },
81 | paramLog
82 | );
83 | this.logger.info(logObject);
84 | }
85 |
86 | error(message, params) {
87 | const paramLog = this.commonParams(params);
88 |
89 | const logObject = Object.assign(
90 | {
91 | message,
92 | },
93 | paramLog
94 | );
95 | this.logger.error(logObject);
96 | }
97 | }
98 |
99 | module.exports = new MyLogger();
100 |
--------------------------------------------------------------------------------
/src/app/v2/services/notification.service.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const _ = require("lodash");
3 |
4 | //* IMPORT
5 | const { BadRequestRequestError } = require("../../../cores/error.response");
6 | const googleApi = require("../../../apis/google.api");
7 | const axiosService = require("../../../libs/method");
8 |
9 | class NotificationService {
10 | async sendDeviceId({
11 | deviceId,
12 | notification,
13 | meta = {
14 | url: "https://i.pinimg.com/originals/ca/05/d1/ca05d1cf1a034f9b2eafc644c102c551.gif",
15 | dl: "https://www.profile-forme.com",
16 | },
17 | }) {
18 | if (_.isEmpty(deviceId) || _.isEmpty(notification)) {
19 | throw new BadRequestRequestError();
20 | }
21 |
22 | const data = {
23 | to: deviceId,
24 | notification,
25 | data: meta,
26 | };
27 |
28 | try {
29 | const response = await axiosService.post(
30 | googleApi.firebase.message.sendSingleDevice,
31 | data
32 | );
33 |
34 | return response;
35 | } catch (error) {
36 | throw new BadRequestRequestError();
37 | }
38 | }
39 |
40 | async sendMulticast({ deviceIds, notification, meta }) {
41 | if (_.isEmpty(deviceIds) || _.isEmpty(notification)) {
42 | throw new BadRequestRequestError();
43 | }
44 | const data = {
45 | registration_ids: deviceIds,
46 | notification,
47 | data: meta,
48 | };
49 |
50 | try {
51 | const response = await axiosService.post(
52 | googleApi.firebase.message.sendSingleDevice,
53 | data
54 | );
55 |
56 | return response;
57 | } catch (error) {
58 | throw new BadRequestRequestError();
59 | }
60 | }
61 |
62 | async sendTopic({ topics = "/topics/class-fullstack", notification, meta }) {
63 | if (_.isEmpty(notification)) {
64 | throw new BadRequestRequestError();
65 | }
66 |
67 | const data = {
68 | to: topics,
69 | notification,
70 | data: meta,
71 | };
72 |
73 | try {
74 | const response = await axiosService.post(
75 | googleApi.firebase.message.sendSingleDevice,
76 | data
77 | );
78 |
79 | return response;
80 | } catch (error) {
81 | throw new BadRequestRequestError();
82 | }
83 | }
84 |
85 | async sendTopicCondition({ condition, notification, meta }) {
86 | if (_.isEmpty(notification) || _.isEmpty(notification)) {
87 | throw new BadRequestRequestError();
88 | }
89 |
90 | const data = {
91 | condition,
92 | notification,
93 | data: meta,
94 | };
95 |
96 | try {
97 | const response = await axiosService.post(
98 | googleApi.firebase.message.sendSingleDevice,
99 | data
100 | );
101 |
102 | return response;
103 | } catch (error) {
104 | throw new BadRequestRequestError();
105 | }
106 | }
107 | }
108 |
109 | module.exports = new NotificationService();
110 |
--------------------------------------------------------------------------------
/src/commons/helpers/checkFieldsBuilder.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const validator = require("validator");
3 |
4 | //* IMPORT
5 | const {
6 | BadRequestRequestError,
7 | UnauthorizedError,
8 | } = require("../../cores/error.response");
9 | const { formatPhone } = require("../utils/convert");
10 |
11 | class CheckFieldsBuilder {
12 | constructor() {
13 | this.fields = {};
14 | }
15 |
16 | setUsername(username) {
17 | if (validator.isEmpty(username)) {
18 | throw new BadRequestRequestError("Username is required");
19 | }
20 | this.fields.username = username;
21 | return this;
22 | }
23 |
24 | setEmail(email) {
25 | if (validator.isEmpty(email)) {
26 | throw new BadRequestRequestError("Email is required");
27 | }
28 | this.fields.email = email;
29 | return this;
30 | }
31 |
32 | setPassword(password) {
33 | if (
34 | !validator.isStrongPassword(password, {
35 | minLength: 8,
36 | minLowercase: 1,
37 | minUppercase: 1,
38 | minSymbols: 1,
39 | })
40 | ) {
41 | throw new UnauthorizedError("Password is not strong enough");
42 | }
43 | this.fields.password = password;
44 | return this;
45 | }
46 |
47 | setPhone(phone) {
48 | if (validator.isEmpty(phone)) {
49 | throw new BadRequestRequestError("Phone number is required");
50 | }
51 | if (!validator.isNumeric(phone)) {
52 | throw new BadRequestRequestError("Phone number must be numeric");
53 | }
54 | if (!validator.isLength(phone, { min: 10, max: 11 })) {
55 | throw new BadRequestRequestError(
56 | "Phone number must be between 10 and 11 digits"
57 | );
58 | }
59 | if (!/^(0\d{9}|84\d{9})$/.test(phone)) {
60 | throw new BadRequestRequestError("Phone number must be Vietnamese");
61 | }
62 | this.fields.phone = phone;
63 | return this;
64 | }
65 |
66 | setAccessToken(accessToken) {
67 | if (!accessToken || validator.isEmpty(accessToken)) {
68 | throw new BadRequestRequestError("AccessToken is required");
69 | }
70 | this.fields.accessToken = accessToken;
71 | return this;
72 | }
73 | setRefetchToken(refetchToken) {
74 | if (!refetchToken || validator.isEmpty(refetchToken)) {
75 | throw new BadRequestRequestError("RefetchToken is required");
76 | }
77 | this.fields.refetchToken = refetchToken;
78 | return this;
79 | }
80 |
81 | setGeneralRandomString(string) {
82 | if (!string || validator.isEmpty(string)) {
83 | throw new BadRequestRequestError();
84 | }
85 | this.fields.string = string;
86 | return this;
87 | }
88 | setId(id) {
89 | if (!id || validator.isEmpty(id)) {
90 | throw new BadRequestRequestError(`Id is required`);
91 | }
92 | this.fields.id = id;
93 | return this;
94 | }
95 |
96 | build() {
97 | return this.fields;
98 | }
99 | }
100 |
101 | module.exports = CheckFieldsBuilder;
102 |
--------------------------------------------------------------------------------
/src/app/v1/controllers/user.controller.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const { SuccessResponse, Created } = require("../../../cores/success.response");
3 | const userService = require("../services/user.service");
4 |
5 | class UserController {
6 | async getAll(req, res, ___) {
7 | const queryPage = req.query.page ? Number(req.query.page) : 0;
8 | const queryLimit = req.query.limit ? Number(req.query.limit) : 0;
9 | const querySearch = req.query.search;
10 |
11 | new SuccessResponse({
12 | metadata: await userService.getAll(
13 | req,
14 | queryPage,
15 | queryLimit,
16 | querySearch
17 | ),
18 | }).send(res);
19 | }
20 |
21 | async getDetail(req, res, ___) {
22 | new SuccessResponse({
23 | metadata: await userService.getDetail(req.params),
24 | }).send(res);
25 | }
26 |
27 | async getTodoFollowUser(req, res, ___) {
28 | new SuccessResponse({
29 | metadata: await userService.getTodoFollowUser(req.params),
30 | }).send(res);
31 | }
32 |
33 | async create(req, res, ___) {
34 | new Created({
35 | metadata: await userService.create(req.body),
36 | }).send(res);
37 | }
38 |
39 | async update(req, res, ___) {
40 | new SuccessResponse({
41 | metadata: await userService.update(req.body, req.params),
42 | }).send(res);
43 | }
44 |
45 | async delete(req, res, ___) {
46 | new SuccessResponse({
47 | metadata: await userService.delete(req.params),
48 | }).send(res);
49 | }
50 |
51 | async register(req, res, ___) {
52 | new Created({
53 | metadata: await userService.register(req.body),
54 | }).send(res);
55 | }
56 |
57 | async login(req, res, ___) {
58 | new SuccessResponse({
59 | metadata: await userService.login(res, req.body),
60 | }).send(res);
61 | }
62 |
63 | async renewToken(req, res, ___) {
64 | const accessToken = req.headers?.authorization?.split(" ")[1];
65 | const refetchToken = req?.cookies?.refresh_token;
66 | new SuccessResponse({
67 | metadata: await userService.renewToken({ refetchToken }, { accessToken }),
68 | }).send(res);
69 | }
70 |
71 | async logout(req, res, ___) {
72 | const refetchToken = req?.cookies?.refresh_token;
73 | new SuccessResponse({
74 | metadata: await userService.logout(res, { refetchToken }),
75 | }).send(res);
76 | }
77 |
78 | async blockRefetchToken(req, res, ___) {
79 | new SuccessResponse({
80 | metadata: await userService.blockRefetchToken(req.body),
81 | }).send(res);
82 | }
83 |
84 | async forgetPassword(req, res, ___) {
85 | new SuccessResponse({
86 | metadata: await userService.forgetPassword(req, req.body),
87 | }).send(res);
88 | }
89 |
90 | async resetPassword(req, res, ___) {
91 | new SuccessResponse({
92 | metadata: await userService.resetPassword(req.params, req.body),
93 | }).send(res);
94 | }
95 |
96 | async changePassword(req, res, ___) {
97 | new SuccessResponse({
98 | metadata: await userService.changePassword(req.userInfo, req.body),
99 | }).send(res);
100 | }
101 |
102 | async acceptResetLogin(req, res, ___) {
103 | const refetchToken = req?.cookies?.refresh_token;
104 | new SuccessResponse({
105 | metadata: await userService.acceptResetLogin(res, { refetchToken }),
106 | }).send(res);
107 | }
108 | }
109 |
110 | module.exports = new UserController();
111 |
--------------------------------------------------------------------------------
/src/app/v1/models/user.model.js:
--------------------------------------------------------------------------------
1 | //* IMPORT
2 | const knexInstance = require("../../../databases/init.knex");
3 |
4 | module.exports = {
5 | createUser: (data) =>
6 | new Promise((resolve, reject) => {
7 | try {
8 | const result = knexInstance("user")
9 | .insert(data)
10 | .onConflict("id")
11 | .merge()
12 | .returning(["id"]);
13 | resolve(result);
14 | } catch (error) {
15 | reject(error);
16 | }
17 | }),
18 |
19 | updateUser: async (data, query) =>
20 | new Promise((resolve, reject) => {
21 | try {
22 | const result = knexInstance("user")
23 | .update(data)
24 | .where(query)
25 | .returning(["id"]);
26 | resolve(result);
27 | } catch (error) {
28 | reject(error);
29 | }
30 | }),
31 |
32 | deleteId: async (query) => {
33 | const result = knexInstance("user").del().where(query).returning(["id"]);
34 | return result;
35 | },
36 |
37 | getUserById: async (query, data) => {
38 | const result = await knexInstance("user").select(data).where(query).first();
39 | return result;
40 | },
41 |
42 | checkExists: async (query) => {
43 | const result = await knexInstance("user")
44 | .where(query)
45 | .select(knexInstance.raw("1"))
46 | .first();
47 | return !!result;
48 | },
49 |
50 | checkExitUserNameAndEmail: async ({ username, email, phone, userId }) => {
51 | let query = knexInstance("user");
52 |
53 | if (username) {
54 | query = query.where({ username });
55 | }
56 |
57 | if (email) {
58 | query = query.orWhere({ email });
59 | }
60 |
61 | if (phone) {
62 | query = query.orWhere({ phone });
63 | }
64 |
65 | if (userId) {
66 | query = query.whereNot({ id: userId });
67 | }
68 |
69 | const result = await query.select(knexInstance.raw("1")).first();
70 | return !!result;
71 | },
72 |
73 | getAllUser: async (data, offset, limit, search) => {
74 | let query = knexInstance("user").select(data);
75 |
76 | if (search) {
77 | query
78 | .where("username", "like", `%${search}%`)
79 | .orWhere("email", "like", `%${search}%`)
80 | .orWhere("phone", "like", `%${search}%`);
81 | }
82 |
83 | const result = await query
84 | .orderBy("created_at", "desc")
85 | .offset(offset)
86 | .limit(limit);
87 | return result;
88 | },
89 |
90 | getTotalUser: async () => {
91 | const totalAggregate = await knexInstance("user")
92 | .count("* as total")
93 | .first();
94 | return totalAggregate?.total || 0;
95 | },
96 |
97 | getTodoFollowUser: async (query, data) => {
98 | const result = await knexInstance("todo_list_label")
99 | .fullOuterJoin(
100 | "todo_list",
101 | "todo_list.id",
102 | "=",
103 | "todo_list_label.todo_list_id"
104 | )
105 | .join("user", "todo_list.user_id", "=", "user.id")
106 | .leftJoin("label", "label.id", "=", "todo_list_label.label_id")
107 | .select(
108 | {
109 | nameLabel: "label.name",
110 | },
111 | data
112 | )
113 | .where(query);
114 |
115 | return result;
116 | },
117 |
118 | upsertUser: (data) =>
119 | new Promise((resolve, reject) => {
120 | try {
121 | const result = knexInstance("user")
122 | .insert(data)
123 | .onConflict("email")
124 | .merge()
125 | .returning(["id", "email"]);
126 |
127 | resolve(result);
128 | } catch (error) {
129 | reject(error);
130 | }
131 | }),
132 | };
133 |
--------------------------------------------------------------------------------
/NEWMAP.md:
--------------------------------------------------------------------------------
1 | # How to use new Map?
2 |
3 | ## 1. Syntax
4 |
5 | ```javascript
6 | // 1. Initialize new Map
7 | const myMap = new Map();
8 |
9 | // 2. Set key and value into new Map
10 | myMap.set(key, value);
11 |
12 | // 3. Get value for key in new Map
13 | const value = myMap.get(key);
14 |
15 | // 4. Check if key exists in new Map
16 | const exists = myMap.has(key);
17 |
18 | // 5. Delete one key-value pair in new Map
19 | myMap.delete(key);
20 |
21 | // 6. Get count of key-value pairs in new Map
22 | const size = myMap.size;
23 |
24 | // 7. Iterate over each key-value pair in new Map
25 | myMap.forEach((value, key) => {
26 | // Do something with each key-value pair
27 | });
28 |
29 | // 8. Delete all keys in new Map
30 | myMap.clear();
31 |
32 | // 9. Get all keys in new Map
33 | const keys = Array.from(myMap.keys());
34 |
35 | // 10. Get all values in new Map
36 | const values = Array.from(myMap.values());
37 |
38 | // 11. Get all entries (key-value pairs) in new Map
39 | const entries = Array.from(myMap.entries());
40 |
41 | // 12. Use [Symbol.iterator]() to iterate over Map using for...of
42 | for (const [key, value] of myMap) {
43 | // Do something with each key-value pair
44 | }
45 |
46 | // 13. Add multiple key-value pairs to new Map
47 | const multipleKeyValuePairs = [
48 | [key1, value1],
49 | [key2, value2],
50 | [key3, value3]
51 | // and so on
52 | ];
53 |
54 | for (const [key, value] of multipleKeyValuePairs) {
55 | myMap.set(key, value);
56 | }
57 | ```
58 |
59 | ## 2. Examples
60 |
61 | ```javascript
62 | // Create a new Map
63 | const yourself = new Map();
64 |
65 | // Add key-value pairs to the Map
66 | yourself.set('fullname', 'Nguyen Tien Tai').set('age', 24).set('job', 'developer and teacher');
67 |
68 | // Get the value for a key from the Map
69 | const valueForKeyAge = yourself.get('age');
70 | console.log('Value for age:', valueForKeyAge);
71 |
72 | // Check if a key exists in the Map
73 | const existsKeyFullname = yourself.has('fullname');
74 | console.log('fullname exists:', existsKeyFullname);
75 |
76 | // Delete a key-value pair from the Map
77 | yourself.delete('job');
78 |
79 | // Get the size of the Map
80 | const sizeOfMap = yourself.size;
81 | console.log('Size of map:', sizeOfMap);
82 |
83 | // Iterate over all key-value pairs in the Map
84 | yourself.forEach((value, key) => {
85 | console.log(`Key: ${key}, Value: ${value}`);
86 | });
87 |
88 | // Clear all keys in the Map
89 | yourself.clear();
90 |
91 | // Create a new Map and add multiple key-value pairs to it
92 | const anotherMap = new Map([
93 | ['apple', 5],
94 | ['banana', 10],
95 | ['orange', 8]
96 | ]);
97 |
98 | // Get all keys in the Map
99 | const keys = Array.from(anotherMap.keys());
100 | console.log('Keys:', keys);
101 | // Output: Keys: [ 'apple', 'banana', 'orange' ]
102 |
103 | // Get all values in the Map
104 | const values = Array.from(anotherMap.values());
105 | console.log('Values:', values); // Output: Values: [ 5, 10, 8 ]
106 |
107 | // Get all entries (key-value pairs) in the Map
108 | const entries = Array.from(anotherMap.entries());
109 | console.log('Entries:', entries);
110 | // Output:
111 | // Entries: [ [ 'apple', 5 ], [ 'banana', 10 ], [ 'orange', 8 ] ]
112 |
113 | // Use [Symbol.iterator]() to iterate over Map using for...of
114 | for (const [key, value] of anotherMap) {
115 | console.log(`Key: ${key}, Value: ${value}`);
116 | }
117 | // Output:
118 | // Key: apple, Value: 5
119 | // Key: banana, Value: 10
120 | // Key: orange, Value: 8
121 |
122 | ```
123 |
--------------------------------------------------------------------------------
/REDIS.md:
--------------------------------------------------------------------------------
1 | # Learn Redis
2 |
3 | ## 1. CONNECT REDIS
4 | ```bash
5 | redis-cli -h -p -a
6 | ```
7 |
8 | ## 2. GLOBAL
9 |
10 | ```bash
11 | # Search key
12 | keys 't*'
13 | ```
14 |
15 | ## 3. STRING
16 | ```bash
17 | # Length
18 | STRLEN name
19 |
20 | # Check
21 | object encoding str
22 |
23 | # Embstring ( <= 44bytes)
24 | SET name 0123456789012345678901234567890123456789abc
25 |
26 | # Raw ( > 44 bytes )
27 | SET name1 0123456789012345678901234567890123456789abcsdsdsdsdsdsdsdasdasdasdsd
28 |
29 | # Int
30 | SET name2 10
31 |
32 | # TTL and handle key exit
33 | set lock_key_unique unique_value NX PX 10000
34 | TTL lock_key_unique
35 |
36 | # Count
37 | INCR like:product-001
38 | INCRBY like:product-001 5
39 | DECR like:product-001
40 | DECRBY like:product-001 5
41 |
42 | # Check exits
43 | EXISTS block_user_id
44 |
45 | # Del
46 | DEL like:product-001
47 | ```
48 |
49 | ## 4. HASH
50 |
51 | ```bash
52 | # Add single and add multiple
53 | HSET cart:1 product1 2
54 | HSET cart:1 product2 1 product3 3 product4 1
55 |
56 | # Length count
57 | HLEN cart:1
58 |
59 | # Check exits
60 | HEXISTS cart:1 product3
61 |
62 | # Get only key
63 | HKEYS cart:1
64 |
65 | # Get all key and value
66 | HGETALL cart:1
67 |
68 | # Get detail key and value
69 | HGET cart:1 product2
70 |
71 | # DEL key
72 | HDEL cart:1 product2
73 |
74 | # DEL ALL
75 | DEL cart:1
76 | ```
77 |
78 | ## 5 LIST
79 | ```bash
80 | # Add
81 | LPUSH order_history:user-001 "order_id_4"
82 | LINSERT order_history:user-001 before order_id_1 order_id_5
83 | LINSERT order_history:user-001 before order_id_4 order_id_6
84 |
85 | # Get all
86 | LRANGE order_history:user-001 0 -1
87 |
88 | # Get Detail flower index
89 | LINDEX order_history:user-001 0
90 |
91 | # Length
92 | LLEN order_history:user-001
93 |
94 | # Exits
95 | EXISTS order_history:user-001
96 |
97 | # Del
98 | LREM order_history:user-001 0 "order_id_1"
99 |
100 | # Advance
101 | lpush l:ticket ticket:01
102 | blpop l:ticket 0
103 | ```
104 |
105 | ## 5 SET
106 | ```bash
107 | # Add
108 | SADD favorite_products:user-001 product1 product2 product3
109 | SADD favorite_products:user-002 product1 product5
110 | SADD favorite_products:user-003 product10 product5
111 |
112 | # Get all
113 | SMEMBERS favorite_products:user-001
114 |
115 | # Get all count
116 | SRANDMEMBER favorite_products:user-001 2
117 |
118 | # counter
119 | SCARD favorite_products:user-001
120 |
121 | # The common ground
122 | SINTER favorite_products:user-001 favorite_products:user-002 favorite_products:user-003
123 | SDIFF favorite_products:user-001 favorite_products:user-002 favorite_products:user-003
124 |
125 | # Get random
126 | SRANDMEMBER favorite_products:user-001
127 |
128 | # The common ground and create key
129 | SUNIONSTORE all_favorite_products favorite_products:user-001 favorite_products:user-002 favorite_products:user-003
130 |
131 | # Not duplicate between all key
132 | SUNION favorite_products:user-001 favorite_products:user-002 favorite_products:user-003
133 | SUNION favorite_products:user-001 favorite_products:user-002 favorite_products:user-003 ... favorite_products:user-n
134 |
135 | # Show data page
136 | SSCAN favorite_products:user-001 0 COUNT 10
137 |
138 | # Del key
139 | SREM favorite_products:user-001 product2
140 |
141 | # Get and delete Random
142 | SPOP favorite_products:user-001
143 | ```
144 |
145 | ## 6 ZSET
146 | ```bash
147 | # Add
148 | ZADD sorted_favorite_products:user-001 1 "product3" 2 "product2" 3 "product1" 4 "product6" 5 "product5" 6 "product4"
149 |
150 | # Get data sort ASC ( 1 2 3 )
151 | ZRANGE sorted_favorite_products:user-001 0 -1
152 | ZRANGE sorted_favorite_products:user-001 0 -1 WITHSCORES
153 |
154 | # Get data sort DESC ( 3 2 1 )
155 | ZREVRANGE sorted_favorite_products:user-001 0 -1
156 | ZREVRANGE sorted_favorite_products:user-001 0 -1 WITHSCORES
157 |
158 | # GET DETAIL
159 | ZSCORE sorted_favorite_products:user-001 "product2"
160 |
161 | # GET is within approx ( Khoang)
162 | ZRANGEBYSCORE sorted_favorite_products:user-001 2 4
163 | ZCOUNT sorted_favorite_products:user-001 2 4
164 |
165 | # Counter
166 | ZCARD sorted_favorite_products:user-001
167 |
168 | # Del
169 | ZREM sorted_favorite_products:user-001 "product3"
170 | DEL sorted_favorite_products:user-001
171 | ```
--------------------------------------------------------------------------------
/src/app/v2/services/user.service.js:
--------------------------------------------------------------------------------
1 | //* LIB
2 | const otpGenerator = require("otp-generator");
3 | const _ = require("lodash");
4 |
5 | //* IMPORT
6 | const { BadRequestRequestError } = require("../../../cores/error.response");
7 | const {
8 | formatPhone,
9 | splitUsername,
10 | } = require("../../../commons/utils/convert");
11 | const sendEmail = require("../../../commons/utils/sendEmail");
12 | const userOtpModel = require("../models/user_otp.model");
13 | const { checkUserSpam } = require("../../../auth/auth.blacklist");
14 | const { SpamOTP } = require("../../../commons/keys/spam");
15 | const { TIME, TIME_TOKEN } = require("../../../commons/constants");
16 | const { createTokenJWT } = require("../../../auth/auth.token");
17 | const userModel = require("../../v1/models/user.model");
18 | const {
19 | app: { accessKey, refetchKey },
20 | } = require("../../../commons/configs/app.config");
21 | const { RefetchToken } = require("../../../commons/keys/token");
22 | const { createCookie } = require("../../../auth/auth.cookie");
23 | const CheckFieldsBuilder = require("../../../commons/helpers/checkFieldsBuilder");
24 |
25 | class UserV2Service {
26 | async loginPhone({ phone }) {
27 | const resultSpam = await checkUserSpam({
28 | key: SpamOTP,
29 | blockDuration: TIME._1_MINUTE,
30 | delDuration: TIME._3_MINUTE,
31 | maxRequest: 3,
32 | });
33 |
34 | if (resultSpam) {
35 | throw new BadRequestRequestError(resultSpam);
36 | }
37 | const checkFieldsBuilder = new CheckFieldsBuilder();
38 |
39 | const fields = checkFieldsBuilder.setPhone(phone).build();
40 |
41 | const formatPhoneVietnamese = formatPhone(fields.phone);
42 |
43 | const result = await userModel.getUserById(
44 | { phone: formatPhoneVietnamese },
45 | ["username", "email", "id"]
46 | );
47 |
48 | if (_.isEmpty(result)) {
49 | throw new BadRequestRequestError();
50 | }
51 |
52 | this.sendOTP(result);
53 | return `OTP had send email ${result.email}`;
54 | }
55 |
56 | async sendOTP({ username, email, id }) {
57 | const OTP = await otpGenerator.generate(6, {
58 | digits: true,
59 | lowerCaseAlphabets: false,
60 | upperCaseAlphabets: false,
61 | specialChars: false,
62 | });
63 | const _5_minutes = new Date(Date.now() + 5 * 60 * 1000);
64 |
65 | userOtpModel.createUserOtp({
66 | user_id: id,
67 | otp: OTP,
68 | expires_at: _5_minutes,
69 | });
70 |
71 | sendEmail({
72 | to: email,
73 | subject: `Send OTP Login Phone`,
74 | template: "otpPhone",
75 | context: {
76 | username,
77 | otp: OTP,
78 | },
79 | });
80 | return OTP;
81 | }
82 |
83 | async verifyOTP(res, { otp }) {
84 | if (_.isEmpty(otp)) {
85 | throw new BadRequestRequestError();
86 | }
87 |
88 | if (_.isNaN(_.toNumber(otp))) {
89 | throw new BadRequestRequestError("Otp must be a number");
90 | }
91 |
92 | const result = await userOtpModel.getUserOTPById(
93 | {
94 | otp,
95 | },
96 | ["user_id"]
97 | );
98 |
99 | if (_.isEmpty(result)) {
100 | throw new BadRequestRequestError("Login Phone Failed");
101 | }
102 | const dataInfo = ["id", "username", "email", "role", "password"];
103 |
104 | const userInfo = await userModel.getUserById(
105 | { id: result.user_id },
106 | dataInfo
107 | );
108 | const resultAccessToken = createTokenJWT(
109 | userInfo,
110 | accessKey,
111 | TIME_TOKEN.ACCESS
112 | );
113 | const resultRefetchToken = createTokenJWT(
114 | userInfo,
115 | refetchKey,
116 | TIME_TOKEN.REFETCH
117 | );
118 |
119 | const checkEmptyToken =
120 | _.isEmpty(resultRefetchToken) || _.isEmpty(resultRefetchToken);
121 |
122 | if (checkEmptyToken) {
123 | throw new BadRequestRequestError();
124 | }
125 |
126 | const resultUpdatePromise = userModel.updateUser(
127 | {
128 | refetch_token: resultRefetchToken,
129 | },
130 | {
131 | id: result.user_id,
132 | }
133 | );
134 |
135 | const resultDeleteOtpPromise = userOtpModel.deleteId({
136 | user_id: result.user_id,
137 | });
138 |
139 | Promise.all([resultUpdatePromise, resultDeleteOtpPromise]);
140 |
141 | createCookie(res, RefetchToken, resultRefetchToken);
142 |
143 | userInfo.accessToken = resultAccessToken;
144 |
145 | return userInfo;
146 | }
147 |
148 | async loginGooglePopup(res, info) {
149 | if (_.isEmpty(info)) {
150 | throw new BadRequestRequestError();
151 | }
152 |
153 | info.username = splitUsername(info.email);
154 |
155 | const resultUser = await userModel.upsertUser({
156 | email: info.email,
157 | username: info.username,
158 | });
159 |
160 | if (_.isEmpty(resultUser)) {
161 | throw new BadRequestRequestError();
162 | }
163 |
164 | const { id } = resultUser[0];
165 |
166 | const dataInfo = ["id", "username", "email", "role"];
167 |
168 | const userInfo = await userModel.getUserById({ id }, dataInfo);
169 | const resultAccessToken = createTokenJWT(
170 | userInfo,
171 | accessKey,
172 | TIME_TOKEN.ACCESS
173 | );
174 | const resultRefetchToken = createTokenJWT(
175 | userInfo,
176 | refetchKey,
177 | TIME_TOKEN.REFETCH
178 | );
179 |
180 | const checkEmptyToken =
181 | _.isEmpty(resultRefetchToken) || _.isEmpty(resultRefetchToken);
182 |
183 | if (checkEmptyToken) {
184 | throw new BadRequestRequestError();
185 | }
186 |
187 | userModel.updateUser(
188 | {
189 | refetch_token: resultRefetchToken,
190 | },
191 | {
192 | id,
193 | }
194 | );
195 |
196 | createCookie(res, RefetchToken, resultRefetchToken);
197 |
198 | userInfo.accessToken = resultAccessToken;
199 |
200 | return userInfo;
201 | }
202 | }
203 |
204 | module.exports = new UserV2Service();
205 |
--------------------------------------------------------------------------------
/migrations/backup/dump_2024-01-06_17_40_28.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- PostgreSQL database dump
3 | --
4 |
5 | -- Dumped from database version 16.1 (Debian 16.1-1.pgdg120+1)
6 | -- Dumped by pg_dump version 16.1 (Debian 16.1-1.pgdg120+1)
7 |
8 | SET statement_timeout = 0;
9 | SET lock_timeout = 0;
10 | SET idle_in_transaction_session_timeout = 0;
11 | SET client_encoding = 'UTF8';
12 | SET standard_conforming_strings = on;
13 | SELECT pg_catalog.set_config('search_path', '', false);
14 | SET check_function_bodies = false;
15 | SET xmloption = content;
16 | SET client_min_messages = warning;
17 | SET row_security = off;
18 |
19 | --
20 | -- Name: public; Type: SCHEMA; Schema: -; Owner: taidev
21 | --
22 |
23 | -- *not* creating schema, since initdb creates it
24 |
25 |
26 | ALTER SCHEMA public OWNER TO taidev;
27 |
28 | --
29 | -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: taidev
30 | --
31 |
32 | COMMENT ON SCHEMA public IS '';
33 |
34 |
35 | --
36 | -- Name: insert_hello_label_after_user_change(); Type: FUNCTION; Schema: public; Owner: taidev
37 | --
38 |
39 | CREATE FUNCTION public.insert_hello_label_after_user_change() RETURNS trigger
40 | LANGUAGE plpgsql
41 | AS $$
42 | BEGIN
43 | -- Code logic into
44 | IF TG_OP='INSERT' AND NEW.username IS NOT NULL THEN
45 | -- Started
46 | INSERT INTO public.label (name) VALUES ('hello create');
47 | END IF;
48 |
49 | IF TG_OP = 'UPDATE' AND NEW.username <> OLD.username THEN
50 | INSERT INTO public.label (name) VALUES ('hello update');
51 | END IF;
52 | RETURN NEW;
53 | END;
54 | $$;
55 |
56 |
57 | ALTER FUNCTION public.insert_hello_label_after_user_change() OWNER TO taidev;
58 |
59 | --
60 | -- Name: insert_or_update_user(character varying, character varying, character varying); Type: PROCEDURE; Schema: public; Owner: taidev
61 | --
62 |
63 | CREATE PROCEDURE public.insert_or_update_user(IN p_username character varying, IN p_email character varying, IN p_password character varying)
64 | LANGUAGE plpgsql
65 | AS $$
66 | BEGIN
67 | -- Kiểm tra xem người dùng có tồn tại hay không
68 | IF EXISTS (SELECT 1 FROM public."user" WHERE username = p_username) THEN
69 | -- Nếu tồn tại, thực hiện UPDATE
70 | UPDATE public."user"
71 | SET email = p_email, password = p_password
72 | WHERE username = p_username;
73 | ELSE
74 | -- Nếu không tồn tại, thực hiện INSERT
75 | INSERT INTO public."user" (username, email, password)
76 | VALUES (p_username, p_email, p_password);
77 | END IF;
78 | END;
79 | $$;
80 |
81 |
82 | ALTER PROCEDURE public.insert_or_update_user(IN p_username character varying, IN p_email character varying, IN p_password character varying) OWNER TO taidev;
83 |
84 | --
85 | -- Name: view_label_data(); Type: FUNCTION; Schema: public; Owner: taidev
86 | --
87 |
88 | CREATE FUNCTION public.view_label_data() RETURNS TABLE(taidev character varying)
89 | LANGUAGE plpgsql
90 | AS $$
91 | BEGIN
92 | --- logic
93 | RETURN QUERY SELECT name FROM public.label;
94 | END;
95 | $$;
96 |
97 |
98 | ALTER FUNCTION public.view_label_data() OWNER TO taidev;
99 |
100 | SET default_tablespace = '';
101 |
102 | SET default_table_access_method = heap;
103 |
104 | --
105 | -- Name: label; Type: TABLE; Schema: public; Owner: taidev
106 | --
107 |
108 | CREATE TABLE public.label (
109 | id integer NOT NULL,
110 | name character varying(255) NOT NULL,
111 | created_at timestamp without time zone DEFAULT now() NOT NULL,
112 | updated_at timestamp without time zone DEFAULT now() NOT NULL
113 | );
114 |
115 |
116 | ALTER TABLE public.label OWNER TO taidev;
117 |
118 | --
119 | -- Name: label_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
120 | --
121 |
122 | CREATE SEQUENCE public.label_id_seq
123 | AS integer
124 | START WITH 1
125 | INCREMENT BY 1
126 | NO MINVALUE
127 | NO MAXVALUE
128 | CACHE 1;
129 |
130 |
131 | ALTER SEQUENCE public.label_id_seq OWNER TO taidev;
132 |
133 | --
134 | -- Name: label_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
135 | --
136 |
137 | ALTER SEQUENCE public.label_id_seq OWNED BY public.label.id;
138 |
139 |
140 | --
141 | -- Name: todo_list; Type: TABLE; Schema: public; Owner: taidev
142 | --
143 |
144 | CREATE TABLE public.todo_list (
145 | id integer NOT NULL,
146 | title character varying(255) NOT NULL,
147 | user_id integer,
148 | created_at timestamp without time zone DEFAULT now() NOT NULL,
149 | updated_at timestamp without time zone DEFAULT now() NOT NULL
150 | );
151 |
152 |
153 | ALTER TABLE public.todo_list OWNER TO taidev;
154 |
155 | --
156 | -- Name: todo_list_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
157 | --
158 |
159 | CREATE SEQUENCE public.todo_list_id_seq
160 | AS integer
161 | START WITH 1
162 | INCREMENT BY 1
163 | NO MINVALUE
164 | NO MAXVALUE
165 | CACHE 1;
166 |
167 |
168 | ALTER SEQUENCE public.todo_list_id_seq OWNER TO taidev;
169 |
170 | --
171 | -- Name: todo_list_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
172 | --
173 |
174 | ALTER SEQUENCE public.todo_list_id_seq OWNED BY public.todo_list.id;
175 |
176 |
177 | --
178 | -- Name: todo_list_label; Type: TABLE; Schema: public; Owner: taidev
179 | --
180 |
181 | CREATE TABLE public.todo_list_label (
182 | todo_list_id integer NOT NULL,
183 | label_id integer NOT NULL,
184 | due_date date
185 | );
186 |
187 |
188 | ALTER TABLE public.todo_list_label OWNER TO taidev;
189 |
190 | --
191 | -- Name: user; Type: TABLE; Schema: public; Owner: taidev
192 | --
193 |
194 | CREATE TABLE public."user" (
195 | id integer NOT NULL,
196 | username character varying(255) NOT NULL,
197 | email character varying(255) NOT NULL,
198 | password character varying(255) NOT NULL,
199 | status smallint DEFAULT 10,
200 | created_at timestamp without time zone DEFAULT now() NOT NULL,
201 | updated_at timestamp without time zone DEFAULT now() NOT NULL
202 | );
203 |
204 |
205 | ALTER TABLE public."user" OWNER TO taidev;
206 |
207 | --
208 | -- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
209 | --
210 |
211 | CREATE SEQUENCE public.user_id_seq
212 | AS integer
213 | START WITH 1
214 | INCREMENT BY 1
215 | NO MINVALUE
216 | NO MAXVALUE
217 | CACHE 1;
218 |
219 |
220 | ALTER SEQUENCE public.user_id_seq OWNER TO taidev;
221 |
222 | --
223 | -- Name: user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
224 | --
225 |
226 | ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id;
227 |
228 |
229 | --
230 | -- Name: label id; Type: DEFAULT; Schema: public; Owner: taidev
231 | --
232 |
233 | ALTER TABLE ONLY public.label ALTER COLUMN id SET DEFAULT nextval('public.label_id_seq'::regclass);
234 |
235 |
236 | --
237 | -- Name: todo_list id; Type: DEFAULT; Schema: public; Owner: taidev
238 | --
239 |
240 | ALTER TABLE ONLY public.todo_list ALTER COLUMN id SET DEFAULT nextval('public.todo_list_id_seq'::regclass);
241 |
242 |
243 | --
244 | -- Name: user id; Type: DEFAULT; Schema: public; Owner: taidev
245 | --
246 |
247 | ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass);
248 |
249 |
250 | --
251 | -- Data for Name: label; Type: TABLE DATA; Schema: public; Owner: taidev
252 | --
253 |
254 | COPY public.label (id, name, created_at, updated_at) FROM stdin;
255 | 6 hello create 2024-01-05 15:36:39.590747 2024-01-05 15:36:39.590747
256 | 7 hello update 2024-01-05 15:37:52.429364 2024-01-05 15:37:52.429364
257 | 8 hello update 2024-01-05 15:40:52.66935 2024-01-05 15:40:52.66935
258 | \.
259 |
260 |
261 | --
262 | -- Data for Name: todo_list; Type: TABLE DATA; Schema: public; Owner: taidev
263 | --
264 |
265 | COPY public.todo_list (id, title, user_id, created_at, updated_at) FROM stdin;
266 | 1 Work 1 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
267 | 2 Personal 2 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
268 | 3 Shopping 1 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
269 | \.
270 |
271 |
272 | --
273 | -- Data for Name: todo_list_label; Type: TABLE DATA; Schema: public; Owner: taidev
274 | --
275 |
276 | COPY public.todo_list_label (todo_list_id, label_id, due_date) FROM stdin;
277 | \.
278 |
279 |
280 | --
281 | -- Data for Name: user; Type: TABLE DATA; Schema: public; Owner: taidev
282 | --
283 |
284 | COPY public."user" (id, username, email, password, status, created_at, updated_at) FROM stdin;
285 | 1 Tai\n user1@email.com password1 10 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
286 | 2 Hào user2@email.com password2 10 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
287 | 5 user5 user5@email.com password5 10 2024-01-04 04:48:35.687558 2024-01-04 04:48:35.687558
288 | 7 user6 user6111@email.com password6 10 2024-01-04 07:05:07.177812 2024-01-04 07:05:07.177812
289 | 3 Tấn user3@email.com password3 20 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
290 | 4 user442 user4@email.com password4 30 2024-01-04 04:46:27.636599 2024-01-04 04:46:27.636599
291 | 8 aaa aa@email.com passworda 10 2024-01-05 15:36:39.590747 2024-01-05 15:36:39.590747
292 | 6 bababa1q user5@email.com password5 40 2024-01-04 04:55:03.747616 2024-01-04 04:55:03.747616
293 | \.
294 |
295 |
296 | --
297 | -- Name: label_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
298 | --
299 |
300 | SELECT pg_catalog.setval('public.label_id_seq', 8, true);
301 |
302 |
303 | --
304 | -- Name: todo_list_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
305 | --
306 |
307 | SELECT pg_catalog.setval('public.todo_list_id_seq', 3, true);
308 |
309 |
310 | --
311 | -- Name: user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
312 | --
313 |
314 | SELECT pg_catalog.setval('public.user_id_seq', 8, true);
315 |
316 |
317 | --
318 | -- Name: label label_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
319 | --
320 |
321 | ALTER TABLE ONLY public.label
322 | ADD CONSTRAINT label_pkey PRIMARY KEY (id);
323 |
324 |
325 | --
326 | -- Name: todo_list_label todo_list_label_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
327 | --
328 |
329 | ALTER TABLE ONLY public.todo_list_label
330 | ADD CONSTRAINT todo_list_label_pkey PRIMARY KEY (todo_list_id, label_id);
331 |
332 |
333 | --
334 | -- Name: todo_list todo_list_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
335 | --
336 |
337 | ALTER TABLE ONLY public.todo_list
338 | ADD CONSTRAINT todo_list_pkey PRIMARY KEY (id);
339 |
340 |
341 | --
342 | -- Name: user user_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
343 | --
344 |
345 | ALTER TABLE ONLY public."user"
346 | ADD CONSTRAINT user_pkey PRIMARY KEY (id);
347 |
348 |
349 | --
350 | -- Name: user trg_insert_hello_label_after_user_change; Type: TRIGGER; Schema: public; Owner: taidev
351 | --
352 |
353 | CREATE TRIGGER trg_insert_hello_label_after_user_change AFTER INSERT OR UPDATE ON public."user" FOR EACH ROW EXECUTE FUNCTION public.insert_hello_label_after_user_change();
354 |
355 |
356 | --
357 | -- Name: todo_list_label todo_list_label_label_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
358 | --
359 |
360 | ALTER TABLE ONLY public.todo_list_label
361 | ADD CONSTRAINT todo_list_label_label_id_fkey FOREIGN KEY (label_id) REFERENCES public.label(id);
362 |
363 |
364 | --
365 | -- Name: todo_list_label todo_list_label_todo_list_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
366 | --
367 |
368 | ALTER TABLE ONLY public.todo_list_label
369 | ADD CONSTRAINT todo_list_label_todo_list_id_fkey FOREIGN KEY (todo_list_id) REFERENCES public.todo_list(id);
370 |
371 |
372 | --
373 | -- Name: todo_list todo_list_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
374 | --
375 |
376 | ALTER TABLE ONLY public.todo_list
377 | ADD CONSTRAINT todo_list_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id);
378 |
379 |
380 | --
381 | -- Name: SCHEMA public; Type: ACL; Schema: -; Owner: taidev
382 | --
383 |
384 | REVOKE USAGE ON SCHEMA public FROM PUBLIC;
385 |
386 |
387 | --
388 | -- PostgreSQL database dump complete
389 | --
390 |
391 |
--------------------------------------------------------------------------------
/SQL.md:
--------------------------------------------------------------------------------
1 | # SQL Queries Collection
2 |
3 | ## Document: https://www.dofactory.com/sql
4 | ## EXAMPLE: https://sqlbolt.com/
5 |
6 | Explore this repository to discover a variety of SQL queries for common tasks. Whether you're a beginner or an experienced SQL user, these examples can help improve your understanding of SQL statements.
7 |
8 | ## COPY TABLE
9 | ```sql
10 | CREATE TABLE public.user_copy as SELECT * FROM public.user;
11 | \! clear
12 | \dt
13 | ```
14 |
15 | ### INSERT
16 | ```sql
17 | -- Insert users
18 | INSERT INTO public."user" (username, email, password)
19 | VALUES
20 | ('user1', 'user1@email.com', 'password1'),
21 | ('user2', 'user2@email.com', 'password2'),
22 | ('user3', 'user3@email.com', 'password3');
23 |
24 | -- Insert todo_list
25 | INSERT INTO public.todo_list (title, user_id)
26 | VALUES
27 | ('Work', 1),
28 | ('Personal', 2),
29 | ('Shopping', 1);
30 |
31 | -- Insert labels
32 | INSERT INTO public.label (name)
33 | VALUES
34 | ('Important'),
35 | ('Home'),
36 | ('Work');
37 |
38 | -- Link todo_list and labels
39 | INSERT INTO public.todo_list_label (todo_list_id, label_id)
40 | VALUES
41 | (1, 1), -- Work list has the label "Important"
42 | (2, 2), -- Personal list has the label "Home"
43 | (3, 3), -- Shopping list has the label "Work"
44 | (3, 1); -- Shopping list has the label "Important"
45 | ```
46 |
47 | ### UPDATE
48 | ```sql
49 | -- Update due_date for all todo_list items
50 | UPDATE public.todo_list
51 | SET due_date = '2022-02-28';
52 | ```
53 |
54 | ### DROP ALL TABLE
55 | ```sql
56 | DROP SCHEMA public CASCADE;
57 | CREATE SCHEMA public;
58 | ```
59 |
60 | ### ALTER
61 | ```sql
62 | ALTER TABLE public.todo_list
63 | ADD COLUMN due_date DATE;
64 | ```
65 |
66 | ### Basic SELECT
67 | ```sql
68 | SELECT public.user.* FROM public.user;
69 | SELECT * FROM public.todo_list;
70 | SELECT email FROM public.user;
71 | SELECT *, NULL AS password FROM public.user;
72 | ```
73 |
74 | ### INTO Statement
75 | ```sql
76 | SELECT * INTO todo_list_copy FROM public.todo_list;
77 | ```
78 |
79 | ### WHERE Clause
80 | ```sql
81 | SELECT * FROM public.user WHERE email = 'user1@email.com';
82 | SELECT * FROM public.todo_list WHERE user_id = 1;
83 | SELECT * FROM public.todo_list WHERE created_at > '2024-05-01';
84 | ```
85 |
86 | ### EXPLAIN
87 | ```sql
88 | EXPLAIN SELECT * FROM public.todo_list WHERE user_id = 1;
89 | ```
90 |
91 | ### ORDER BY Clause
92 | ```sql
93 | SELECT * FROM public.user ORDER BY username ASC; -- Sort small -> big
94 | SELECT * FROM public.todo_list ORDER BY created_at DESC; -- Sort big -> small
95 | SELECT * FROM public.todo_list ORDER BY due_date ASC, title ASC;
96 | ```
97 |
98 | ## JOIN Operations
99 | ```sql
100 | SELECT todo_list.*, "user".username FROM public.todo_list INNER JOIN public."user" ON todo_list.user_id = "user".id;
101 | SELECT t.*, u.username FROM public.todo_list AS t LEFT JOIN public.user AS u ON t.user_id = u.id; -- Rename
102 | SELECT "user".*, todo_list.* FROM public."user" JOIN public.todo_list ON "user".id = todo_list.user_id;
103 |
104 | -- Get info with due_date
105 | SELECT todo_list.*, name, due_date FROM public.todo_list
106 | JOIN public.todo_list_label ON todo_list.id = todo_list_label.todo_list_id
107 | JOIN public.label ON todo_list_label.label_id = label.id;
108 | ```
109 |
110 | ### LIMIT and OFFSET
111 | ```sql
112 | SELECT * FROM public.user ORDER BY username DESC LIMIT 2;
113 | SELECT * FROM public.todo_list ORDER BY title ASC OFFSET 1; -- Start from row number five
114 | ```
115 |
116 | ### DISTINCT
117 | ```sql
118 | SELECT DISTINCT title, user_id FROM public.todo_list;
119 | ```
120 |
121 | ### LOGICAL Operators
122 | ```sql
123 | SELECT * FROM public.user WHERE id = 1 AND status = 10;
124 | SELECT * FROM public.user WHERE id = 1 OR status = 10;
125 | SELECT * FROM public.user WHERE NOT status = 20;
126 | ```
127 |
128 | ### CASE Statement
129 | ```sql
130 | SELECT *,
131 | CASE
132 | WHEN status = 20 THEN 'user'
133 | WHEN status = 10 THEN 'admin'
134 | ELSE 'anonymous'
135 | END AS role
136 | FROM public.user;
137 | ```
138 |
139 | ### BETWEEN OPERATOR
140 | ```sql
141 | SELECT * FROM public.user WHERE status BETWEEN 1 AND 10;
142 | ```
143 |
144 | ### LIKE OPERATOR
145 | ```sql
146 | SELECT * FROM public.user WHERE username LIKE '%user%'; -- Search anywhere
147 | SELECT * FROM public.user WHERE username LIKE 'user%'; -- Must start with 'user'
148 | SELECT * FROM public.user WHERE username LIKE 'user%'; -- Must end with 'user'
149 | ```
150 |
151 | ### EXISTS OPERATOR
152 | ```sql
153 | SELECT *
154 | FROM public.user
155 | WHERE EXISTS (
156 | SELECT 1 -- 1 is true, 0 is false
157 | FROM public.user T
158 | WHERE T.status = 10
159 | );
160 | ```
161 |
162 | ### HAVING
163 | ```sql
164 | SELECT user_id, COUNT(*) AS todo_count
165 | FROM public.todo_list
166 | GROUP BY user_id
167 | HAVING COUNT(*) >= 2;
168 | ```
169 |
170 | ### COUNT
171 | ```sql
172 | SELECT user_id, COUNT(*) AS todo_count FROM public.todo_list GROUP BY user_id;
173 | ```
174 |
175 | ### MIN and MAX
176 | ```sql
177 | SELECT MIN(id) AS smallest_user_id, MAX(id) AS largest_user_id FROM public.user;
178 | SELECT MIN(due_date) AS earliest_due_date, MAX(due_date) AS latest_due_date FROM public.todo_list;
179 | ```
180 |
181 | ### TRANSACTION
182 |
183 | ```sql
184 | -- Started transaction
185 | BEGIN;
186 |
187 | -- Command sql 1
188 | INSERT INTO public.user (username, email, password)
189 | VALUES ('user4', 'user4@email.com', 'password1');
190 |
191 | -- Command sql 2
192 | INSERT INTO public.todo_list (title, user_id)
193 | VALUES ('Play', 4);
194 |
195 | -- Commit if it succeeded
196 | COMMIT;
197 |
198 | -- Rollback if 1 command failed
199 | ROLLBACK;
200 | ```
201 |
202 | ### TRIGGER
203 | ```sql
204 | CREATE OR REPLACE FUNCTION insert_hello_label_after_user_change()
205 | RETURNS TRIGGER AS
206 | $$
207 | BEGIN
208 | IF TG_OP = 'INSERT' AND NEW.username IS NOT NULL THEN
209 | INSERT INTO public.label (name) VALUES ('hello create');
210 | END IF;
211 |
212 | IF TG_OP = 'UPDATE' AND NEW.username <> OLD.username THEN
213 | INSERT INTO public.label (name) VALUES ('hello update');
214 | END IF;
215 |
216 | RETURN NEW;
217 | END;
218 | $$
219 | LANGUAGE plpgsql;
220 |
221 | CREATE TRIGGER trg_insert_hello_label_after_user_change
222 | AFTER INSERT OR UPDATE
223 | ON public."user"
224 | FOR EACH ROW
225 | EXECUTE FUNCTION insert_hello_label_after_user_change();
226 |
227 | SELECT public.user.* FROM public.user;
228 | SELECT public.label.* FROM public.label;
229 |
230 | INSERT INTO public."user" (username, email, password)
231 | VALUES
232 | ('user5', 'user5@email.com', 'password5')
233 |
234 | UPDATE public.user SET username = 'user442' where id=4;
235 | ```
236 |
237 | ### FUNCTION
238 | ```sql
239 | CREATE OR REPLACE FUNCTION view_label_data()
240 | RETURNS TABLE ( label_name VARCHAR(255)) AS
241 | $$
242 | BEGIN
243 | RETURN QUERY
244 | SELECT name
245 | FROM public.label;
246 | END;
247 | $$
248 | LANGUAGE plpgsql;
249 |
250 | SELECT * FROM view_label_data();
251 | ```
252 |
253 | ### PRODUCER
254 | ```sql
255 | CREATE OR REPLACE PROCEDURE insert_or_update_user(
256 | p_username VARCHAR,
257 | p_email VARCHAR,
258 | p_password VARCHAR
259 | )
260 | LANGUAGE plpgsql
261 | AS $$
262 | BEGIN
263 | -- Check username exits
264 | IF EXISTS (SELECT 1 FROM public."user" WHERE username = p_username) THEN
265 | -- If exit executed UPDATE
266 | UPDATE public."user"
267 | SET email = p_email, password = p_password
268 | WHERE username = p_username;
269 | ELSE
270 | -- If not exit executed INSERT
271 | INSERT INTO public."user" (username, email, password)
272 | VALUES (p_username, p_email, p_password);
273 | END IF;
274 | END;
275 | $$;
276 | CALL insert_or_update_user('taidev', 'taidev@email.com', 'password6');
277 | ```
278 |
279 | # SECURITY
280 |
281 | ## 1. CHECK ROLE USER
282 | ```sql
283 | SELECT * FROM pg_user;
284 |
285 | SELECT current_user;
286 |
287 | SELECT COUNT(*) FROM pg_user;
288 |
289 | SELECT * FROM pg_roles where rolname='tai_demo';
290 |
291 | SELECT * FROM information_schema.table_privileges WHERE grantee = 'taidev';
292 | ```
293 |
294 | ## 2. CREATE USER AND FORGET PASSWORD
295 | ```sql
296 | CREATE USER tai_demo WITH ENCRYPTED PASSWORD '123456';
297 |
298 | ALTER USER taidev WITH PASSWORD 'taidev';
299 | ```
300 |
301 | ## 3. GRANT ACCESS SCHEMA PUBLIC
302 | ```sql
303 | GRANT USAGE ON SCHEMA public TO tai_demo;
304 |
305 | GRANT SELECT ON ALL TABLES IN SCHEMA public TO tai_demo;
306 |
307 | GRANT SELECT ON products TO tai_demo;
308 |
309 | GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA public TO tai_demo;
310 |
311 | GRANT SELECT ON TABLE public.user TO tai_demo;
312 | ```
313 |
314 | ## 4. GRANT ACCESS PRIVILEGES TABLE SCHEMA PUBLIC
315 | ```sql
316 | GRANT SELECT ON ALL TABLES IN SCHEMA public TO tai_demo;
317 |
318 | GRANT SELECT,CREATE ON ALL TABLES IN SCHEMA public TO tai_demo;
319 |
320 | GRANT CREATE,UPDATE ON ALL TABLES IN SCHEMA public TO tai_demo;
321 |
322 | GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO tai_demo;
323 | ```
324 |
325 | ## 5. REVOKE
326 | ```sql
327 | REVOKE CREATE ON TABLE public.user FROM tai_demo;
328 |
329 | REVOKE ALL ON public.products FROM tai_demo;
330 |
331 | REVOKE CREATE ON ALL TABLES IN SCHEMA public FROM tai_demo;
332 | ```
333 |
334 | ## 6. DROP USER AND REVOKE
335 | ```sql
336 | REVOKE ALL PRIVILEGES ON DATABASE class_fullstack_todolist FROM tai_demo;
337 |
338 | REVOKE ALL PRIVILEGES ON SCHEMA public FROM tai_demo;
339 |
340 | REVOKE ALL PRIVILEGES ON ALL TABLES IN SCHEMA public FROM tai_demo;
341 |
342 | DROP USER tai_demo;
343 | ```
344 |
345 | ## 7. SET PERMISSION
346 | ```sql
347 | ALTER USER tai_demo WITH SUPERUSER, CREATEDB, LOGIN;
348 |
349 | ALTER USER fdhhhdjd WITH NOSUPERUSER;
350 |
351 | SELECT * FROM pg_user where usename='taidev';
352 | ```
353 |
354 | ## 8. DISCONNECT ACCOUNT
355 | ```sql
356 | ALTER USER username WITH NOLOGIN;
357 |
358 | ALTER USER username WITH LOGIN;
359 | ```
360 |
361 | # BACKUP and RESTORE
362 |
363 | # 1. pg_dump, pg_dumpall
364 | ```sql
365 | pg_dump or pg_dumpall -h [host] -U [username] -W [database] > [path file]
366 | ```
367 |
368 | # 2. RESTORE
369 | ```sql;
370 | cat ./migrations/backup/file.sql | psql "link online"
371 | ```
372 |
373 |
374 | # INDEX
375 |
376 | ```sql
377 | SELECT indexname, indexdef FROM pg_indexes WHERE tablename = 'user';
378 | SHOW INDEX FROM public."user";
379 |
380 | SELECT * from public.user WHERE status = 40;
381 |
382 | CREATE INDEX idx_email_status on public.user(email, status);
383 | CREATE INDEX idx_status_email on public.user(status, email);
384 |
385 | DROP INDEX idx_email_status;
386 | DROP INDEX idx_status_email;
387 |
388 | EXPLAIN analyze SELECT status from public.user WHERE status = 40;
389 |
390 | EXPLAIN analyze SELECT email from public.user WHERE email='user128@example.com';
391 |
392 | EXPLAIN ANALYZE SELECT email FROM public."user" WHERE email = 'user128@example.com' AND status = 40;
393 |
394 | EXPLAIN ANALYZE SELECT email FROM public."user" WHERE status = 40 AND email = 'user128@example.com';
395 |
396 | ---
397 | CREATE INDEX idx_email on public.user(email);
398 |
399 | DROP INDEX idx_email;
400 |
401 | EXPLAIN analyze SELECT email from public.user WHERE email='user128@example.com';
402 |
403 | EXPLAIN analyze SELECT * from public.user WHERE email='user128@example.com';
404 | EXPLAIN analyze SELECT email, status from public.user WHERE email='user128@example.com';
405 | ```
406 |
407 | ## 6. REVOKE DROP TRUNCATE DELETE
408 | ```sql
409 | CREATE USER demo WITH ENCRYPTED PASSWORD '123456';
410 | SHOW GRANTS FOR demo;
411 |
412 | SELECT privilege_type FROM information_schema.table_privileges WHERE table_name = 'todo_list_copy' AND grantee = 'demo';
413 |
414 | REVOKE DELETE ON ALL TABLES IN SCHEMA public FROM demo; -- Revoke TRUNCATE and Delete
415 |
416 | REVOKE DROP ON ALL TABLES IN SCHEMA public FROM demo;
417 | ```
418 |
419 | # DELETE
420 | ```sql
421 | CREATE TABLE public.todo_list_copy as SELECT * FROM public.todo_list;
422 |
423 | INSERT INTO public.todo_list_copy SELECT * FROM public.todo_list;
424 |
425 | SELECT * FROM public.todo_list_copy;
426 |
427 | DROP TABLE public.todo_list_copy;
428 |
429 | DELETE from public.todo_list_copy;
430 |
431 | DELETE from public.todo_list_copy where user_id=1;
432 |
433 | TRUNCATE public.todo_list_copy;
434 |
435 | BEGIN;
436 | DELETE from public.todo_list_copy
437 | ROLLBACK;
438 |
439 | ```
440 |
441 |
--------------------------------------------------------------------------------
/src/views/forgotPassword.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | Quên mật khẩu
10 |
431 |
432 |
433 |
437 |
444 |
445 |
446 |
453 |
454 | |
455 | Lấy lại mật khẩu
460 | |
461 |
462 |
463 |
464 |
470 |
478 |
479 |
480 |
481 |
482 | Xin chào {{username}},
483 |
484 | Bạn vừa yêu cầu đặt lại mật khẩu cho tài khoản
485 | {{username}} của bạn. Sử dụng nút bên dưới để đặt lại
486 | mật khẩu.
487 | Yêu cầu đặt lại mật khẩu này chỉ có hiệu lực trong
489 | 15 phút tiếp theo.
491 |
492 |
493 |
501 |
502 | |
503 |
521 | |
522 |
523 |
524 |
525 | Trân trọng,
526 | Lớp fullstack
527 |
528 |
529 |
530 |
531 | |
532 |
533 | Nếu bạn gặp vấn đề với nút ở trên, hãy sao chép
534 | và dán URL sau vào trình duyệt web của bạn.
535 |
536 | |
537 |
538 |
539 |
540 | |
541 |
542 |
543 | |
544 |
545 |
546 | |
547 |
563 | |
564 |
565 |
566 | |
567 |
568 |
569 |
570 |
571 |
--------------------------------------------------------------------------------
/migrations/backup/dump_2024-01-04_15_48_56.sql:
--------------------------------------------------------------------------------
1 | --
2 | -- PostgreSQL database cluster dump
3 | --
4 |
5 | SET default_transaction_read_only = off;
6 |
7 | SET client_encoding = 'UTF8';
8 | SET standard_conforming_strings = on;
9 |
10 | --
11 | -- Drop databases (except postgres and template1)
12 | --
13 |
14 | DROP DATABASE "class-fullstack";
15 |
16 |
17 |
18 |
19 | --
20 | -- Drop roles
21 | --
22 |
23 | DROP ROLE taidev;
24 |
25 |
26 | --
27 | -- Roles
28 | --
29 |
30 | CREATE ROLE taidev;
31 | ALTER ROLE taidev WITH SUPERUSER INHERIT CREATEROLE CREATEDB LOGIN REPLICATION BYPASSRLS PASSWORD 'SCRAM-SHA-256$4096:QfDIoqa0D1qxM9/eJ0qRrw==$BJPZQSsIJtNoK2gBFxbTftl3GYyP3j7apzZPnXfE/rg=:l+SymSeqsryLpBXTJPSfJ+dUNB8Z3r8U9aa7TjGwihs=';
32 |
33 | --
34 | -- User Configurations
35 | --
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | --
45 | -- Databases
46 | --
47 |
48 | --
49 | -- Database "template1" dump
50 | --
51 |
52 | --
53 | -- PostgreSQL database dump
54 | --
55 |
56 | -- Dumped from database version 16.1 (Debian 16.1-1.pgdg120+1)
57 | -- Dumped by pg_dump version 16.1 (Debian 16.1-1.pgdg120+1)
58 |
59 | SET statement_timeout = 0;
60 | SET lock_timeout = 0;
61 | SET idle_in_transaction_session_timeout = 0;
62 | SET client_encoding = 'UTF8';
63 | SET standard_conforming_strings = on;
64 | SELECT pg_catalog.set_config('search_path', '', false);
65 | SET check_function_bodies = false;
66 | SET xmloption = content;
67 | SET client_min_messages = warning;
68 | SET row_security = off;
69 |
70 | UPDATE pg_catalog.pg_database SET datistemplate = false WHERE datname = 'template1';
71 | DROP DATABASE template1;
72 | --
73 | -- Name: template1; Type: DATABASE; Schema: -; Owner: taidev
74 | --
75 |
76 | CREATE DATABASE template1 WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8';
77 |
78 |
79 | ALTER DATABASE template1 OWNER TO taidev;
80 |
81 | \connect template1
82 |
83 | SET statement_timeout = 0;
84 | SET lock_timeout = 0;
85 | SET idle_in_transaction_session_timeout = 0;
86 | SET client_encoding = 'UTF8';
87 | SET standard_conforming_strings = on;
88 | SELECT pg_catalog.set_config('search_path', '', false);
89 | SET check_function_bodies = false;
90 | SET xmloption = content;
91 | SET client_min_messages = warning;
92 | SET row_security = off;
93 |
94 | --
95 | -- Name: DATABASE template1; Type: COMMENT; Schema: -; Owner: taidev
96 | --
97 |
98 | COMMENT ON DATABASE template1 IS 'default template for new databases';
99 |
100 |
101 | --
102 | -- Name: template1; Type: DATABASE PROPERTIES; Schema: -; Owner: taidev
103 | --
104 |
105 | ALTER DATABASE template1 IS_TEMPLATE = true;
106 |
107 |
108 | \connect template1
109 |
110 | SET statement_timeout = 0;
111 | SET lock_timeout = 0;
112 | SET idle_in_transaction_session_timeout = 0;
113 | SET client_encoding = 'UTF8';
114 | SET standard_conforming_strings = on;
115 | SELECT pg_catalog.set_config('search_path', '', false);
116 | SET check_function_bodies = false;
117 | SET xmloption = content;
118 | SET client_min_messages = warning;
119 | SET row_security = off;
120 |
121 | --
122 | -- Name: DATABASE template1; Type: ACL; Schema: -; Owner: taidev
123 | --
124 |
125 | REVOKE CONNECT,TEMPORARY ON DATABASE template1 FROM PUBLIC;
126 | GRANT CONNECT ON DATABASE template1 TO PUBLIC;
127 |
128 |
129 | --
130 | -- PostgreSQL database dump complete
131 | --
132 |
133 | --
134 | -- Database "class-fullstack" dump
135 | --
136 |
137 | --
138 | -- PostgreSQL database dump
139 | --
140 |
141 | -- Dumped from database version 16.1 (Debian 16.1-1.pgdg120+1)
142 | -- Dumped by pg_dump version 16.1 (Debian 16.1-1.pgdg120+1)
143 |
144 | SET statement_timeout = 0;
145 | SET lock_timeout = 0;
146 | SET idle_in_transaction_session_timeout = 0;
147 | SET client_encoding = 'UTF8';
148 | SET standard_conforming_strings = on;
149 | SELECT pg_catalog.set_config('search_path', '', false);
150 | SET check_function_bodies = false;
151 | SET xmloption = content;
152 | SET client_min_messages = warning;
153 | SET row_security = off;
154 |
155 | --
156 | -- Name: class-fullstack; Type: DATABASE; Schema: -; Owner: taidev
157 | --
158 |
159 | CREATE DATABASE "class-fullstack" WITH TEMPLATE = template0 ENCODING = 'UTF8' LOCALE_PROVIDER = libc LOCALE = 'en_US.utf8';
160 |
161 |
162 | ALTER DATABASE "class-fullstack" OWNER TO taidev;
163 |
164 | \connect -reuse-previous=on "dbname='class-fullstack'"
165 |
166 | SET statement_timeout = 0;
167 | SET lock_timeout = 0;
168 | SET idle_in_transaction_session_timeout = 0;
169 | SET client_encoding = 'UTF8';
170 | SET standard_conforming_strings = on;
171 | SELECT pg_catalog.set_config('search_path', '', false);
172 | SET check_function_bodies = false;
173 | SET xmloption = content;
174 | SET client_min_messages = warning;
175 | SET row_security = off;
176 |
177 | --
178 | -- Name: public; Type: SCHEMA; Schema: -; Owner: taidev
179 | --
180 |
181 | -- *not* creating schema, since initdb creates it
182 |
183 |
184 | ALTER SCHEMA public OWNER TO taidev;
185 |
186 | --
187 | -- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: taidev
188 | --
189 |
190 | COMMENT ON SCHEMA public IS '';
191 |
192 |
193 | --
194 | -- Name: insert_hello_label_after_user_change(); Type: FUNCTION; Schema: public; Owner: taidev
195 | --
196 |
197 | CREATE FUNCTION public.insert_hello_label_after_user_change() RETURNS trigger
198 | LANGUAGE plpgsql
199 | AS $$
200 | BEGIN
201 | -- Kiểm tra sự kiện là INSERT và có cột 'username'
202 | IF TG_OP = 'INSERT' AND NEW.username IS NOT NULL THEN
203 | -- Chèn một nhãn mới với tên là 'hello create' vào bảng label
204 | INSERT INTO public.label (name) VALUES ('hello create');
205 | END IF;
206 |
207 | -- Kiểm tra sự kiện là UPDATE và cột 'username' thay đổi
208 | IF TG_OP = 'UPDATE' AND NEW.username <> OLD.username THEN
209 | -- Chèn một nhãn mới với tên là 'hello update' vào bảng label
210 | INSERT INTO public.label (name) VALUES ('hello update');
211 | END IF;
212 |
213 | RETURN NEW;
214 | END;
215 | $$;
216 |
217 |
218 | ALTER FUNCTION public.insert_hello_label_after_user_change() OWNER TO taidev;
219 |
220 | --
221 | -- Name: insert_or_update_user(character varying, character varying, character varying); Type: PROCEDURE; Schema: public; Owner: taidev
222 | --
223 |
224 | CREATE PROCEDURE public.insert_or_update_user(IN p_username character varying, IN p_email character varying, IN p_password character varying)
225 | LANGUAGE plpgsql
226 | AS $$
227 | BEGIN
228 | -- Kiểm tra xem người dùng có tồn tại hay không
229 | IF EXISTS (SELECT 1 FROM public."user" WHERE username = p_username) THEN
230 | -- Nếu tồn tại, thực hiện UPDATE
231 | UPDATE public."user"
232 | SET email = p_email, password = p_password
233 | WHERE username = p_username;
234 | ELSE
235 | -- Nếu không tồn tại, thực hiện INSERT
236 | INSERT INTO public."user" (username, email, password)
237 | VALUES (p_username, p_email, p_password);
238 | END IF;
239 | END;
240 | $$;
241 |
242 |
243 | ALTER PROCEDURE public.insert_or_update_user(IN p_username character varying, IN p_email character varying, IN p_password character varying) OWNER TO taidev;
244 |
245 | --
246 | -- Name: view_label_data(); Type: FUNCTION; Schema: public; Owner: taidev
247 | --
248 |
249 | CREATE FUNCTION public.view_label_data() RETURNS TABLE(label_name character varying)
250 | LANGUAGE plpgsql
251 | AS $$
252 | BEGIN
253 | RETURN QUERY
254 | SELECT name
255 | FROM public.label;
256 | END;
257 | $$;
258 |
259 |
260 | ALTER FUNCTION public.view_label_data() OWNER TO taidev;
261 |
262 | SET default_tablespace = '';
263 |
264 | SET default_table_access_method = heap;
265 |
266 | --
267 | -- Name: label; Type: TABLE; Schema: public; Owner: taidev
268 | --
269 |
270 | CREATE TABLE public.label (
271 | id integer NOT NULL,
272 | name character varying(255) NOT NULL,
273 | created_at timestamp without time zone DEFAULT now() NOT NULL,
274 | updated_at timestamp without time zone DEFAULT now() NOT NULL
275 | );
276 |
277 |
278 | ALTER TABLE public.label OWNER TO taidev;
279 |
280 | --
281 | -- Name: label_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
282 | --
283 |
284 | CREATE SEQUENCE public.label_id_seq
285 | AS integer
286 | START WITH 1
287 | INCREMENT BY 1
288 | NO MINVALUE
289 | NO MAXVALUE
290 | CACHE 1;
291 |
292 |
293 | ALTER SEQUENCE public.label_id_seq OWNER TO taidev;
294 |
295 | --
296 | -- Name: label_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
297 | --
298 |
299 | ALTER SEQUENCE public.label_id_seq OWNED BY public.label.id;
300 |
301 |
302 | --
303 | -- Name: todo_list; Type: TABLE; Schema: public; Owner: taidev
304 | --
305 |
306 | CREATE TABLE public.todo_list (
307 | id integer NOT NULL,
308 | title character varying(255) NOT NULL,
309 | user_id integer,
310 | created_at timestamp without time zone DEFAULT now() NOT NULL,
311 | updated_at timestamp without time zone DEFAULT now() NOT NULL
312 | );
313 |
314 |
315 | ALTER TABLE public.todo_list OWNER TO taidev;
316 |
317 | --
318 | -- Name: todo_list_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
319 | --
320 |
321 | CREATE SEQUENCE public.todo_list_id_seq
322 | AS integer
323 | START WITH 1
324 | INCREMENT BY 1
325 | NO MINVALUE
326 | NO MAXVALUE
327 | CACHE 1;
328 |
329 |
330 | ALTER SEQUENCE public.todo_list_id_seq OWNER TO taidev;
331 |
332 | --
333 | -- Name: todo_list_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
334 | --
335 |
336 | ALTER SEQUENCE public.todo_list_id_seq OWNED BY public.todo_list.id;
337 |
338 |
339 | --
340 | -- Name: todo_list_label; Type: TABLE; Schema: public; Owner: taidev
341 | --
342 |
343 | CREATE TABLE public.todo_list_label (
344 | todo_list_id integer NOT NULL,
345 | label_id integer NOT NULL,
346 | due_date date
347 | );
348 |
349 |
350 | ALTER TABLE public.todo_list_label OWNER TO taidev;
351 |
352 | --
353 | -- Name: user; Type: TABLE; Schema: public; Owner: taidev
354 | --
355 |
356 | CREATE TABLE public."user" (
357 | id integer NOT NULL,
358 | username character varying(255) NOT NULL,
359 | email character varying(255) NOT NULL,
360 | password character varying(255) NOT NULL,
361 | status smallint DEFAULT 10,
362 | created_at timestamp without time zone DEFAULT now() NOT NULL,
363 | updated_at timestamp without time zone DEFAULT now() NOT NULL
364 | );
365 |
366 |
367 | ALTER TABLE public."user" OWNER TO taidev;
368 |
369 | --
370 | -- Name: user_id_seq; Type: SEQUENCE; Schema: public; Owner: taidev
371 | --
372 |
373 | CREATE SEQUENCE public.user_id_seq
374 | AS integer
375 | START WITH 1
376 | INCREMENT BY 1
377 | NO MINVALUE
378 | NO MAXVALUE
379 | CACHE 1;
380 |
381 |
382 | ALTER SEQUENCE public.user_id_seq OWNER TO taidev;
383 |
384 | --
385 | -- Name: user_id_seq; Type: SEQUENCE OWNED BY; Schema: public; Owner: taidev
386 | --
387 |
388 | ALTER SEQUENCE public.user_id_seq OWNED BY public."user".id;
389 |
390 |
391 | --
392 | -- Name: label id; Type: DEFAULT; Schema: public; Owner: taidev
393 | --
394 |
395 | ALTER TABLE ONLY public.label ALTER COLUMN id SET DEFAULT nextval('public.label_id_seq'::regclass);
396 |
397 |
398 | --
399 | -- Name: todo_list id; Type: DEFAULT; Schema: public; Owner: taidev
400 | --
401 |
402 | ALTER TABLE ONLY public.todo_list ALTER COLUMN id SET DEFAULT nextval('public.todo_list_id_seq'::regclass);
403 |
404 |
405 | --
406 | -- Name: user id; Type: DEFAULT; Schema: public; Owner: taidev
407 | --
408 |
409 | ALTER TABLE ONLY public."user" ALTER COLUMN id SET DEFAULT nextval('public.user_id_seq'::regclass);
410 |
411 |
412 | --
413 | -- Data for Name: label; Type: TABLE DATA; Schema: public; Owner: taidev
414 | --
415 |
416 | COPY public.label (id, name, created_at, updated_at) FROM stdin;
417 | 1 hello 2024-01-04 04:46:27.636599 2024-01-04 04:46:27.636599
418 | 2 hello 2024-01-04 04:48:35.687558 2024-01-04 04:48:35.687558
419 | 3 hello update 2024-01-04 04:53:37.714473 2024-01-04 04:53:37.714473
420 | 4 hello create 2024-01-04 04:55:03.747616 2024-01-04 04:55:03.747616
421 | 5 hello create 2024-01-04 07:05:07.177812 2024-01-04 07:05:07.177812
422 | \.
423 |
424 |
425 | --
426 | -- Data for Name: todo_list; Type: TABLE DATA; Schema: public; Owner: taidev
427 | --
428 |
429 | COPY public.todo_list (id, title, user_id, created_at, updated_at) FROM stdin;
430 | 1 Work 1 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
431 | 2 Personal 2 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
432 | 3 Shopping 1 2024-01-03 16:56:35.114859 2024-01-03 16:56:35.114859
433 | \.
434 |
435 |
436 | --
437 | -- Data for Name: todo_list_label; Type: TABLE DATA; Schema: public; Owner: taidev
438 | --
439 |
440 | COPY public.todo_list_label (todo_list_id, label_id, due_date) FROM stdin;
441 | \.
442 |
443 |
444 | --
445 | -- Data for Name: user; Type: TABLE DATA; Schema: public; Owner: taidev
446 | --
447 |
448 | COPY public."user" (id, username, email, password, status, created_at, updated_at) FROM stdin;
449 | 1 Tai\n user1@email.com password1 10 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
450 | 2 Hào user2@email.com password2 10 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
451 | 3 Tấn user3@email.com password3 10 2024-01-03 16:20:54.969231 2024-01-03 16:20:54.969231
452 | 5 user5 user5@email.com password5 10 2024-01-04 04:48:35.687558 2024-01-04 04:48:35.687558
453 | 4 user442 user4@email.com password4 10 2024-01-04 04:46:27.636599 2024-01-04 04:46:27.636599
454 | 6 user5 user5@email.com password5 10 2024-01-04 04:55:03.747616 2024-01-04 04:55:03.747616
455 | 7 user6 user6111@email.com password6 10 2024-01-04 07:05:07.177812 2024-01-04 07:05:07.177812
456 | \.
457 |
458 |
459 | --
460 | -- Name: label_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
461 | --
462 |
463 | SELECT pg_catalog.setval('public.label_id_seq', 5, true);
464 |
465 |
466 | --
467 | -- Name: todo_list_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
468 | --
469 |
470 | SELECT pg_catalog.setval('public.todo_list_id_seq', 3, true);
471 |
472 |
473 | --
474 | -- Name: user_id_seq; Type: SEQUENCE SET; Schema: public; Owner: taidev
475 | --
476 |
477 | SELECT pg_catalog.setval('public.user_id_seq', 7, true);
478 |
479 |
480 | --
481 | -- Name: label label_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
482 | --
483 |
484 | ALTER TABLE ONLY public.label
485 | ADD CONSTRAINT label_pkey PRIMARY KEY (id);
486 |
487 |
488 | --
489 | -- Name: todo_list_label todo_list_label_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
490 | --
491 |
492 | ALTER TABLE ONLY public.todo_list_label
493 | ADD CONSTRAINT todo_list_label_pkey PRIMARY KEY (todo_list_id, label_id);
494 |
495 |
496 | --
497 | -- Name: todo_list todo_list_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
498 | --
499 |
500 | ALTER TABLE ONLY public.todo_list
501 | ADD CONSTRAINT todo_list_pkey PRIMARY KEY (id);
502 |
503 |
504 | --
505 | -- Name: user user_pkey; Type: CONSTRAINT; Schema: public; Owner: taidev
506 | --
507 |
508 | ALTER TABLE ONLY public."user"
509 | ADD CONSTRAINT user_pkey PRIMARY KEY (id);
510 |
511 |
512 | --
513 | -- Name: user trg_insert_hello_label_after_user_change; Type: TRIGGER; Schema: public; Owner: taidev
514 | --
515 |
516 | CREATE TRIGGER trg_insert_hello_label_after_user_change AFTER INSERT OR UPDATE ON public."user" FOR EACH ROW EXECUTE FUNCTION public.insert_hello_label_after_user_change();
517 |
518 |
519 | --
520 | -- Name: todo_list_label todo_list_label_label_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
521 | --
522 |
523 | ALTER TABLE ONLY public.todo_list_label
524 | ADD CONSTRAINT todo_list_label_label_id_fkey FOREIGN KEY (label_id) REFERENCES public.label(id);
525 |
526 |
527 | --
528 | -- Name: todo_list_label todo_list_label_todo_list_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
529 | --
530 |
531 | ALTER TABLE ONLY public.todo_list_label
532 | ADD CONSTRAINT todo_list_label_todo_list_id_fkey FOREIGN KEY (todo_list_id) REFERENCES public.todo_list(id);
533 |
534 |
535 | --
536 | -- Name: todo_list todo_list_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: taidev
537 | --
538 |
539 | ALTER TABLE ONLY public.todo_list
540 | ADD CONSTRAINT todo_list_user_id_fkey FOREIGN KEY (user_id) REFERENCES public."user"(id);
541 |
542 |
543 | --
544 | -- Name: SCHEMA public; Type: ACL; Schema: -; Owner: taidev
545 | --
546 |
547 | REVOKE USAGE ON SCHEMA public FROM PUBLIC;
548 |
549 |
550 | --
551 | -- PostgreSQL database dump complete
552 | --
553 |
554 | --
555 | -- PostgreSQL database cluster dump complete
556 | --
557 |
558 |
--------------------------------------------------------------------------------
/src/commons/utils/statusCodes.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | module.exports = {
3 | /**
4 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1
5 | *
6 | * This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.
7 | */
8 | CONTINUE: 100,
9 | /**
10 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2
11 | *
12 | * This code is sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too.
13 | */
14 | SWITCHING_PROTOCOLS: 101,
15 | /**
16 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.1
17 | *
18 | * This code indicates that the server has received and is processing the request, but no response is available yet.
19 | */
20 | PROCESSING: 102,
21 | /**
22 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1
23 | *
24 | * The request has succeeded. The meaning of a success varies depending on the HTTP method:
25 | * GET: The resource has been fetched and is transmitted in the message body.
26 | * HEAD: The entity headers are in the message body.
27 | * POST: The resource describing the result of the action is transmitted in the message body.
28 | * TRACE: The message body contains the request message as received by the server
29 | */
30 | OK: 200,
31 | /**
32 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2
33 | *
34 | * The request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request.
35 | */
36 | CREATED: 201,
37 | /**
38 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.3
39 | *
40 | * The request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing.
41 | */
42 | ACCEPTED: 202,
43 | /**
44 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4
45 | *
46 | * This response code means returned meta-information set is not exact set as available from the origin server, but collected from a local or a third party copy. Except this condition, 200 OK response should be preferred instead of this response.
47 | */
48 | NON_AUTHORITATIVE_INFORMATION: 203,
49 | /**
50 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5
51 | *
52 | * There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones.
53 | */
54 | NO_CONTENT: 204,
55 | /**
56 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6
57 | *
58 | * This response code is sent after accomplishing request to tell user agent reset document view which sent this request.
59 | */
60 | RESET_CONTENT: 205,
61 | /**
62 | * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.1
63 | *
64 | * This response code is used because of range header sent by the client to separate download into multiple streams.
65 | */
66 | PARTIAL_CONTENT: 206,
67 | /**
68 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.2
69 | *
70 | * A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate.
71 | */
72 | MULTI_STATUS: 207,
73 | /**
74 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.1
75 | *
76 | * The request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses.
77 | */
78 | MULTIPLE_CHOICES: 300,
79 | /**
80 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.2
81 | *
82 | * This response code means that URI of requested resource has been changed. Probably, new URI would be given in the response.
83 | */
84 | MOVED_PERMANENTLY: 301,
85 | /**
86 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.3
87 | *
88 | * This response code means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests.
89 | */
90 | MOVED_TEMPORARILY: 302,
91 | /**
92 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.4
93 | *
94 | * Server sent this response to directing client to get requested resource to another URI with an GET request.
95 | */
96 | SEE_OTHER: 303,
97 | /**
98 | * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1
99 | *
100 | * This is used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response.
101 | */
102 | NOT_MODIFIED: 304,
103 | /**
104 | * @deprecated
105 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.6
106 | *
107 | * Was defined in a previous version of the HTTP specification to indicate that a requested response must be accessed by a proxy. It has been deprecated due to security concerns regarding in-band configuration of a proxy.
108 | */
109 | USE_PROXY: 305,
110 | /**
111 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.7
112 | *
113 | * Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.
114 | */
115 | TEMPORARY_REDIRECT: 307,
116 | /**
117 | * Official Documentation @ https://tools.ietf.org/html/rfc7538#section-3
118 | *
119 | * This means that the resource is now permanently located at another URI, specified by the Location: HTTP Response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.
120 | */
121 | PERMANENT_REDIRECT: 308,
122 | /**
123 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.1
124 | *
125 | * This response means that server could not understand the request due to invalid syntax.
126 | */
127 | BAD_REQUEST: 400,
128 | /**
129 | * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1
130 | *
131 | * Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response.
132 | */
133 | UNAUTHORIZED: 401,
134 | /**
135 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.2
136 | *
137 | * This response code is reserved for future use. Initial aim for creating this code was using it for digital payment systems however this is not used currently.
138 | */
139 | PAYMENT_REQUIRED: 402,
140 | /**
141 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3
142 | *
143 | * The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to give proper response. Unlike 401, the client's identity is known to the server.
144 | */
145 | FORBIDDEN: 403,
146 | /**
147 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.4
148 | *
149 | * The server can not find requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurence on the web.
150 | */
151 | NOT_FOUND: 404,
152 | /**
153 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5
154 | *
155 | * The request method is known by the server but has been disabled and cannot be used. For example, an API may forbid DELETE-ing a resource. The two mandatory methods, GET and HEAD, must never be disabled and should not return this error code.
156 | */
157 | METHOD_NOT_ALLOWED: 405,
158 | /**
159 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.6
160 | *
161 | * This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent.
162 | */
163 | NOT_ACCEPTABLE: 406,
164 | /**
165 | * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.2
166 | *
167 | * This is similar to 401 but authentication is needed to be done by a proxy.
168 | */
169 | PROXY_AUTHENTICATION_REQUIRED: 407,
170 | /**
171 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7
172 | *
173 | * This response is sent on an idle connection by some servers, even without any previous request by the client. It means that the server would like to shut down this unused connection. This response is used much more since some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also note that some servers merely shut down the connection without sending this message.
174 | */
175 | REQUEST_TIMEOUT: 408,
176 | /**
177 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.8
178 | *
179 | * This response is sent when a request conflicts with the current state of the server.
180 | */
181 | CONFLICT: 409,
182 | /**
183 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9
184 | *
185 | * This response would be sent when the requested content has been permenantly deleted from server, with no forwarding address. Clients are expected to remove their caches and links to the resource. The HTTP specification intends this status code to be used for "limited-time, promotional services". APIs should not feel compelled to indicate resources that have been deleted with this status code.
186 | */
187 | GONE: 410,
188 | /**
189 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.10
190 | *
191 | * The server rejected the request because the Content-Length header field is not defined and the server requires it.
192 | */
193 | LENGTH_REQUIRED: 411,
194 | /**
195 | * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.2
196 | *
197 | * The client has indicated preconditions in its headers which the server does not meet.
198 | */
199 | PRECONDITION_FAILED: 412,
200 | /**
201 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11
202 | *
203 | * Request entity is larger than limits defined by server; the server might close the connection or return an Retry-After header field.
204 | */
205 | REQUEST_TOO_LONG: 413,
206 | /**
207 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.12
208 | *
209 | * The URI requested by the client is longer than the server is willing to interpret.
210 | */
211 | REQUEST_URI_TOO_LONG: 414,
212 | /**
213 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13
214 | *
215 | * The media format of the requested data is not supported by the server, so the server is rejecting the request.
216 | */
217 | UNSUPPORTED_MEDIA_TYPE: 415,
218 | /**
219 | * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.4
220 | *
221 | * The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data.
222 | */
223 | REQUESTED_RANGE_NOT_SATISFIABLE: 416,
224 | /**
225 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.14
226 | *
227 | * This response code means the expectation indicated by the Expect request header field can't be met by the server.
228 | */
229 | EXPECTATION_FAILED: 417,
230 | /**
231 | * Official Documentation @ https://tools.ietf.org/html/rfc2324#section-2.3.2
232 | *
233 | * Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout.
234 | */
235 | IM_A_TEAPOT: 418,
236 | /**
237 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
238 | *
239 | * The 507 (Insufficient Storage) status code means the method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. This condition is considered to be temporary. If the request which received this status code was the result of a user action, the request MUST NOT be repeated until it is requested by a separate user action.
240 | */
241 | INSUFFICIENT_SPACE_ON_RESOURCE: 419,
242 | /**
243 | * @deprecated
244 | * Official Documentation @ https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt
245 | *
246 | * A deprecated response used by the Spring Framework when a method has failed.
247 | */
248 | METHOD_FAILURE: 420,
249 | /**
250 | * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.2
251 | *
252 | * Defined in the specification of HTTP/2 to indicate that a server is not able to produce a response for the combination of scheme and authority that are included in the request URI.
253 | */
254 | MISDIRECTED_REQUEST: 421,
255 | /**
256 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3
257 | *
258 | * The request was well-formed but was unable to be followed due to semantic errors.
259 | */
260 | UNPROCESSABLE_ENTITY: 422,
261 | /**
262 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.4
263 | *
264 | * The resource that is being accessed is locked.
265 | */
266 | LOCKED: 423,
267 | /**
268 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.5
269 | *
270 | * The request failed due to failure of a previous request.
271 | */
272 | FAILED_DEPENDENCY: 424,
273 | /**
274 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-3
275 | *
276 | * The origin server requires the request to be conditional. Intended to prevent the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict.
277 | */
278 | PRECONDITION_REQUIRED: 428,
279 | /**
280 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4
281 | *
282 | * The user has sent too many requests in a given amount of time ("rate limiting").
283 | */
284 | TOO_MANY_REQUESTS: 429,
285 | /**
286 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5
287 | *
288 | * The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields.
289 | */
290 | REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
291 | /**
292 | * Official Documentation @ https://tools.ietf.org/html/rfc7725
293 | *
294 | * The user-agent requested a resource that cannot legally be provided, such as a web page censored by a government.
295 | */
296 | UNAVAILABLE_FOR_LEGAL_REASONS: 451,
297 | /**
298 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.1
299 | *
300 | * The server encountered an unexpected condition that prevented it from fulfilling the request.
301 | */
302 | INTERNAL_SERVER_ERROR: 500,
303 | /**
304 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2
305 | *
306 | * The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD.
307 | */
308 | NOT_IMPLEMENTED: 501,
309 | /**
310 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.3
311 | *
312 | * This error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response.
313 | */
314 | BAD_GATEWAY: 502,
315 | /**
316 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.4
317 | *
318 | * The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This responses should be used for temporary conditions and the Retry-After: HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached.
319 | */
320 | SERVICE_UNAVAILABLE: 503,
321 | /**
322 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.5
323 | *
324 | * This error response is given when the server is acting as a gateway and cannot get a response in time.
325 | */
326 | GATEWAY_TIMEOUT: 504,
327 | /**
328 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.6
329 | *
330 | * The HTTP version used in the request is not supported by the server.
331 | */
332 | HTTP_VERSION_NOT_SUPPORTED: 505,
333 | /**
334 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
335 | *
336 | * The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process.
337 | */
338 | INSUFFICIENT_STORAGE: 507,
339 | /**
340 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-6
341 | *
342 | * The 511 status code indicates that the client needs to authenticate to gain network access.
343 | */
344 | NETWORK_AUTHENTICATION_REQUIRED: 511,
345 | };
346 |
--------------------------------------------------------------------------------
/src/commons/utils/reasonPhrases.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | module.exports = {
3 | /**
4 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.3
5 | *
6 | * The request has been received but not yet acted upon. It is non-committal, meaning that there is no way in HTTP to later send an asynchronous response indicating the outcome of processing the request. It is intended for cases where another process or server handles the request, or for batch processing.
7 | */
8 | ACCEPTED: "Accepted",
9 | /**
10 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.3
11 | *
12 | * This error response means that the server, while working as a gateway to get a response needed to handle the request, got an invalid response.
13 | */
14 | BAD_GATEWAY: "Bad Gateway",
15 | /**
16 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.1
17 | *
18 | * This response means that server could not understand the request due to invalid syntax.
19 | */
20 | BAD_REQUEST: "Bad Request",
21 | /**
22 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.8
23 | *
24 | * This response is sent when a request conflicts with the current state of the server.
25 | */
26 | CONFLICT: "Conflict",
27 | /**
28 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.1
29 | *
30 | * This interim response indicates that everything so far is OK and that the client should continue with the request or ignore it if it is already finished.
31 | */
32 | CONTINUE: "Continue",
33 | /**
34 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.2
35 | *
36 | * The request has succeeded and a new resource has been created as a result of it. This is typically the response sent after a PUT request.
37 | */
38 | CREATED: "Created",
39 | /**
40 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.14
41 | *
42 | * This response code means the expectation indicated by the Expect request header field can't be met by the server.
43 | */
44 | EXPECTATION_FAILED: "Expectation Failed",
45 | /**
46 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.5
47 | *
48 | * The request failed due to failure of a previous request.
49 | */
50 | FAILED_DEPENDENCY: "Failed Dependency",
51 | /**
52 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.3
53 | *
54 | * The client does not have access rights to the content, i.e. they are unauthorized, so server is rejecting to give proper response. Unlike 401, the client's identity is known to the server.
55 | */
56 | FORBIDDEN: "Forbidden",
57 | /**
58 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.5
59 | *
60 | * This error response is given when the server is acting as a gateway and cannot get a response in time.
61 | */
62 | GATEWAY_TIMEOUT: "Gateway Timeout",
63 | /**
64 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.9
65 | *
66 | * This response would be sent when the requested content has been permenantly deleted from server, with no forwarding address. Clients are expected to remove their caches and links to the resource. The HTTP specification intends this status code to be used for "limited-time, promotional services". APIs should not feel compelled to indicate resources that have been deleted with this status code.
67 | */
68 | GONE: "Gone",
69 | /**
70 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.6
71 | *
72 | * The HTTP version used in the request is not supported by the server.
73 | */
74 | HTTP_VERSION_NOT_SUPPORTED: "HTTP Version Not Supported",
75 | /**
76 | * Official Documentation @ https://tools.ietf.org/html/rfc2324#section-2.3.2
77 | *
78 | * Any attempt to brew coffee with a teapot should result in the error code "418 I'm a teapot". The resulting entity body MAY be short and stout.
79 | */
80 | IM_A_TEAPOT: "I am a teapot",
81 | /**
82 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
83 | *
84 | * The 507 (Insufficient Storage) status code means the method could not be performed on the resource because the server is unable to store the representation needed to successfully complete the request. This condition is considered to be temporary. If the request which received this status code was the result of a user action, the request MUST NOT be repeated until it is requested by a separate user action.
85 | */
86 | INSUFFICIENT_SPACE_ON_RESOURCE: "Insufficient Space on Resource",
87 | /**
88 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.6
89 | *
90 | * The server has an internal configuration error: the chosen variant resource is configured to engage in transparent content negotiation itself, and is therefore not a proper end point in the negotiation process.
91 | */
92 | INSUFFICIENT_STORAGE: "Insufficient Storage",
93 | /**
94 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.1
95 | *
96 | * The server encountered an unexpected condition that prevented it from fulfilling the request.
97 | */
98 | INTERNAL_SERVER_ERROR: "Internal Server Error",
99 | /**
100 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.10
101 | *
102 | * The server rejected the request because the Content-Length header field is not defined and the server requires it.
103 | */
104 | LENGTH_REQUIRED: "Length Required",
105 | /**
106 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.4
107 | *
108 | * The resource that is being accessed is locked.
109 | */
110 | LOCKED: "Locked",
111 | /**
112 | * @deprecated
113 | * Official Documentation @ https://tools.ietf.org/rfcdiff?difftype=--hwdiff&url2=draft-ietf-webdav-protocol-06.txt
114 | *
115 | * A deprecated response used by the Spring Framework when a method has failed.
116 | */
117 | METHOD_FAILURE: "Method Failure",
118 | /**
119 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.5
120 | *
121 | * The request method is known by the server but has been disabled and cannot be used. For example, an API may forbid DELETE-ing a resource. The two mandatory methods, GET and HEAD, must never be disabled and should not return this error code.
122 | */
123 | METHOD_NOT_ALLOWED: "Method Not Allowed",
124 | /**
125 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.2
126 | *
127 | * This response code means that URI of requested resource has been changed. Probably, new URI would be given in the response.
128 | */
129 | MOVED_PERMANENTLY: "Moved Permanently",
130 | /**
131 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.3
132 | *
133 | * This response code means that URI of requested resource has been changed temporarily. New changes in the URI might be made in the future. Therefore, this same URI should be used by the client in future requests.
134 | */
135 | MOVED_TEMPORARILY: "Moved Temporarily",
136 | /**
137 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.2
138 | *
139 | * A Multi-Status response conveys information about multiple resources in situations where multiple status codes might be appropriate.
140 | */
141 | MULTI_STATUS: "Multi-Status",
142 | /**
143 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.1
144 | *
145 | * The request has more than one possible responses. User-agent or user should choose one of them. There is no standardized way to choose one of the responses.
146 | */
147 | MULTIPLE_CHOICES: "Multiple Choices",
148 | /**
149 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-6
150 | *
151 | * The 511 status code indicates that the client needs to authenticate to gain network access.
152 | */
153 | NETWORK_AUTHENTICATION_REQUIRED: "Network Authentication Required",
154 | /**
155 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.5
156 | *
157 | * There is no content to send for this request, but the headers may be useful. The user-agent may update its cached headers for this resource with the new ones.
158 | */
159 | NO_CONTENT: "No Content",
160 | /**
161 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.4
162 | *
163 | * This response code means returned meta-information set is not exact set as available from the origin server, but collected from a local or a third party copy. Except this condition, 200 OK response should be preferred instead of this response.
164 | */
165 | NON_AUTHORITATIVE_INFORMATION: "Non Authoritative Information",
166 | /**
167 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.6
168 | *
169 | * This response is sent when the web server, after performing server-driven content negotiation, doesn't find any content following the criteria given by the user agent.
170 | */
171 | NOT_ACCEPTABLE: "Not Acceptable",
172 | /**
173 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.4
174 | *
175 | * The server can not find requested resource. In the browser, this means the URL is not recognized. In an API, this can also mean that the endpoint is valid but the resource itself does not exist. Servers may also send this response instead of 403 to hide the existence of a resource from an unauthorized client. This response code is probably the most famous one due to its frequent occurence on the web.
176 | */
177 | NOT_FOUND: "Not Found",
178 | /**
179 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.2
180 | *
181 | * The request method is not supported by the server and cannot be handled. The only methods that servers are required to support (and therefore that must not return this code) are GET and HEAD.
182 | */
183 | NOT_IMPLEMENTED: "Not Implemented",
184 | /**
185 | * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.1
186 | *
187 | * This is used for caching purposes. It is telling to client that response has not been modified. So, client can continue to use same cached version of response.
188 | */
189 | NOT_MODIFIED: "Not Modified",
190 | /**
191 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.1
192 | *
193 | * The request has succeeded. The meaning of a success varies depending on the HTTP method:
194 | * GET: The resource has been fetched and is transmitted in the message body.
195 | * HEAD: The entity headers are in the message body.
196 | * POST: The resource describing the result of the action is transmitted in the message body.
197 | * TRACE: The message body contains the request message as received by the server
198 | */
199 | OK: "OK",
200 | /**
201 | * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.1
202 | *
203 | * This response code is used because of range header sent by the client to separate download into multiple streams.
204 | */
205 | PARTIAL_CONTENT: "Partial Content",
206 | /**
207 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.2
208 | *
209 | * This response code is reserved for future use. Initial aim for creating this code was using it for digital payment systems however this is not used currently.
210 | */
211 | PAYMENT_REQUIRED: "Payment Required",
212 | /**
213 | * Official Documentation @ https://tools.ietf.org/html/rfc7538#section-3
214 | *
215 | * This means that the resource is now permanently located at another URI, specified by the Location: HTTP Response header. This has the same semantics as the 301 Moved Permanently HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.
216 | */
217 | PERMANENT_REDIRECT: "Permanent Redirect",
218 | /**
219 | * Official Documentation @ https://tools.ietf.org/html/rfc7232#section-4.2
220 | *
221 | * The client has indicated preconditions in its headers which the server does not meet.
222 | */
223 | PRECONDITION_FAILED: "Precondition Failed",
224 | /**
225 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-3
226 | *
227 | * The origin server requires the request to be conditional. Intended to prevent the 'lost update' problem, where a client GETs a resource's state, modifies it, and PUTs it back to the server, when meanwhile a third party has modified the state on the server, leading to a conflict.
228 | */
229 | PRECONDITION_REQUIRED: "Precondition Required",
230 | /**
231 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.1
232 | *
233 | * This code indicates that the server has received and is processing the request, but no response is available yet.
234 | */
235 | PROCESSING: "Processing",
236 | /**
237 | * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.2
238 | *
239 | * This is similar to 401 but authentication is needed to be done by a proxy.
240 | */
241 | PROXY_AUTHENTICATION_REQUIRED: "Proxy Authentication Required",
242 | /**
243 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-5
244 | *
245 | * The server is unwilling to process the request because its header fields are too large. The request MAY be resubmitted after reducing the size of the request header fields.
246 | */
247 | REQUEST_HEADER_FIELDS_TOO_LARGE: "Request Header Fields Too Large",
248 | /**
249 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.7
250 | *
251 | * This response is sent on an idle connection by some servers, even without any previous request by the client. It means that the server would like to shut down this unused connection. This response is used much more since some browsers, like Chrome, Firefox 27+, or IE9, use HTTP pre-connection mechanisms to speed up surfing. Also note that some servers merely shut down the connection without sending this message.
252 | */
253 | REQUEST_TIMEOUT: "Request Timeout",
254 | /**
255 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.11
256 | *
257 | * Request entity is larger than limits defined by server; the server might close the connection or return an Retry-After header field.
258 | */
259 | REQUEST_TOO_LONG: "Request Entity Too Large",
260 | /**
261 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.12
262 | *
263 | * The URI requested by the client is longer than the server is willing to interpret.
264 | */
265 | REQUEST_URI_TOO_LONG: "Request-URI Too Long",
266 | /**
267 | * Official Documentation @ https://tools.ietf.org/html/rfc7233#section-4.4
268 | *
269 | * The range specified by the Range header field in the request can't be fulfilled; it's possible that the range is outside the size of the target URI's data.
270 | */
271 | REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range Not Satisfiable",
272 | /**
273 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.3.6
274 | *
275 | * This response code is sent after accomplishing request to tell user agent reset document view which sent this request.
276 | */
277 | RESET_CONTENT: "Reset Content",
278 | /**
279 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.4
280 | *
281 | * Server sent this response to directing client to get requested resource to another URI with an GET request.
282 | */
283 | SEE_OTHER: "See Other",
284 | /**
285 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.6.4
286 | *
287 | * The server is not ready to handle the request. Common causes are a server that is down for maintenance or that is overloaded. Note that together with this response, a user-friendly page explaining the problem should be sent. This responses should be used for temporary conditions and the Retry-After: HTTP header should, if possible, contain the estimated time before the recovery of the service. The webmaster must also take care about the caching-related headers that are sent along with this response, as these temporary condition responses should usually not be cached.
288 | */
289 | SERVICE_UNAVAILABLE: "Service Unavailable",
290 | /**
291 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.2.2
292 | *
293 | * This code is sent in response to an Upgrade request header by the client, and indicates the protocol the server is switching too.
294 | */
295 | SWITCHING_PROTOCOLS: "Switching Protocols",
296 | /**
297 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.7
298 | *
299 | * Server sent this response to directing client to get requested resource to another URI with same method that used prior request. This has the same semantic than the 302 Found HTTP response code, with the exception that the user agent must not change the HTTP method used: if a POST was used in the first request, a POST must be used in the second request.
300 | */
301 | TEMPORARY_REDIRECT: "Temporary Redirect",
302 | /**
303 | * Official Documentation @ https://tools.ietf.org/html/rfc6585#section-4
304 | *
305 | * The user has sent too many requests in a given amount of time ("rate limiting").
306 | */
307 | TOO_MANY_REQUESTS: "Too Many Requests",
308 | /**
309 | * Official Documentation @ https://tools.ietf.org/html/rfc7235#section-3.1
310 | *
311 | * Although the HTTP standard specifies "unauthorized", semantically this response means "unauthenticated". That is, the client must authenticate itself to get the requested response.
312 | */
313 | UNAUTHORIZED: "Unauthorized",
314 | /**
315 | * Official Documentation @ https://tools.ietf.org/html/rfc7725
316 | *
317 | * The user-agent requested a resource that cannot legally be provided, such as a web page censored by a government.
318 | */
319 | UNAVAILABLE_FOR_LEGAL_REASONS: "Unavailable For Legal Reasons",
320 | /**
321 | * Official Documentation @ https://tools.ietf.org/html/rfc2518#section-10.3
322 | *
323 | * The request was well-formed but was unable to be followed due to semantic errors.
324 | */
325 | UNPROCESSABLE_ENTITY: "Unprocessable Entity",
326 | /**
327 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.5.13
328 | *
329 | * The media format of the requested data is not supported by the server, so the server is rejecting the request.
330 | */
331 | UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
332 | /**
333 | * @deprecated
334 | * Official Documentation @ https://tools.ietf.org/html/rfc7231#section-6.4.6
335 | *
336 | * Was defined in a previous version of the HTTP specification to indicate that a requested response must be accessed by a proxy. It has been deprecated due to security concerns regarding in-band configuration of a proxy.
337 | */
338 | USE_PROXY: "Use Proxy",
339 | /**
340 | * Official Documentation @ https://datatracker.ietf.org/doc/html/rfc7540#section-9.1.2
341 | *
342 | * Defined in the specification of HTTP/2 to indicate that a server is not able to produce a response for the combination of scheme and authority that are included in the request URI.
343 | */
344 | MISDIRECTED_REQUEST: "Misdirected Request",
345 | };
346 |
--------------------------------------------------------------------------------