Page is OK! Edits, About me, ip ban

This commit is contained in:
Akif9748 2022-08-29 19:31:59 +03:00
parent 5b020ff548
commit 5c259f02f3
23 changed files with 4096 additions and 102 deletions

View file

@ -20,18 +20,21 @@ But in front end, the API will works with session.
- POST `/api/users/:id/delete` for delete user. - POST `/api/users/:id/delete` for delete 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. - POST `/api/users/:id/admin` for give admin permissions for a user.
- POST `/api/users/:id/edit` for edit user.
- GET `/api/threads/:id` for fetch thread. - GET `/api/threads/:id` for fetch thread.
- GET `/api/threads/:id/messages/` for fetch messages in thread. - GET `/api/threads/:id/messages/` for fetch messages in thread.
- POST `/api/threads` for create thread. - POST `/api/threads` for create thread.
- POST `/api/threads/:id/delete` for delete thread. - POST `/api/threads/:id/delete` for delete thread.
- POST `/api/threads/:id/undelete` for undelete thread. - POST `/api/threads/:id/undelete` for undelete thread.
- POST `/api/threads/:id/edit` for edit thread.
- GET `/api/messages/:id` for fetch message. - GET `/api/messages/:id` for fetch message.
- POST `/api/messages` for create message. - POST `/api/messages` for create message.
- POST `/api/messages/:id/delete` for delete message. - POST `/api/messages/:id/delete` for delete message.
- POST `/api/messages/:id/undelete` for undelete message. - POST `/api/messages/:id/undelete` for undelete message.
- POST `/api/messages/:id/react/:type` for react to a message. - POST `/api/messages/:id/react/:type` for react to a message.
- POST `/api/messages/:id/edit` for edit message.
### Example request: ### Example request:
GET ```/api/messages/0``` GET ```/api/messages/0```

View file

@ -31,17 +31,15 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
### TO-DO: ### TO-DO:
- If thread deleted, not show its messages in API. - If thread deleted, not show its messages in API.
- Profile photos will store in database - Profile photos will store in database
- regex for pfp for now and - replacer function global
- admin perm for undelete, thread + message
- page support for threads, send, if multi page, send => other page
- message "<b>"
- author name of thread - author name of thread
- page for threads - users - page for threads - users []
- API, ?fast= - API, ?fast=
- fix error messages, ~~declared as id~~, other... - extra ratelimits
- better edits
### Frontend ### Frontend
### User #### User
| To do | Is done? | Priority | | To do | Is done? | Priority |
| ----- | -------- | -------- | | ----- | -------- | -------- |
| Login via redirect query | 🟢 | HIGH | | Login via redirect query | 🟢 | HIGH |
@ -51,12 +49,12 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
| Message count | 🟢 | MEDIUM | | Message count | 🟢 | MEDIUM |
| Delete user | 🟢 | HIGH | | Delete user | 🟢 | HIGH |
| Undelete | 🟢 | MEDIUM | | Undelete | 🟢 | MEDIUM |
| PM | 🔴 | MEDIUM | | About me | 🟢 | LOW |
| About me | 🔴 | LOW | | Edit user | 🟡 | HIGH |
| Edit user | 🔴 | HIGH | | IP ban | 🟢 | MEDIUM |
| IP ban | 🔴 | MEDIUM | | Profile Message | 🔴 | MEDIUM |
### Messages #### Messages
| To do | Is done? | Priority | | To do | Is done? | Priority |
| ----- | -------- | -------- | | ----- | -------- | -------- |
| Ratelimit | 🟢 | HIGH | | Ratelimit | 🟢 | HIGH |
@ -65,16 +63,16 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
| Regex for scripts | 🟢 | HIGH | | Regex for scripts | 🟢 | HIGH |
| Undelete | 🟢 | MEDIUM | | Undelete | 🟢 | MEDIUM |
| React | 🟢 | MEDIUM | | React | 🟢 | MEDIUM |
| Edit | 🔴 | MEDIUM | | Edit | 🟢 | MEDIUM |
### Threads #### Threads
| To do | Is done? | Priority | | To do | Is done? | Priority |
| ----- | -------- | -------- | | ----- | -------- | -------- |
| Ratelimit | 🟢 | HIGH | | Ratelimit | 🟢 | HIGH |
| Create | 🟢 | HIGH | | Create | 🟢 | HIGH |
| Delete | 🟢 | HIGH | | Delete | 🟢 | HIGH |
| Undelete | 🟢 | MEDIUM | | Undelete | 🟢 | MEDIUM |
| Edit | 🔴 | MEDIUM | | Edit | 🟢 | MEDIUM |
### API ### API
| To do | Is done? | To do | Is done?
@ -85,17 +83,17 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
| Get message & thread & user | 🟢 | Get message & thread & user | 🟢
| Delete message & thread & user | 🟢 | Delete message & thread & user | 🟢
| Undelete message & thread & user | 🟢 | Undelete message & thread & user | 🟢
| Edit message & thread & user | 🔴 | Edit message & thread & user | 🟢
### Other ### Other
| To do | Is done? | Priority | | To do | Is done? | Priority |
| ----- | -------- | -------- | | ----- | -------- | -------- |
| from form to AJAX | 🟢 | HIGH | | from form to AJAX | 🟢 | HIGH |
| auto-scroll | 🟢 | LOW | | auto-scroll | 🟢 | LOW |
| Page support, support message limit correct | 🟢 | MEDIUM |
| Multi-theme support, black theme | 🟡 | LOW | | Multi-theme support, black theme | 🟡 | LOW |
| Search | 🔴 | MEDIUM | | Search | 🔴 | MEDIUM |
| Page support, support message limit correct | 🔴 | MEDIUM | | Locales | 🔴 | LOW |
| Locales | 🔴 | MEDIUM |
| Footer | 🔴 | LOW | | Footer | 🔴 | LOW |
## Major Version History ## Major Version History
- V3: New Theme - V3: New Theme

View file

@ -1,37 +1,43 @@
const { def_theme } = require("./config.json"), const { def_theme } = require("./config.json"),
session = require('express-session'), ipBlock = require('express-ip-block'),
{ UserModel } = require("./models"), session = require('express-session'),
bodyParser = require('body-parser'), { UserModel, BanModel } = require("./models"),
port = process.env.PORT || 3000, bodyParser = require('body-parser'),
mongoose = require("mongoose"), port = process.env.PORT || 3000,
express = require('express'), mongoose = require("mongoose"),
fs = require("fs"), express = require('express'),
app = express(); fs = require("fs"),
app = express();
app.ips = [];
require("dotenv").config(); require("dotenv").config();
mongoose.connect(process.env.MONGO_DB_URL, () => console.log("Database is connected")); mongoose.connect(process.env.MONGO_DB_URL,
async () => console.log("Connected to mongoDB with", app.ips = await BanModel.find({}).select("ip"), "banned IPs"));
app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }));
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static("public"));
app.set("view engine", "ejs"); app.set("view engine", "ejs");
app.use(express.json());
app.use(async (req, res, next) => { app.use(session({ secret: 'secret', resave: true, saveUninitialized: true }),
bodyParser.urlencoded({ extended: true }),
express.static("public"), express.json(), ipBlock(app.ips),
async (req, res, next) => {
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 });
res.error = (type, error) => res.reply("error", { type, error }, type); res.error = (type, error) => res.reply("error", { type, error }, type);
if (req.user?.deleted) { if (req.user?.deleted) {
req.session.destroy(); req.session.destroy();
return res.error(403, "Your account has been deleted."); return res.error(403, "Your account has been deleted.");
} }
next(); next();
}); }
);
for (const file of fs.readdirSync("./routes")) for (const file of fs.readdirSync("./routes"))
app.use("/" + file.replace(".js", ""), require(`./routes/${file}`)); app.use("/" + file.replace(".js", ""), require(`./routes/${file}`));
app.all("*", (req, res) => res.error(404, "We have not got this page.")); app.all("*", (req, res) => res.error(404, "We have not got this page."));

7
models/Ban.js Normal file
View file

@ -0,0 +1,7 @@
const mongoose = require("mongoose")
const schema = new mongoose.Schema({
ip: { type: String, unique: true }
});
module.exports = mongoose.model('ban', schema);

View file

@ -14,9 +14,7 @@ const schema = new mongoose.Schema({
react: { react: {
like: [Number], like: [Number],
dislike: [Number] dislike: [Number]
}, }
index: { type: Number, default: 0 }
}) })
schema.virtual('authorID').get(function () { return this.author?.id; }); schema.virtual('authorID').get(function () { return this.author?.id; });

View file

@ -1,5 +1,6 @@
const mongoose = require("mongoose") const mongoose = require("mongoose");
const UserModel = require("./User"); const UserModel = require("./User");
const MessageModel = require("./Message");
const schema = new mongoose.Schema({ const schema = new mongoose.Schema({
id: { type: String, unique: true }, id: { type: String, unique: true },
@ -8,13 +9,19 @@ const schema = new mongoose.Schema({
title: String, title: String,
time: { type: Date, default: Date.now }, time: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false }, deleted: { type: Boolean, default: false },
edited: { type: Boolean, default: false },
messages: [String], messages: [String],
views: { type: Number, default: 0 } views: { type: Number, default: 0 }
}); });
schema.virtual('authorID').get(function() { return this.author?.id; }); schema.virtual('authorID').get(function () { return this.author?.id; });
schema.methods.messageCount = async function (admin = false) {
const query = { threadID: this.id };
if (!admin) query.deleted = false;
return await MessageModel.count(query) || 0;
};
schema.methods.push = function (messageID) { schema.methods.push = function (messageID) {
this.messages.push(messageID); this.messages.push(messageID);
return this; return this;

View file

@ -7,6 +7,8 @@ const schema = new mongoose.Schema({
avatar: { type: String, default: "/images/guest.png" }, avatar: { type: String, default: "/images/guest.png" },
time: { type: Date, default: Date.now }, time: { type: Date, default: Date.now },
deleted: { type: Boolean, default: false }, deleted: { type: Boolean, default: false },
edited: { type: Boolean, default: false },
about: { type: String, default: "" },
admin: { type: Boolean, default: false }, admin: { type: Boolean, default: false },
theme: { type: String, default: def_theme } theme: { type: String, default: def_theme }

View file

@ -1,6 +1,7 @@
const UserModel = require("./User"), const UserModel = require("./User"),
MessageModel = require("./Message"), MessageModel = require("./Message"),
ThreadModel = require("./Thread"), ThreadModel = require("./Thread"),
SecretModel = require("./Secret"); SecretModel = require("./Secret"),
BanModel = require("./Ban");
module.exports = { UserModel, MessageModel, ThreadModel, SecretModel }; module.exports = { UserModel, MessageModel, ThreadModel, SecretModel, BanModel };

3860
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -29,8 +29,11 @@
"dotenv": "^16.0.1", "dotenv": "^16.0.1",
"ejs": "^3.1.6", "ejs": "^3.1.6",
"express": "^4.17.3", "express": "^4.17.3",
"express-ip-block": "^0.1.2",
"express-rate-limit": "^6.5.1", "express-rate-limit": "^6.5.1",
"express-session": "^1.17.2", "express-session": "^1.17.2",
"mongoose": "^6.5.1" "i": "^0.3.7",
"mongoose": "^6.5.1",
"npm": "^8.18.0"
} }
} }

View file

@ -1,38 +1,40 @@
import request from "./request.js"; import request from "./request.js";
/** window.edit_t = async function (id) {
* Message Sender const title = prompt("Enter new title!");
*/ const res = await request(`/api/threads/${id}/edit`, "POST", { title });
document.getElementById("send")?.addEventListener("submit", async e => { if (res.error) return;
alert(`Thread updated`);
document.getElementById("title").innerHTML = title;
e.preventDefault(); }
const form = e.target;
const data = new FormData(form);
request("/api/messages", "POST", { threadID: data.get("threadID"), content: data.get("content") }) window.thread = async function (id, un = "") {
.then(res => { const res = await request(`/api/threads/${id}/${un}delete`);
if (res) location.href = `/messages/${res.id}`; if (res.error) return;
});
});
/**
* OTHER FUNCTIONS
*/
window.thread = async function (id, un= "") {
await request(`/api/threads/${id}/${un}delete`);
alert(`Thread ${un}deleted`); alert(`Thread ${un}deleted`);
location.reload(); location.reload();
} }
window.edit_message = async function (id) {
const content = prompt("Enter new content!");
const res = await request(`/api/messages/${id}/edit`, "POST", { content });
if (res.error) return;
alert(`Message updated`);
document.getElementById("message-" + id).querySelector(".content").innerHTML = content;
}
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(); document.getElementById("deleted-" + id).remove();
document.getElementById("dot-" + id).innerHTML = ` document.getElementById("dot-" + id).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) {
@ -40,10 +42,10 @@ window.delete_message = async function (id) {
document.getElementById("dots-" + id).innerHTML = ` document.getElementById("dots-" + id).innerHTML = `
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: RED;"></i> <i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: RED;"></i>
`+ document.getElementById("dots-" + id).innerHTML; `+ document.getElementById("dots-" + id).innerHTML;
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`; document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
} }
} }
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; document.getElementById(`like-${id}`).innerHTML = res.react.like.length;

View file

@ -23,7 +23,7 @@ app.use(async (req, res, next) => {
const user = await SecretModel.findOne({ username }); const user = await SecretModel.findOne({ username });
if (!user) if (!user)
return res.error(401, "We have not got any user has got this name") return res.error(401, `We don't have any thread with name ${username}.`)
if (!await bcrypt.compare(password, user.password)) return res.error(401, 'Incorrect Password!'); if (!await bcrypt.compare(password, user.password)) return res.error(401, 'Incorrect Password!');

View file

@ -14,6 +14,23 @@ app.get("/:id", async (req, res) => {
res.complate(message.toObject({ virtuals: true })); res.complate(message.toObject({ virtuals: true }));
})
app.post("/:id/edit", async (req, res) => {
const message = await MessageModel.get(req.params.id);
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
if (req.user.id !== message.authorID && !req.user.admin) return res.error(403, "You have not got permission for this.");
const { content = null } = req.body;
if (!content) return res.error(400, "Missing message content in request body.");
message.content = content;
message.edited=true;
await message.save();
res.complate(message.toObject({ virtuals: true }));
}) })
app.post("/", rateLimit({ app.post("/", rateLimit({
@ -31,7 +48,7 @@ app.post("/", rateLimit({
if (!thread) return res.error(404, `We don't have any thread with id ${threadID}.`); if (!thread) return res.error(404, `We don't have any thread with id ${threadID}.`);
const message = await new MessageModel({ content, author: req.user, threadID: thread.id, index: thread.messages.length }).takeId(); const message = await new MessageModel({ content, author: req.user, threadID: thread.id }).takeId();
await message.save(); await message.save();
await thread.push(message.id).save(); await thread.push(message.id).save();

View file

@ -53,7 +53,21 @@ app.post("/", async (req, res) => {
res.complate(thread.toObject({ virtuals: true })); res.complate(thread.toObject({ virtuals: true }));
}); });
app.post("/:id/edit", async (req, res) => {
const thread = await ThreadModel.get(req.params.id);
if (!thread || (thread.deleted && req.user && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
if (req.user.id !== thread.authorID && !req.user.admin) return res.error(403, "You have not got permission for this.");
const { title = null } = req.body;
if (!title) return res.error(400, "Missing thread title in request body.");
thread.title = title;
await thread.save();
res.complate(thread.toObject({ virtuals: true }));
})
app.post("/:id/delete", async (req, res) => { app.post("/:id/delete", async (req, res) => {
const thread = await ThreadModel.get(req.params.id); const thread = await ThreadModel.get(req.params.id);
if (!thread || thread.deleted) return res.error(404, `We don't have any thread with id ${req.params.id}.`); if (!thread || thread.deleted) return res.error(404, `We don't have any thread with id ${req.params.id}.`);
@ -77,6 +91,8 @@ app.post("/:id/undelete", async (req, res) => {
if (!thread.deleted) return res.error(404, "This thread is not deleted, first, delete it."); if (!thread.deleted) return res.error(404, "This thread is not deleted, first, delete it.");
thread.deleted = false; thread.deleted = false;
thread.edited=true;
await thread.save(); await thread.save();
res.complate(thread.toObject({ virtuals: true })); res.complate(thread.toObject({ virtuals: true }));

View file

@ -34,7 +34,7 @@ app.post("/:id/undelete/", async (req, res) => {
const member = await UserModel.get(req.params.id); const member = await UserModel.get(req.params.id);
if (!member ) return res.error(404, `We don't have any user with id ${req.params.id}.`); if (!member) return res.error(404, `We don't have any user with id ${req.params.id}.`);
if (!member.deleted) return res.error(404, "This user is not deleted, first, delete it."); if (!member.deleted) return res.error(404, "This user is not deleted, first, delete it.");
@ -44,6 +44,31 @@ app.post("/:id/undelete/", async (req, res) => {
res.complate(member.toObject({ virtuals: true })); res.complate(member.toObject({ virtuals: true }));
}) })
app.post("/:id/edit", async (req, res) => {
const member = await UserModel.get(req.params.id);
if (!member || (member.deleted && !req.user.admin)) return res.error(404, `We don't have any message with id ${req.params.id}.`);
if (req.user.id !== member.id && !req.user.admin) return res.error(403, "You have not got permission for this.");
const { avatar, name, about } = req.body;
if (!avatar && !name) return res.error(400, "Missing member informations in request body.");
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) member.name = name;
if (about) member.about = about;
member.edited = true;
await member.save();
res.complate(member.toObject({ virtuals: true }));
})
app.post("/:id/admin/", async (req, res) => { app.post("/:id/admin/", async (req, res) => {
const user = req.user; const user = req.user;
@ -54,10 +79,10 @@ app.post("/:id/admin/", async (req, res) => {
if (!user2) if (!user2)
return res.error(404, `We don't have any user with id ${id}.`); return res.error(404, `We don't have any user with id ${id}.`);
else {
user2.admin = true; user2.admin = true;
await user2.save() await user2.save()
}
res.complate(user2); res.complate(user2);

View file

@ -9,7 +9,7 @@ app.get("/:id", async (req, res) => {
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error( 404, if (!message || (message.deleted && req.user && !req.user.admin)) return res.error( 404,
`We don't have any message with id ${req.params.id}.`); `We don't have any message with id ${req.params.id}.`);
res.redirect(`/threads/${message.threadID}?scroll=${req.params.id}`); res.redirect(`/threads/${message.threadID}?scroll=${message.id}`);
}); });

View file

@ -14,7 +14,7 @@ app.post("/", async (req, res) => {
req.session.userid = null; req.session.userid = null;
let { username = null, password: body_pass = null, avatar } = req.body; let { username = null, password: body_pass = null, avatar, about } = req.body;
if (!username || !body_pass) return res.error(res, 400, "You forgot entering some values"); if (!username || !body_pass) return res.error(res, 400, "You forgot entering some values");
const user = await SecretModel.findOne({ username }); const user = await SecretModel.findOne({ username });
@ -24,6 +24,9 @@ app.post("/", async (req, res) => {
const user2 = new UserModel({ name: req.body.username }) const user2 = new UserModel({ name: req.body.username })
if (avatar && /https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/g.test(avatar)) user2.avatar = 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)) user2.avatar = avatar;
if (about) user2.about = about;
await user2.takeId() await user2.takeId()
await user2.save(); await user2.save();

View file

@ -18,8 +18,11 @@ app.get("/:id/", async (req, res) => {
const { user, params: { id } } = req const { user, params: { id } } = req
const page = Number(req.query.page || 0); let page = Number(req.query.page || 0);
const thread = await ThreadModel.get(id) const thread = await ThreadModel.get(id)
thread.count = await thread.messageCount(user?.admin);
thread.pages = Math.ceil(thread.count / 10);
if (thread && (user?.admin || !thread.deleted)) { if (thread && (user?.admin || !thread.deleted)) {
thread.views++; thread.views++;
const query = { threadID: id }; const query = { threadID: id };
@ -34,13 +37,12 @@ app.get("/:id/", async (req, res) => {
return message.toObject({ virtuals: true }); return message.toObject({ virtuals: true });
})) }))
res.reply("thread", { page, thread, messages, scroll: req.query.scroll || thread.messages[0].id }); res.reply("thread", { page, thread, messages, scroll: req.query.scroll || thread.messages[0].id });
thread.save(); thread.save();
} else } else
res.error(404, "We have not got this thread."); res.error(404, `We don't have any thread with id ${id}.`);
}); });

View file

@ -18,9 +18,13 @@ app.get("/:id", async (req, res) => {
const message = await MessageModel.count({ "author.id": id }); const message = await MessageModel.count({ "author.id": id });
const thread = await ThreadModel.count({ "author.id": id }); const thread = await ThreadModel.count({ "author.id": id });
member.about = member.about.replaceAll("&", "&amp;")
.replaceAll("<", "&lt;").replaceAll(">", "&gt;")
.replaceAll("\"", "&quot;").replaceAll("'", "&#39;")
.replaceAll("\n", "<br>");
res.reply("user", { member, counts: { message, thread } }) res.reply("user", { member, counts: { message, thread } })
} }
else res.error(404, `We don't have any user with id ${id}.`); else res.error(404, `We don't have any user with id ${id}.`);
}); });

View file

View file

@ -17,8 +17,9 @@
<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="text" name="avatar" placeholder="Avatar URL" class="input"> <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>

View file

@ -14,7 +14,7 @@
<% }; %> <% }; %>
<div style="text-align:center;padding:8px"> <div style="text-align:center;padding:8px">
<div class="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 %> <%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>
</div> </div>
@ -26,7 +26,7 @@
<% if (user && !thread.deleted){ %> <% if (user && !thread.deleted){ %>
<a onclick="thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</a> <a onclick="thread('<%= thread.id %>')" class="btn-outline-primary" >DELETE</a>
<a onclick="edit_thread('<%= thread.id %>')" class="btn-outline-primary" >EDIT</a> <a onclick="edit_t('<%= 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="thread('<%= thread.id %>', 'un')" class="btn-primary" >UNDELETE</a> <a onclick="thread('<%= thread.id %>', 'un')" class="btn-primary" >UNDELETE</a>
@ -58,6 +58,9 @@
<% if (message.deleted){ %> <% if (message.deleted){ %>
<i class='bx bx-trash bx-sm' id="deleted-<%=message.id %>" style="color: RED;"></i> <i class='bx bx-trash bx-sm' id="deleted-<%=message.id %>" style="color: RED;"></i>
<% } %> <% } %>
<% if (message.edited){ %>
<i class='bx bx-pencil bx-sm' id="edited-<%=message.id %>" style="color: GREEN;"></i>
<% } %>
<i class='bx bx-dots-horizontal-rounded' ></i> <i class='bx bx-dots-horizontal-rounded' ></i>
</div> </div>
@ -88,32 +91,53 @@
<% }); %> <% }); %>
</div> </div>
<% if (user){ %>
<div class="message"> <div class="message">
<form id="send"> <form id="send">
<textarea rows="4" cols="100" name="content"></textarea> <textarea rows="4" cols="100" 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>
<% if (user){ %> <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>
<%} else {%>
<a class="btn-outline-primary" href="/login?redirect=<%= thread.getLink() %>">Login for send</a>
<% }%>
</form> </form>
</div> </div>
<% }%>
<div class="pagination"> <div class="pagination">
<div class="back"> <div class="back">
<% if (page > 0){ %> <% if (page > 0){ %>
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a> <a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
<% } %> <% } %>
</div> </div>
<div class="numbers"> <div class="numbers">
<a class="number" href="<%= thread.getLink() %>?page=<%= page %>"><%= page+1 %></a> <% for(let i=0; i < thread.pages; i++){ %>
<a class="number <%= i==page?'active':'' %>" href="<%= thread.getLink() %>?page=<%= i %>"><%= i+1 %></a>
<% } %>
</div> </div>
<div class="after"> <div class="after">
<% if (Math.ceil(messages.length/10) > page){ %> <% if (thread.pages-1 > page) { %>
<a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a> <a href="<%= thread.getLink() %>?page=<%= page +1 %>" class='bx bxs-chevron-right'></a>
<% } %> <% } %>
</div> </div>

View file

@ -35,10 +35,18 @@
<h2 class="box-title">Thread:</h2> <h2 class="box-title">Thread:</h2>
<h2 class="box-value"><%= counts.thread %></h2> <h2 class="box-value"><%= counts.thread %></h2>
</div> </div>
<div class="box">
<h2 class="box-title">About:</h2><br>
</div>
<p class="box-value">
<%= member.about %>
</p>
<% if (user?.admin && !member.deleted) {%> <a class="btn-outline-primary" id="edit">Change name 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> <% if (user?.admin && !member.deleted) {%>
<a class="btn-outline-primary" id="admin">Give admin permissions!</a>
<a class="btn-outline-primary" id="delete">Delete user!</a>
<script type="module"> <script type="module">
@ -59,6 +67,15 @@
alert("User is deleted!"); alert("User is deleted!");
location.reload() location.reload()
}else if (e.target.id == "edit") {
const name = prompt("Enter new username!");
const res =await request(`/api/users/<%= member.id %>/edit`, "POST", { name });
if (res.error) return;
alert(`User updated!`);
location.reload();
} }