This repository contains the source code of AuralCandy.Net. Please note that this application is tailored to our needs - it's not a generic, turn-key podcast platform. This source code is released for educational purposes for anyone who wishes to learn more about developing Contentful applications using Ruby and Sinatra.
This source code is distributed under Unlicense and can be used for non-commericial purposes only. Any commercial use of this source code requires explicit permission from the author. This application comes with absolutely no warranty. The author assumes no responsibility of data loss or any other unintended side-effects.
Teemu Tammela
- teemu.tammela@auralcandy.net
- www.auralcandy.net
- github.com/teemutammela
- www.linkedin.com/in/teemutammela
- t.me/teemutammela
AuralCandy.Net is a House Music podcast hosted by the Finnish DJ duo MK-Ultra & Mesmic, with more than three decades of combined experience under their belt. Ever since its establishment in 2008, AuralCandy.Net podcasts have reached tens of thousands of listeners from over 150 countries. AuralCandy.Net collaborates with several prominent record labels, such as Piston Recordings and Bosh Recordings.
-
Technology Stack
- Built upon the Sinatra framework
- Utilizes Padrino stand-alone helpers
- Content management and delivery by Contentful
- Ready to be deployed on Heroku (tested with
heroku-22
stack) - Includes sample data and Rack::Test tests
-
Mobile Friendly Responsive Layout
- Built with Bootstrap 4
- Vector icons by Fontawesome 5
- WebP image support on Chromium based browsers (JPEG fallback for Firefox, Safari etc.)
-
Episode Search
- Search by brand and genre
- Pagination and variable items per page
- Sort by date, title or popularity
-
Embedded Media Player
- Saves player state in localStorage
- Continuous playback between page loads 1)
-
Episode Landing Pages
- Episode description
- Genre tags (as defined in MusicRecording schema)
- Track listing
- Related recording labels
- Related episodes
-
RSS/XML Feed
- Compatible with Apple Podcasts, Google Podcasts, iTunes, VLC Media Player etc.
- Episode descriptions
- Episode images (as defined in iTunes Podcast DTD)
- Genre keywords (as defined in iTunes Podcast DTD)
- Track listing
- Related recording labels
-
Search Engine Optimization
- Machine-readable microdata schemas
- Sitemap XML
- Web Application Manifest
- Support for Twitter Card and Open Graph embedding
-
Performance Optimization
- Efficient use of caching, content compression and headers on the application level
- Low amount of HTTP requests and memory footprint
- JavaScript and SASS asset pipeline via Grunt
- Full Cloudflare compatibility
-
Certification
1) Unless prevented by browser autoplay policy. See Media Engagement Index documentation for further details. Some browsers like Brave will require explicit permission from user to allow autoplay.
1) Clone or fork the repository and install the required Ruby gems listed in Gemfile
via Bundler.
$ git clone https://github.com/teemutammela/auralcandy.net.git
$ cd auralcandy.net
$ bundle install
2) Login to Contentful CLI and select the target space.
$ contentful login
$ contentful space use
3) Import content models to target space.
$ contentful space import --content-file import/content-models.json
4) Import example content to target space.
$ contentful space import --content-file import/example-content.json
NOTE! Tests (app/test/tests.rb
) are designed to match the contents of example-content.json
. Altering the example content in Contentful is likely to cause the tests to fail. It is recommended to set up two spaces or environments (e.g. production
and testing
) and keep the unmodified example content in the latter.
Contentful API keys, space ID and environment must be set as environment variables. Create a new .env
file by copying the example file.
$ cp .env.example .env
$ nano .env
On Heroku environment variables, also known as Config Vars, can be set either via the Dashboard or via Heroku CLI.
$ heroku config:set VARIABLE_NAME=variable_value
Variable Name | Description |
---|---|
APPLE_PODCAST_ID |
Apple Podcasts ID (optional). 1) |
CONTENTFUL_SPACE_ID |
Contentful Space ID (source of content). |
CONTENTFUL_DELIVERY_KEY |
Contentful Delivery API key. |
CONTENTFUL_MANAGEMENT_KEY |
Contentful Management API key. |
CONTENTFUL_ENVIRONMENT |
Contentful environment name (e.g. master ). |
OP3_SHOW_UUID |
Open Podcast Prefix Project show UUID. 2) |
OP3_API_TOKEN |
Open Podcast Prefix Project API token. 2) |
1) If ENV["APPLE_PODCAST_ID"]
is set, a apple-itunes-app
meta tag will be inserted into the <head>
section of the page. Read Apple's Smart App Banners documentation for further information.
2) Required by the Rake task for updating episode download count via the OP3 REST API. See section Statistic for further details.
1) Start the application via the heroku local
command. Application is now running at http://localhost:9292.
$ heroku local -p 9292
2) Default environment is development
. Set production environment via the APP_ENV
variable.
$ export APP_ENV=production
NOTE! Global variable $base_url
(set in app/modules/podcast/defaults.rb
) forces HTTPS in production mode. This may break some links while running the application in production mode on a local workstation. You may disable this feature by commenting the following line in app/modules/podcast/defaults.rb
.
$base_url = $base_url.sub("http://", "https://") unless settings.development?
1) Create a new Heroku application via the dashboard.
2) Login to Heroku and associate the repository with the Heroku application.
$ heroku login
$ heroku git:remote -a <APP_NAME>
3) Deploy commits to production by pushing to Heroku master repository.
$ git push heroku master
The default URL of the application is https://appname.herokuapp.com
. More domains can be attached to the application via the Settings tab in the dashboard.
NOTE! Before deploying your site to public production environment, change the line Sitemap: https://www.auralcandy.net/sitemap.xml
in public/robots.txt
to match the domain of your production site.
1) Install the required npm packages listed in package.json
.
$ npm install
$ npm install -g grunt-cli
2) Launch the task runner while working with JavaScripts and stylesheets. Upon file save, *.js
and *.scss
files in directories /assets/javascripts/
and /assets/sass/
will be combined and compressed into target directories /public/javascripts/
and /public/stylesheets/
as configured in Gruntfile.js
.
$ grunt watch
Configuration for development
and production
environments is set in app/app.rb
. See Sinatra documentation for further details about configuration settings.
Directory | Description |
---|---|
app/assets |
JavaScripts and SASS stylesheets. |
app/classes |
Classes for wrapping content objects. |
app/modules |
Contentful clients and application modules. |
app/public |
Static files (images, compiled JavaScripts and CSS stylesheets etc.). |
app/views |
ERB view templates and partials. |
Modules are included and registered in app/app.rb
. Modules follow Sinatra's standard modular extensions pattern.
Directory | Module | Description |
---|---|---|
app/modules/contentful |
delivery.rb |
Contentful Delivery API client. |
app/modules/contentful |
management.rb |
Contentful Management API client. |
app/modules/podcast |
defaults.rb |
Shared defaults (brands, genres, search form parameters and footer). |
app/modules/podcast |
helpers.rb |
Generic helpers, mostly for parsing strings for various purposes. |
app/modules/podcast |
queries.rb |
Query content from Contentful and wrap it to objects (registered as helpers). |
app/modules/podcast |
routes.rb |
Route and URL parameter handling. |
Classes are included in app/app.rb
. Classes are wrappers for corresponding Contentful content models. Classes are used for formatting field values, handling related content by wrapping them with appropriate classes, adding helper methods as object properties and defining the accessible properties of said class.
Content Model | Contentful ID | Class | Description |
---|---|---|---|
Brand |
brand |
brand.rb |
Podcast brand 1). |
DJ |
author |
dj.rb |
Author DJ of a podcast episode. |
Episode |
episode |
episode.rb |
Podcast episode. |
Label |
label |
label.rb |
Recording label related to an episode. |
Navigation Anchor |
navigationAnchor |
navigation_anchor.rb |
Navigation menu in-page anchor. |
Navigation Link |
navigationLink |
navigation_link.rb |
Navigation menu internal or external URL. |
1) Brand
content model is also used to manage site's default settings and navigation menu links and anchors. Each Brand
instance can have its unique navigation menu.
Contentful's free Community tier service package enforces a maximum asset size of 50MB, which is insufficient for podcast usage. Therefor the Episode
content type is designed with external audio file hosting such as Amazon S3 in mind. Please note, that Dropbox and Google Drive are not suitable for audio file hosting, as they introduce too many redirects and latency issues.
However, if you have Team or Enterprise service package and wish to host audio files in Contentful and use the asset reference field type, simply apply the following modifications.
1) Add a file attachment field called Audio
to the Episode
content type.
2) Modify the @audio_url
and @file_size
property definitions in app/classes/episode.rb
as follows.
@audio_url = entry.fields[:audio].url
@file_size = entry.fields[:audio].file.details['size']
3) Remove fields File URL
and File Size
from the Episode
content type.
Query episode downloads from OP3 and update the downloads
properties in Contentful episode entries. Define start date as task parameter.
$ rake statistics:update_episode_downloads[YYYY-MM-DD]