JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 8
  • Score
    100M100P100Q56235F
  • License MIT

Simple cms for simple services and websites

Package Exports

    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, and postgres

      • 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 definitions

    • each field has:

      • name – column name
      • type – SQL or CMS-specific
      • distinct_type – enforced SQL type for CMS types
      • required – like NOT NULL
      • localized – creates per-locale columns
      • relation – SQL-like foreign key

    ZY CMS types

    CMS-specific types:

    • json – stored as LONGTEXT or equivalent
    • markdown – stored as LONGTEXT
    • datetime – stored as ISO string (VARCHAR(30))

    For PostgreSQL: you must specify distinct_type, since PostgreSQL doesn’t have LONGTEXT

    Relations

    Analogous to SQL REFERENCES. Requires:

    • table and column
    • kind: one-to-one or many-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 "@tripod311/zy_cms";
    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 ✨