├── app ├── middlewares │ ├── index.js │ ├── authMongo.js │ └── authJwt.js ├── config │ ├── server.config.js │ ├── auth.config.js │ ├── index.js │ └── db.config.js ├── models │ ├── index.js │ ├── user.model.js │ ├── badword.model.js │ └── contribute.model.js ├── routes │ ├── auth.route.js │ ├── contribute.route.js │ ├── badword.route.js │ ├── db.route.js │ └── cache.route.js ├── controllers │ ├── user.controller.js │ ├── auth.controller.js │ ├── contribute.controller.js │ ├── redis.controller.js │ └── badword.controller.js └── utils │ └── fileparser.js ├── vercel.json ├── .env.public ├── package.json ├── CONTRIBUTING.md ├── index.js ├── .gitignore ├── README.md ├── README-API.md └── APIs.md /app/middlewares/index.js: -------------------------------------------------------------------------------- 1 | const authJwt = require("./authJwt"); 2 | 3 | module.exports = { 4 | authJwt, 5 | authMongo 6 | }; 7 | -------------------------------------------------------------------------------- /app/config/server.config.js: -------------------------------------------------------------------------------- 1 | var dotent = require('dotenv'); 2 | 3 | const port = process.env.PORT; 4 | 5 | module.exports = { 6 | PORT: port, 7 | }; 8 | -------------------------------------------------------------------------------- /app/config/auth.config.js: -------------------------------------------------------------------------------- 1 | var dotent = require('dotenv'); 2 | 3 | const secretJwt = process.env.SECRET_JWT; 4 | 5 | module.exports = { 6 | SECRET_JWT: secretJwt, 7 | }; 8 | -------------------------------------------------------------------------------- /app/models/index.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | mongoose.Promise = global.Promise; 3 | 4 | const db = {}; 5 | 6 | db.mongoose = mongoose; 7 | 8 | module.exports = db; -------------------------------------------------------------------------------- /app/config/index.js: -------------------------------------------------------------------------------- 1 | const serverConfig = require('./server.config'); 2 | const authConfig = require('./auth.config'); 3 | const dbConfig = require('./db.config'); 4 | var dotent = require('dotenv'); 5 | 6 | module.exports = { 7 | serverConfig, 8 | authConfig, 9 | dbConfig, 10 | }; -------------------------------------------------------------------------------- /app/config/db.config.js: -------------------------------------------------------------------------------- 1 | var dotent = require('dotenv'); 2 | 3 | const redisURI = process.env.REDIS_URI; 4 | const mongodbURI = process.env.MONGODB_URI; 5 | const prefix = process.env.PREFIX; 6 | 7 | module.exports = { 8 | MONGO_URI: mongodbURI, 9 | REDIS_URI: redisURI, 10 | PREFIX_CACHE: prefix 11 | }; -------------------------------------------------------------------------------- /vercel.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 2, 3 | "builds": [ 4 | { 5 | "src": "./index.js", 6 | "use": "@vercel/node" 7 | } 8 | ], 9 | "routes": [ 10 | { 11 | "src": "/(.*)", 12 | "dest": "/" 13 | }, 14 | { 15 | "src": "/api/(.*)", 16 | "dest": "./index.js" 17 | } 18 | ] 19 | } -------------------------------------------------------------------------------- /.env.public: -------------------------------------------------------------------------------- 1 | PORT=8080 2 | SECRET_JWT=#########-davi-root 3 | MONGODB_URI=mongodb+srv://##############/moderator?retryWrites=true&w=majority 4 | MONGODB_DB=moderator 5 | MONGODB_COLLECTION_BADWORDS=badwords 6 | REDIS_URI=##############@singapore-redis.render.com:6379 7 | REDIS_URI_BACKUP=##############@singapore-redis.render.com:6379 8 | PREFIX=bw::: -------------------------------------------------------------------------------- /app/routes/auth.route.js: -------------------------------------------------------------------------------- 1 | const controller = require("../controllers/auth.controller"); 2 | 3 | module.exports = function (app) { 4 | app.use(function (req, res, next) { 5 | res.header( 6 | "Access-Control-Allow-Headers", 7 | "Origin, Content-Type, Accept" 8 | ); 9 | next(); 10 | }); 11 | 12 | app.post("/api/auth/signin", controller.signin); 13 | app.post("/api/auth/signout", controller.signout); 14 | }; 15 | -------------------------------------------------------------------------------- /app/models/user.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const userSchema = new mongoose.Schema({ 4 | createDate: { 5 | type: Date 6 | }, 7 | username: { 8 | type: String 9 | }, 10 | fullname: { 11 | type: String 12 | }, 13 | password: { 14 | type: String 15 | }, 16 | roles: { 17 | type: Array 18 | }, 19 | token: { 20 | type: String 21 | } 22 | }); 23 | 24 | 25 | const User = mongoose.model('users', userSchema); 26 | 27 | module.exports = User; -------------------------------------------------------------------------------- /app/routes/contribute.route.js: -------------------------------------------------------------------------------- 1 | const controller = require("../controllers/contribute.controller"); 2 | 3 | module.exports = function (app) { 4 | app.use(function (req, res, next) { 5 | res.header( 6 | "Access-Control-Allow-Headers", 7 | "Origin, Content-Type, Accept" 8 | ); 9 | next(); 10 | }); 11 | 12 | app.get("/api/contributes", controller.getAll); 13 | app.post("/api/contribute", controller.save); 14 | app.post("/api/contribute/badword", controller.contributeBadword); 15 | app.get("/api/contribute/push", controller.addContributeToDB); //un=dangth&tk= 16 | }; 17 | -------------------------------------------------------------------------------- /app/models/badword.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | 3 | const badwordSchema = new mongoose.Schema({ 4 | _id: { 5 | type: mongoose.Types.ObjectId, 6 | default: mongoose.Types.ObjectId, // Đảm bảo sử dụng một giá trị mặc định 7 | }, 8 | createDate: { 9 | type: Date 10 | }, 11 | label: { 12 | type: Number 13 | }, 14 | name: { 15 | type: String 16 | }, 17 | severityLevel: { 18 | type: Number 19 | }, 20 | deleted: { 21 | type: Boolean 22 | } 23 | }); 24 | 25 | 26 | const Badword = mongoose.model('Badword', badwordSchema); 27 | 28 | module.exports = Badword; -------------------------------------------------------------------------------- /app/controllers/user.controller.js: -------------------------------------------------------------------------------- 1 | const Badword = require("../models/badword.model"); 2 | const Users = require("../models/user.model"); 3 | 4 | exports.getAll = async (req, res, redis) => { 5 | const user = await Users.find({ token: req.token }); 6 | console.log(`token: ${req.token} => User: ${user}`); 7 | }; 8 | 9 | exports.addUser = async (req, res, redis) => { 10 | const user = { 11 | createDate: new Date(), 12 | username: "dangth", 13 | fullname: "Trần Hữu Đang", 14 | password: "123", 15 | role: ['MODERATOR', 'ADMIN'], 16 | token: "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9676x56fheiu" 17 | } 18 | 19 | const newUser = new Users(user); 20 | const saveUser = newUser.save(); 21 | 22 | console.log(saveUser) 23 | }; 24 | -------------------------------------------------------------------------------- /app/middlewares/authMongo.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("../config/auth.config.js"); 3 | const db = require("../models"); 4 | const Users = require("../models/user.model.js"); 5 | 6 | verifyToken = async (req, res, next) => { 7 | 8 | const tokenBearer = req.header('Authorization'); 9 | if (!tokenBearer) 10 | return res.status(403).send({ message: "No token provided!" }); 11 | 12 | let token = tokenBearer.substring(7); 13 | 14 | const user = await Users.findOne({ token: token }); 15 | 16 | if (!user) { 17 | return res.status(401).send({ 18 | message: "Unauthorized!", 19 | }); 20 | } 21 | req.roles = user.roles; 22 | next(); 23 | }; 24 | 25 | 26 | const authMongo = { 27 | verifyToken, 28 | }; 29 | module.exports = authMongo; 30 | -------------------------------------------------------------------------------- /app/routes/badword.route.js: -------------------------------------------------------------------------------- 1 | const controller = require("../controllers/badword.controller"); 2 | const auth = require("../middlewares/authJwt"); 3 | 4 | module.exports = function (app, redis, prefix) { 5 | app.use(function (req, res, next) { 6 | 7 | res.header( 8 | "Access-Control-Allow-Headers", 9 | "Origin, Content-Type, Accept" 10 | ); 11 | next(); 12 | }); 13 | 14 | app.get('/api/badwords', auth.isAdmin, (req, res) => controller.getAllBadwords(req, res, redis)); 15 | app.get('/api/badword', (req, res) => controller.getBadwordByName(req, res, redis, prefix)); //api/badword?name=cút 16 | app.post('/api/badwords', (req, res) => controller.checkBadword(req, res, redis)); 17 | app.post('/api/cleanwords', (req, res) => controller.cleanWords(req, res, redis, prefix)); 18 | app.get('/api/cleanwords', (req, res) => controller.cleanWords(req, res, redis, prefix)); //api/cleanword?word=cút 19 | }; 20 | -------------------------------------------------------------------------------- /app/routes/db.route.js: -------------------------------------------------------------------------------- 1 | const controller = require("../controllers/badword.controller"); 2 | const auth = require("../middlewares/authJwt"); 3 | 4 | module.exports = function (app) { 5 | app.use(function (req, res, next) { 6 | 7 | res.header( 8 | "Access-Control-Allow-Headers", 9 | "Origin, Content-Type, Accept" 10 | ); 11 | next(); 12 | }); 13 | //CRUD 14 | app.get('/api/dbs', auth.isAdmin, (req, res) => controller.getAllBadwordFromDB(req, res)); //api/db?name=cút 15 | app.get('/api/db', auth.isModerator, (req, res) => controller.getBadwordFromDbByName(req, res)); //api/db?name=cút 16 | app.post('/api/db', auth.isModerator, (req, res) => controller.postBadwordToDB(req, res)); //api/db 17 | app.put('/api/db', auth.isModerator, (req, res) => controller.updateBadwordInDbByName(req, res)); //api/db 18 | app.delete('/api/db', auth.isModerator, (req, res) => controller.deleteBadwordInDbByName(req, res)); //api/db?name=cút 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "recommender_system", 3 | "version": "1.0.0", 4 | "description": "Recommender System", 5 | "main": "server.js", 6 | "scripts": { 7 | "start": "nodemon index.js", 8 | "dev": "node --watch index.js" 9 | }, 10 | "keywords": [ 11 | "node.js", 12 | "express", 13 | "jwt", 14 | "login", 15 | "registration", 16 | "authentication", 17 | "authorization", 18 | "mongodb", 19 | "mongoose" 20 | ], 21 | "author": "nghiaquach", 22 | "license": "ISC", 23 | "dependencies": { 24 | "@aws-sdk/client-s3": "^3.481.0", 25 | "@aws-sdk/lib-storage": "^3.481.0", 26 | "aws-sdk": "^2.1531.0", 27 | "axios": "^1.6.5", 28 | "bcryptjs": "^2.4.3", 29 | "body-parser": "^1.20.2", 30 | "cheerio": "^1.0.0-rc.12", 31 | "cookie-session": "^2.0.0", 32 | "cors": "^2.8.5", 33 | "dotenv": "^16.3.2", 34 | "express": "^4.18.2", 35 | "formidable": "^3.5.1", 36 | "google-auth-library": "^9.4.1", 37 | "ioredis": "^5.3.2", 38 | "jsonwebtoken": "^9.0.0", 39 | "mongoose": "^6.11.2", 40 | "nodemon": "^3.0.2", 41 | "redis": "^4.6.12", 42 | "socket.io": "^4.7.4" 43 | } 44 | } -------------------------------------------------------------------------------- /app/models/contribute.model.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const User = require('./user.model'); 3 | const Badword = require('./badword.model'); 4 | 5 | const contributeSchema = new mongoose.Schema({ 6 | _id: { 7 | type: mongoose.Types.ObjectId, 8 | default: mongoose.Types.ObjectId, // Đảm bảo sử dụng một giá trị mặc định 9 | }, 10 | createDate: { 11 | type: Date, 12 | default: new Date() 13 | }, 14 | author: { 15 | type: String 16 | }, 17 | message: { 18 | type: String 19 | }, 20 | word: { 21 | type: String 22 | }, 23 | added: { 24 | type: Boolean, 25 | default: false 26 | }, 27 | badword: { 28 | createDate: { 29 | type: Date, 30 | default: new Date() 31 | }, 32 | name: { 33 | type: String, 34 | default: this.word 35 | }, 36 | label: { 37 | type: Number, 38 | default: 1 39 | }, 40 | severityLevel: { 41 | type: Number, 42 | default: 1 43 | }, 44 | deleted: { 45 | type: Boolean, 46 | default: false 47 | } 48 | }, 49 | }); 50 | 51 | 52 | const Contribute = mongoose.model('contribute', contributeSchema); 53 | 54 | module.exports = Contribute; -------------------------------------------------------------------------------- /app/controllers/auth.controller.js: -------------------------------------------------------------------------------- 1 | const config = require("../config/auth.config"); 2 | var jwt = require("jsonwebtoken"); 3 | const User = require("../models/user.model"); 4 | 5 | exports.signin = async (req, res) => { 6 | try { 7 | let username = req.body.username; 8 | let password = req.body.password; 9 | 10 | const user = await User.findOne({ username: username }); 11 | //console.log("currentUser", user); 12 | 13 | if (!user) { 14 | return res.status(400).json({ 15 | message: "You are not allowed to access the system. Please contact administrator", 16 | }); 17 | } 18 | else { 19 | if (user.password === password) 20 | return res.status(201).json({ 21 | message: "Login was successful", 22 | user: { 23 | fullname: user?.fullname, 24 | username: user?.username, 25 | token: jwt.sign({ username: user?.username, role: user?.role }, secretJwt, { 26 | expiresIn: "1d", 27 | }), 28 | }, 29 | }); 30 | return res.status(403).json({ 31 | message: "Login was failed, username or password not same with our system!", 32 | }); 33 | } 34 | } catch (error) { 35 | console.log(error); 36 | res.status(500).json({ 37 | message: error?.message || error, 38 | }); 39 | } 40 | }; 41 | 42 | exports.signout = async (req, res) => { 43 | try { 44 | req.session = null; 45 | return res.status(200).send({ message: "You've been signed out!" }); 46 | } catch (err) { 47 | this.next(err); 48 | } 49 | }; 50 | 51 | -------------------------------------------------------------------------------- /app/routes/cache.route.js: -------------------------------------------------------------------------------- 1 | const cache = require("../controllers/redis.controller"); 2 | const auth = require("../middlewares/authJwt"); 3 | 4 | module.exports = function (app, redis, prefix) { 5 | app.use(function (req, res, next) { 6 | 7 | res.header( 8 | "Access-Control-Allow-Headers", 9 | "Origin, Content-Type, Accept" 10 | ); 11 | next(); 12 | }); 13 | 14 | app.get('/api/cache/top', (req, res) => cache.getTop100(req, res, redis, prefix)); 15 | app.get('/api/caches', auth.isAdmin, (req, res) => cache.getAllCache(req, res, redis, prefix)); 16 | app.get('/api/caches/pattern', (req, res) => cache.getCachesPater(req, res, redis, prefix)); 17 | app.get('/api/cache/missingRedis', auth.isModerator, (req, res) => cache.missingRedis(req, res, redis, prefix)); 18 | app.get('/api/cache/missingMongo', auth.isModerator, (req, res) => cache.missingMongo(req, res, redis, prefix)); 19 | app.post('/api/caches', auth.isModerator, (req, res) => cache.addAllMongoToRedis(req, res, redis, prefix)); 20 | app.get('/api/cache', (req, res) => cache.getCacheByKey(req, res, redis, prefix)); //api/cache?key=cút 21 | app.delete('/api/caches', auth.isModerator, (req, res) => cache.deleteCaches(req, res, redis, prefix)); //api/cache?key=cút 22 | app.delete('/api/cache', auth.isModerator, (req, res) => cache.deleteByKey(req, res, redis, prefix)); //api/cache?key=cút 23 | app.put('/api/cache', auth.isModerator, (req, res) => cache.updateByKey(req, res, redis, prefix)); //api/cache?key=cút 24 | app.post('/api/cache', auth.isModerator, (req, res) => cache.postCache(req, res, redis, prefix)); //api/cache 25 | }; 26 | -------------------------------------------------------------------------------- /app/middlewares/authJwt.js: -------------------------------------------------------------------------------- 1 | const jwt = require("jsonwebtoken"); 2 | const config = require("../config/auth.config.js"); 3 | const db = require("../models"); 4 | const Users = require("../models/user.model"); 5 | 6 | verifyToken = async (req, res, next) => { 7 | const tokenBearer = req.header('Authorization'); 8 | let token = tokenBearer.substring(7); 9 | 10 | if (!token) { 11 | return res.status(403).send({ message: "No token provided!" }); 12 | } 13 | 14 | jwt.verify(token, 15 | config.secret, 16 | (err, decoded) => { 17 | if (err) { 18 | return res.status(401).send({ 19 | message: "Unauthorized!", 20 | }); 21 | } 22 | console.log(decoded) 23 | req.username = decoded.username; 24 | req.roles = decoded.roles; 25 | next(); 26 | }); 27 | }; 28 | 29 | isAdmin = (req, res, next) => { 30 | const tokenBearer = req.header('Authorization'); 31 | let token = tokenBearer.substring(7); 32 | 33 | if (!token) { 34 | return res.status(403).send({ message: "No token provided!" }); 35 | } 36 | jwt.verify(token, 37 | config.secret, 38 | (err, decoded) => { 39 | if (err) { 40 | return res.status(403).send({ 41 | message: "Forbidden!", 42 | }); 43 | } 44 | console.log(decoded) 45 | if (!decoded.roles.includes('ADMIN')) 46 | return res.status(403).send({ 47 | message: "Forbidden!", 48 | }); 49 | next(); 50 | }); 51 | }; 52 | 53 | isModerator = (req, res, next) => { 54 | const tokenBearer = req.header('Authorization'); 55 | let token = tokenBearer.substring(7); 56 | 57 | if (!token) { 58 | return res.status(403).send({ message: "No token provided!" }); 59 | } 60 | jwt.verify(token, 61 | config.secret, 62 | (err, decoded) => { 63 | if (err) { 64 | return res.status(403).send({ 65 | message: "Forbidden!", 66 | }); 67 | } 68 | console.log(decoded) 69 | if (!decoded.roles.includes('MODERATOR')) 70 | return res.status(403).send({ 71 | message: "Forbidden!", 72 | }); 73 | next(); 74 | }); 75 | }; 76 | 77 | const authJwt = { 78 | verifyToken, 79 | isAdmin, 80 | isModerator 81 | }; 82 | module.exports = authJwt; 83 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Đóng Góp 2 | 3 | Chào mừng bạn đến với dự án **VulgarVeto**! Chúng tôi rất hoan nghênh mọi hình thức đóng góp từ cộng đồng. 4 | 5 | ## Người đóng góp 6 |
12 | 13 | ---------- 14 | 15 | 16 | ## Hướng dẫn Đóng Góp 17 | 18 | Nếu bạn muốn đóng góp vào dự án, vui lòng tuân thủ các bước sau: 19 | 20 | ### 1. Fork dự án 21 | Fork từ [GitHub repository](https://github.com/theanishtar/VulgarVeto) của chúng tôi. 22 | 23 | ### 2. Clone repository 24 | Clone repo đã fork về máy tính của bạn: `git clone https://github.com/username/VulgarVeto.git` 25 | 26 | ### 3. Tạo nhánh làm việc 27 | Tạo một nhánh mới để làm việc trên: `git checkout -b feature/your-feature` 28 | 29 | Nhánh mới sẽ có cấu trúc như sau: `
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
34 |
35 | ## Đặc Điểm
36 |
37 | - **Phát Hiện Từ Ngữ Thô Tục**: Phát hiện và loại bỏ các từ ngữ thô tục và phản cảm trong nội dung văn bản.
38 | - **Tùy Chỉnh Bộ Lọc Từ**: Tùy chỉnh bộ lọc từ ngữ thô tục dựa trên nhu cầu cụ thể và hướng dẫn của cộng đồng.
39 | - **Phân Tích Văn Bản**: Phân tích nội dung văn bản để tìm từ ngữ không phù hợp, tâm trạng và cách diễn đạt.
40 | - **Mở Rộng và Đáng Tin Cậy**: Các API của chúng tôi được xây dựng để có khả năng mở rộng và đáng tin cậy, đảm bảo hiệu suất cao ngay cả khi có tải nặng.
41 | - **Dễ Dàng Tích Hợp**: Tích hợp đơn giản và dễ dàng với các ứng dụng và nền tảng hiện có của bạn.
42 |
43 | ## Bắt Đầu
44 |
45 | Để bắt đầu sử dụng Vetonary Check APIs trong các dự án của bạn, hãy tuân theo các bước đơn giản sau đây:
46 |
47 | 1. **Đăng Ký**: Tạo tài khoản trên trang web của chúng tôi và nhận khóa API của bạn.
48 | 2. **Tích Hợp APIs**: Theo hướng dẫn trong [Tài Liệu API](apis.md) của chúng tôi để tích hợp các API vào ứng dụng của bạn.
49 | 3. **Bắt Đầu Kiểm Tra Từ Ngữ Thô Tục**: Sử dụng các API của chúng tôi để kiểm tra nội dung văn bản để phát hiện từ ngữ thô tục và không phù hợp.
50 |
51 | ## Lợi Ích
52 |
53 | - **Nâng Cao Trải Nghiệm Người Dùng**: Đảm bảo trải nghiệm người dùng tích cực và an toàn bằng cách loại bỏ nội dung không thích hợp.
54 | - **Kiểm Soát Cộng Đồng**: Cung cấp cho các người quản lý cộng đồng của bạn các công cụ mạnh mẽ để duy trì một môi trường lịch sự.
55 | - **Tuân Thủ và Quy Định**: Tuân thủ các quy định và hướng dẫn về kiểm soát nội dung.
56 | - **An Tâm**: Yên tâm rằng các nền tảng của bạn sẽ không chứa từ ngữ thô tục và nội dung không phù hợp.
57 |
58 | ## Hỗ Trợ và Phản Hồi
59 |
60 | Chúng tôi luôn sẵn lòng giúp đỡ bạn mọi lúc! Nếu bạn có bất kỳ câu hỏi, phản hồi hoặc cần trợ giúp, đừng ngần ngại liên hệ với [đội hỗ trợ](mailto:support@vatonary.com) của chúng tôi.
61 |
62 | Bắt đầu sử dụng Vetonary Check APIs ngay hôm nay và tạo ra một môi trường
63 |
64 | ## Những người đóng góp mã nguồn
65 |
66 |
67 |
73 |
74 | Xem chi tiết
75 |
--------------------------------------------------------------------------------
/app/utils/fileparser.js:
--------------------------------------------------------------------------------
1 | const { Upload } = require("@aws-sdk/lib-storage");
2 | const { S3Client } = require("@aws-sdk/client-s3");
3 | const Transform = require('stream').Transform;
4 | const AWS = require('aws-sdk');
5 | var formidable = require("formidable");
6 | require('dotenv').config();
7 |
8 | const accessKeyId = process.env.AWS_ACCESS_KEY_ID;
9 | const secretAccessKey = process.env.AWS_SECRET_ACCESS_KEY;
10 | const sessionToken = process.env.AWS_SESSION_TOKEN;
11 | const region = process.env.AWS_REGION;
12 | const Bucket = process.env.AWS_BUCKET_NAME;
13 |
14 | exports.parsefile = async (req) => {
15 | return new Promise((resolve, reject) => {
16 | let options = {
17 | maxFileSize: 10 * 1024 * 1024, //10 MBs converted to bytes,
18 | allowEmptyFiles: false
19 | }
20 |
21 | var form = new formidable.IncomingForm();
22 |
23 | form.parse(req, (err, fields, files) => { });
24 |
25 | form.on('error', error => {
26 | console.log("error upload");
27 | reject(error.message)
28 | })
29 |
30 | form.on('data', data => {
31 | if (data.name === "successUpload") {
32 | console.log("success upload");
33 | resolve(data.value);
34 | }
35 | })
36 |
37 | form.on('fileBegin', (formName, file) => {
38 | if (file.size > options.maxFileSize) {
39 | reject('File size is too large. Maximum size is 10MB.');
40 | }
41 |
42 | if (!(
43 | file.mimetype === 'image/jpeg' ||
44 | file.mimetype === 'image/png' ||
45 | file.mimetype === 'application/pdf')
46 | ) {
47 | reject('Invalid file type. Only PDF and image files are allowed.');
48 | }
49 |
50 | file.open = async function () {
51 | this._writeStream = new Transform({
52 | transform(chunk, encoding, callback) {
53 | callback(null, chunk)
54 | }
55 | })
56 |
57 | this._writeStream.on('error', e => {
58 | form.emit('error', e)
59 | reject(e)
60 | });
61 |
62 | // upload to S3
63 | new Upload({
64 | client: new S3Client({
65 | credentials: {
66 | accessKeyId,
67 | secretAccessKey
68 | },
69 | region
70 | }),
71 | params: {
72 | ACL: 'public-read',
73 | Bucket,
74 | Key: `${Date.now().toString()}-${this.originalFilename}`,
75 | Body: this._writeStream
76 | },
77 | tags: [], // optional tags
78 | queueSize: 4, // optional concurrency configuration
79 | partSize: 1024 * 1024 * 5, // optional size of each part, in bytes, at least 5MB
80 | leavePartsOnError: false, // optional manually handle dropped parts
81 | })
82 | .done()
83 | .then(data => {
84 | form.emit('data', { name: "complete", value: data });
85 | resolve(data);
86 | }).catch((err) => {
87 | form.emit('error', err);
88 | reject(err);
89 | })
90 | }
91 |
92 | file.end = function (cb) {
93 | this._writeStream.on('finish', () => {
94 | this.emit('end')
95 | cb()
96 | })
97 | this._writeStream.end()
98 | }
99 | })
100 | })
101 | }
102 |
103 | exports.deleteImageFromS3 = async (objectLocation) => {
104 | const key = objectLocation.replace('https://s3.amazonaws.com/', '');
105 | const s3 = new AWS.S3({
106 | accessKeyId: accessKeyId,
107 | secretAccessKey: secretAccessKey,
108 | region: region
109 | });
110 |
111 | const params = {
112 | Bucket: Bucket,
113 | Key: key
114 | };
115 |
116 | try {
117 | await s3.deleteObject(params).promise();
118 | console.log(`Successfully deleted image with key: ${key}`);
119 | } catch (error) {
120 | console.error(`Error deleting image from S3: ${error.message}`);
121 | throw error;
122 | }
123 | };
--------------------------------------------------------------------------------
/README-API.md:
--------------------------------------------------------------------------------
1 | # Check từ ngữ bậy bạ tiếng Việt
2 |
3 | Trước khi đến với dự án, mình sẽ hướng dẫn các bạn cách sử dụng trước nhé
4 |
5 | ## Các APIs được public
6 | |Ngày phát hành| Chức năng | Phương thức | APIs | Body | Kết quả |
7 | |---------|---------------------|-------------|-------------------|-----------------------------------------------|---------------------------|
8 | | | làm sạch một câu nếu có chứa từ xấu | GET | `/api/cleanword?word=thằng chó cút đi` | `thằng ... ... đi` |
9 | | | kiểm tra từ xấu | GET | `/api/badword?name=chó` | `This is VN badword` |
10 | | 19/02/2024| Tối ưu tìm kiếm | POST | `/api/badwords` | tâm dâm quá|This is VN badword|
11 | |23/02/2024 | Tối ưu tìm kiếm | POST | `api/cleanwords` |tâm dâm quá|******* quá|
12 |
13 | User sẽ gửi dữ liệu lên Request, ví dụ:
14 |
15 |
16 | POST: http://localhost:8080/api/cleanwords
17 | ```json
18 | "body": {
19 | {
20 | "words": "con chó loz !!!"
21 | }
22 | }
23 | "response": {
24 | {
25 | "badWords": [
26 | {
27 | "_id": "652f83a480116248fd2069cb",
28 | "createDate": "2020-05-18T14:10:30.000Z",
29 | "formatDate": "18-05-2020",
30 | "label": 1,
31 | "name": "chó",
32 | "severityLevel": 10
33 | },
34 | {
35 | "_id": "652f83a480116248fd2066a8",
36 | "createDate": "2020-05-18T14:10:30.000Z",
37 | "formatDate": "18-05-2020",
38 | "label": 1,
39 | "name": "loz",
40 | "severityLevel": 2
41 | }
42 | ],
43 | "label": 2,
44 | "cleanWord": "con *** *** !!!",
45 | "message": "This is VN badword"
46 | }
47 | }
48 | ```
49 |
50 |
51 |
52 | ----
53 | Dữ liệu có chứa `chó` và `loz` là một từ khiếm nhã, xem tất cả tại [đây](https://github.com/frogsage/vietnam-sensitive-words/blob/main/bad_words.json)
54 |
55 | vì vậy ta phải giải quyết, ***không cho phép*** `data` trên được lưu vào `database`
56 |
57 | ## Giải pháp
58 |
59 | Kết hợp `MongoDB` và `Redis`
60 |
61 | - **MongoDB**: Một `CSDL` ***No-SQL*** lưu trữ Realtime tương tự `Firebase` nhưng bảo mật và tối ưu hơn.
62 | - **Redis**: Một bộ nhớ `Cache` lưu trữ trên `RAM` vì vậy tốc độ nhanh gấp 1000 (*một nghìn*) lần so với My-SQL hay SQL-Server lưu tại **Memory**.
63 |
64 |
65 | ## Thông tin chung
66 |
67 | - Dữ liệu sẽ tồn tại trên `MongoDB` (để đảm bảo tính lưu trữ an toàn và lâu dài của dữ liệu)
68 | - Mỗi Request gửi lên server sẽ được check bằng số data ở `Redis` (đảm bảo tốc độ và sự chịu tải của server)
69 | - `MongoDB` sẽ được xem là bảng Backup của `Redis`
70 |
71 | ### Dữ liệu được lưu trên `MongoDB`
72 |
73 | 
74 |
75 | ## Luồng xử lý
76 |
77 | Lưu trữ từ khóa nhạy cảm trong `MongoDB`:
78 |
79 | 1. Thiết kế một `collection` trong `MongoDB` để lưu trữ danh sách các từ khóa nhạy cảm. Mỗi từ khóa có thể có một số thuộc tính, chẳng hạn như tên, mức độ nghiêm trọng, và ngày tạo.
80 | Nhập danh sách từ khóa nhạy cảm vào `MongoDB` hoặc cung cấp một giao diện quản trị để quản lý danh sách này.
81 | Sử dụng `Redis` làm `cache`:
82 |
83 | 2. Khi server khởi động hoặc tải danh sách từ khóa nhạy cảm từ `MongoDB`, lưu trữ chúng trong `Redis` để cung cấp truy cập nhanh chóng.
84 | Sử dụng các thời gian sống **(TTL)** trong `Redis` để tự động xóa `cache` sau một khoảng thời gian cố định để đảm bảo dữ liệu `cache` là cập nhật và không lỗi thời.
85 | Xử lý yêu cầu kiểm tra từ khóa nhạy cảm:
86 |
87 | 3. Khi một yêu cầu gửi lên server, trước khi xử lý yêu cầu, server sẽ kiểm tra nội dung của yêu cầu xem có chứa từ khóa nhạy cảm hay không.
88 | Server sẽ truy vấn `Redis` để kiểm tra xem từ khóa nhạy cảm có trong `cache` hay không. Nếu từ khóa tồn tại trong `cache`, server có thể ngay lập tức phát hiện và xử lý nội dung nhạy cảm.
89 | Nếu từ khóa không tồn tại trong `cache`, server sẽ thực hiện một truy vấn đến `MongoDB` để kiểm tra từ khóa nhạy cảm và sau đó cập nhật vào `Redis` cho lần sau.
90 | Phản hồi cho người dùng:
91 |
92 | 4. Server sẽ trả về kết quả kiểm tra từ khóa nhạy cảm cho người dùng. Nếu từ khóa nhạy cảm được phát hiện, server có thể từ chối yêu cầu hoặc thực hiện các biện pháp an toàn, chẳng hạn như ẩn bình luận hoặc chặn nội dung độc hại.
93 | Quản lý và cập nhật danh sách từ khóa nhạy cảm:
94 |
95 | 5. Cung cấp giao diện quản trị cho quản lý danh sách từ khóa nhạy cảm. Các từ khóa nhạy cảm mới có thể được thêm vào hoặc sửa đổi từ giao diện này và sau đó được cập nhật trong `MongoDB` và `Redis`.
96 |
97 | ## Các chức năng chính
98 | - Quản lí các từ nhạy cảm `bad-words` **(CRUD)**
99 | - Cập nhật từ MongoDB lên `Redis` (có thể làm tự động)
100 | - Ghi lại số lần được nhắc đến của một từ ngữ (tăng `severityLevel` khi có một `user` gọi đến từ ngữ đó)
101 | - Sắp xếp để thống kê các từ ngữ vi phạm phổ biến
102 | - Ưu tiên kiểm tra các từ ngữ bị vi phạm phổ biến trước
103 |
104 | ### Đẩy data lên MongoDB
105 |
106 | Phân quyền phù hợp và xây dựng trang UI cho `Moderator` quản lí các tác vụ trên
107 |
108 | ---
109 | ## Lưu ý:
110 | Data sẽ tồn tại ở `MongoDB` và `Redis` chỉ lưu tạm thời để tăng tốc độ `caching`
111 |
112 |
113 |
114 | ## Các APIs
115 |
116 | - Clean words:
117 | - Dùng để làm sạch các từ ngữ trong một chuỗi, và chuyển chúng thành dạng `beep (*)`
118 | - Ví dụ: `con chó nhamloz` -> `con *** *******` | vì [***chó, nhamloz là 2 từ xấu***]()
119 |
120 |
121 | ## Bảo mật
122 |
123 | Những APIs tương tác với database hoặc CRUD dữ liệu, cần phải truyền token để sử dụng
124 |
--------------------------------------------------------------------------------
/APIs.md:
--------------------------------------------------------------------------------
1 | # Danh sách các APIs
2 |
3 | ## Công cộng
4 |
5 | ### Làm sạch
6 |
7 | #### Làm sạch các từ ngữ thông qua `word`
8 | - Feature: Cleanword by word
9 | - Description: vì GET nên chỉ dùng cho các câu ngắn
10 | - Protocol: GET
11 | - authorization: Không
12 | - url: `/api/cleanwords?word=tâm dâm quá đi chó`
13 | - res:
14 | >```json
15 | >{
16 | > "badWords": [...], // dữ liệu dài nên không để ở đây, vui lòng test api nếu cần
17 | > "label": 2,
18 | > "cleanWord": "******* quá đi ***",
19 | > "message": "This is VN badword"
20 | >}
21 | >```
22 |
25 |
26 | #### Làm sạch các từ ngữ thông qua `word`
27 | - Feature: Cleanwords by name
28 | - Description: check dữ liệu lớn hơn
29 | - Protocol: POST
30 | - authorization: Không
31 | - url: `/api/cleanwords`
32 | - body:
33 | >```json
34 | >"words": "tâm dâm quá thằng chó"
35 | >```
36 |
37 | - res:
38 | >```json
39 | >{
40 | > "badWords": [...], // dữ liệu dài nên không để ở đây, vui lòng test api nếu cần
41 | > "label": 3,
42 | > "cleanWord": "******* quá ***** ***",
43 | > "message": "This is VN badword"
44 | >}
45 | >```
46 |
49 |
50 | ### Kiểm tra
51 |
52 | #### Lấy tất cả Badwords trong Cache và Database
53 | - Feature: Get All Badword
54 | - Descrition: Lấy tất cả Badwords trong Cache và Database
55 | - Protocol: GET
56 | - url: `/api/badwords`
57 | - authorization: Yêu cầu quyền Admin
58 |
59 | #### Lấy một Badwords với `patch name`
60 | - Feature: Get Badword by name
61 | - Descrition: Lấy trong Cache trước, nếu không có thì lấy trong Database
62 | - Protocol: GET
63 | - url: `/api/badword?name=`
64 | - authorization: Không
65 |
66 | #### Kiểm tra một câu có chứa Badwords hay không
67 | - Feature: Check Badword by words
68 | - Descrition: Dùng POST nên có thể kiểm tra dữ liệu lớn
69 | - Protocol: POST
70 | - url: `/api/badwords`
71 | - authorization: Không
72 | - body:
73 | >```json
74 | >"words": "thằng chó"
75 | >```
76 |
77 | - res:
78 | >```json
79 | >{
80 | > "badWords": [...], // dữ liệu dài nên không để ở đây, vui lòng test api nếu cần
81 | > "label": 1,
82 | > "message": "This is VN badword"
83 | >}
84 | >```
85 |
86 | ## Bảo mật
87 |
88 | ### Đăng nhập
89 |
90 | #### Đăng nhập bằng username và password
91 | - Feature: Signin
92 | - DescriptionL: lấy Token
93 | - Protocol: POST
94 | - authorization: Không
95 | - url: `/api/auth/signin`
96 | - body:
97 | >```json
98 | >{
99 | > "username": "your_username", // NOT NULL
100 | > "password": "your_password", // NOT NULL
101 | >}
102 | >```
103 |
104 | ### Đăng xuất
105 | #### Đăng xuất
106 | - Feature: Singout
107 | - Descrioption: xóa token
108 | - Protocol: POST
109 | - url: `/api/auth/signout`
110 | - authorization: Không
111 |
112 | ## Database
113 | #### Lấy tất cả dữ liệu collections Badwords
114 | - Feature: Get ALl Badwords
115 | - Description: Lấy tất cả dữ liệu collections Badwords
116 | - Protocol: GET
117 | - url: `/api/dbs`
118 | - authorization: Yêu cầu quyền Admin
119 |
120 |
121 | #### Lấy một Badwords với `patch name`
122 | - Feature: Get Badword from Database by name
123 | - Descrition: Lấy một Badwords với `patch name`
124 | - Protocol: GET
125 | - url: `/api/db?name=`
126 | - authorization: Yêu cầu quyền Moderator
127 |
128 | #### Thêm một Badwords mới vào database
129 | - Feature: Post Badword to Database
130 | -Description: Thêm một Badwords mới vào database
131 | - Protocol: POST
132 | - authorization: Yêu cầu quyền Moderator
133 | - url: `/api/db`
134 | - body:
135 | >```json
136 | >{
137 | > "name": "example", // NOT NULL
138 | > "label": , // NULL (fefault is 1)
139 | > "severityLevel": , // NULL (fefault is 1)
140 | >}
141 | >```
142 |
143 | #### Cập nhật một Badwords Thông qua `patch name`
144 | - Feature: Update Badword from Database by name
145 | - Descripton: Cập nhật một Badwords Thông qua `patch name`
146 | - Protocol: PUT
147 | - url: `/api/db?name=`
148 | - authorization: Yêu cầu quyền Moderator
149 |
150 | #### Xóa một Badwords Thông qua `patch name`
151 | - Feature: Delete Badword from Database by name
152 | - Description: Xóa một Badwords Thông qua `patch name`
153 | - Protocol: DELETE
154 | - url: `/api/db?name=`
155 | - authorization: Yêu cầu quyền Moderator
156 |
157 | ## Caches
158 |
159 | #### Lấy tất cả dữ liệu caches Badwords
160 | - Feature: Get ALl Badwords in Cache
161 | - Description: Lấy tất cả dữ liệu caches Badwords
162 | - Protocol: GET
163 | - url: `/api/caches`
164 | - authorization: Yêu cầu quyền Moderator
165 |
166 | #### Lấy một Badwords với `patch key`
167 | - Feature: Get Badword from Cache by key
168 | - Description: Lấy một Badwords với `patch key`
169 | - Protocol: GET
170 | - url: `/api/cache?key=`
171 | - authorization: Không
172 |
173 | #### Thêm Badwords từ MongoDB vào Cache
174 | - Feature: Post All Badword from Database to Cache
175 | - Description: Thêm Badwords từ MongoDB vào Cache
176 | - Protocol: POST
177 | - authorization: Yêu cầu quyền Moderator
178 | - url: `/api/caches`
179 |
180 | #### Thêm một Badwords vào Cache
181 | - Feature: Post Badwords to Cache
182 | - Description: dữ liệu nên được lấy từ MongoDB
183 | - Protocol: POST
184 | - url: `/api/cache`
185 | - authorization: Yêu cầu quyền Moderator
186 | - body:
187 | >```json
188 | >"name": "QWERTY",
189 | >"label": 1,
190 | >"severityLevel": 1,
191 | >"createDate": 2020-05-18T14:10:30Z
192 | >```
193 |
194 | #### Cập nhật một Badwords Thông qua `patch key`
195 | - Feature: Update Badword from Cache by key
196 | - Description: Cập nhật một Badwords Thông qua `patch key`
197 | - Protocol: PUT
198 | - url: `/api/cache?key=`
199 | - authorization: Yêu cầu quyền Moderator
200 | - body:
201 | >```json
202 | >"name": "QWERTY", // NULL (default is key from query path)
203 | >"label": 1,
204 | >"severityLevel": 1,
205 | >"createDate": 2020-05-18T14:10:30Z
206 | >```
207 |
208 | #### Xóa một Badwords Thông qua `patch name`
209 | - Feature: Delete Badword from Cache by name
210 | - Description: Xóa một Badwords Thông qua `patch name`
211 | - Protocol: DELETE
212 | - url: `/api/cache?name=`
213 | - authorization: Yêu cầu quyền Moderator
214 |
215 | #### Lấy toàn bộ Badwords có trong Database mà không có trong Cache
216 | - Feature: Get Badword from Cache
217 | - Desciption: Lấy toàn bộ Badwords có trong Database mà không có trong Cache
218 | - Protocol: GET
219 | - url: `/api/cache/missingRedis`
220 | - authorization: Yêu cầu quyền Moderator
221 |
222 | #### Lấy toàn bộ Badwords có trong Cache mà không có trong Database
223 | - Feature: Get Badword from Cache
224 | - Description: Lấy toàn bộ Badwords có trong Cache mà không có trong Database
225 | - Protocol: GET
226 | - url: `/api/cache/missingMongo`
227 | - authorization: Yêu cầu quyền Moderator
228 |
229 | #### Lấy 100 Badwords có level cao nhất (phổ biến nhất)
230 | - Feature: Get Top 100 Badword from Cache (Lấy toàn bộ Badwords có trong Cache mà không có trong Database)
231 | - Description: Lấy 100 Badwords có level cao nhất (phổ biến nhất). Đọc trong Cache trước, nếu không có sẽ đọc từ Database
232 | - Protocol: GET
233 | - url: `/api/cache/top`
234 | - authorization: Yêu cầu quyền Moderator
235 |
--------------------------------------------------------------------------------
/app/controllers/redis.controller.js:
--------------------------------------------------------------------------------
1 | const Badword = require("../models/badword.model");
2 |
3 | const dataArr = [
4 | {
5 | name: 'dangdepzai',
6 | label: 1,
7 | severityLevel: 1,
8 | createDate: '2020-05-18T14:10:30Z'
9 | }
10 | ]
11 | // Function to retrieve keys starting with a specific prefix
12 | async function getKeysByPrefix(redis, prefix) {
13 | try {
14 | let cursor = '0';
15 | let data = [];
16 | do {
17 | const result = await redis.scan(cursor, 'MATCH', prefix + '*', 'COUNT', '1000');
18 | cursor = result[0];
19 | let keysWithPrefix = result[1];
20 |
21 | // Loại bỏ prefix từ các keys và thêm chúng vào mảng keys
22 | let keysWithoutPrefix = keysWithPrefix.map(key => key.replace(prefix, ''));
23 | const cache = {
24 | key: keysWithoutPrefix,
25 | // value: await redis.get(keysWithPrefix)
26 | }
27 | data = data.concat(keysWithoutPrefix);
28 | } while (cursor !== '0');
29 |
30 | return data;
31 | } catch (error) {
32 | console.error('Error retrieving keys by prefix:', error);
33 | return [];
34 | }
35 | }
36 |
37 | exports.addData = async (req, res, redis, prefix) => {
38 | dataArr.forEach(e => {
39 | let bdw = { name: e.name, label: e.label, severityLevel: e.severityLevel, createDate: e.createDate }
40 | redis.set(prefix + e.name, JSON.stringify(bdw))
41 | .then(() => {
42 | console.log('Dữ liệu đã được thêm vào Redis thành công.', bdw);
43 | })
44 | .catch((error) => {
45 | console.error('Đã xảy ra lỗi khi thêm dữ liệu vào Redis:', error);
46 | })
47 | .finally(() => {
48 | // Đóng kết nối Redis sau khi hoàn tất
49 | // redis.quit();
50 | // console.log("SUCCESS !!!")
51 | });
52 | })
53 | };
54 |
55 | exports.getCachesPater = async (req, res, redis, prefix) => {
56 | try {
57 | const pref = req.query.pref || prefix;
58 | getKeysByPrefix(redis, pref)
59 | .then(keys => {
60 | // console.log(keys)
61 | // console.log('Keys starting with prefix', prefix + ':', keys.length);
62 | return res.json({
63 | prefix: pref,
64 | data: keys
65 | })
66 | })
67 | .catch(error => {
68 | console.error('Error:', error);
69 | });
70 |
71 | } catch (error) {
72 |
73 | }
74 | }
75 |
76 | exports.getAllCache = async (req, res, redis, prefix) => {
77 | try {
78 | let data = [];
79 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
80 | const pipeline = redis.pipeline(); // Tạo một pipeline để thực hiện các lệnh redis một cách tuần tự
81 |
82 | // Thêm các lệnh hỏi Redis để lấy dữ liệu tương ứng với từng key vào pipeline
83 | keys.forEach(key => {
84 | pipeline.get(key);
85 | });
86 |
87 | // Thực hiện pipeline để lấy dữ liệu từ Redis
88 | const results = await pipeline.exec();
89 |
90 | // Tạo một đối tượng chứa dữ liệu từ Redis
91 | results.forEach((result, index) => {
92 | const key = keys[index];
93 | const value = result[1]; // result[1] chứa giá trị được trả về từ Redis
94 | console.log(result[1]);
95 | // data.push({ key: key, value: JSON.parse(unescapedValue) });
96 | data.push({ key: key, value: JSON.parse(value) });
97 |
98 |
99 | });
100 |
101 | return res.json(data)
102 |
103 | } catch (error) {
104 | console.log(error);
105 | res.status(500).json({ error: "Error" });
106 | }
107 | };
108 |
109 | exports.postCache = async (req, res, redis, prefix) => {
110 |
111 | /*
112 | const data = {
113 | name: "bw:::QWERTY",
114 | label: 1,
115 | severityLevel: 1,
116 | createDate: 2020-05-18T14:10:30Z
117 | }
118 | */
119 | try {
120 | // Giá trị mới cần thiết lập cho key
121 | const badWord = {
122 | name: prefix + req.body.name,
123 | label: req.body.label || 1,
124 | severityLevel: req.body.severityLevel || 1,
125 | createDate: new Date()
126 | }
127 | const key = badWord.name;
128 | console.log(badWord)
129 | // Kiểm tra xem dữ liệu có trong cache không
130 | const findCache = await redis.get(key);
131 | if (findCache)
132 | return res.status(203).json({ data: "", message: "Word repeating" });
133 |
134 | // Thêm giá trị của key trong Redis
135 | redis.set(key, JSON.stringify(badWord)).then((result) => {
136 | console.log('POST Successfully!', result);
137 | if (result === 'OK') {
138 | // Trả về phản hồi nếu thêm thành công
139 | return res.json({ action: `Post cache with key ${key}`, status: "Success", data: result });
140 | } else {
141 | // Trả về lỗi nếu không thể cập nhật key
142 | return res.status(500).json({ action: `post cache with key ${key}`, status: "Error", data: badWord });
143 | }
144 | }).catch((err) => {
145 | console.error('Error:', err);
146 | // Trả về lỗi nếu có lỗi xảy ra trong quá trình cập nhật
147 | res.status(500).json(err);
148 | }).finally(() => {
149 | // Đóng kết nối Redis
150 | });
151 | } catch (error) {
152 | res.json(err)
153 | }
154 | }
155 |
156 |
157 | exports.deleteCaches = async (req, res, redis, prefix) => {
158 | const key = prefix + req.query.key;
159 | try {
160 | // Xóa tất cả từ Redis
161 | // Flush all caches
162 | // Get number of keys before flush
163 | let keysBeforeFlush;
164 |
165 | redis.dbsize()
166 | .then(count => {
167 | keysBeforeFlush = count;
168 | console.log('Number of keys before flush:', keysBeforeFlush);
169 | // Flush all caches
170 | // return res.json({ action: `del cache with key is ${key}`, status: "Sucess", data: result });
171 | return redis.flushall();
172 | })
173 | .then(() => {
174 | console.log('All caches have been deleted successfully.');
175 | // Get number of keys after flush
176 | return redis.dbsize();
177 | })
178 | .then(keysAfterFlush => {
179 | const keysDeleted = keysBeforeFlush - keysAfterFlush;
180 | console.log('Number of caches deleted:', keysDeleted);
181 | return res.json({ action: `del caches`, status: "Sucess", data: keysDeleted });
182 | })
183 | .catch((err) => {
184 | console.error('Error:', err);
185 | return res.json({ action: `del caches`, status: "Faild", data: 0 });
186 | })
187 | .finally(() => {
188 | // Close the Redis connection
189 | // redis.quit();
190 | });
191 | } catch (error) {
192 | res.json(err)
193 | }
194 | }
195 |
196 |
197 | exports.deleteByKey = async (req, res, redis, prefix) => {
198 | const key = prefix + req.query.key;
199 | try {
200 | // Xóa một key từ Redis
201 | redis.del(key).then((result) => {
202 | console.log('Deleted Successfully!', result);
203 | if (result > 0)
204 | return res.json({ action: `del cache with key is ${key}`, status: "Sucess", data: result });
205 | return res.status(404).json({ action: `del cache with key is ${key}`, status: "Not found", data: result });
206 | }).catch((err) => {
207 | console.error('Error:', err);
208 | res.json(err)
209 | }).finally(() => {
210 | });
211 | } catch (error) {
212 | res.json(err)
213 | }
214 | }
215 |
216 | exports.updateByKey = async (req, res, redis, prefix) => {
217 | const key = prefix + req.query.key;
218 |
219 | /*
220 | const data = {
221 | name: "QWERTY",
222 | label: 1,
223 | severityLevel: 1,
224 | createDate: 2020-05-18T14:10:30Z
225 | }
226 | */
227 | try {
228 | // Kiểm tra xem dữ liệu có trong cache không
229 | const findCache = await redis.get(key);
230 | if (!findCache)
231 | return res.status(404).json({ data: "", message: "Word not found" });
232 |
233 | // Giá trị mới cần thiết lập cho key
234 | const badWord = {
235 | name: req.body.name || key,
236 | label: req.body.label || findCache.label,
237 | severityLevel: req.body.severityLevel || findCache.label,
238 | createDate: req.body.createDate || findCache.createDate
239 | }
240 | console.log(badWord)
241 |
242 | // Cập nhật giá trị của key trong Redis
243 | redis.set(key, JSON.stringify(badWord)).then((result) => {
244 | console.log('Updated Successfully!', result);
245 | if (result === 'OK') {
246 | // Trả về phản hồi nếu cập nhật thành công
247 | return res.json({ action: `update cache with key ${key}`, status: "Success", data: result });
248 | } else {
249 | // Trả về lỗi nếu không thể cập nhật key
250 | return res.status(500).json({ action: `update cache with key ${key}`, status: "Error", data: badWord });
251 | }
252 | }).catch((err) => {
253 | console.error('Error:', err);
254 | // Trả về lỗi nếu có lỗi xảy ra trong quá trình cập nhật
255 | res.status(500).json(err);
256 | }).finally(() => {
257 | // Đóng kết nối Redis
258 | });
259 | } catch (error) {
260 | res.json(err)
261 | }
262 |
263 | }
264 |
265 | exports.getCacheByKey = async (req, res, redis, prefix) => {
266 | const key = prefix + req.query.key;
267 | try {
268 | // Kiểm tra xem dữ liệu có trong cache không
269 | const findCache = await redis.get(key);
270 | console.log(findCache)
271 | if (findCache)
272 | return res.status(200).json({ data: JSON.parse(findCache) });
273 |
274 | console.log("Khong co trong cache")
275 |
276 | return res.status(404).json({ data: "", message: "Word not found" });
277 | } catch (error) {
278 | return res.status(500).json({ error: "Error" });
279 | }
280 | };
281 |
282 | exports.addAllMongoToRedis = async (req, res, redis, prefix) => {
283 | const badwords = await Badword.find();
284 | let len = 0;
285 | badwords.forEach(e => {
286 | len++;
287 | const bw = JSON.stringify(e);
288 | redis.set(prefix + e.name, bw)
289 | .then(() => {
290 | console.log('Dữ liệu đã được thêm vào Redis thành công.', e);
291 | })
292 | .catch((error) => {
293 | console.error('Đã xảy ra lỗi khi thêm dữ liệu vào Redis:', error);
294 | return res.status(200).json(
295 | { action: "Add Mongo to Redis", status: "Failed", message: `Failed when add ${badwords.length} to Cache memory`, success: `${len} Objects` });
296 | })
297 | .finally(() => {
298 | });
299 | })
300 | return res.status(200).json({ action: "Add Mongo to Redis", status: "success", message: `Add ${badwords.length} to Cache memory`, success: `${len} Objects` });
301 | };
302 |
303 | exports.missingRedis = async (req, res, redis, prefix) => {
304 | const badwords = await Badword.find();
305 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
306 | let data = [];
307 |
308 | // Sử dụng Promise.all để xử lý bất đồng bộ
309 | badwords.forEach(e => {
310 | if (keys.indexOf(e.name) < 0)
311 | data.push(e);
312 | })
313 |
314 | return res.status(200).json({ data: data, message: `${data.length} objects from Mongo are currently not present in Redis` });
315 | }
316 |
317 | exports.missingMongo = async (req, res, redis, prefix) => {
318 | let data = [];
319 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
320 | const badwords = await Badword.find().select('name');
321 | const names = badwords.map(badword => badword.name);
322 |
323 | for (let key of keys) {
324 | if (names.indexOf(key) < 0) {
325 | const cache = await redis.get(key);
326 | data.push(JSON.parse(cache));
327 | }
328 | }
329 |
330 | return res.status(200).json({ data: data, message: `${data.length} objects from Mongo are currently not present in Redis` });
331 | }
332 |
333 |
334 | exports.getTop100 = async (req, res, redis, prefix) => {
335 |
336 | let data = [];
337 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
338 | const pipeline = redis.pipeline(); // Tạo một pipeline để thực hiện các lệnh redis một cách tuần tự
339 |
340 | // Thêm các lệnh hỏi Redis để lấy dữ liệu tương ứng với từng key vào pipeline
341 | keys.forEach((key) => {
342 | pipeline.get(key);
343 | });
344 |
345 | // Thực hiện pipeline để lấy dữ liệu từ Redis
346 | const results = await pipeline.exec();
347 |
348 | // Tạo một đối tượng chứa dữ liệu từ Redis
349 | results.forEach((result, index) => {
350 | const key = keys[index];
351 | const value = result[1]; // result[1] chứa giá trị được trả về từ Redis
352 | data.push({ key: key, value: JSON.parse(value) });
353 | });
354 |
355 | data.sort((a, b) => b.value.severityLevel - a.value.severityLevel);
356 |
357 | // Lấy 100 dòng đầu tiên của 'data'
358 | const first100Lines = data.slice(0, 100);
359 | if (data.length > 0)
360 | return res.status(200).json({
361 | data: first100Lines,
362 | message: `APIs are developed for reference purposes, therefore only ${first100Lines.length} lines of data are available. Please contact github.com/Theanishtar to obtain the entire dataset.`,
363 | src: `Cahes`,
364 | sponsor: {
365 | sub: `To upgrade your account, please support the developer according to the following information!`,
366 | bank_name: `Viettinbank`,
367 | acc_number: `104878145669`,
368 | user_name: `TRAN HUU DANG`
369 | }
370 | });
371 |
372 | const badwords = await Badword.find();
373 | const bwfirst100Lines = badwords.slice(0, 100);
374 | return res.status(200).json({
375 | data: bwfirst100Lines,
376 | message: `APIs are developed for reference purposes, therefore only ${bwfirst100Lines.length} lines of data are available. Please contact github.com/Theanishtar to obtain the entire dataset.`,
377 | src: `Database`,
378 | sponsor: {
379 | sub: `To upgrade your account, please support the developer according to the following information!`,
380 | bank_name: `Viettinbank`,
381 | acc_number: `104878145669`,
382 | user_name: `TRAN HUU DANG`
383 | }
384 | });
385 |
386 | }
--------------------------------------------------------------------------------
/app/controllers/badword.controller.js:
--------------------------------------------------------------------------------
1 | const Badword = require("../models/badword.model");
2 |
3 | exports.getAllBadwords = async (req, res, redis) => {
4 | try {
5 | redis.keys("*", function (err, keys) {
6 | if (err) {
7 | console.error("Error retrieving keys: ", err);
8 | return;
9 | }
10 |
11 | // Lặp qua từng key và lấy giá trị của nó
12 | keys.forEach(function (key) {
13 | redis.get(key, function (err, value) {
14 | if (err) {
15 | console.error("Error retrieving value for key", key, ":", err);
16 | return;
17 | }
18 | // console.log("Key:", key, "Value:", value);
19 | });
20 | });
21 | });
22 |
23 |
24 | const badwords = await Badword.find();
25 | res.json(badwords);
26 | } catch (error) {
27 | res.status(500).json({ error: "Error: " + error });
28 | }
29 | };
30 |
31 | exports.getBadwordByName = async (req, res, redis, prefix) => {
32 | try {
33 | const name = prefix + req.query.name;
34 | // Kiểm tra xem dữ liệu có trong cache không
35 | const findCache = await redis.get(name);
36 | if (findCache)
37 | return res.status(200).json({ data: JSON.parse(findCache), message: "This is VN badword" });
38 |
39 | let data = [];
40 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
41 | const pipeline = redis.pipeline(); // Tạo một pipeline để thực hiện các lệnh redis một cách tuần tự
42 |
43 | // Thêm các lệnh hỏi Redis để lấy dữ liệu tương ứng với từng key vào pipeline
44 | keys.forEach(key => {
45 | pipeline.get(key);
46 | });
47 |
48 | // Thực hiện pipeline để lấy dữ liệu từ Redis
49 | const results = await pipeline.exec();
50 |
51 | // Tạo một đối tượng chứa dữ liệu từ Redis
52 | results.forEach((result, index) => {
53 | const key = keys[index];
54 | const value = result[1]; // result[1] chứa giá trị được trả về từ Redis
55 | data.push({ key: key, value: JSON.parse(value) });
56 | });
57 | data.forEach(e => {
58 | if (name.includes(e.key.substring(5))) {
59 | checkContains = e.value.name;
60 | return;
61 | }
62 | })
63 | if (checkContains)
64 | return res.status(200).json({ data: checkContains, message: "This is VN badword" });
65 |
66 | const badwords = await Badword.find({ name });
67 | console.log(badwords.length)
68 | if (badwords.length == 0)
69 | return res.status(404).json({ data: "", message: "Word not found" });
70 |
71 | return res.status(200).json({ data: badwords, message: "This is VN badword" });
72 | } catch (error) {
73 | return res.status(500).json({ error: `Error ${error}` });
74 | }
75 | };
76 |
77 | /*
78 | body: {
79 | words: "text"
80 | }
81 | */
82 | exports.checkBadword = async (req, res, redis) => {
83 | const line = req.body.words;
84 | let hasBadword; // Cờ để kiểm tra xem có badword không
85 | console.log(line);
86 | let badwords = [];
87 | try {
88 | // Kiểm tra xem dữ liệu có trong cache không
89 | const findCache = await redis.get(line);
90 | const resCache = JSON.parse(findCache);
91 | if (findCache) {
92 | badwords.push(resCache);
93 | return res.status(200).json(
94 | {
95 | badWords: badwords,
96 | label: resCache.label,
97 | message: "This is VN badword"
98 | });
99 | }
100 |
101 |
102 | let data = [];
103 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
104 | const pipeline = redis.pipeline(); // Tạo một pipeline để thực hiện các lệnh redis một cách tuần tự
105 |
106 | // Thêm các lệnh hỏi Redis để lấy dữ liệu tương ứng với từng key vào pipeline
107 | keys.forEach(key => {
108 | pipeline.get(key);
109 | });
110 |
111 | // Thực hiện pipeline để lấy dữ liệu từ Redis
112 | const results = await pipeline.exec();
113 |
114 | // Tạo một đối tượng chứa dữ liệu từ Redis
115 | results.forEach((result, index) => {
116 | const key = keys[index];
117 | const value = result[1]; // result[1] chứa giá trị được trả về từ Redis
118 | data.push({ key: key, value: JSON.parse(value) });
119 | });
120 | data.forEach(e => {
121 | if (line.includes(e.key)) {
122 | checkContains = e.value;
123 | badwords.push(checkContains);
124 | return;
125 | }
126 | })
127 | if (checkContains)
128 | return res.status(200).json(
129 | {
130 | badWords: badwords,
131 | label: badwords.length,
132 | message: "This is VN badword"
133 | });
134 | const name = line;
135 | const badw = await Badword.find({ name });
136 | console.log(badw.length > 0 && badw)
137 | if (badw && badw.length > 0) {
138 | badwords.push(badw);
139 | return res.status(200).json(
140 | {
141 | badWords: badwords,
142 | label: badw.label,
143 | message: "This is VN badword"
144 | });
145 | }
146 | const db = word.map(async (nameW) => {
147 | const bad = await Badword.find({ name: nameW });
148 | return { nameW, bad };
149 | });
150 | const dbResults = await Promise.all(db);
151 | dbResults.forEach(e => {
152 | if (e.bad.length > 0) {
153 | hasBadword = e.bad;
154 | return;
155 | }
156 | });
157 |
158 | // Nếu không có badword nào trong results, trả về phản hồi 'Word not found'
159 | if (hasBadword) {
160 | badwords.push(hasBadword);
161 | return res.status(200).json(
162 | {
163 | badWords: badwords,
164 | label: hasBadword.label,
165 | message: "This is VN badword"
166 | });
167 | }
168 | return res.status(404).json({ badWords: badwords, label: 0, message: "Word not found" });
169 | } catch (error) {
170 | console.log(error)
171 | return res.status(500).json({ error: "ERR" });
172 | }
173 | };
174 |
175 |
176 | exports.cleanWords = async (req, res, redis, prefix) => {
177 | const line = prefix + req.query.word;
178 | let badwords = [];
179 | try {
180 | // Cả chuỗi đều là badwords
181 | const findCache = await redis.get(line);
182 | const resCache = JSON.parse(findCache);
183 | if (findCache) {
184 | badwords.push(resCache);
185 | let cleanWords = '*'.repeat(resCache.name.length);
186 | return res.status(200).json(
187 | {
188 | badWords: badwords,
189 | label: badwords.length,
190 | cleanWord: cleanWords,
191 | message: "This is VN badword"
192 | });
193 | }
194 |
195 | let data = [];
196 | const keys = await redis.keys('*'); // Lấy tất cả các key trong Redis
197 | const pipeline = redis.pipeline(); // Tạo một pipeline để thực hiện các lệnh redis một cách tuần tự
198 |
199 | // Thêm các lệnh hỏi Redis để lấy dữ liệu tương ứng với từng key vào pipeline
200 | keys.forEach(key => {
201 | pipeline.get(key);
202 | });
203 |
204 | // Thực hiện pipeline để lấy dữ liệu từ Redis
205 | const results = await pipeline.exec();
206 |
207 | // Tạo một đối tượng chứa dữ liệu từ Redis
208 | results.forEach((result, index) => {
209 | const key = keys[index];
210 | const value = result[1]; // result[1] chứa giá trị được trả về từ Redis
211 | data.push({ key: key, value: JSON.parse(value) });
212 | });
213 | data.forEach(e => {
214 | if (line.includes(e.key.substring(5))) {
215 | checkContains = e.value.name;
216 | badwords.push(checkContains);
217 | // return;
218 | }
219 | })
220 |
221 | if (badwords.length > 0) {
222 | cleanWords = cleanWordsInLine(line.substring(5), badwords);
223 | return res.status(200).json(
224 | {
225 | badWords: badwords,
226 | label: badwords.length,
227 | cleanWord: cleanWords,
228 | message: "This is VN badword"
229 | });
230 | }
231 |
232 | return res.status(404).json({ badWords: badwords, label: 0, message: "This is not badword" });
233 | } catch (error) {
234 | return res.status(500).json({ error: "ERR", exp: error });
235 | }
236 | };
237 |
238 | exports.getCleanWords = async (req, res, redis) => {
239 | const line = req.query.word;
240 | let hasBadword; // Cờ để kiểm tra xem có badword không
241 | let badwords = [];
242 | try {
243 | // Cả chuỗi đều là badwords
244 | const findCache = await redis.get(line);
245 | const resCache = JSON.parse(findCache);
246 | if (findCache) {
247 | badwords.push(resCache);
248 | const cleanWords = '*'.repeat(resCache.name.length);
249 | return res.status(200).json(
250 | {
251 | badWords: badwords,
252 | label: badwords.length,
253 | cleanWord: cleanWords,
254 | message: "This is VN badword"
255 | });
256 | }
257 |
258 | // Tách từng chuỗi con
259 | const word = line.split(" ");
260 | const cache = word.map(async (name) => {
261 | const bad = await redis.get(name);
262 | return { name, bad };
263 | });
264 | const results = await Promise.all(cache);
265 |
266 | results.forEach(e => {
267 | if (e.bad != null) {
268 | hasBadword = JSON.parse(e.bad);
269 | badwords.push(hasBadword);
270 | }
271 | });
272 | if (badwords.length > 0) {
273 | const cleanWords = cleanWordsInLine(line, badwords);
274 | console.log(cleanWords)
275 | return res.status(200).json(
276 | {
277 | badWords: badwords,
278 | label: badwords.length,
279 | cleanWord: cleanWords,
280 | message: "This is VN badword"
281 | });
282 | }
283 | const name = line;
284 | const badw = await Badword.find({ name });
285 | console.log(badw.length > 0 && badw)
286 | if (badw && badw.length > 0) {
287 | badwords.push(badw);
288 | }
289 | const db = word.map(async (nameW) => {
290 | const bad = await Badword.find({ name: nameW });
291 | return { nameW, bad };
292 | });
293 | const dbResults = await Promise.all(db);
294 | dbResults.forEach(e => {
295 | if (e.bad.length > 0) {
296 | hasBadword = e.bad;
297 | return;
298 | }
299 | });
300 |
301 | if (hasBadword) {
302 | badwords.push(hasBadword);
303 | }
304 |
305 | if (badwords.length > 0) {
306 | const cleanWords = this.cleanWords(line, badwords);
307 | return res.status(200).json(
308 | {
309 | badWords: badwords,
310 | label: badwords.length,
311 | cleanWord: cleanWords,
312 | message: "This is VN badword"
313 | });
314 | }
315 |
316 | return res.status(404).json({ badWords: badwords, label: 0, message: "Word not found" });
317 | } catch (error) {
318 | console.log(error)
319 | return res.status(500).json({ error: "ERR", mesage: error });
320 | }
321 | };
322 |
323 |
324 | function cleanWordsInLine(s, badwords) {
325 | console.log("line: " + s);
326 | console.log(badwords)
327 | badwords.forEach((e, i) => {
328 | let find = s.indexOf(badwords[i]);
329 | while (find >= 0) {
330 | let sFirts = s.substring(0, find);
331 | let beep = '*'.repeat(badwords[i].length)
332 | let sLast = s.substring(find + badwords[i].length);
333 | s = `${sFirts}${beep}${sLast} `;
334 | find = s.indexOf(badwords[i], find + beep.length);
335 | }
336 | });
337 | return s.trim();
338 | }
339 |
340 |
341 | exports.getAllBadwordFromDB = async (req, res) => {
342 | try {
343 | const badwords = await Badword.find();
344 |
345 | return res.status(200).json({
346 | badwords
347 | });
348 | } catch (error) {
349 | return res.status(500).json({ error: "Error" });
350 | }
351 | };
352 |
353 | //api/db?name=cút
354 | exports.getBadwordFromDbByName = async (req, res) => {
355 | const name = req.query.name;
356 | try {
357 | const badwords = await Badword.find({ name });
358 | console.log(badwords.length)
359 | if (badwords.length == 0)
360 | return res.status(404).json({
361 | badWords: badwords,
362 | label: badwords.length,
363 | message: "Word not found"
364 | });
365 |
366 | if (badwords[0].deleted == true)
367 | return res.status(404).json({
368 | badWords: badwords,
369 | label: badwords.length,
370 | message: "Word not found"
371 | });
372 |
373 | return res.status(200).json({
374 | badwords: badwords,
375 | label: badwords.length,
376 | message: "This is VN badword"
377 | });
378 | } catch (error) {
379 | return res.status(500).json({ error: "Error" });
380 | }
381 | };
382 |
383 | exports.postBadwordToDB = async (req, res) => {
384 | const { name, label, severityLevel } = req.body;
385 | try {
386 | const badwords = await Badword.find({ name });
387 | if (badwords.length != 0)
388 | return res.status(201).json({
389 | badwords: badwords,
390 | status: 0,
391 | action: "Failed",
392 | message: "Word is same from database, please post new data"
393 | });
394 | if (badwords[0].deleted == true)
395 | return res.status(201).json({
396 | badwords: badwords,
397 | status: 0,
398 | action: "Failed",
399 | message: "The word has been deleted from the database. Would you like to restore it?"
400 | });
401 |
402 | const badword = {
403 | name: name,
404 | label: label || 1,
405 | severityLevel: severityLevel || 1,
406 | createDate: new Date()
407 | }
408 | const newBadword = new Badword(badword);
409 | const saveBadword = newBadword.save();
410 | return res.status(200).json({
411 | badword: newBadword,
412 | status: 1,
413 | action: "Successfully",
414 | message: "Word is saved to database"
415 | });
416 | } catch (error) {
417 | return res.status(500).json({ error: "Error" });
418 | }
419 | };
420 |
421 |
422 | exports.updateBadwordInDbByName = async (req, res) => {
423 | const { name, label, severityLevel } = req.body;
424 | try {
425 | const badwords = await Badword.find({ name });
426 | if (badwords.length == 0)
427 | return res.status(404).json({
428 | badwords: badwords,
429 | status: 0,
430 | action: "Failed",
431 | message: `Word with name = ${name} is not found`
432 | });
433 | console.log(badwords[0]._id);
434 | const badword = {
435 | name: name,
436 | label: label,
437 | severityLevel: severityLevel,
438 | createDate: badwords[0].createDate
439 | }
440 | const updatedBadword = await Badword.updateOne({ _id: badwords[0]._id }, { $set: badword });
441 | return res.status(200).json({
442 | badword: badword,
443 | status: 1 ? updatedBadword.acknowledged : 0,
444 | change: updatedBadword.modifiedCount,
445 | action: "Successfully",
446 | message: "Word is updated to database"
447 | });
448 | } catch (error) {
449 | return res.status(500).json({ error: "Error" });
450 | }
451 | };
452 |
453 |
454 | //api/db?name=cút
455 | exports.deleteBadwordInDbByName = async (req, res) => {
456 | const name = req.query.name;
457 | try {
458 | const badwords = await Badword.find({ name });
459 | console.log(badwords.length)
460 | if (badwords.length == 0)
461 | return res.status(404).json({
462 | badWords: badwords,
463 | label: badwords.length,
464 | message: "Word not found"
465 | });
466 |
467 | if (badwords[0].deleted == true)
468 | return res.status(200).json({
469 | badwords: badwords[0],
470 | status: -1,
471 | change: 0,
472 | action: "Failed",
473 | message: "The word has been deleted from the database. Would you like to restore it?"
474 | });
475 |
476 | // const deleteBadword = await Badword.deleteOne({ _id: badwords[0]._id });
477 | const deleteBadword = await Badword.updateOne({ _id: badwords[0]._id }, { $set: { deleted: true } });
478 | badwords[0].deleted = true;
479 | return res.status(200).json({
480 | badwords: badwords[0],
481 | status: 1 ? deleteBadword.acknowledged : 0,
482 | change: deleteBadword.deletedCount,
483 | action: "Successfully",
484 | message: "Word is deleted to database"
485 | });
486 | } catch (error) {
487 | return res.status(500).json({ error: "Error" });
488 | }
489 | };
490 | //CRUD MONGODB
491 |
--------------------------------------------------------------------------------