Skip to content

Latest commit

 

History

History
269 lines (213 loc) · 7.02 KB

terminus_blog.md

File metadata and controls

269 lines (213 loc) · 7.02 KB

A Blogging Platform in TerminusDB

How easy is it to make a blog-focused CMS in TerminusDB?

Very!

This terminusBlog is designed to show how easy it is to build a custom web-app using TerminusDB from scratch.

You should be able to clone the repository and run it locally provided you have npm and terminusdb installed.

Just run:

make
npm start & terminusdb serve &

And you should be able to view the blog at localhost:3000. The TerminusDB backend will be available at localhost:6363.

The Schema

What do we want to store in a blog? Typically we have: Authors, Posts, SiteMap, and Pages. Let's start with those...

{ "@type" : "Class",
  "@id" : "Author",
  "name" : "xsd:string",
  "avatar" : "xsd:anyURI"
  "bio" : "xsd:string" }

Our author is just a name, a bit of text describing who they are, and a URI which we will use to display an image.

{ "@type" : "Class",
  "@id" : "Post",
  "title" : "xsd:string",
  "content" : "xsd:string",
  "date" : "xsd:dateTime",
  "author" : { "@type" : "Set", "@class" : "Author" } }

Posts have a title, the actual content of the blog post, and a set of possible authors (since sometimes you have more than one person writing a piece) and when it was made.

{ "@type" : "Class",
  "@id" : "Page",
  "title" : "xsd:string",
  "content" : "xsd:string" }

Not much to a page! Just a timeless post.

{ "@type" : "Class",
  "@id" : "SiteMap",
  "items" : { "@type" : "Set", "@class" : "SiteItem" }}

{ "@type" : "Class",
  "@id" : "SiteItem",
  "name" : "xsd:string",
  "location" : "xsd:anyURI" }

And a site map is just a set of items, with a name and location.

We can put all of this in a file, schema.json and then (assuming we've already installed TerminusDB) load it with the command:

terminusdb store init
terminusdb db create admin/blog
terminusdb doc insert admin/blog -g schema -f < schema.json

Terminus will respond with:

Documents inserted:
 1: Author
 2: Post
 3: Page
 4: SiteMap
 5: SiteItem

Layout

Now we want to actually design our website. First, let's create a basic structure.

Assuming you have npm installed, you can run the following to start a react app.

npx create-react-app blog

I would also like to use GraphQL, so I'll install the Apollo client.

npm install @apollo/client

And since I hate writing HTML, and write all of my blogs in Markdown, I'm going to use a Markdown renderer.

npm install react-markdown
npm install remark-gfm
npm install react-syntax-highlighter
npm install parse-numeric-range

React markdown is really great. It makes your source code look very readable and deals with a wide variety of formats. To make it look really beautiful however, I had to crib some tricks from Amir Ardalan's blog on Markdown.

Now we're basically ready to go, we just need to edit our App.js file and give ourselves a river of news.

function PostRiver() {
  const offsets = get_offsets()
  const { loading, error, data } = useQuery(POSTS_QUERY, {variables:offsets});
  if (loading) return <Loading />;
  if (error) return `Error! ${error.message}`;
  return (
    <div>
      <div name='post_river'>
      {data.Post.map((post) => {
          console.log(post)
          const date_time_obj = new Date(post.date);
          var options = { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }
          var date_time = date_time_obj.toLocaleDateString("en-US", options)
          var id = post.id.replace(/^iri:\/\/data/, '')
          var path = `${id}`
          var content = snippit(post.content) + `... **[see more](${path})**`
          var image = post.feature ? Image(post.feature) : ''
          return (
           <div key={id} id={id} name='BlogCard'>
             <table className='blogTable'>
              <tr>
                <td className='blogData'>
                  <span><h2><a href={path}>{post.title}</a></h2></span><em>{date_time}</em>
                  <ReactMarkdown components={MarkdownComponents}>
                  {content}
                  </ReactMarkdown>
                </td>
                {image}
              </tr>
             </table>
             <hr />
          </div>
          )})}
      </div>
      <More />
    </div>
  );
}

Ok, so that has a lot in it.

The first bit, get_offsets() just graphs the current page if we're not the home page, but have pressed the more... button.

We do a bit of date presentation stuff, some id munging, trimming of the post to get a snippit, and then the actual blog synopsis card.

We do a GraphQL query to get the posts we are interested in, namely:

 query PostsQuery($offset: Int, $limit: Int) {
    Post(offset: $offset, limit: $limit, orderBy: { date : DESC }) {
        id
        date
        title
        content
        feature {
           alt
           location
        }
    }
}

This gives us a feature image to go with our synopsis (if one exists).

Router

We need to serve up a few pages, so its convenient to put in a router.

npm install react-router-dom

The main App() is pretty straight forward.

function App() {
  let routes = useRoutes([
    { path: "/", element: <Posts /> },
    { path: "p", children : [
       { path: ":page", element: <Posts /> }]},
    { path: "Post", children : [
       { path: ":id", element: <SinglePost /> }]},
    { path: "Page", children : [
       { path: ":id", element: <SinglePage /> }]}
  ]);
  return routes;
}

We have the home page, further down the blog stream, a single post display, and individual page display.

The SiteMap

The SiteMap query gives back an ordered list of items which have a name and a location. We use the order field, an int, to make sure we get them back as an ordered list.

query SitemapQuery {
    SiteMap {
        items(orderBy: { order : ASC }) {
           id
           name
           location
        }
    }
}

Getting Some Data In

To get the data in, I just wrote a short python script that grabbed my already existing blogs.

Since the files are already Markdown, I just stick them in a content field, extract a title and feature image using regexp magic and guess a datetime from the unix timestamp of the file.

Reflections

I never really used the Author object, as I'm the only Author, but otherwise it's a fairly useable blogging engine as is.

Blog engine in TerminusDB

I'm keen to put this up on TerminusX, and try to host the front end on something like Heroku.

The whole thing demonstrates that TerminusDB makes a flexible GraphQL backend for a content management system, with very little effort from the developer.