Browse Source

version 0.1.0

master
Inhji Y. 2 years ago
parent
commit
edabc2b237
  1. 10
      assets/css/app.scss
  2. 16
      assets/js/app.js
  3. 5
      assets/package-lock.json
  4. 1
      assets/package.json
  5. 5
      lib/dagon/application.ex
  6. 2
      lib/dagon/listens.ex
  7. 31
      lib/dagon/listens/albums.ex
  8. 5
      lib/dagon/listens/albums/album.ex
  9. 23
      lib/dagon/listens/artists.ex
  10. 3
      lib/dagon/listens/artists/artist.ex
  11. 16
      lib/dagon/listens/listens.ex
  12. 14
      lib/dagon/listens/tracks.ex
  13. 109
      lib/dagon/listens/workers/coverart_album_worker.ex
  14. 31
      lib/dagon/listens/workers/discogs_album_worker.ex
  15. 7
      lib/dagon/listens/workers/discogs_artist_worker.ex
  16. 74
      lib/dagon/listens/workers/listenbrainz_now_worker.ex
  17. 85
      lib/dagon/listens/workers/listenbrainz_worker.ex
  18. 10
      lib/dagon_web/controllers/album_controller.ex
  19. 35
      lib/dagon_web/controllers/dashboard_controller.ex
  20. 10
      lib/dagon_web/templates/album/edit.html.eex
  21. 12
      lib/dagon_web/templates/album/form.html.eex
  22. 31
      lib/dagon_web/templates/album/index.html.eex
  23. 19
      lib/dagon_web/templates/album/show.html.eex
  24. 14
      lib/dagon_web/templates/artist/form.html.eex
  25. 7
      lib/dagon_web/templates/artist/show.html.eex
  26. 49
      lib/dagon_web/templates/dashboard/index.html.eex
  27. 14
      lib/dagon_web/templates/layout/navigation.html.eex
  28. 4
      lib/dagon_web/templates/listen/listen.html.eex
  29. 6
      lib/dagon_web/templates/listen/show.html.eex
  30. 24
      lib/dagon_web/templates/track/show.html.eex
  31. 25
      lib/dagon_web/templates/worker/index.html.eex
  32. 4
      lib/dagon_web/views/listen_view.ex
  33. 0
      navigation.html.exs
  34. 3
      priv/repo/migrations/20191023181552_create_artists.exs
  35. 1
      priv/repo/migrations/20191023205308_create_albums.exs
  36. 10
      priv/repo/migrations/20191109200616_add_image_to_artist.exs

10
assets/css/app.scss

@ -1,16 +1,18 @@
@charset "utf-8";
// Import a Google Font
@import url('https://fonts.googleapis.com/css?family=Nunito:400,700');
@import url('https://fonts.googleapis.com/css?family=Open+Sans:400,700');
$family-sans-serif: "Nunito", sans-serif;
@import "~bulma/sass/utilities/_all";
$pinkyred: #dd3344;
$family-sans-serif: "Open Sans", sans-serif;
$dimensions: 16 24 32 48 64 96 128 196;
$spacing-default: 1.5rem;
$body-background-color: #dd3344;
$body-background-color: $grey-dark;
@import "~bulma/sass/utilities/_all";
@import "~bulma/sass/base/_all";
@import "~bulma/sass/elements/_all";
@import "~bulma/sass/components/_all";

16
assets/js/app.js

@ -10,19 +10,27 @@ import css from "../css/app.scss"
// Import dependencies
//
import "phoenix_html"
import * as eva from 'eva-icons'
import Dailychart from "dailychart"
// Import local files
//
// 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: 30,
width: 200
});
colorPositive: '#dd3344',
colorNegative: '#46c9f1'
})
eva.replace({
fill: "#ffffff",
width: 20,
height: 20,
class: ""
})
})

5
assets/package-lock.json

@ -2672,6 +2672,11 @@
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
"dev": true
},
"eva-icons": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/eva-icons/-/eva-icons-1.1.2.tgz",
"integrity": "sha512-R5iD6nYcQS/qQOfC1ooP1hs8Xg+VKYfO95SXDzVWFUwYdxXrJYc7+ZnbE7tsl1amFjbRMm+c/vnH4NVaB2zrUQ=="
},
"events": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.0.0.tgz",

1
assets/package.json

@ -8,6 +8,7 @@
"dependencies": {
"bulma": "^0.8.0",
"dailychart": "^1.3.1",
"eva-icons": "^1.1.2",
"phoenix": "file:../deps/phoenix",
"phoenix_html": "file:../deps/phoenix_html"
},

5
lib/dagon/application.ex

@ -16,8 +16,11 @@ defmodule Dagon.Application do
{Dagon.Listens.Workers.ListenbrainzWorker, %{}},
{Dagon.Listens.Workers.DiscogsAlbumWorker, %{}},
{Dagon.Listens.Workers.DiscogsArtistWorker, %{}},
{Dagon.Listens.Workers.CoverartAlbumWorker, %{}},
{Dagon.Listens.Workers.ListenbrainzNowWorker, %{username: "inhji"}},
:hackney_pool.child_spec(:discogs_pool, timeout: 500, max_connections: 100),
:hackney_pool.child_spec(:listenbrainz_pool, timeout: 500, max_connections: 100)
:hackney_pool.child_spec(:listenbrainz_pool, timeout: 500, max_connections: 100),
:hackney_pool.child_spec(:coverartarchive_pool, timeout: 500, max_connections: 100)
]
# See https://hexdocs.pm/elixir/Supervisor.html

2
lib/dagon/listens.ex

@ -0,0 +1,2 @@
defmodule Dagon.Listens do
end

31
lib/dagon/listens/albums.ex

@ -34,6 +34,15 @@ defmodule Dagon.Listens.Albums do
|> Repo.preload(:artist)
end
def list_albums_without_cover_and_mbid(opts \\ []) do
Album
|> where([a], is_nil(a.image))
|> where([a], not is_nil(a.mbid))
|> limit(10)
|> Repo.all(opts)
|> Repo.preload(:artist)
end
def list_albums_with_cover do
Album
|> where([a], not is_nil(a.image))
@ -80,6 +89,28 @@ defmodule Dagon.Listens.Albums do
)
end
def list_albums_by_listens_by_time(limit, time_diff) do
time_ago = Timex.shift(DateTime.utc_now(), time_diff)
sq =
from a in Album,
left_join: l in assoc(a, :listens),
group_by: a.id,
order_by: [desc: count(a.id)],
where: l.listened_at > ^time_ago,
select: %{id: a.id, listens: count(a.id)},
limit: ^limit
Repo.all(
from a in Album,
join: s in subquery(sq),
on: a.id == s.id,
select: %{album: a, listens: s.listens},
order_by: [desc: s.listens],
limit: ^limit
)
end
@doc """
Gets a single album.

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

@ -5,7 +5,6 @@ defmodule Dagon.Listens.Albums.Album do
schema "albums" do
field :name, :string
field :slug, :string
field :image, Dagon.Listens.Albums.Uploader.Type
field :mbid, :string
@ -22,8 +21,8 @@ defmodule Dagon.Listens.Albums.Album do
@doc false
def changeset(album, attrs) do
album
|> cast(attrs, [:name, :slug, :mbid, :msid, :artist_id, :discogs_id])
|> cast(attrs, [:name, :mbid, :msid, :artist_id, :discogs_id])
|> cast_attachments(attrs, [:image], allow_paths: true)
|> validate_required([:name, :slug, :msid, :artist_id])
|> validate_required([:name, :msid, :artist_id])
end
end

23
lib/dagon/listens/artists.ex

@ -52,6 +52,29 @@ defmodule Dagon.Listens.Artists do
)
end
def list_artists_by_listens_by_time(limit, time_diff) do
time_ago = Timex.shift(DateTime.utc_now(), time_diff)
sq =
from a in Artist,
left_join: l in assoc(a, :listens),
group_by: a.id,
order_by: [desc: count(a.id)],
where: l.listened_at > ^time_ago,
select: %{id: a.id, listens: count(a.id)},
limit: ^limit
Repo.all(
from a in Artist,
join: s in subquery(sq),
on: a.id == s.id,
select: %{artist: a, listens: s.listens},
order_by: [desc: s.listens],
preload: [:albums],
limit: ^limit
)
end
@doc """
Gets a single artist.

3
lib/dagon/listens/artists/artist.ex

@ -5,7 +5,6 @@ defmodule Dagon.Listens.Artists.Artist do
schema "artists" do
field :name, :string
field :slug, :string
field :image, Dagon.Listens.Artists.Uploader.Type
field :msid, :string
@ -22,7 +21,7 @@ defmodule Dagon.Listens.Artists.Artist do
@doc false
def changeset(artist, attrs) do
artist
|> cast(attrs, [:name, :slug, :msid, :mbid, :discogs_id])
|> cast(attrs, [:name, :msid, :mbid, :discogs_id])
|> cast_attachments(attrs, [:image], allow_paths: true)
|> validate_required([:name, :msid])
end

16
lib/dagon/listens/listens.ex

@ -47,20 +47,26 @@ defmodule Dagon.Listens.Listens do
|> Repo.preload(@preloads)
end
def listens_per_month_by_artist(artist) do
defp listens_per_month_query 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))
end
def listens_per_month() do
listens_per_month_query()
|> Repo.all()
end
def listens_per_month_by_artist(artist) do
listens_per_month_query()
|> where([l], l.artist_id == ^artist.id)
|> Repo.all()
end
def listens_per_month_by_album(album) 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))
listens_per_month_query()
|> where([l], l.album_id == ^album.id)
|> Repo.all()
end

14
lib/dagon/listens/tracks.ex

@ -38,7 +38,19 @@ defmodule Dagon.Listens.Tracks do
** (Ecto.NoResultsError)
"""
def get_track!(id), do: Repo.get!(Track, id) |> Repo.preload([:album, :artist, :listens])
def get_track!(id),
do:
Repo.get!(Track, id)
|> Repo.preload([:album, :artist, :listens])
def get_track_by_info(name, artist) do
Track
|> join(:left, [t], ar in Dagon.Listens.Artists.Artist, on: ar.id == t.artist_id)
|> where([t, ar, al], t.name == ^name)
|> where([t, ar, al], ar.name == ^artist)
|> Repo.all()
|> Repo.preload([:album, :artist])
end
@doc """
Creates a track.

109
lib/dagon/listens/workers/coverart_album_worker.ex

@ -0,0 +1,109 @@
defmodule Dagon.Listens.Workers.CoverartAlbumWorker do
use GenServer
require Logger
alias Dagon.Repo
alias Dagon.Listens.Albums
alias Dagon.Listens.Albums.Album
@name "Coverartarchive Album Worker"
@fetch_interval 30 * 1_000
def start_link(args) do
Logger.info("Starting #{@name}..")
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def fetch() do
GenServer.call(__MODULE__, :fetch, 30000)
end
def update_fetch_interval(interval_in_seconds) do
GenServer.cast(__MODULE__, {:update_fetch_interval, interval_in_seconds})
end
def init(_state) do
state = %{
rate_limit: %{
total: -1,
remaining: -1
},
fetch_interval: @fetch_interval,
updated_at: DateTime.utc_now()
}
schedule_fetch(state, 10_000)
{:ok, state}
end
def get_state() do
GenServer.call(__MODULE__, :get_state)
end
def handle_call(:get_state, _from, state) do
{:reply, state, state}
end
def handle_call(:fetch, _from, state) do
new_state = do_fetch(state)
{:reply, :ok, new_state}
end
def handle_info(:fetch, state) do
new_state = do_fetch(state)
{:noreply, new_state}
end
def handle_info({:ssl_closed, _}, state) do
Logger.error("Temporary TLS error")
{:noreply, state}
end
def handle_cast({:update_fetch_interval, interval_in_seconds}, state) do
Logger.info("Updating fetch_interval to #{interval_in_seconds}s for #{@name}")
new_state = Map.replace!(state, :fetch_interval, interval_in_seconds * 1000)
{:noreply, new_state}
end
def schedule_fetch(state) do
Logger.info("Scheduling #{@name}")
Process.send_after(self(), :fetch, state.fetch_interval)
end
def schedule_fetch(_state, wait_time) do
Logger.info("Scheduling #{@name}")
Process.send_after(self(), :fetch, wait_time)
end
def do_fetch(state) do
albums = Albums.list_albums_without_cover_and_mbid(log: false)
state =
Enum.reduce(albums, state, fn album, state ->
fetch_album_cover(state, album)
end)
schedule_fetch(state)
state
end
def fetch_album_cover(state, album) do
url = "https://coverartarchive.org/release/#{album.mbid}/front"
state =
case HTTPoison.get!(url, [{"User-Agent", Dagon.user_agent()}],
hackney: [pool: :coverartarchive_pool]
) do
%HTTPoison.Response{status_code: 307} ->
{:ok, url}
Dagon.Listens.Albums.update_album(album, %{image: url})
state
_ ->
state
end
state = Map.put(state, :updated_at, DateTime.utc_now())
state
end
end

31
lib/dagon/listens/workers/discogs_album_worker.ex

@ -7,8 +7,7 @@ defmodule Dagon.Listens.Workers.DiscogsAlbumWorker do
alias Dagon.Listens.Albums.Album
@name "Discogs Album Worker"
# @fetch_interval 2 * 60 * 1_000
@fetch_interval 10 * 1_000
@fetch_interval 30 * 1_000
@base_url "https://api.discogs.com/database/search"
@token "kRIDCYTMRucJojWzQKlXlDAnDlQSgmXboMEZiUBT"
@invalid_discogs_id -1
@ -140,34 +139,6 @@ defmodule Dagon.Listens.Workers.DiscogsAlbumWorker do
})
|> Repo.update(log: false)
end
# data =
# body
# |> Jason.decode!(keys: :atoms)
# |> Map.get(:results)
# |> List.first()
# if not is_nil(data) and not String.ends_with?(data.cover_image, "spacer.gif") do
# discogs_id = data.id
# image = data.cover_image
# Logger.info("Saving image for album '#{album.name}'.")
# album
# |> Album.changeset(%{
# image: image,
# discogs_id: discogs_id
# })
# |> Repo.update(log: false)
# else
# Logger.warn("No data found for '#{album.name}'. Marking album cover as invalid.")
# album
# |> Album.changeset(%{
# discogs_id: @invalid_discogs_id
# })
# |> Repo.update(log: false)
# end
end
def get_url(album) do

7
lib/dagon/listens/workers/discogs_artist_worker.ex

@ -6,15 +6,14 @@ defmodule Dagon.Listens.Workers.DiscogsArtistWorker do
alias Dagon.Listens.Artists
alias Dagon.Listens.Artists.Artist
@name "Discogs Album Worker"
# @fetch_interval 2 * 60 * 1_000
@fetch_interval 10 * 1_000
@name "Discogs Artist Worker"
@fetch_interval 30 * 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..")
Logger.info("Starting #{@name}..")
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end

74
lib/dagon/listens/workers/listenbrainz_now_worker.ex

@ -0,0 +1,74 @@
defmodule Dagon.Listens.Workers.ListenbrainzNowWorker do
use GenServer
require Logger
# 10 Minutes
@fetch_interval 60 * 1000
def start_link(opts) do
args = %{
user: opts.username,
now: nil
}
GenServer.start_link(__MODULE__, args, name: __MODULE__)
end
def get_now() do
GenServer.call(__MODULE__, :now)
end
def init(state) do
schedule_listen_fetch(1_000)
{:ok, state}
end
def handle_info(:fetch, state) do
listen = do_fetch(state)
new_state =
case listen do
nil ->
state
listen ->
track_name = listen.track_metadata.track_name
artist_name = listen.track_metadata.artist_name
tracks = Dagon.Listens.Tracks.get_track_by_info(track_name, artist_name)
track =
cond do
Enum.count(tracks) == 0 -> nil
true -> List.first(tracks)
end
%{state | now: track}
end
schedule_listen_fetch(@fetch_interval)
{:noreply, new_state}
end
def handle_call(:now, _from, state) do
{:reply, state[:now], state}
end
defp schedule_listen_fetch(interval) do
Process.send_after(self(), :fetch, interval)
end
def do_fetch(%{:user => user}) do
case HTTPoison.get!("https://api.listenbrainz.org/1/user/#{user}/playing-now") do
%HTTPoison.Response{body: body} ->
body
|> Jason.decode!(keys: :atoms)
|> Map.get(:payload)
|> Map.get(:listens)
|> List.first()
_ ->
nil
end
end
end

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

@ -9,10 +9,9 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
alias Dagon.Listens.Tracks.Track
alias Dagon.Listens.Listens.Listen
# 2 Minutes
# @fetch_interval 2 * 60 * 1_000
@fetch_interval 10 * 1_000
@fetch_interval 30 * 1_000
@base_url "https://api.listenbrainz.org/1/user"
@listen_count 100
@msid_missing "MISSING"
@ -40,6 +39,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
remaining: -1
},
fetch_interval: @fetch_interval,
last_timestamp: 0,
updated_at: DateTime.utc_now()
}
@ -83,9 +83,9 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
end
def do_fetch(state) do
last_ts = last_listen_timestamp()
last_ts = last_listen_timestamp(state)
user = "inhji"
url = "#{@base_url}/#{user}/listens?min_ts=#{last_ts}"
url = "#{@base_url}/#{user}/listens?min_ts=#{last_ts}&count=#{@listen_count}"
Logger.info("Fetching new Listens for Timestamp #{last_ts}:")
Logger.info(url)
@ -98,10 +98,13 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
state =
case response do
%HTTPoison.Response{body: body, headers: headers} ->
handle_fetch_response(body)
newest_timestamp = handle_fetch_response(state, body)
rate_limit = Dagon.Listens.RateLimit.calculate(headers, "LB")
Map.put(state, :rate_limit, rate_limit)
state =
state
|> Map.put(:rate_limit, rate_limit)
|> Map.put(:last_timestamp, newest_timestamp)
_ ->
state
@ -113,27 +116,44 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
state
end
def handle_fetch_response(body) do
body
|> Jason.decode!(keys: :atoms)
|> Map.get(:payload)
|> Map.get(:listens)
def handle_fetch_response(state, body) do
listens =
body
|> Jason.decode!(keys: :atoms)
|> Map.get(:payload)
|> Map.get(:listens)
listens
|> prepare_listens()
|> Enum.filter(fn l -> !is_nil(l) end)
|> Enum.each(fn changeset ->
Repo.insert(changeset, log: false)
end)
end
def last_listen_timestamp do
query =
from l in Dagon.Listens.Listens.Listen,
order_by: [desc: l.listened_at],
limit: 1
case Enum.empty?(listens) do
true ->
state.last_timestamp
false ->
listens
|> List.last()
|> Map.get(:listened_at)
end
end
case Repo.one(query, log: false) do
nil -> 1
listen -> DateTime.to_unix(listen.listened_at)
def last_listen_timestamp(state) do
if state.last_timestamp > 0 do
state.last_timestamp
else
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
end
end
@ -148,7 +168,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
with {:ok, artist} <- maybe_create_artist(meta.artist_name, info.artist_msid),
{:ok, album} <- maybe_create_album(meta.release_name, info.release_msid, artist),
{:ok, track} <- maybe_create_track(meta.track_name, artist, album) do
Logger.info("Creating new Listen: #{meta.track_name} - #{meta.artist_name}")
Logger.info("[Listen] #{meta.track_name}")
Listen.changeset(%Listen{}, %{
track: listen.track_metadata.track_name,
@ -170,19 +190,18 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
def maybe_create_artist(nil, messybrainz_id) do
Logger.warn("Artist name was nil, skipping.")
maybe_create_artist("Unknown", messybrainz_id)
{:warn, :artist_name_nil}
end
def maybe_create_artist(name, messybrainz_id) do
artist =
case Repo.get_by(Artist, [msid: messybrainz_id], log: false) do
nil ->
Logger.info("Creating new Artist #{name}")
Logger.info("[Artist] #{name}")
%Artist{}
|> Artist.changeset(%{
name: name,
slug: slugify(name),
msid: messybrainz_id || @msid_missing
})
|> Repo.insert!(log: false)
@ -194,28 +213,20 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
{:ok, artist}
end
def slugify(string) do
case Slugger.slugify_downcase(string) do
"" -> Ecto.UUID.generate()
slug -> slug
end
end
def maybe_create_album(nil, messybrainz_id, artist) do
Logger.warn("Album name was nil, skipping.")
maybe_create_album("Unknown", messybrainz_id, artist)
{:warn, :album_name_nil}
end
def maybe_create_album(name, messybrainz_id, artist) do
album =
case Repo.get_by(Album, [name: name], log: false) do
case Repo.get_by(Album, [msid: messybrainz_id], log: false) do
nil ->
Logger.info("Creating new Album #{name}")
Logger.info("[Album] #{name}")
%Album{}
|> Album.changeset(%{
name: name,
slug: slugify(name),
msid: messybrainz_id || @msid_missing,
artist_id: artist.id
})
@ -237,7 +248,7 @@ defmodule Dagon.Listens.Workers.ListenbrainzWorker do
track =
case track do
nil ->
Logger.info("Creating new Track #{name}")
Logger.info("[Track] #{name}")
%Track{}
|> Track.changeset(%{

10
lib/dagon_web/controllers/album_controller.ex

@ -6,8 +6,14 @@ defmodule DagonWeb.AlbumController do
alias Dagon.Listens.Albums.Album
def index(conn, _params) do
albums = Albums.list_albums()
render(conn, "index.html", albums: albums)
grouped_albums =
Albums.list_albums_with_cover()
|> Enum.group_by(fn album ->
String.first(album.name)
end)
|> Enum.sort()
render(conn, "index.html", albums: grouped_albums)
end
def new(conn, _params) do

35
lib/dagon_web/controllers/dashboard_controller.ex

@ -2,14 +2,41 @@ defmodule DagonWeb.DashboardController do
use DagonWeb, :controller
def index(conn, _params) do
last_listens = Dagon.Listens.Listens.list_listens(10)
top_artists = Dagon.Listens.Artists.list_artists_by_listens(9)
top_albums = Dagon.Listens.Albums.list_albums_by_listens(9)
last_listens = Dagon.Listens.Listens.list_listens(8)
top_artists = Dagon.Listens.Artists.list_artists_by_listens_by_time(9, weeks: -1)
top_albums = Dagon.Listens.Albums.list_albums_by_listens_by_time(9, weeks: -1)
listens_per_month = Dagon.Listens.Listens.listens_per_month()
average_listens =
listens_per_month
|> Enum.reduce(0, fn [listens, _], sum -> sum + listens end)
|> Integer.floor_div(Enum.count(listens_per_month))
IO.inspect(average_listens)
oldest = Dagon.Listens.Listens.get_oldest_listen()
newest = Dagon.Listens.Listens.get_newest_listen()
listens_per_month = Dagon.Listens.Sparkline.create(listens_per_month, oldest, newest)
listen_count = Dagon.Listens.Listens.count_listens()
current_track = Dagon.Listens.Workers.ListenbrainzNowWorker.get_now()
per_month = %{
listens: listens_per_month,
oldest: oldest,
newest: newest
}
IO.inspect(listens_per_month)
render(conn, "index.html",
listens: last_listens,
artists: top_artists,
albums: top_albums
albums: top_albums,
per_month: per_month,
count: listen_count,
current_track: current_track,
average_listens: average_listens
)
end
end

10
lib/dagon_web/templates/album/edit.html.eex

@ -1,5 +1,5 @@
<h1>Edit Album</h1>
<%= render "form.html", Map.put(assigns, :action, Routes.album_path(@conn, :update, @album)) %>
<span><%= link "Back", to: Routes.album_path(@conn, :index) %></span>
<div class="card has-margin-top">
<div class="card-content">
<%= render "form.html", Map.put(assigns, :action, Routes.album_path(@conn, :update, @album)) %>
</div>
</div>

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

@ -9,19 +9,19 @@
<%= text_input f, :name, class: "input" %>
<%= error_tag f, :name %>
<%= label f, :slug, class: "label" %>
<%= text_input f, :slug, class: "input" %>
<%= error_tag f, :slug %>
<%= label f, :mbid, class: "label" %>
<%= label f, "Musicbrainz Id", class: "label" %>
<%= text_input f, :mbid, class: "input" %>
<%= error_tag f, :mbid %>
<%= label f, "Messybrainz Id", class: "label" %>
<%= text_input f, :msid, class: "input" %>
<%= error_tag f, :msid %>
<%= label f, :discogs_id, class: "label" %>
<%= text_input f, :discogs_id, class: "input" %>
<%= error_tag f, :discogs_id %>
<div>
<%= submit "Save" %>
<%= submit "Save", class: "button" %>
</div>
<% end %>

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

@ -1,13 +1,20 @@
<h1 class="title">Listing Albums</h1>
<div class="card has-margin-top">
<div class="card-content">
<%= for {letter, albums} <- @albums do %>
<%= letter %>
<div class="columns is-multiline">
<%= for album <- @albums do %>
<div class="column is-1">
<a href="<%= Routes.album_path(@conn, :show, album) %>" title="<%= album.name %>">
<figure class="image is-96x96">
<img src="<%= Dagon.Listens.Albums.Uploader.url({album.image, album}, :thumb) %>">
</figure>
</a>
</div>
<% end %>
</div>
<div class="columns is-multiline">
<%= for album <- albums do %>
<div class="column is-1">
<a href="<%= Routes.album_path(@conn, :show, album) %>" title="<%= album.name %>">
<figure class="image is-96x96">
<img src="<%= Dagon.Listens.Albums.Uploader.url({album.image, album}, :thumb) %>">
</figure>
</a>
</div>
<% end %>
</div>
<% end %>
</div>
</div>

19
lib/dagon_web/templates/album/show.html.eex

@ -1,4 +1,4 @@
<div class="card">
<div class="card has-margin-top">
<div class="card-content">
<div class="media">
<div class="media-left">
@ -23,10 +23,13 @@
</div>
</div>
<div class="card albums">
<div class="card tracks has-margin-top">
<div class="card-content">
<%= for item <- @tracks do %>
<%= if item.track.album.image do %>
<% IO.inspect(@tracks) %>
<%= if Enum.empty?(@tracks) do %>
<strong>No Tracks found (yet)</strong>
<% else %>
<%= for item <- @tracks do %>
<div class="media">
<div class="media-left">
<a href="<%= Routes.album_path(@conn, :show, item.track.album) %>">
@ -40,7 +43,13 @@
<div><%= item.listens %> listens</div>
</div>
</div>
<% end %>
<% end %>
<% end %>
</div>
</div>
<div class="card has-margin-top">
<div class="card-content">
<a href="<%= Routes.album_path(@conn, :edit, @album) %>" class="button is-dark">Edit</a>
</div>
</div>

14
lib/dagon_web/templates/artist/form.html.eex

@ -9,9 +9,17 @@
<%= text_input f, :name %>
<%= error_tag f, :name %>
<%= label f, :slug %>
<%= text_input f, :slug %>
<%= error_tag f, :slug %>
<%= label f, :msid %>
<%= text_input f, :msid %>
<%= error_tag f, :msid %>
<%= label f, :mbid %>
<%= text_input f, :mbid %>
<%= error_tag f, :mbid %>
<%= label f, :discogs_id %>
<%= text_input f, :discogs_id %>
<%= error_tag f, :discogs_id %>
<div>
<%= submit "Save" %>

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

@ -47,7 +47,8 @@
</div>
</div>
<div>
<span><%= link "Edit", to: Routes.artist_path(@conn, :edit, @artist) %></span>
<span><%= link "Back", to: Routes.artist_path(@conn, :index) %></span>
<div class="card has-margin-top">
<div class="card-content">
<%= link "Edit", to: Routes.artist_path(@conn, :edit, @artist), class: "button is-dark" %>
</div>
</div>

49
lib/dagon_web/templates/dashboard/index.html.eex

@ -1,19 +1,61 @@
<div class="columns has-margin-top">
<div class="column">
<div class="card">
<div class="card-content">
<%= if @current_track do %>
<div class="media is-pulled-right">
<div class="media-content">
<div class="content">
<p class="has-text-right">
<strong><span class="has-text-danger">Now:</span> <%= @current_track.name %></strong>
<br />
<span>by <%= @current_track.artist.name %></span>
</p>
</div>
</div>
<figure class="media-right">
<a class="image is-64x64" href='<%= Routes.album_path(@conn, :show, @current_track.album) %>'>
<img src='<%= Dagon.Listens.Albums.Uploader.url({@current_track.album.image, @current_track.album}, :thumb) %>'>
</a>
</figure>
</div>
<% end %>
<h2 class="title">Listens</h2>
<h3 class="subtitle"><%= @count %> since <%= Timex.format!(@per_month.oldest.listened_at, "{YYYY}") %></h3>
<div class="listen-chart" style="width: 100%; height: 50px">
<div id="dailychart"
data-dailychart-close="<%= @average_listens %>"
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>
</div>
</div>
</div>
<div class="columns">
<div class="column is-one-third">
<div class="card has-margin-top">
<div class="card">
<div class="card-content">
<h2 class="title">Last Listens</h2>
<%= for listen <- @listens do %>
<%= render DagonWeb.ListenView, "listen.html", conn: @conn, listen: listen %>
<%= render DagonWeb.ListenView, "listen.html", conn: @conn, listen: listen, size: 64 %>
<%end %>
</div>
</div>
</div>
<div class="column">
<div class="card has-margin-top">
<div class="card">
<div class="card-content">
<h2 class="title">Top Artists</h2>
<h3 class="subtitle">Last week</h3>
<div class="columns is-multiline">
<%= for %{artist: artist, listens: listens} <- @artists do %>
@ -43,6 +85,7 @@
<div class="card has-margin-top">
<div class="card-content">
<h2 class="title">Top Albums</h2>
<h3 class="subtitle">Last week</h3>
<div class="columns is-multiline">
<%= for %{album: album, listens: listens} <- @albums do %>

14
lib/dagon_web/templates/layout/navigation.html.eex

@ -2,7 +2,7 @@
<div class="container">
<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">
DAGON
</a>
<a role="button" class="navbar-burger burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
@ -15,19 +15,11 @@
<div id="navbarBasicExample" class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item" href="<%= Routes.dashboard_path(@conn, :index) %>">
Dashboard
</a>
<a class="navbar-item" href="<%= Routes.artist_path(@conn, :index) %>">
Artists
</a>
<a class="navbar-item" href="<%= Routes.album_path(@conn, :index) %>">
Albums
<i data-eva="pie-chart-outline"></i> Dashboard
</a>
<a class="navbar-item" href="<%= Routes.worker_path(@conn, :index) %>">
Workers
<i data-eva="settings-outline"></i> Workers
</a>
</div>
</div>

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

@ -1,6 +1,8 @@
<% size = assigns[:size] || 64 %>
<article class="media is-dense">
<figure class="media-left">
<a class="image is-64x64" href="<%= Routes.album_path(@conn, :show, @listen.album) %>">
<a class="image <%= image_class(size) %>" href="<%= Routes.album_path(@conn, :show, @listen.album) %>">
<img src="<%= Dagon.Listens.Albums.Uploader.url({@listen.album.image, @listen.album}, :thumb) %>">
</a>
</figure>

6
lib/dagon_web/templates/listen/show.html.eex

@ -1 +1,5 @@
<%= render "listen.html", conn: @conn, listen: @listen %>
<div class="card has-margin-top">
<div class="card-content">
<%= render "listen.html", conn: @conn, listen: @listen, size: 196 %>
</div>
</div>

24
lib/dagon_web/templates/track/show.html.eex

@ -1,12 +1,16 @@
<div class="media">
<div class="media-left">
<figure class="image is-196x196">
<img src="<%= Dagon.Listens.Albums.Uploader.url({@track.album.image, @track.album}, :large) %>">
</figure>
</div>
<div class="media-content">
<h1 class="title"><%= @track.name %></h1>
<h2 class="subtitle">by <%= @track.artist.name %></h2>
<div><%= Enum.count(@track.listens) %> Listens</div>
<div class="card has-margin-top">
<div class="card-content">
<div class="media">
<div class="media-left">
<figure class="image is-196x196">
<img src="<%= Dagon.Listens.Albums.Uploader.url({@track.album.image, @track.album}, :large) %>">
</figure>
</div>
<div class="media-content">
<h1 class="title"><%= @track.name %></h1>
<h2 class="subtitle">by <%= @track.artist.name %></h2>
<div><%= Enum.count(@track.listens) %> Listens</div>
</div>
</div>
</div>
</div>

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

@ -1,18 +1,15 @@
<h1 class="title">Worker Settings</h1>
<div class="columns">
<div class="columns has-margin-top">
<div class="column">
<div class="card">
<div class="card-header has-background-primary">
<h3 class="card-header-title has-text-light">
<div class="card-header">
<h3 class="card-header-title">
Discogs Album Worker
</h3>
</div>
<div class="card-content">
<p>
<%= if not is_nil(@state.discogs_album) do %>
<div>Ratelimit Total: <%= @state.discogs_album.rate_limit.total %></div>
<div>Ratelimit Remaining: <%= @state.discogs_album.rate_limit.remaining %></div>
<div>Ratelimit Total: <%= @state.discogs_album.rate_limit.remaining %>/<%= @state.discogs_album.rate_limit.total %></div>
<div>Updated At: <%= Timex.from_now(@state.discogs_album.updated_at) %></div>
<div>Fetch Interval: <%= @state.discogs_album.fetch_interval / 1000 %> seconds</div>
<% end %>
@ -26,16 +23,15 @@
</div>
<div class="column">
<div class="card">
<div class="card-header has-background-primary">
<h3 class="card-header-title has-text-light">
<div class="card-header">
<h3 class="card-header-title">
Discogs Artist Worker
</h3>
</div>
<div class="card-content">
<p>
<%= if not is_nil(@state.discogs_artist) do %>
<div>Ratelimit Total: <%= @state.discogs_artist.rate_limit.total %></div>
<div>Ratelimit Remaining: <%= @state.discogs_artist.rate_limit.remaining %></div>
<div>Ratelimit Total: <%= @state.discogs_artist.rate_limit.remaining %>/<%= @state.discogs_artist.rate_limit.total %></div>
<div>Updated At: <%= Timex.from_now(@state.discogs_artist.updated_at) %></div>
<div>Fetch Interval: <%= @state.discogs_artist.fetch_interval / 1000 %> seconds</div>
<% end %>
@ -49,16 +45,15 @@
</div>
<div class="column">
<div class="card">
<div class="card-header has-background-primary">
<h3 class="card-header-title has-text-light">
<div class="card-header">
<h3 class="card-header-title">
Listenbrainz Worker
</h3>
</div>
<div class="card-content">
<p>
<%= if not is_nil(@state.listenbrainz) do %>
<div>Ratelimit Total: <%= @state.listenbrainz.rate_limit.total %></div>
<div>Ratelimit Remaining: <%= @state.listenbrainz.rate_limit.remaining %></div>
<div>Ratelimit Total: <%= @state.listenbrainz.rate_limit.remaining %>/<%= @state.listenbrainz.rate_limit.total %></div>
<div>Updated At: <%= Timex.from_now(@state.listenbrainz.updated_at) %></div>
<div>Fetch Interval: <%= @state.listenbrainz.fetch_interval / 1000 %> seconds</div>
<% end %>

4
lib/dagon_web/views/listen_view.ex

@ -1,3 +1,7 @@
defmodule DagonWeb.ListenView do
use DagonWeb, :view
def image_class(image_size) do
"is-#{image_size}x#{image_size}"
end
end

0
navigation.html.exs

3
priv/repo/migrations/20191023181552_create_artists.exs

@ -4,8 +4,9 @@ defmodule Dagon.Repo.Migrations.CreateArtists do
def change do
create table(:artists) do
add :name, :string
add :slug, :string
add :image, :string
add :discogs_id, :integer
add :mbid, :string
add :msid, :string

1
priv/repo/migrations/20191023205308_create_albums.exs

@ -4,7 +4,6 @@ defmodule Dagon.Repo.Migrations.CreateAlbums do
def change do
create table(:albums) do
add :name, :string
add :slug, :string
add :image, :string
add :mbid, :string

10
priv/repo/migrations/20191109200616_add_image_to_artist.exs

@ -1,10 +0,0 @@
defmodule Dagon.Repo.Migrations.AddImageToArtist do
use Ecto.Migration
def change do
alter table(:artists) do
add :discogs_id, :integer
add :image, :string
end
end
end
Loading…
Cancel
Save