Compare commits
10 Commits
b68462a815
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| ce72dd40a8 | |||
| 0b3a178715 | |||
| d61797a748 | |||
| 82b1a82aac | |||
| 5b1307a662 | |||
| 8f1461ce2b | |||
| 6195f80c4a | |||
| fce1a6aa71 | |||
| 934f8960cd | |||
| e55b42fdac |
@@ -1,6 +1,6 @@
|
||||
<mxfile host="Electron" modified="2023-11-04T11:34:00.376Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="zK0hocMrlzpD75Dksgz9" version="22.0.2" type="device">
|
||||
<mxfile host="Electron" modified="2023-11-16T17:33:19.287Z" agent="Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) draw.io/22.0.2 Chrome/114.0.5735.289 Electron/25.8.4 Safari/537.36" etag="ZbU5Z7KAwFtN-KRkF2Te" version="22.0.2" type="device">
|
||||
<diagram id="R2lEEEUBdFMjLlhIrx00" name="Page-1">
|
||||
<mxGraphModel dx="1669" dy="479" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
|
||||
<mxGraphModel dx="1645" dy="474" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0" extFonts="Permanent Marker^https://fonts.googleapis.com/css?family=Permanent+Marker">
|
||||
<root>
|
||||
<mxCell id="0" />
|
||||
<mxCell id="1" parent="0" />
|
||||
@@ -62,21 +62,21 @@
|
||||
<mxRectangle width="220" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-1" value="" style="shape=partialRectangle;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="C-vyLk0tnHw3VtMMgP7b-23">
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-1" value="" style="shape=partialRectangle;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" parent="C-vyLk0tnHw3VtMMgP7b-23" vertex="1">
|
||||
<mxGeometry y="120" width="250" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-2" value="" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;" vertex="1" parent="lzMxBIVNoJ9j-Thf5CWH-1">
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-2" value="" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;" parent="lzMxBIVNoJ9j-Thf5CWH-1" vertex="1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-3" value="settings JSON NOT NULL" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;" vertex="1" parent="lzMxBIVNoJ9j-Thf5CWH-1">
|
||||
<mxCell id="lzMxBIVNoJ9j-Thf5CWH-3" value="settings JSON NOT NULL" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;" parent="lzMxBIVNoJ9j-Thf5CWH-1" vertex="1">
|
||||
<mxGeometry x="30" width="220" height="30" as="geometry">
|
||||
<mxRectangle width="220" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="IqcACfFecUyFIL0cjSVW-16" value="post" style="shape=table;startSize=30;container=1;collapsible=1;childLayout=tableLayout;fixedRows=1;rowLines=0;fontStyle=1;align=center;resizeLast=1;" parent="1" vertex="1">
|
||||
<mxGeometry x="470" y="290" width="250" height="210" as="geometry" />
|
||||
<mxGeometry x="470" y="282" width="250" height="240" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="IqcACfFecUyFIL0cjSVW-17" value="" style="shape=partialRectangle;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=1;" parent="IqcACfFecUyFIL0cjSVW-16" vertex="1">
|
||||
<mxGeometry y="30" width="250" height="30" as="geometry" />
|
||||
@@ -156,6 +156,19 @@
|
||||
<mxRectangle width="220" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Z5fAuwPoposVQ_Fsqkp6-1" value="" style="shape=partialRectangle;collapsible=0;dropTarget=0;pointerEvents=0;fillColor=none;points=[[0,0.5],[1,0.5]];portConstraint=eastwest;top=0;left=0;right=0;bottom=0;" vertex="1" parent="IqcACfFecUyFIL0cjSVW-16">
|
||||
<mxGeometry y="210" width="250" height="30" as="geometry" />
|
||||
</mxCell>
|
||||
<mxCell id="Z5fAuwPoposVQ_Fsqkp6-2" value="" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;" vertex="1" parent="Z5fAuwPoposVQ_Fsqkp6-1">
|
||||
<mxGeometry width="30" height="30" as="geometry">
|
||||
<mxRectangle width="30" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="Z5fAuwPoposVQ_Fsqkp6-3" value="options VARCHAR(255)" style="shape=partialRectangle;overflow=hidden;connectable=0;fillColor=none;top=0;left=0;bottom=0;right=0;align=left;spacingLeft=6;" vertex="1" parent="Z5fAuwPoposVQ_Fsqkp6-1">
|
||||
<mxGeometry x="30" width="220" height="30" as="geometry">
|
||||
<mxRectangle width="220" height="30" as="alternateBounds" />
|
||||
</mxGeometry>
|
||||
</mxCell>
|
||||
<mxCell id="IqcACfFecUyFIL0cjSVW-23" value="" style="edgeStyle=entityRelationEdgeStyle;fontSize=12;html=1;endArrow=ERoneToMany;rounded=0;exitX=0.998;exitY=0.676;exitDx=0;exitDy=0;exitPerimeter=0;entryX=0;entryY=0.5;entryDx=0;entryDy=0;" parent="1" source="IqcACfFecUyFIL0cjSVW-12" target="C-vyLk0tnHw3VtMMgP7b-3" edge="1">
|
||||
<mxGeometry width="100" height="100" relative="1" as="geometry">
|
||||
<mxPoint x="440" y="270" as="sourcePoint" />
|
||||
|
||||
@@ -1,17 +1,18 @@
|
||||
CREATE TABLE boards (
|
||||
board_id VARCHAR(5) PRIMARY KEY,
|
||||
board_name VARCHAR(32) NOT NULL,
|
||||
threads_ids INT8[],
|
||||
options JSON
|
||||
);
|
||||
|
||||
CREATE TABLE threads (
|
||||
thread_id SERIAL8 PRIMARY KEY,
|
||||
board_id VARCHAR(5) NOT NULL,
|
||||
thread_id BIGINT NOT NULL,
|
||||
thread_name VARCHAR(32),
|
||||
posts_ids BIGINT[],
|
||||
is_locked boolean NOT NULL,
|
||||
is_pinned boolean NOT NULL,
|
||||
options VARCHAR(255)
|
||||
options VARCHAR(255),
|
||||
PRIMARY KEY(board_id, thread_id)
|
||||
);
|
||||
|
||||
CREATE TABLE media (
|
||||
@@ -19,12 +20,16 @@ CREATE TABLE media (
|
||||
);
|
||||
|
||||
CREATE TABLE posts (
|
||||
post_id SERIAL8 PRIMARY KEY,
|
||||
board_id VARCHAR(5) NOT NULL,
|
||||
thread_id BIGINT NOT NULL,
|
||||
post_id BIGINT NOT NULL,
|
||||
options VARCHAR(255),
|
||||
content TEXT NOT NULL,
|
||||
media_ids VARCHAR(22)[],
|
||||
is_root BOOL NOT NULL,
|
||||
timestamp TIMESTAMP NOT NULL,
|
||||
user_ip CIDR NOT NULL
|
||||
user_ip CIDR NOT NULL,
|
||||
PRIMARY KEY(board_id, thread_id, post_id)
|
||||
);
|
||||
|
||||
CREATE TABLE admins (
|
||||
|
||||
@@ -2,9 +2,9 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<link rel="icon" href="/favicon.ico">
|
||||
<link rel="icon" href="/favicon.svg">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Vite App</title>
|
||||
<title>Dachan</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
|
||||
21
package-lock.json
generated
21
package-lock.json
generated
@@ -12,6 +12,7 @@
|
||||
"@vitejs/plugin-vue": "^4.4.1",
|
||||
"axios": "^1.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
@@ -779,6 +780,18 @@
|
||||
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
|
||||
"integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
|
||||
},
|
||||
"node_modules/cors": {
|
||||
"version": "2.8.5",
|
||||
"resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz",
|
||||
"integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==",
|
||||
"dependencies": {
|
||||
"object-assign": "^4",
|
||||
"vary": "^1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/csstype": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
|
||||
@@ -1531,6 +1544,14 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-assign": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
|
||||
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/object-inspect": {
|
||||
"version": "1.13.1",
|
||||
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
|
||||
|
||||
@@ -7,7 +7,8 @@
|
||||
"dev": "nodemon src/index.js & vite --host",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"start": "node src/index.js & vite --host"
|
||||
"start": "node src/index.js & vite --host",
|
||||
"back": "nodemon src/index.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -22,6 +23,7 @@
|
||||
"@vitejs/plugin-vue": "^4.4.1",
|
||||
"axios": "^1.6.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"ejs": "^3.1.9",
|
||||
"express": "^4.18.2",
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 4.2 KiB |
80
public/favicon.svg
Normal file
80
public/favicon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 8.8 KiB |
@@ -1,49 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<header>
|
||||
<div class="headerIneer">
|
||||
<div class="headerLogo"><h1 class="headerLogoTitle">Dach</h1></div>
|
||||
<div class="headerFaq"><a class="headerFaqTitle" href="faq.html">FAQ</a></div>
|
||||
</div>
|
||||
</header>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
header {
|
||||
display: flex;
|
||||
flex: 0 0 100%;
|
||||
min-width: 100vh;
|
||||
}
|
||||
.headerIneer{
|
||||
display: flex;
|
||||
width: 100%;
|
||||
border-radius: 1em;
|
||||
background: #171515fb; /* fallback for old browsers */
|
||||
}
|
||||
.headerLogo{
|
||||
height: 100%;
|
||||
margin: 0 5%;
|
||||
}
|
||||
.headerLogoTitle{
|
||||
font-family: 'Tilt Neon', sans-serif;
|
||||
font-size: 2.5em;
|
||||
color: whitesmoke;
|
||||
}
|
||||
|
||||
.headerFaq{
|
||||
margin: 2%;
|
||||
min-height: 2em;
|
||||
margin-left: auto;
|
||||
margin-right: 5em;
|
||||
}
|
||||
|
||||
.headerFaqTitle{
|
||||
font-family: 'Tilt Neon', sans-serif;
|
||||
font-size: 1.5em;
|
||||
text-decoration: none;
|
||||
color: whitesmoke;
|
||||
}
|
||||
</style>
|
||||
85
src/index.js
85
src/index.js
@@ -6,6 +6,7 @@ const MemoryStore = require('memorystore')(session);
|
||||
const fs = require('fs');
|
||||
const bcrypt = require('bcryptjs');
|
||||
// const fileupload = require('express-fileupload');
|
||||
const cors = require('cors');
|
||||
|
||||
const app = express();
|
||||
|
||||
@@ -50,7 +51,7 @@ let tokens = {};
|
||||
|
||||
app.use(express.urlencoded({ extended: true }))
|
||||
app.use(express.json())
|
||||
|
||||
app.use(cors());
|
||||
|
||||
app.use(session({
|
||||
secret: process.env.SESSION_SECRET,
|
||||
@@ -67,51 +68,82 @@ app.post('/api/uploadMedia', async (req, res) => {
|
||||
|
||||
});
|
||||
|
||||
app.get('/api/getPosts/:boardId/:threadId', async (req, res) => {
|
||||
posts = [];
|
||||
(await db.query('SELECT post_id, content, timestamp, options FROM posts WHERE board_id = $1 AND thread_id = $2', [req.params.boardId, req.params.threadId])).rows
|
||||
.forEach((post) => posts.push(post))
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(posts));
|
||||
});
|
||||
|
||||
app.post('/api/post', async (req, res) => {
|
||||
const { content, } = req.body;
|
||||
let login = req.session.login,
|
||||
token = req.session.token,
|
||||
isAdmin = false;
|
||||
const {options, content, threadId, boardId} = req.body;
|
||||
|
||||
if (!threadId || !boardId) return res.status(400).send("Не указано ID треда или доски");
|
||||
|
||||
if (login && token) {
|
||||
if (authorize(login, token)) isAdmin = true;
|
||||
else res.status(403).send("Невалидный токен");
|
||||
}
|
||||
|
||||
let postId = Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1
|
||||
await db.query('INSERT INTO posts(board_id, thread_id, post_id, options, content, media_ids, is_root, timestamp, user_ip) VALUES ($1, $2, $3, $4, $5, \'{}\', false, NOW(), $6)', [boardId, threadId, postId, options, content, req.socket.remoteAddress]);
|
||||
await db.query('UPDATE threads SET posts_ids = ARRAY_APPEND(posts_ids, $1) WHERE thread_id = $2 AND board_id = $3', [postId, threadId, boardId]);
|
||||
|
||||
res.status(200).send("Пост отправлен");
|
||||
});
|
||||
|
||||
app.post('/api/createThread', async (req, res) => {
|
||||
let login, token, isLocked, isPinned
|
||||
let login = req.session.login,
|
||||
token = req.session.token,
|
||||
isLocked,
|
||||
isPinned,
|
||||
isAdmin = false;
|
||||
const { boardId, threadTitle, content, options} = req.body;
|
||||
|
||||
isLocked = isLocked? isLocked : false;
|
||||
if (!boardId) return res.status(400).send("Не указано имя доски");
|
||||
|
||||
isLocked = isLocked? isLocked : false; // if undefined then false
|
||||
isPinned = isPinned? isPinned : false;
|
||||
|
||||
console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`);
|
||||
|
||||
try {
|
||||
let currentSession = req.session;
|
||||
token = currentSession.token;
|
||||
login = currentSession.login;
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
if (login && token) {
|
||||
if (authorize(login, token)) isAdmin = true;
|
||||
else res.status(403).send("Невалидный токен");
|
||||
}
|
||||
|
||||
const boardOptions = (await db.query('SELECT options FROM boards WHERE board_id = $1', [boardId])).rows[0].options;
|
||||
|
||||
if (login && token && token != tokens[login]) return res.status(403).send("Невалидный токен");
|
||||
let isAdmin = token? true : false;
|
||||
|
||||
const boardOptions = (await db.query('SELECT * FROM boards WHERE board_id = $1', [boardId])).rows[0].options
|
||||
let postId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'posts\', \'post_id\'))')).rows[0].nextval;
|
||||
let threadId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'threads\', \'thread_id\'))')).rows[0].nextval;
|
||||
let threadId = (await db.query('SELECT EXISTS(SELECT FROM threads WHERE board_id = $1)', [boardId])).rows[0].exists?
|
||||
Number((await db.query('SELECT thread_id FROM threads WHERE board_id = $1 ORDER BY thread_id DESC LIMIT 1', [boardId])).rows[0].thread_id) + 1
|
||||
: 0
|
||||
let postId = (await db.query('SELECT EXISTS(SELECT FROM posts WHERE board_id = $1)', [boardId])).rows[0].exists?
|
||||
Number((await db.query('SELECT post_id FROM posts WHERE board_id = $1 ORDER BY post_id DESC LIMIT 1', [boardId])).rows[0].post_id) + 1
|
||||
: 0
|
||||
console.log(`ThreadId: ${threadId} postId: ${postId}`);
|
||||
|
||||
let validateResults = validateThread(threadTitle, isLocked,
|
||||
isPinned, content, options,
|
||||
boardOptions, isAdmin);
|
||||
if (validateResults != "ok") return res.status(400).send(validateResults);
|
||||
|
||||
await db.query('INSERT INTO posts (post_id, content, is_root, timestamp, user_ip) VALUES($1, $2, $3, NOW(), $4)', [postId, content, true, req.socket.remoteAddress]);
|
||||
await db.query('INSERT INTO threads (thread_id, thread_name, posts_ids, is_locked, is_pinned, options) VALUES ($1, $2, $3, $4, $5, $6)', [threadId, threadTitle, [postId], isLocked, isPinned, options]);
|
||||
await db.query('UPDATE boards SET threads_ids = ARRAY_APPEND(threads_ids, $1) WHERE board_id = $2', [threadId, boardId]);
|
||||
await db.query('INSERT INTO posts (board_id, thread_id, post_id, content, is_root, timestamp, user_ip) VALUES($1, $2, $3, $4, $5, NOW(), $6)', [boardId, threadId, postId, content, true, req.socket.remoteAddress]);
|
||||
await db.query('INSERT INTO threads (board_id, thread_id, thread_name, posts_ids, is_locked, is_pinned, options) VALUES ($1, $2, $3, $4, $5, $6, $7)', [boardId, threadId, threadTitle, [postId], isLocked, isPinned, options]);
|
||||
res.redirect(`/${boardId}/${postId}`);
|
||||
});
|
||||
|
||||
app.get('/api/getThreads/:boardId', async (req, res) => {
|
||||
let queryRes = (await db.query('SELECT * FROM boards WHERE board_id = $1', [req.params.boardId])).rows[0];
|
||||
|
||||
threads = [];
|
||||
(await db.query('SELECT thread_id FROM threads WHERE board_id = $1', [req.params.boardId])).rows
|
||||
.forEach((thread) => threads.push(thread.thread_id))
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(queryRes.threads_ids));
|
||||
res.end(JSON.stringify(threads));
|
||||
});
|
||||
|
||||
app.get('/api/getBoards', async (req, res) => {
|
||||
@@ -119,6 +151,7 @@ app.get('/api/getBoards', async (req, res) => {
|
||||
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
res.end(JSON.stringify(queryRes.rows));
|
||||
// res.json(JSON.stringify(queryRes.rows));
|
||||
});
|
||||
|
||||
app.post('/api/login', async (req, res) => {
|
||||
@@ -178,8 +211,12 @@ app.listen(process.env.APP_PORT, () => {
|
||||
|
||||
const validateThread = (threadName, isLocked, isPinned, content, options, boardOptions, isAdmin) => {
|
||||
if ((isPinned || isLocked) && !isAdmin) return "Нет прав на выставление админских флагов";
|
||||
if (!content) return "Нельзя создать тред без текста";
|
||||
if (!content && boardOptions.requireContentForThreadCreation) return "Нельзя создать тред без текста";
|
||||
|
||||
//TODO: check if image is required
|
||||
return 'ok'
|
||||
}
|
||||
};
|
||||
|
||||
const authorize = (login, token) => {
|
||||
return tokens[login] == token? true : false;
|
||||
};
|
||||
Reference in New Issue
Block a user