mirror of
https://github.com/Akif9748/akf-forum.git
synced 2024-12-22 23:59:08 +03:00
Page is OK! Edits, About me, ip ban
This commit is contained in:
parent
5b020ff548
commit
5c259f02f3
23 changed files with 4096 additions and 102 deletions
|
@ -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/undelete` for undelete 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/messages/` for fetch messages in thread.
|
||||
- POST `/api/threads` for create thread.
|
||||
- POST `/api/threads/:id/delete` for delete thread.
|
||||
- POST `/api/threads/:id/undelete` for undelete thread.
|
||||
- POST `/api/threads/:id/edit` for edit thread.
|
||||
|
||||
- GET `/api/messages/:id` for fetch message.
|
||||
- POST `/api/messages` for create message.
|
||||
- POST `/api/messages/:id/delete` for delete message.
|
||||
- POST `/api/messages/:id/undelete` for undelete message.
|
||||
- POST `/api/messages/:id/react/:type` for react to a message.
|
||||
- POST `/api/messages/:id/edit` for edit message.
|
||||
|
||||
### Example request:
|
||||
GET ```/api/messages/0```
|
||||
|
|
34
README.md
34
README.md
|
@ -31,17 +31,15 @@ Akf-forum has got an API for AJAX, other clients etc. And, you can learn about A
|
|||
### TO-DO:
|
||||
- If thread deleted, not show its messages in API.
|
||||
- Profile photos will store in database
|
||||
- regex for pfp for now and
|
||||
- admin perm for undelete, thread + message
|
||||
- page support for threads, send, if multi page, send => other page
|
||||
- message "<b>"
|
||||
- replacer function global
|
||||
- author name of thread
|
||||
- page for threads - users
|
||||
- page for threads - users []
|
||||
- API, ?fast=
|
||||
- fix error messages, ~~declared as id~~, other...
|
||||
- extra ratelimits
|
||||
- better edits
|
||||
|
||||
### Frontend
|
||||
### User
|
||||
#### User
|
||||
| To do | Is done? | Priority |
|
||||
| ----- | -------- | -------- |
|
||||
| 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 |
|
||||
| Delete user | 🟢 | HIGH |
|
||||
| Undelete | 🟢 | MEDIUM |
|
||||
| PM | 🔴 | MEDIUM |
|
||||
| About me | 🔴 | LOW |
|
||||
| Edit user | 🔴 | HIGH |
|
||||
| IP ban | 🔴 | MEDIUM |
|
||||
| About me | 🟢 | LOW |
|
||||
| Edit user | 🟡 | HIGH |
|
||||
| IP ban | 🟢 | MEDIUM |
|
||||
| Profile Message | 🔴 | MEDIUM |
|
||||
|
||||
### Messages
|
||||
#### Messages
|
||||
| To do | Is done? | Priority |
|
||||
| ----- | -------- | -------- |
|
||||
| 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 |
|
||||
| Undelete | 🟢 | MEDIUM |
|
||||
| React | 🟢 | MEDIUM |
|
||||
| Edit | 🔴 | MEDIUM |
|
||||
| Edit | 🟢 | MEDIUM |
|
||||
|
||||
### Threads
|
||||
#### Threads
|
||||
| To do | Is done? | Priority |
|
||||
| ----- | -------- | -------- |
|
||||
| Ratelimit | 🟢 | HIGH |
|
||||
| Create | 🟢 | HIGH |
|
||||
| Delete | 🟢 | HIGH |
|
||||
| Undelete | 🟢 | MEDIUM |
|
||||
| Edit | 🔴 | MEDIUM |
|
||||
| Edit | 🟢 | MEDIUM |
|
||||
|
||||
### API
|
||||
| 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 | 🟢
|
||||
| Delete message & thread & user | 🟢
|
||||
| Undelete message & thread & user | 🟢
|
||||
| Edit message & thread & user | 🔴
|
||||
| Edit message & thread & user | 🟢
|
||||
|
||||
### Other
|
||||
| To do | Is done? | Priority |
|
||||
| ----- | -------- | -------- |
|
||||
| from form to AJAX | 🟢 | HIGH |
|
||||
| auto-scroll | 🟢 | LOW |
|
||||
| Page support, support message limit correct | 🟢 | MEDIUM |
|
||||
| Multi-theme support, black theme | 🟡 | LOW |
|
||||
| Search | 🔴 | MEDIUM |
|
||||
| Page support, support message limit correct | 🔴 | MEDIUM |
|
||||
| Locales | 🔴 | MEDIUM |
|
||||
| Locales | 🔴 | LOW |
|
||||
| Footer | 🔴 | LOW |
|
||||
## Major Version History
|
||||
- V3: New Theme
|
||||
|
|
44
index.js
44
index.js
|
@ -1,37 +1,43 @@
|
|||
const { def_theme } = require("./config.json"),
|
||||
session = require('express-session'),
|
||||
{ UserModel } = require("./models"),
|
||||
bodyParser = require('body-parser'),
|
||||
port = process.env.PORT || 3000,
|
||||
mongoose = require("mongoose"),
|
||||
express = require('express'),
|
||||
fs = require("fs"),
|
||||
app = express();
|
||||
ipBlock = require('express-ip-block'),
|
||||
session = require('express-session'),
|
||||
{ UserModel, BanModel } = require("./models"),
|
||||
bodyParser = require('body-parser'),
|
||||
port = process.env.PORT || 3000,
|
||||
mongoose = require("mongoose"),
|
||||
express = require('express'),
|
||||
fs = require("fs"),
|
||||
app = express();
|
||||
app.ips = [];
|
||||
|
||||
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.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);
|
||||
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);
|
||||
|
||||
if (req.user?.deleted) {
|
||||
req.session.destroy();
|
||||
return res.error(403, "Your account has been deleted.");
|
||||
req.session.destroy();
|
||||
return res.error(403, "Your account has been deleted.");
|
||||
}
|
||||
next();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
|
||||
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."));
|
||||
|
||||
|
|
7
models/Ban.js
Normal file
7
models/Ban.js
Normal 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);
|
|
@ -14,9 +14,7 @@ const schema = new mongoose.Schema({
|
|||
react: {
|
||||
like: [Number],
|
||||
dislike: [Number]
|
||||
},
|
||||
index: { type: Number, default: 0 }
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
schema.virtual('authorID').get(function () { return this.author?.id; });
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const mongoose = require("mongoose")
|
||||
const UserModel = require("./User");
|
||||
const mongoose = require("mongoose");
|
||||
const UserModel = require("./User");
|
||||
const MessageModel = require("./Message");
|
||||
const schema = new mongoose.Schema({
|
||||
id: { type: String, unique: true },
|
||||
|
||||
|
@ -8,13 +9,19 @@ const schema = new mongoose.Schema({
|
|||
title: String,
|
||||
time: { type: Date, default: Date.now },
|
||||
deleted: { type: Boolean, default: false },
|
||||
edited: { type: Boolean, default: false },
|
||||
|
||||
messages: [String],
|
||||
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) {
|
||||
this.messages.push(messageID);
|
||||
return this;
|
||||
|
|
|
@ -7,6 +7,8 @@ const schema = new mongoose.Schema({
|
|||
avatar: { type: String, default: "/images/guest.png" },
|
||||
time: { type: Date, default: Date.now },
|
||||
deleted: { type: Boolean, default: false },
|
||||
edited: { type: Boolean, default: false },
|
||||
about: { type: String, default: "" },
|
||||
admin: { type: Boolean, default: false },
|
||||
theme: { type: String, default: def_theme }
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const UserModel = require("./User"),
|
||||
MessageModel = require("./Message"),
|
||||
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
3860
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -29,8 +29,11 @@
|
|||
"dotenv": "^16.0.1",
|
||||
"ejs": "^3.1.6",
|
||||
"express": "^4.17.3",
|
||||
"express-ip-block": "^0.1.2",
|
||||
"express-rate-limit": "^6.5.1",
|
||||
"express-session": "^1.17.2",
|
||||
"mongoose": "^6.5.1"
|
||||
"i": "^0.3.7",
|
||||
"mongoose": "^6.5.1",
|
||||
"npm": "^8.18.0"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,38 +1,40 @@
|
|||
import request from "./request.js";
|
||||
|
||||
/**
|
||||
* Message Sender
|
||||
*/
|
||||
document.getElementById("send")?.addEventListener("submit", async e => {
|
||||
window.edit_t = async function (id) {
|
||||
const title = prompt("Enter new title!");
|
||||
const res = await request(`/api/threads/${id}/edit`, "POST", { title });
|
||||
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") })
|
||||
.then(res => {
|
||||
if (res) location.href = `/messages/${res.id}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
window.thread = async function (id, un = "") {
|
||||
const res = await request(`/api/threads/${id}/${un}delete`);
|
||||
if (res.error) return;
|
||||
|
||||
/**
|
||||
* OTHER FUNCTIONS
|
||||
*/
|
||||
window.thread = async function (id, un= "") {
|
||||
await request(`/api/threads/${id}/${un}delete`);
|
||||
alert(`Thread ${un}deleted`);
|
||||
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) {
|
||||
const response = await request(`/api/messages/${id}/undelete`);
|
||||
if (response.deleted) return;
|
||||
document.getElementById("deleted-" + id).remove();
|
||||
document.getElementById("dot-" + id).innerHTML = `
|
||||
<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) {
|
||||
const response = await request(`/api/messages/${id}/delete`);
|
||||
if (response.deleted) {
|
||||
|
@ -40,10 +42,10 @@ window.delete_message = async function (id) {
|
|||
document.getElementById("dots-" + id).innerHTML = `
|
||||
<i class='bx bx-trash bx-sm' id="deleted-${id}" style="color: RED;"></i>
|
||||
`+ document.getElementById("dots-" + id).innerHTML;
|
||||
|
||||
document.getElementById("dot-" + id).innerHTML = `<a onclick="undelete_message('${id}');">UNDELETE</a>`;
|
||||
}
|
||||
}
|
||||
|
||||
window.react = async function (id, type) {
|
||||
const res = await request(`/api/messages/${id}/react/${type}`)
|
||||
document.getElementById(`like-${id}`).innerHTML = res.react.like.length;
|
||||
|
|
|
@ -23,7 +23,7 @@ app.use(async (req, res, next) => {
|
|||
const user = await SecretModel.findOne({ username });
|
||||
|
||||
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!');
|
||||
|
||||
|
|
|
@ -14,6 +14,23 @@ app.get("/:id", async (req, res) => {
|
|||
|
||||
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({
|
||||
|
@ -31,7 +48,7 @@ app.post("/", rateLimit({
|
|||
|
||||
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 thread.push(message.id).save();
|
||||
|
||||
|
|
|
@ -53,7 +53,21 @@ app.post("/", async (req, res) => {
|
|||
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) => {
|
||||
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}.`);
|
||||
|
@ -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.");
|
||||
|
||||
thread.deleted = false;
|
||||
thread.edited=true;
|
||||
|
||||
await thread.save();
|
||||
|
||||
res.complate(thread.toObject({ virtuals: true }));
|
||||
|
|
|
@ -34,7 +34,7 @@ app.post("/:id/undelete/", async (req, res) => {
|
|||
|
||||
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.");
|
||||
|
||||
|
@ -44,6 +44,31 @@ app.post("/:id/undelete/", async (req, res) => {
|
|||
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) => {
|
||||
|
||||
const user = req.user;
|
||||
|
@ -54,10 +79,10 @@ app.post("/:id/admin/", async (req, res) => {
|
|||
if (!user2)
|
||||
return res.error(404, `We don't have any user with id ${id}.`);
|
||||
|
||||
else {
|
||||
user2.admin = true;
|
||||
await user2.save()
|
||||
}
|
||||
|
||||
user2.admin = true;
|
||||
await user2.save()
|
||||
|
||||
|
||||
res.complate(user2);
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ app.get("/:id", async (req, res) => {
|
|||
|
||||
if (!message || (message.deleted && req.user && !req.user.admin)) return res.error( 404,
|
||||
`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}`);
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@ app.post("/", async (req, res) => {
|
|||
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");
|
||||
const user = await SecretModel.findOne({ username });
|
||||
|
@ -24,6 +24,9 @@ app.post("/", async (req, res) => {
|
|||
|
||||
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 (about) user2.about = about;
|
||||
|
||||
await user2.takeId()
|
||||
await user2.save();
|
||||
|
||||
|
|
|
@ -18,11 +18,14 @@ app.get("/:id/", async (req, res) => {
|
|||
|
||||
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)
|
||||
thread.count = await thread.messageCount(user?.admin);
|
||||
thread.pages = Math.ceil(thread.count / 10);
|
||||
if (thread && (user?.admin || !thread.deleted)) {
|
||||
thread.views++;
|
||||
const query = { threadID: id };
|
||||
const query = { threadID: id };
|
||||
if (!user || !user.admin) query.deleted = false;
|
||||
|
||||
const messages = await MessageModel.find(query).sort({ time: 1 }).limit(10).skip(page * 10)
|
||||
|
@ -34,13 +37,12 @@ app.get("/:id/", async (req, res) => {
|
|||
return message.toObject({ virtuals: true });
|
||||
}))
|
||||
|
||||
|
||||
res.reply("thread", { page, thread, messages, scroll: req.query.scroll || thread.messages[0].id });
|
||||
|
||||
thread.save();
|
||||
|
||||
} else
|
||||
res.error(404, "We have not got this thread.");
|
||||
res.error(404, `We don't have any thread with id ${id}.`);
|
||||
});
|
||||
|
||||
|
||||
|
|
|
@ -18,9 +18,13 @@ app.get("/:id", async (req, res) => {
|
|||
|
||||
const message = await MessageModel.count({ "author.id": id });
|
||||
const thread = await ThreadModel.count({ "author.id": id });
|
||||
member.about = member.about.replaceAll("&", "&")
|
||||
.replaceAll("<", "<").replaceAll(">", ">")
|
||||
.replaceAll("\"", """).replaceAll("'", "'")
|
||||
.replaceAll("\n", "<br>");
|
||||
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}.`);
|
||||
|
||||
});
|
||||
|
||||
|
|
|
@ -15,10 +15,11 @@
|
|||
|
||||
|
||||
<input type="text" name="username" placeholder="Username" class="input" required>
|
||||
|
||||
|
||||
<input type="password" name="password" placeholder="Password" class="input" required>
|
||||
|
||||
<input type="text" name="avatar" placeholder="Avatar URL" class="input">
|
||||
<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="submit" class="btn-primary" style="width:100%;" value="Register">
|
||||
</form>
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<% }; %>
|
||||
|
||||
<div style="text-align:center;padding:8px">
|
||||
<div class="title"><%= thread.title %></div>
|
||||
<div class="title" id="title"><%= thread.title %></div>
|
||||
<div class="date">
|
||||
<%= new Date(thread.time).toLocaleString() %> • Views: <%= thread.views %>
|
||||
</div>
|
||||
|
@ -26,7 +26,7 @@
|
|||
<% if (user && !thread.deleted){ %>
|
||||
|
||||
<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) { %>
|
||||
<h3 style="display:inline;">This thread has been deleted</h3>
|
||||
<a onclick="thread('<%= thread.id %>', 'un')" class="btn-primary" >UNDELETE</a>
|
||||
|
@ -58,6 +58,9 @@
|
|||
<% if (message.deleted){ %>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
|
@ -88,32 +91,53 @@
|
|||
|
||||
<% }); %>
|
||||
</div>
|
||||
<% if (user){ %>
|
||||
|
||||
<div class="message">
|
||||
<form id="send">
|
||||
<textarea rows="4" cols="100" name="content"></textarea>
|
||||
<input name="threadID" type="hidden" value="<%= thread.id %>"></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();
|
||||
|
||||
<% if (user){ %>
|
||||
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>
|
||||
<%} else {%>
|
||||
<a class="btn-outline-primary" href="/login?redirect=<%= thread.getLink() %>">Login for send</a>
|
||||
<% }%>
|
||||
|
||||
</form>
|
||||
</div>
|
||||
<% }%>
|
||||
<div class="pagination">
|
||||
<div class="back">
|
||||
<% if (page > 0){ %>
|
||||
<% if (page > 0){ %>
|
||||
<a href="<%= thread.getLink() %>?page=<%= page-1 %>" class='bx bxs-chevron-left'></a>
|
||||
<% } %>
|
||||
<% } %>
|
||||
</div>
|
||||
|
||||
|
||||
<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 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>
|
||||
<% } %>
|
||||
</div>
|
||||
|
|
|
@ -35,11 +35,19 @@
|
|||
<h2 class="box-title">Thread:</h2>
|
||||
<h2 class="box-value"><%= counts.thread %></h2>
|
||||
</div>
|
||||
<div class="box">
|
||||
<h2 class="box-title">About:</h2><br>
|
||||
</div>
|
||||
<p class="box-value">
|
||||
<%= member.about %>
|
||||
</p>
|
||||
|
||||
<a class="btn-outline-primary" id="edit">Change name of the 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>
|
||||
|
||||
<% 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">
|
||||
|
||||
import request from "../../js/request.js";
|
||||
|
@ -59,6 +67,15 @@
|
|||
alert("User is deleted!");
|
||||
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();
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
|
Loading…
Reference in a new issue