Browse Source

automatic cover loading with discogs api and then some

master
Inhji Y. 2 years ago
parent
commit
05ee36fbfc
  1. 2
      .gitignore
  2. 14
      .iex.exs
  3. 3
      assets/css/app.css
  4. 15
      assets/css/app.scss
  5. 134
      assets/css/phoenix.css
  6. 13
      assets/js/app.js
  7. 5218
      assets/package-lock.json
  8. 8
      assets/package.json
  9. BIN
      assets/static/images/album.png
  10. BIN
      assets/static/images/phoenix.png
  11. 14
      assets/webpack.config.js
  12. 3
      config/config.exs
  13. 3
      lib/dagon/application.ex
  14. 20
      lib/dagon/listens/albums.ex
  15. 6
      lib/dagon/listens/albums/album.ex
  16. 35
      lib/dagon/listens/albums/uploader.ex
  17. 5
      lib/dagon/listens/artists.ex
  18. 45
      lib/dagon/listens/listens.ex
  19. 49
      lib/dagon/listens/sparkline.ex
  20. 5
      lib/dagon/listens/tracks.ex
  21. 93
      lib/dagon/listens/workers/discogs_worker.ex
  22. 35
      lib/dagon/listens/workers/listenbrainz_worker.ex
  23. 2
      lib/dagon_web/controllers/album_controller.ex
  24. 15
      lib/dagon_web/controllers/artist_controller.ex
  25. 4
      lib/dagon_web/controllers/page_controller.ex
  26. 5
      lib/dagon_web/endpoint.ex
  27. 16
      lib/dagon_web/templates/album/form.html.eex
  28. 41
      lib/dagon_web/templates/album/index.html.eex
  29. 16
      lib/dagon_web/templates/artist/index.html.eex
  30. 32
      lib/dagon_web/templates/artist/show.html.eex
  31. 43
      lib/dagon_web/templates/layout/app.html.eex
  32. 25
      lib/dagon_web/templates/listen/index.html.eex
  33. 18
      lib/dagon_web/templates/listen/listen.html.eex
  34. 61
      lib/dagon_web/templates/page/index.html.eex
  35. 12
      lib/dagon_web/templates/track/index.html.eex
  36. 5
      mix.exs
  37. 5
      mix.lock
  38. 2
      priv/repo/migrations/20191023205308_create_albums.exs

2
.gitignore

@ -32,3 +32,5 @@ npm-debug.log
# we ignore priv/static. You may want to comment
# this depending on your deployment strategy.
/priv/static/
/uploads/

14
.iex.exs

@ -0,0 +1,14 @@
import Ecto.Query
alias Dagon.Repo
alias Dagon.Listens.Listens.Listen
alias Dagon.Listens.Albums.Album
alias Dagon.Listens.Artists.Artist
alias Dagon.Listens.Tracks.Track
alias Dagon.Listens.Listens
alias Dagon.Listens.Albums
alias Dagon.Listens.Artists
alias Dagon.Listens.Tracks

3
assets/css/app.css

@ -1,3 +0,0 @@
/* This file is for your main application css. */
@import "./phoenix.css";

15
assets/css/app.scss

@ -0,0 +1,15 @@
@import "~bulma/bulma.sass";
.media.is-dense {
margin-top: 0;
padding-top: 0;
border: 0;
}
.has-padding-bottom {
padding-bottom: 1rem;
}
.has-margin-bottom {
margin-bottom: 1rem;
}

134
assets/css/phoenix.css

File diff suppressed because one or more lines are too long

13
assets/js/app.js

@ -1,7 +1,7 @@
// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.
import css from "../css/app.css"
import css from "../css/app.scss"
// webpack automatically bundles all modules in your
// entry points. Those entry points can be configured
@ -15,3 +15,14 @@ import "phoenix_html"
//
// Local files can be imported directly using relative paths, for example:
// import socket from "./socket"
import Dailychart from "dailychart"
document.addEventListener('DOMContentLoaded', function () {
Dailychart.create('#dailychart', {
lineWidth: 2,
height: 50,
width: 200
});
})

5218
assets/package-lock.json

File diff suppressed because it is too large

8
assets/package.json

@ -6,6 +6,8 @@
"watch": "webpack --mode development --watch"
},
"dependencies": {
"bulma": "^0.8.0",
"dailychart": "^1.3.1",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html"
},
@ -16,9 +18,11 @@
"copy-webpack-plugin": "^4.5.0",
"css-loader": "^2.1.1",
"mini-css-extract-plugin": "^0.4.0",
"node-sass": "^4.13.0",
"optimize-css-assets-webpack-plugin": "^4.0.0",
"sass-loader": "^8.0.0",
"uglifyjs-webpack-plugin": "^1.2.4",
"webpack": "4.4.0",
"webpack-cli": "^2.0.10"
"webpack": "^4.36.0",
"webpack-cli": "^3.3.9"
}
}

BIN
assets/static/images/album.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

BIN
assets/static/images/phoenix.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

14
assets/webpack.config.js

@ -31,6 +31,20 @@ module.exports = (env, options) => ({
{
test: /\.css$/,
use: [MiniCssExtractPlugin.loader, 'css-loader']
},
{
test: /\.s[ca]ss$/i,
use: [
MiniCssExtractPlugin.loader,
{
loader: 'css-loader',
options: {}
},
{
loader: 'sass-loader',
options: {}
}
]
}
]
},

3
config/config.exs

@ -25,6 +25,9 @@ config :logger, :console,
# Use Jason for JSON parsing in Phoenix
config :phoenix, :json_library, Jason
config :arc,
storage: Arc.Storage.Local
# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

3
lib/dagon/application.ex

@ -13,7 +13,8 @@ defmodule Dagon.Application do
# Start the endpoint when the application starts
DagonWeb.Endpoint,
# Starts a worker by calling: Dagon.Worker.start_link(arg)
{Dagon.Listens.Workers.ListenbrainzWorker, %{}}
{Dagon.Listens.Workers.ListenbrainzWorker, %{}},
{Dagon.Listens.Workers.DiscogsWorker, %{}}
]
# See https://hexdocs.pm/elixir/Supervisor.html

20
lib/dagon/listens/albums.ex

@ -18,7 +18,25 @@ defmodule Dagon.Listens.Albums do
"""
def list_albums do
Repo.all(Album)
Album
|> order_by(:name)
|> Repo.all()
end
def list_albums_without_cover do
Album
|> where([a], is_nil(a.image))
|> where([a], is_nil(a.discogs_id))
|> limit(10)
|> Repo.all()
|> Repo.preload(:artist)
end
def list_albums_with_cover do
Album
|> where([a], not is_nil(a.image))
|> Repo.all()
|> Repo.preload(:artist)
end
@doc """

6
lib/dagon/listens/albums/album.ex

@ -1,13 +1,16 @@
defmodule Dagon.Listens.Albums.Album do
use Ecto.Schema
use Arc.Ecto.Schema
import Ecto.Changeset
schema "albums" do
field :name, :string
field :slug, :string
field :image, Dagon.Listens.Albums.Uploader.Type
field :mbid, :string
field :msid, :string
field :discogs_id, :integer
belongs_to :artist, Dagon.Listens.Artists.Artist
has_many :tracks, Dagon.Listens.Tracks.Track
@ -19,7 +22,8 @@ defmodule Dagon.Listens.Albums.Album do
@doc false
def changeset(album, attrs) do
album
|> cast(attrs, [:name, :slug, :mbid, :msid, :artist_id])
|> cast(attrs, [:name, :slug, :mbid, :msid, :artist_id, :discogs_id])
|> cast_attachments(attrs, [:image], allow_paths: true)
|> validate_required([:name, :slug, :msid, :artist_id])
end
end

35
lib/dagon/listens/albums/uploader.ex

@ -0,0 +1,35 @@
defmodule Dagon.Listens.Albums.Uploader do
use Arc.Definition
use Arc.Ecto.Definition
# Include ecto support (requires package arc_ecto installed):
# use Arc.Ecto.Definition
@versions [:thumb, :large]
# Whitelist file extensions:
def validate({_file, _scope}), do: true
def transform(:large, _) do
{:convert, "-strip -thumbnail 500x500^ -gravity center -extent 500x500 -format png", :png}
end
def transform(:thumb, _) do
{:convert, "-strip -thumbnail 100x100^ -gravity center -extent 100x100 -format png", :png}
end
# Override the persisted filenames:
def filename(version, {_file, scope}) do
"#{scope.msid}_#{version}"
end
# Override the storage directory:
def storage_dir(_, _) do
"uploads/album"
end
# Provide a default URL if there hasn't been a file uploaded
def default_url(_version, _scope) do
"/images/album.png"
end
end

5
lib/dagon/listens/artists.ex

@ -18,7 +18,10 @@ defmodule Dagon.Listens.Artists do
"""
def list_artists do
Repo.all(Artist)
Artist
|> order_by(:name)
|> Repo.all()
|> Repo.preload([:listens, :albums, :tracks])
end
@doc """

45
lib/dagon/listens/listens.ex

@ -18,7 +18,36 @@ defmodule Dagon.Listens.Listens do
"""
def list_listens do
Repo.all(Listen)
Listen
|> order_by(desc: :listened_at)
|> Repo.all()
|> Repo.preload([:artist, :album, :track])
end
@doc """
Returns the limted list of listens.
## Examples
iex> list_listens(10)
[%Listen{}, ...]
"""
def list_listens(limit) do
Listen
|> order_by(desc: :listened_at)
|> limit(^limit)
|> Repo.all()
|> Repo.preload([:artist, :album, :track])
end
def listens_per_month_by_artist(artist) do
Listen
|> select([l], [count(l.id), fragment("date_trunc('month', ?) as month", l.listened_at)])
|> order_by([l], fragment("date_trunc('month', ?)", l.listened_at))
|> group_by([l], fragment("date_trunc('month', ?)", l.listened_at))
|> where([l], l.artist_id == ^artist.id)
|> Repo.all()
end
@doc """
@ -37,6 +66,20 @@ defmodule Dagon.Listens.Listens do
"""
def get_listen!(id), do: Repo.get!(Listen, id)
def get_oldest_listen() do
Listen
|> order_by(asc: :listened_at)
|> limit(1)
|> Repo.one!()
end
def get_newest_listen() do
Listen
|> order_by(desc: :listened_at)
|> limit(1)
|> Repo.one!()
end
@doc """
Creates a listen.

49
lib/dagon/listens/sparkline.ex

@ -0,0 +1,49 @@
defmodule Dagon.Listens.Sparkline do
def create(listens_per_month, oldest_listen, newest_listen) do
[_, date_first] = List.first(listens_per_month)
[_, date_last] = List.last(listens_per_month)
beginning_interval = get_duration(oldest_listen.listened_at, date_first)
end_interval = get_duration(date_last, newest_listen.listened_at)
filled_listens =
listens_per_month
|> fill_gaps()
fill_list(beginning_interval) ++
filled_listens ++
fill_list(end_interval)
end
defp fill_list(0), do: []
defp fill_list(count), do: Enum.reduce(1..count, [], fn _, acc -> acc ++ [0] end)
defp get_duration(start_date, end_date) do
if Timex.after?(start_date, end_date) do
0
else
Timex.Interval.new(from: start_date, until: end_date, step: [months: 1])
|> Timex.Interval.duration(:months)
end
end
defp fill_gaps(listens) do
new_list =
for {[count, listen], index} <- Enum.with_index(listens) do
case Enum.at(listens, index + 1) do
nil ->
count
[_, next_listen] ->
duration =
Timex.Interval.new(from: listen, until: next_listen)
|> Timex.Interval.duration(:months)
|> fill_list()
[count] ++ duration
end
end
List.flatten(new_list)
end
end

5
lib/dagon/listens/tracks.ex

@ -18,7 +18,10 @@ defmodule Dagon.Listens.Tracks do
"""
def list_tracks do
Repo.all(Track)
Track
|> order_by(:name)
|> Repo.all()
|> Repo.preload([:artist, :listens])
end
@doc """

93
lib/dagon/listens/workers/discogs_worker.ex

@ -0,0 +1,93 @@
defmodule Dagon.Listens.Workers.DiscogsWorker do
use GenServer
require Logger
alias Dagon.Listens.Albums
@fetch_interval 1 * 60 * 1_000
@base_url "https://api.discogs.com/database/search"
@token "kRIDCYTMRucJojWzQKlXlDAnDlQSgmXboMEZiUBT"
@invalid_discogs_id -1
def start_link(args) do
Logger.info("Starting Discogs Worker..")
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def update() do
GenServer.cast(__MODULE__, :update)
end
def init(state \\ %{}) do
schedule_fetch(10_000)
{:ok, state}
end
def handle_info(:fetch, state) do
new_state = do_fetch(state)
{:noreply, new_state}
end
def schedule_fetch(wait_time \\ @fetch_interval) do
Logger.info("Scheduling Discogs Worker..")
Process.send_after(self(), :fetch, wait_time)
end
def do_fetch(state) do
albums = Albums.list_albums_without_cover()
Enum.each(albums, &fetch_album_cover/1)
# todo
schedule_fetch()
state
end
def fetch_album_cover(album) do
url = get_url(album)
case HTTPoison.get!(url, [{"User-Agent", "Dagon/0.1.0"}]) do
%HTTPoison.Response{body: body} ->
data =
body
|> Jason.decode!(keys: :atoms)
|> Map.get(:results)
|> List.first()
if not is_nil(data) do
discogs_id = data.id
image = data.cover_image
Logger.info("Saving cover image for #{album.name}")
Logger.debug("Remote filename: #{image}")
Albums.update_album(
album,
%{
image: image,
discogs_id: discogs_id
}
)
else
Logger.warn("No data found for #{album.name}")
Albums.update_album(
album,
%{
discogs_id: @invalid_discogs_id
}
)
end
_ ->
nil
end
end
def get_url(album) do
album_name = URI.encode(album.name)
artist_name = URI.encode(album.artist.name)
"#{@base_url}?token=#{@token}&release_title=#{album_name}&artist=#{artist_name}"
end
end

35
lib/dagon/listens/workers/listenbrainz_worker.ex

@ -42,8 +42,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
user = "inhji"
url = "#{@base_url}/#{user}/listens?min_ts=#{last_ts}"
Logger.info("Last listen timestamp: #{last_ts}")
Logger.info("Fetching new Listens..")
Logger.info("Fetching new Listens for Timestamp #{last_ts}:")
Logger.info(url)
listens =
@ -62,8 +61,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
|> prepare_listens()
|> Enum.filter(fn l -> !is_nil(l) end)
|> Enum.each(fn changeset ->
result = Repo.insert(changeset)
Logger.debug(inspect(result))
Repo.insert(changeset, log: false)
end)
schedule_fetch()
@ -71,11 +69,12 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
end
def last_listen_timestamp do
case Repo.one(
from l in Dagon.Listens.Listens.Listen,
order_by: [desc: l.listened_at],
limit: 1
) do
query =
from l in Dagon.Listens.Listens.Listen,
order_by: [desc: l.listened_at],
limit: 1
case Repo.one(query, log: false) do
nil -> 1
listen -> DateTime.to_unix(listen.listened_at)
end
@ -114,9 +113,9 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
def maybe_create_artist(name, messybrainz_id) do
artist =
case Repo.get_by(Artist, msid: messybrainz_id) do
case Repo.get_by(Artist, [msid: messybrainz_id], log: false) do
nil ->
Logger.info("Creating new artist #{name}")
Logger.info("Creating new Artist #{name}")
%Artist{}
|> Artist.changeset(%{
@ -124,7 +123,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
slug: Slugger.slugify_downcase(name),
msid: messybrainz_id
})
|> Repo.insert!()
|> Repo.insert!(log: false)
artist ->
artist
@ -139,9 +138,9 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
def maybe_create_album(name, messybrainz_id, artist) do
album =
case Repo.get_by(Album, name: name) do
case Repo.get_by(Album, [name: name], log: false) do
nil ->
Logger.info("Creating new album #{name}")
Logger.info("Creating new Album #{name}")
%Album{}
|> Album.changeset(%{
@ -150,7 +149,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
msid: messybrainz_id,
artist_id: artist.id
})
|> Repo.insert!()
|> Repo.insert!(log: false)
album ->
album
@ -162,13 +161,13 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
def maybe_create_track(name, artist, album) do
track =
Track
|> Repo.get_by(name: name, artist_id: artist.id, album_id: album.id)
|> Repo.get_by([name: name, artist_id: artist.id, album_id: album.id], log: false)
|> Repo.preload([:album, :artist])
track =
case track do
nil ->
Logger.info("Creating new track #{name}")
Logger.info("Creating new Track #{name}")
%Track{}
|> Track.changeset(%{
@ -176,7 +175,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
artist_id: artist.id,
album_id: album.id
})
|> Repo.insert!()
|> Repo.insert!(log: false)
track ->
track

2
lib/dagon_web/controllers/album_controller.ex

@ -5,7 +5,7 @@ defmodule DagonWeb.AlbumController do
alias Dagon.Listens.Albums.Album
def index(conn, _params) do
albums = Albums.list_albums()
albums = Albums.list_albums_with_cover()
render(conn, "index.html", albums: albums)
end

15
lib/dagon_web/controllers/artist_controller.ex

@ -1,6 +1,7 @@
defmodule DagonWeb.ArtistController do
use DagonWeb, :controller
alias Dagon.Listens.Listens
alias Dagon.Listens.Artists
alias Dagon.Listens.Artists.Artist
@ -28,7 +29,19 @@ defmodule DagonWeb.ArtistController do
def show(conn, %{"id" => id}) do
artist = Artists.get_artist!(id)
render(conn, "show.html", artist: artist)
oldest = Listens.get_oldest_listen()
newest = Listens.get_newest_listen()
listens = Listens.listens_per_month_by_artist(artist)
listens_per_month = Dagon.Listens.Sparkline.create(listens, oldest, newest)
per_month = %{
listens: listens_per_month,
oldest: oldest,
newest: newest
}
render(conn, "show.html", artist: artist, per_month: per_month)
end
def edit(conn, %{"id" => id}) do

4
lib/dagon_web/controllers/page_controller.ex

@ -2,6 +2,8 @@ defmodule DagonWeb.PageController do
use DagonWeb, :controller
def index(conn, _params) do
render(conn, "index.html")
last_listens = Dagon.Listens.Listens.list_listens(3)
render(conn, "index.html", listens: last_listens)
end
end

5
lib/dagon_web/endpoint.ex

@ -15,6 +15,11 @@ defmodule DagonWeb.Endpoint do
gzip: false,
only: ~w(css fonts images js favicon.ico robots.txt)
plug Plug.Static,
at: "/uploads",
from: Path.expand("./uploads"),
gzip: false
# Code reloading can be explicitly enabled under the
# :code_reloader configuration of your endpoint.
if code_reloading? do

16
lib/dagon_web/templates/album/form.html.eex

@ -5,18 +5,22 @@
</div>
<% end %>
<%= label f, :name %>
<%= text_input f, :name %>
<%= label f, :name, class: "label" %>
<%= text_input f, :name, class: "input" %>
<%= error_tag f, :name %>
<%= label f, :slug %>
<%= text_input f, :slug %>
<%= label f, :slug, class: "label" %>
<%= text_input f, :slug, class: "input" %>
<%= error_tag f, :slug %>
<%= label f, :mbid %>
<%= text_input f, :mbid %>
<%= label f, :mbid, class: "label" %>
<%= text_input f, :mbid, class: "input" %>
<%= error_tag f, :mbid %>
<%= label f, :discogs_id, class: "label" %>
<%= text_input f, :discogs_id, class: "input" %>
<%= error_tag f, :discogs_id %>
<div>
<%= submit "Save" %>
</div>

41
lib/dagon_web/templates/album/index.html.eex

@ -1,30 +1,11 @@
<h1>Listing Albums</h1>
<table>
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th>Mbid</th>
<th></th>
</tr>
</thead>
<tbody>
<%= for album <- @albums do %>
<tr>
<td><%= album.name %></td>
<td><%= album.slug %></td>
<td><%= album.mbid %></td>
<td>
<span><%= link "Show", to: Routes.album_path(@conn, :show, album) %></span>
<span><%= link "Edit", to: Routes.album_path(@conn, :edit, album) %></span>
<span><%= link "Delete", to: Routes.album_path(@conn, :delete, album), method: :delete, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= link "New Album", to: Routes.album_path(@conn, :new) %></span>
<h1 class="title">Listing Albums</h1>
<div class="columns is-multiline">
<%= for album <- @albums do %>
<div class="column is-1">
<figure class="image is-96x96">
<img src="<%= Dagon.Listens.Albums.Uploader.url({album.image, album}, :thumb) %>">
</figure>
</div>
<% end %>
</div>

16
lib/dagon_web/templates/artist/index.html.eex

@ -1,10 +1,12 @@
<h1>Listing Artists</h1>
<h1 class="title">Listing Artists</h1>
<table>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Slug</th>
<th>Albums #</th>
<th>Tracks #</th>
<th>Listens #</th>
<th></th>
</tr>
@ -13,7 +15,9 @@
<%= for artist <- @artists do %>
<tr>
<td><%= artist.name %></td>
<td><%= artist.slug %></td>
<td><%= Enum.count(artist.albums) %></td>
<td><%= Enum.count(artist.tracks) %></td>
<td><%= Enum.count(artist.listens) %></td>
<td>
<span><%= link "Show", to: Routes.artist_path(@conn, :show, artist) %></span>
@ -23,6 +27,4 @@
</tr>
<% end %>
</tbody>
</table>
<span><%= link "New Artist", to: Routes.artist_path(@conn, :new) %></span>
</table>

32
lib/dagon_web/templates/artist/show.html.eex

@ -1,18 +1,14 @@
<h1>Show Artist</h1>
<ul>
<li>
<strong>Name:</strong>
<%= @artist.name %>
</li>
<li>
<strong>Slug:</strong>
<%= @artist.slug %>
</li>
</ul>
<span><%= link "Edit", to: Routes.artist_path(@conn, :edit, @artist) %></span>
<span><%= link "Back", to: Routes.artist_path(@conn, :index) %></span>
<h1 class="title"><%= @artist.name %></h1>
<div class="listen-chart" style="width: 200px; height: 50px">
<div id="dailychart"
data-dailychart-values='<%= Enum.join(@per_month.listens, ",") %>'
data-dailychart-length="<%= Enum.count(@per_month.listens) %>"></div>
<small class="has-text-grey is-pulled-left"><%= Timex.format!(@per_month.oldest.listened_at, "{YYYY}") %></small>
<small class="has-text-grey is-pulled-right"><%= Timex.format!(@per_month.newest.listened_at, "{YYYY}") %></small>
</div>
<div>
<span><%= link "Edit", to: Routes.artist_path(@conn, :edit, @artist) %></span>
<span><%= link "Back", to: Routes.artist_path(@conn, :index) %></span>
</div>

43
lib/dagon_web/templates/layout/app.html.eex

@ -10,14 +10,39 @@
<body>
<header>
<section class="container">
<nav role="navigation">
<ul>
<li><a href="https://hexdocs.pm/phoenix/overview.html">Get Started</a></li>
</ul>
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="<%= Routes.page_path(@conn, :index) %>">
<img src="https://bulma.io/images/bulma-logo.png" width="112" height="28">
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="<%= Routes.artist_path(@conn, :index) %>">
Artists
</a>
<a class="navbar-item" href="<%= Routes.album_path(@conn, :index) %>">
Albums
</a>
<a class="navbar-item" href="<%= Routes.track_path(@conn, :index) %>">
Tracks
</a>
<a class="navbar-item" href="<%= Routes.listen_path(@conn, :index) %>">
Listens
</a>
</div>
</div>
</nav>
<a href="http://phoenixframework.org/" class="phx-logo">
<img src="<%= Routes.static_path(@conn, "/images/phoenix.png") %>" alt="Phoenix Framework Logo"/>
</a>
</section>
</header>
<main role="main" class="container">
@ -25,6 +50,10 @@
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<%= render @view_module, @view_template, assigns %>
</main>
<footer class="footer container">
<hr />
<p>Made by Inhji with help from <a href="https://phoenixframework.org/">Phoenix</a>, <a href="https://listenbrainz.org/">Listenbrainz</a> and <a href="https://www.discogs.com">Discogs</a></p>
</footer>
<script type="text/javascript" src="<%= Routes.static_path(@conn, "/js/app.js") %>"></script>
</body>
</html>

25
lib/dagon_web/templates/listen/index.html.eex

@ -1,24 +1,5 @@
<h1>Listing Listens</h1>
<h1 class="title">Listing Listens</h1>
<table>
<thead>
<tr>
<th></th>
</tr>
</thead>
<tbody>
<%= for listen <- @listens do %>
<tr>
<td>
<span><%= link "Show", to: Routes.listen_path(@conn, :show, listen) %></span>
<span><%= link "Edit", to: Routes.listen_path(@conn, :edit, listen) %></span>
<span><%= link "Delete", to: Routes.listen_path(@conn, :delete, listen), method: :delete, data: [confirm: "Are you sure?"] %></span>
</td>
</tr>
<% end %>
</tbody>
</table>
<span><%= link "New Listen", to: Routes.listen_path(@conn, :new) %></span>
<%= render "listen.html", conn: @conn, listen: listen %>
<% end %>

18
lib/dagon_web/templates/listen/listen.html.eex

@ -0,0 +1,18 @@
<article class="media is-dense">
<figure class="media-left">
<a class="image is-64x64" href="<%= Routes.album_path(@conn, :show, @listen.album) %>">
<img src="<%= Dagon.Listens.Albums.Uploader.url({@listen.album.image, @listen.album}, :thumb) %>">
</a>
</figure>
<div class="media-content">
<div class="content">
<p>
<strong><%= link @listen.track.name, to: Routes.track_path(@conn, :show, @listen.track), class: "has-text-dark" %></strong> <small>
<%= link Timex.from_now(@listen.listened_at), to: Routes.listen_path(@conn, :show, @listen), class: "has-text-grey" %>
</small>
<br />
<span>by <%= link @listen.artist.name, to: Routes.artist_path(@conn, :show, @listen.artist), class: "has-text-dark" %></span>
</p>
</div>
</div>
</article>

61
lib/dagon_web/templates/page/index.html.eex

@ -1,35 +1,40 @@
<section class="phx-hero">
<h1><%= gettext "Welcome to %{name}!", name: "Phoenix" %></h1>
<p>A productive web framework that<br/>does not compromise speed or maintainability.</p>
<section class="hero is-primary is-bold has-margin-bottom">
<div class="hero-body">
<h1 class="title"><%= gettext "Welcome to %{name}!", name: "Dagon" %></h1>
<p class="subtitle">A last.fm clone written in Elixir.</p>
</div>
</section>
<section class="row">
<section class="columns">
<article class="column">
<h2>Resources</h2>
<ul>
<li>
<a href="https://hexdocs.pm/phoenix/overview.html">Guides &amp; Docs</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix">Source</a>
</li>
<li>
<a href="https://github.com/phoenixframework/phoenix/blob/v1.4/CHANGELOG.md">v1.4 Changelog</a>
</li>
</ul>
<div class="card">
<div class="card-header">
<h3 class="card-header-title">
Last Listens
</h3>
</div>
<div class="card-content">
<ul>
<%= for listen <- @listens do %>
<%= render DagonWeb.ListenView, "listen.html", conn: @conn, listen: listen %>
<%end %>
</ul>
</div>
</div>
</article>
<article class="column">
<h2>Help</h2>
<ul>
<li>
<a href="https://elixirforum.com/c/phoenix-forum">Forum</a>
</li>
<li>
<a href="https://webchat.freenode.net/?channels=elixir-lang">#elixir-lang on Freenode IRC</a>
</li>
<li>
<a href="https://twitter.com/elixirphoenix">Twitter @elixirphoenix</a>
</li>
</ul>
<div class="card">
<div class="card-header">
<h3 class="card-header-title">
Popular Tracks
</h3>
</div>
<div class="card-content">
lol
</div>
</div>
</article>
</section>

12
lib/dagon_web/templates/track/index.html.eex

@ -1,9 +1,11 @@
<h1>Listing Tracks</h1>
<h1 class="title">Listing Tracks</h1>
<table>
<table class="table">
<thead>
<tr>
<th>Name</th>
<th>Artist</th>
<th>Listens</th>
<th></th>
</tr>
@ -12,6 +14,8 @@
<%= for track <- @tracks do %>
<tr>
<td><%= track.name %></td>
<td><%= track.artist.name %></td>
<td><%= Enum.count(track.listens) %></td>
<td>
<span><%= link "Show", to: Routes.track_path(@conn, :show, track) %></span>
@ -21,6 +25,4 @@
</tr>
<% end %>
</tbody>
</table>
<span><%= link "New Track", to: Routes.track_path(@conn, :new) %></span>
</table>

5
mix.exs

@ -33,6 +33,8 @@ defmodule Dagon.MixProject do
# Type `mix help deps` for examples and options.
defp deps do
[
{:arc, "~> 0.11.0"},
{:arc_ecto, "~> 0.11.2"},
{:ecto_sql, "~> 3.1"},
{:gettext, "~> 0.11"},
{:httpoison, "~> 1.6"},
@ -44,7 +46,8 @@ defmodule Dagon.MixProject do
{:phoenix_pubsub, "~> 1.1"},
{:plug_cowboy, "~> 2.0"},
{:postgrex, ">= 0.0.0"},
{:slugger, "~> 0.3.0"}
{:slugger, "~> 0.3.0"},
{:timex, "~> 3.6"}
]
end

5
mix.lock

@ -1,5 +1,8 @@
%{
"arc": {:hex, :arc, "0.11.0", "ac7a0cc03035317b6fef9fe94c97d7d9bd183a3e7ce1606aa0c175cfa8d1ba6d", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:ex_aws_s3, "~> 2.0", [hex: :ex_aws_s3, repo: "hexpm", optional: true]}, {:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}, {:poison, "~> 2.2 or ~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm"},
"arc_ecto": {:hex, :arc_ecto, "0.11.2", "bd9b0c78ec7e09749c47e7e57a52076b5e0c3b9fd19be55f043b3445690ad95b", [:mix], [{:arc, "~> 0.11.0", [hex: :arc, repo: "hexpm", optional: false]}, {:ecto, ">= 2.1.0", [hex: :ecto, repo: "hexpm", optional: false]}], "hexpm"},
"certifi": {:hex, :certifi, "2.5.1", "867ce347f7c7d78563450a18a6a28a8090331e77fa02380b4a21962a65d36ee5", [:rebar3], [{:parse_trans, "~>3.3", [hex: :parse_trans, repo: "hexpm", optional: false]}], "hexpm"},
"combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm"},
"connection": {:hex, :connection, "1.0.4", "a1cae72211f0eef17705aaededacac3eb30e6625b04a6117c1b2db6ace7d5976", [:mix], [], "hexpm"},
"cowboy": {:hex, :cowboy, "2.7.0", "91ed100138a764355f43316b1d23d7ff6bdb0de4ea618cb5d8677c93a7a2f115", [:rebar3], [{:cowlib, "~> 2.8.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "~> 1.7.1", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm"},
"cowlib": {:hex, :cowlib, "2.8.0", "fd0ff1787db84ac415b8211573e9a30a3ebe71b5cbff7f720089972b2319c8a4", [:rebar3], [], "hexpm"},
@ -30,5 +33,7 @@
"slugger": {:hex, :slugger, "0.3.0", "efc667ab99eee19a48913ccf3d038b1fb9f165fa4fbf093be898b8099e61b6ed", [:mix], [], "hexpm"},
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.5", "6eaf7ad16cb568bb01753dbbd7a95ff8b91c7979482b95f38443fe2c8852a79b", [:make, :mix, :rebar3], [], "hexpm"},
"telemetry": {:hex, :telemetry, "0.4.0", "8339bee3fa8b91cb84d14c2935f8ecf399ccd87301ad6da6b71c09553834b2ab", [:rebar3], [], "hexpm"},
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm"},
"tzdata": {:hex, :tzdata, "1.0.2", "6c4242c93332b8590a7979eaf5e11e77d971e579805c44931207e32aa6ad3db1", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.4.1", "d869e4c68901dd9531385bb0c8c40444ebf624e60b6962d95952775cac5e90cd", [:rebar3], [], "hexpm"},
}

2
priv/repo/migrations/20191023205308_create_albums.exs

@ -5,9 +5,11 @@ defmodule Dagon.Repo.Migrations.CreateAlbums do
create table(:albums) do
add :name, :string
add :slug, :string
add :image, :string
add :mbid, :string
add :msid, :string
add :discogs_id, :integer
add :artist_id, references(:artists, on_delete: :nothing)

Loading…
Cancel
Save