A Look Through the Changelog.com Source Code
Changelog recently open sourced the Phoenix application that provides the backend for their website and podcast publishing platform. They published an announcement post along with the source code on Github.
This was a good oppurtunity for me to take a look at someone else’s Phoenix application, to see how they do things. Below you can find the notes I made while looking through the source.
The Basics
- Phoenix 1.2
- Elixir 1.3
- Ecto 3.0
- Webpack 2.0
Dependencies
The following dependencies were ones I didn’t recognise or found interesting:
- timex - Full-featured Elixir Time Library
- ex_machina - Test data generator
- scrivener - Pagination
- cmark - Markdown rendering
- arc - File upload
- hashids - Reversible non-numerical IDs
- bamboo - Transactional email
- ecto_enum - Enums for Ecto models
- con_cache - Key-Value store
- credo - Elixir Static Analysis
Routes
- Separate
admin
andpublic
pipelines. admin
pipeline sets layout, requires admin.auth
pipeline looks for current user.public
pipeline usesLoadPodcasts
plug to pre-load podcasts.- Most paths are served by
PageController
, which loads a template based on the action name. /weekly
and/nightly
routes are for mailing list subscriptions.- Sub-paths for subscribe, confirm, unsubscribe, etc.
Lib
ConCache
is a worker child of the ChangeLog application./uploads
directory is setup to be served statically for dev only inEndpoint
./lib/changelog/enums.ex
defines a custom column type for the podcast status. This could be used in multiple models./lib/changelog/factory.ex
is a collection of factories for the various models, usingex_machina
./lib/changelog/hashid.ex
sets up the encode/decode for the hashed IDs. Salt also set here.- Commonly used regular expressions are defined in a
Regexp
module. Scrivener
default page size is setup inRepo
.- Page
<title>
set viaMeta.Title
andMeta.AdminTitle
. Imported inLayoutView
. - Other metadata (twitter, images, feeds, description) set by additional Meta modules.
- craisin is the name of the application that handles Campaign Monitor access. Is based on HTTPoison with multiple resources as sub-modules. All mailing list alterations done over API, no local DB records for this.
Models
- Channels are not currently exposed in the public interface. They are playlists for episodes from different podcasts.
- Not all the models are
Ecto
backed.Newsletter
for example. - Use of
Ecto.get_change
to delete records where thedelete
key has been set in a changeset. Pattern is shown inEcto.Changeset
documentation. Uses a virtual delete field to remove nested records. Untested. (1) Episode.with_numbered_slug
is used to scope episodes using a regex.- Episode
slug
is a string. Episodes with numeric-only slugs are “numbered episodes”. Episodes with any non-numeric characters are bonus content. Episode.extract_duration_seconds
uses FFMPEG viaSystem.cmd
to get the episode duration.
Controllers
- There is a lot of application logic in the controllers. (2)
scrub_params
is used to covert empty strings in changeset values to nil.- Slack integration for listing upcoming episodes with a countdown.
Helpers
ViewHelpers.no_widowed_words
prevents an overflowed string from only having a single word on the final line. Inserts a
between the last two words.Viewhelpers.tweet_url
has multipledefs
fortweet_url
, with a default AND a guard. Header w. default, header w. guard, other headers as usual.
File Uploads
- Uses Arc.
- Uploadables defined in web/uploaders/
- Arc provides a way to run transformations using system binaries, without the user invoking
System.run
directly. cast_attachments
is used in model changesets to handle the attachment.
Views/Templates
- Nested models supported using
inputs_for
. Models handle these fields withcast_assoc
.
Javascript & CSS
- Switched from Brunch to Webpack in 935b7c5.
- Turbolinks to do page transitions. Fast page loads, and additionally allows player to keep playing between page loads? (3)
- Semantic UI for admin area CSS framework.
- Separate assets pipelines for main site and admin area.
- CSS is SASS
- Javascript is ECMA 2015 (ES6.0).
- Uses Umbrella.js for DOM/events/AJAX
- ES6.0 JS classes are used to describe domain objects and encapsulate their functionality. Episode and Player for example.
Search
- Admin-only at present.
- Uses
LIKE
SQL queries. - Multiple or single result type (People, Episodes, etc)
- Serviced via AJAX.
Transactional Email:
- Uses Bamboo.
- SMTP provider is Campaign Monitor.
- Username and password configured in config as ENV variables.
- Config picks up environment variables using e.g.:
password: {:system, "CM_SMTP_TOKEN”}
. auth_view
used to generate URLs in emails, passing in the application endpoint instead of a current connection.
Passwordless Logins
- User visits
/in
to enter their email and be sent a login email GET /in
displays the formPOST /in
processes the form. Two function definitions, with one being pattern matched to look foremail
param.- Token and expiry set on user in controller action.
- Error if user not found, user being shown a 404.
- When in dev environment it gives a login link in the response page, and skips sending the email.
- Response page checks for
@person
instance variable, shows confirmation message. - 15 minute expiry. Checked after retrieving user, not part of query.
- Auth token in URL is a Base16 encoded email and random token with a separator,
|
. GET /in/:token
decodes the auth token, checks for user with email and matching random token.- One-time-use. Resets the token and expiry on successful login.
Did I get something wrong? Get in touch with me on Twitter.
Followup
Added on 2016-11-16.
- I actually mistook this as something that was unused. I created a pull request doing so. Jerod Santo kindly showed me where it was used.
- Jerod Santo asked what I meant by this. See my replies there, and also my How I Put Rails Models on a Diet post.
- Confirmed. I’m a big fan of Turbolinks.js, even the statically generated Pincount Podcast site uses it.