Integrated database and better S3 storage management (#11)

The new docker-compose file creates a postgresql database automatically and uses it internally. Users are no longer expected to connect it to an existing database outside of the project.
S3 storage was also changed so that YAB can now run without one. The current implementation lacks some QoL improvements, however the software will no longer fail due to any missing S3 information.

This change now allows YAB to run immediately without connecting to outside services.

Reviewed-on: #11
Co-authored-by: Armored Dragon <publicmail@armoreddragon.com>
Co-committed-by: Armored Dragon <publicmail@armoreddragon.com>
pull/2/head
Armored Dragon 2024-04-01 20:01:13 +00:00 committed by Armored Dragon
parent 32c0ca36ef
commit a403b114e8
8 changed files with 212 additions and 54 deletions

View File

@ -1,17 +1,19 @@
FROM node:21-bullseye-slim
WORKDIR /app
RUN apt-get update || : && apt-get install -y
RUN apt-get update || : && apt-get install -y netcat-openbsd
COPY package.json .
EXPOSE 5004
COPY . .
COPY prisma ./prisma/
RUN npm install
RUN npx prisma generate
COPY . .
CMD ["npm", "start"]
EXPOSE 5004
COPY wait-for-db.sh /usr/local/bin/wait-for-db.sh
RUN chmod +x /usr/local/bin/wait-for-db.sh
CMD ["/usr/local/bin/wait-for-db.sh"]

View File

@ -1,9 +1,51 @@
# yet-another-blog
# Yet Another Blog
A open source blogging website made for both personal blogs and big projects.
Yet Another Blog (YAB) is a self-hosted blogging service made to be quick to set up and easy to use. YAB is currently in alpha and breaking changes happen frequently.
TODO
Setup instructions
Database
S3
Admin settings
**YAB is not currently recommended for production environments.**
# Installation
## Clone the repository
Note, this software is currently in alpha, you need to clone the "alpha" branch in order to use the software in its current state.
```bash
git clone -b alpha https://github.com/Armored-Dragon/yet-another-blog
```
## Move into the repository directory
```bash
cd yet-another-blog
```
## Edit the .env file
The `template.env` file is a complete template of all valid values. As a template, there are few prefilled values, and those that are prefilled are done so that YAB will preform as expected.
```bash
cp template.env .env # Copy the template.env file into a regular .env file
nano .env # Replace "nano" with any other text editor of your choice
```
Replace `BASE_URL` with your domain name for your blog.
Replace the `S3_*` variable values with the correct information to be able to upload images to your bucket.
Media uploads will not work without an S3 bucket.
Please change the `POSTGRES_PASSWORD` value to a secure password.
## Run docker-compose
```bash
docker-compose up
```
## Create an administrator account
The first account created will be created with administrator privileges, so it is important to create your account immediately after installation. YAB will use port `5004` by default, so visit your instance by navigating to `http://localhost:5004`.
## Administrator panel
You can visit the administrator panel by visiting `http://localhost:5004/admin`

View File

@ -3,14 +3,7 @@ const prisma = new PrismaClient();
const sharp = require("sharp");
const { S3Client, PutObjectCommand, GetObjectCommand, DeleteObjectCommand, ListObjectsCommand, DeleteObjectsCommand } = require("@aws-sdk/client-s3");
const { getSignedUrl } = require("@aws-sdk/s3-request-presigner");
const s3 = new S3Client({
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
region: process.env.S3_REGION,
endpoint: process.env.S3_ENDPOINT,
});
let s3;
const md = require("markdown-it")()
.use(require("markdown-it-underline"))
.use(require("markdown-it-footnote"))
@ -40,10 +33,33 @@ let settings = {
BLOG_MINIMUM_DESCRIPTION_LENGTH: 7,
BLOG_MINIMUM_CONTENT_LENGTH: 7,
};
let use_s3_storage = false;
let groups = [];
_initS3Storage();
_getSettings();
_getGroups();
// Checks to see if S3 storage is set
function _initS3Storage() {
if (process.env.S3_ACCESS_KEY && process.env.S3_SECRET_KEY && process.env.S3_REGION && process.env.S3_ENDPOINT) {
console.log("S3 Server configured. Proceeding using S3 bucket");
use_s3_storage = true;
s3 = new S3Client({
credentials: {
accessKeyId: process.env.S3_ACCESS_KEY,
secretAccessKey: process.env.S3_SECRET_KEY,
},
region: process.env.S3_REGION,
endpoint: process.env.S3_ENDPOINT,
});
return;
} else {
console.log("S3 Server NOT SET. Media uploads will not work.");
use_s3_storage = false;
return;
}
}
async function registerUser(username, password, options) {
let user_database_entry;
let user_profile_database_entry;
@ -193,7 +209,7 @@ async function postBlog(blog_post, owner_id) {
const image = blog_post.images[i];
const image_data = Buffer.from(image.data_blob.split(",")[1], "base64");
const name = await _uploadImage(database_blog.id, "blog", false, image_data, image.id);
uploaded_images.push(name);
if (name) uploaded_images.push(name);
}
}
@ -201,7 +217,7 @@ async function postBlog(blog_post, owner_id) {
if (blog_post.thumbnail) {
const image_data = Buffer.from(blog_post.thumbnail.data_blob.split(",")[1], "base64");
const name = await _uploadImage(database_blog.id, "blog", true, image_data, blog_post.thumbnail.id);
uploaded_thumbnail = name;
if (name) uploaded_thumbnail = name;
}
// Update the blog post to include references to our images
@ -265,7 +281,7 @@ async function updateBlog(blog_post, requester_id) {
const image = blog_post.images[i];
const image_data = Buffer.from(image.data_blob.split(",")[1], "base64");
const name = await _uploadImage(post.id, "blog", false, image_data, image.id);
uploaded_images.push(name);
if (name) uploaded_images.push(name);
}
}
@ -276,7 +292,7 @@ async function updateBlog(blog_post, requester_id) {
if (blog_post.thumbnail) {
const image_data = Buffer.from(blog_post.thumbnail.data_blob.split(",")[1], "base64");
const name = await _uploadImage(post.data.id, "blog", true, image_data, blog_post.thumbnail.id);
uploaded_thumbnail = name;
if (name) uploaded_thumbnail = name;
data_to_update.thumbnail = uploaded_thumbnail;
}
@ -312,6 +328,7 @@ async function deleteImage(image, requester_id) {
return { success: true };
}
async function _uploadImage(parent_id, parent_type, is_thumbnail, buffer, name) {
if (!use_s3_storage) return null;
let size = { width: 1920, height: 1080 };
if (is_thumbnail) size = { width: 300, height: 300 };
@ -333,6 +350,7 @@ async function _uploadImage(parent_id, parent_type, is_thumbnail, buffer, name)
return name;
}
async function _getImage(parent_id, parent_type, name) {
if (!use_s3_storage) return null;
let params;
// Default image
if (name === "DEFAULT") params = { Bucket: process.env.S3_BUCKET_NAME, Key: `defaults/thumbnail.webp` };

24
docker-compose.yml Normal file
View File

@ -0,0 +1,24 @@
name: yet-another-blog
services:
db:
image: postgres
restart: always
expose:
- "5432"
volumes:
- yab-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${POSTGRES_USER}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: ${POSTGRES_NAME}
blog:
build: .
container_name: yab-app
restart: on-failure
ports:
- "5004:5004"
env_file:
- .env
volumes:
yab-data:

73
package-lock.json generated
View File

@ -11,7 +11,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.410.0",
"@aws-sdk/s3-request-presigner": "^3.410.0",
"@prisma/client": "^5.2.0",
"@prisma/client": "^5.8.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"dotenv": "^16.3.1",
@ -25,7 +25,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-underline": "^1.0.1",
"multer": "^1.4.5-lts.1",
"prisma": "^5.6.0",
"prisma": "^5.11.0",
"sharp": "^0.32.5",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"
@ -856,13 +856,10 @@
}
},
"node_modules/@prisma/client": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.2.0.tgz",
"integrity": "sha512-AiTjJwR4J5Rh6Z/9ZKrBBLel3/5DzUNntMohOy7yObVnVoTNVFi2kvpLZlFuKO50d7yDspOtW6XBpiAd0BVXbQ==",
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.11.0.tgz",
"integrity": "sha512-SWshvS5FDXvgJKM/a0y9nDC1rqd7KG0Q6ZVzd+U7ZXK5soe73DJxJJgbNBt2GNXOa+ysWB4suTpdK5zfFPhwiw==",
"hasInstallScript": true,
"dependencies": {
"@prisma/engines-version": "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f"
},
"engines": {
"node": ">=16.13"
},
@ -875,16 +872,50 @@
}
}
},
"node_modules/@prisma/engines": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.6.0.tgz",
"integrity": "sha512-Mt2q+GNJpU2vFn6kif24oRSBQv1KOkYaterQsi0k2/lA+dLvhRX6Lm26gon6PYHwUM8/h8KRgXIUMU0PCLB6bw==",
"hasInstallScript": true
"node_modules/@prisma/debug": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.11.0.tgz",
"integrity": "sha512-N6yYr3AbQqaiUg+OgjkdPp3KPW1vMTAgtKX6+BiB/qB2i1TjLYCrweKcUjzOoRM5BriA4idrkTej9A9QqTfl3A=="
},
"node_modules/@prisma/engines-version": {
"version": "5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.2.0-25.2804dc98259d2ea960602aca6b8e7fdc03c1758f.tgz",
"integrity": "sha512-jsnKT5JIDIE01lAeCj2ghY9IwxkedhKNvxQeoyLs6dr4ZXynetD0vTy7u6wMJt8vVPv8I5DPy/I4CFaoXAgbtg=="
"node_modules/@prisma/engines": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.11.0.tgz",
"integrity": "sha512-gbrpQoBTYWXDRqD+iTYMirDlF9MMlQdxskQXbhARhG6A/uFQjB7DZMYocMQLoiZXO/IskfDOZpPoZE8TBQKtEw==",
"hasInstallScript": true,
"dependencies": {
"@prisma/debug": "5.11.0",
"@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102",
"@prisma/fetch-engine": "5.11.0",
"@prisma/get-platform": "5.11.0"
}
},
"node_modules/@prisma/engines/node_modules/@prisma/engines-version": {
"version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102.tgz",
"integrity": "sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA=="
},
"node_modules/@prisma/fetch-engine": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.11.0.tgz",
"integrity": "sha512-994viazmHTJ1ymzvWugXod7dZ42T2ROeFuH6zHPcUfp/69+6cl5r9u3NFb6bW8lLdNjwLYEVPeu3hWzxpZeC0w==",
"dependencies": {
"@prisma/debug": "5.11.0",
"@prisma/engines-version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102",
"@prisma/get-platform": "5.11.0"
}
},
"node_modules/@prisma/fetch-engine/node_modules/@prisma/engines-version": {
"version": "5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102",
"resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.11.0-15.efd2449663b3d73d637ea1fd226bafbcf45b3102.tgz",
"integrity": "sha512-WXCuyoymvrS4zLz4wQagSsc3/nE6CHy8znyiMv8RKazKymOMd5o9FP5RGwGHAtgoxd+aB/BWqxuP/Ckfu7/3MA=="
},
"node_modules/@prisma/get-platform": {
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.11.0.tgz",
"integrity": "sha512-rxtHpMLxNTHxqWuGOLzR2QOyQi79rK1u1XYAVLZxDGTLz/A+uoDnjz9veBFlicrpWjwuieM4N6jcnjj/DDoidw==",
"dependencies": {
"@prisma/debug": "5.11.0"
}
},
"node_modules/@smithy/abort-controller": {
"version": "2.0.6",
@ -3316,12 +3347,12 @@
}
},
"node_modules/prisma": {
"version": "5.6.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.6.0.tgz",
"integrity": "sha512-EEaccku4ZGshdr2cthYHhf7iyvCcXqwJDvnoQRAJg5ge2Tzpv0e2BaMCp+CbbDUwoVTzwgOap9Zp+d4jFa2O9A==",
"version": "5.11.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.11.0.tgz",
"integrity": "sha512-KCLiug2cs0Je7kGkQBN9jDWoZ90ogE/kvZTUTgz2h94FEo8pczCkPH7fPNXkD1sGU7Yh65risGGD1HQ5DF3r3g==",
"hasInstallScript": true,
"dependencies": {
"@prisma/engines": "5.6.0"
"@prisma/engines": "5.11.0"
},
"bin": {
"prisma": "build/index.js"

View File

@ -24,7 +24,7 @@
"dependencies": {
"@aws-sdk/client-s3": "^3.410.0",
"@aws-sdk/s3-request-presigner": "^3.410.0",
"@prisma/client": "^5.2.0",
"@prisma/client": "^5.8.0",
"bcrypt": "^5.1.1",
"body-parser": "^1.20.2",
"dotenv": "^16.3.1",
@ -38,7 +38,7 @@
"markdown-it-sup": "^1.0.0",
"markdown-it-underline": "^1.0.1",
"multer": "^1.4.5-lts.1",
"prisma": "^5.6.0",
"prisma": "^5.11.0",
"sharp": "^0.32.5",
"ts-node": "^10.9.1",
"typescript": "^5.2.2"

View File

@ -1,14 +1,19 @@
ENVIRONMENT='production'
ENVIRONMENT="production"
# The URL your site is hosted on. (example: https://myawesomeblog.com)
BASE_URL=""
# PostgreSQL database url
DATABASE_URL=""
DATABASE_URL="postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DATABASE_NAME}"
# S3 bucket settings used to store images and videos
S3_BUCKET_NAME=''
S3_BUCKET_NAME=""
S3_ACCESS_KEY=""
S3_SECRET_KEY=""
S3_REGION=""
S3_ENDPOINT=""
# Internal database settings
POSTGRES_USER="postgres"
POSTGRES_PASSWORD="CHANGE_ME_PLEASE" # Change this password to ANYTHING else, *PLEASE*!
POSTGRES_DATABASE_NAME="yab"

36
wait-for-db.sh Normal file
View File

@ -0,0 +1,36 @@
#!/bin/bash
# Function to wait for the database to be ready
wait_for_db() {
echo "Waiting for the database to be ready..."
until nc -z db 5432; do
echo "Database is not ready yet. Retrying in 5 seconds..."
sleep 5
done
echo "Database is now ready!"
}
# Function to generate Prisma client
generate_prisma() {
echo "Generating Prisma client..."
npx prisma generate
}
# Function to deploy Prisma migrations
deploy_migrations() {
echo "Deploying Prisma migrations..."
npx prisma migrate deploy
}
# Wait for the database to be ready
wait_for_db
# Generate Prisma client
generate_prisma
# Deploy Prisma migrations
deploy_migrations
# Start the application
echo "Starting the application..."
npm start