Mongoose is fixed & optimized

The dublicate key error fixed, ids as string.
classes removed. using mongoose's magic
This commit is contained in:
Akif9748 2022-08-09 19:16:34 +03:00
parent 4d0e2a8594
commit ae83e014a0
32 changed files with 1346 additions and 507 deletions

View File

3
.gitignore vendored
View File

@ -1,6 +1,9 @@
# Dependency directories
node_modules/
# env
.env
# Test files:
a.js
db.js

View File

@ -6,13 +6,13 @@ A forum software written in Node.js.
## Installation
- Clone or download this repo.
- Run `npm i` to install **dependencies**.
- Run `node reset` to **reset the database**, and `npm start` for run it.
- Run `node util/reset` to **reset the database**, and `npm start` for run it.
**Note:** Reseting the database is important!
## API
Akf-forum has got an API for other clients etc. You can test api with run apitest.py.
And, you can learn about API in `APIDOCS.md`.
And, you can learn about API in `util/APIDOCS.md`.
## Credits
* [Akif9748](https://github.com/Akif9748) - Project mainteiner, main developer
@ -23,6 +23,8 @@ And, you can learn about API in `APIDOCS.md`.
- Redirect query.
- middleware for timeouts
- DELETED USERS: USERLIST
- Will fix API
- if admin you van see deleted messages.
## Roadmap
### User

View File

@ -1,8 +0,0 @@
const Message = require("./message");
const Thread = require("./thread");
const User = require("./user");
/**
* Classes for akf-forum;
*/
module.exports = { Message, Thread, User };

View File

@ -1,57 +0,0 @@
const { MessageModel } = require("../models");
const User = require("./user");
module.exports = class Message {
constructor(content, author = User, threadID = null, time = Date.now(), deleted = false, edited = false, react = {}) {
this.authorID = author?.id;
this.content = content;
this.author = author;
this.time = time;
this.threadID = threadID;
this.deleted = deleted;
this.edited = edited;
this.react = react;
}
async getById(id = this.id) {
try {
this.id = Number(id);
const message = await MessageModel.findOne({ id });
if (!message) return null;
const { content, authorID, author = null, threadID = null, time = Date.now(), deleted = false, edited = false, react = {} } = message;
this.content = content;
this.threadID = threadID;
this.author = author;
this.authorID = authorID;
this.time = time;
this.deleted = deleted;
this.edited = edited;
this.react = react;
return this;
} catch (e) {
return null;
}
}
async takeId() {
this.id = await MessageModel.count({}) || 0;
return this;
}
async write(id = this.id) {
const writing = await MessageModel.findOneAndUpdate({ id }, this);
if (!writing)
await MessageModel.create(this);
return this;
}
getLink(id = this.id) {
return "/messages/" + id;
}
}

View File

@ -1,63 +0,0 @@
const User = require("./user")
const { ThreadModel } = require("../models");
module.exports = class Thread {
constructor(title, author = User, messages = [], time = Date.now(), deleted = false) {
this.author = author;
this.authorID = author?.id;
this.title = title;
this.messages = messages;
this.time = time;
this.deleted = deleted;
}
async getById(id = this.id) {
try {
this.id = Number(id);
const thread = await ThreadModel.findOne({ id });
if (!thread) return null;
const { title, authorID, author, messages = [], time = Date.now(), deleted = false } = thread;
this.title = title
this.author = author;
this.authorID = authorID;
this.messages = messages;
this.time = time;
this.deleted = deleted;
return this;
} catch (e) {
return null;
}
}
push(messageID) {
this.messages.push(messageID)
return this;
}
async takeId() {
this.id = await ThreadModel.count({}) || 0;
return this;
}
async write(id = this.id) {
const writing = await ThreadModel.findOneAndUpdate({ id }, this);
if (!writing)
await ThreadModel.create(this);
return this;
}
getLink(id = this.id) {
return "/threads/" + id;
}
}

View File

@ -1,75 +0,0 @@
const { UserModel } = require("../models");
module.exports = class User {
constructor(name = "guest", avatar = "/images/guest.png", time = Date.now(), admin = false, deleted = false) {
this.name = name;
this.avatar = avatar;
this.time = time;
this.admin = admin;
this.deleted = deleted;
}
async getById(id = this.id) {
try {
this.id = Number(id);
const user = await UserModel.findOne({ id });
if (!user) return null;
const { name = "guest", avatar = "/images/guest.png", time = Date.now(), admin = false, deleted = false } = user;
this.name = name;
this.avatar = avatar;
this.time = time;
this.admin = admin;
this.deleted = deleted;
return this;
} catch (e) {
return null;
}
}
async getByName(Name = this.name) {
try {
const user = await UserModel.findOne({ name: Name });
if (!user) return null;
const { id, name = "guest", avatar = "/images/guest.png", time = Date.now(), admin = false, deleted = false } = user;
this.id = Number(id);
this.name = name;
this.avatar = avatar;
this.time = time;
this.admin = admin;
this.deleted = deleted;
return this;
} catch (e) {
return null;
}
}
async takeId() {
this.id = await UserModel.count({}) || 0;
return this;
}
async write(id = this.id) {
const writing = await UserModel.findOneAndUpdate({ id }, this);
if (!writing)
await UserModel.create(this);
return this;
}
getLink(id = this.id) {
return "/users/" + id;
}
}

View File

@ -1,13 +1,14 @@
const error = require("./errors/error.js"),
session = require('express-session'),
bodyParser = require('body-parser'),
port = process.env.PORT ?? 3000,
port = process.env.PORT || 3000,
mongoose = require("mongoose"),
express = require('express'),
fs = require("fs"),
app = express();
mongoose.connect(process.env.MONGO_DB_URL ?? "mongodb://localhost:27017/akf-forum", () => console.log("Database is connected"));
require("dotenv").config();
mongoose.connect(process.env.MONGO_DB_URL || "mongodb://localhost:27017/akf-forum", () => console.log("Database is connected"));
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }));
app.use(bodyParser.urlencoded({ extended: true }));

View File

@ -1,4 +1,4 @@
module.exports = (req, res, next) => {
if (!req.session.userid?.toString()) return res.redirect('/login');
if (!req.session.userid) return res.redirect('/login');
next();
}

View File

@ -1,6 +1,6 @@
const { User } = require("../classes");
const { UserModel } = require("../models");
module.exports = async (req, res, next) => {
req.user = await new User().getById(req.session.userid);
req.user = await UserModel.get(req.session.userid);
next();
}

View File

@ -1,17 +1,34 @@
const { Schema, model } = require("mongoose")
const mongoose = require("mongoose")
const UserModel = require("./User");
module.exports = model('message', new Schema({
id: { type: Number, unique: true },
const schema = new mongoose.Schema({
id: { type: String, unique: true },
threadID: String,
author: UserModel.schema, // user-model
authorID: Number,
threadID: Number,
author: Object,
content: String,
time: Number,
time: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
edited: { type: Boolean, default: false },
messages: [Number],
react: Object
react: { type:Object, default: {} }
})
schema.virtual('authorID').get(function() { return this.author?.id; });
schema.methods.takeId = async function () {
this.id = String(await model.count() || 0);
return this;
}
schema.methods.getLink = function (id = this.id) {
return "/messages/" + id;
}
const model = mongoose.model('message', schema);
model.get = id => model.findOne({ id });
module.exports = model;
}))

View File

@ -1,10 +1,12 @@
const { Schema, model } = require("mongoose")
module.exports = model('secret', new Schema({
const schema = new Schema({
username: { type: String, unique: true },
password: String,
id: { type: Number, unique: true }
id: { type:String, unique: true }
}))
});
module.exports = model('secret', schema);

View File

@ -1,15 +1,36 @@
const { Schema, model } = require("mongoose")
const mongoose = require("mongoose")
const UserModel = require("./User");
const schema = new mongoose.Schema({
id: { type: String, unique: true },
module.exports = model('thread', new Schema({
id: { type: Number, unique: true },
authorID: Number,
author: Object,
author: UserModel.schema,
title: String,
time: Number,
time: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
messages: [Number]
messages: [String]
}))
});
schema.virtual('authorID').get(function() { return this.author?.id; });
schema.methods.push = function (messageID) {
this.messages.push(messageID);
return this;
}
schema.methods.takeId = async function () {
this.id = await model.count() || 0;
return this;
}
schema.methods.getLink = function (id = this.id) {
return "/threads/" + id;
}
const model = mongoose.model('thread', schema);
model.get = id => model.findOne({ id });
module.exports = model;

View File

@ -1,12 +1,28 @@
const { Schema, model } = require("mongoose")
const mongoose = require("mongoose")
module.exports = model('user', new Schema({
id: { type: Number, unique: true },
const schema = new mongoose.Schema({
id: { type: String, unique: true },
name: String,
avatar: String,
time: Number,
avatar: { type: String, default: "/images/guest.png" },
time: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false },
admin: { type: Boolean, default: false }
}))
});
schema.methods.takeId = async function () {
this.id = String(await model.count() || 0);
return this;
}
schema.methods.getLink = function (id = this.id) {
return "/users/" + id;
}
const model = mongoose.model('user', schema);
model.get = id => model.findOne({ id });
module.exports = model;

1366
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -21,10 +21,12 @@
},
"homepage": "https://akf-forum.herokuapp.com/",
"dependencies": {
"bcrypt": "^5.0.1",
"body-parser": "^1.19.2",
"dotenv": "^16.0.1",
"ejs": "^3.1.6",
"express": "^4.17.3",
"express-session": "^1.17.2",
"mongoose": "^6.2.9"
"mongoose": "^6.5.1"
}
}

View File

@ -1,4 +1,4 @@
const { User } = require("../classes");
const { UserModel } = require("../models")
const { Router } = require("express")
const error = require("../errors/error")
@ -20,14 +20,14 @@ app.post("/", async (req, res) => {
const user = req.user;
if (!user.admin) return error(res, 403, "You have not got permissions for view to this page.");
const user2 = await new User().getById(req.body.userid)
const user2 = await UserModel.get(req.body.userid);
if (!user2)
return error(res, 404, "We have not got this user in all of the forum. Vesselam.");
else {
user2.admin = true;
user2.write()
await user2.save()
}
res.render("admin", { user, user2 })

View File

@ -1,7 +1,10 @@
const { Router } = require("express")
const app = Router();
/**
* @deprecated
* for less time
*/
const { request, response } = require("express");
const { SecretModel } = require("../../models")
const ApiResponse = require("./ApiResponse")
@ -31,7 +34,7 @@ const ApiResponse = require("./ApiResponse")
* @param {request} req
* @param {response} res
*/
/*
app.use(async (req, res, next) => {
const error = (status, error) =>
res.status(status).json(new ApiResponse(status, { error }))
@ -54,7 +57,7 @@ app.use(async (req, res, next) => {
app.use("/messages", require("./routes/message"))
app.use("/users", require("./routes/user"))
app.use("/threads", require("./routes/threads"))
*/
app.all("*", (req, res) => res.status(400).json(new ApiResponse(400, { error: "Bad request" })));
module.exports = app;

View File

@ -31,8 +31,8 @@ app.post("/", async (req, res) => {
const message = await new Message(content, await new User().getByName(req.headers.username), thread.id).takeId()
message.write();
thread.push(message.id).write();
message.save();
thread.push(message.id).save();
res.status(200).json(new ApiResponse(200, message));

View File

@ -33,8 +33,8 @@ app.post("/", async (req, res) => {
const user = await new User().getByName(req.headers.username)
const thread = await new Thread(title, user).takeId()
const message = await new Message(content, user, thread.id).takeId()
thread.push(message.id).write();
await message.write();
thread.push(message.id).save();
await message.save();
res.status(200).json(new ApiResponse(200, thread));

View File

@ -1,6 +1,5 @@
const { User } = require("../classes");
const { UserModel, SecretModel } = require("../models");
const { Router } = require("express");
const { SecretModel } = require("../models");
const error = require("../errors/error");
const app = Router();
@ -15,7 +14,7 @@ app.post("/", async (req, res) => {
const user = await SecretModel.findOne({ username });
if (user) {
if (user.password !== password) return error(res, 403, 'Incorrect Password!')
const member = await new User().getByName(username)
const member = await UserModel.findOne({ name: username });
if (!member || member.deleted) return error(res, 403, 'Incorrect Username and/or Password!')
req.session.userid = user.id;

View File

@ -1,4 +1,4 @@
const { Thread, Message, User } = require("../classes");
const { ThreadModel, MessageModel } = require("../models");
const error = require("../errors/error")
const { Router } = require("express");
@ -6,7 +6,7 @@ const { Router } = require("express");
const app = Router();
app.get("/:id", async (req, res) => {
const message = await new Message().getById(req.params.id);
const message = await MessageModel.get(req.params.id);
if (!message || message.deleted) return error(res, 404, "We have not got any message declared as this id.");
res.redirect("/threads/" + message.threadID);
@ -18,12 +18,11 @@ app.use(require("../middlewares/login"));
app.post("/", async (req, res) => {
const thread = await new Thread().getById(req.body.threadID);
const thread = await ThreadModel.get(req.body.threadID);
if (thread) {
const message = await new Message(req.body.content, req.user, thread.id).takeId()
await message.write();
thread.push(message.id);
thread.write();
const message = await new MessageModel({ content: req.body.content, author: req.user, threadID: thread.id }).takeId();
await message.save();
await thread.push(message.id).save();
res.redirect('/threads/' + req.body.threadID);
@ -34,29 +33,29 @@ app.post("/", async (req, res) => {
});
app.post("/:id/delete", async (req, res) => {
const message = await new Message().getById(req.params.id)
const message = await MessageModel.get(req.params.id);
if (!message || message.deleted) return error(res, 404, "We have not got any message declared as this id.");
const user = req.user;
if (user.id != message.authorID && !user.admin)
return error(res, 403, "You have not got permission for this.");
message.deleted = true;
message.write();
await message.save();
res.status(200).redirect("/threads/" + message.threadID);
res.status(200).redirect( "/threads/" + message.threadID);
})
app.post("/:id/react", async (req, res) => {
const { id = null } = req.params;
const info = req.body;
const message = await new Message().getById(id);
const message = await MessageModel.get(req.params.id);
if (message) {
if (!(req.session.userid in message.react))
message.react[req.session.userid] = "like" in info;
else
if (req.user.id in message.react)
delete message.react[req.session.userid];
else
message.react[req.session.userid] = "like" in info;
message.write();
await message.save();
res.redirect("/threads/" + message.threadID);
} else error(res, 404, "We have not got this Message for reacting.");

View File

@ -1,6 +1,4 @@
const { User } = require("../classes");
const { SecretModel } = require("../models");
const { UserModel, SecretModel } = require("../models");
const { Router } = require("express")
const error = require("../errors/error")
@ -12,7 +10,7 @@ app.post("/", async (req, res) => {
req.session.userid = null;
let { username = null, password = null, avatar } = req.body;
let { username = null, password = null, avatar } = req.body;
if (username && password) {
const user = await SecretModel.findOne({ username });
@ -22,15 +20,13 @@ app.post("/", async (req, res) => {
else {
if (!avatar) avatar ="/images/guest.png";
const user2 = await new User(req.body.username, avatar).takeId();
const user2 = new UserModel({ name: req.body.username, avatar })
await user2.takeId()
await user2.save();
await SecretModel.create({ username, password, id: user2.id })
req.session.userid = user2.id;
user2.write();
res.redirect('/');
}

View File

@ -1,26 +1,24 @@
const { Router } = require("express");
const app = Router();
const { Thread, Message, User } = require("../classes");
const error = require("../errors/error")
const { ThreadModel } = require("../models")
const { ThreadModel, MessageModel } = require("../models")
app.get("/", async (req, res) => {
const user = req.user;
const threads = await Promise.all((await ThreadModel.find({}).limit(10))
.map(async threads => await new Thread().getById(threads.id)));
const threads = await ThreadModel.find({}).limit(10);
return res.render("threads", { threads, user });
});
app.get("/open*", async (req, res) => {
app.get("/create*", async (req, res) => {
const user = req.user
res.render("openThread", { user })
res.render("createThread", { user })
});
@ -28,13 +26,13 @@ app.get("/:id", async (req, res) => {
const { id } = req.params;
const thread = await new Thread().getById(id);
const thread = await ThreadModel.get(id);
if (thread) {
const user = req.user;
const messages = await Promise.all(thread.messages.map(async id => {
const message = await new Message().getById(id)
const message = await MessageModel.get(id)
return (message.deleted || !message) ? null : message;
}));
@ -54,13 +52,14 @@ app.post("/", async (req, res) => {
const { title = null, content = null } = req.body;
if (!title || !content) return error(res, 400, "Title and/or content is missing");
const user = req.user
const thread = await new Thread(title, user).takeId()
const message = await new Message(content, user, thread.id).takeId()
const user = req.user
const thread = await new ThreadModel({ title, author: user }).takeId()
thread.push(message.id).write();
const message = await new MessageModel({ content, author: user, threadID: thread.id }).takeId()
message.write();
await thread.push(message.id).save();
await message.save();
res.redirect('/threads/' + thread.id);
})

View File

@ -2,31 +2,26 @@ const { Router } = require("express");
const app = Router();
const error = require("../errors/error");
const { User } = require("../classes");
const { UserModel, MessageModel, ThreadModel } = require("../models");
app.get("/", async (req, res) => {
const user = req.user
const users = await UserModel.find({});
const links = users.filter(user => !user.deleted).map(user => "/users/" + user.id);
return res.render("users", { users, links, user })
const users = await UserModel.find({ deleted: false });
return res.render("users", { users, user })
});
app.get("/:id", async (req, res) => {
const user = req.user
const { id = null } = req.params;
const member = await new User().getById(req.params.id);
const member = await UserModel.get(id);
if (member && (user?.admin || !member.deleted)) {
const message = await MessageModel.count({ authorID: id });
const thread = await ThreadModel.count({ authorID: id });
const counts = { message, thread }
res.render("user", { user, member, counts })
res.render("user", { user, member, counts:{ message, thread } })
}
else error(res, 404, "We have not got this user.");
@ -41,12 +36,12 @@ app.post("/:id/delete/", async (req, res) => {
return error(res, 403, "You have not got permission for this.");
const { id = null } = req.params;
const member = await new User().getById(id);
const member = await UserModel.get(id);
if (!member || member.deleted) return error(res, 404, "We have not got any user declared as this id.");
member.deleted = true;
member.write();
await member.save();
res.redirect("/admin");
});

15
util/admin.js Normal file
View File

@ -0,0 +1,15 @@
const mongoose = require("mongoose");
require("dotenv").config();
mongoose.connect(process.env.MONGO_DB_URL, () => console.log("Database is connected"));
const { UserModel } = require("../models");
(async () => {
const member= await UserModel.get(0);
console.log(member);
member.admin = true;
member.save();
})();

View File

@ -1,7 +1,9 @@
const mongoose = require("mongoose");
mongoose.connect('mongodb://localhost:27017/akf-forum', () => console.log("Database is connected"));
require("dotenv").config();
const { SecretModel, UserModel, MessageModel, ThreadModel } = require("./models");
mongoose.connect(process.env.MONGO_DB_URL, () => console.log("Database is connected"));
const { SecretModel, UserModel, MessageModel, ThreadModel } = require("../models");
(async () => {
await UserModel.deleteMany({});

View File

@ -1,7 +1,7 @@
<!DOCTYPE html>
<html lang="en">
<%- include("extra/header", {title: "Open Thread!" }) %>
<%- include("extra/header", {title: "Create Thread!" }) %>
<body>

View File

@ -2,7 +2,7 @@
<link rel="stylesheet" href="/css/styles.css" />
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= title ?? "Akf-forum" %> </title>
<title><%= title || "Akf-forum" %> </title>
<meta name="description" content="Akf-forum!">
<meta name="author" content="Akif9748">
<link rel="icon" type="image/x-icon" href="/images/favicon.jpg">

View File

@ -16,7 +16,7 @@
<a href="/threads">THREADS</a>
<a href="/users">USERS</a>
<a href="/search">SEARCH</a>
<a href="/threads/open/">CREATE THREAD</a>
<a href="/threads/create/">CREATE THREAD</a>
<% if (user){ %>
<div style="float: right;" class="user" id="user">

View File

@ -12,7 +12,7 @@
<ul>
<% users.forEach(user=>{ %>
<li>
<h1><a href=<%=links[user.id] %> > <%= user.name %></a>
<h1><a href=<%= user.getLink() %> > <%= user.name %></a>
<img class="yuvarlak" src=<%=user.avatar %> alt=<%= user.name %>>
</h1>
</li>