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">
 | 
					  <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>
 | 
					      <root>
 | 
				
			||||||
        <mxCell id="0" />
 | 
					        <mxCell id="0" />
 | 
				
			||||||
        <mxCell id="1" parent="0" />
 | 
					        <mxCell id="1" parent="0" />
 | 
				
			||||||
@@ -62,21 +62,21 @@
 | 
				
			|||||||
            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
					            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
				
			||||||
          </mxGeometry>
 | 
					          </mxGeometry>
 | 
				
			||||||
        </mxCell>
 | 
					        </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" />
 | 
					          <mxGeometry y="120" width="250" height="30" as="geometry" />
 | 
				
			||||||
        </mxCell>
 | 
					        </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">
 | 
					          <mxGeometry width="30" height="30" as="geometry">
 | 
				
			||||||
            <mxRectangle width="30" height="30" as="alternateBounds" />
 | 
					            <mxRectangle width="30" height="30" as="alternateBounds" />
 | 
				
			||||||
          </mxGeometry>
 | 
					          </mxGeometry>
 | 
				
			||||||
        </mxCell>
 | 
					        </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">
 | 
					          <mxGeometry x="30" width="220" height="30" as="geometry">
 | 
				
			||||||
            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
					            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
				
			||||||
          </mxGeometry>
 | 
					          </mxGeometry>
 | 
				
			||||||
        </mxCell>
 | 
					        </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">
 | 
					        <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>
 | 
				
			||||||
        <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">
 | 
					        <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" />
 | 
					          <mxGeometry y="30" width="250" height="30" as="geometry" />
 | 
				
			||||||
@@ -156,6 +156,19 @@
 | 
				
			|||||||
            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
					            <mxRectangle width="220" height="30" as="alternateBounds" />
 | 
				
			||||||
          </mxGeometry>
 | 
					          </mxGeometry>
 | 
				
			||||||
        </mxCell>
 | 
					        </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">
 | 
					        <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">
 | 
					          <mxGeometry width="100" height="100" relative="1" as="geometry">
 | 
				
			||||||
            <mxPoint x="440" y="270" as="sourcePoint" />
 | 
					            <mxPoint x="440" y="270" as="sourcePoint" />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,17 +1,18 @@
 | 
				
			|||||||
CREATE TABLE boards (
 | 
					CREATE TABLE boards (
 | 
				
			||||||
    board_id VARCHAR(5) PRIMARY KEY,
 | 
					    board_id VARCHAR(5) PRIMARY KEY,
 | 
				
			||||||
    board_name VARCHAR(32) NOT NULL,
 | 
					    board_name VARCHAR(32) NOT NULL,
 | 
				
			||||||
    threads_ids INT8[],
 | 
					 | 
				
			||||||
    options JSON
 | 
					    options JSON
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE threads (
 | 
					CREATE TABLE threads (
 | 
				
			||||||
    thread_id SERIAL8 PRIMARY KEY,
 | 
					    board_id VARCHAR(5) NOT NULL,
 | 
				
			||||||
 | 
					    thread_id BIGINT NOT NULL,
 | 
				
			||||||
    thread_name VARCHAR(32),
 | 
					    thread_name VARCHAR(32),
 | 
				
			||||||
    posts_ids BIGINT[],
 | 
					    posts_ids BIGINT[],
 | 
				
			||||||
    is_locked boolean NOT NULL,
 | 
					    is_locked boolean NOT NULL,
 | 
				
			||||||
    is_pinned boolean NOT NULL,
 | 
					    is_pinned boolean NOT NULL,
 | 
				
			||||||
    options VARCHAR(255)
 | 
					    options VARCHAR(255),
 | 
				
			||||||
 | 
					    PRIMARY KEY(board_id, thread_id)
 | 
				
			||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE media (
 | 
					CREATE TABLE media (
 | 
				
			||||||
@@ -19,12 +20,16 @@ CREATE TABLE media (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE posts (
 | 
					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,
 | 
					    content TEXT NOT NULL,
 | 
				
			||||||
    media_ids VARCHAR(22)[],
 | 
					    media_ids VARCHAR(22)[],
 | 
				
			||||||
    is_root BOOL NOT NULL,
 | 
					    is_root BOOL NOT NULL,
 | 
				
			||||||
    timestamp TIMESTAMP 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 (
 | 
					CREATE TABLE admins (
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,9 +2,9 @@
 | 
				
			|||||||
<html lang="en">
 | 
					<html lang="en">
 | 
				
			||||||
  <head>
 | 
					  <head>
 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					    <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">
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
    <title>Vite App</title>
 | 
					    <title>Dachan</title>
 | 
				
			||||||
  </head>
 | 
					  </head>
 | 
				
			||||||
  <body>
 | 
					  <body>
 | 
				
			||||||
    <div id="app"></div>
 | 
					    <div id="app"></div>
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										21
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								package-lock.json
									
									
									
										generated
									
									
									
								
							@@ -12,6 +12,7 @@
 | 
				
			|||||||
        "@vitejs/plugin-vue": "^4.4.1",
 | 
					        "@vitejs/plugin-vue": "^4.4.1",
 | 
				
			||||||
        "axios": "^1.6.1",
 | 
					        "axios": "^1.6.1",
 | 
				
			||||||
        "bcryptjs": "^2.4.3",
 | 
					        "bcryptjs": "^2.4.3",
 | 
				
			||||||
 | 
					        "cors": "^2.8.5",
 | 
				
			||||||
        "dotenv": "^16.3.1",
 | 
					        "dotenv": "^16.3.1",
 | 
				
			||||||
        "ejs": "^3.1.9",
 | 
					        "ejs": "^3.1.9",
 | 
				
			||||||
        "express": "^4.18.2",
 | 
					        "express": "^4.18.2",
 | 
				
			||||||
@@ -779,6 +780,18 @@
 | 
				
			|||||||
      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
 | 
				
			||||||
      "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ=="
 | 
					      "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": {
 | 
					    "node_modules/csstype": {
 | 
				
			||||||
      "version": "3.1.2",
 | 
					      "version": "3.1.2",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
 | 
				
			||||||
@@ -1531,6 +1544,14 @@
 | 
				
			|||||||
        "node": ">=0.10.0"
 | 
					        "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": {
 | 
					    "node_modules/object-inspect": {
 | 
				
			||||||
      "version": "1.13.1",
 | 
					      "version": "1.13.1",
 | 
				
			||||||
      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
 | 
					      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,8 @@
 | 
				
			|||||||
    "dev": "nodemon src/index.js & vite --host",
 | 
					    "dev": "nodemon src/index.js & vite --host",
 | 
				
			||||||
    "build": "vite build",
 | 
					    "build": "vite build",
 | 
				
			||||||
    "preview": "vite preview",
 | 
					    "preview": "vite preview",
 | 
				
			||||||
    "start": "node src/index.js & vite --host"
 | 
					    "start": "node src/index.js & vite --host",
 | 
				
			||||||
 | 
					    "back": "nodemon src/index.js"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "repository": {
 | 
					  "repository": {
 | 
				
			||||||
    "type": "git",
 | 
					    "type": "git",
 | 
				
			||||||
@@ -22,6 +23,7 @@
 | 
				
			|||||||
    "@vitejs/plugin-vue": "^4.4.1",
 | 
					    "@vitejs/plugin-vue": "^4.4.1",
 | 
				
			||||||
    "axios": "^1.6.1",
 | 
					    "axios": "^1.6.1",
 | 
				
			||||||
    "bcryptjs": "^2.4.3",
 | 
					    "bcryptjs": "^2.4.3",
 | 
				
			||||||
 | 
					    "cors": "^2.8.5",
 | 
				
			||||||
    "dotenv": "^16.3.1",
 | 
					    "dotenv": "^16.3.1",
 | 
				
			||||||
    "ejs": "^3.1.9",
 | 
					    "ejs": "^3.1.9",
 | 
				
			||||||
    "express": "^4.18.2",
 | 
					    "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>
 | 
					 | 
				
			||||||
							
								
								
									
										83
									
								
								src/index.js
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								src/index.js
									
									
									
									
									
								
							@@ -6,6 +6,7 @@ const MemoryStore = require('memorystore')(session);
 | 
				
			|||||||
const fs = require('fs');
 | 
					const fs = require('fs');
 | 
				
			||||||
const bcrypt = require('bcryptjs');
 | 
					const bcrypt = require('bcryptjs');
 | 
				
			||||||
// const fileupload = require('express-fileupload');
 | 
					// const fileupload = require('express-fileupload');
 | 
				
			||||||
 | 
					const cors = require('cors');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const app = express();
 | 
					const app = express();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -50,7 +51,7 @@ let tokens = {};
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
app.use(express.urlencoded({ extended: true }))
 | 
					app.use(express.urlencoded({ extended: true }))
 | 
				
			||||||
app.use(express.json())
 | 
					app.use(express.json())
 | 
				
			||||||
 | 
					app.use(cors());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use(session({
 | 
					app.use(session({
 | 
				
			||||||
    secret: process.env.SESSION_SECRET,
 | 
					    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) => {
 | 
					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) => {
 | 
					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;
 | 
					    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;
 | 
					    isPinned = isPinned? isPinned : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`);
 | 
					    console.log(`Board id: ${boardId}\nThread name: ${threadTitle}\nIs locked: ${isLocked}\nIs pinned: ${isPinned}\nContent: ${content}\nOptions: ${options}`);
 | 
				
			||||||
    
 | 
					    
 | 
				
			||||||
    try {
 | 
					    if (login && token) {
 | 
				
			||||||
        let currentSession = req.session;
 | 
					        if (authorize(login, token)) isAdmin = true;
 | 
				
			||||||
        token = currentSession.token;
 | 
					        else res.status(403).send("Невалидный токен");
 | 
				
			||||||
        login = currentSession.login;
 | 
					 | 
				
			||||||
    } catch (err) {
 | 
					 | 
				
			||||||
        console.log(err);
 | 
					 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    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 threadId = (await db.query('SELECT EXISTS(SELECT FROM threads WHERE board_id = $1)', [boardId])).rows[0].exists?
 | 
				
			||||||
    let isAdmin = token? true : false;
 | 
					        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
 | 
				
			||||||
    const boardOptions = (await db.query('SELECT * FROM boards WHERE board_id = $1', [boardId])).rows[0].options
 | 
					    let postId = (await db.query('SELECT EXISTS(SELECT FROM posts WHERE board_id = $1)', [boardId])).rows[0].exists?
 | 
				
			||||||
    let postId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'posts\', \'post_id\'))')).rows[0].nextval;
 | 
					        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
 | 
				
			||||||
    let threadId = (await db.query('SELECT nextval(pg_get_serial_sequence(\'threads\', \'thread_id\'))')).rows[0].nextval;
 | 
					        : 0
 | 
				
			||||||
 | 
					    console.log(`ThreadId: ${threadId} postId: ${postId}`);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    let validateResults = validateThread(threadTitle, isLocked, 
 | 
					    let validateResults = validateThread(threadTitle, isLocked, 
 | 
				
			||||||
                         isPinned, content, options, 
 | 
					                         isPinned, content, options, 
 | 
				
			||||||
                         boardOptions, isAdmin);
 | 
					                         boardOptions, isAdmin);
 | 
				
			||||||
    if (validateResults != "ok") return res.status(400).send(validateResults);
 | 
					    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 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 (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('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]);
 | 
				
			||||||
    await db.query('UPDATE boards SET threads_ids = ARRAY_APPEND(threads_ids, $1) WHERE board_id = $2', [threadId, boardId]);
 | 
					 | 
				
			||||||
    res.redirect(`/${boardId}/${postId}`);    
 | 
					    res.redirect(`/${boardId}/${postId}`);    
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.get('/api/getThreads/:boardId', async (req, res) => {
 | 
					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.setHeader('Content-Type', 'application/json');
 | 
				
			||||||
    res.end(JSON.stringify(queryRes.threads_ids));
 | 
					    res.end(JSON.stringify(threads));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.get('/api/getBoards', async (req, res) => {
 | 
					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.setHeader('Content-Type', 'application/json');
 | 
				
			||||||
    res.end(JSON.stringify(queryRes.rows));
 | 
					    res.end(JSON.stringify(queryRes.rows));
 | 
				
			||||||
 | 
					    // res.json(JSON.stringify(queryRes.rows));
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.post('/api/login', async (req, res) => {
 | 
					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) => {
 | 
					const validateThread = (threadName, isLocked, isPinned, content, options, boardOptions, isAdmin) => {
 | 
				
			||||||
    if ((isPinned || isLocked) && !isAdmin) return "Нет прав на выставление админских флагов";
 | 
					    if ((isPinned || isLocked) && !isAdmin) return "Нет прав на выставление админских флагов";
 | 
				
			||||||
    if (!content) return "Нельзя создать тред без текста";
 | 
					    if (!content && boardOptions.requireContentForThreadCreation) return "Нельзя создать тред без текста";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    //TODO: check if image is required
 | 
					    //TODO: check if image is required
 | 
				
			||||||
    return 'ok'
 | 
					    return 'ok'
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const authorize = (login, token) => {
 | 
				
			||||||
 | 
					    return tokens[login] == token? true : false;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
		Reference in New Issue
	
	Block a user