Working with Posts

Every document in the database is considered a post. All posts share some standard functionality but can be extended and changed in big ways based on post type. In this document, we'll go over ways to work with posts in Factor.

Post Types

When doing advanced work with posts, the first step in many cases is to create and configure a new post type. This allows you to set up a unique set of posts with a custom schema, data handling, and management configuration.

This example adds two post types and connects them via population. Whenever the example post type is queried; population will get the posts listed in anotherList and replace the _id's with post objects.

import { addPostType, ObjectId } from "@factor/api"

addPostType({
  postType: "example",
  schemaPopulated: ["anotherList"],
  schemaDefinition: {
    exampleTitle: String,
    anotherList: [{ ref: "anotherPostType", type: ObjectId }],
  },
})

addPostType({
  postType: "anotherPostType",
  schemaDefinition: {
    anotherTitle: String,
  },
})

Doc - Read the post types doc for more information on setting up post types

Posts and the Store

The post system is designed to work smoothly with the flat store.

Whenever posts are queried they are added to the store directly and which makes them available throughout Factor by _id. This simplifies things since we don't need to pass around posts anymore, only their _id.

A simplified example:

import { requestPostSingle, storeItem } from "@factor/api"

const post = requestPostSingle({ _id })

storeItem(_id, post)

Now later in a component:

<template>
  <div>{{post.title}}</div>
</template>
<script>
  import {stored} from "@factor/api"
  export default {
    props: {
      // Pass only the _id to the component
      postId: {type: String}
    }
    computed: {
      // Using stored() we get the full post
      post(){ return stored(this.postId)}
    }
  }
</script>

The above makes posts portable and universal. This results in making posts easy to move around and work with because you're only working with the post _ids.

Post Population

Factor allows you to populate posts based on their post type configuration. This happens automatically with native post request functions like requestPostIndex but you can also use the requestPostPopulate function.

// Get posts from DB
const posts = await myEndpointRequest("customQuery")

// Populate posts to the store based on their post type
await requestPostPopulate({ posts, context: "list" })

The result of the above is that all the retrieved posts are in the store along with the posts from their populated fields.

Requesting Single Posts and Indexes

The two most common post requests you'll be doing are requesting single posts and requesting post lists or indexes of posts that include meta information about them (total count, categories, etc..)

Factor provides three native helpers to make these easy:

Request Single Post

To request a single post, use the requestPostSingle function. The post and populated fields will be added to the store automatically.

import {requestPostSingle} from "@factor/api"

// Get post by permalink
const post = await requestPostSingle({ permalink: "my-permalink"})

// Get post by _id
const post = await requestPostSingle({ _id: "---objectId---"})

// Get post by conditions
const post = await requestPostSingle({ conditions: {title: "Post Title"}})

// Advanced condition
const post = await requestPostSingle({ conditions: {$text: { $search: search }}})

// Additional options
requestPostSingle({
  _id: "---objectId---",
  log: "myExampleContext", // Logs the context on request, useful for optimization
  createOnEmpty: true // creates a new post if it doesn't exist (requires post type, doesn't save)
  postType: "blog" // required for 'createOnEmpty'
})

createOnEmpty
If you pass the createOnEmpty property, the requested post will be created if it doesn't exist.

Requesting Post Lists and Indexes

Often you'll want to get lists or indexes of posts. An index also returns meta information like total count, taxonomy information (e.g. count per category)

Let's walk through the native helper requestPostIndex...

const {posts, meta} = requestPostIndex({
  limit: 20, // posts per page
  page: 1, // page number
  postType: "blog", // post type
  status: "published" // publication status (published | draft | trash)
  sort: {createdAt: "descending"} // query sort
})

// you can also query by conditions
const {posts, meta} = requestPostIndex({
  postType: "blog", // post type
  conditions: { additionalCondition: "fieldValue" },
})

// common conditions are supported as options
const {posts, meta} = requestPostIndex({
  postType: "blog", // post type
  search: "my search term", // sets search condition
  tag: "my-tag", // gets by tag
  category: "my-category", // gets by category
  time: "day", // posts created within time range (momentJS) "day" | "week" | "month"
  cache: false // cache unique indexes in the store prevents reruns for same data (default: true)
})

Indexes and the Store

When you request an index, the index is added to the store with a storeKey which defaults to the name of the post type (postType). You can change this if you need to run multiple indexes of the same post type on the same page.
Query to the arguments provided. This prevents you from running the same queries over and over again as a user navigates.

const { posts, meta } = requestPostIndex({
  postType: "blog", // post type
  storeKey: "exampleList",
  cache: false, // run every time instead of use stored cache (default: true)
})
// blog-single.vue
export default {
  computed: {
    post() {
      return stored("exampleList") || {}
    },
  },
}

Returned Meta Information

Post index queries return meta information about the query and results. This is useful for displaying pagination, taxonomy, links, and info to the user.

{
  "page": 1,
  "pageCount": 10,
  "pageCurrent": 1,
  "total": 3,
  "totalForQuery": 3,
  "category": [{ "_id": "my-category", "count": 1 }],
  "tag": [{ "_id": "my-tag", "count": 1 }],
  "role": [{ "_id": "my-role", "count": 1 }],
  "status": [{ "_id": "my-status", "count": 1 }]
}

Saving A Single Post

Updating or Deleting Multiple Posts

In some cases, you'll want to update or delete multiple posts at a time. Factor offers some utilities for this as well:

const posts = requestPostSaveMany({
  _ids: ["---objectId---", "---objectId---"],
  data: { dataToSaveToEach: true },
  postType: "blog",
})

const posts = requestPostDeleteMany({
  _ids: ["---objectId---", "---objectId---"],
  postType: "blog",
})

When navigating an app it is common to want post information that is related to a permalink that is in the URL. To help with this Factor will prefetch post information that corresponds to a permalink or _id if it is available in the route.

This helps reduce boilerplate and reduces errors in handling this information yourself. To implement it, all that is needed is to add a route with a parameter of permalink.

addContentRoute({
  path: "/blog/:permalink",
  component: () => import("./blog-single.vue"),
})

When you've done that, the post that corresponds to permalink will be pre-fetched and added to the store with the key of permalink.

// blog-single.vue
export default {
  computed: {
    post() {
      return stored("permalink") || {}
    },
  },
}

Posts on the Dashboard

When you create new post types, you'll have the option to use Factor's default post-management handling with the managePost option. You can also add your own handling and functionality to the dashboard via filters if desired.

Learn more about creating post types.

Embedded Posts

There are situations when you might not want to create new posts for every data point. For example, in forum threads, comments, or chat.

For this use case, Factor has an embedded post system that adds some additional tooling that makes this sort of thing easy.

Learn more about embedded posts.

Direct Requests On The Server

If you are working in the server environment, for example in endpoints or middleware, then you can make a direct call to server functions for posts; you only have to make sure to pass along the bearer parameter discussed in endpoints and middleware.

import { savePost, getSinglePost } from "@factor/api/server"

export const myEndpoint = async (data, { bearer }) => {
  // Save a post
  await savePost({ postType: "user", data }, { bearer })

  // Get a post
  await getSinglePost({ postType: "attachment", _id: "---objectId---" }, { bearer })
}

New Posts on Save
If no _id is passed to the savePost function a new post will be created.