diff --git a/APIDOCS.md b/APIDOCS.md
index 1df62d1..04231eb 100644
--- a/APIDOCS.md
+++ b/APIDOCS.md
@@ -25,7 +25,6 @@ But in front end, the API will works with session.
- DELETE `/api/users/:id/` for delete user.
- PATCH `/api/users/:id/` for edit user.
- POST `/api/users/:id/undelete` for undelete user.
-- POST `/api/users/:id/admin` for give admin permissions for a user.
- GET `/api/threads/:id` for fetch thread.
- DELETE `/api/threads/:id/` for delete thread.
diff --git a/README.md b/README.md
index 8255e81..c37c0f7 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,7 @@ Run `node util/reset` to **reset the database**, and run `node util/admin` for g
Edit `config.json` for default themes of users...
## API
-Akf-forum has got an API for AJAX, other clients etc. And, you can learn about API in `util/APIDOCS.md`.
+Akf-forum has got an API for AJAX (fetch), other clients etc. And, you can learn about API in `util/APIDOCS.md`.
## Credits
* [Akif9748](https://github.com/Akif9748) - Project mainteiner, main developer, made **old** frontend
@@ -32,23 +32,17 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
| To do | Is done? | Priority |
| ----- | -------- | -------- |
| Profile Message | 🔴 | LOW |
-| from form to AJAX | 🟢 | HIGH |
-| auto-scroll | 🟡 | LOW |
-| Page support, support message limit correct | 🟢 | MEDIUM |
-| Multi-theme support, black theme | 🟢 | LOW |
| Search | 🔴 | MEDIUM |
-| Footer | 🟢 | LOW |
+| Footer | 🟡 | LOW |
-- Profile photos will store in database
+- Better Auth
+- Profile photos will store in a folder
- replacer function global
-- author name of thread
- page for threads - users
-- extra ratelimits
-- better edits
-- IP BAN fix, user -> ips []
+- IPs of users will add SecretModel
- message counts for API
-- ZATEN SİLİNDİ BU KİŞİ & MESAJ
-- delete admin request, moreover, add it to user patch delete 😳, better theme patch
+- better theme patch UserModel
+- ajax, delete update thread dom
### API
| To do | Is done?
diff --git a/index.js b/index.js
index e3dc9b2..db57fcd 100644
--- a/index.js
+++ b/index.js
@@ -8,6 +8,7 @@ const { UserModel, BanModel } = require("./models"),
express = require('express'),
fs = require("fs"),
app = express();
+const rateLimit = require('express-rate-limit')
app.ips = [];
@@ -17,10 +18,11 @@ mongoose.connect(process.env.MONGO_DB_URL,
app.set("view engine", "ejs");
-app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }),
- bodyParser.urlencoded({ extended: true }),
+app.use(
+ session({ secret: 'secret', resave: true, saveUninitialized: true }),
express.static("public"), express.json(), ipBlock(app.ips),
async (req, res, next) => {
+ req.headers["x-forwarded-for"]
req.user = await UserModel.get(req.session.userID);
res.reply = (page, options = {}, status = 200) => res.status(status)
.render(page, { user: req.user, theme: req.user?.theme || def_theme, ...options });
@@ -32,7 +34,10 @@ app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }),
return res.error(403, "Your account has been deleted.");
}
next();
- }
+ }, rateLimit({
+ windowMs: 60_000, max: 10,
+ handler: (req, res, _n, opts) => !req.user.admin ? res.error(opts.statusCode, "You are begin ratelimited") : next()
+ }), bodyParser.urlencoded({ extended: true })
);
for (const file of fs.readdirSync("./routes"))
diff --git a/models/Secret.js b/models/Secret.js
index e8c71e0..c42e701 100644
--- a/models/Secret.js
+++ b/models/Secret.js
@@ -2,7 +2,7 @@ const mongoose = require("mongoose")
const schema = new mongoose.Schema({
username: { type: String, unique: true },
- password: String,
+ password: String, ips: [String],
id: { type: String, unique: true }
});
diff --git a/public/css/thread.css b/public/css/thread.css
index daa33d8..48b2aca 100644
--- a/public/css/thread.css
+++ b/public/css/thread.css
@@ -149,7 +149,12 @@
}
-
+.send>textarea{
+ font-family:inherit;
+ width: 100%;
+ margin: 10px;
+ border: 2px solid #e3e3e3;
+}
/* Media Query */
@media(max-width:980px) {
diff --git a/public/js/thread.js b/public/js/thread.js
index 0dad06b..1ea48c0 100644
--- a/public/js/thread.js
+++ b/public/js/thread.js
@@ -1,5 +1,7 @@
import request from "./request.js";
+// THREAD:
+
window.edit_thread = async function (id) {
const title = prompt("Enter new title!");
const res = await request(`/api/threads/${id}/`, "PATCH", { title });
@@ -11,7 +13,6 @@ window.edit_thread = async function (id) {
window.delete_thread = async function (id) {
const res = await request(`/api/threads/${id}/`, "DELETE");
if (res.error) return;
-
alert(`Thread deleted`);
location.reload();
}
@@ -19,41 +20,56 @@ window.delete_thread = async function (id) {
window.undelete_thread = async function (id) {
const res = await request(`/api/threads/${id}/undelete`);
if (res.error) return;
-
alert(`Thread undeleted`);
location.reload();
}
-window.edit_message = async function (id) {
- const content = prompt("Enter new content!");
- const res = await request(`/api/messages/${id}/`, "PATCH", { content });
- if (res && res.error) return;
+
+// MESSAGES:
+window.send_edit = async function (id) {
+ const message = document.getElementById(`message-${id}`);
+ const content = message.querySelector("#content").value;
+
+ const res = await request(`/api/messages/${id}/`, "PATCH", { content });
+ if (res.error) return;
alert(`Message updated`);
- document.getElementById("message-" + id).querySelector(".content").innerHTML = content;
+ message.querySelector(".content").innerHTML = res.content;
+}
+window.edit_message = async function (id) {
+ const content = document.getElementById(`message-${id}`).querySelector(".content");
+
+ content.innerHTML = `
+
+ `;
}
window.undelete_message = async function (id) {
const response = await request(`/api/messages/${id}/undelete`);
if (response.deleted) return;
- document.getElementById("deleted-" + id).remove();
- document.getElementById("dot-" + id).innerHTML = `
+ const message = document.getElementById("message-" + id);
+
+ message.querySelector("#deleted").remove();
+ message.querySelector(".dots-menu").innerHTML = `
DELETE
EDIT`
}
window.delete_message = async function (id) {
const response = await request(`/api/messages/${id}/`, "DELETE");
- if (response.deleted) {
- alert("Message deleted");
- document.getElementById("dots-" + id).innerHTML = `
-
- `+ document.getElementById("dots-" + id).innerHTML;
- document.getElementById("dot-" + id).innerHTML = `UNDELETE`;
- }
+ if (!response.deleted) return
+ const message = document.getElementById(`message-${id}`);
+ alert("Message deleted");
+
+ message.querySelector(".dots-menu").innerHTML = `UNDELETE`;
+
+ let dots = message.querySelector(".dots");
+ dots.innerHTML = "" + dots.innerHTML;
+
}
window.react = async function (id, type) {
const res = await request(`/api/messages/${id}/react/${type}`)
- document.getElementById(`like-${id}`).innerHTML = res.react.like.length;
- document.getElementById(`dislike-${id}`).innerHTML = res.react.dislike.length;
+ const message = document.getElementById(`message-${id}`);
+ for (const react in res.react)
+ message.querySelector("#" + react).innerHTML = res.react[react].length;
}
diff --git a/routes/api/routes/messages.js b/routes/api/routes/messages.js
index bca3877..88dda90 100644
--- a/routes/api/routes/messages.js
+++ b/routes/api/routes/messages.js
@@ -10,20 +10,20 @@ app.param("id", async (req, res, next, id) => {
if (!req.message) return res.error(404, `We don't have any message with id ${id}.`);
- if(req.message.deleted && !req.user?.admin)
+ if (req.message.deleted && !req.user?.admin)
return res.error(404, `You do not have permissions to view this message with id ${id}.`)
-
+
next();
});
app.get("/:id", async (req, res) => {
-
+
res.complate(message);
})
app.patch("/:id/", async (req, res) => {
-
+
const { message, user } = req;
@@ -59,7 +59,7 @@ app.post("/", rateLimit({
})
app.post("/:id/react/:type", async (req, res) => {
-
+
const { message } = req;
@@ -91,12 +91,13 @@ app.post("/:id/react/:type", async (req, res) => {
});
app.delete("/:id/", async (req, res) => {
-
+
const { message, user } = req;
if (user.id != message.authorID && !user.admin)
return res.error(403, "You have not got permission for this.");
+ if (message.deleted) return res.error(403, "This message is already deleted.");
message.deleted = true;
await message.save();
@@ -105,7 +106,7 @@ app.delete("/:id/", async (req, res) => {
})
app.post("/:id/undelete", async (req, res) => {
-
+
const { message } = req;
diff --git a/routes/api/routes/threads.js b/routes/api/routes/threads.js
index 827c9f4..397a7ec 100644
--- a/routes/api/routes/threads.js
+++ b/routes/api/routes/threads.js
@@ -70,6 +70,7 @@ app.delete("/:id/", async (req, res) => {
if (user.id != thread.authorID && !user.admin)
return res.error(403, "You have not got permission for this.");
+ if (thread.deleted) return res.error(403, "This thread is already deleted.");
thread.deleted = true;
await thread.save();
console.log(thread)
diff --git a/routes/api/routes/users.js b/routes/api/routes/users.js
index 293ce6b..3434ff6 100644
--- a/routes/api/routes/users.js
+++ b/routes/api/routes/users.js
@@ -14,16 +14,10 @@ app.param("id", async (req, res, next, id) => {
next();
});
-app.get("/:id", async (req, res) => {
-
- if (req.member.not()) return;
- res.complate(member);
-
-});
+app.get("/:id", async (req, res) => res.complate(req.member));
app.delete("/:id/", async (req, res) => {
const { user, member } = req;
- if (req.member.not()) return;
if (!user.admin)
return res.error(403, "You have not got permission for this.");
@@ -55,22 +49,28 @@ app.post("/:id/undelete/", async (req, res) => {
app.patch("/:id/", async (req, res) => {
-
const { user, member } = req;
- if (req.user.id !== member.id && !req.user.admin) return res.error(403, "You have not got permission for this.");
- const { avatar, name, about, theme } = req.body;
- if (!avatar && !name && !about && !theme) return res.error(400, "Missing member informations in request body.");
+ if (req.user.id !== member.id && !user.admin) return res.error(403, "You have not got permission for this.");
+ if (!Object.values(req.body).some(Boolean)) return res.error(400, "Missing member informations in request body.");
+
+ const { avatar, name, about, theme, admin } = req.body;
+
+ if (admin?.length && !req.user.admin) return res.error(403, "You have not got permission for edit 'admin' information, or bad request.");
+
if (avatar && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g.test(avatar))
member.avatar = avatar;
+
if (name) {
await SecretModel.findOneAndUpdate({ name: member.name }, { name });
member.name = name;
}
if (about) member.about = about;
- if (theme) member.theme = member.theme === "default" ? "black" : "default";
- member.theme = theme;
+ if (theme)
+ member.theme = member.theme === "default" ? "black" : "default";
+
+ if(typeof admin === "boolean" || ["false","true"].includes(admin)) member.admin = admin;
member.edited = true;
await member.save();
@@ -79,22 +79,4 @@ app.patch("/:id/", async (req, res) => {
})
-app.post("/:id/admin/", async (req, res) => {
-
- const user = req.user;
-
- if (!user.admin) return res.error(403, "You have not got permission for this.");
- const user2 = await UserModel.get(req.params.id);
-
- if (!user2)
- return res.error(404, `We don't have any user with id ${id}.`);
-
-
- user2.admin = true;
- await user2.save()
-
-
- res.complate(user2);
-
-});
module.exports = app;
\ No newline at end of file
diff --git a/routes/login.js b/routes/login.js
index 0d15321..ccf3daf 100644
--- a/routes/login.js
+++ b/routes/login.js
@@ -28,9 +28,6 @@ app.post("/", async (req, res) => {
} else
res.error(400, "You forgot entering some values")
-
-
-})
-
+});
module.exports = app;
\ No newline at end of file
diff --git a/routes/register.js b/routes/register.js
index d7750bc..d943c5c 100644
--- a/routes/register.js
+++ b/routes/register.js
@@ -8,11 +8,10 @@ const app = Router();
app.get("/", (req, res) => res.reply("register", { user: null }));
app.post("/", rateLimit({
- windowMs: 24 * 60 * 60_000, max: 10, standardHeaders: true, legacyHeaders: false,
+ windowMs: 24 * 60 * 60_000, max: 5, standardHeaders: true, legacyHeaders: false,
handler: (_r, response, _n, options) => response.error(options.statusCode, "You are begin ratelimited")
}), async (req, res) => {
- req.session.userID = null;
-
+ req.session.destroy()
let { username = null, password: body_pass = null, avatar, about } = req.body;
diff --git a/routes/users.js b/routes/users.js
index 675d8e6..d4def5c 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -9,6 +9,17 @@ app.get("/", async ({ user }, res) => {
});
+app.get("/:id/edit", async (req, res) => {
+ if(!req.user || (!req.user.admin&&req.params.id !== req.user.id)) return res.error(403, "You have not got permission for this.");
+ const member = await UserModel.get(req.params.id);
+
+ if (member && (req.user?.admin || !member.deleted))
+ res.reply("edit_user", { member })
+ else
+ res.error(404, `We don't have any user with id ${req.params.id}.`);
+
+});
+
app.get("/:id", async (req, res) => {
const user = req.user
const { id } = req.params;
diff --git a/views/admin.ejs b/views/admin.ejs
index 704f0e8..d379280 100644
--- a/views/admin.ejs
+++ b/views/admin.ejs
@@ -12,66 +12,68 @@
border-collapse: collapse;
width: 100%;
}
-
- td, th {
+
+ td,
+ th {
border: 1px solid #dddddd;
text-align: left;
padding: 8px;
color: var(--reaction-hover);
}
-
+
tr:nth-child(even) {
background-color: #dddddd;
}
-
+
Welcome to the admin panel of the forum, <%= user.name %>!
-
+
IP BAN
REMOVE IP BAN
-
+
Banned users:
-
-
- IP |
- Reason |
- AuthorID |
-
- <% for (const ban of bans) { %>
-
- <%=ban.ip%> |
- <%=ban.reason%> |
- <%=ban.authorID%> |
-
- <% } %>
-
-
-
-
+
+
+ IP |
+ Reason |
+ AuthorID |
+
+ <% for (const ban of bans) { %>
+
+ <%=ban.ip%> |
+ <%=ban.reason%> |
+ <%=ban.authorID%> |
+
+ <% } %>
+
+
+
+
-
-