From a403b114e8af22c1dcd54d3e0ddd74155f3cce6e Mon Sep 17 00:00:00 2001 From: Armored Dragon Date: Mon, 1 Apr 2024 20:01:13 +0000 Subject: [PATCH] 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: https://git.armoreddragon.com/ArmoredDragon/yet-another-blog/pulls/11 Co-authored-by: Armored Dragon Co-committed-by: Armored Dragon --- Dockerfile | 18 ++++++----- README.md | 56 ++++++++++++++++++++++++++++----- backend/core/core.js | 42 +++++++++++++++++-------- docker-compose.yml | 24 +++++++++++++++ package-lock.json | 73 +++++++++++++++++++++++++++++++------------- package.json | 4 +-- template.env | 13 +++++--- wait-for-db.sh | 36 ++++++++++++++++++++++ 8 files changed, 212 insertions(+), 54 deletions(-) create mode 100644 docker-compose.yml create mode 100644 wait-for-db.sh diff --git a/Dockerfile b/Dockerfile index e0a2247..0758527 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] diff --git a/README.md b/README.md index c11b2e8..bd67d92 100644 --- a/README.md +++ b/README.md @@ -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` diff --git a/backend/core/core.js b/backend/core/core.js index d75f32f..459272e 100644 --- a/backend/core/core.js +++ b/backend/core/core.js @@ -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` }; diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..a5ddd6c --- /dev/null +++ b/docker-compose.yml @@ -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: diff --git a/package-lock.json b/package-lock.json index 4a911ff..574bc02 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 0223fd3..b8025c0 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/template.env b/template.env index f3ab587..568228e 100644 --- a/template.env +++ b/template.env @@ -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="" \ No newline at end of file +S3_ENDPOINT="" + +# Internal database settings +POSTGRES_USER="postgres" +POSTGRES_PASSWORD="CHANGE_ME_PLEASE" # Change this password to ANYTHING else, *PLEASE*! +POSTGRES_DATABASE_NAME="yab" \ No newline at end of file diff --git a/wait-for-db.sh b/wait-for-db.sh new file mode 100644 index 0000000..601e316 --- /dev/null +++ b/wait-for-db.sh @@ -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 \ No newline at end of file