npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

medusa-plugin-blogger

v0.0.1

Published

A blog implementation for MedusaJS

Downloads

77

Readme

Content

  1. Introduction
  2. Getting started
  3. API endpoints
  4. Architecture overview

Introduction

A blog integration for your MedusaJS admin page, enabling you to create and manage blog articles directly from the admin interface. This plugin extends the capabilities of MedusaJS, a powerful headless commerce platform, by adding a dedicated blogging feature. With this integration, store administrators can effortlessly create, edit, and publish blog posts to enhance their content marketing strategy, engage customers, and improve SEO.

The Medusa-Plugin-Blogger is designed to provide a seamless user experience, utilizing modern tools and libraries for rich text editing and tag management. By incorporating this plugin into your MedusaJS setup, you can maintain a cohesive content and commerce environment, streamlining your workflow and ensuring consistency across your brand's digital presence.

Getting started

Installation

Run the following command in the directory of the Medusa backend:

yarn add medusa-plugin-blogger

Add to medusa-config.js

In medusa-config.js add the following to the plugins array:

const  plugins = {
  	///...other plugins
  	{
		resolve: 'medusa-plugin-blogger',
		options: {
			enableUI: true,
		},
	}
}

Update database schema

Run the following command from the root of the project to update database with a new table required for storing product variant

npx medusa migrations run

Required dependencies

A file service is required for this plugin to work.

API endpoints

The max size of the body that an endpoint can receive is 10000000B which is the equivalent to 10MB, enough to store tens of academic papers inside an article object. This is enough to store approximately 1.600.000 words In case you go over this threshold with your article, you will not be able to save it.

Store endpoints

GET /store/blog/articles

Returns a json object of all the articles respecting the conditions passed as query parameters, this endpoint accepts query parameters to do a conditional search using TypeORM find parameter, blog_articles can be search using any filter found in the PostgreSQL documentation, because only query parameters are accepted, a function to convert objects to query parameters is provided down here:

export const objectToQueryString = (obj) => {
    return Object.keys(obj)
        .map(key => {
            if (Array.isArray(obj[key]) || typeof obj[key] == "object") {
                return encodeURIComponent(key) + '=' + encodeURIComponent(JSON.stringify(obj[key]))
            } else {
                return encodeURIComponent(key) + '=' + encodeURIComponent(obj[key])
            }
        }).join('&');
}

Here is an example with a commonly found object:

const example_object = {
	"where": {
		"id": "01HZHPGPY4MTR97EVX6FDDEXZE"
	},
	"take": 7,
	"skip": 2,
	"select": ["title", "subtitle", "body"],
	"order": {
		"created_at": "ASC"
	}
}
console.log(objectToQueryString(example_object))

Output: where=%7B%22id%22%3A%2201HZHPGPY4MTR97EVX6FDDEXZE%22%7D&take=7&skip=2&select=%5B%22title%22%2C%22subtitle%22%2C%22body%22%5D&order=%7B%22created_at%22%3A%22ASC%22%7D This output may look strange at first, almost impossible to understand, but the api routes already parse this url properly into the object that will be passed to search the database.

Find operators: if you want to use a specific find operator like Like or ILike you can send an object like this:

{
  "where": {
    "title": {
      "find_operator": "Like",
      "value": "%Hello World%"
    }
  }
}

The supported find operators are: ILike, Like, Raw, LessThan, LessThanOrEqual, MoreThan, MoreThanOrEqual, you can find the meaning of these operators in the official TypeORM documentation. If no supported find operator is found in the object the value will be searched as it is without throwing any error.

In the where field if you search for created_at and updated_at key values, the plugin will automatically try to convert the string values into dates using new Date(string_value), if no date is created than the value searched will be a string, even if it will not give any results because the type of created_at and updated_at is DATE, be mindful when using these two fields as you'll need to provide a correct date to receive an appropriate result.

See the Typeorm documentation to understand better what every of these parameters does, keep in mind that the behavior of where is a little bit different from the one in the documentation, there are some things to take into consideration:

  • The search works using an equal condition, for keys where the value is not id or tags, for example if you want to search for an element that has the title "I like pizza", the query parameters that you'll need to send in the request is { where { title: "I like pizza" } }
  • IDs can be fetched with these two formats blog_article_01HZHPGPY4MTR97EVX6FDDEXZE and 01HZHPGPY4MTR97EVX6FDDEXZE, both versions are valid
  • When adding a tags key to the body, they must be an array and the database will be searched for an element that has at least all the tags inside the tags value in the query parameters
  • We do not yet support searches over the body of the article
  • If you want an OR condition in your where value you need a list where you need to put all the separate conditions like this [ {"title" : "Hello World!", "subtitle": "Awesome article"}, {"title" : "Example", "subtitle": "Awesome example"} ]
  • If you want an AND condition in your where value for a specific field, you need to list all the conditions for that key, let's say you want a date range you can do it like this { "created_at": [{"find_operator": "LessThan","value": "2024-06-14"},{"find_operator": "MoreThanOrEqual","value": "2024-06-12"}]}
  • The body is sanitized automatically from SQL injections

Admin endpoints

GET /admin/blog/articles

Returns a json object of all the articles respecting the conditions passed as query parameters, it works that same way as the homonymous store API routes.

POST /admin/blog/articles

Create a new blog article.

NOTE: No server validation is done to check if the url-slug value is a valid url as this is already done in the frontend and it is assumed that the admin would not do anything to harm its store.

GET /admin/blog/articles/:id

Return a json object of the article having the id in the url.

POST /admin/blog/articles/:id

Modify an already existing blog article, this route requires the new BlogArticle object as well as the id in the url because the old object if completely overwritten with the new one passed over the body.

DELETE /admin/blog/articles/:id

Delete an article having the id in the url.

Architecture overview

Dependencies

Medusa-Plugin-Blogger relies on several key dependencies to provide a rich user experience and robust functionality:

  • Editor.js: A block-styled editor that allows for rich text content creation. Editor.js is highly modular and extensible, and the plugin leverages several Editor.js tools including:

  • React Dropzone: A simple React component for creating file upload zones. This is used in the blog plugin to facilitate image and file uploads directly within the admin interface.

  • Tagify: A powerful tagging library that provides an easy-to-use interface for adding and managing tags. Tagify ensures that blog articles can be tagged efficiently, enhancing content categorization and searchability.

Blog article entity

The BlogArticle entity requires only draft as a mandatory column, this is already handled by the store frontend but there might be need for a custom implementation if working with API routes directly. The choice of not making more columns mandatory was made because the implementation and use the plugin depends strictly on your storefront.

@Entity()
export class BlogArticle extends BaseEntity {
    @PrimaryGeneratedColumn()
    id: string;

    @Column({ nullable: true })
    author: string;

    @Column('text', { array: true, nullable: true })
    tags: string[];

    @Column({ nullable: true, unique: true })
    seo_title: string;

    @Column({ nullable: true })
    seo_keywords: string;

    @Column({ nullable: true, unique: true })
    url_slug: string;

    @Column({ nullable: true, unique: true })
    seo_description: string;

    @Column({ nullable: true })
    thumbnail_image: string;

    @Column({ nullable: true })
    title: string;

    @Column({ nullable: true })
    subtitle: string;

    @Column('json', { nullable: true, array: false })
    body: any; // Assuming body will be a complex JSON structure

    @Column("text", { array: true, nullable: true})
    body_images: string[];

    @Column({ nullable: false })
    draft: boolean;

    @BeforeInsert()
    private beforeInsert(): void {
      this.id = generateEntityId(this.id, "blog_article")
    }
}

License

This project is licensed under the MIT License. See the LICENSE file for details.


A special thank to

Stargazers

Stargazers repo roster for @MoscatelliMarco/medusa-plugin-blogger

Forkers

Forkers repo roster for @MoscatelliMarco/medusa-plugin-blogger