Browse Source

automatic cover loading with discogs api and then some

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

+ 2
- 0
.gitignore View File

@@ -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
- 0
.iex.exs View File

@@ -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


+ 0
- 3
assets/css/app.css View File

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

@import "./phoenix.css";

+ 15
- 0
assets/css/app.scss View File

@@ -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;
}

+ 0
- 134
assets/css/phoenix.css
File diff suppressed because it is too large
View File


+ 12
- 1
assets/js/app.js View File

@@ -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
});
})


+ 1687
- 3531
assets/package-lock.json
File diff suppressed because it is too large
View File


+ 6
- 2
assets/package.json View File

@@ -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 View File

Before After
Width: 174  |  Height: 174  |  Size: 3.6 KiB

BIN
assets/static/images/phoenix.png View File

Before After
Width: 728  |  Height: 99  |  Size: 14 KiB

+ 14
- 0
assets/webpack.config.js View File

@@ -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
- 0
config/config.exs View File

@@ -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"

+ 2
- 1
lib/dagon/application.ex View File

@@ -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


+ 19
- 1
lib/dagon/listens/albums.ex View File

@@ -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 """


+ 5
- 1
lib/dagon/listens/albums/album.ex View File

@@ -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
- 0
lib/dagon/listens/albums/uploader.ex View File

@@ -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

+ 4
- 1
lib/dagon/listens/artists.ex View File

@@ -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 """


+ 44
- 1
lib/dagon/listens/listens.ex View File

@@ -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
- 0
lib/dagon/listens/sparkline.ex View File

@@ -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

+ 4
- 1
lib/dagon/listens/tracks.ex View File

@@ -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
- 0
lib/dagon/listens/workers/discogs_worker.ex View File

@@ -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

+ 17
- 18
lib/dagon/listens/workers/listenbrainz_worker.ex View File

@@ -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


+ 1
- 1
lib/dagon_web/controllers/album_controller.ex View File

@@ -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



+ 14
- 1
lib/dagon_web/controllers/artist_controller.ex View File

@@ -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


+ 3
- 1
lib/dagon_web/controllers/page_controller.ex View File

@@ -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
- 0
lib/dagon_web/endpoint.ex View File

@@ -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


+ 10
- 6
lib/dagon_web/templates/album/form.html.eex View File

@@ -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>


+ 11
- 30
lib/dagon_web/templates/album/index.html.eex View File

@@ -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>

+ 9
- 7
lib/dagon_web/templates/artist/index.html.eex View File

@@ -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>

+ 14
- 18
lib/dagon_web/templates/artist/show.html.eex View File

@@ -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>

+ 36
- 7
lib/dagon_web/templates/layout/app.html.eex View File

@@ -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>

+ 3
- 22
lib/dagon_web/templates/listen/index.html.eex View File

@@ -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
- 0
lib/dagon_web/templates/listen/listen.html.eex View File

@@ -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>

+ 33
- 28
lib/dagon_web/templates/page/index.html.eex View File

@@ -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>

+ 7
- 5
lib/dagon_web/templates/track/index.html.eex View File

@@ -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>

+ 4
- 1
mix.exs View File

@@ -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
- 0
mix.lock View File

@@ -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
- 0
priv/repo/migrations/20191023205308_create_albums.exs View File

@@ -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