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
adminandpublicpipelines. adminpipeline sets layout, requires admin.authpipeline looks for current user.publicpipeline usesLoadPodcastsplug to pre-load podcasts.- Most paths are served by
PageController, which loads a template based on the action name. /weeklyand/nightlyroutes are for mailing list subscriptions.- Sub-paths for subscribe, confirm, unsubscribe, etc.
Lib
ConCacheis a worker child of the ChangeLog application./uploadsdirectory is setup to be served statically for dev only inEndpoint./lib/changelog/enums.exdefines a custom column type for the podcast status. This could be used in multiple models./lib/changelog/factory.exis a collection of factories for the various models, usingex_machina./lib/changelog/hashid.exsets up the encode/decode for the hashed IDs. Salt also set here.- Commonly used regular expressions are defined in a
Regexpmodule. Scrivenerdefault page size is setup inRepo.- Page
<title>set viaMeta.TitleandMeta.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
Ectobacked.Newsletterfor example. - Use of
Ecto.get_changeto delete records where thedeletekey has been set in a changeset. Pattern is shown inEcto.Changesetdocumentation. Uses a virtual delete field to remove nested records. Untested. (1) Episode.with_numbered_slugis used to scope episodes using a regex.- Episode
slugis a string. Episodes with numeric-only slugs are “numbered episodes”. Episodes with any non-numeric characters are bonus content. Episode.extract_duration_secondsuses FFMPEG viaSystem.cmdto get the episode duration.
Controllers
- There is a lot of application logic in the controllers. (2)
scrub_paramsis used to covert empty strings in changeset values to nil.- Slack integration for listing upcoming episodes with a countdown.
Helpers
ViewHelpers.no_widowed_wordsprevents an overflowed string from only having a single word on the final line. Inserts a between the last two words.Viewhelpers.tweet_urlhas multipledefsfortweet_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.rundirectly. cast_attachmentsis 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
LIKESQL 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_viewused to generate URLs in emails, passing in the application endpoint instead of a current connection.
Passwordless Logins
- User visits
/into enter their email and be sent a login email GET /indisplays the formPOST /inprocesses the form. Two function definitions, with one being pattern matched to look foremailparam.- 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
@personinstance 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/:tokendecodes 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.