├── 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 | Linkedin 5 | Profile 6 | Phone 7 | License 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 | ![giphy](https://3.bp.blogspot.com/-SzGvXn2sTmw/V6k-90GH3ZI/AAAAAAAAIsk/Q678Pil-0kITLPa3fD--JkNdnJVKi_BygCLcB/s1600/cf10-fbc08%2B%25281%2529.gif) 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 |
58 | Tai Developer 61 |
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 | Sử dụng liên kết này để đặt lại mật khẩu của bạn. Liên kết chỉ có hiệu 435 | lực trong vòng 15 phút. 437 | 444 | 445 | 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 | --------------------------------------------------------------------------------