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.
|
||||
- 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.
|
||||
|
|
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...
|
||||
|
||||
## 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?
|
||||
|
|
11
index.js
11
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"))
|
||||
|
|
|
@ -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 }
|
||||
});
|
||||
|
||||
|
|
|
@ -149,7 +149,12 @@
|
|||
|
||||
}
|
||||
|
||||
|
||||
.send>textarea{
|
||||
font-family:inherit;
|
||||
width: 100%;
|
||||
margin: 10px;
|
||||
border: 2px solid #e3e3e3;
|
||||
}
|
||||
/* Media Query */
|
||||
|
||||
@media(max-width:980px) {
|
||||
|
|
|
@ -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 = `
|
||||
<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) {
|
||||
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 = `
|
||||
<a onclick="delete_message('${id}');">DELETE</a>
|
||||
<a onclick="edit_message('${id}');">EDIT</a>`
|
||||
}
|
||||
|
||||
window.delete_message = async function (id) {
|
||||
const response = await request(`/api/messages/${id}/`, "DELETE");
|
||||
if (response.deleted) {
|
||||
if (!response.deleted) return
|
||||
const message = document.getElementById(`message-${id}`);
|
||||
alert("Message deleted");
|
||||
document.getElementById("dots-" + id).innerHTML = `
|
||||
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: var(--important)"></i>
|
||||
`+ document.getElementById("dots-" + id).innerHTML;
|
||||
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
||||
}
|
||||
|
||||
message.querySelector(".dots-menu").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) {
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -10,7 +10,7 @@ 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();
|
||||
|
@ -97,6 +97,7 @@ app.delete("/:id/", async (req, res) => {
|
|||
|
||||
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();
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
|
@ -28,9 +28,6 @@ app.post("/", async (req, res) => {
|
|||
} else
|
||||
res.error(400, "You forgot entering some values")
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
module.exports = app;
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -13,7 +13,8 @@
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
td, th {
|
||||
td,
|
||||
th {
|
||||
border: 1px solid #dddddd;
|
||||
text-align: left;
|
||||
padding: 8px;
|
||||
|
@ -33,7 +34,7 @@
|
|||
|
||||
<h2 style="color: var(--second);">Banned users:</h2>
|
||||
|
||||
<table >
|
||||
<table>
|
||||
<tr>
|
||||
<th>IP</th>
|
||||
<th>Reason</th>
|
||||
|
@ -52,19 +53,19 @@
|
|||
<script type="module">
|
||||
import request from "../../js/request.js";
|
||||
|
||||
window.unban = async function () {
|
||||
window.unban = async function() {
|
||||
const ip = prompt("Enter ip to unban");
|
||||
const response = await request("/api/bans/"+ip,"DELETE");
|
||||
if(response)
|
||||
const response = await request("/api/bans/" + ip, "DELETE");
|
||||
if (response)
|
||||
alert("IP unbanned!");
|
||||
else
|
||||
alert("IP is not unbanned!");
|
||||
location.reload();
|
||||
}
|
||||
window.ban = async function () {
|
||||
window.ban = async function() {
|
||||
const ip = prompt("Enter ip to ban");
|
||||
const response = await request("/api/bans/"+ip);
|
||||
if(response)
|
||||
const response = await request("/api/bans/" + ip);
|
||||
if (response)
|
||||
alert("IP banned!");
|
||||
else
|
||||
alert("IP is not banned!");
|
||||
|
@ -74,4 +75,5 @@
|
|||
</div>
|
||||
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -3,8 +3,6 @@
|
|||
|
||||
<%- include("extra/meta", {title: "Create thread!" }) %>
|
||||
|
||||
|
||||
|
||||
<body style="text-align: center;">
|
||||
<link rel="stylesheet" href="/css/create_thread.css" />
|
||||
|
||||
|
@ -21,11 +19,10 @@
|
|||
|
||||
|
||||
<button class="btn-primary" style="width:100%" type="submit">Create Thread!</button>
|
||||
</form>
|
||||
</form>
|
||||
|
||||
|
||||
<script type="module">
|
||||
|
||||
<script type="module">
|
||||
import request from "../../js/request.js";
|
||||
|
||||
document.addEventListener("submit", async e => {
|
||||
|
@ -34,7 +31,8 @@
|
|||
const data = new FormData(form);
|
||||
|
||||
const response = await request("/api/threads/", "POST", {
|
||||
title: data.get("title"), content: data.get("content")
|
||||
title: data.get("title"),
|
||||
content: data.get("content")
|
||||
});
|
||||
|
||||
|
||||
|
@ -43,9 +41,7 @@
|
|||
|
||||
|
||||
});
|
||||
|
||||
|
||||
</script>
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
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>
|
||||
<html lang="en">
|
||||
|
||||
<%- include("extra/meta", {title: "User list!" }) %>
|
||||
<%- include("extra/meta", {title: type+" error!" }) %>
|
||||
|
||||
|
||||
<body style="text-align: center;">
|
||||
|
|
|
@ -6,4 +6,8 @@
|
|||
<meta name="author" content="Akif9748">
|
||||
<link rel="icon" type="image/x-icon" href="/images/favicon.jpg">
|
||||
<link rel="stylesheet" href="/css/themes/<%= theme %>.css" />
|
||||
<% if (theme === "black") { %>
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<% } %>
|
||||
|
||||
</head>
|
|
@ -12,7 +12,7 @@
|
|||
<div class="buttons">
|
||||
|
||||
<% if (user){ %>
|
||||
<a href="<%=user.getLink() %>" class="btn-outline-primary" >
|
||||
<a href="<%=user.getLink() %>" class="btn-outline-primary">
|
||||
<div class="box-username"><%= user.name %>
|
||||
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
||||
</div>
|
||||
|
@ -21,8 +21,11 @@
|
|||
<a onclick="invert()" class="btn-outline-primary"><%=(user.theme === "default" ? "black" : "default" ) + " mode" %></a>
|
||||
<script>
|
||||
async function invert() {
|
||||
await fetch('/api/users/<%= user.id %>',{method:'PATCH',
|
||||
body:JSON.stringify({theme:"<%=user.theme === `default` ? `black` : `default` %>"}),
|
||||
await fetch('/api/users/<%= user.id %>', {
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
theme: "<%=user.theme === `default` ? `black` : `default` %>"
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json"
|
||||
}
|
||||
|
@ -35,7 +38,7 @@
|
|||
<a id="login" href="/login" class="btn-primary">Login</a>
|
||||
<a href="/register" class="btn-outline-primary">Register</a>
|
||||
<script>
|
||||
document.getElementById("login").href += "?redirect="+window.location.pathname;
|
||||
document.getElementById("login").href += "?redirect=" + window.location.pathname;
|
||||
</script>
|
||||
<% } %>
|
||||
|
||||
|
@ -52,6 +55,5 @@
|
|||
for (let i = 0; i < menuItems.length; i++)
|
||||
if (menuItems[i].getAttribute("href") == window.location.pathname)
|
||||
menuItems[i].classList.add("active-menu");
|
||||
|
||||
</script>
|
||||
</div>
|
|
@ -4,7 +4,7 @@
|
|||
<%- include("extra/meta", {title: "Welcome to the Akf-forum!" }) %>
|
||||
|
||||
|
||||
<body >
|
||||
<body>
|
||||
<link rel="stylesheet" href="/css/user.css" />
|
||||
|
||||
<%- include("extra/navbar") %>
|
||||
|
@ -13,7 +13,8 @@
|
|||
|
||||
<div class="content">
|
||||
<% 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>
|
||||
|
@ -58,4 +59,3 @@
|
|||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
@ -16,7 +15,7 @@
|
|||
|
||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||
<input type="submit" style="width:100%"class="btn-primary" value="Login">
|
||||
<input type="submit" style="width:100%" class="btn-primary" value="Login">
|
||||
</form>
|
||||
|
||||
|
||||
|
|
|
@ -17,10 +17,8 @@
|
|||
<input type="text" name="username" placeholder="Username" 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="text" name="avatar" placeholder="Avatar URL (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="submit" class="btn-primary" style="width:100%;" value="Register">
|
||||
</form>
|
||||
|
||||
|
|
|
@ -16,31 +16,33 @@
|
|||
<div style="text-align:center;padding:8px">
|
||||
<div class="title" id="title"><%= thread.title %></div>
|
||||
<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 style="text-align:center;padding:8px">
|
||||
<!-- THREAD AUTHOR AND PROFILE PHOTO -->
|
||||
|
||||
<% if (user && !thread.deleted){ %>
|
||||
|
||||
<a onclick="delete_thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</a>
|
||||
<a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary" >EDIT</a>
|
||||
<a onclick="delete_thread('<%= thread.id %>')" class="btn-outline-primary">DELETE</a>
|
||||
<a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary">EDIT</a>
|
||||
<% } else if (thread.deleted) { %>
|
||||
<h3 style="display:inline;">This thread has been deleted</h3>
|
||||
<a onclick="undelete_thread('<%= thread.id %>')" class="btn-primary" >UNDELETE</a>
|
||||
<a onclick="undelete_thread('<%= thread.id %>')" class="btn-primary">UNDELETE</a>
|
||||
|
||||
<% }; %>
|
||||
</div>
|
||||
|
||||
<div id="messages" value="<%= thread.id %>">
|
||||
<div id="messages">
|
||||
|
||||
<% messages.filter(Boolean).forEach(message=>{ %>
|
||||
|
||||
<div class="message" id="message-<%= message.id %>">
|
||||
<div class="left">
|
||||
<img src="<%= message.author.avatar || '/images/guest.png' %>"/>
|
||||
<img src="<%= message.author.avatar || '/images/guest.png' %>" />
|
||||
<div class="username"><a href="/users/<%=message.authorID %>"><%=message.author.name %></a></div>
|
||||
<div class="date">
|
||||
<%= new Date(message.time).toLocaleDateString() %>
|
||||
|
@ -54,17 +56,17 @@
|
|||
<% if(user){ %>
|
||||
<% 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){ %>
|
||||
<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){ %>
|
||||
<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 class="dots-menu" id="dot-<%=message.id %>">
|
||||
<div class="dots-menu">
|
||||
<% if (!message.deleted){ %>
|
||||
<a onclick="delete_message('<%=message.id %>');">Delete</a>
|
||||
<a onclick="edit_message('<%=message.id %>');">Edit</a>
|
||||
|
@ -74,15 +76,14 @@
|
|||
</div>
|
||||
<% } %>
|
||||
|
||||
|
||||
<div class="reactions">
|
||||
<div>
|
||||
<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>
|
||||
<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>
|
||||
<% }; %>
|
||||
|
@ -93,17 +94,16 @@
|
|||
</div>
|
||||
<% if (user){ %>
|
||||
|
||||
<div class="message">
|
||||
<div class="message" id="send-div">
|
||||
<form id="send" style="width:100%">
|
||||
<textarea rows="4"
|
||||
style="
|
||||
font-family:inherit;
|
||||
width: 100%;
|
||||
margin: 10px;
|
||||
border: 2px solid #e3e3e3;" name="content"></textarea>
|
||||
<textarea rows="4" name="content"></textarea>
|
||||
<input name="threadID" type="hidden" value="<%= thread.id %>"></input>
|
||||
<input name="page" type="hidden" value="<%= page %>"></input>
|
||||
<button class="btn-primary">Send!</button>
|
||||
|
||||
</form>
|
||||
|
||||
</div>
|
||||
<script type="module">
|
||||
import request from "../../js/request.js";
|
||||
|
||||
|
@ -113,20 +113,19 @@
|
|||
|
||||
const data = new FormData(e.target);
|
||||
|
||||
request("/api/messages", "POST", { threadID: "<%= thread.id %>", content: data.get("content") })
|
||||
.then(res => {
|
||||
const res = await request("/api/messages", "POST", {
|
||||
threadID: "<%= thread.id %>",
|
||||
content: data.get("content")
|
||||
})
|
||||
|
||||
let tp = Number("<%= thread.pages %>")
|
||||
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}`;
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
</script>
|
||||
<button class="btn-primary">Send!</button>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<% }%>
|
||||
<div class="pagination">
|
||||
<div class="back">
|
||||
|
@ -151,8 +150,9 @@
|
|||
|
||||
<script>
|
||||
document.getElementById("message-<%= scroll %>").scrollIntoView();
|
||||
|
||||
function dots(id) {
|
||||
document.getElementById('dot-'+id).classList.toggle('active')
|
||||
document.getElementById('message-' + id).querySelector(".dots-menu").classList.toggle('active')
|
||||
}
|
||||
</script>
|
||||
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
<%- include("extra/meta", {title: "Thread list!" }) %>
|
||||
|
||||
<body>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
||||
|
||||
<link rel="stylesheet" href="/css/threads.css" />
|
||||
|
||||
<%- include("extra/navbar") %>
|
||||
<%- include("extra/navbar") %>
|
||||
|
||||
<div class="threads">
|
||||
<div class="threads">
|
||||
|
||||
<% threads.forEach(thread=>{ %>
|
||||
<a href="<%= thread.getLink() %>" class="">
|
||||
|
@ -31,6 +31,6 @@
|
|||
<br>
|
||||
<% }); %>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
101
views/user.ejs
101
views/user.ejs
|
@ -6,10 +6,40 @@
|
|||
|
||||
<body>
|
||||
<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") %>
|
||||
|
||||
<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;">
|
||||
<img style="width:100px;height:100px;border-radius:50%;" src="<%=member.avatar %>">
|
||||
</div>
|
||||
|
@ -17,7 +47,6 @@
|
|||
<h2 class="box-title">Name:</h2>
|
||||
<h2 class="box-value"><%= member.name %></h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2 class="box-title">Created at:</h2>
|
||||
<h2 class="box-value"><%= new Date(member.time).toLocaleString() %></h2>
|
||||
|
@ -26,9 +55,8 @@
|
|||
<h2 class="box-title">Is admin?</h2>
|
||||
<h2 class="box-value"><%= member.admin ? "Yes" : "No" %></h2>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h2 class="box-title"> Message:</h2>
|
||||
<h2 class="box-title">Message:</h2>
|
||||
<h2 class="box-value"><%= counts.message %></h2>
|
||||
</div>
|
||||
<div class="box">
|
||||
|
@ -42,71 +70,6 @@
|
|||
<%= member.about %>
|
||||
</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>
|
||||
</body>
|
||||
|
||||
|
|
|
@ -23,4 +23,3 @@
|
|||
|
||||
</div>
|
||||
</body>
|
||||
|
||||
|
|
Loading…
Reference in a new issue