mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-22 20:10:40 +03:00
Better edits, already deleted, global ratelimit
This commit is contained in:
parent
cd801c94c7
commit
c110b195a3
25 changed files with 455 additions and 431 deletions
|
@ -25,7 +25,6 @@ But in front end, the API will works with session.
|
||||||
- DELETE `/api/users/:id/` for delete user.
|
- DELETE `/api/users/:id/` for delete user.
|
||||||
- PATCH `/api/users/:id/` for edit user.
|
- PATCH `/api/users/:id/` for edit user.
|
||||||
- POST `/api/users/:id/undelete` for undelete 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.
|
- GET `/api/threads/:id` for fetch thread.
|
||||||
- DELETE `/api/threads/:id/` for delete thread.
|
- DELETE `/api/threads/:id/` for delete thread.
|
||||||
|
|
20
README.md
20
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...
|
Edit `config.json` for default themes of users...
|
||||||
|
|
||||||
## API
|
## 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
|
## Credits
|
||||||
* [Akif9748](https://github.com/Akif9748) - Project mainteiner, main developer, made **old** frontend
|
* [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 |
|
| To do | Is done? | Priority |
|
||||||
| ----- | -------- | -------- |
|
| ----- | -------- | -------- |
|
||||||
| Profile Message | 🔴 | LOW |
|
| 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 |
|
| Search | 🔴 | MEDIUM |
|
||||||
| Footer | 🟢 | LOW |
|
| Footer | 🟡 | LOW |
|
||||||
|
|
||||||
- Profile photos will store in database
|
- Better Auth
|
||||||
|
- Profile photos will store in a folder
|
||||||
- replacer function global
|
- replacer function global
|
||||||
- author name of thread
|
|
||||||
- page for threads - users
|
- page for threads - users
|
||||||
- extra ratelimits
|
- IPs of users will add SecretModel
|
||||||
- better edits
|
|
||||||
- IP BAN fix, user -> ips []
|
|
||||||
- message counts for API
|
- message counts for API
|
||||||
- ZATEN SİLİNDİ BU KİŞİ & MESAJ
|
- better theme patch UserModel
|
||||||
- delete admin request, moreover, add it to user patch delete 😳, better theme patch
|
- ajax, delete update thread dom
|
||||||
|
|
||||||
### API
|
### API
|
||||||
| To do | Is done?
|
| To do | Is done?
|
||||||
|
|
11
index.js
11
index.js
|
@ -8,6 +8,7 @@ const { UserModel, BanModel } = require("./models"),
|
||||||
express = require('express'),
|
express = require('express'),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
app = express();
|
app = express();
|
||||||
|
const rateLimit = require('express-rate-limit')
|
||||||
|
|
||||||
app.ips = [];
|
app.ips = [];
|
||||||
|
|
||||||
|
@ -17,10 +18,11 @@ mongoose.connect(process.env.MONGO_DB_URL,
|
||||||
|
|
||||||
app.set("view engine", "ejs");
|
app.set("view engine", "ejs");
|
||||||
|
|
||||||
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }),
|
app.use(
|
||||||
bodyParser.urlencoded({ extended: true }),
|
session({ secret: 'secret', resave: true, saveUninitialized: true }),
|
||||||
express.static("public"), express.json(), ipBlock(app.ips),
|
express.static("public"), express.json(), ipBlock(app.ips),
|
||||||
async (req, res, next) => {
|
async (req, res, next) => {
|
||||||
|
req.headers["x-forwarded-for"]
|
||||||
req.user = await UserModel.get(req.session.userID);
|
req.user = await UserModel.get(req.session.userID);
|
||||||
res.reply = (page, options = {}, status = 200) => res.status(status)
|
res.reply = (page, options = {}, status = 200) => res.status(status)
|
||||||
.render(page, { user: req.user, theme: req.user?.theme || def_theme, ...options });
|
.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.");
|
return res.error(403, "Your account has been deleted.");
|
||||||
}
|
}
|
||||||
next();
|
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"))
|
for (const file of fs.readdirSync("./routes"))
|
||||||
|
|
|
@ -2,7 +2,7 @@ const mongoose = require("mongoose")
|
||||||
|
|
||||||
const schema = new mongoose.Schema({
|
const schema = new mongoose.Schema({
|
||||||
username: { type: String, unique: true },
|
username: { type: String, unique: true },
|
||||||
password: String,
|
password: String, ips: [String],
|
||||||
id: { type: String, unique: true }
|
id: { type: String, unique: true }
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -149,7 +149,12 @@
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.send>textarea{
|
||||||
|
font-family:inherit;
|
||||||
|
width: 100%;
|
||||||
|
margin: 10px;
|
||||||
|
border: 2px solid #e3e3e3;
|
||||||
|
}
|
||||||
/* Media Query */
|
/* Media Query */
|
||||||
|
|
||||||
@media(max-width:980px) {
|
@media(max-width:980px) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
import request from "./request.js";
|
import request from "./request.js";
|
||||||
|
|
||||||
|
// THREAD:
|
||||||
|
|
||||||
window.edit_thread = async function (id) {
|
window.edit_thread = async function (id) {
|
||||||
const title = prompt("Enter new title!");
|
const title = prompt("Enter new title!");
|
||||||
const res = await request(`/api/threads/${id}/`, "PATCH", { 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) {
|
window.delete_thread = async function (id) {
|
||||||
const res = await request(`/api/threads/${id}/`, "DELETE");
|
const res = await request(`/api/threads/${id}/`, "DELETE");
|
||||||
if (res.error) return;
|
if (res.error) return;
|
||||||
|
|
||||||
alert(`Thread deleted`);
|
alert(`Thread deleted`);
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
|
@ -19,41 +20,56 @@ window.delete_thread = async function (id) {
|
||||||
window.undelete_thread = async function (id) {
|
window.undelete_thread = async function (id) {
|
||||||
const res = await request(`/api/threads/${id}/undelete`);
|
const res = await request(`/api/threads/${id}/undelete`);
|
||||||
if (res.error) return;
|
if (res.error) return;
|
||||||
|
|
||||||
alert(`Thread undeleted`);
|
alert(`Thread undeleted`);
|
||||||
location.reload();
|
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`);
|
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 = `
|
||||||
|
<textarea rows="4" cols="40" id="content">${content.innerHTML}</textarea>
|
||||||
|
<button onclick="send_edit(${id});" class="btn-primary">Edit!</button>`;
|
||||||
|
|
||||||
}
|
}
|
||||||
window.undelete_message = async function (id) {
|
window.undelete_message = async function (id) {
|
||||||
const response = await request(`/api/messages/${id}/undelete`);
|
const response = await request(`/api/messages/${id}/undelete`);
|
||||||
if (response.deleted) return;
|
if (response.deleted) return;
|
||||||
document.getElementById("deleted-" + id).remove();
|
const message = document.getElementById("message-" + id);
|
||||||
document.getElementById("dot-" + id).innerHTML = `
|
|
||||||
|
message.querySelector("#deleted").remove();
|
||||||
|
message.querySelector(".dots-menu").innerHTML = `
|
||||||
<a onclick="delete_message('${id}');">DELETE</a>
|
<a onclick="delete_message('${id}');">DELETE</a>
|
||||||
<a onclick="edit_message('${id}');">EDIT</a>`
|
<a onclick="edit_message('${id}');">EDIT</a>`
|
||||||
}
|
}
|
||||||
|
|
||||||
window.delete_message = async function (id) {
|
window.delete_message = async function (id) {
|
||||||
const response = await request(`/api/messages/${id}/`, "DELETE");
|
const response = await request(`/api/messages/${id}/`, "DELETE");
|
||||||
if (response.deleted) {
|
if (!response.deleted) return
|
||||||
|
const message = document.getElementById(`message-${id}`);
|
||||||
alert("Message deleted");
|
alert("Message deleted");
|
||||||
document.getElementById("dots-" + id).innerHTML = `
|
|
||||||
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: var(--important)"></i>
|
message.querySelector(".dots-menu").innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
||||||
`+ document.getElementById("dots-" + id).innerHTML;
|
|
||||||
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
let dots = message.querySelector(".dots");
|
||||||
}
|
dots.innerHTML = "<i class='bx bx-trash bx-sm' id='deleted' style='color: var(--important)'></i>" + dots.innerHTML;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
window.react = async function (id, type) {
|
window.react = async function (id, type) {
|
||||||
const res = await request(`/api/messages/${id}/react/${type}`)
|
const res = await request(`/api/messages/${id}/react/${type}`)
|
||||||
document.getElementById(`like-${id}`).innerHTML = res.react.like.length;
|
const message = document.getElementById(`message-${id}`);
|
||||||
document.getElementById(`dislike-${id}`).innerHTML = res.react.dislike.length;
|
for (const react in res.react)
|
||||||
|
message.querySelector("#" + react).innerHTML = res.react[react].length;
|
||||||
}
|
}
|
||||||
|
|
|
@ -97,6 +97,7 @@ app.delete("/:id/", async (req, res) => {
|
||||||
|
|
||||||
if (user.id != message.authorID && !user.admin)
|
if (user.id != message.authorID && !user.admin)
|
||||||
return res.error(403, "You have not got permission for this.");
|
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;
|
message.deleted = true;
|
||||||
await message.save();
|
await message.save();
|
||||||
|
|
|
@ -70,6 +70,7 @@ app.delete("/:id/", async (req, res) => {
|
||||||
if (user.id != thread.authorID && !user.admin)
|
if (user.id != thread.authorID && !user.admin)
|
||||||
return res.error(403, "You have not got permission for this.");
|
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;
|
thread.deleted = true;
|
||||||
await thread.save();
|
await thread.save();
|
||||||
console.log(thread)
|
console.log(thread)
|
||||||
|
|
|
@ -14,16 +14,10 @@ app.param("id", async (req, res, next, id) => {
|
||||||
next();
|
next();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get("/:id", async (req, res) => {
|
app.get("/:id", async (req, res) => res.complate(req.member));
|
||||||
|
|
||||||
if (req.member.not()) return;
|
|
||||||
res.complate(member);
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
app.delete("/:id/", async (req, res) => {
|
app.delete("/:id/", async (req, res) => {
|
||||||
const { user, member } = req;
|
const { user, member } = req;
|
||||||
if (req.member.not()) return;
|
|
||||||
|
|
||||||
if (!user.admin)
|
if (!user.admin)
|
||||||
return res.error(403, "You have not got permission for this.");
|
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) => {
|
app.patch("/:id/", async (req, res) => {
|
||||||
|
|
||||||
const { user, member } = req;
|
const { user, member } = req;
|
||||||
|
|
||||||
if (req.user.id !== member.id && !req.user.admin) return res.error(403, "You have not got permission for this.");
|
if (req.user.id !== member.id && !user.admin) return res.error(403, "You have not got permission for this.");
|
||||||
const { avatar, name, about, theme } = req.body;
|
if (!Object.values(req.body).some(Boolean)) return res.error(400, "Missing member informations in request body.");
|
||||||
if (!avatar && !name && !about && !theme) 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))
|
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;
|
member.avatar = avatar;
|
||||||
|
|
||||||
if (name) {
|
if (name) {
|
||||||
await SecretModel.findOneAndUpdate({ name: member.name }, { name });
|
await SecretModel.findOneAndUpdate({ name: member.name }, { name });
|
||||||
member.name = name;
|
member.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (about) member.about = about;
|
if (about) member.about = about;
|
||||||
if (theme) member.theme = member.theme === "default" ? "black" : "default";
|
if (theme)
|
||||||
member.theme = theme;
|
member.theme = member.theme === "default" ? "black" : "default";
|
||||||
|
|
||||||
|
if(typeof admin === "boolean" || ["false","true"].includes(admin)) member.admin = admin;
|
||||||
member.edited = true;
|
member.edited = true;
|
||||||
|
|
||||||
await member.save();
|
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;
|
module.exports = app;
|
|
@ -28,9 +28,6 @@ app.post("/", async (req, res) => {
|
||||||
} else
|
} else
|
||||||
res.error(400, "You forgot entering some values")
|
res.error(400, "You forgot entering some values")
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
module.exports = app;
|
module.exports = app;
|
|
@ -8,11 +8,10 @@ const app = Router();
|
||||||
app.get("/", (req, res) => res.reply("register", { user: null }));
|
app.get("/", (req, res) => res.reply("register", { user: null }));
|
||||||
|
|
||||||
app.post("/", rateLimit({
|
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")
|
handler: (_r, response, _n, options) => response.error(options.statusCode, "You are begin ratelimited")
|
||||||
}), async (req, res) => {
|
}), async (req, res) => {
|
||||||
req.session.userID = null;
|
req.session.destroy()
|
||||||
|
|
||||||
|
|
||||||
let { username = null, password: body_pass = null, avatar, about } = req.body;
|
let { username = null, password: body_pass = null, avatar, about } = req.body;
|
||||||
|
|
||||||
|
|
|
@ -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) => {
|
app.get("/:id", async (req, res) => {
|
||||||
const user = req.user
|
const user = req.user
|
||||||
const { id } = req.params;
|
const { id } = req.params;
|
||||||
|
|
|
@ -13,7 +13,8 @@
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
|
||||||
td, th {
|
td,
|
||||||
|
th {
|
||||||
border: 1px solid #dddddd;
|
border: 1px solid #dddddd;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
padding: 8px;
|
padding: 8px;
|
||||||
|
@ -74,4 +75,5 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
|
@ -3,8 +3,6 @@
|
||||||
|
|
||||||
<%- include("extra/meta", {title: "Create thread!" }) %>
|
<%- include("extra/meta", {title: "Create thread!" }) %>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<body style="text-align: center;">
|
<body style="text-align: center;">
|
||||||
<link rel="stylesheet" href="/css/create_thread.css" />
|
<link rel="stylesheet" href="/css/create_thread.css" />
|
||||||
|
|
||||||
|
@ -25,7 +23,6 @@
|
||||||
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
|
|
||||||
import request from "../../js/request.js";
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
document.addEventListener("submit", async e => {
|
document.addEventListener("submit", async e => {
|
||||||
|
@ -34,7 +31,8 @@
|
||||||
const data = new FormData(form);
|
const data = new FormData(form);
|
||||||
|
|
||||||
const response = await request("/api/threads/", "POST", {
|
const response = await request("/api/threads/", "POST", {
|
||||||
title: data.get("title"), content: data.get("content")
|
title: data.get("title"),
|
||||||
|
content: data.get("content")
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -43,8 +41,6 @@
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
51
views/edit_user.ejs
Normal file
51
views/edit_user.ejs
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<%- include("extra/meta", {title: "Edit "+member.name+"!" }) %>
|
||||||
|
|
||||||
|
|
||||||
|
<body style="text-align: center;">
|
||||||
|
<link rel="stylesheet" href="/css/login.css" />
|
||||||
|
<link rel="stylesheet" href="/css/user.css" />
|
||||||
|
|
||||||
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
|
<h1 class="title">Edit <a href="/users/<%= member.id %>"><%= member.name %></a></h1>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<form id="form">
|
||||||
|
<input type="text" name="name" placeholder="<%=member.name%>" class="input">
|
||||||
|
<input type="url" name="avatar" placeholder="<%=member.avatar%>" class="input">
|
||||||
|
<textarea class="input" name="about" rows="4" name="content" placeholder="<%=member.about%>"></textarea>
|
||||||
|
<% if (user.admin){ %>
|
||||||
|
Is Admin? <input id='admin' type='checkbox' value='true' name='admin' <%=member.admin ? "checked": ""%>>
|
||||||
|
<input id='adminHidden' type='hidden' value='false' name='admin'>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<button class="btn-primary" style="width:100%;">Update User!</button>
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
const form = document.getElementById("form");
|
||||||
|
import request from "../../js/request.js";
|
||||||
|
form.addEventListener("submit", async e => {
|
||||||
|
e.preventDefault();
|
||||||
|
document.getElementById('adminHidden').disabled = document.getElementById("admin").checked;
|
||||||
|
|
||||||
|
const object = {};
|
||||||
|
new FormData(e.target).forEach((value, key) => object[key] = value);
|
||||||
|
console.log(object)
|
||||||
|
const res = await request("/api/users/<%=member.id%>", "PATCH", object);
|
||||||
|
if (res) alert(`User is updated!`);
|
||||||
|
location.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
|
@ -1,7 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<%- include("extra/meta", {title: "User list!" }) %>
|
<%- include("extra/meta", {title: type+" error!" }) %>
|
||||||
|
|
||||||
|
|
||||||
<body style="text-align: center;">
|
<body style="text-align: center;">
|
||||||
|
|
|
@ -6,4 +6,8 @@
|
||||||
<meta name="author" content="Akif9748">
|
<meta name="author" content="Akif9748">
|
||||||
<link rel="icon" type="image/x-icon" href="/images/favicon.jpg">
|
<link rel="icon" type="image/x-icon" href="/images/favicon.jpg">
|
||||||
<link rel="stylesheet" href="/css/themes/<%= theme %>.css" />
|
<link rel="stylesheet" href="/css/themes/<%= theme %>.css" />
|
||||||
|
<% if (theme === "black") { %>
|
||||||
|
<meta name="theme-color" content="#000000" />
|
||||||
|
<% } %>
|
||||||
|
|
||||||
</head>
|
</head>
|
|
@ -21,8 +21,11 @@
|
||||||
<a onclick="invert()" class="btn-outline-primary"><%=(user.theme === "default" ? "black" : "default" ) + " mode" %></a>
|
<a onclick="invert()" class="btn-outline-primary"><%=(user.theme === "default" ? "black" : "default" ) + " mode" %></a>
|
||||||
<script>
|
<script>
|
||||||
async function invert() {
|
async function invert() {
|
||||||
await fetch('/api/users/<%= user.id %>',{method:'PATCH',
|
await fetch('/api/users/<%= user.id %>', {
|
||||||
body:JSON.stringify({theme:"<%=user.theme === `default` ? `black` : `default` %>"}),
|
method: 'PATCH',
|
||||||
|
body: JSON.stringify({
|
||||||
|
theme: "<%=user.theme === `default` ? `black` : `default` %>"
|
||||||
|
}),
|
||||||
headers: {
|
headers: {
|
||||||
"Content-Type": "application/json"
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
|
@ -52,6 +55,5 @@
|
||||||
for (let i = 0; i < menuItems.length; i++)
|
for (let i = 0; i < menuItems.length; i++)
|
||||||
if (menuItems[i].getAttribute("href") == window.location.pathname)
|
if (menuItems[i].getAttribute("href") == window.location.pathname)
|
||||||
menuItems[i].classList.add("active-menu");
|
menuItems[i].classList.add("active-menu");
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</div>
|
</div>
|
|
@ -13,7 +13,8 @@
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
<% if (user) { %>
|
<% if (user) { %>
|
||||||
<h2 style="color: var(--main);"><div class="box-username">Welcome, <%= user.name %>
|
<h2 style="color: var(--main);">
|
||||||
|
<div class="box-username">Welcome, <%= user.name %>
|
||||||
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
@ -58,4 +59,3 @@
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
|
@ -17,10 +17,8 @@
|
||||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
<input type="text" name="username" placeholder="Username" class="input" required>
|
||||||
|
|
||||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||||
<input type="text" name="about" placeholder="About you... Not required" class="input">
|
<input type="url" name="avatar" placeholder="Avatar URL (not required)" class="input">
|
||||||
|
<textarea class="input" name="about" rows="4" placeholder="About you... Not required"></textarea>
|
||||||
<input type="text" name="avatar" placeholder="Avatar URL (not required)" class="input">
|
|
||||||
|
|
||||||
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
|
@ -16,9 +16,11 @@
|
||||||
<div style="text-align:center;padding:8px">
|
<div style="text-align:center;padding:8px">
|
||||||
<div class="title" id="title"><%= thread.title %></div>
|
<div class="title" id="title"><%= thread.title %></div>
|
||||||
<div class="date">
|
<div class="date">
|
||||||
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %> <%= "• "+(thread.edited ? "Edited" : "Not edited")%>
|
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>%>
|
||||||
|
</div>
|
||||||
|
<div class="date">
|
||||||
|
<a style="color: var(--reaction-hover);" href="/users/<%= thread.author.id %>"><%= thread.author.name %></a> <%= "• "+(thread.edited ? "Edited" : "Not edited")%>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
<div style="text-align:center;padding:8px">
|
<div style="text-align:center;padding:8px">
|
||||||
<!-- THREAD AUTHOR AND PROFILE PHOTO -->
|
<!-- THREAD AUTHOR AND PROFILE PHOTO -->
|
||||||
|
@ -34,7 +36,7 @@
|
||||||
<% }; %>
|
<% }; %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="messages" value="<%= thread.id %>">
|
<div id="messages">
|
||||||
|
|
||||||
<% messages.filter(Boolean).forEach(message=>{ %>
|
<% messages.filter(Boolean).forEach(message=>{ %>
|
||||||
|
|
||||||
|
@ -54,17 +56,17 @@
|
||||||
<% if(user){ %>
|
<% if(user){ %>
|
||||||
<% if(user.id === message.authorID || user.admin){ %>
|
<% if(user.id === message.authorID || user.admin){ %>
|
||||||
|
|
||||||
<div class="dots" id="dots-<%=message.id %>" onclick="dots('<%=message.id %>')">
|
<div class="dots" onclick="dots('<%=message.id %>')">
|
||||||
<% if (message.deleted){ %>
|
<% if (message.deleted){ %>
|
||||||
<i class='bx bx-trash bx-sm' id="deleted-<%=message.id %>" style="color: var(--important);"></i>
|
<i class='bx bx-trash bx-sm' id="deleted" style="color: var(--important);"></i>
|
||||||
<% } %>
|
<% } %>
|
||||||
<% if (message.edited){ %>
|
<% if (message.edited){ %>
|
||||||
<i class='bx bx-pencil bx-sm' id="edited-<%=message.id %>" style="color: GREEN;"></i>
|
<i class='bx bx-pencil bx-sm' id="edited" style="color: GREEN;"></i>
|
||||||
<% } %>
|
<% } %>
|
||||||
<i class='bx bx-dots-horizontal-rounded'></i>
|
<i class='bx bx-dots-horizontal-rounded'></i>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="dots-menu" id="dot-<%=message.id %>">
|
<div class="dots-menu">
|
||||||
<% if (!message.deleted){ %>
|
<% if (!message.deleted){ %>
|
||||||
<a onclick="delete_message('<%=message.id %>');">Delete</a>
|
<a onclick="delete_message('<%=message.id %>');">Delete</a>
|
||||||
<a onclick="edit_message('<%=message.id %>');">Edit</a>
|
<a onclick="edit_message('<%=message.id %>');">Edit</a>
|
||||||
|
@ -74,15 +76,14 @@
|
||||||
</div>
|
</div>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
|
|
||||||
<div class="reactions">
|
<div class="reactions">
|
||||||
<div>
|
<div>
|
||||||
<i onclick='react("<%= message.id %>","like");' class='bx bx-like'></i>
|
<i onclick='react("<%= message.id %>","like");' class='bx bx-like'></i>
|
||||||
<div id="like-<%= message.id %>"><%=message.react.like.length %></div>
|
<div id="like"><%=message.react.like.length %></div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<i onclick='react("<%= message.id %>","dislike");' class='bx bx-dislike'></i>
|
<i onclick='react("<%= message.id %>","dislike");' class='bx bx-dislike'></i>
|
||||||
<div id="dislike-<%= message.id %>"><%=message.react.dislike.length %></div>
|
<div id="dislike"><%=message.react.dislike.length %></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% }; %>
|
<% }; %>
|
||||||
|
@ -93,17 +94,16 @@
|
||||||
</div>
|
</div>
|
||||||
<% if (user){ %>
|
<% if (user){ %>
|
||||||
|
|
||||||
<div class="message">
|
<div class="message" id="send-div">
|
||||||
<form id="send" style="width:100%">
|
<form id="send" style="width:100%">
|
||||||
<textarea rows="4"
|
<textarea rows="4" name="content"></textarea>
|
||||||
style="
|
|
||||||
font-family:inherit;
|
|
||||||
width: 100%;
|
|
||||||
margin: 10px;
|
|
||||||
border: 2px solid #e3e3e3;" name="content"></textarea>
|
|
||||||
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
|
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
|
||||||
<input name="page" type="hidden" value="<%= page %>"></input>
|
<input name="page" type="hidden" value="<%= page %>"></input>
|
||||||
|
<button class="btn-primary">Send!</button>
|
||||||
|
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</div>
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import request from "../../js/request.js";
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
|
@ -113,20 +113,19 @@
|
||||||
|
|
||||||
const data = new FormData(e.target);
|
const data = new FormData(e.target);
|
||||||
|
|
||||||
request("/api/messages", "POST", { threadID: "<%= thread.id %>", content: data.get("content") })
|
const res = await request("/api/messages", "POST", {
|
||||||
.then(res => {
|
threadID: "<%= thread.id %>",
|
||||||
|
content: data.get("content")
|
||||||
|
})
|
||||||
|
|
||||||
let tp = Number("<%= thread.pages %>")
|
let tp = Number("<%= thread.pages %>")
|
||||||
let tm = Number("<%= thread.count %>")
|
let tm = Number("<%= thread.count %>")
|
||||||
if (tp * 10 === tm) tp++;
|
if (tp * 10 === tm) tp++;
|
||||||
if (res) location.href = `/threads/${data.get("threadID")}?page=${tp-1}`;
|
if (res) location.href = `/threads/${data.get("threadID")}?page=${tp-1}`;
|
||||||
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<button class="btn-primary">Send!</button>
|
|
||||||
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
<% }%>
|
<% }%>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<div class="back">
|
<div class="back">
|
||||||
|
@ -151,8 +150,9 @@
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("message-<%= scroll %>").scrollIntoView();
|
document.getElementById("message-<%= scroll %>").scrollIntoView();
|
||||||
|
|
||||||
function dots(id) {
|
function dots(id) {
|
||||||
document.getElementById('dot-'+id).classList.toggle('active')
|
document.getElementById('message-' + id).querySelector(".dots-menu").classList.toggle('active')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -6,10 +6,40 @@
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
<link rel="stylesheet" href="/css/user.css" />
|
<link rel="stylesheet" href="/css/user.css" />
|
||||||
|
<%if (user) {%>
|
||||||
|
<script type="module">
|
||||||
|
import request from "../../js/request.js";
|
||||||
|
document.addEventListener("click", async e => {
|
||||||
|
if (e.target.id == "delete") {
|
||||||
|
const response = await request("/api/users/<%= member.id %>", "DELETE");
|
||||||
|
if (!response.deleted) return
|
||||||
|
alert("User is deleted!");
|
||||||
|
location.reload()
|
||||||
|
} else if (e.target.id == "undelete") {
|
||||||
|
const response = await request("/api/users/<%= member.id %>/undelete");
|
||||||
|
if (response.deleted) return;
|
||||||
|
alert("User is undeleted successfully!");
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<% }; %>
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<div class="content">
|
<div class="content">
|
||||||
|
|
||||||
|
<% if (user?.admin || user?.id === member.id) { %>
|
||||||
|
<a class="btn-outline-primary" href="<%= member.getLink() %>/edit">Edit user!</a>
|
||||||
|
<% } %>
|
||||||
|
|
||||||
|
<% if (member.deleted) {%>
|
||||||
|
<h1>This user has been deleted!</h1>
|
||||||
|
<a id="undelete" class="btn-primary">Undelete user! </a>
|
||||||
|
<% } else if (user?.admin){ %>
|
||||||
|
<a id="delete" class="btn-outline-primary">Delete user! </a>
|
||||||
|
<% }; %>
|
||||||
|
|
||||||
|
|
||||||
<div class="box" style="justify-content:center;">
|
<div class="box" style="justify-content:center;">
|
||||||
<img style="width:100px;height:100px;border-radius:50%;" src="<%=member.avatar %>">
|
<img style="width:100px;height:100px;border-radius:50%;" src="<%=member.avatar %>">
|
||||||
</div>
|
</div>
|
||||||
|
@ -17,7 +47,6 @@
|
||||||
<h2 class="box-title">Name:</h2>
|
<h2 class="box-title">Name:</h2>
|
||||||
<h2 class="box-value"><%= member.name %></h2>
|
<h2 class="box-value"><%= member.name %></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h2 class="box-title">Created at:</h2>
|
<h2 class="box-title">Created at:</h2>
|
||||||
<h2 class="box-value"><%= new Date(member.time).toLocaleString() %></h2>
|
<h2 class="box-value"><%= new Date(member.time).toLocaleString() %></h2>
|
||||||
|
@ -26,7 +55,6 @@
|
||||||
<h2 class="box-title">Is admin?</h2>
|
<h2 class="box-title">Is admin?</h2>
|
||||||
<h2 class="box-value"><%= member.admin ? "Yes" : "No" %></h2>
|
<h2 class="box-value"><%= member.admin ? "Yes" : "No" %></h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<h2 class="box-title">Message:</h2>
|
<h2 class="box-title">Message:</h2>
|
||||||
<h2 class="box-value"><%= counts.message %></h2>
|
<h2 class="box-value"><%= counts.message %></h2>
|
||||||
|
@ -42,71 +70,6 @@
|
||||||
<%= member.about %>
|
<%= member.about %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<% if (user?.admin && !member.deleted) {%>
|
|
||||||
<a class="btn-outline-primary" id="edit_name">Change name of the user!</a>
|
|
||||||
<a class="btn-outline-primary" id="edit_avatar">Change avatar of the user!</a>
|
|
||||||
<a class="btn-outline-primary" id="edit_about">Change about of the user!</a>
|
|
||||||
<a class="btn-outline-primary" id="admin">Give admin permissions!</a>
|
|
||||||
<a class="btn-outline-primary" id="delete">Delete user!</a>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
|
|
||||||
import request from "../../js/request.js";
|
|
||||||
document.addEventListener("click", async e => {
|
|
||||||
if (e.target.id == "admin") {
|
|
||||||
|
|
||||||
const response = await request("/api/users/<%= member.id %>/admin");
|
|
||||||
|
|
||||||
if (response.admin)
|
|
||||||
return alert("Making admin of " + response.name + " is success!");
|
|
||||||
|
|
||||||
} else if (e.target.id == "delete") {
|
|
||||||
|
|
||||||
const response = await request("/api/users/<%= member.id %>","DELETE");
|
|
||||||
|
|
||||||
if (!response.deleted) return
|
|
||||||
alert("User is deleted!");
|
|
||||||
location.reload()
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
const body = {};
|
|
||||||
|
|
||||||
if (e.target.id == "edit_name")
|
|
||||||
body.name = prompt("Enter new username!");
|
|
||||||
else if (e.target.id == "edit_avatar")
|
|
||||||
body.avatar = prompt("Enter new avatar URL!");
|
|
||||||
else if (e.target.id == "edit_about")
|
|
||||||
body.about = prompt("Enter new about text!");
|
|
||||||
else return;
|
|
||||||
const res = await request(`/api/users/<%= member.id %>`, "PATCH", body);
|
|
||||||
|
|
||||||
if (res.error) return;
|
|
||||||
alert(`User updated!`);
|
|
||||||
location.reload();
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
</script>
|
|
||||||
<% }; %>
|
|
||||||
<% if (member.deleted) {%>
|
|
||||||
<h1>This user has been deleted!</h1>
|
|
||||||
<a onclick="undelete();" class="btn-primary" >Undelete user! </a>
|
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import request from "../../js/request.js";
|
|
||||||
window.undelete= async function undelete(params) {
|
|
||||||
|
|
||||||
const response = await request("/api/users/<%= member.id %>/undelete");
|
|
||||||
|
|
||||||
if (response.deleted) return;
|
|
||||||
alert("User is undeleted successfully!");
|
|
||||||
location.reload()
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<% }; %>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
|
@ -23,4 +23,3 @@
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue