Package Exports
- @tripod311/zy_cms
- @tripod311/zy_cms/dist/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@tripod311/zy_cms) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
ZY CMS
CMS engine for Node.js. Name can be interpreted as laZY/eaZY CMS.
Once upon a time I had to make 3 similar services (database + file storage + admin panel + API) in one month. I don't want to do it again, so I created this.
π Table of Contents
Installation
Simply run:
npm install @tripod311/zy_cms
Configuration
Here's an example configuration file:
admin_panel: true
storage:
enable: true
path: "./data/media"
publicGET: true
db:
type: "postgres"
path: "./data/db.sqlite"
host: "localhost"
port: 5432
user: "tripod"
password: "1234"
database: "test"
http:
cookie_secret: "cookie_secret"
port: 8080
credentials:
key: "path-to-key"
cert: "path-to-cert"
ca: "path-to-ca"
cors:
origin:
- "http://localhost:8080"
methods:
- GET
- POST
- PUT
- DELETE
auth:
enable: true
jwt_secret: "jwt_secret"
secure_cookies: false
localization:
enable: true
locales: ["ru", "en"]
fallbackLocale: "en"
Explanation of fields:
admin_panel β toggle default admin panel
storage β file storage options
- enable β toggle file storage
- path β path to where files will be stored
- publicGET β if true,
/storage/:alias
can be accessed without authorization
db β supports
sqlite
,mysql
, andpostgres
- path β only for sqlite
- other options apply to mysql/postgres
http β webserver settings
- cookie_secret β optional
- port β port to listen on
- credentials β set to
null
for http mode - cors β CORS rules
auth β authorization system
- must be enabled if using admin panel
localization β currently affects only
localized
fields in schema
Put this config into config.yaml
in the project root.
Database schema
Example schema.yaml
:
tables:
- name: pages
fields:
- name: name
type: "VARCHAR(300)"
required: true
unique: true
- name: content
type: "json"
distinct_type: "TEXT"
required: true
localized: true
- name: content
type: "markdown"
distinct_type: "TEXT"
required: true
localized: true
- name: switch
type: "BOOLEAN"
required: false
- name: text
type: "TEXT"
- name: date
type: "datetime"
- name: author
type: "VARCHAR(255)"
relation:
table: "users"
column: "login"
kind: many-to-one
onDelete: setNull
- name: some_media
type: "VARCHAR(255)"
relation:
table: "media"
column: "alias"
kind: one-to-one
onDelete: setNull
tables
is a list of table definitionseach field has:
name
β column nametype
β SQL or CMS-specificdistinct_type
β enforced SQL type for CMS typesrequired
β likeNOT NULL
localized
β creates per-locale columnsrelation
β SQL-like foreign key
ZY CMS types
CMS-specific types:
json
β stored as LONGTEXT or equivalentmarkdown
β stored as LONGTEXTdatetime
β stored as ISO string (VARCHAR(30)
)
For PostgreSQL: you must specify
distinct_type
, since PostgreSQL doesnβt haveLONGTEXT
Relations
Analogous to SQL REFERENCES
. Requires:
table
andcolumn
kind
:one-to-one
ormany-to-one
onDelete
:cascade
,setNull
,restrict
,noAction
,setDefault
Many-to-many must be modeled manually with intermediate tables
Basic tables
If auth is enabled, a users
table is created with:
id
,login
,password
- password is stored as bcrypt hash in
CHAR(60)
If storage is enabled, a media
table is created with:
id
,alias
,path
First start
Create an empty file called firstLaunch
in your project root.
This allows creating the first user in admin panel.
Example:
import path from "path";
import Application from "./dist/application.js";
import FastifyStatic from "@fastify/static";
const app = new Application();
async function start () {
await app.setup();
await app.start();
}
function stop () {
(async () => {
await app.stop();
})();
}
process.on("SIGINT", stop);
process.on("SIGTERM", stop);
start();
Admin panel will be available at /admin/
.
API overview
DB
app.db.create<T>(tableName: string, data: Partial<T>): Promise<void>
app.db.read<T>(tableName: string, options: ReadOptions<T>): Promise<T>
app.db.update<T>(tableName: string, data: Partial<T>, options: UpdateOptions<T>): Promise<void>
app.db.delete<T>(tableName: string, options: DeleteOptions<T>): Promise<void>
Auth
app.auth.create(login: string, password: string): Promise<void>
app.auth.delete(login: string): Promise<void>
Middlewares:
app.auth.forceAuth(request, reply) // restricts to logged-in users
app.auth.checkAuth(request, reply) // optional login (request.user?.login)
Route handler:
app.auth.authorize(request, reply) // expects JSON { login, password }, sets cookie
Storage
app.storage.create(file: StorageFile): Promise<void>
app.storage.read(file: StorageFile): Promise<string>
app.storage.update(file: StorageFile): Promise<void>
app.storage.delete(file: StorageFile): Promise<void>
Type reference
StorageFile
interface StorageFile {
id?: number;
alias: string;
extension?: string;
path?: string;
content?: Buffer;
}
ReadOptions
interface ReadOptions<T> {
where?: Partial<T> | WhereFilter<T>;
fields?: (keyof T)[];
orderBy?: keyof T | `${keyof T & string} ASC` | `${keyof T & string} DESC`;
limit?: number;
offset?: number;
}
UpdateOptions
interface UpdateOptions<T> {
returning?: boolean;
where?: Partial<T> | WhereFilter<T>;
}
Feel free to open an issue or PR if youβd like to contribute or request a feature β¨