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