Current section

Files

Jump to
blogit lib blogit.ex
Raw

lib/blogit.ex

defmodule Blogit do
@moduledoc """
The Blogit Application module for the application `:blogit`.
The application is started using this module and can be used through the
public interface it provides.
What is Blogit? Blogit is a blog engine. It uses a repository (by default
a git repository) containing markdown files to build posts and
configuration for a blog. It can be used by, for example, Phoenix
application as a back-end. One such Phoenix application is
[Blogit Web](https://github.com/meddle0x53/blogit_web).
An example of a running blog which uses `Blogit` and `Blogit Web` is the
blog of the Elixir Course of the Sofia University :
[blog.elixir-lang.bg](https://blog.elixir-lang.bg/posts).
Its git repository, can be found here https://github.com/ElixirCourse/blog.
Yes, Blogit is the Elixir version of Octopress. That said, Blogit is very
extensible - the repository it uses can be something other than a Git one.
For example it could be FTP, local file system, Mnesia, Postgres, etc...
The only thing needed is to implement the `Blogit.RepositoryProvider`
behaviour and to configure Blogit to use it:
Example (`config/prod.exs`):
```elixir
config :blogit,
repository_url: "some_protocol://some_location.net",
repository_provider: Blogit.RepositoryProviders.MyProvider
```
For now the post files have to be in markdown, but this will be configurable
too. The meta-data of a post is in YAML which could be included in the
markdown source file of the post, in the beginning.
As Blogit is simple OTP Application with very simple public interface,
presented by this module, it can have custom front-end too.
It can be Phoenix+HTLM, Phoenix+React.js, Phoenix+ELM, it can be just
some Plug application, it can use `:gen_tcp` or be some blog-in-the-terminal.
So Blogit is blog engine by a programmer for programmers.
"""
use Application
alias Blogit.Components.Posts
alias Blogit.Components.Metas
alias Blogit.Components.PostsByDate
alias Blogit.Components.Configuration
alias Blogit.Models.Post
alias Blogit.Logic.Search
import Blogit.Settings
@repository_provider Application.get_env(
:blogit,
:repository_provider,
Blogit.RepositoryProviders.Git
)
@default_from_posts 0
@default_size_posts :infinity
def start(_type, _args) do
Blogit.Supervisor.start_link(@repository_provider)
end
@doc """
Returns a list of `Blogit.Models.Post.Meta` structs representing posts in
the blog. The posts are sorted by their creation date, newest first.
All the markdown files in the posts folder will be transformed
into `Blogit.Models.Post` structs and their `created_at` meta field
will be read using the configured `Blogit.RepositoryProvider`. The 'meta'
fields of every post include a `preview` field. Its value is HTML preview
of the post. The provider implementation should search (by default) for
posts in a `posts` folder, which should be located at the root of the git
repository.
Example configuration (`config/prod.exs`):
```elixir
config :blogit,
repository_url: "some_protocol://some_location.net",
repository_provider: Blogit.RepositoryProviders.MyProvider,
languages: ["en", "bg"]
```
## Options
* `:from` (non-negative integer) - all the posts for the given/default
`language` ordered by their creation date and with indexes smaller than the
value of this option will be skipped and not present in the result list of
post meta data. Can be used along with the `:size` option to implement simple
paging. Defaults to `0`.
* `:size` (positive integer or `:infinity`) - the maximum number of post meta
structs in the result. By default it is `:infinity`.
Can be used along with the `:from` option to implement simple paging.
* `:language` - different sets of posts can exist for every language
configured. This option can be used to specify which of these sets should be
used as the source of the post meta data to be returned.
By default posts of the default language will be returned.
## Example
```
posts = Blogit.list_posts(from: 5, size: 10, language: "es")
```
"""
@spec list_posts(keyword) :: [Post.Meta.t()]
def list_posts(options \\ []) do
{from, size, language} = read_options(options)
GenServer.call(Metas.name(language), {:list, from, size})
end
@doc """
Returns a list of tuples. Every such tuple has the unique name of a post as first
element and its title as second. These tuples are sorted by the
last updated date of the posts they represent.
All the markdown files in the source posts folder will be transformed
into `Blogit.Models.Post` structs and their `updated_at` meta field
will be read using the configured `Blogit.RepositoryProvider`.
Pinned posts are posts which have specified `pinned: true` in their meta
data.
These are special posts which should be easy to find in the front-end
implementation.
The only supported option is `language`.
It defaults to the default language (the first one in the configured `languages` list).
Pinned post tuples for the given `language` will be returned.
"""
@spec list_pinned(keyword) :: [{String.t(), String.t()}]
def list_pinned(options \\ []) do
name = Metas.name(options[:language] || default_language())
GenServer.call(name, :list_pinned)
end
@doc """
Returns a list of `Blogit.Models.Post.Meta` structs, filtered by given criteria.
The first argument of the function is a map of filters.
This map supports zero or more of the following key-values:
* "author" - Used to filter posts by their `.meta.author` field.
* "category" - Used to filter posts by their `.meta.category` field.
* "tags" - Used to filter posts by their `.meta.tags` field.
The value for this key should a string of comma separated tags.
* "year" - Used to filter posts by their `.meta.year` field.
* "month" - Used to filter posts by their `.meta.month` field.
* "q" - A query to filter posts by their content or title. Supports text in
double quotes in order to search for phrases.
For more information on the filtering see
`Blogit.Logic.Search.filter_by_params/2`.
## Options
* `:from` (non-negative integer) - all the posts meeting the given filtering
criteria for the given/default `language` ordered by their creation date
and with indexes smaller than the value of this option will be skipped and
not present in the result list of posts list. Defaults to `0`.
Can be used along with the `:size` option to implement simple paging.
* `:size` (positive integer or `:infinity`) - the maximum number of posts in
the result. By default it is `:infinity`. Can be used along with the
`:from` option to implement simple paging.
* `:language` - different sets of posts can exist for every language
configured. This option can be used to specify which of these sets should be
used as the source of the posts to be returned.
By default posts of the default language will be returned.
"""
@type filters :: %{String.t() => Search.search_value()}
@spec filter_posts(filters, keyword) :: [Post.t()]
def filter_posts(params, options \\ []) do
{from, size, language} = read_options(options)
GenServer.call(Posts.name(language), {:filter, params, from, size})
end
@doc """
Returns a list of tuples of three elements from the given list of posts.
The first element of a tuple is a year.
The second is a month number.
The third is a counter - how many posts are created during that month
and that year.
Can be used for implementing an easy-to-browse view component by years/months.
The only supported option is `language`.
It defaults to the default language (the first one in the configured `languages` list).
Statistics for the posts in the given `language` will be returned.
For more information see `Blogit.Models.Post.collect_by_year_and_month/1`.
"""
@spec posts_by_dates(keyword) :: Post.year_month_count_result()
def posts_by_dates(options \\ []) do
id = PostsByDate.name(options[:language] || default_language())
GenServer.call(id, :get)
end
@doc """
Returns a single post by its unique identifier - its `name` field.
The name should be an atom. The result is in the form `{:ok, post}` if a
post with the given `name` exist for the given (or default) language.
The `post` element of that tuple is a `Blogit.Models.Post` struct.
Posts have unique names, usually constructed using the file path of their
source markdown file in the repository.
If there is no post with the given `name`,
the tuple `{:error, "No post with name the-passed-name found."}` is returned.
The only supported option is `language`.
It defaults to the default language (the first one in the configured `languages` list).
Post with the given `name` in the given `language` will be returned if found.
"""
@spec post_by_name(atom, keyword) :: {:ok, Post.t()} | {:error, String.t()}
def post_by_name(name, options \\ []) do
{_, _, language} = read_options(options)
GenServer.call(Posts.name(language), {:by_name, name})
end
@doc """
Retrieves the blog configuration. The configuration is in the form
of a `Blogit.Models.Configuration` struct.
The configuration is generated from an YML file (by default blog.yml, stored
in the root of the repository of the blog). This location can be configured
through setting the `:configuration_file` key of the `:blogit` configuration.
The only supported option is `language`.
It defaults to the default language (the first one in the configured `languages` list).
Configuration for the given `language` will be returned.
"""
@spec configuration(keyword) :: Blogit.Models.Configuration.t()
def configuration(options \\ []) do
name = Configuration.name(options[:language] || default_language())
GenServer.call(name, :get)
end
###########
# Private #
###########
defp read_options(options) do
from = Keyword.get(options, :from, @default_from_posts)
size = Keyword.get(options, :size, @default_size_posts)
language = Keyword.get(options, :language, default_language())
{from, size, language}
end
end