├── 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: `_` 30 | 31 | ### 4. Thực hiện các thay đổi cần thiết trong mã nguồn của bạn. 32 | 33 | ### 5. Commit 34 | Commit các thay đổi của bạn: `git commit -m 'feat: new feature'` 35 | 36 | Lưu ý: Vui lòng tham khảo tại [đây](https://davisupers.web.app/github/session7.html) để commit trở nên chuẩn nhất 37 | 38 | ### 6. Đẩy nhánh của bạn lên repository của bạn trên GitHub: `git push origin feature/your-feature` 39 | 40 | ### 7. Tạo PR 41 | 42 | Tạo một yêu cầu kéo (pull request) tới nhánh `contribute` của dự án chính. 43 | 44 | ## Quy Tắc Đóng Góp 45 | 46 | - Hãy tôn trọng mã nguồn mở của dự án và người khác tham gia. 47 | - Đảm bảo rằng mã của bạn tuân thủ các tiêu chuẩn mã hoá và phong cách của dự án. 48 | - Hãy tạo mô tả rõ ràng và chấp nhận yêu cầu kéo. 49 | 50 | ## Báo Lỗi và Đề Xuất Tính Năng 51 | 52 | Nếu bạn phát hiện lỗi hoặc có ý kiến đề xuất tính năng, vui lòng mở một issue trên GitHub. 53 | 54 | ## Liên Hệ 55 | 56 | Nếu bạn có bất kỳ câu hỏi hoặc gặp vấn đề, xin đừng ngần ngại liên hệ với chúng tôi qua [email](mailto:dangthpc@gmail.com) hoặc [issues trên GitHub](https://github.com/theanishtar/VulgarVeto/issues). 57 | 58 | Cảm ơn bạn đã quan tâm và đóng góp vào dự án của chúng tôi! 59 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const express = require("express"); 2 | const cors = require("cors"); 3 | const cookieSession = require("cookie-session"); 4 | const bodyParser = require('body-parser'); 5 | const http = require('http'); // Import module http 6 | const Redis = require("ioredis"); 7 | const db = require("./app/models"); 8 | const config = require('./app/config/index'); 9 | var dotent = require('dotenv'); 10 | const fs = require('fs'); 11 | 12 | const app = express(); 13 | const server = http.createServer(app); // Tạo server từ express app 14 | 15 | dotent.config(); 16 | app.use(cors()); 17 | app.use(bodyParser.json()); 18 | // parse requests of content-type - application/x-www-form-urlencoded 19 | app.use(express.urlencoded({ extended: true })); 20 | 21 | //---------- CONFIG SERVER --------------------- 22 | // set port, listen for requests 23 | const PORT = process.env.PORT || 5152; 24 | const redisURI = process.env.REDIS_URI; 25 | const mongodbURI = process.env.MONGODB_URI; 26 | const prefix = process.env.PREFIX; 27 | /*----------------------------------------------*/ 28 | /**--------------------- DB CONNECTIONS -------------------------*/ 29 | const connectionStatus = { 30 | redis: false, 31 | mongoDB: false 32 | } 33 | const redis = new Redis(redisURI); // Khởi tạo một đối tượng Redis 34 | // Kiểm tra trạng thái kết nối 35 | redis.on("connect", function () { 36 | connectionStatus.redis = true; 37 | console.log("Connected to Redis successfully!"); 38 | }); 39 | // Xử lý lỗi kết nối 40 | redis.on("error", function (error) { 41 | console.error("Redis connection error:", error); 42 | }); 43 | 44 | db.mongoose 45 | .connect(mongodbURI, { 46 | useNewUrlParser: true, 47 | useUnifiedTopology: true 48 | }) 49 | .then(() => { 50 | connectionStatus.mongoDB = true; 51 | console.log("Successfully connect to MongoDB." + mongodbURI); 52 | }) 53 | .catch(err => { 54 | console.error("Connection error", err); 55 | process.exit(); 56 | }); 57 | 58 | /*-------------------------- ROUTES ------------------- */ 59 | app.get('/', (req, res) => { 60 | res.json({ 61 | live: "Hello server is live", 62 | connection: connectionStatus 63 | }); 64 | }); 65 | require("./app/routes/badword.route")(app, redis, prefix); 66 | require("./app/routes/cache.route")(app, redis, prefix); 67 | require("./app/routes/auth.route")(app); 68 | require("./app/routes/db.route")(app); 69 | require("./app/routes/contribute.route")(app); 70 | 71 | 72 | //Thay vì sử dụng app.listen, sử dụng server.listen để sử dụng cùng một cổng cho cả express app và Socket.IO: 73 | server.listen(PORT, '0.0.0.0', () => { 74 | console.log(`Server is running on: http://localhost:${PORT}`); 75 | }); 76 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | server.pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | # .env.public 82 | 83 | # parcel-bundler cache (https://parceljs.org/) 84 | .cache 85 | .parcel-cache 86 | 87 | # Next.js build output 88 | .next 89 | out 90 | 91 | # Nuxt.js build / generate output 92 | .nuxt 93 | dist 94 | 95 | # Gatsby files 96 | .cache/ 97 | # Comment in the public line in if your project uses Gatsby and not Next.js 98 | # https://nextjs.org/blog/next-9-1#public-directory-support 99 | # public 100 | 101 | # vuepress build output 102 | .vuepress/dist 103 | 104 | # vuepress v2.x temp and cache directory 105 | .temp 106 | .cache 107 | 108 | # Docusaurus cache and generated files 109 | .docusaurus 110 | 111 | # Serverless directories 112 | .serverless/ 113 | 114 | # FuseBox cache 115 | .fusebox/ 116 | 117 | # DynamoDB Local files 118 | .dynamodb/ 119 | 120 | # TernJS port file 121 | .tern-port 122 | 123 | # Stores VSCode versions used for testing VSCode extensions 124 | .vscode-test 125 | 126 | # yarn v2 127 | .yarn/cache 128 | .yarn/unplugged 129 | .yarn/build-state.yml 130 | .yarn/install-state.gz 131 | .pnp.* 132 | package-lock.json 133 | Archive.zip 134 | app/.DS_Store 135 | .DS_Store 136 | # package.json 137 | -------------------------------------------------------------------------------- /app/controllers/contribute.controller.js: -------------------------------------------------------------------------------- 1 | const Contribute = require("../models/contribute.model"); 2 | const Users = require("../models/user.model"); 3 | const Badword = require("../models/badword.model"); 4 | 5 | exports.getAll = async (req, res) => { 6 | const contributes = await Contribute.find(); 7 | res.json(contributes) 8 | }; 9 | 10 | exports.save = async (req, res) => { 11 | const { name, label, severityLevel, contributor } = req.body; 12 | const { fullname, mess } = contributor; 13 | 14 | const contribute = { 15 | name: name || "nonameyet", 16 | label: label || 1, 17 | severityLevel: severityLevel || 1, 18 | createDate: new Date(), 19 | contributor: { 20 | fullname: "Trần Hữu Đang", 21 | mess: "Lời nhắn 1" 22 | } 23 | }; 24 | 25 | const newContribute = new Contribute(contribute); 26 | const saveContribute = newContribute.save(); 27 | return res.status(200).json({ 28 | badword: newContribute, 29 | status: 1, 30 | action: "Successfully", 31 | message: "Word is saved to database temp" 32 | }); 33 | } 34 | 35 | exports.contributeBadword = async (req, res) => { 36 | const { author, message, badword, word } = req.body; 37 | // const { label, severityLevel, name } = req.body.badword; 38 | 39 | const contribute = { 40 | author: author || "nonameyet", 41 | message: message || "nobioyet", 42 | word: word, 43 | badword: { 44 | name: word 45 | } 46 | }; 47 | 48 | const exits = await Contribute.find({ word: word }); 49 | 50 | if (exits.length > 0) { 51 | return res.status(200).json({ 52 | badword: contribute.badword, 53 | status: 0, 54 | action: "Failed", 55 | message: "Word is exits from database temp" 56 | }); 57 | } 58 | 59 | const newContribute = new Contribute(contribute); 60 | const saveContribute = newContribute.save(); 61 | return res.status(200).json({ 62 | badword: contribute.badword, 63 | status: 1, 64 | action: "Successfully", 65 | message: "Word is saved to database temp" 66 | }); 67 | } 68 | 69 | 70 | exports.addContributeToDB = async (req, res) => { 71 | try { 72 | const user = await Users.find({ token: req.query.tk, username: req.query.un }); 73 | 74 | if (user.length === 0) { 75 | return res.status(200).json({ 76 | author: user, 77 | status: 0, 78 | action: "Failed", 79 | message: "Username or token unvaliable" 80 | }) 81 | } 82 | 83 | const badwordContribute = await Contribute.find(); 84 | let saveToDB = []; 85 | let existingBadwords = []; 86 | 87 | for (let obj of badwordContribute) { 88 | 89 | if (!obj.added) { 90 | const badwordSame = await Badword.find({ name: obj.badword.name }); 91 | 92 | if (badwordSame.length === 0) { 93 | //Lưu vào conllection badwords 94 | const newBadword = new Badword(obj.badword); 95 | const saveBadword = newBadword.save(); 96 | saveToDB.push(obj.badword.name); 97 | 98 | // cập nhật trạng thái đã thêm 99 | await Contribute.findByIdAndUpdate(obj._id, { added: true }); 100 | } else { 101 | existingBadwords.push(obj.badword.name); 102 | } 103 | } 104 | } 105 | 106 | return res.status(200).json({ 107 | author: user.fullname, 108 | newBadword: saveToDB.length, 109 | existing: existingBadwords.length, 110 | details: { 111 | saveToDB: saveToDB, 112 | existingBadwords: existingBadwords 113 | } 114 | }); 115 | } catch (error) { 116 | return res.status(400).json(error); 117 | } 118 | } 119 | 120 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Chào mừng đến với Vetonary Check APIs 3 | 4 | Vetonary APIs cung cấp các công cụ mạnh mẽ cho các nhà phát triển để kiểm tra và phân tích nội dung văn bản để phát hiện những từ ngữ thô tục, phản cảm, không phù hợp. Các API của chúng tôi giúp bạn duy trì một môi trường sạch sẽ và an toàn cho người dùng và cộng đồng của bạn. 5 | 6 |

7 |
8 | GitHub last commit 9 | 10 | GitHub forks 11 | GitHub Repo stars 12 | Discord 13 | 14 |

15 | 16 | --- 17 | 18 | Truy cập vào trang website chính thức của chúng tôi tại [https://vetonary.web.app/](https://vetonary.web.app/) hoặc [https://vetonary.vercel.app/](https://vetonary.vercel.app/) để sử dụng miễn phí các APIs 19 | 20 | ## Phụ lục 21 | 22 | - [Đôi nét về dự án](https://www.facebook.com/groups/devoiminhdidauthe/permalink/24628197220157317/?mibextid=oFDknk) 23 | - [Mô tả về Server](./README-API.md) 24 | - [Đóng góp từ ngữ cho chúng tôi tại](https://theanishtar.github.io/vetonary/) 25 | - [Danh sách APIs](./APIs.md) 26 | 27 | --- 28 | 29 | Việc kiểm tra các từ ngữ là rất quan trọng với những dự án cộng đồng như mạng xã hội hay diễn đàn 30 | 31 | Bạn cũng có thể cung cấp thêm từ ngữ thô tục cho chúng tôi tại [đây](https://theanishtar.github.io/vetonary/). Rất cảm ơn ! 32 | 33 | 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 |
68 |
69 | Image 1 70 |
71 | 72 |
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 | ![](https://github.com/theanishtar/vietnamese-bad-words-detector/blob/main/images/data-in-mongodb.png?raw=true) 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 | --------------------------------------------------------------------------------