mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-11-23 04: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
|
||||||
alert("Message deleted");
|
const message = document.getElementById(`message-${id}`);
|
||||||
document.getElementById("dots-" + id).innerHTML = `
|
alert("Message deleted");
|
||||||
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: var(--important)"></i>
|
|
||||||
`+ document.getElementById("dots-" + id).innerHTML;
|
message.querySelector(".dots-menu").innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) 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}.`)
|
return res.error(404, `You do not have permissions to view this message with id ${id}.`)
|
||||||
|
|
||||||
next();
|
next();
|
||||||
|
@ -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;
|
||||||
|
@ -23,7 +24,7 @@
|
||||||
tr:nth-child(even) {
|
tr:nth-child(even) {
|
||||||
background-color: #dddddd;
|
background-color: #dddddd;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<h1 style="color: var(--main);">Welcome to the admin panel of the forum, <%= user.name %>!</h1>
|
<h1 style="color: var(--main);">Welcome to the admin panel of the forum, <%= user.name %>!</h1>
|
||||||
|
@ -33,45 +34,46 @@
|
||||||
|
|
||||||
<h2 style="color: var(--second);">Banned users:</h2>
|
<h2 style="color: var(--second);">Banned users:</h2>
|
||||||
|
|
||||||
<table >
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
<th>Reason</th>
|
<th>Reason</th>
|
||||||
<th>AuthorID</th>
|
<th>AuthorID</th>
|
||||||
</tr>
|
</tr>
|
||||||
<% for (const ban of bans) { %>
|
<% for (const ban of bans) { %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%=ban.ip%></td>
|
<td><%=ban.ip%></td>
|
||||||
<td><%=ban.reason%></td>
|
<td><%=ban.reason%></td>
|
||||||
<td><%=ban.authorID%></td>
|
<td><%=ban.authorID%></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% } %>
|
<% } %>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import request from "../../js/request.js";
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
window.unban = async function () {
|
window.unban = async function() {
|
||||||
const ip = prompt("Enter ip to unban");
|
const ip = prompt("Enter ip to unban");
|
||||||
const response = await request("/api/bans/"+ip,"DELETE");
|
const response = await request("/api/bans/" + ip, "DELETE");
|
||||||
if(response)
|
if (response)
|
||||||
alert("IP unbanned!");
|
alert("IP unbanned!");
|
||||||
else
|
else
|
||||||
alert("IP is not unbanned!");
|
alert("IP is not unbanned!");
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
window.ban = async function () {
|
window.ban = async function() {
|
||||||
const ip = prompt("Enter ip to ban");
|
const ip = prompt("Enter ip to ban");
|
||||||
const response = await request("/api/bans/"+ip);
|
const response = await request("/api/bans/" + ip);
|
||||||
if(response)
|
if (response)
|
||||||
alert("IP banned!");
|
alert("IP banned!");
|
||||||
else
|
else
|
||||||
alert("IP is not banned!");
|
alert("IP is not banned!");
|
||||||
location.reload();
|
location.reload();
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</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" />
|
||||||
|
|
||||||
|
@ -20,32 +18,30 @@
|
||||||
<textarea rows="4" cols="50" name="content" class="input"></textarea>
|
<textarea rows="4" cols="50" name="content" class="input"></textarea>
|
||||||
|
|
||||||
|
|
||||||
<button class="btn-primary" style="width:100%" type="submit">Create Thread!</button>
|
<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";
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
document.addEventListener("submit", async e => {
|
document.addEventListener("submit", async e => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const form = e.target;
|
const form = e.target;
|
||||||
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")
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
if (response)
|
if (response)
|
||||||
window.location.href = "/threads/" + response.id;
|
window.location.href = "/threads/" + response.id;
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</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>
|
<!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;">
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<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="description" content="Akf-forum!">
|
||||||
<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>
|
|
@ -2,56 +2,58 @@
|
||||||
|
|
||||||
<% if (user?.admin){ %>
|
<% if (user?.admin){ %>
|
||||||
<div class="admin-bar">
|
<div class="admin-bar">
|
||||||
<a href="/admin" class="admin-bar">You are admin, and you can go your page!</a>
|
<a href="/admin" class="admin-bar">You are admin, and you can go your page!</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<div class="header">
|
<div class="header">
|
||||||
<a class="logo" href="/">AKF <span>FORUM</span></a>
|
<a class="logo" href="/">AKF <span>FORUM</span></a>
|
||||||
<div class="buttons">
|
<div class="buttons">
|
||||||
|
|
||||||
<% if (user){ %>
|
<% 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="box-username"><%= user.name %>
|
||||||
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<a id="logout" href="/login" class="btn-primary">Logout</a>
|
<a id="logout" href="/login" class="btn-primary">Logout</a>
|
||||||
<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',
|
||||||
headers: {
|
body: JSON.stringify({
|
||||||
"Content-Type": "application/json"
|
theme: "<%=user.theme === `default` ? `black` : `default` %>"
|
||||||
}
|
}),
|
||||||
})
|
headers: {
|
||||||
location.reload()
|
"Content-Type": "application/json"
|
||||||
}
|
}
|
||||||
</script>
|
})
|
||||||
|
location.reload()
|
||||||
|
}
|
||||||
|
</script>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
|
|
||||||
<a id="login" href="/login" class="btn-primary">Login</a>
|
<a id="login" href="/login" class="btn-primary">Login</a>
|
||||||
<a href="/register" class="btn-outline-primary">Register</a>
|
<a href="/register" class="btn-outline-primary">Register</a>
|
||||||
<script>
|
<script>
|
||||||
document.getElementById("login").href += "?redirect="+window.location.pathname;
|
document.getElementById("login").href += "?redirect=" + window.location.pathname;
|
||||||
</script>
|
</script>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="menu">
|
<div class="menu">
|
||||||
<a href="/threads" class="menu-item">Threads</a>
|
<a href="/threads" class="menu-item">Threads</a>
|
||||||
<a href="/users" class="menu-item">Users</a>
|
<a href="/users" class="menu-item">Users</a>
|
||||||
<a href="/search" class="menu-item">Search</a>
|
<a href="/search" class="menu-item">Search</a>
|
||||||
<a href="/threads/create/" class="menu-item">Create Thread</a>
|
<a href="/threads/create/" class="menu-item">Create Thread</a>
|
||||||
<script>
|
<script>
|
||||||
const menuItems = document.getElementsByClassName("menu-item");
|
const menuItems = document.getElementsByClassName("menu-item");
|
||||||
|
|
||||||
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>
|
|
@ -4,58 +4,58 @@
|
||||||
<%- include("extra/meta", {title: "Welcome to the Akf-forum!" }) %>
|
<%- include("extra/meta", {title: "Welcome to the Akf-forum!" }) %>
|
||||||
|
|
||||||
|
|
||||||
<body >
|
<body>
|
||||||
<link rel="stylesheet" href="/css/user.css" />
|
<link rel="stylesheet" href="/css/user.css" />
|
||||||
|
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<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="avatar"><img src="<%=user.avatar %>"></div>
|
<div class="box-username">Welcome, <%= user.name %>
|
||||||
|
<div class="avatar"><img src="<%=user.avatar %>"></div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
You can log out of the site here:
|
You can log out of the site here:
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
<a href="/login" class="btn-outline-primary">LOGOUT</a>
|
<a href="/login" class="btn-outline-primary">LOGOUT</a>
|
||||||
<br>
|
<br>
|
||||||
<% } else { %>
|
<% } else { %>
|
||||||
<h2 style="color: var(--main);">Welcome, Guest!<br>You can press the button to register:
|
<h2 style="color: var(--main);">Welcome, Guest!<br>You can press the button to register:
|
||||||
<a href="/register" class="btn-outline-primary">REGISTER</a>
|
<a href="/register" class="btn-outline-primary">REGISTER</a>
|
||||||
|
|
||||||
</h2>
|
</h2>
|
||||||
<% } %>
|
<% } %>
|
||||||
|
|
||||||
<h2 style="color: var(--second);">Statistics:</h2>
|
<h2 style="color: var(--second);">Statistics:</h2>
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<h2 class="box-title">Message count:</h2>
|
|
||||||
<h2 class="box-value"><%= messages %></h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<h2 class="box-title">User count: </h2>
|
|
||||||
<h2 class="box-value"><%= users %> </h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<h2 class="box-title">Thread count:</h2>
|
|
||||||
<h2 class="box-value"><%= threads %></h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="box">
|
|
||||||
<h2 class="box-title">Memory usage:</h2>
|
|
||||||
<h2 class="box-value"><%= mem.toFixed(2); %> MB</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2 class="box-title">Message count:</h2>
|
||||||
|
<h2 class="box-value"><%= messages %></h2>
|
||||||
</div>
|
</div>
|
||||||
<%- include("extra/footer") %>
|
|
||||||
|
<div class="box">
|
||||||
|
<h2 class="box-title">User count: </h2>
|
||||||
|
<h2 class="box-value"><%= users %> </h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2 class="box-title">Thread count:</h2>
|
||||||
|
<h2 class="box-value"><%= threads %></h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="box">
|
||||||
|
<h2 class="box-title">Memory usage:</h2>
|
||||||
|
<h2 class="box-value"><%= mem.toFixed(2); %> MB</h2>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<%- include("extra/footer") %>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
@ -8,15 +7,15 @@
|
||||||
<body style="text-align: center;">
|
<body style="text-align: center;">
|
||||||
<link rel="stylesheet" href="/css/login.css" />
|
<link rel="stylesheet" href="/css/login.css" />
|
||||||
|
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<h1 class="title">Login</h1>
|
<h1 class="title">Login</h1>
|
||||||
|
|
||||||
<form action="/login?redirect=<%= redirect !== "/register" ? redirect : "/" %>" method="post">
|
<form action="/login?redirect=<%= redirect !== "/register" ? redirect : "/" %>" method="post">
|
||||||
|
|
||||||
<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="submit" style="width:100%"class="btn-primary" value="Login">
|
<input type="submit" style="width:100%" class="btn-primary" value="Login">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -5,24 +5,22 @@
|
||||||
|
|
||||||
|
|
||||||
<body style="text-align: center;">
|
<body style="text-align: center;">
|
||||||
<link rel="stylesheet" href="/css/login.css" />
|
<link rel="stylesheet" href="/css/login.css" />
|
||||||
|
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<h1 class="title">Register</h1>
|
<h1 class="title">Register</h1>
|
||||||
|
|
||||||
<form action="/register" method="post">
|
<form action="/register" method="post">
|
||||||
|
|
||||||
|
|
||||||
<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">
|
||||||
|
</form>
|
||||||
<input type="submit" class="btn-primary" style="width:100%;" value="Register">
|
|
||||||
</form>
|
|
||||||
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -16,31 +16,33 @@
|
||||||
<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 -->
|
||||||
|
|
||||||
<% if (user && !thread.deleted){ %>
|
<% if (user && !thread.deleted){ %>
|
||||||
|
|
||||||
<a onclick="delete_thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</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>
|
<a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary">EDIT</a>
|
||||||
<% } else if (thread.deleted) { %>
|
<% } else if (thread.deleted) { %>
|
||||||
<h3 style="display:inline;">This thread has been deleted</h3>
|
<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>
|
||||||
|
|
||||||
<div id="messages" value="<%= thread.id %>">
|
<div id="messages">
|
||||||
|
|
||||||
<% messages.filter(Boolean).forEach(message=>{ %>
|
<% messages.filter(Boolean).forEach(message=>{ %>
|
||||||
|
|
||||||
<div class="message" id="message-<%= message.id %>">
|
<div class="message" id="message-<%= message.id %>">
|
||||||
<div class="left">
|
<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="username"><a href="/users/<%=message.authorID %>"><%=message.author.name %></a></div>
|
||||||
<div class="date">
|
<div class="date">
|
||||||
<%= new Date(message.time).toLocaleDateString() %>
|
<%= new Date(message.time).toLocaleDateString() %>
|
||||||
|
@ -54,35 +56,34 @@
|
||||||
<% 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>
|
||||||
<% }else { %>
|
<% }else { %>
|
||||||
<a onclick="undelete_message('<%=message.id %>');">Undelete</a>
|
<a onclick="undelete_message('<%=message.id %>');">Undelete</a>
|
||||||
<% } %>
|
<% } %>
|
||||||
</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,40 +94,38 @@
|
||||||
</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>
|
||||||
|
|
||||||
<script type="module">
|
|
||||||
import request from "../../js/request.js";
|
|
||||||
|
|
||||||
document.getElementById("send").addEventListener("submit", async e => {
|
|
||||||
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
const data = new FormData(e.target);
|
|
||||||
|
|
||||||
request("/api/messages", "POST", { threadID: "<%= thread.id %>", content: data.get("content") })
|
|
||||||
.then(res => {
|
|
||||||
let tp = Number("<%= thread.pages %>")
|
|
||||||
let tm = Number("<%= thread.count %>")
|
|
||||||
if (tp*10===tm) tp++;
|
|
||||||
if (res) location.href = `/threads/${data.get("threadID")}?page=${tp-1}`;
|
|
||||||
|
|
||||||
});
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
<button class="btn-primary">Send!</button>
|
<button class="btn-primary">Send!</button>
|
||||||
|
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
<script type="module">
|
||||||
|
import request from "../../js/request.js";
|
||||||
|
|
||||||
|
document.getElementById("send").addEventListener("submit", async e => {
|
||||||
|
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
const data = new FormData(e.target);
|
||||||
|
|
||||||
|
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 (res) location.href = `/threads/${data.get("threadID")}?page=${tp-1}`;
|
||||||
|
|
||||||
|
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<% }%>
|
<% }%>
|
||||||
<div class="pagination">
|
<div class="pagination">
|
||||||
<div class="back">
|
<div class="back">
|
||||||
|
@ -150,9 +149,10 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
|
|
|
@ -3,34 +3,34 @@
|
||||||
<%- include("extra/meta", {title: "Thread list!" }) %>
|
<%- include("extra/meta", {title: "Thread list!" }) %>
|
||||||
|
|
||||||
<body>
|
<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" />
|
<link rel="stylesheet" href="/css/threads.css" />
|
||||||
|
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<div class="threads">
|
<div class="threads">
|
||||||
|
|
||||||
<% threads.forEach(thread=>{ %>
|
<% threads.forEach(thread=>{ %>
|
||||||
<a href="<%= thread.getLink() %>" class="">
|
<a href="<%= thread.getLink() %>" class="">
|
||||||
<div class="threads-box">
|
<div class="threads-box">
|
||||||
<div class="thread-box-title">
|
<div class="thread-box-title">
|
||||||
<% if (thread.deleted) { %> <span>[DELETED]</span><% } %>
|
<% if (thread.deleted) { %> <span>[DELETED]</span><% } %>
|
||||||
<%= thread.title %>
|
<%= thread.title %>
|
||||||
</div>
|
</div>
|
||||||
<div class="box-username">
|
<div class="box-username">
|
||||||
<% if (user && !thread.deleted){ %>
|
<% if (user && !thread.deleted){ %>
|
||||||
<a class="btn-danger" onclick="fetch('/api/threads/<%= thread.id %>/',{method:'DELETE'})"><i class="fa-solid fa-trash-can"></i></a>
|
<a class="btn-danger" onclick="fetch('/api/threads/<%= thread.id %>/',{method:'DELETE'})"><i class="fa-solid fa-trash-can"></i></a>
|
||||||
<% } %>
|
<% } %>
|
||||||
<%= thread.author.name %> <div class="avatar"><img src="<%=thread.author.avatar %>"> </div>
|
<%= thread.author.name %> <div class="avatar"><img src="<%=thread.author.avatar %>"> </div>
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
</a>
|
</a>
|
||||||
<br>
|
<br>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</body>
|
</body>
|
103
views/user.ejs
103
views/user.ejs
|
@ -6,18 +6,47 @@
|
||||||
|
|
||||||
<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>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
<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,9 +55,8 @@
|
||||||
<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>
|
||||||
</div>
|
</div>
|
||||||
<div class="box">
|
<div class="box">
|
||||||
|
@ -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>
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,16 @@
|
||||||
<%- include("extra/navbar") %>
|
<%- include("extra/navbar") %>
|
||||||
|
|
||||||
<div class="users">
|
<div class="users">
|
||||||
<% users.forEach(user=>{ %>
|
<% users.forEach(user=>{ %>
|
||||||
<div style="display:flex;justify-content:center;">
|
<div style="display:flex;justify-content:center;">
|
||||||
<div class="user-box">
|
<div class="user-box">
|
||||||
<img src="<%= user.avatar %>" class="user-box-img">
|
<img src="<%= user.avatar %>" class="user-box-img">
|
||||||
<div class="user-box-title"> <a href="<%= user.getLink() %>">
|
<div class="user-box-title"> <a href="<%= user.getLink() %>">
|
||||||
<% if (user.deleted) { %> <span style="color: var(--important);">[DELETED]</span><% } %>
|
<% if (user.deleted) { %> <span style="color: var(--important);">[DELETED]</span><% } %>
|
||||||
<%= user.name %></a></div>
|
<%= user.name %></a></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% }); %>
|
<% }); %>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue