Experience the power of a flexible Vue-based CMS with a dynamic block system that you can personalize to suit your needs

CPlusPatch CMS

A fast, sleek and beautiful website + full content management system developed with NuxtJS.

It is designed to be minimal but efficient, secure and lightweight.


CPlusPatch CMS is a customizable system for making blogs and personal pages with a dynamic Vue-based templating engine. This is a project in beta, and as such should be treated as unstable.

## Requirements

  • A system that can run Node.js (Linux is HEAVILY recommended), or Docker on Linux
  • An S3 or S3 compatible bucket (for example, Cloudflare R2)

Downloading and running

You may use the docker-compose.yml file included in this repository to bootstrap a working environment very fast.

Alternatively, download and install manually:

git clone https://codeberg.org/CPlusPatch/web-business.git
cd web-business
pnpm install

Then, fill out .env with credentials for the S3 or S3-compatible bucket (such as Cloudflare R2, which I am using) and for PostgreSQL.

cp .env.example .env

For the CDN URL, do not include a trailing slash at the end.

Building manually

pnpm build # Bundles project into JS files

# EITHER
pnpm preview # For testing
pm2 start ecosystem.config.js # For production deployment with PM2

Administration

Adding new users (for now)

NOTE: These instructions are temporary, an admin panel for adding users is coming soon

You may use the following JS scripts to generate a hashed password with Node’s crypto library:

import { randomBytes, pbkdf2 } from "crypto";

export function createSalt(length = 128) {
	return randomBytes(length).toString("base64");
}

export function createPasswordHash(
	password,
	salt = "",
	iterations = 10000
) {
	return new Promise((resolve, reject) => {
		pbkdf2(password, salt, iterations, 100, "sha256", (err, derivedKey) => {
			if (err) return reject(err);
			return resolve(`${derivedKey.toString("hex")}:${salt}`);
		});
	});
}

To use it, run createSalt to generate a random salt, then run createPasswordHash("your password here", salt) to generate a new salted password.

Passwords are stored as hash:salt in the database, and createPasswordHash outputs this format.


To continue, you will need some kind of PostgreSQL browser and a working instance of the web app: more administration tools are coming soon.

Open to the user table, and insert the following row:

┌────────────┬───────────────────┬───────────────┬──────────────────────┐
│  username  │   display_name    │ oauthAccounts │       password       │
├────────────┼───────────────────┼───────────────┼──────────────────────┤
│ A username │ New display name  │ []            │ <password hash here> │
└────────────┴───────────────────┴───────────────┴──────────────────────┘

All the other fields may be left blank or will be generated automatically. If the user needs to be an admin, that is a creator of posts, insert this row instead:

┌────────────┬───────────────────┬───────────────┬──────────────────────┬───────┐
│  username  │   display_name    │ oauthAccounts │       password       │ role  │
├────────────┼───────────────────┼───────────────┼──────────────────────┼───────┤
│ A username │ New display name  │ []            │ <password hash here> │ admin │
└────────────┴───────────────────┴───────────────┴──────────────────────┴───────┘

You can save and login with your new credentials now.

Scripts for development

Images

Convert all JPEG images to WebP (uses ImageMagick)

magick mogrify -format webp *.jpeg

Convert all JPEG images to WebP (uses ImageMagick)

Resize all images to 250×250 (when adding languages to /public/static/languages/)

mogrify -path . -auto-orient -thumbnail 250x *.png

Writing custom templates

To add custom templates to the CMS block system, open the templates/ directory, then create two files inside a category folder: TemplateName.vue and TemplateName.json. You may also create new folders, the location doesn’t matter as long as it’s inside templates/.

Inside the .vue file you may write a new Vue component. You will need to add this block of code:

<script setup lang="ts">
defineProps<{
	editable: boolean;
	// You may add arbitrary props here
}>();
</script>

You may use TypeScript and all classes provided by https://unocss.dev/, as well as NuxtJS 3.6 utilities and any package that is included in package.json (such as nuxt-img).

Here is an example for a big hero header:

<script setup lang="ts">
defineProps<{
	imageMain?: string;
	editable: boolean;
}>();
</script>

<template>
	<!-- Main hero -->
	<div class="relative px-4 mx-auto max-w-7xl w-full sm:px-6">
		<nuxt-img
			preload
			:width="1920"
			:height="1080"
			sizes="lg:1920px md:700px sm:400px"
			class="rounded-lg aspect-video shadow-lg w-full duration-150"
			:src="
				imageMain === '' ? 'https://placehold.co/1920x1080' : imageMain
			"
			alt="VSCode screenshot" />
	</div>
</template>

It is recommended that you look at other files inside templates to get a hang of the syntax.

Syntax for the metadata

Once your component is written, you may add your template metadata to TemplateName.json, like this:

{
	"category": "heroes",
	"name": "BigHero",
	"displayName": "Big Banner",
	"description": "Big banner for showing off an image",
	"inputs": {
		"image-main": "string"
	},
	"defaults": {
		"image-main": ""
	}
}

This will allow you to auto-generate the configuration panel for your component.

The syntax for inputs goes as follow:

Input types

"inputs": {
	"header-image": "image",
	"large-text": "paragraph",
	"text-input": "string",
	"invert-order": "boolean",
}

Arrays

Arrays can be declared with an array of a single object, which will be the type of all objects in the array. You can nest arrays inside arrays at will.

"inputs": {
	"grid": [
		{
			"id": "string",
			"tags": [
				{
					"id": "string",
					"name": "string",
					"color": "string",
					"text-color": "string"
				}
			],
			"description": "string",
			"name": "string",
			"href": "string"
		}
	]
},
Note: These input attributes will be passed to the Vue component as props, so their name will be converted to camelCase (e.g. image-main will be converted to imageMain)

Attributions

I will add more attributions when I find the image sources (mostly logos)

Evan You, https://github.com/yyx990803, CC BY 4.0 https://creativecommons.org/licenses/by/4.0, via Wikimedia Commons, for the VueJS logo

Internals

Data is stored inside a PostgreSQL database.

When uploading images, they are converted to WebP inside the browser itself before upload.


Licensed under GPLv3. Made with 💕

GitHub

View Github

Related Posts

Recent Posts

ഇടുക്കിയിലെ മലയോര മേഖലകളിൽ രാത്രിയാത്ര നിരോധിച്ചു. രാത്രി ഏഴു മുതൽ രാവിലെ ആറു വരെയാണ് നിരോധനം

ഏന്തയാർ ഈസ്റ്റിൽ പ്രളയത്തിൽ തകർന്ന പാലത്തിന് പകരം പുതിയ പാലം നിർമ്മിക്കുവാൻ താത്ക്കാലിക പാലം പൊളിച്ച് നീക്കി

Explore the Investment Opportunities: A Comprehensive Guide to Different Types of Mutual Funds

Title: Understanding Mutual Funds: A Beginner's Guide to Investing

തീവ്രമഴ മുന്നറിയിപ്പിന്റെ പശ്ചാതലത്തിൽ സംസ്ഥാനം ജാഗ്രതയിൽ

250,000 അപേക്ഷകൾ വർദ്ധിച്ചതിനാൽ ട്രാൻസ്‌പോർട്ട് കമ്മീഷണർ പരിശോധന പുനരാരംഭിക്കും

ഏലക്കയിൽ കീടനാശിനി സാന്നിധ്യം; ആറര ലക്ഷത്തിലധികം ടിൻ അരവണ നശിപ്പിക്കാൻ ടെൻഡർ ക്ഷണിച്ച് ദേവസ്വം ബോർഡ്‌

ഭീമൻ പാറക്കഷണങ്ങൾ അടർന്ന് ദേശീയ പാതയിലേക്ക് വീഴുന്നത് പതിവാകുന്നു. കുട്ടിക്കാനത്തിനും മുണ്ടക്കയത്തിനുമിടയിൽ നിലനിൽക്കുന്നത് വൻ അപകട ഭീഷണി

ചക്രവാതച്ചുഴി:അതിശക്തമായ മഴ വരുന്നു

പ്ലസ് വൺ പ്രവേശനം. അക്ഷയയിൽ തിക്കി തിരക്കേണ്ട, നെറ്റിവിറ്റി/ജാതി തെളിയിക്കാൻ പത്താംതരം സർട്ടിഫിക്കറ്റ് മതി