NAME
Rapi::Blog::Manual - Rapi::Blog user and developer manual
OVERVIEW
Rapi::Blog is a turn-key, customizable blog platform written using RapidApp.
INSTALLATION
Rapi::Blog can be installed from CPAN in the usual manner:
cpanm Rapi::Blog
It is also distributed with the RapidApp Docker images on Docker Hub, specifically the rapi/psgi image which contains the full stack as of version 1.3001-B
. You can pull this image to any system with docker installed by running this command:
docker pull rapi/psgi
CREATING NEW SITES
Rapi::Blog is a Plack app and can be ran directly (See the SYNOPSIS
in Rapi::Blog). However, for most cases, setting up a new blog can be done using the included utility script rabl.pl:
rabl.pl create path/to/my-cool-blog
Where "my-cool-blog" is a directory which does not exist (or is empty). This script will start a wizard which will allow you to choose from one of the built-in scaffolds to use as a starting point. This will also give you a chance to set a custom password for the admin user (or use the default init from RapidApp which is pass
).
The script will create the databases and files within the directory you choose, including an app.psgi
file which you can use to plackup the site.
The public content will be copied into the scaffold
directory from the built-in skeleton scaffold you selected. Depending on the one you chose (only a couple choices as of version 1.0), this will provide a fully working blog out-of-the-box. You can then replace/change any of the html files for your own needs within the scaffold, which is just an ordinary html site, with access to call template directives.
Creating with Docker
In order to create a new site using Docker and the RapidApp rapi/psgi image, you simply need to create a new container and then run the rabl.pl script from witin it.
First, create the directory you want to use for the site, then create and start a new container. For this example, we'll use port 5001
to run the app:
# Manually create the site directory:
mkdir path/to/my-cool-blog
# Create the new container:
docker create -it \
--name=my-cool-blog --hostname=my-cool-blog \
-p 5001:5000 \
-v $(pwd)/my-cool-blog:/opt/app \
rapi/psgi
# Start the container:
docker start my-cool-blog
Since the app directory is empty, the app will start in a shutdown state. Next, open a shell on the running container and run the rabl.pl create script using the special /opt/app
directory as the path:
docker exec -it my-cool-blog bash
# Now from the shell of the docker container, create the site:
rabl.pl create /opt/app
Once the site is created, you can either restart the docker container, or run the provided app-restart
command from the container shell. Now that the app directory is populated, the container will start normally going forward.
For more information, see the rapi/psgi
documentation on Docker Hub:
https://hub.docker.com/r/rapi/psgi/
SCAFFOLDS
Rapi::Blog serves public facing content from a local "scaffold" which is a simple directory of HTML and associated content, mounted at the root /
of the app. The other, backend namespaces for the various controllers/modules are merged into the common root-based namespace. This design allows for the scaffolds to be structured as an ordinary HTML site following the same conventions as a static site living in a folder on your PC. The only difference between a Rapi::Blog scaffold and a static HTML directory is that files within the scaffold are able to *optionally* call template directives for dynamic content via simple variable substitution.
Scaffolds may also define an additional config which declares details about how it can be used by the backend. See below.
SCAFFOLD CONFIG
The scaffold config is defined in a YAML text file named scaffold.yml in the root of the scaffold directory.
The scaffold.yml supports the following options:
static_paths
List of path prefixes which are considered 'static' meaning no template processing will happen on those paths. You want to list asset/image dirs like 'css/', 'fonts/' etc in this param, which helps performance for paths which the system doesn't need to parse for template directives.
private_paths
List of path prefixes which will not be served publicly, but will still be available internally to include, use as a wrapper, etc. Note that this option isn't really about security but about cleaning the public namespace of paths which are known not to be able to render as stand-alone pages in that context (like snippets, block directives, etc etc)
landing_page
Path/template to render when a user lands on the home page at '/'
(or the URL the app is mounted on)
not_found
Path/template to render when a requested path is not found (404). This provides a mechanism to have branded 404 pages. If not specified, a default/plain 404 page is used.
favicon
Path to the favicon to use for the site.
view_wrappers
The view_wrappers
are what expose Posts in the database to public URLs. The view_wrappers are supplied as a list of HashRefs defining the config of an individual view_wrapper. For example:
view_wrappers:
- { path: post/, type: include, wrapper: private/post.html }
For typical setups, only one view_wrapper is needed, but you can setup as many as you choose.
Each 'view_wrapper'
config requires the following three params:
path
The path prefix to be exposed. The Post name is appended to this path to produce a valid URL for the given Post. Using the above example, a Post named 'my-cool-post'
would be accessible at the public URL '/post/my-cool-post'
.
wrapper
The scaffold template to use when rendering the Post. This should be a TT template which has been setup as a WRAPPER with a the Post body content loaded in [% content %]
. The given Post row object will also be available as [% Post %]
.
type
Must be either 'insert'
or 'include'
to control which TT directive is used internally to load the Post into the [% content %]
in the wrapper. With 'include'
(default) the content of the template, along with its template directives, is processed in advance and then populated into the [% content %]
variable, while with 'insert'
the content is inserted as-is, leaving any directives to be processed in the scope of the wrapper. You shouldn't need to use 'insert'
unless you have a specific reason and know what you are doing.
default_view_path
The default 'view_wrapper'
, identified by its configured path
, to use for accessing posts. This is used in locations where the system generates URLs when it needs to publicly link to a post. Defaults to the first 'include'
type view_wrapper.
preview_path
Optional alternative path to use instead of default_view_path
for the purpose of rendering a "preview" of the post as it will display on the site. This preview is presented in an iframe on the "Publish Preview" tab on the internal Post row page. This is useful in case you want to use a different wrapper that excludes items like top navigation, sidebars, etc, and display just the post itself with the active styles on the site. It is totally optional, and for some cases you may prefer using the same exact view as the public site to be able to really see exactly how the post will look. This would be one of the common a cases for a second 'view_wrapper'
to be defined.
internal_post_path
The one, "real" URL path used to access Posts *internally*. This is used by the view_wrappers internally to resolve the Post from the supplied URL. Defaults to 'private/post/'
which shouldn't normally need to be changed. It is suggested that it be within a private (i.e. covered by 'private_paths'
) namespace.
default_ext
Default file extension for scaffold templates, defaults to html
. This makes it so that the template '/path/to/doc.html'
can alternatively be accessed at '/path/to/doc'
for cleaner/nicer looking URLs
User-defined scaffold datapoints
You may also define any additional datapoints you like in the scaffold config which can be used by the scaffold templates internally. The built-in scaffolds define custom datapoints such as "pages" which they use internally to build menus, etc. The scaffold can be used as a general-purpose meta data store which is available in templates via the [% scaffold %]
template variable.
Taxonomies
As of the current version of Rapi::Blog, besides Author, there are three other general purpose, built-in (i.e. defined by the default database schema) taxonomies, Tags, Categories and Sections. Tags and Categories operate as many-to-many
links, meaning Posts can have multiple Tags and be in multiple Categories, with the difference between them being the manner in which they are set in the admin interface (see below), and Sections are single FK relationships, meaning a Post can be in exactly one Section (or none).
Tags
Tags are created automatically by using social-media syle hashtags in the Post body. For example, if you include the string #foo
the Post will have the tag "foo," if you include the string #CodingStuff
the Post will have the tag "codingstuff" (names are normalized lower-case). The first time a tag name is seen a new Tag row (which is a first class object) is created automatically.
Categories
Categories must be setup in advance and then can be selected when created or editing a Post. When no Category rows exist, the category selector/field is not shown on Post add/edit screens, meaning if you don't want to use Categories, just don't create any.
Sections
Like Categories, Sections must be set up in advance and are also not required. If you don't create any sections, the selector/field will not be shown when creating Posts. Unlike Catagories and Tags whith are many-to-many, Sections are single relationships so a Post can only have one Section. However, Sections can be divided into a hierarchy of parent/child sections, meaning a given Section may be a sub-section of another, which in turn may be a sub-section, and so on (currently depth is limited to 10 levels by rule, and circular references are not allowed). While sub-sections may be defined, this is also not required, and all Sections could be created on the top-level (i.e. not setting any parents).
These choices are are left intentionally ambiguous to give maximum flexibility to the user/scaffold to use the taxonomies they want, and not use the ones the don't want.
TEMPLATE DIRECTIVES
Files within the scaffold (which are not defined as static_paths
) are processed as Template::Toolkit templates with the following methods/parameters exposed in the template variables (i.e. the Template::Stash):
BlogCfg
A HashRef of key/value pairs matching the params of the Rapi::Blog builder object. This is the same as the priviledged template variable c.ra_builder
except instead of being the actual object instance, it is just a copy of public attribute values. This allows templates access to the Rapi::Blog constructor params and computed settings without having to expose the actual live object which is super-user access.
scaffold
The active scaffold config/data structure. Note: since the addition of multiple layered scaffolds, this object represents the effective, merged config of all active scaffolds.
list_posts
Interface to retrieve and filter Posts from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.
Accepted params (all optional):
search
String to search/filter results. The search will match substrings in the body
, name
, title
, summary
, body
and exact match on a tag
. Only Posts with at least one match will be returned.
tag
Limit posts to those containing the named tag
.
category
Limit posts to those containing the named category
.
limit
The maximum number of results to return (i.e. page size).
page
The page number to return, in conjunction with limit
.
sort
Sort keyword for the order posts are returned in. Currently supports 3 possible values (defaults to newest
)
newest
Newest (by Post Date/Time) Posts first
popularity
Posts with the most "Hits" first. Note that in order for a hit to be recorded, the scaffold must call the template directive [% Post.record_hit %]
.
most_comments
Posts with the most comments (includes sub-comments/replies) first
list_tags
Interface to retrieve and filter Tags from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.
Accepted params (all optional):
search
String to search/filter results. The search will match substrings of the tag name.
post_id
Limit results to Tags linked to the supplied post_id
sort
Sort keyword for the order tags are returned in. Currently supports 3 possible values (defaults to popularity
)
popularity
Most popular tags by number of posts which use them.
alphabetical
Tags in alphabetical order.
recent
Most recently used (according to Post Date/Time) tags first.
list_categories
Interface to retrieve and filter Categories from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.
Accepted params (all optional):
search
String to search/filter results. The search will match substrings of the category name.
post_id
Limit results to Categories linked to the supplied post_id
sort
Sort keyword for the order tags are returned in. Currently supports 3 possible values (defaults to popularity
)
popularity
Most popular categories by number of posts which use them.
alphabetical
Categories in alphabetical order.
recent
Most recently used (according to Post Date/Time) categories first.
list_users
Interface to retrieve and filter Users from the database. This is exposed with the ListAPI interface which accepts several named params and returns a common ListAPI result packet.
Accepted params (all optional):
search
String to search/filter results. The search will match substrings of the username
or the full_name
.
only
Limit users to those with the matching named permission, which are currently 'authors' or 'commenters'
User
The currently logged in user, or undef
if not logged in. Returns a Rapi::Blog::DB::Result::User object.
request_path
The path of the current request
remote_action_path
URL path that a client can use to access the Remote controller which provides additional API end-points that can be used to post comments, etc.
add_post_path
URL path to directly load the "New Post" form. Useful if the scaffold wants to provide its own "New Post" links(s).
mount_url
Same as [% c.mount_url %]
- from RapidApp, the mounted path of the app if it is at a sub-url (e.g. using mount
with Plack::Builder, etc). If the app is mounted normally on /
this will be an empty string ''
.
This is provided as a top-level variable so non-privileged templates can access it (since they do not have access to the actual context object in [% c %]
.
local_info
Access to arbitrary data that is specific to both the current session as well as the current URL/path of the active request.
This is used in certain Remote controller calls, such as password_reset, which involve a UI lifecycle that needs to span several requests and allow the template to display messages to the user. This is being set according to the API of the various features which use this, but can also be passed a value from the template call to be set as well.
This also supports the special value 'clear' which can be supplied which will clear/delete the value after returning it.
Post
When the target template of the request is a Post, including requests dispatched via a view_wrapper
, 'Post'
template variable will also be available. This will be a reference to the associated Post Row object (see Rapi::Blog::DB::Result::Post). For non-post requests (i.e. requests to ordinary templates within the scaffold) this value will be undef.
PRIVILEGED TEMPLATE DIRECTIVES
Both scaffold templates as well as post templates are able to access the above template directives/attributes, however, additional privileged directives are available to scaffold templates only (excludes view_wrappers which are processed in the role of a post)
c
A reference to the Catalyst context object for the current request. This provides an entrypoint to the entire application which can be used to perform actions based on the query string params (e.g. [% c.req.params.foo %]
, direct access to the database (e.g. [% c.model('DB::Post').search_rs(...) %]
) or anything else which can be reached via the context.
ensure_logged_out
Whenever called, if a user is already logged in, they are logged out in the background. This can be placed on any pages or in any templates where ensuring a user is not logged in is desired, such as on sign up pages, or other screens where it doesn't make sense for a user to be logged in.
AUTOMATIC DATABASE UPGRADES
Rapi::Blog was designed from the ground up to support automatic schema migrations for all publically released versions of the built-in Schema. This is done via scan/fingerprint of the schema of the existing database to identify the known/previous schema version, and running the all DDL (i.e ALTER TABLE, etc) needed to upgrade it to the current version. When the database does not match any known fingerprints (including the fingerprint of the current version) the app will refuse to start.
Whenever a database is upgraded, a backup of the old database file is made and saved in the directory .bkp.rapi_blog.db/
within the site_dir
.
This means that under normal circumstances, you don't need to worry about upgrading sites from previous versions of Rapi::Blog
-- it should just work and automagically do the right thing.