Compare commits
1 Commits
74a086c855
..
deploy
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e8417e09b |
@@ -0,0 +1,7 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# A sample docker-setup hook
|
||||||
|
#
|
||||||
|
# Sets up a Docker network which can then be used by the application’s containers
|
||||||
|
|
||||||
|
ssh user@example.com docker network create kamal
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A sample post-deploy hook
|
||||||
|
#
|
||||||
|
# These environment variables are available:
|
||||||
|
# KAMAL_RECORDED_AT
|
||||||
|
# KAMAL_PERFORMER
|
||||||
|
# KAMAL_VERSION
|
||||||
|
# KAMAL_HOSTS
|
||||||
|
# KAMAL_ROLE (if set)
|
||||||
|
# KAMAL_DESTINATION (if set)
|
||||||
|
# KAMAL_RUNTIME
|
||||||
|
|
||||||
|
echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds"
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Rebooted Traefik on $KAMAL_HOSTS"
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# A sample pre-build hook
|
||||||
|
#
|
||||||
|
# Checks:
|
||||||
|
# 1. We have a clean checkout
|
||||||
|
# 2. A remote is configured
|
||||||
|
# 3. The branch has been pushed to the remote
|
||||||
|
# 4. The version we are deploying matches the remote
|
||||||
|
#
|
||||||
|
# These environment variables are available:
|
||||||
|
# KAMAL_RECORDED_AT
|
||||||
|
# KAMAL_PERFORMER
|
||||||
|
# KAMAL_VERSION
|
||||||
|
# KAMAL_HOSTS
|
||||||
|
# KAMAL_ROLE (if set)
|
||||||
|
# KAMAL_DESTINATION (if set)
|
||||||
|
|
||||||
|
if [ -n "$(git status --porcelain)" ]; then
|
||||||
|
echo "Git checkout is not clean, aborting..." >&2
|
||||||
|
git status --porcelain >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
first_remote=$(git remote)
|
||||||
|
|
||||||
|
if [ -z "$first_remote" ]; then
|
||||||
|
echo "No git remote set, aborting..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
current_branch=$(git branch --show-current)
|
||||||
|
|
||||||
|
if [ -z "$current_branch" ]; then
|
||||||
|
echo "Not on a git branch, aborting..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1)
|
||||||
|
|
||||||
|
if [ -z "$remote_head" ]; then
|
||||||
|
echo "Branch not pushed to remote, aborting..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$KAMAL_VERSION" != "$remote_head" ]; then
|
||||||
|
echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# A sample pre-connect check
|
||||||
|
#
|
||||||
|
# Warms DNS before connecting to hosts in parallel
|
||||||
|
#
|
||||||
|
# These environment variables are available:
|
||||||
|
# KAMAL_RECORDED_AT
|
||||||
|
# KAMAL_PERFORMER
|
||||||
|
# KAMAL_VERSION
|
||||||
|
# KAMAL_HOSTS
|
||||||
|
# KAMAL_ROLE (if set)
|
||||||
|
# KAMAL_DESTINATION (if set)
|
||||||
|
# KAMAL_RUNTIME
|
||||||
|
|
||||||
|
hosts = ENV["KAMAL_HOSTS"].split(",")
|
||||||
|
results = nil
|
||||||
|
max = 3
|
||||||
|
|
||||||
|
elapsed = Benchmark.realtime do
|
||||||
|
results = hosts.map do |host|
|
||||||
|
Thread.new do
|
||||||
|
tries = 1
|
||||||
|
|
||||||
|
begin
|
||||||
|
Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME)
|
||||||
|
rescue SocketError
|
||||||
|
if tries < max
|
||||||
|
puts "Retrying DNS warmup: #{host}"
|
||||||
|
tries += 1
|
||||||
|
sleep rand
|
||||||
|
retry
|
||||||
|
else
|
||||||
|
puts "DNS warmup failed: #{host}"
|
||||||
|
host
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tries
|
||||||
|
end
|
||||||
|
end.map(&:value)
|
||||||
|
end
|
||||||
|
|
||||||
|
retries = results.sum - hosts.size
|
||||||
|
nopes = results.count { |r| r == max }
|
||||||
|
|
||||||
|
puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ]
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
#!/usr/bin/env ruby
|
||||||
|
|
||||||
|
# A sample pre-deploy hook
|
||||||
|
#
|
||||||
|
# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds.
|
||||||
|
#
|
||||||
|
# Fails unless the combined status is "success"
|
||||||
|
#
|
||||||
|
# These environment variables are available:
|
||||||
|
# KAMAL_RECORDED_AT
|
||||||
|
# KAMAL_PERFORMER
|
||||||
|
# KAMAL_VERSION
|
||||||
|
# KAMAL_HOSTS
|
||||||
|
# KAMAL_COMMAND
|
||||||
|
# KAMAL_SUBCOMMAND
|
||||||
|
# KAMAL_ROLE (if set)
|
||||||
|
# KAMAL_DESTINATION (if set)
|
||||||
|
|
||||||
|
# Only check the build status for production deployments
|
||||||
|
if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production"
|
||||||
|
exit 0
|
||||||
|
end
|
||||||
|
|
||||||
|
require "bundler/inline"
|
||||||
|
|
||||||
|
# true = install gems so this is fast on repeat invocations
|
||||||
|
gemfile(true, quiet: true) do
|
||||||
|
source "https://rubygems.org"
|
||||||
|
|
||||||
|
gem "octokit"
|
||||||
|
gem "faraday-retry"
|
||||||
|
end
|
||||||
|
|
||||||
|
MAX_ATTEMPTS = 72
|
||||||
|
ATTEMPTS_GAP = 10
|
||||||
|
|
||||||
|
def exit_with_error(message)
|
||||||
|
$stderr.puts message
|
||||||
|
exit 1
|
||||||
|
end
|
||||||
|
|
||||||
|
class GithubStatusChecks
|
||||||
|
attr_reader :remote_url, :git_sha, :github_client, :combined_status
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/")
|
||||||
|
@git_sha = `git rev-parse HEAD`.strip
|
||||||
|
@github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"])
|
||||||
|
refresh!
|
||||||
|
end
|
||||||
|
|
||||||
|
def refresh!
|
||||||
|
@combined_status = github_client.combined_status(remote_url, git_sha)
|
||||||
|
end
|
||||||
|
|
||||||
|
def state
|
||||||
|
combined_status[:state]
|
||||||
|
end
|
||||||
|
|
||||||
|
def first_status_url
|
||||||
|
first_status = combined_status[:statuses].find { |status| status[:state] == state }
|
||||||
|
first_status && first_status[:target_url]
|
||||||
|
end
|
||||||
|
|
||||||
|
def complete_count
|
||||||
|
combined_status[:statuses].count { |status| status[:state] != "pending"}
|
||||||
|
end
|
||||||
|
|
||||||
|
def total_count
|
||||||
|
combined_status[:statuses].count
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_status
|
||||||
|
if total_count > 0
|
||||||
|
"Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..."
|
||||||
|
else
|
||||||
|
"Build not started..."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
$stdout.sync = true
|
||||||
|
|
||||||
|
puts "Checking build status..."
|
||||||
|
attempts = 0
|
||||||
|
checks = GithubStatusChecks.new
|
||||||
|
|
||||||
|
begin
|
||||||
|
loop do
|
||||||
|
case checks.state
|
||||||
|
when "success"
|
||||||
|
puts "Checks passed, see #{checks.first_status_url}"
|
||||||
|
exit 0
|
||||||
|
when "failure"
|
||||||
|
exit_with_error "Checks failed, see #{checks.first_status_url}"
|
||||||
|
when "pending"
|
||||||
|
attempts += 1
|
||||||
|
end
|
||||||
|
|
||||||
|
exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS
|
||||||
|
|
||||||
|
puts checks.current_status
|
||||||
|
sleep(ATTEMPTS_GAP)
|
||||||
|
checks.refresh!
|
||||||
|
end
|
||||||
|
rescue Octokit::NotFound
|
||||||
|
exit_with_error "Build status could not be found"
|
||||||
|
end
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
echo "Rebooting Traefik on $KAMAL_HOSTS..."
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
defmodule LolAnalytics.Analyzer do
|
||||||
|
@callback analyze(:url, path :: String.t()) :: :ok
|
||||||
|
end
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
defmodule LolAnalytics.Analyzer.ChampionAnalyzer do
|
||||||
|
alias Hex.HTTP
|
||||||
|
@behaviour LolAnalytics.Analyzer
|
||||||
|
|
||||||
|
def analyze_all_matches do
|
||||||
|
Storage.MatchStorage.S3MatchStorage.list_files("ranked")
|
||||||
|
|> Enum.map(& &1.key)
|
||||||
|
|> Enum.each(fn path ->
|
||||||
|
LolAnalytics.Analyzer.ChampionAnalyzer.analyze(:url, "http://localhost:9000/ranked/#{path}")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
iex> LolAnalytics.Analyzer.ChampionAnalyzer.analyze(:url, "http://localhost:9000/ranked/14.9.580.2108/EUW1_6923309745.json")
|
||||||
|
"""
|
||||||
|
@impl true
|
||||||
|
@spec analyze(atom(), String.t()) :: :ok
|
||||||
|
def analyze(:url, path) do
|
||||||
|
data = HTTPoison.get!(path)
|
||||||
|
analyze(:data, data.body)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
@spec analyze(atom(), any()) :: list(LoLAPI.Model.Participant.t())
|
||||||
|
def analyze(:data, data) do
|
||||||
|
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
||||||
|
participants = decoded_match.info.participants
|
||||||
|
version = extract_game_version(decoded_match)
|
||||||
|
|
||||||
|
participants
|
||||||
|
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
||||||
|
if participant.teamPosition != "" do
|
||||||
|
LolAnalytics.ChampionWinRate.ChampionWinRateRepo.add_champion_win_rate(
|
||||||
|
participant.championId,
|
||||||
|
version,
|
||||||
|
participant.teamPosition,
|
||||||
|
participant.win
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp extract_game_version(game_data) do
|
||||||
|
game_data.info.gameVersion
|
||||||
|
|> String.split(".")
|
||||||
|
|> Enum.take(2)
|
||||||
|
|> Enum.join(".")
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -46,7 +46,7 @@ defmodule LolAnalytics.ChampionWinRate.ChampionWinRateRepo do
|
|||||||
Repo.all(ChampionWinRateSchema)
|
Repo.all(ChampionWinRateSchema)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_champion_win_rate(champion_id, _patch) do
|
def get_champion_win_rate(champion_id, patch) do
|
||||||
champion_query =
|
champion_query =
|
||||||
from cwr in LolAnalytics.ChampionWinRate.ChampionWinRateSchema,
|
from cwr in LolAnalytics.ChampionWinRate.ChampionWinRateSchema,
|
||||||
where: cwr.champion_id == ^champion_id
|
where: cwr.champion_id == ^champion_id
|
||||||
|
|||||||
@@ -1,41 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Champion.ChampionMetadata do
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
|
||||||
@champions_data_url "https://ddragon.leagueoflegends.com/cdn/14.11.1/data/en_US/champion.json"
|
|
||||||
|
|
||||||
def update_metadata() do
|
|
||||||
{:ok, %{"data" => data}} = get_champions()
|
|
||||||
|
|
||||||
data
|
|
||||||
|> Enum.each(&save_metadata/1)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_champions() do
|
|
||||||
with {:ok, resp} <- HTTPoison.get(@champions_data_url),
|
|
||||||
data <- Poison.decode(resp.body) do
|
|
||||||
data
|
|
||||||
else
|
|
||||||
{:error, reason} -> {:error, reason}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_metadata({_champion, info}) do
|
|
||||||
%{
|
|
||||||
"image" => %{
|
|
||||||
"full" => full_image
|
|
||||||
},
|
|
||||||
"name" => name,
|
|
||||||
"key" => key_string
|
|
||||||
} = info
|
|
||||||
|
|
||||||
attrs = %{
|
|
||||||
image: full_image,
|
|
||||||
name: name
|
|
||||||
}
|
|
||||||
|
|
||||||
{champion_id, _} = Integer.parse(key_string)
|
|
||||||
|
|
||||||
ChampionRepo.update(champion_id, attrs)
|
|
||||||
|
|
||||||
info
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Champion.ChampionRepo do
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
|
||||||
|
|
||||||
@spec get_or_create(String.t()) :: struct()
|
|
||||||
def get_or_create(champion_id) do
|
|
||||||
champion = Repo.get_by(ChampionSchema, champion_id: champion_id)
|
|
||||||
|
|
||||||
case champion do
|
|
||||||
nil ->
|
|
||||||
changeset = ChampionSchema.changeset(%ChampionSchema{}, %{champion_id: champion_id})
|
|
||||||
Repo.insert(changeset)
|
|
||||||
|
|
||||||
champion ->
|
|
||||||
champion
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def update(champion_id, attrs) do
|
|
||||||
get_or_create(champion_id)
|
|
||||||
|> ChampionSchema.changeset(attrs)
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec list_champions() :: any()
|
|
||||||
def list_champions() do
|
|
||||||
Repo.all(ChampionSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Champion.ChampionSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_champion" do
|
|
||||||
field :champion_id, :integer
|
|
||||||
field :name, :string
|
|
||||||
field :image, :string
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(champion = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
champion
|
|
||||||
|> cast(attrs, [:champion_id, :name, :image])
|
|
||||||
|> validate_required([:champion_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Item.ItemRepo do
|
|
||||||
alias LolAnalytics.Dimensions.Item.ItemSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
def get_or_create(item_id) do
|
|
||||||
item = Repo.get(ItemSchema, item_id: item_id)
|
|
||||||
|
|
||||||
case item do
|
|
||||||
nil ->
|
|
||||||
item_changeset = ItemSchema.changeset(%ItemSchema{}, %{item_id: item_id})
|
|
||||||
Repo.insert(item_changeset)
|
|
||||||
|
|
||||||
item ->
|
|
||||||
item
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_items() do
|
|
||||||
Repo.all(ItemSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Item.ItemSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_item" do
|
|
||||||
field :item_id, :integer
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(item = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
item
|
|
||||||
|> cast(attrs, [:item_id])
|
|
||||||
|> validate_required([:item_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Match.MatchRepo do
|
|
||||||
alias LolAnalytics.Dimensions.Match.MatchSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
@spec get_or_create(String.t()) :: %MatchSchema{}
|
|
||||||
def get_or_create(match_id) do
|
|
||||||
query = from m in MatchSchema, where: m.match_id == ^match_id
|
|
||||||
match = Repo.one(query)
|
|
||||||
|
|
||||||
case match do
|
|
||||||
nil ->
|
|
||||||
match_changeset =
|
|
||||||
MatchSchema.changeset(
|
|
||||||
%MatchSchema{},
|
|
||||||
%{match_id: match_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
Repo.insert(match_changeset)
|
|
||||||
|
|
||||||
match ->
|
|
||||||
match
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_matches() do
|
|
||||||
Repo.all(MatchSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Match.MatchSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_match" do
|
|
||||||
field :match_id, :string
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(match = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
match
|
|
||||||
|> cast(attrs, [:match_id])
|
|
||||||
|> validate_required([:match_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Patch.PatchRepo do
|
|
||||||
alias LolAnalytics.Dimensions.Patch.PatchSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
def get_or_create(patch_number) do
|
|
||||||
patch = Repo.get(PatchSchema, patch_number: patch_number)
|
|
||||||
|
|
||||||
case patch do
|
|
||||||
nil ->
|
|
||||||
patch_changeset =
|
|
||||||
PatchSchema.changeset(
|
|
||||||
%PatchSchema{},
|
|
||||||
%{patch_number: patch_number}
|
|
||||||
)
|
|
||||||
|
|
||||||
Repo.insert(patch_changeset)
|
|
||||||
|
|
||||||
patch ->
|
|
||||||
patch
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_patches() do
|
|
||||||
Repo.all(PatchSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Patch.PatchSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_patch" do
|
|
||||||
field :patch_number, :string
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(patch = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
patch
|
|
||||||
|> cast(attrs, [:patch_number])
|
|
||||||
|> validate_required([:patch_number])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Player.PlayerRepo do
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias LolAnalytics.Dimensions.Player.PlayerSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
def get_or_create(puuid) do
|
|
||||||
query = from p in PlayerSchema, where: p.puuid == ^puuid
|
|
||||||
player = Repo.one(query)
|
|
||||||
|
|
||||||
case player do
|
|
||||||
nil ->
|
|
||||||
player_changeset =
|
|
||||||
PlayerSchema.changeset(
|
|
||||||
%PlayerSchema{},
|
|
||||||
%{puuid: puuid}
|
|
||||||
)
|
|
||||||
Repo.insert(player_changeset)
|
|
||||||
|
|
||||||
player ->
|
|
||||||
player
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_players() do
|
|
||||||
Repo.all(PlayerSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.Player.PlayerSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_player" do
|
|
||||||
field :puuid, :string
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(player = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
player
|
|
||||||
|> cast(attrs, [:puuid])
|
|
||||||
|> validate_required([:puuid])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellMetadata do
|
|
||||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo
|
|
||||||
@spells_url "https://ddragon.leagueoflegends.com/cdn/14.11.1/data/en_US/summoner.json"
|
|
||||||
|
|
||||||
def update_metadata() do
|
|
||||||
get_spells()
|
|
||||||
|> Enum.each(&save_metadata/1)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp get_spells() do
|
|
||||||
case HTTPoison.get(@spells_url) do
|
|
||||||
{:ok, resp} ->
|
|
||||||
Poison.decode!(resp.body)["data"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp save_metadata({name, metadata}) do
|
|
||||||
%{"key" => spell_id} = metadata
|
|
||||||
|
|
||||||
SummonerSpellRepo.update(spell_id, %{metadata: metadata})
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo do
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
@spec get_or_create(String.t()) :: any()
|
|
||||||
def get_or_create(spell_id) do
|
|
||||||
query = from s in SummonerSpellSchema, where: s.spell_id == ^spell_id
|
|
||||||
spell = Repo.one(query)
|
|
||||||
|
|
||||||
case spell do
|
|
||||||
nil ->
|
|
||||||
spell_changeset =
|
|
||||||
SummonerSpellSchema.changeset(
|
|
||||||
%SummonerSpellSchema{},
|
|
||||||
%{spell_id: spell_id}
|
|
||||||
)
|
|
||||||
|
|
||||||
Repo.insert(spell_changeset)
|
|
||||||
|
|
||||||
spell ->
|
|
||||||
spell
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec update(spell_id :: String.t(), attrs :: map()) :: any()
|
|
||||||
def update(spell_id, attrs) do
|
|
||||||
get_or_create(spell_id)
|
|
||||||
|> SummonerSpellSchema.changeset(attrs)
|
|
||||||
|> Repo.update()
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_spells() do
|
|
||||||
Repo.all(SummonerSpellSchema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
defmodule LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema do
|
|
||||||
use Ecto.Schema
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
schema "dim_summoner_spell" do
|
|
||||||
field :spell_id, :integer
|
|
||||||
field :metadata, :map
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(summoner_spell = %__MODULE__{}, attrs) do
|
|
||||||
summoner_spell
|
|
||||||
|> cast(attrs, [:spell_id, :metadata])
|
|
||||||
|> validate_required([:spell_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.FactProcessor do
|
|
||||||
alias LolAnalytics.Facts.ChampionPickedSummonerSpell
|
|
||||||
|
|
||||||
def process_game_at_url(url) do
|
|
||||||
data = HTTPoison.get!(url)
|
|
||||||
process_game_data(data.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp process_game_data(data) do
|
|
||||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
|
||||||
participants = decoded_match.info.participants
|
|
||||||
version = extract_game_version(decoded_match)
|
|
||||||
|
|
||||||
participants
|
|
||||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
|
||||||
if participant.teamPosition != "" do
|
|
||||||
|
|
||||||
attrs_spell_1 = %{
|
|
||||||
champion_id: participant.championId,
|
|
||||||
match_id: decoded_match.metadata.matchId,
|
|
||||||
is_win: participant.win,
|
|
||||||
summoner_spell_id: participant.summoner1Id,
|
|
||||||
game_length_seconds: decoded_match.info.gameDuration,
|
|
||||||
queue_id: decoded_match.info.queueId,
|
|
||||||
puuid: participant.puuid,
|
|
||||||
team_position: participant.teamPosition,
|
|
||||||
patch_number: version
|
|
||||||
}
|
|
||||||
attrs_spell_2 = %{
|
|
||||||
champion_id: participant.championId,
|
|
||||||
match_id: decoded_match.metadata.matchId,
|
|
||||||
is_win: participant.win,
|
|
||||||
summoner_spell_id: participant.summoner2Id,
|
|
||||||
game_length_seconds: decoded_match.info.gameDuration,
|
|
||||||
queue_id: decoded_match.info.queueId,
|
|
||||||
puuid: participant.puuid,
|
|
||||||
team_position: participant.teamPosition,
|
|
||||||
patch_number: version
|
|
||||||
}
|
|
||||||
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_1)
|
|
||||||
ChampionPickedSummonerSpell.Repo.insert(attrs_spell_2)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp extract_game_version(game_data) do
|
|
||||||
game_data.info.gameVersion
|
|
||||||
|> String.split(".")
|
|
||||||
|> Enum.take(2)
|
|
||||||
|> Enum.join(".")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo do
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellSchema
|
|
||||||
alias LolAnalytics.Dimensions.SummonerSpell.SummonerSpellRepo
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
|
||||||
|
|
||||||
alias LolAnalytics.Facts.ChampionPickedSummonerSpell.Schema
|
|
||||||
|
|
||||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
|
||||||
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
@type insert_attrs :: %{
|
|
||||||
match_id: String.t(),
|
|
||||||
champion_id: String.t(),
|
|
||||||
puuid: String.t(),
|
|
||||||
summoner_spell_id: :integer
|
|
||||||
}
|
|
||||||
|
|
||||||
@spec insert(insert_attrs()) :: any()
|
|
||||||
def insert(attrs) do
|
|
||||||
_match = MatchRepo.get_or_create(attrs.match_id)
|
|
||||||
_champion = ChampionRepo.get_or_create(attrs.champion_id)
|
|
||||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
|
||||||
_spell = SummonerSpellRepo.get_or_create(attrs.summoner_spell_id)
|
|
||||||
|
|
||||||
prev =
|
|
||||||
from(f in Schema,
|
|
||||||
where:
|
|
||||||
f.match_id == ^attrs.match_id and
|
|
||||||
f.champion_id == ^attrs.champion_id and
|
|
||||||
f.summoner_spell_id ==
|
|
||||||
^attrs.summoner_spell_id
|
|
||||||
)
|
|
||||||
|> Repo.one()
|
|
||||||
|
|
||||||
changeset = Schema.changeset(prev || %Schema{}, attrs)
|
|
||||||
|
|
||||||
IO.inspect(attrs)
|
|
||||||
|
|
||||||
Repo.insert_or_update(changeset)
|
|
||||||
|> IO.inspect()
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_champion_picked_summoners(championId, team_position \\ "MIDDLE") do
|
|
||||||
query =
|
|
||||||
from f in Schema,
|
|
||||||
where: f.champion_id == ^championId and f.team_position == ^team_position,
|
|
||||||
join: c in ChampionSchema,
|
|
||||||
on: c.champion_id == f.champion_id,
|
|
||||||
join: s in SummonerSpellSchema,
|
|
||||||
on: s.spell_id == f.summoner_spell_id,
|
|
||||||
select: %{
|
|
||||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", f.is_win),
|
|
||||||
win_rate:
|
|
||||||
fragment(
|
|
||||||
"
|
|
||||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
|
||||||
)",
|
|
||||||
f.is_win
|
|
||||||
),
|
|
||||||
id: f.champion_id,
|
|
||||||
spell_id: f.summoner_spell_id,
|
|
||||||
metadata: s.metadata,
|
|
||||||
champion_name: c.name,
|
|
||||||
champion_id: c.champion_id,
|
|
||||||
team_position: f.team_position,
|
|
||||||
total_games: count("*")
|
|
||||||
},
|
|
||||||
group_by: [
|
|
||||||
f.champion_id,
|
|
||||||
f.summoner_spell_id,
|
|
||||||
s.metadata,
|
|
||||||
c.image,
|
|
||||||
c.name,
|
|
||||||
c.champion_id,
|
|
||||||
f.team_position
|
|
||||||
]
|
|
||||||
|
|
||||||
Repo.all(query)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_facts() do
|
|
||||||
Repo.all(Schema)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPickedSummonerSpell.Schema do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
@params [
|
|
||||||
:champion_id,
|
|
||||||
:summoner_spell_id,
|
|
||||||
:match_id,
|
|
||||||
:is_win,
|
|
||||||
:game_length_seconds,
|
|
||||||
:queue_id,
|
|
||||||
:team_position,
|
|
||||||
:puuid
|
|
||||||
]
|
|
||||||
|
|
||||||
schema "fact_champion_picked_summoner_spell" do
|
|
||||||
field :champion_id, :integer
|
|
||||||
field :summoner_spell_id, :integer
|
|
||||||
field :match_id, :string
|
|
||||||
field :is_win, :boolean
|
|
||||||
field :game_length_seconds, :integer
|
|
||||||
field :queue_id, :integer
|
|
||||||
field :team_position, :string
|
|
||||||
field :puuid, :string
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(fact = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
fact
|
|
||||||
|> cast(attrs, @params)
|
|
||||||
|> validate_required(@params)
|
|
||||||
|> unique_constraint([:puuid, :match_id, :summoner_spell_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,50 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.Repo do
|
|
||||||
import Ecto.Query
|
|
||||||
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionSchema
|
|
||||||
alias LolAnalytics.Dimensions.Player.PlayerRepo
|
|
||||||
alias LolAnalytics.Dimensions.Champion.ChampionRepo
|
|
||||||
alias LolAnalytics.Dimensions.Match.MatchRepo
|
|
||||||
alias LolAnalytics.Facts.ChampionPlayedGame.Schema
|
|
||||||
alias LoLAnalytics.Repo
|
|
||||||
|
|
||||||
def insert(attrs) do
|
|
||||||
_match = MatchRepo.get_or_create(attrs.match_id)
|
|
||||||
_champion = ChampionRepo.get_or_create(attrs.champion_id)
|
|
||||||
_player = PlayerRepo.get_or_create(attrs.puuid)
|
|
||||||
changeset = Schema.changeset(%Schema{}, attrs)
|
|
||||||
Repo.insert(changeset)
|
|
||||||
end
|
|
||||||
|
|
||||||
def list_played_matches() do
|
|
||||||
Repo.all(Schema)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_win_rates do
|
|
||||||
query =
|
|
||||||
from m in Schema,
|
|
||||||
join: c in ChampionSchema,
|
|
||||||
on: c.champion_id == m.champion_id,
|
|
||||||
select: %{
|
|
||||||
wins: fragment("count(CASE WHEN ? THEN 1 END)", m.is_win),
|
|
||||||
win_rate:
|
|
||||||
fragment(
|
|
||||||
"
|
|
||||||
((cast(count(CASE WHEN ? THEN 1 END) as float) / cast(count(*) as float)) * 100.0
|
|
||||||
)",
|
|
||||||
m.is_win
|
|
||||||
),
|
|
||||||
id: m.champion_id,
|
|
||||||
name: c.name,
|
|
||||||
image: c.image,
|
|
||||||
team_position: m.team_position,
|
|
||||||
total_games: count("*")
|
|
||||||
},
|
|
||||||
group_by: [m.champion_id, c.image, c.name, m.team_position]
|
|
||||||
|
|
||||||
Repo.all(query)
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_win_rates_by_roles() do
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.Schema do
|
|
||||||
use Ecto.Schema
|
|
||||||
|
|
||||||
import Ecto.Changeset
|
|
||||||
|
|
||||||
@casting_attrs [
|
|
||||||
:champion_id,
|
|
||||||
:match_id,
|
|
||||||
:is_win,
|
|
||||||
:game_length_seconds,
|
|
||||||
:team_position,
|
|
||||||
:puuid,
|
|
||||||
:queue_id
|
|
||||||
]
|
|
||||||
|
|
||||||
schema "fact_champion_played_game" do
|
|
||||||
field :champion_id, :integer
|
|
||||||
field :match_id, :string
|
|
||||||
field :is_win, :boolean
|
|
||||||
field :game_length_seconds, :integer
|
|
||||||
field :team_position, :string
|
|
||||||
field :puuid, :string
|
|
||||||
field :queue_id, :integer
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
def changeset(fact = %__MODULE__{}, attrs \\ %{}) do
|
|
||||||
fact
|
|
||||||
|> cast(attrs, @casting_attrs)
|
|
||||||
|> validate_required(@casting_attrs)
|
|
||||||
|> unique_constraint([:id, :champion_id, :queue_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.ChampionPlayedGame.FactProcessor do
|
|
||||||
def process_game_at_url(path) do
|
|
||||||
data = HTTPoison.get!(path)
|
|
||||||
process_game_data(data.body)
|
|
||||||
end
|
|
||||||
|
|
||||||
def process_game_data(data) do
|
|
||||||
decoded_match = Poison.decode!(data, as: %LoLAPI.Model.MatchResponse{})
|
|
||||||
participants = decoded_match.info.participants
|
|
||||||
version = extract_game_version(decoded_match)
|
|
||||||
|
|
||||||
participants
|
|
||||||
|> Enum.each(fn participant = %LoLAPI.Model.Participant{} ->
|
|
||||||
if participant.teamPosition != "" do
|
|
||||||
attrs = %{
|
|
||||||
champion_id: participant.championId,
|
|
||||||
match_id: decoded_match.metadata.matchId,
|
|
||||||
is_win: participant.win,
|
|
||||||
game_length_seconds: decoded_match.info.gameDuration,
|
|
||||||
queue_id: decoded_match.info.queueId,
|
|
||||||
puuid: participant.puuid,
|
|
||||||
team_position: participant.teamPosition,
|
|
||||||
patch_number: version
|
|
||||||
}
|
|
||||||
|
|
||||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.insert(attrs)
|
|
||||||
end
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp extract_game_version(game_data) do
|
|
||||||
game_data.info.gameVersion
|
|
||||||
|> String.split(".")
|
|
||||||
|> Enum.take(2)
|
|
||||||
|> Enum.join(".")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
defmodule LolAnalytics.Facts.FactsRunner do
|
|
||||||
alias LolAnalytics.Facts
|
|
||||||
|
|
||||||
def analyze_all_matches do
|
|
||||||
Storage.MatchStorage.S3MatchStorage.stream_files("ranked")
|
|
||||||
|> Enum.each(fn %{key: path} ->
|
|
||||||
get_facts()
|
|
||||||
|> Enum.each(fn fact_runner ->
|
|
||||||
apply(fact_runner, ["http://192.168.1.55:9000/ranked/#{path}"])
|
|
||||||
end)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
|
|
||||||
def analyze_match() do
|
|
||||||
end
|
|
||||||
|
|
||||||
def get_facts() do
|
|
||||||
[
|
|
||||||
&Facts.ChampionPickedSummonerSpell.FactProcessor.process_game_at_url/1
|
|
||||||
]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -13,7 +13,7 @@ defmodule LolAnalytics.Match.MatchRepo do
|
|||||||
LoLAnalytics.Repo.one(query)
|
LoLAnalytics.Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec get_match(String.t()) :: %LolAnalytics.Match.MatchSchema{} | nil
|
@spec get_match(String.t()) :: %LolAnalytics.Match.MatchSchema{}
|
||||||
def get_match(match_id) do
|
def get_match(match_id) do
|
||||||
query = from m in MatchSchema, where: m.match_id == ^match_id
|
query = from m in MatchSchema, where: m.match_id == ^match_id
|
||||||
|
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ defmodule LoLAnalytics.MixProject do
|
|||||||
{:postgrex, ">= 0.0.0"},
|
{:postgrex, ">= 0.0.0"},
|
||||||
{:jason, "~> 1.2"},
|
{:jason, "~> 1.2"},
|
||||||
{:lol_api, in_umbrella: true},
|
{:lol_api, in_umbrella: true},
|
||||||
{:storage, in_umbrella: true},
|
|
||||||
{:httpoison, "~> 2.2"},
|
{:httpoison, "~> 2.2"},
|
||||||
{:poison, "~> 5.0"}
|
{:poison, "~> 5.0"}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,62 +0,0 @@
|
|||||||
defmodule LoLAnalytics.Repo.Migrations.AnalyticsTables do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def change do
|
|
||||||
create table("dim_champion") do
|
|
||||||
add :champion_id, :integer, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_champion", [:champion_id], unique: true)
|
|
||||||
|
|
||||||
create table("dim_item") do
|
|
||||||
add :item_id, :integer, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_item", [:item_id], unique: true)
|
|
||||||
|
|
||||||
create table("dim_match") do
|
|
||||||
add :match_id, :string, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_match", [:match_id], unique: true)
|
|
||||||
|
|
||||||
create table("dim_patch") do
|
|
||||||
add :patch_number, :string, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_patch", [:patch_number], unique: true)
|
|
||||||
|
|
||||||
create table("dim_player") do
|
|
||||||
add :puuid, :string, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_player", [:puuid], unique: true)
|
|
||||||
|
|
||||||
create table("dim_summoner_spell") do
|
|
||||||
add :spell_id, :integer, primary_key: true, null: false
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("dim_summoner_spell", [:spell_id], unique: true)
|
|
||||||
|
|
||||||
create table("fact_champion_played_game") do
|
|
||||||
add :champion_id, references("dim_champion", column: :champion_id, type: :integer)
|
|
||||||
add :match_id, references("dim_match", column: :match_id, type: :string)
|
|
||||||
add :is_win, :boolean
|
|
||||||
add :game_length_seconds, :integer
|
|
||||||
add :queue_id, :integer
|
|
||||||
add :patch_number, references("dim_patch", column: :patch_number, type: :string)
|
|
||||||
add :team_position, :string
|
|
||||||
add :puuid, references("dim_player", column: :puuid, type: :string)
|
|
||||||
timestamps()
|
|
||||||
end
|
|
||||||
|
|
||||||
create index("fact_champion_played_game", [:id, :champion_id, :queue_id])
|
|
||||||
create index("fact_champion_played_game", [:puuid, :match_id], unique: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
defmodule LoLAnalytics.Repo.Migrations.ChampionDimName do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def change do
|
|
||||||
alter table("dim_champion") do
|
|
||||||
add :name, :string
|
|
||||||
add :image, :string
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellWinRate do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def change do
|
|
||||||
create table("fact_champion_picked_summoner_spell") do
|
|
||||||
add :champion_id, references("dim_champion", column: :champion_id, type: :integer)
|
|
||||||
add :summoner_spell_id, references("dim_summoner_spell", column: :spell_id, type: :integer)
|
|
||||||
add :match_id, references("dim_match", column: :match_id, type: :string)
|
|
||||||
add :is_win, :boolean
|
|
||||||
add :game_length_seconds, :integer
|
|
||||||
add :queue_id, :integer
|
|
||||||
add :team_position, :string
|
|
||||||
add :puuid, references("dim_player", column: :puuid, type: :string)
|
|
||||||
add :patch_number, references("dim_patch", column: :patch_number, type: :string)
|
|
||||||
end
|
|
||||||
|
|
||||||
create index(
|
|
||||||
"fact_champion_picked_summoner_spell",
|
|
||||||
[:puuid, :match_id, :summoner_spell_id],
|
|
||||||
unique: true
|
|
||||||
)
|
|
||||||
|
|
||||||
create index("fact_champion_picked_summoner_spell", [
|
|
||||||
:is_win,
|
|
||||||
:team_position,
|
|
||||||
:queue_id,
|
|
||||||
:summoner_spell_id,
|
|
||||||
:patch_number
|
|
||||||
])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellMetadata do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def change do
|
|
||||||
alter table("dim_summoner_spell") do
|
|
||||||
add :metadata, :map
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
defmodule LoLAnalytics.Repo.Migrations.SummonerSpellMetadataIndex do
|
|
||||||
use Ecto.Migration
|
|
||||||
|
|
||||||
def up do
|
|
||||||
execute("CREATE INDEX dim_summoner_spell_metadata ON dim_summoner_spell USING GIN(metadata)")
|
|
||||||
end
|
|
||||||
|
|
||||||
def down do
|
|
||||||
execute("DROP INDEX dim_summoner_spell_metadata")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionAvatar do
|
|
||||||
use Phoenix.Component
|
|
||||||
|
|
||||||
attr :id, :integer, required: true
|
|
||||||
attr :image, :string, required: true
|
|
||||||
attr :name, :string, required: true
|
|
||||||
|
|
||||||
def champion_avatar(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div class="flex flex-col w-40">
|
|
||||||
<img src={@image} alt="champion-icon" />
|
|
||||||
<p class="w-full text-center"><%= @name %></p>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionCard.Props do
|
|
||||||
defstruct [:id, :win_rate, :image, :name, :team_position, :wins, :total_games]
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule LolAnalyticsWeb.ChampionComponents.ChampionCard do
|
|
||||||
use Phoenix.Component
|
|
||||||
|
|
||||||
alias LolAnalyticsWeb.ChampionComponents.ChampionCard.Props
|
|
||||||
|
|
||||||
attr :props, Props, default: %Props{}
|
|
||||||
|
|
||||||
def champion_card(assigns) do
|
|
||||||
~H"""
|
|
||||||
<.link patch={"/champions/#{@props.id}"}>
|
|
||||||
<div class="flex flex-col rounded-xl bg-clip-border overflow-hidden bg-gray-200">
|
|
||||||
<div class="flex flex-column overflow-hidden">
|
|
||||||
<div class=" px-4 py-1 opacity-80 gap-2 absolute z-10 align-bottom flex mx-auto bg-black w-max">
|
|
||||||
<img src={team_position_image(@props.team_position)} class="w-5 h-5" />
|
|
||||||
<h3 class="text-white"><%= @props.name %></h3>
|
|
||||||
</div>
|
|
||||||
<img
|
|
||||||
class="static w-40 h-40"
|
|
||||||
src={"https://ddragon.leagueoflegends.com/cdn/14.11.1/img/champion/#{@props.image}"}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="py-1" />
|
|
||||||
<div class="pl-2">
|
|
||||||
<h3>Win rate: <%= @props.win_rate %></h3>
|
|
||||||
<h3>Wins: <%= @props.wins %> / <%= @props.total_games %></h3>
|
|
||||||
</div>
|
|
||||||
<div class="py-1" />
|
|
||||||
</div>
|
|
||||||
</.link>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
|
|
||||||
defp team_position_image("BOTTOM"), do: "/images/lanes/bot.png"
|
|
||||||
defp team_position_image("MIDDLE"), do: "/images/lanes/mid.png"
|
|
||||||
defp team_position_image("TOP"), do: "/images/lanes/top.png"
|
|
||||||
defp team_position_image("JUNGLE"), do: "/images/lanes/jungle.png"
|
|
||||||
defp team_position_image("UTILITY"), do: "/images/lanes/utility.png"
|
|
||||||
end
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props.SummonerSpell do
|
|
||||||
defstruct [:id, :name, :win_rate, :wins, :total_games, :image]
|
|
||||||
|
|
||||||
@type t :: %{
|
|
||||||
id: integer(),
|
|
||||||
win_rate: float(),
|
|
||||||
wins: integer(),
|
|
||||||
total_games: integer(),
|
|
||||||
image: String.t(),
|
|
||||||
name: String.t()
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props do
|
|
||||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props.SummonerSpell
|
|
||||||
|
|
||||||
defstruct spell: %SummonerSpell{}
|
|
||||||
|
|
||||||
@type t :: %{spell: SummonerSpell.t()}
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpell do
|
|
||||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props
|
|
||||||
use Phoenix.Component
|
|
||||||
|
|
||||||
attr :spells, Props, default: %Props{}
|
|
||||||
|
|
||||||
def summoner_spells(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div class="flex flex-wrap flex-wrap gap-4">
|
|
||||||
<%= for spell <- assigns.spells.summoner_spells do %>
|
|
||||||
<div clas="flex flex-col gap-1">
|
|
||||||
<img src={spell.spell.image} />
|
|
||||||
<p><%= spell.spell.name %></p>
|
|
||||||
<p><%= spell.spell.win_rate %>%</p>
|
|
||||||
<p><%= spell.spell.wins %>/<%= spell.spell.total_games %></p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -302,9 +302,6 @@ defmodule LoLAnalyticsWeb.CoreComponents do
|
|||||||
end
|
end
|
||||||
|
|
||||||
def input(%{type: "checkbox"} = assigns) do
|
def input(%{type: "checkbox"} = assigns) do
|
||||||
IO.puts(">>>>")
|
|
||||||
IO.inspect(assigns)
|
|
||||||
|
|
||||||
assigns =
|
assigns =
|
||||||
assign_new(assigns, :checked, fn ->
|
assign_new(assigns, :checked, fn ->
|
||||||
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
|
Phoenix.HTML.Form.normalize_value("checkbox", assigns[:value])
|
||||||
@@ -318,8 +315,8 @@ defmodule LoLAnalyticsWeb.CoreComponents do
|
|||||||
type="checkbox"
|
type="checkbox"
|
||||||
id={@id}
|
id={@id}
|
||||||
name={@name}
|
name={@name}
|
||||||
value="false"
|
value="true"
|
||||||
checked={false}
|
checked={@checked}
|
||||||
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
|
class="rounded border-zinc-300 text-zinc-900 focus:ring-0"
|
||||||
{@rest}
|
{@rest}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -2,16 +2,25 @@
|
|||||||
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
<div class="flex items-center justify-between border-b border-zinc-100 py-3 text-sm">
|
||||||
<div class="flex items-center gap-4">
|
<div class="flex items-center gap-4">
|
||||||
<a href="/">
|
<a href="/">
|
||||||
LoL Analytics
|
<img src={~p"/images/logo.svg"} width="36" />
|
||||||
</a>
|
</a>
|
||||||
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
<p class="bg-brand/5 text-brand rounded-full px-2 font-medium leading-6">
|
||||||
v<%= Application.spec(:phoenix, :vsn) %>
|
v<%= Application.spec(:phoenix, :vsn) %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
<div class="flex items-center gap-4 font-semibold leading-6 text-zinc-900">
|
||||||
<.link patch="/champions">
|
<a href="https://twitter.com/elixirphoenix" class="hover:text-zinc-700">
|
||||||
Champions
|
@elixirphoenix
|
||||||
</.link>
|
</a>
|
||||||
|
<a href="https://github.com/phoenixframework/phoenix" class="hover:text-zinc-700">
|
||||||
|
GitHub
|
||||||
|
</a>
|
||||||
|
<a
|
||||||
|
href="https://hexdocs.pm/phoenix/overview.html"
|
||||||
|
class="rounded-lg bg-zinc-100 px-2 py-1 hover:bg-zinc-200/80"
|
||||||
|
>
|
||||||
|
Get Started <span aria-hidden="true">→</span>
|
||||||
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
@@ -20,4 +29,4 @@
|
|||||||
<.flash_group flash={@flash} />
|
<.flash_group flash={@flash} />
|
||||||
<%= @inner_content %>
|
<%= @inner_content %>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="[scrollbar-gutter:stable]">
|
<html lang="en" class="[scrollbar-gutter:stable]">
|
||||||
|
<head>
|
||||||
<head>
|
<meta charset="utf-8" />
|
||||||
<meta charset="utf-8" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="csrf-token" content={get_csrf_token()} />
|
||||||
<meta name="csrf-token" content={get_csrf_token()} />
|
<.live_title suffix=" · Phoenix Framework">
|
||||||
<.live_title suffix=" · Phoenix Framework">
|
<%= assigns[:page_title] || "LoLAnalytics" %>
|
||||||
<%= assigns[:page_title] || "LoLAnalytics" %>
|
</.live_title>
|
||||||
</.live_title>
|
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
||||||
<link phx-track-static rel="stylesheet" href={~p"/assets/app.css"} />
|
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
||||||
<script defer phx-track-static type="text/javascript" src={~p"/assets/app.js"}>
|
</script>
|
||||||
</script>
|
</head>
|
||||||
</head>
|
<body class="bg-white antialiased">
|
||||||
|
<%= @inner_content %>
|
||||||
<body class="bg-white antialiased">
|
</body>
|
||||||
<%= @inner_content %>
|
</html>
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
||||||
|
|||||||
@@ -1,28 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.Loader do
|
|
||||||
use Phoenix.Component
|
|
||||||
|
|
||||||
def loader(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div role="status" class="flex flex-col mx-auto w-max justify-center items-center gap-4">
|
|
||||||
<span class="text-3xl">Loading</span>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
class="w-16 h-16 text-gray-300 animate-spin dark:text-gray-300 fill-orange-600"
|
|
||||||
viewBox="0 0 100 101"
|
|
||||||
fill="none"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path
|
|
||||||
d="M100 50.5908C100 78.2051 77.6142 100.591 50 100.591C22.3858 100.591 0 78.2051 0 50.5908C0 22.9766 22.3858 0.59082 50 0.59082C77.6142 0.59082 100 22.9766 100 50.5908ZM9.08144 50.5908C9.08144 73.1895 27.4013 91.5094 50 91.5094C72.5987 91.5094 90.9186 73.1895 90.9186 50.5908C90.9186 27.9921 72.5987 9.67226 50 9.67226C27.4013 9.67226 9.08144 27.9921 9.08144 50.5908Z"
|
|
||||||
fill="currentColor"
|
|
||||||
/>
|
|
||||||
<path
|
|
||||||
d="M93.9676 39.0409C96.393 38.4038 97.8624 35.9116 97.0079 33.5539C95.2932 28.8227 92.871 24.3692 89.8167 20.348C85.8452 15.1192 80.8826 10.7238 75.2124 7.41289C69.5422 4.10194 63.2754 1.94025 56.7698 1.05124C51.7666 0.367541 46.6976 0.446843 41.7345 1.27873C39.2613 1.69328 37.813 4.19778 38.4501 6.62326C39.0873 9.04874 41.5694 10.4717 44.0505 10.1071C47.8511 9.54855 51.7191 9.52689 55.5402 10.0491C60.8642 10.7766 65.9928 12.5457 70.6331 15.2552C75.2735 17.9648 79.3347 21.5619 82.5849 25.841C84.9175 28.9121 86.7997 32.2913 88.1811 35.8758C89.083 38.2158 91.5421 39.6781 93.9676 39.0409Z"
|
|
||||||
fill="currentFill"
|
|
||||||
/>
|
|
||||||
</svg>
|
|
||||||
<span class="sr-only">Loading...</span>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionLive.ChampionSummary do
|
|
||||||
defstruct [:id, :win_rate, :image, :name, :team_position, :wins, :total_games]
|
|
||||||
end
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionFilters do
|
|
||||||
use LoLAnalyticsWeb, :live_component
|
|
||||||
|
|
||||||
@roles_definition [
|
|
||||||
%{title: "All", value: "all"},
|
|
||||||
%{title: "Top", value: "TOP"},
|
|
||||||
%{title: "Jungle", value: "JUNGLE"},
|
|
||||||
%{title: "Mid", value: "MIDDLE"},
|
|
||||||
%{title: "Bot", value: "BOTTOM"},
|
|
||||||
%{title: "Support", value: "UTILITY"}
|
|
||||||
]
|
|
||||||
|
|
||||||
def on_role_selected(role) do
|
|
||||||
send_update(LolAnalyticsWeb.ChampionLive, role)
|
|
||||||
end
|
|
||||||
|
|
||||||
def mount(socket) do
|
|
||||||
socket =
|
|
||||||
assign(socket, :roles, @roles_definition)
|
|
||||||
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
attr :selectedrole, :string, required: true
|
|
||||||
attr :roles, :list, default: []
|
|
||||||
|
|
||||||
def render(assigns) do
|
|
||||||
selected_class =
|
|
||||||
"px-8 py-2 flex flex-row gap-2 align-middle rounded-full border-gray-200 border border-orange-600 cursor-pointer"
|
|
||||||
|
|
||||||
~H"""
|
|
||||||
<div>
|
|
||||||
<div class="flex flex-row justify-between p-10">
|
|
||||||
<%= for role <- @roles do %>
|
|
||||||
<%!-- <%= IO.inspect(role) %> --%>
|
|
||||||
<%= if (assigns.selectedrole == role.value) do %>
|
|
||||||
<div phx-click="filter" phx-value-role={role.title} class={selected_class}>
|
|
||||||
<p><%= role.title %></p>
|
|
||||||
</div>
|
|
||||||
<% else %>
|
|
||||||
<div
|
|
||||||
phx-click="filter"
|
|
||||||
phx-value-role={role.value}
|
|
||||||
class="px-8 py-2 flex flex-row gap-2 align-middle cursor-pointer"
|
|
||||||
>
|
|
||||||
<p><%= role.title %></p>
|
|
||||||
</div>
|
|
||||||
<% end %>
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defmodule LolAnalyticsWeb.ChampionLive.Components.ChampionFilters.EventHandler do
|
|
||||||
@callback role_selected() :: none()
|
|
||||||
end
|
|
||||||
@@ -1,98 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.ChampionLive.Index do
|
|
||||||
use LoLAnalyticsWeb, :live_view
|
|
||||||
|
|
||||||
import LolAnalyticsWeb.ChampionComponents.ChampionCard
|
|
||||||
import LolAnalyticsWeb.Loader
|
|
||||||
import Phoenix.VerifiedRoutes
|
|
||||||
|
|
||||||
alias LolAnalyticsWeb.ChampionLive.Mapper
|
|
||||||
alias LolAnalyticsWeb.ChampionLive.Components.ChampionFilters
|
|
||||||
|
|
||||||
@behaviour LolAnalyticsWeb.ChampionFilters.EventHandler
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
socket =
|
|
||||||
socket
|
|
||||||
|> assign(:champions, %{status: :loading})
|
|
||||||
|> load_champs("all")
|
|
||||||
|> assign(:selected_role, "all")
|
|
||||||
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_params(params, _uri, socket) do
|
|
||||||
socket =
|
|
||||||
case params["role"] do
|
|
||||||
role -> {:noreply, assign(socket, selected_role: role)}
|
|
||||||
_ -> {:noreply, socket}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_event("filter", %{"role" => selected_role} = params, socket) do
|
|
||||||
{:reply, %{},
|
|
||||||
socket
|
|
||||||
|> push_navigate(to: ~p"/champions?#{params}")
|
|
||||||
|> assign(:champions, %{status: :loading})
|
|
||||||
|> load_champs(selected_role)
|
|
||||||
|> assign(:selected_role, selected_role)}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp load_champs(socket, selected_role) do
|
|
||||||
socket
|
|
||||||
|> start_async(
|
|
||||||
:get_champs,
|
|
||||||
fn ->
|
|
||||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.get_win_rates()
|
|
||||||
|> filter_champs(selected_role)
|
|
||||||
end
|
|
||||||
)
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_async(:get_champs, {:ok, fetched_events} = params, socket) do
|
|
||||||
{:noreply, assign(socket, :champions, %{status: :data, data: fetched_events})}
|
|
||||||
end
|
|
||||||
|
|
||||||
def render_champions(assigns) do
|
|
||||||
case assigns.champions do
|
|
||||||
%{status: :loading} ->
|
|
||||||
~H"""
|
|
||||||
<.loader />
|
|
||||||
"""
|
|
||||||
|
|
||||||
%{status: :data, data: champions} ->
|
|
||||||
~H"""
|
|
||||||
<div id="champions" class="grid grid-cols-4 gap-4">
|
|
||||||
<%= for champion <- champions do %>
|
|
||||||
<.champion_card props={champion} />
|
|
||||||
<% end %>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp filter_champs(champs, selected_role) do
|
|
||||||
champs =
|
|
||||||
LolAnalytics.Facts.ChampionPlayedGame.Repo.get_win_rates()
|
|
||||||
|> Enum.filter((&filter_role/1).(selected_role))
|
|
||||||
|> Mapper.map_champs()
|
|
||||||
|> Enum.sort(&(&1.win_rate >= &2.win_rate))
|
|
||||||
end
|
|
||||||
|
|
||||||
defp filter_role(role) do
|
|
||||||
fn champ ->
|
|
||||||
champ.team_position == role || role == "all"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_params(params, _url, socket) do
|
|
||||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp apply_action(socket, :index, _params) do
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, "Listing Champions")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<.header>
|
|
||||||
Champions
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<h1>Filters</h1>
|
|
||||||
|
|
||||||
|
|
||||||
<.live_component module={ChampionFilters} id="role-filters" selectedrole={@selected_role} />
|
|
||||||
|
|
||||||
<.render_champions champions={@champions} />
|
|
||||||
|
|
||||||
|
|
||||||
<.modal :if={@live_action in [:new, :edit]} id="champion-modal" show on_cancel={JS.patch(~p"/champions")}>
|
|
||||||
<.live_component module={LoLAnalyticsWeb.ChampionLive.FormComponent} id={@champion.champion_id || :new}
|
|
||||||
title={@page_title} action={@live_action} champion={@champion} patch={~p"/champions"} />
|
|
||||||
</.modal>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionLive.Mapper do
|
|
||||||
alias LolAnalyticsWeb.ChampionLive.ChampionSummary
|
|
||||||
|
|
||||||
def map_champs(champs) do
|
|
||||||
champs
|
|
||||||
|> Enum.map(fn champ ->
|
|
||||||
%{
|
|
||||||
champ
|
|
||||||
| win_rate: :erlang.float_to_binary(champ.win_rate, decimals: 2)
|
|
||||||
}
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,39 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.ChampionLive.Show do
|
|
||||||
use LoLAnalyticsWeb, :live_view
|
|
||||||
|
|
||||||
import LolAnalyticsWeb.ChampionComponents.SummonerSpell
|
|
||||||
import LolAnalyticsWeb.ChampionComponents.ChampionAvatar
|
|
||||||
|
|
||||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_params(%{"id" => id}, _, socket) do
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))
|
|
||||||
|> assign(:champion, load_champion_info(id) |> ShowMapper.map_champion())
|
|
||||||
|> assign(:summoner_spells, %{
|
|
||||||
summoner_spells: load_summoner_spells(id) |> ShowMapper.map_spells()
|
|
||||||
})}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp load_summoner_spells(champion_id) do
|
|
||||||
LolAnalytics.Facts.ChampionPickedSummonerSpell.Repo.get_champion_picked_summoners(champion_id)
|
|
||||||
end
|
|
||||||
|
|
||||||
defp load_champion_info(champion_id) do
|
|
||||||
champion = LolAnalytics.Dimensions.Champion.ChampionRepo.get_or_create(champion_id)
|
|
||||||
|
|
||||||
IO.inspect(champion)
|
|
||||||
|
|
||||||
champion
|
|
||||||
end
|
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Champion"
|
|
||||||
defp page_title(:edit), do: "Edit Champion"
|
|
||||||
end
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<.header>
|
|
||||||
Champion <%= @champion.id %>
|
|
||||||
<:actions></:actions>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.champion_avatar id={@champion.id} name={@champion.name}
|
|
||||||
image={"https://ddragon.leagueoflegends.com/cdn/14.11.1/img/champion/#{@champion.image}"} />
|
|
||||||
|
|
||||||
<div class="my-4" />
|
|
||||||
|
|
||||||
<.summoner_spells spells={@summoner_spells} />
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
defmodule LolAnalyticsWeb.ChampionComponents.SummonerSpells.ShowMapper do
|
|
||||||
alias LolAnalyticsWeb.ChampionComponents.SummonerSpells.SummonerSpell
|
|
||||||
|
|
||||||
@spec map_champion(%LolAnalytics.Dimensions.Champion.ChampionSchema{}) :: map()
|
|
||||||
def map_champion(champion) do
|
|
||||||
%{
|
|
||||||
id: champion.champion_id,
|
|
||||||
name: champion.name,
|
|
||||||
image: champion.image
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
def map_spells(items) do
|
|
||||||
items
|
|
||||||
|> Enum.map(&map_spell/1)
|
|
||||||
|> Enum.sort(&(&1.spell.total_games > &2.spell.total_games))
|
|
||||||
end
|
|
||||||
|
|
||||||
def map_spell(spell) do
|
|
||||||
image = spell.metadata["image"]["full"]
|
|
||||||
|
|
||||||
%LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props{
|
|
||||||
spell: %LolAnalyticsWeb.ChampionComponents.SummonerSpells.Props.SummonerSpell{
|
|
||||||
id: spell.id,
|
|
||||||
win_rate: :erlang.float_to_binary(spell.win_rate, decimals: 2),
|
|
||||||
total_games: spell.total_games,
|
|
||||||
image: "https://ddragon.leagueoflegends.com/cdn/14.11.1/img/spell/#{image}",
|
|
||||||
name: spell.metadata["name"],
|
|
||||||
wins: spell.wins
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.RoleLive.FormComponent do
|
|
||||||
use LoLAnalyticsWeb, :live_component
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def render(assigns) do
|
|
||||||
~H"""
|
|
||||||
<div>
|
|
||||||
<.header>
|
|
||||||
<%= @title %>
|
|
||||||
<:subtitle>Use this form to manage role records in your database.</:subtitle>
|
|
||||||
</.header>
|
|
||||||
|
|
||||||
<.simple_form
|
|
||||||
for={@form}
|
|
||||||
id="role-form"
|
|
||||||
phx-target={@myself}
|
|
||||||
phx-change="validate"
|
|
||||||
phx-submit="save"
|
|
||||||
>
|
|
||||||
<:actions>
|
|
||||||
<.button phx-disable-with="Saving...">Save Role</.button>
|
|
||||||
</:actions>
|
|
||||||
</.simple_form>
|
|
||||||
</div>
|
|
||||||
"""
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.RoleLive.Index do
|
|
||||||
use LoLAnalyticsWeb, :live_view
|
|
||||||
|
|
||||||
@roles ["ALL", "TOP", "MIDDLE", "JUNGLE", "UTILITY", "BOTTOM"]
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
{:ok, stream(socket, :roles, @roles)}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_params(params, _url, socket) do
|
|
||||||
{:noreply, apply_action(socket, socket.assigns.live_action, params)}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp apply_action(socket, :index, _params) do
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, "Listing Roles")
|
|
||||||
|> assign(:role, nil)
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_info({LoLAnalyticsWeb.RoleLive.FormComponent, {:saved, role}}, socket) do
|
|
||||||
{:noreply, stream_insert(socket, :roles, role)}
|
|
||||||
end
|
|
||||||
|
|
||||||
# @impl true
|
|
||||||
# def handle_event("delete", %{"id" => id}, socket) do
|
|
||||||
# role = Accounts.get_role!(id)
|
|
||||||
# {:ok, _} = Accounts.delete_role(role)
|
|
||||||
|
|
||||||
# {:noreply, stream_delete(socket, :roles, role)}
|
|
||||||
# end
|
|
||||||
end
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<.header>
|
|
||||||
Listing Roles
|
|
||||||
<:actions></:actions>
|
|
||||||
</.header>
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.RoleLive.Show do
|
|
||||||
use LoLAnalyticsWeb, :live_view
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def mount(_params, _session, socket) do
|
|
||||||
{:ok, socket}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_params(%{"id" => _id}, _, socket) do
|
|
||||||
{:noreply,
|
|
||||||
socket
|
|
||||||
|> assign(:page_title, page_title(socket.assigns.live_action))}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp page_title(:show), do: "Show Role"
|
|
||||||
defp page_title(:edit), do: "Edit Role"
|
|
||||||
end
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<.header>
|
|
||||||
Role <%= @role.id %>
|
|
||||||
<:subtitle>This is a role record from your database.</:subtitle>
|
|
||||||
<:actions></:actions>
|
|
||||||
</.header>
|
|
||||||
@@ -18,12 +18,6 @@ defmodule LoLAnalyticsWeb.Router do
|
|||||||
pipe_through :browser
|
pipe_through :browser
|
||||||
|
|
||||||
get "/", PageController, :home
|
get "/", PageController, :home
|
||||||
live "/champions", ChampionLive.Index, :index
|
|
||||||
live "/champions/new", ChampionLive.Index, :new
|
|
||||||
live "/champions/:id/edit", ChampionLive.Index, :edit
|
|
||||||
|
|
||||||
live "/champions/:id", ChampionLive.Show, :show
|
|
||||||
live "/champions/:id/show/edit", ChampionLive.Show, :edit
|
|
||||||
end
|
end
|
||||||
|
|
||||||
# Other scopes may use custom stacks.
|
# Other scopes may use custom stacks.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 152 B |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 12 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.5 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 6.6 KiB |
|
Before Width: | Height: | Size: 7.9 KiB |
@@ -1,5 +0,0 @@
|
|||||||
# See https://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
|
|
||||||
#
|
|
||||||
# To ban all spiders from the entire site uncomment the next two lines:
|
|
||||||
# User-agent: *
|
|
||||||
# Disallow: /
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.ChampionLiveTest do
|
|
||||||
use LoLAnalyticsWeb.ConnCase
|
|
||||||
|
|
||||||
import Phoenix.LiveViewTest
|
|
||||||
import LoLAnalytics.AccountsFixtures
|
|
||||||
|
|
||||||
@create_attrs %{}
|
|
||||||
@update_attrs %{}
|
|
||||||
@invalid_attrs %{}
|
|
||||||
|
|
||||||
defp create_champion(_) do
|
|
||||||
champion = champion_fixture()
|
|
||||||
%{champion: champion}
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "Index" do
|
|
||||||
setup [:create_champion]
|
|
||||||
|
|
||||||
test "lists all champions", %{conn: conn} do
|
|
||||||
{:ok, _index_live, html} = live(conn, ~p"/champions")
|
|
||||||
|
|
||||||
assert html =~ "Listing Champions"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "saves new champion", %{conn: conn} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
|
||||||
|
|
||||||
assert index_live |> element("a", "New Champion") |> render_click() =~
|
|
||||||
"New Champion"
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/champions/new")
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#champion-form", champion: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#champion-form", champion: @create_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/champions")
|
|
||||||
|
|
||||||
html = render(index_live)
|
|
||||||
assert html =~ "Champion created successfully"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "updates champion in listing", %{conn: conn, champion: champion} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
|
||||||
|
|
||||||
assert index_live |> element("#champions-#{champion.id} a", "Edit") |> render_click() =~
|
|
||||||
"Edit Champion"
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/champions/#{champion}/edit")
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#champion-form", champion: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#champion-form", champion: @update_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/champions")
|
|
||||||
|
|
||||||
html = render(index_live)
|
|
||||||
assert html =~ "Champion updated successfully"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "deletes champion in listing", %{conn: conn, champion: champion} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/champions")
|
|
||||||
|
|
||||||
assert index_live |> element("#champions-#{champion.id} a", "Delete") |> render_click()
|
|
||||||
refute has_element?(index_live, "#champions-#{champion.id}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "Show" do
|
|
||||||
setup [:create_champion]
|
|
||||||
|
|
||||||
test "displays champion", %{conn: conn, champion: champion} do
|
|
||||||
{:ok, _show_live, html} = live(conn, ~p"/champions/#{champion}")
|
|
||||||
|
|
||||||
assert html =~ "Show Champion"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "updates champion within modal", %{conn: conn, champion: champion} do
|
|
||||||
{:ok, show_live, _html} = live(conn, ~p"/champions/#{champion}")
|
|
||||||
|
|
||||||
assert show_live |> element("a", "Edit") |> render_click() =~
|
|
||||||
"Edit Champion"
|
|
||||||
|
|
||||||
assert_patch(show_live, ~p"/champions/#{champion}/show/edit")
|
|
||||||
|
|
||||||
assert show_live
|
|
||||||
|> form("#champion-form", champion: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert show_live
|
|
||||||
|> form("#champion-form", champion: @update_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(show_live, ~p"/champions/#{champion}")
|
|
||||||
|
|
||||||
html = render(show_live)
|
|
||||||
assert html =~ "Champion updated successfully"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
defmodule LoLAnalyticsWeb.RoleLiveTest do
|
|
||||||
use LoLAnalyticsWeb.ConnCase
|
|
||||||
|
|
||||||
import Phoenix.LiveViewTest
|
|
||||||
import LoLAnalytics.AccountsFixtures
|
|
||||||
|
|
||||||
@create_attrs %{}
|
|
||||||
@update_attrs %{}
|
|
||||||
@invalid_attrs %{}
|
|
||||||
|
|
||||||
defp create_role(_) do
|
|
||||||
role = role_fixture()
|
|
||||||
%{role: role}
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "Index" do
|
|
||||||
setup [:create_role]
|
|
||||||
|
|
||||||
test "lists all roles", %{conn: conn} do
|
|
||||||
{:ok, _index_live, html} = live(conn, ~p"/roles")
|
|
||||||
|
|
||||||
assert html =~ "Listing Roles"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "saves new role", %{conn: conn} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
|
||||||
|
|
||||||
assert index_live |> element("a", "New Role") |> render_click() =~
|
|
||||||
"New Role"
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/roles/new")
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#role-form", role: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#role-form", role: @create_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/roles")
|
|
||||||
|
|
||||||
html = render(index_live)
|
|
||||||
assert html =~ "Role created successfully"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "updates role in listing", %{conn: conn, role: role} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
|
||||||
|
|
||||||
assert index_live |> element("#roles-#{role.id} a", "Edit") |> render_click() =~
|
|
||||||
"Edit Role"
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/roles/#{role}/edit")
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#role-form", role: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert index_live
|
|
||||||
|> form("#role-form", role: @update_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(index_live, ~p"/roles")
|
|
||||||
|
|
||||||
html = render(index_live)
|
|
||||||
assert html =~ "Role updated successfully"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "deletes role in listing", %{conn: conn, role: role} do
|
|
||||||
{:ok, index_live, _html} = live(conn, ~p"/roles")
|
|
||||||
|
|
||||||
assert index_live |> element("#roles-#{role.id} a", "Delete") |> render_click()
|
|
||||||
refute has_element?(index_live, "#roles-#{role.id}")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "Show" do
|
|
||||||
setup [:create_role]
|
|
||||||
|
|
||||||
test "displays role", %{conn: conn, role: role} do
|
|
||||||
{:ok, _show_live, html} = live(conn, ~p"/roles/#{role}")
|
|
||||||
|
|
||||||
assert html =~ "Show Role"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "updates role within modal", %{conn: conn, role: role} do
|
|
||||||
{:ok, show_live, _html} = live(conn, ~p"/roles/#{role}")
|
|
||||||
|
|
||||||
assert show_live |> element("a", "Edit") |> render_click() =~
|
|
||||||
"Edit Role"
|
|
||||||
|
|
||||||
assert_patch(show_live, ~p"/roles/#{role}/show/edit")
|
|
||||||
|
|
||||||
assert show_live
|
|
||||||
|> form("#role-form", role: @invalid_attrs)
|
|
||||||
|> render_change() =~ "can't be blank"
|
|
||||||
|
|
||||||
assert show_live
|
|
||||||
|> form("#role-form", role: @update_attrs)
|
|
||||||
|> render_submit()
|
|
||||||
|
|
||||||
assert_patch(show_live, ~p"/roles/#{role}")
|
|
||||||
|
|
||||||
html = render(show_live)
|
|
||||||
assert html =~ "Role updated successfully"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
@@ -18,7 +18,7 @@ defmodule LoLAPI.AccountApi do
|
|||||||
200 ->
|
200 ->
|
||||||
{:ok, Poison.decode(response.body)}
|
{:ok, Poison.decode(response.body)}
|
||||||
|
|
||||||
_code ->
|
code ->
|
||||||
Logger.error("Error getting puuid from player #{name} \##{tag}")
|
Logger.error("Error getting puuid from player #{name} \##{tag}")
|
||||||
{:err, response.status_code}
|
{:err, response.status_code}
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,9 +1,19 @@
|
|||||||
defmodule Scrapper.MatchClassifier do
|
defmodule Scrapper.MatchClassifier do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
def stream_classify_matches_by_queue(queue \\ 420, bucket \\ "ranked") do
|
@spec classify_match(%LoLAPI.Model.MatchResponse{}) :: nil
|
||||||
Storage.MatchStorage.S3MatchStorage.stream_files("matches")
|
def classify_match(match = %LoLAPI.Model.MatchResponse{}) do
|
||||||
|> Stream.each(fn match ->
|
classify_match_by_queue(match.info.queueId)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec classify_match_by_queue(String.t()) :: nil
|
||||||
|
def classify_match_by_queue("420") do
|
||||||
|
matches = Storage.MatchStorage.S3MatchStorage.list_files("matches")
|
||||||
|
total_matches = Enum.count(matches)
|
||||||
|
|
||||||
|
matches
|
||||||
|
|> Enum.with_index(fn match, index -> {match, index} end)
|
||||||
|
|> Scrapper.Parallel.peach(fn {match, index} ->
|
||||||
%{key: json_file} = match
|
%{key: json_file} = match
|
||||||
[key | _] = String.split(json_file, ".")
|
[key | _] = String.split(json_file, ".")
|
||||||
|
|
||||||
@@ -15,12 +25,15 @@ defmodule Scrapper.MatchClassifier do
|
|||||||
%{"info" => %{"gameVersion" => gameVersion, "queueId" => queueId}} =
|
%{"info" => %{"gameVersion" => gameVersion, "queueId" => queueId}} =
|
||||||
Poison.decode!(response.body)
|
Poison.decode!(response.body)
|
||||||
|
|
||||||
if queueId == queue do
|
if queueId == 420 do
|
||||||
Storage.MatchStorage.S3MatchStorage.store_match(key, response.body, bucket, gameVersion)
|
Storage.MatchStorage.S3MatchStorage.store_match(key, response.body, "ranked", gameVersion)
|
||||||
Logger.info("Match #{key} processed")
|
Logger.info("Match at #{index} of #{total_matches} is classified")
|
||||||
end
|
end
|
||||||
|
|
||||||
match
|
match
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def classify_match_by_queue(_) do
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -66,6 +66,6 @@ defmodule Scrapper.Processor.MatchProcessor do
|
|||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_resp({:err, _code}, _match_id) do
|
def process_resp({:err, code}, match_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
defmodule Scrapper.Processor.PlayerProcessor do
|
defmodule Scrapper.Processor.PlayerProcessor do
|
||||||
|
alias Calendar.ISO
|
||||||
use Broadway
|
use Broadway
|
||||||
|
|
||||||
def start_link(_opts) do
|
def start_link(_opts) do
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ defmodule Scrapper.Queue.MatchQueue do
|
|||||||
LolAnalytics.Match.MatchRepo.get_match(match_id)
|
LolAnalytics.Match.MatchRepo.get_match(match_id)
|
||||||
|> case do
|
|> case do
|
||||||
nil -> GenServer.call(__MODULE__, {:queue_match, match_id})
|
nil -> GenServer.call(__MODULE__, {:queue_match, match_id})
|
||||||
_match -> :already_processed
|
match -> :already_processed
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
defmodule Storage.MatchStorage do
|
defmodule Storage.MatchStorage do
|
||||||
@callback stream_files(String.t()) :: Enumerable.t()
|
|
||||||
@callback get_match(String.t()) :: {:ok, Scrapper.Data.Match.t()} | {:error, :not_found}
|
@callback get_match(String.t()) :: {:ok, Scrapper.Data.Match.t()} | {:error, :not_found}
|
||||||
@callback store_match(match_id :: String.t(), match_data :: String.t(), path :: String.t()) ::
|
@callback save_match(String.t(), Scrapper.Data.Match.t()) :: :ok
|
||||||
String.t()
|
@callback list_matches() :: [map()]
|
||||||
@callback store_match(
|
@callback store_match(match_id :: String.t(), match :: map(), path :: String.t()) :: String.t()
|
||||||
match_id :: String.t(),
|
|
||||||
match_data :: String.t(),
|
|
||||||
bucket :: String.t(),
|
|
||||||
path :: String.t()
|
|
||||||
) ::
|
|
||||||
String.t()
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -2,14 +2,43 @@ defmodule Storage.MatchStorage.S3MatchStorage do
|
|||||||
require Logger
|
require Logger
|
||||||
@behaviour Storage.MatchStorage
|
@behaviour Storage.MatchStorage
|
||||||
|
|
||||||
@impl true
|
def get_match(match_id) do
|
||||||
def get_match(_match_id) do
|
""
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Lists all files at the given path.
|
||||||
|
|
||||||
|
iex > Storage.MatchStorage.S3MatchStorage.list_files("matches")
|
||||||
|
"""
|
||||||
@impl true
|
@impl true
|
||||||
def stream_files(path) do
|
def list_files(path) do
|
||||||
ExAws.S3.list_objects_v2(path)
|
{:ok, %{:body => %{:contents => contents, next_continuation_token: next_continuation_token}}} =
|
||||||
|> ExAws.stream!()
|
ExAws.S3.list_objects_v2(path)
|
||||||
|
|> ExAws.request()
|
||||||
|
|
||||||
|
if next_continuation_token do
|
||||||
|
list_files(path, contents, next_continuation_token)
|
||||||
|
# |> Enum.map(fn %{key: key} -> key end)
|
||||||
|
else
|
||||||
|
contents
|
||||||
|
# |> Enum.map(fn %{key: key} -> key end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec list_files(String.t(), list(String.t()), String.t()) :: list(String.t())
|
||||||
|
defp list_files(path, acc, continuation_token) do
|
||||||
|
resp =
|
||||||
|
{:ok,
|
||||||
|
%{:body => %{:contents => contents, next_continuation_token: next_continuation_token}}} =
|
||||||
|
ExAws.S3.list_objects_v2(path, continuation_token: continuation_token)
|
||||||
|
|> ExAws.request()
|
||||||
|
|
||||||
|
if next_continuation_token == "" do
|
||||||
|
acc ++ contents
|
||||||
|
else
|
||||||
|
list_files(path, acc ++ contents, next_continuation_token)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
|
|||||||
@@ -0,0 +1,101 @@
|
|||||||
|
# Name of your application. Used to uniquely configure containers.
|
||||||
|
service: my-app
|
||||||
|
|
||||||
|
# Name of the container image.
|
||||||
|
image: user/my-app
|
||||||
|
|
||||||
|
# Deploy to these servers.
|
||||||
|
servers:
|
||||||
|
- 192.168.0.1
|
||||||
|
|
||||||
|
# Credentials for your image host.
|
||||||
|
registry:
|
||||||
|
# Specify the registry server, if you're not using Docker Hub
|
||||||
|
# server: registry.digitalocean.com / ghcr.io / ...
|
||||||
|
username: my-user
|
||||||
|
|
||||||
|
# Always use an access token rather than real password when possible.
|
||||||
|
password:
|
||||||
|
- KAMAL_REGISTRY_PASSWORD
|
||||||
|
|
||||||
|
# Inject ENV variables into containers (secrets come from .env).
|
||||||
|
# Remember to run `kamal env push` after making changes!
|
||||||
|
# env:
|
||||||
|
# clear:
|
||||||
|
# DB_HOST: 192.168.0.2
|
||||||
|
# secret:
|
||||||
|
# - RAILS_MASTER_KEY
|
||||||
|
|
||||||
|
# Use a different ssh user than root
|
||||||
|
# ssh:
|
||||||
|
# user: app
|
||||||
|
|
||||||
|
# Configure builder setup.
|
||||||
|
# builder:
|
||||||
|
# args:
|
||||||
|
# RUBY_VERSION: 3.2.0
|
||||||
|
# secrets:
|
||||||
|
# - GITHUB_TOKEN
|
||||||
|
# remote:
|
||||||
|
# arch: amd64
|
||||||
|
# host: ssh://app@192.168.0.1
|
||||||
|
|
||||||
|
# Use accessory services (secrets come from .env).
|
||||||
|
# accessories:
|
||||||
|
# db:
|
||||||
|
# image: mysql:8.0
|
||||||
|
# host: 192.168.0.2
|
||||||
|
# port: 3306
|
||||||
|
# env:
|
||||||
|
# clear:
|
||||||
|
# MYSQL_ROOT_HOST: '%'
|
||||||
|
# secret:
|
||||||
|
# - MYSQL_ROOT_PASSWORD
|
||||||
|
# files:
|
||||||
|
# - config/mysql/production.cnf:/etc/mysql/my.cnf
|
||||||
|
# - db/production.sql:/docker-entrypoint-initdb.d/setup.sql
|
||||||
|
# directories:
|
||||||
|
# - data:/var/lib/mysql
|
||||||
|
# redis:
|
||||||
|
# image: redis:7.0
|
||||||
|
# host: 192.168.0.2
|
||||||
|
# port: 6379
|
||||||
|
# directories:
|
||||||
|
# - data:/data
|
||||||
|
|
||||||
|
# Configure custom arguments for Traefik. Be sure to reboot traefik when you modify it.
|
||||||
|
# traefik:
|
||||||
|
# args:
|
||||||
|
# accesslog: true
|
||||||
|
# accesslog.format: json
|
||||||
|
|
||||||
|
# Configure a custom healthcheck (default is /up on port 3000)
|
||||||
|
# healthcheck:
|
||||||
|
# path: /healthz
|
||||||
|
# port: 4000
|
||||||
|
|
||||||
|
# Bridge fingerprinted assets, like JS and CSS, between versions to avoid
|
||||||
|
# hitting 404 on in-flight requests. Combines all files from new and old
|
||||||
|
# version inside the asset_path.
|
||||||
|
#
|
||||||
|
# If your app is using the Sprockets gem, ensure it sets `config.assets.manifest`.
|
||||||
|
# See https://github.com/basecamp/kamal/issues/626 for details
|
||||||
|
#
|
||||||
|
# asset_path: /rails/public/assets
|
||||||
|
|
||||||
|
# Configure rolling deploys by setting a wait time between batches of restarts.
|
||||||
|
# boot:
|
||||||
|
# limit: 10 # Can also specify as a percentage of total hosts, such as "25%"
|
||||||
|
# wait: 2
|
||||||
|
|
||||||
|
# Configure the role used to determine the primary_host. This host takes
|
||||||
|
# deploy locks, runs health checks during the deploy, and follow logs, etc.
|
||||||
|
#
|
||||||
|
# Caution: there's no support for role renaming yet, so be careful to cleanup
|
||||||
|
# the previous role on the deployed hosts.
|
||||||
|
# primary_role: web
|
||||||
|
|
||||||
|
# Controls if we abort when see a role with no hosts. Disabling this may be
|
||||||
|
# useful for more complex deploy configurations.
|
||||||
|
#
|
||||||
|
# allow_empty_roles: false
|
||||||
@@ -8,7 +8,7 @@ import Config
|
|||||||
config :lol_analytics_web, LoLAnalyticsWeb.Endpoint,
|
config :lol_analytics_web, LoLAnalyticsWeb.Endpoint,
|
||||||
server: true,
|
server: true,
|
||||||
http: [ip: {0, 0, 0, 0}, port: 4000],
|
http: [ip: {0, 0, 0, 0}, port: 4000],
|
||||||
url: [host: "localhost", port: 80],
|
url: [host: "lol-analytics.kaizer.cloud", port: 80],
|
||||||
cache_static_manifest: "priv/static/cache_manifest.json"
|
cache_static_manifest: "priv/static/cache_manifest.json"
|
||||||
|
|
||||||
# Do not print debug messages in production
|
# Do not print debug messages in production
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import Config
|
|||||||
# and secrets from environment variables or elsewhere. Do not define
|
# and secrets from environment variables or elsewhere. Do not define
|
||||||
# any compile-time configuration in here, as it won't be applied.
|
# any compile-time configuration in here, as it won't be applied.
|
||||||
# The block below contains prod specific runtime configuration.
|
# The block below contains prod specific runtime configuration.
|
||||||
if config_env() == :prod || true do
|
if config_env() == :prod do
|
||||||
database_url =
|
database_url =
|
||||||
System.get_env("DATABASE_URL") ||
|
System.get_env("DATABASE_URL") ||
|
||||||
raise """
|
raise """
|
||||||
|
|||||||
@@ -1,36 +0,0 @@
|
|||||||
version: '3.8'
|
|
||||||
services:
|
|
||||||
db:
|
|
||||||
image: postgres:latest
|
|
||||||
volumes:
|
|
||||||
- postgres-data:/var/lib/postgresql/data
|
|
||||||
environment:
|
|
||||||
POSTGRES_PASSWORD: postgres
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_DB: postgres
|
|
||||||
ports:
|
|
||||||
- "5432:5432"
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:3-management-alpine
|
|
||||||
container_name: 'rabbitmq'
|
|
||||||
ports:
|
|
||||||
- "15671:15671"
|
|
||||||
- "15672:15672"
|
|
||||||
- "15691:15691"
|
|
||||||
- "15692:15692"
|
|
||||||
- "25672:25672"
|
|
||||||
- "4639:4639"
|
|
||||||
- "5671:5671"
|
|
||||||
- "5672:5672"
|
|
||||||
volumes:
|
|
||||||
- ~/.docker-conf/rabbitmq/data/:/var/lib/rabbitmq/
|
|
||||||
- ~/.docker-conf/rabbitmq/log/:/var/log/rabbitmq
|
|
||||||
networks:
|
|
||||||
- rabbitmq_go_net
|
|
||||||
volumes:
|
|
||||||
postgres-data:
|
|
||||||
|
|
||||||
|
|
||||||
networks:
|
|
||||||
rabbitmq_go_net:
|
|
||||||
driver: bridge
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
```
|
|
||||||
SELECT
|
|
||||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
|
||||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
|
||||||
count(*) as total_games,
|
|
||||||
champion_id
|
|
||||||
FROM fact_champion_played_game
|
|
||||||
GROUP BY champion_id
|
|
||||||
ORDER BY win_rate desc;
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
SELECT
|
|
||||||
(cast(count(CASE WHEN is_win THEN 1 END) as float) / cast(count(*) as float)) * 100.0 as win_rate,
|
|
||||||
count(CASE WHEN is_win THEN 1 END) as games_won,
|
|
||||||
count(*) as total_games,
|
|
||||||
champion_id,
|
|
||||||
team_position
|
|
||||||
FROM fact_champion_played_game
|
|
||||||
GROUP BY champion_id, team_position
|
|
||||||
ORDER BY win_rate desc;
|
|
||||||
```
|
|
||||||