├── .ignore
├── priv
├── repo
│ ├── csv_seed_data
│ │ ├── owners.csv
│ │ ├── champ_with_events_results.csv
│ │ ├── waivers.csv
│ │ ├── fantasy_leagues.csv
│ │ ├── franchises.csv
│ │ ├── trades.csv
│ │ ├── trade_line_items.csv
│ │ ├── final_rankings.csv
│ │ ├── sports_leagues.csv
│ │ ├── league_sports.csv
│ │ ├── championship_results.csv
│ │ ├── fantasy_teams.csv
│ │ └── championship_slots.csv
│ ├── migrations
│ │ ├── 20160731103547_add_admin_to_user.exs
│ │ ├── 20161002161106_add_process_at_to_waiver.exs
│ │ ├── 20160908231000_add_abbrev_to_sports_league.exs
│ │ ├── 20170423182027_add_year_to_championship.exs
│ │ ├── 20181110154218_add_slack_name_to_users_table.exs
│ │ ├── 20181112232239_add_order_to_historical_records.exs
│ │ ├── 20190209231153_add_drafted_at_to_draft_pick.exs
│ │ ├── 20170528152505_add_winnings_adj_to_fantasy_team.exs
│ │ ├── 20180728192433_add_max_flex_to_fantasy_league.exs
│ │ ├── 20190929195252_add_max_flex_adj_to_fantasy_team.exs
│ │ ├── 20161028181031_add_commish_notes_to_fantasy_team.exs
│ │ ├── 20170210224425_add_overall_id_to_championship.exs
│ │ ├── 20170408221017_add_draft_pick_to_fantasy_player.exs
│ │ ├── 20220804165234_add_draft_starts_at_to_championships.exs
│ │ ├── 20250723040130_add_is_keeper_to_draft_picks.exs
│ │ ├── 20170305200054_add_hide_waivers_to_sports_league.exs
│ │ ├── 20220802040558_add_max_draft_mins_to_champioships.exs
│ │ ├── 20250419232727_alter_trade_additional_terms_to_text.exs
│ │ ├── 20170408220019_add_in_season_draft_to_championship.exs
│ │ ├── 20180322222911_add_sport_draft_to_fantasy_league.exs
│ │ ├── 20190713193904_add_max_draft_hours_to_fantasy_league.exs
│ │ ├── 20190721005538_add_acq_method_to_roster_positions.exs
│ │ ├── 20220802041502_add_drafted_at_to_in_season_draft_picks.exs
│ │ ├── 20160806113743_create_trade.exs
│ │ ├── 20161123212323_add_active_at_to_roster_position.exs
│ │ ├── 20190922222537_add_total_draft_mins_adj_to_fantasy_team.exs
│ │ ├── 20170214145352_update_points_winnings_to_float.exs
│ │ ├── 20170923130822_add_active_years_to_players.exs
│ │ ├── 20250725042432_add_draft_picks_locked_to_fantasy_leagues.exs
│ │ ├── 20250802230356_add_draft_fields_to_fantasy_teams.exs
│ │ ├── 20160709234145_create_fantasy_league.exs
│ │ ├── 20181024014213_create_historical_winnings.exs
│ │ ├── 20160928222113_add_winnings_dues_to_fantasy_team.exs
│ │ ├── 20160921232648_add_status_and_date_to_roster_position.exs
│ │ ├── 20190908210456_add_start_end_dates_to_fantasy_player.exs
│ │ ├── 20240331033202_create_chats.exs
│ │ ├── 20200816034556_add_new_options_to_fantasy_league.exs
│ │ ├── 20171203180919_create_unique_index_for_trade_vote.exs
│ │ ├── 20190803213203_add_championship_date_range_to_fantasy_league.exs
│ │ ├── 20160722030421_remove_round_column_from_draft_pick.exs
│ │ ├── 20200908035227_rename_ir_add_player_column.exs
│ │ ├── 20160925153823_add_default_status_to_waiver.exs
│ │ ├── 20190923002500_create_unique_index_for_draft_picks.exs
│ │ ├── 20160710122608_create_roster_transaction.exs
│ │ ├── 20200531155101_add_future_pick_to_trade_line_items.exs
│ │ ├── 20160724210543_add_name_to_fantasy_league.exs
│ │ ├── 20200908164944_remove_status_col_from_injured_reserves.exs
│ │ ├── 20160710012444_create_sports_league.exs
│ │ ├── 20161012003424_add_null_constraint_to_position.exs
│ │ ├── 20190930000845_create_unique_index_for_in_season_draft_picks.exs
│ │ ├── 20160722051921_change_decimal_to_float_on_draft_pick.exs
│ │ ├── 20160925160332_add_default_position_to_roster_position.exs
│ │ ├── 20160710012609_create_fantasy_player.exs
│ │ ├── 20160730191328_create_coherence_invitable.exs
│ │ ├── 20181024004723_create_historical_records.exs
│ │ ├── 20220811205302_add_oban_jobs_table.exs
│ │ ├── 20190604013901_add_pow_invitation_to_users.exs
│ │ ├── 20160806131130_drop_roster_transaction.exs
│ │ ├── 20180115062531_add_status_to_draft_queue.exs
│ │ ├── 20190929223403_add_fantasy_league_to_in_season_draft_pick.exs
│ │ ├── 20200525125001_add_rules_to_owners.exs
│ │ ├── 20160817234415_create_owner.exs
│ │ ├── 20200818041024_update_default_for_must_draft_each_sport.exs
│ │ ├── 20200218020811_delete_unused_fantasy_players_fields.exs
│ │ ├── 20160710002129_create_fantasy_team.exs
│ │ ├── 20171203023116_add_user_and_team_to_trade.exs
│ │ ├── 20181024012435_add_type_to_historical_records.exs
│ │ ├── 20161010221818_add_constraints_to_roster_position.exs
│ │ ├── 20200518234631_drop_coherence_invitations_table.exs
│ │ ├── 20240331213542_create_messages.exs
│ │ ├── 20170421014248_create_league_sport.exs
│ │ ├── 20170505002641_add_gaining_losing_teams_to_trade_line_item.exs
│ │ ├── 20170528153600_change_winnings_dues_in_fantasy_team.exs
│ │ ├── 20180324155253_add_navbar_display_to_fantasy_leagues.exs
│ │ ├── 20180908194517_add_autodraft_setting_to_fantasy_team.exs
│ │ ├── 20190616203656_add_draft_method_to_fantasy_leagues.exs
│ │ ├── 20200908165807_add_status_enum_to_injured_reserves.exs
│ │ ├── 20170306022658_delete_dates_from_sports_leagues.exs
│ │ ├── 20200530204751_create_future_picks.exs
│ │ ├── 20200531173117_create_check_constraint_for_trade_line_items.exs
│ │ ├── 20200908161109_delete_ir_remove_player_column.exs
│ │ ├── 20170116162012_create_championship_slot.exs
│ │ ├── 20160927222122_create_championship.exs
│ │ ├── 20180114222214_create_draft_queues.exs
│ │ ├── 20160710024225_create_roster_position.exs
│ │ ├── 20161114231216_create_championship_result.exs
│ │ ├── 20170525153855_remove_unused_trade_line_item_columns.exs
│ │ ├── 20170213213035_create_champ_with_events_result.exs
│ │ ├── 20171203155647_create_trade_votes.exs
│ │ ├── 20160730191329_create_coherence_rememberable.exs
│ │ ├── 20160803104408_create_waiver.exs
│ │ ├── 20160806114105_create_trade_line_item.exs
│ │ ├── 20160721220202_create_draft_pick.exs
│ │ ├── 20170408222357_create_in_season_draft_pick.exs
│ │ ├── 20200518235114_drop_coherence_rememberables_table.exs
│ │ ├── 20160710122632_create_transaction_line_item.exs
│ │ ├── 20240403232051_create_fantasy_league_drafts.exs
│ │ ├── 20170115152858_create_injured_reserve.exs
│ │ ├── 20160806130855_drop_transaction_line_item.exs
│ │ ├── 20160730191327_create_coherence_user.exs
│ │ ├── 20240308035310_create_users_auth_tables.exs
│ │ └── 20200518235225_remove_coherence_user_columns.exs
│ └── annual_league_setup
│ │ ├── data
│ │ ├── league_sports.csv
│ │ ├── owners.csv
│ │ └── fantasy_teams.csv
│ │ └── draft_data.exs
└── static
│ ├── favicon.ico
│ ├── favicon-128.png
│ ├── mstile-70x70.png
│ ├── favicon-16x16.png
│ ├── favicon-196x196.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ └── robots.txt
├── .tool-versions
├── Procfile
├── scripts
└── batch_process_pending_waivers.exs
├── README_assets
├── ex338_home_screen.png
└── ex338_fantasy_players_screen.png
├── lib
├── ex338.ex
├── ex338_web
│ ├── components
│ │ └── layouts
│ │ │ └── app.html.heex
│ ├── notifiers
│ │ ├── notifier_template.ex
│ │ └── commish_notifier.ex
│ ├── plugs
│ │ ├── user_login_redirector.ex
│ │ ├── load_leagues.ex
│ │ ├── load_user_teams.ex
│ │ └── canonical_domain.ex
│ ├── presence.ex
│ ├── controllers
│ │ ├── archived_league_controller.ex
│ │ ├── archived_league_html.ex
│ │ ├── fantasy_team_controller.ex
│ │ ├── fantasy_player_controller.ex
│ │ ├── fantasy_league
│ │ │ └── calendar_download_controller.ex
│ │ ├── owner_controller.ex
│ │ ├── error_html.ex
│ │ ├── championship_slot_admin_controller.ex
│ │ ├── fantasy_team_html.ex
│ │ ├── authorization.ex
│ │ ├── page_controller.ex
│ │ ├── table_upload_controller.ex
│ │ ├── table_upload_html.ex
│ │ ├── commish_email_controller.ex
│ │ ├── commish_email_html.ex
│ │ ├── owner_html.ex
│ │ ├── waiver_admin_html.ex
│ │ ├── user_session_controller.ex
│ │ ├── user_controller.ex
│ │ └── waiver_admin_controller.ex
│ ├── gettext.ex
│ ├── channels
│ │ └── user_socket.ex
│ ├── live
│ │ ├── commish
│ │ │ └── fantasy_league_live
│ │ │ │ └── edit.ex
│ │ ├── fantasy_league_live
│ │ │ └── standings_chart_component.ex
│ │ ├── user_invitation_live.ex
│ │ ├── user_login_live.ex
│ │ ├── user_confirmation_instructions_live.ex
│ │ └── fantasy_team_draft_queues
│ │ │ └── edit.ex
│ ├── mailer.ex
│ └── telemetry.ex
└── ex338
│ ├── events.ex
│ ├── rulebooks
│ └── rulebook.ex
│ ├── calendar_assistant.ex
│ ├── chats
│ ├── message.ex
│ └── chat.ex
│ ├── jobs.ex
│ ├── fantasy_leagues
│ ├── historical_winning.ex
│ ├── league_sport.ex
│ ├── fantasy_league_draft.ex
│ ├── fantasy_league_admin.ex
│ └── historical_record.ex
│ ├── rulebooks.ex
│ ├── repo.ex
│ ├── ecto_enums.ex
│ ├── validate_helpers.ex
│ ├── roster_positions
│ ├── ir_position.ex
│ └── admin.ex
│ ├── workers
│ └── in_season_autodraft_worker.ex
│ ├── fantasy_teams
│ ├── deadlines.ex
│ └── owner.ex
│ ├── trades
│ └── trade_vote.ex
│ ├── draft_picks
│ └── future_pick.ex
│ ├── roster_positions.ex
│ ├── fantasy_team_authorizer.ex
│ ├── abilities.ex
│ ├── championships
│ ├── championship_slot.ex
│ └── create_slot.ex
│ ├── chats.ex
│ ├── uploader.ex
│ ├── application.ex
│ ├── fantasy_players
│ └── sports_league.ex
│ ├── icalendar.ex
│ ├── waivers
│ └── batch_process.ex
│ └── injured_reserves
│ └── injured_reserve.ex
├── assets
├── .prettierrc.json
├── .eslintrc.json
├── js
│ ├── confirm_submit.js
│ ├── hooks
│ │ ├── enter-submit-hook.js
│ │ ├── index.js
│ │ ├── local-time-hooks.js
│ │ ├── chat-hooks.js
│ │ ├── vega-lite-hook.js
│ │ └── sortable-hooks.js
│ ├── draft_pick_confirm.js
│ ├── filter_players_list.js
│ └── filter_players.js
├── package.json
└── css
│ ├── app.css
│ └── live_view.css
├── test
├── fixtures
│ ├── fantasy_player_csv_table.csv
│ └── fantasy_team_csv_table.csv
├── test_helper.exs
├── ex338_web
│ ├── plugs
│ │ ├── load_leagues_test.exs
│ │ ├── canonical_domain_test.exs
│ │ └── load_user_teams_test.exs
│ ├── mailer_test.exs
│ ├── views
│ │ └── error_view_test.exs
│ ├── live
│ │ ├── user_invitation_live_test.exs
│ │ ├── championship_live
│ │ │ └── index_test.exs
│ │ └── fantasy_league_live
│ │ │ └── show_test.exs
│ └── controllers
│ │ ├── fantasy_league
│ │ └── calendar_download_controller_test.exs
│ │ ├── owner_controller_test.exs
│ │ ├── waiver_index_controller_test.exs
│ │ └── draft_pick_html_test.exs
├── ex338
│ ├── fantasy_leagues
│ │ ├── league_sport_test.exs
│ │ └── historical_winning_test.exs
│ ├── chats_test.exs
│ ├── rulebooks_test.exs
│ ├── calendar_assistant_test.exs
│ ├── jobs_test.exs
│ ├── validate_helpers_test.exs
│ ├── trades
│ │ ├── trade_vote_test.exs
│ │ └── admin_test.exs
│ ├── roster_positions
│ │ └── open_position_test.exs
│ ├── commish_notifier_test.exs
│ └── accounts
│ │ └── user_test.exs
└── support
│ ├── fixtures
│ └── accounts_fixtures.ex
│ ├── channel_case.ex
│ └── data_case.ex
├── elixir_buildpack.config
├── .well-known
└── brave-payments-verification.txt
├── .formatter.exs
├── phoenix_static_buildpack.config
├── .github
└── dependabot.yml
├── config
├── prod.exs
└── test.exs
├── LICENSE
├── .iex.exs
├── README.md
└── .gitignore
/.ignore:
--------------------------------------------------------------------------------
1 | assets/node_modules
2 | git
3 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/owners.csv:
--------------------------------------------------------------------------------
1 | Fantasy Team, User
2 | 4,1
3 | 2,2
4 |
--------------------------------------------------------------------------------
/.tool-versions:
--------------------------------------------------------------------------------
1 | elixir 1.18.3-otp-27
2 | erlang 27.3
3 | nodejs 22.14.0
4 |
--------------------------------------------------------------------------------
/Procfile:
--------------------------------------------------------------------------------
1 | release: mix ecto.migrate
2 | web: MIX_ENV=prod mix phx.server
3 |
--------------------------------------------------------------------------------
/scripts/batch_process_pending_waivers.exs:
--------------------------------------------------------------------------------
1 | Ex338.Waivers.batch_process_all()
2 |
--------------------------------------------------------------------------------
/priv/static/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon.ico
--------------------------------------------------------------------------------
/priv/static/favicon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon-128.png
--------------------------------------------------------------------------------
/priv/static/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/mstile-70x70.png
--------------------------------------------------------------------------------
/priv/static/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon-16x16.png
--------------------------------------------------------------------------------
/priv/static/favicon-196x196.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon-196x196.png
--------------------------------------------------------------------------------
/priv/static/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon-32x32.png
--------------------------------------------------------------------------------
/priv/static/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/favicon-96x96.png
--------------------------------------------------------------------------------
/priv/static/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/mstile-144x144.png
--------------------------------------------------------------------------------
/priv/static/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/mstile-150x150.png
--------------------------------------------------------------------------------
/priv/static/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/mstile-310x150.png
--------------------------------------------------------------------------------
/priv/static/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/mstile-310x310.png
--------------------------------------------------------------------------------
/README_assets/ex338_home_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/README_assets/ex338_home_screen.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/lib/ex338.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338 do
2 | @moduledoc """
3 | Ex338 keeps the contexts for the 338 application.
4 | """
5 | end
6 |
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/priv/static/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/priv/static/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/assets/.prettierrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "trailingComma": "es5",
3 | "tabWidth": 2,
4 | "semi": false,
5 | "singleQuote": false
6 | }
7 |
--------------------------------------------------------------------------------
/README_assets/ex338_fantasy_players_screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/axelclark/ex338/HEAD/README_assets/ex338_fantasy_players_screen.png
--------------------------------------------------------------------------------
/lib/ex338_web/components/layouts/app.html.heex:
--------------------------------------------------------------------------------
1 |
2 | <.flash_group flash={@flash} />
3 | {@inner_content}
4 |
5 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/champ_with_events_results.csv:
--------------------------------------------------------------------------------
1 | championship_id,fantasy_team_id,rank,points,winnings
2 | 16,6,1,8,25
3 | 16,11,2,5,15
4 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/waivers.csv:
--------------------------------------------------------------------------------
1 | Fantasy Team Id,Add Fantasy Player Id,Drop Fantasy Player Id,Status,Process At
2 | 7,306,294,pending,3
3 | 12,,268,pending,2
4 | 16,395,,pending,1
5 |
--------------------------------------------------------------------------------
/test/fixtures/fantasy_player_csv_table.csv:
--------------------------------------------------------------------------------
1 | projected_id,player_name,draft_pick,sports_league_id
2 | 734,Algeria,FALSE,1
3 | 735,Antigua and Barbuda,TRUE,1
4 | 736,Argentina,FALSE,1
5 |
--------------------------------------------------------------------------------
/elixir_buildpack.config:
--------------------------------------------------------------------------------
1 | # Erlang version
2 | erlang_version=27.3
3 |
4 | # Elixir version
5 | elixir_version=1.18.3
6 |
7 | config_vars_to_export=(SENDGRID_API_KEY)
8 |
9 | always_rebuild=true
10 |
--------------------------------------------------------------------------------
/test/test_helper.exs:
--------------------------------------------------------------------------------
1 | {:ok, _} = Application.ensure_all_started(:ex_machina)
2 |
3 | ExUnit.configure(exclude: [pending: true])
4 | ExUnit.start()
5 |
6 | Ecto.Adapters.SQL.Sandbox.mode(Ex338.Repo, :manual)
7 |
--------------------------------------------------------------------------------
/.well-known/brave-payments-verification.txt:
--------------------------------------------------------------------------------
1 | This is a Brave Payments publisher verification file.
2 |
3 | Domain: the338challenge.herokuapp.com
4 | Token: 9c705b9168f6d9c699092fc9b6250280075f72f2b66ce38fa72522cc9c3ff229
5 |
--------------------------------------------------------------------------------
/test/fixtures/fantasy_team_csv_table.csv:
--------------------------------------------------------------------------------
1 | projected_id,team_name,waiver_position,winnings_adj,dues_paid,winnings_received,commish_notes,fantasy_league_id
2 | 68,A,1,0,0,0,,1
3 | 69,B,1,0,0,0,"My team",1
4 | 70,C,1,0,0,0,"Your team",2
5 |
--------------------------------------------------------------------------------
/priv/static/robots.txt:
--------------------------------------------------------------------------------
1 | # See http://www.robotstxt.org/robotstxt.html for documentation on how to use the robots.txt file
2 | #
3 | # To ban all spiders from the entire site uncomment the next two lines:
4 | # User-agent: *
5 | # Disallow: /
6 |
--------------------------------------------------------------------------------
/assets/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "browser": true,
4 | "es6": true,
5 | "node": true
6 | },
7 | "extends": ["eslint:recommended", "prettier"],
8 | "parserOptions": {
9 | "sourceType": "module"
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/fantasy_leagues.csv:
--------------------------------------------------------------------------------
1 | Fantasy League Name, Year, Division,Championship Starts At,Championship Ends At
2 | 2017 Div A,2017,A,-104,261
3 | 2017 Div B,2017,B,-104,261
4 | 2018 Div A,2018,A,255,620
5 | 2018 Div B,2018,B,255,620
6 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/franchises.csv:
--------------------------------------------------------------------------------
1 | Brown
Swin/Mara
Kintz/Mori
Axel/Ken
BD
Bartch
Wilson
Miller/Wernz
Flo
Drew/Gabe
Davie/Paul
Bro/Heli
Jake
Zach
Vondy/Clark
Dan
Evan
Hughes
Erik/Gavin
Murphy
Tim J
Tim/Mike
Riss/Ehrman
Bailey
Nelson
Teeter
Mick/Jason
Tom
--------------------------------------------------------------------------------
/lib/ex338/events.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Events do
2 | @moduledoc """
3 | Defines Event structs for use within the pubsub system.
4 | """
5 | defmodule MessageCreated do
6 | @moduledoc false
7 | defstruct message: nil
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160731103547_add_admin_to_user.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddAdminToUser do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:users) do
6 | add :admin, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161002161106_add_process_at_to_waiver.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddProcessAtToWaiver do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:waivers) do
6 | add :process_at, :utc_datetime
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/trades.csv:
--------------------------------------------------------------------------------
1 | Id,Status,Additional Terms
2 | 1,Approved,""
3 | 2,Approved,"Black Widows gets She-Unit 5th Rd Pick"
4 | 3,Approved,""
5 | 4,Approved,"Tigers get Victorious' 9th Rd Pick"
6 | 5,Approved,"Mints get Daddy's 7th Rd Pick"
7 | 6,Approved,""
8 | 7,Approved,""
9 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160908231000_add_abbrev_to_sports_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddAbbrevToSportsLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:sports_leagues) do
6 | add :abbrev, :string
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/.formatter.exs:
--------------------------------------------------------------------------------
1 | [
2 | import_deps: [:ecto, :ecto_sql, :phoenix],
3 | subdirectories: ["priv/*/migrations"],
4 | plugins: [Phoenix.LiveView.HTMLFormatter, Styler],
5 | inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}", "priv/*/seeds.exs"],
6 | line_length: 98
7 | ]
8 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170423182027_add_year_to_championship.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddYearToChampionship do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:championships) do
6 | add :year, :integer, default: 2017
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20181110154218_add_slack_name_to_users_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddSlackNameToUsersTable do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:users) do
6 | add :slack_name, :text, default: ""
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20181112232239_add_order_to_historical_records.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddOrderToHistoricalRecords do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table("historical_records") do
6 | add :order, :float
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190209231153_add_drafted_at_to_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftedAtToDraftPick do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:draft_picks) do
6 | add(:drafted_at, :utc_datetime)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170528152505_add_winnings_adj_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddWinningsAdjToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add :winnings_adj, :float, default: 0
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180728192433_add_max_flex_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddMaxFlexToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add :max_flex_spots, :integer, default: 6
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190929195252_add_max_flex_adj_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddMaxFlexAdjToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add(:max_flex_adj, :integer, default: 0)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161028181031_add_commish_notes_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddCommishNotesToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add :commish_notes, :text, default: ""
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170210224425_add_overall_id_to_championship.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddOverallIdToChampionship do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:championships) do
6 | add :overall_id, references(:championships)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170408221017_add_draft_pick_to_fantasy_player.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftPickToFantasyPlayer do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_players) do
6 | add :draft_pick, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220804165234_add_draft_starts_at_to_championships.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftStartsAtToChampionships do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:championships) do
6 | add(:draft_starts_at, :utc_datetime)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250723040130_add_is_keeper_to_draft_picks.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddIsKeeperToDraftPicks do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:draft_picks) do
6 | add :is_keeper, :boolean, default: false, null: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170305200054_add_hide_waivers_to_sports_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddHideWaiversToSportsLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:sports_leagues) do
6 | add :hide_waivers, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220802040558_add_max_draft_mins_to_champioships.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddMaxDraftMinsToChampioships do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:championships) do
6 | add(:max_draft_mins, :integer, default: 5)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250419232727_alter_trade_additional_terms_to_text.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AlterTradeAdditionalTermsToText do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:trades) do
6 | modify :additional_terms, :text, from: :string
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/ex338/rulebooks/rulebook.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Rulebooks.Rulebook do
2 | @moduledoc false
3 | @enforce_keys [:year, :draft_method, :body]
4 | defstruct [:year, :draft_method, :body]
5 |
6 | def build(_filename, attrs, body) do
7 | struct!(__MODULE__, [body: body] ++ Map.to_list(attrs))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170408220019_add_in_season_draft_to_championship.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddInSeasonDraftToChampionship do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:championships) do
6 | add :in_season_draft, :boolean, default: false
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180322222911_add_sport_draft_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddSportDraftToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add :sport_draft_id, references(:sports_leagues)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190713193904_add_max_draft_hours_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddMaxDraftHoursToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add(:max_draft_hours, :integer, default: 0)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190721005538_add_acq_method_to_roster_positions.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddAcqMethodToRosterPositions do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:roster_positions) do
6 | add(:acq_method, :string, default: "unknown")
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220802041502_add_drafted_at_to_in_season_draft_picks.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftedAtToInSeasonDraftPicks do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:in_season_draft_picks) do
6 | add(:drafted_at, :utc_datetime)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/assets/js/confirm_submit.js:
--------------------------------------------------------------------------------
1 | const formToConfirm = document.getElementById("js-confirm-submit")
2 |
3 | const confirmSubmit = function () {
4 | if (confirm("Do you really want to submit the pick?")) return true
5 | else return false
6 | }
7 |
8 | if (formToConfirm) {
9 | formToConfirm.onsubmit = confirmSubmit
10 | }
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160806113743_create_trade.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateTrade do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:trades) do
6 | add :status, :string
7 | add :additional_terms, :string
8 |
9 | timestamps()
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/trade_line_items.csv:
--------------------------------------------------------------------------------
1 | Id,Trade Id,Losing Team Id,Fantasy Player Id,Gaining Team Id
2 | 23,7,18,221,23
3 | 21,7,23,206,18
4 | 19,6,12,422,1
5 | 17,6,12,297,1
6 | 15,6,1,285,12
7 | 13,5,10,278,21
8 | 11,4,7,277,18
9 | 9,3,10,210,14
10 | 8,3,10,370,14
11 | 5,2,12,74,11
12 | 3,1,10,245,13
13 | 1,1,13,210,10
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161123212323_add_active_at_to_roster_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddActiveAtToRosterPosition do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:roster_positions) do
6 | add :active_at, :utc_datetime, default: fragment("now()")
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190922222537_add_total_draft_mins_adj_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddTotalDraftMinsAdjToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add(:total_draft_mins_adj, :integer, default: 0)
7 | end
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170214145352_update_points_winnings_to_float.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.UpdatePointsWinningsToFloat do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:champ_with_events_results) do
6 | modify :points, :float
7 | modify :winnings, :float
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170923130822_add_active_years_to_players.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddActiveYearsToPlayers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_players) do
6 | add :start_year, :integer, default: 2017
7 | add :end_year, :integer
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20250725042432_add_draft_picks_locked_to_fantasy_leagues.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftPicksLockedToFantasyLeagues do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add :draft_picks_locked?, :boolean, default: false, null: false
7 | end
8 | end
9 | end
--------------------------------------------------------------------------------
/priv/repo/migrations/20250802230356_add_draft_fields_to_fantasy_teams.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftFieldsToFantasyTeams do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add :draft_grade, :string
7 | add :draft_analysis, :text
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160709234145_create_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:fantasy_leagues) do
6 | add :year, :integer
7 | add :division, :string
8 |
9 | timestamps()
10 | end
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20181024014213_create_historical_winnings.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateHistoricalWinnings do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:historical_winnings) do
6 | add :team, :string
7 | add :amount, :integer
8 |
9 | timestamps()
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/ex338_web/notifiers/notifier_template.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.NotifierTemplate do
2 | @moduledoc false
3 |
4 | import Swoosh.Email
5 |
6 | def plain_text(data) do
7 | new()
8 | |> bcc(data.bcc)
9 | |> cc(data.cc)
10 | |> from(data.from)
11 | |> subject(data.subject)
12 | |> text_body(data.message)
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160928222113_add_winnings_dues_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddWinningsDuesToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_teams) do
6 | add :dues_paid, :decimal, default: 0
7 | add :winnings_received, :decimal, default: 0
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160921232648_add_status_and_date_to_roster_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddStatusAndDateToRosterPosition do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:roster_positions) do
6 | add :status, :string, default: "active"
7 | add :released_at, :utc_datetime
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190908210456_add_start_end_dates_to_fantasy_player.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddStartEndDatesToFantasyPlayer do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_players) do
6 | add(:available_starting_at, :utc_datetime)
7 | add(:archived_at, :utc_datetime)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240331033202_create_chats.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateChats do
2 | @moduledoc false
3 | use Ecto.Migration
4 |
5 | def change do
6 | create table(:chats) do
7 | add :room_name, :string, null: false
8 | timestamps()
9 | end
10 |
11 | create unique_index(:chats, [:room_name])
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200816034556_add_new_options_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddNewOptionsToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add(:only_flex?, :boolean, default: false)
7 | add(:must_draft_each_sport?, :boolean, default: false)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20171203180919_create_unique_index_for_trade_vote.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateUniqueIndexForTradeVote do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create unique_index(:trade_votes, [:trade_id, :fantasy_team_id])
6 | end
7 |
8 | def down do
9 | drop unique_index(:trade_votes, [:trade_id, :fantasy_team_id])
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190803213203_add_championship_date_range_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddChampionshipDateRangeToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:fantasy_leagues) do
6 | add(:championships_start_at, :utc_datetime)
7 | add(:championships_end_at, :utc_datetime)
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160722030421_remove_round_column_from_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.RemoveRoundColumnFromDraftPick do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:draft_picks) do
6 | remove :round
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:draft_picks) do
12 | add :round, :integer
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200908035227_rename_ir_add_player_column.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.RenameIRAddPlayerColumn do
2 | use Ecto.Migration
3 |
4 | def change do
5 | drop(index("injured_reserves", [:add_player_id]))
6 | rename(table("injured_reserves"), :add_player_id, to: :injured_player_id)
7 | create(index("injured_reserves", [:injured_player_id]))
8 | end
9 | end
10 |
--------------------------------------------------------------------------------
/lib/ex338_web/plugs/user_login_redirector.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Plugs.UserLoginRedirector do
2 | @moduledoc false
3 | use Phoenix.VerifiedRoutes, endpoint: Ex338Web.Endpoint, router: Ex338Web.Router
4 |
5 | def init(default), do: default
6 |
7 | def call(conn, _opts) do
8 | conn
9 | |> Phoenix.Controller.redirect(to: ~p"/users/log_in")
10 | |> Plug.Conn.halt()
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/lib/ex338_web/presence.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Presence do
2 | @moduledoc false
3 | use Phoenix.Presence,
4 | otp_app: :ex338,
5 | pubsub_server: Ex338.PubSub
6 |
7 | alias Ex338Web.Presence
8 |
9 | def list_presences(topic) do
10 | topic
11 | |> Presence.list()
12 | |> Enum.map(fn {_user_id, data} ->
13 | List.first(data[:metas])
14 | end)
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160925153823_add_default_status_to_waiver.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDefaultStatusToWaiver do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:waivers) do
6 | modify :status, :string, default: "pending"
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:waivers) do
12 | modify :status, :string
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190923002500_create_unique_index_for_draft_picks.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateUniqueIndexForDraftPicks do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create(unique_index(:draft_picks, [:fantasy_league_id, :fantasy_player_id]))
6 | end
7 |
8 | def down do
9 | drop(unique_index(:draft_picks, [:fantasy_league_id, :fantasy_player_id]))
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710122608_create_roster_transaction.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateRosterTransaction do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:roster_transactions) do
6 | add :category, :string
7 | add :additional_terms, :text
8 | add :roster_transaction_on, :utc_datetime
9 |
10 | timestamps()
11 | end
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200531155101_add_future_pick_to_trade_line_items.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddFuturePickToTradeLineItems do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:trade_line_items) do
6 | add(:future_pick_id, references(:future_picks, on_delete: :delete_all))
7 | end
8 |
9 | create(index(:trade_line_items, [:future_pick_id]))
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160724210543_add_name_to_fantasy_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddNameToFantasyLeague do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:fantasy_leagues) do
6 | add :fantasy_league_name, :string
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:fantasy_leagues) do
12 | remove :fantasy_league_name
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200908164944_remove_status_col_from_injured_reserves.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.RemoveStatusColFromInjuredReserves do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table("injured_reserves") do
6 | remove(:status)
7 | end
8 | end
9 |
10 | def down do
11 | alter table("injured_reserves") do
12 | add(:status, :string)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/assets/js/hooks/enter-submit-hook.js:
--------------------------------------------------------------------------------
1 | const EnterSubmitHook = {
2 | mounted() {
3 | this.el.addEventListener("keydown", (e) => {
4 | if (e.key == "Enter" && e.shiftKey == false) {
5 | this.el.form.dispatchEvent(
6 | new Event("submit", { bubbles: true, cancelable: true })
7 | )
8 | this.el.value = ""
9 | }
10 | })
11 | },
12 | }
13 |
14 | export default EnterSubmitHook
15 |
--------------------------------------------------------------------------------
/assets/js/hooks/index.js:
--------------------------------------------------------------------------------
1 | import ChatScrollToBottom from "./chat-hooks"
2 | import EnterSubmitHook from "./enter-submit-hook"
3 | import LocalTimeHook from "./local-time-hooks"
4 | import SortableInputsFor from "./sortable-hooks"
5 | import VegaLiteHook from "./vega-lite-hook"
6 |
7 | export default {
8 | ChatScrollToBottom,
9 | EnterSubmitHook,
10 | LocalTimeHook,
11 | SortableInputsFor,
12 | VegaLiteHook,
13 | }
14 |
--------------------------------------------------------------------------------
/test/ex338_web/plugs/load_leagues_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.LoadLeaguesTest do
2 | use Ex338Web.ConnCase
3 |
4 | alias Ex338Web.LoadLeagues
5 |
6 | @opts LoadLeagues.init([])
7 |
8 | test "loads all Fantasy Leagues into assigns" do
9 | league = insert(:fantasy_league)
10 | conn = build_conn()
11 |
12 | conn = LoadLeagues.call(conn, @opts)
13 |
14 | assert conn.assigns.leagues == [league]
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/assets/js/hooks/local-time-hooks.js:
--------------------------------------------------------------------------------
1 | const LocalTimeHook = {
2 | mounted() {
3 | this.updated()
4 | },
5 | updated() {
6 | let dt = new Date(this.el.textContent)
7 | let options = {
8 | timeStyle: "short",
9 | dateStyle: "short",
10 | }
11 | this.el.textContent = `${dt.toLocaleString("en-US", options)}`
12 | this.el.classList.remove("invisible")
13 | },
14 | }
15 |
16 | export default LocalTimeHook
17 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/archived_league_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ArchivedLeagueController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues
5 |
6 | def index(conn, _params) do
7 | leagues = FantasyLeagues.get_leagues_by_status("archived")
8 |
9 | render(
10 | conn,
11 | :index,
12 | fantasy_leagues: leagues,
13 | page_title: "Past Fantasy Leagues"
14 | )
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710012444_create_sports_league.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateSportsLeague do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:sports_leagues) do
6 | add :league_name, :string
7 | add :waiver_deadline, :utc_datetime
8 | add :trade_deadline, :utc_datetime
9 | add :championship_date, :utc_datetime
10 |
11 | timestamps()
12 | end
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161012003424_add_null_constraint_to_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddNullConstraintsToRosterPosition do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create constraint(:roster_positions, :position_not_null,
6 | check: "position IS NOT NULL")
7 | end
8 |
9 | def down do
10 | drop constraint(:roster_positions, :position_not_null)
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190930000845_create_unique_index_for_in_season_draft_picks.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateUniqueIndexForInSeasonDraftPicks do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create(unique_index(:in_season_draft_picks, [:fantasy_league_id, :drafted_player_id]))
6 | end
7 |
8 | def down do
9 | drop(unique_index(:in_season_draft_picks, [:fantasy_league_id, :drafted_player_id]))
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160722051921_change_decimal_to_float_on_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.ChangeDecimalToFloatOnDraftPick do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:draft_picks) do
6 | modify :draft_position, :float
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:draft_picks) do
12 | modify :draft_position, :decimal, precision: 5, scale: 2
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160925160332_add_default_position_to_roster_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDefaultStatusToRosterPosition do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:roster_positions) do
6 | modify :position, :string, default: "Unassigned"
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:roster_positions) do
12 | modify :position, :string
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710012609_create_fantasy_player.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateFantasyPlayer do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:fantasy_players) do
6 | add :player_name, :string
7 | add :sports_league_id, references(:sports_leagues, on_delete: :delete_all)
8 |
9 | timestamps()
10 | end
11 | create index(:fantasy_players, [:sports_league_id])
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160730191328_create_coherence_invitable.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateCoherenceInvitable do
2 | use Ecto.Migration
3 | def change do
4 | create table(:invitations) do
5 | add :name, :string
6 | add :email, :string
7 | add :token, :string
8 | timestamps()
9 | end
10 | create unique_index(:invitations, [:email])
11 | create index(:invitations, [:token])
12 |
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20181024004723_create_historical_records.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateHistoricalRecords do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:historical_records) do
6 | add :description, :string
7 | add :record, :string
8 | add :team, :string
9 | add :year, :string
10 | add :archived, :boolean, default: false
11 |
12 | timestamps()
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20220811205302_add_oban_jobs_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddObanJobsTable do
2 | use Ecto.Migration
3 |
4 | def up do
5 | Oban.Migrations.up(version: 11)
6 | end
7 |
8 | # We specify `version: 1` in `down`, ensuring that we'll roll all the way back down if
9 | # necessary, regardless of which version we've migrated `up` to.
10 | def down do
11 | Oban.Migrations.down(version: 1)
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/assets/js/hooks/chat-hooks.js:
--------------------------------------------------------------------------------
1 | const ChatScrollToBottom = {
2 | mounted() {
3 | this.el.scrollTo(0, this.el.scrollHeight)
4 | },
5 |
6 | updated() {
7 | const pixelsBelowBottom =
8 | this.el.scrollHeight - this.el.clientHeight - this.el.scrollTop
9 |
10 | if (pixelsBelowBottom < this.el.clientHeight * 0.3) {
11 | this.el.scrollTo(0, this.el.scrollHeight)
12 | }
13 | },
14 | }
15 |
16 | export default ChatScrollToBottom
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190604013901_add_pow_invitation_to_users.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddPowInvitationToUsers do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:users) do
6 | add :invitation_token, :string
7 | add :invitation_accepted_at, :utc_datetime
8 | add :invited_by_id, references("users", on_delete: :nothing)
9 | end
10 |
11 | create unique_index(:users, [:invitation_token])
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/final_rankings.csv:
--------------------------------------------------------------------------------
1 | 1,2016,1,8,25
10,2016,2,5,10
24,2016,3,3,
34,2016,4,3,
41,2016,5,1,
33,2016,6,1,
42,2016,7,1,
16,2016,8,1,
82,2016,1,8,25
64,2016,2,5,10
67,2016,3,3,
70,2016,4,3,
85,2016,5,1,
58,2016,6,1,
75,2016,7,1,
68,2016,8,1,
146,2016,1,8,25
125,2016,2,5,10
128,2016,3,3,
138,2016,4,3,
129,2016,5,1,
147,2016,6,1,
109,2016,7,1,
124,2016,8,1,
186,2016,1,8,25
194,2016,2,5,10
158,2016,3,3,
168,2016,4,3,
181,2016,5,1,
175,2016,6,1,
178,2016,7,1,
169,2016,8,1,
--------------------------------------------------------------------------------
/priv/repo/migrations/20160806131130_drop_roster_transaction.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DropRosterTransaction do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop table(:roster_transactions)
6 | end
7 |
8 | def down do
9 | create table(:roster_transactions) do
10 | add :category, :string
11 | add :additional_terms, :text
12 | add :roster_transaction_on, :datetime
13 |
14 | timestamps()
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180115062531_add_status_to_draft_queue.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddStatusToDraftQueue do
2 | use Ecto.Migration
3 |
4 | def up do
5 | DraftQueueStatusEnum.create_type
6 | alter table("draft_queues") do
7 | add :status, :draft_queue_status
8 | end
9 | end
10 |
11 | def down do
12 | alter table("draft_queues") do
13 | remove :status
14 | end
15 | DraftQueueStatusEnum.drop_type
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190929223403_add_fantasy_league_to_in_season_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddFantasyLeagueToInSeasonDraftPick do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:in_season_draft_picks) do
6 | add(
7 | :fantasy_league_id,
8 | references(:fantasy_leagues, on_delete: :delete_all)
9 | )
10 | end
11 |
12 | create(index(:in_season_draft_picks, [:fantasy_league_id]))
13 | end
14 | end
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200525125001_add_rules_to_owners.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddRulesToOwners do
2 | use Ecto.Migration
3 |
4 | def up do
5 | OwnerRulesEnum.create_type()
6 |
7 | alter table("owners") do
8 | add(:rules, OwnerRulesEnum.type(), default: "unaccepted")
9 | end
10 | end
11 |
12 | def down do
13 | alter table("owners") do
14 | remove(:rules)
15 | end
16 |
17 | OwnerRulesEnum.drop_type()
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/ex338_web/plugs/load_leagues.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.LoadLeagues do
2 | @moduledoc """
3 | Loads all fantasy leagues into conn assigns for use in header
4 | """
5 |
6 | import Plug.Conn
7 |
8 | alias Ex338.FantasyLeagues
9 |
10 | def init(options) do
11 | # initialize options
12 |
13 | options
14 | end
15 |
16 | def call(conn, _opts) do
17 | leagues = FantasyLeagues.list_fantasy_leagues()
18 | assign(conn, :leagues, leagues)
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/archived_league_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ArchivedLeagueHTML do
2 | use Ex338Web, :html
3 |
4 | alias Ex338Web.Components.FantasyLeague
5 |
6 | def index(assigns) do
7 | ~H"""
8 |
9 |
13 |
14 | """
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/sports_leagues.csv:
--------------------------------------------------------------------------------
1 | Name,Abbrv
2 | College Football,CFB
3 | National Football League,NFL
4 | College Basketball,CBB
5 | College Hockey,CHK
6 | Kentucky Derby,KD
7 | Champions League,CL
8 | National Hockey League,NHL
9 | National Basketball Association,NBA
10 | Professional Golf Association,PGA
11 | English Premier League,EPL
12 | Little League World Series,LLWS
13 | Men's Tennis,MTn
14 | Women's Tennis,WTn
15 | Major League Baseball,MLB
16 | Major League Soccer,MLS
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160817234415_create_owner.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateOwner do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:owners) do
6 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :delete_all)
7 | add :user_id, references(:users, on_delete: :delete_all)
8 |
9 | timestamps()
10 | end
11 | create index(:owners, [:fantasy_team_id])
12 | create index(:owners, [:user_id])
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200818041024_update_default_for_must_draft_each_sport.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.UpdateDefaultForMustDraftEachSport do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:fantasy_leagues) do
6 | modify(:must_draft_each_sport?, :boolean, default: true)
7 | end
8 | end
9 |
10 | def down do
11 | alter table(:fantasy_leagues) do
12 | modify(:must_draft_each_sport?, :boolean, default: false)
13 | end
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/annual_league_setup/data/league_sports.csv:
--------------------------------------------------------------------------------
1 | League,Sport
2 | 8,1
3 | 8,2
4 | 8,3
5 | 8,4
6 | 8,5
7 | 8,6
8 | 8,7
9 | 8,8
10 | 8,9
11 | 8,11
12 | 8,12
13 | 8,13
14 | 8,14
15 | 8,16
16 | 8,17
17 | 9,1
18 | 9,2
19 | 9,3
20 | 9,4
21 | 9,5
22 | 9,6
23 | 9,7
24 | 9,8
25 | 9,9
26 | 9,11
27 | 9,12
28 | 9,13
29 | 9,14
30 | 9,16
31 | 9,17
32 | 10,1
33 | 10,2
34 | 10,3
35 | 10,4
36 | 10,5
37 | 10,6
38 | 10,7
39 | 10,8
40 | 10,9
41 | 10,11
42 | 10,12
43 | 10,13
44 | 10,14
45 | 10,16
46 | 10,17
47 |
--------------------------------------------------------------------------------
/lib/ex338/calendar_assistant.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.CalendarAssistant do
2 | @moduledoc """
3 | Functions to add days to a date
4 | """
5 |
6 | def days_from_now(days) do
7 | days = 86_400 * days
8 | now = DateTime.truncate(DateTime.utc_now(), :second)
9 |
10 | DateTime.add(now, days)
11 | end
12 |
13 | def mins_from_now(mins) do
14 | mins = 60 * mins
15 | now = DateTime.truncate(DateTime.utc_now(), :second)
16 |
17 | DateTime.add(now, mins)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/fantasy_team_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyTeamController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues
5 | alias Ex338.FantasyTeams
6 |
7 | def index(conn, %{"fantasy_league_id" => league_id}) do
8 | league = FantasyLeagues.get(league_id)
9 |
10 | render(
11 | conn,
12 | :index,
13 | fantasy_league: league,
14 | fantasy_teams: FantasyTeams.find_all_for_league(league)
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200218020811_delete_unused_fantasy_players_fields.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DeleteUnusedFantasyPlayersFields do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:fantasy_players) do
6 | remove(:start_year)
7 | remove(:end_year)
8 | end
9 | end
10 |
11 | def down do
12 | alter table(:fantasy_players) do
13 | add(:start_year, :integer, default: 2017)
14 | add(:end_year, :integer)
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/phoenix_static_buildpack.config:
--------------------------------------------------------------------------------
1 | clean_cache=true
2 | compile="compile"
3 |
4 | # We can set the version of Node to use for the app here
5 | node_version=22.14.0
6 |
7 | # We can set the version of NPM to use for the app here
8 | npm_version=11.2.0
9 |
10 | # We can change path that npm dependencies are in relation to phoenix app. E.g. assets for phoenix 1.3 support.
11 | assets_path=assets
12 |
13 | # We can change phoenix mix namespace tasks. E.g. `phoenix` for phoenix < 1.3 support.
14 | phoenix_ex=phx
15 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710002129_create_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:fantasy_teams) do
6 | add :team_name, :string, null: false
7 | add :waiver_position, :integer
8 | add :fantasy_league_id,
9 | references(:fantasy_leagues, on_delete: :delete_all)
10 |
11 | timestamps()
12 | end
13 | create index(:fantasy_teams, [:fantasy_league_id])
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20171203023116_add_user_and_team_to_trade.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddUserAndTeamToTrade do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:trades) do
6 | add :submitted_by_user_id, references(:users, on_delete: :nilify_all)
7 | add :submitted_by_team_id, references(:fantasy_teams, on_delete: :nilify_all)
8 | end
9 | create index(:trades, [:submitted_by_user_id])
10 | create index(:trades, [:submitted_by_team_id])
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20181024012435_add_type_to_historical_records.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddTypeToHistoricalRecords do
2 | use Ecto.Migration
3 |
4 | def up do
5 | HistoricalRecordTypeEnum.create_type
6 | alter table("historical_records") do
7 | add :type, :historical_record_type, default: "season"
8 | end
9 | end
10 |
11 | def down do
12 | alter table("historical_records") do
13 | remove :type
14 | end
15 | HistoricalRecordTypeEnum.drop_type
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/test/ex338_web/mailer_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.MailerTest do
2 | use Ex338.DataCase, async: true
3 |
4 | import ExUnit.CaptureLog
5 |
6 | alias Ex338Web.Mailer
7 |
8 | require Logger
9 |
10 | describe "handle_delivery/1" do
11 | test "logs an error email" do
12 | delivery = {:error, {:error, "reason"}}
13 | expected_msg = "reason"
14 |
15 | assert capture_log(fn ->
16 | Mailer.handle_delivery(delivery)
17 | end) =~ expected_msg
18 | end
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/fantasy_player_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyPlayerController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues
5 | alias Ex338.FantasyPlayers
6 |
7 | def index(conn, %{"fantasy_league_id" => league_id}) do
8 | fantasy_league = FantasyLeagues.get(league_id)
9 |
10 | render(
11 | conn,
12 | :index,
13 | fantasy_league: fantasy_league,
14 | fantasy_players: FantasyPlayers.all_players_for_league(fantasy_league)
15 | )
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161010221818_add_constraints_to_roster_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddConstraintsToRosterPosition do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create unique_index(:roster_positions, [:position, :fantasy_team_id],
6 | where: "status LIKE 'active' AND position != 'Unassigned'")
7 | end
8 |
9 | def down do
10 | drop unique_index(:roster_positions, [:position, :fantasy_team_id],
11 | where: "status LIKE 'active' AND position != 'Unassigned'")
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/test/ex338_web/plugs/canonical_domain_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.CanonicalDomainTest do
2 | use Ex338Web.ConnCase
3 |
4 | import Plug.Test
5 |
6 | alias Ex338Web.CanonicalDomain
7 |
8 | @opts CanonicalDomain.init([])
9 |
10 | test "redirects to root domain when host is heroku domain" do
11 | conn = conn(:get, "https://the338challenge.herokuapp.com/foo?bar=10")
12 |
13 | conn = CanonicalDomain.call(conn, @opts)
14 |
15 | assert redirected_to(conn, 301) =~ "https://localhost/foo?bar=10"
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/fantasy_league/calendar_download_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyLeague.CalendarDownloadController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues
5 |
6 | def show(conn, %{"fantasy_league_id" => fantasy_league_id}) do
7 | calendar = FantasyLeagues.generate_calendar(fantasy_league_id)
8 |
9 | send_download(
10 | conn,
11 | {:binary, calendar},
12 | content_type: "text/calendar",
13 | filename: "338_calendar.ics"
14 | )
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200518234631_drop_coherence_invitations_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DropCoherenceInvitationsTable do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop(table(:invitations))
6 | end
7 |
8 | def down do
9 | create table(:invitations) do
10 | add(:name, :string)
11 | add(:email, :string)
12 | add(:token, :string)
13 | timestamps()
14 | end
15 |
16 | create(unique_index(:invitations, [:email]))
17 | create(index(:invitations, [:token]))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240331213542_create_messages.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateMessages do
2 | @moduledoc false
3 | use Ecto.Migration
4 |
5 | def change do
6 | create table(:messages) do
7 | add :content, :text, null: false
8 | add :user_id, references(:users, on_delete: :delete_all)
9 | add :chat_id, references(:chats, on_delete: :delete_all)
10 |
11 | timestamps()
12 | end
13 |
14 | create index(:messages, [:user_id])
15 | create index(:messages, [:chat_id])
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170421014248_create_league_sport.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateLeagueSport do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:league_sports) do
6 | add :fantasy_league_id, references(:fantasy_leagues, on_delete: :nothing)
7 | add :sports_league_id, references(:sports_leagues, on_delete: :nothing)
8 |
9 | timestamps()
10 | end
11 | create index(:league_sports, [:fantasy_league_id])
12 | create index(:league_sports, [:sports_league_id])
13 |
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170505002641_add_gaining_losing_teams_to_trade_line_item.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddGainingLosingTeamsToTradeLineItem do
2 | use Ecto.Migration
3 |
4 | def change do
5 | alter table(:trade_line_items) do
6 | add :gaining_team_id, references(:fantasy_teams, on_delete: :nothing)
7 | add :losing_team_id, references(:fantasy_teams, on_delete: :nothing)
8 | end
9 | create index(:trade_line_items, [:losing_team_id])
10 | create index(:trade_line_items, [:gaining_team_id])
11 |
12 | end
13 | end
14 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170528153600_change_winnings_dues_in_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.ChangeWinningsDuesInFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:fantasy_teams) do
6 | modify :dues_paid, :float, default: 0
7 | modify :winnings_received, :float, default: 0
8 | end
9 | end
10 |
11 | def down do
12 | alter table(:fantasy_teams) do
13 | modify :dues_paid, :decimal, default: 0
14 | modify :winnings_received, :decimal, default: 0
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # To get started with Dependabot version updates, you'll need to specify which
2 | # package ecosystems to update and where the package manifests are located.
3 | # Please see the documentation for all configuration options:
4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
5 |
6 | version: 2
7 | updates:
8 | - package-ecosystem: "mix" # See documentation for possible values
9 | directory: "/" # Location of package manifests
10 | schedule:
11 | interval: "weekly"
12 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180324155253_add_navbar_display_to_fantasy_leagues.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddNavbarDisplayToFantasyLeagues do
2 | use Ecto.Migration
3 |
4 | def up do
5 | FantasyLeagueNavbarDisplayEnum.create_type
6 | alter table("fantasy_leagues") do
7 | add :navbar_display, :fantasy_league_navbar_display, default: "primary"
8 | end
9 | end
10 |
11 | def down do
12 | alter table("fantasy_leagues") do
13 | remove :navbar_display
14 | end
15 | FantasyLeagueNavbarDisplayEnum.drop_type
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180908194517_add_autodraft_setting_to_fantasy_team.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddAutodraftSettingToFantasyTeam do
2 | use Ecto.Migration
3 |
4 | def up do
5 | FantasyTeamAutodraftSettingEnum.create_type
6 | alter table("fantasy_teams") do
7 | add :autodraft_setting, :fantasy_team_autodraft_setting, default: "on"
8 | end
9 | end
10 |
11 | def down do
12 | alter table("fantasy_teams") do
13 | remove :autodraft_setting
14 | end
15 | FantasyTeamAutodraftSettingEnum.drop_type
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20190616203656_add_draft_method_to_fantasy_leagues.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddDraftMethodToFantasyLeagues do
2 | use Ecto.Migration
3 |
4 | def up do
5 | FantasyLeagueDraftMethodEnum.create_type()
6 |
7 | alter table("fantasy_leagues") do
8 | add(:draft_method, :fantasy_league_draft_method, default: "redraft")
9 | end
10 | end
11 |
12 | def down do
13 | alter table("fantasy_leagues") do
14 | remove(:draft_method)
15 | end
16 |
17 | FantasyLeagueDraftMethodEnum.drop_type()
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200908165807_add_status_enum_to_injured_reserves.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.AddStatusEnumToInjuredReserves do
2 | use Ecto.Migration
3 |
4 | def up do
5 | InjuredReserveStatusEnum.create_type()
6 |
7 | alter table("injured_reserves") do
8 | add(:status, InjuredReserveStatusEnum.type(), default: "submitted", null: false)
9 | end
10 | end
11 |
12 | def down do
13 | alter table("injured_reserves") do
14 | remove(:status)
15 | end
16 |
17 | InjuredReserveStatusEnum.drop_type()
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/assets/js/draft_pick_confirm.js:
--------------------------------------------------------------------------------
1 | const draftPickForm = document.getElementById("draft-pick-form")
2 |
3 | const confirmDraftPick = function () {
4 | const playerSelect = draftPickForm.querySelector('select[name="draft_pick[fantasy_player_id]"]')
5 | const selectedOption = playerSelect?.options[playerSelect.selectedIndex]
6 | const playerName = selectedOption?.text || "this player"
7 |
8 | if (confirm(`Do you really want to draft ${playerName}?`)) return true
9 | else return false
10 | }
11 |
12 | if (draftPickForm) {
13 | draftPickForm.onsubmit = confirmDraftPick
14 | }
--------------------------------------------------------------------------------
/priv/repo/migrations/20170306022658_delete_dates_from_sports_leagues.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DeleteDatesFromSportsLeagues do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:sports_leagues) do
6 | remove :waiver_deadline
7 | remove :trade_deadline
8 | remove :championship_date
9 | end
10 | end
11 |
12 | def down do
13 | alter table(:sports_leagues) do
14 | add :waiver_deadline, :utc_datetime
15 | add :trade_deadline, :utc_datetime
16 | add :championship_date, :utc_datetime
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200530204751_create_future_picks.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateFuturePicks do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:future_picks) do
6 | add(:round, :integer)
7 | add(:original_team_id, references(:fantasy_teams, on_delete: :delete_all))
8 | add(:current_team_id, references(:fantasy_teams, on_delete: :delete_all))
9 |
10 | timestamps()
11 | end
12 |
13 | create(index(:future_picks, [:original_team_id]))
14 | create(index(:future_picks, [:current_team_id]))
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/league_sports.csv:
--------------------------------------------------------------------------------
1 | Fantasy League ID,Sports League ID
2 | 1,1
3 | 1,2
4 | 1,3
5 | 1,4
6 | 1,5
7 | 1,6
8 | 1,7
9 | 1,8
10 | 1,9
11 | 1,10
12 | 1,11
13 | 1,12
14 | 1,13
15 | 1,14
16 | 2,1
17 | 2,2
18 | 2,3
19 | 2,4
20 | 2,5
21 | 2,6
22 | 2,7
23 | 2,8
24 | 2,9
25 | 2,10
26 | 2,11
27 | 2,12
28 | 2,13
29 | 2,14
30 | 3,1
31 | 3,2
32 | 3,3
33 | 3,15
34 | 3,5
35 | 3,6
36 | 3,7
37 | 3,8
38 | 3,9
39 | 3,10
40 | 3,11
41 | 3,12
42 | 3,13
43 | 3,14
44 | 4,1
45 | 4,2
46 | 4,3
47 | 4,15
48 | 4,5
49 | 4,6
50 | 4,7
51 | 4,8
52 | 4,9
53 | 4,10
54 | 4,11
55 | 4,12
56 | 4,13
57 | 4,14
58 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200531173117_create_check_constraint_for_trade_line_items.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateCheckConstraintForTradeLineItems do
2 | use Ecto.Migration
3 |
4 | def up do
5 | create(
6 | constraint(:trade_line_items, :one_asset_per_line_item,
7 | check:
8 | "((fantasy_player_id IS NOT NULL OR future_pick_id IS NOT NULL) AND (fantasy_player_id IS NULL OR future_pick_id IS NULL)) "
9 | )
10 | )
11 | end
12 |
13 | def down do
14 | drop(constraint(:trade_line_items, :one_asset_per_line_item))
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200908161109_delete_ir_remove_player_column.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DeleteIrRemovePlayerColumn do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop(index(:injured_reserves, [:remove_player_id]))
6 |
7 | alter table("injured_reserves") do
8 | remove(:remove_player_id)
9 | end
10 | end
11 |
12 | def down do
13 | alter table("injured_reserves") do
14 | add(:remove_player_id, references(:fantasy_players, on_delete: :nothing))
15 | end
16 |
17 | create(index(:injured_reserves, [:remove_player_id]))
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170116162012_create_championship_slot.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateChampionshipSlot do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:championship_slots) do
6 | add :slot, :integer
7 | add :roster_position_id, references(:roster_positions, on_delete: :nothing)
8 | add :championship_id, references(:championships, on_delete: :nothing)
9 |
10 | timestamps()
11 | end
12 | create index(:championship_slots, [:roster_position_id])
13 | create index(:championship_slots, [:championship_id])
14 |
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/lib/ex338_web/plugs/load_user_teams.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.LoadUserTeams do
2 | @moduledoc """
3 | Preloads all fantasy teams into conn assigns current user
4 | """
5 |
6 | import Plug.Conn
7 |
8 | alias Ex338.Accounts
9 |
10 | def init(options) do
11 | # initialize options
12 |
13 | options
14 | end
15 |
16 | def call(conn, _opts) do
17 | case conn.assigns[:current_user] do
18 | nil ->
19 | conn
20 |
21 | current_user ->
22 | user = Accounts.load_user_teams(current_user)
23 | assign(conn, :current_user, user)
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160927222122_create_championship.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateChampionship do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:championships) do
6 | add :title, :string
7 | add :category, :string
8 | add :waiver_deadline_at, :utc_datetime
9 | add :trade_deadline_at, :utc_datetime
10 | add :championship_at, :utc_datetime
11 | add :sports_league_id, references(:sports_leagues, on_delete: :delete_all)
12 |
13 | timestamps()
14 | end
15 | create index(:championships, [:sports_league_id])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/ex338/chats/message.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Chats.Message do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 |
7 | schema "messages" do
8 | field :content, :string
9 | belongs_to :user, Ex338.Accounts.User
10 | belongs_to :chat, Ex338.Chats.Chat
11 |
12 | timestamps(type: :utc_datetime_usec)
13 | end
14 |
15 | @doc false
16 | def changeset(message, attrs \\ %{}) do
17 | message
18 | |> cast(attrs, [:content, :user_id, :chat_id])
19 | |> validate_required([:content, :chat_id])
20 | |> validate_length(:content, min: 1, max: 280)
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20180114222214_create_draft_queues.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateDraftQueues do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:draft_queues) do
6 | add :order, :integer, null: false
7 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :delete_all), null: false
8 | add :fantasy_player_id, references(:fantasy_players, on_delete: :delete_all), null: false
9 |
10 | timestamps()
11 | end
12 |
13 | create index(:draft_queues, [:fantasy_team_id])
14 | create index(:draft_queues, [:fantasy_player_id])
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/test/ex338/fantasy_leagues/league_sport_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.LeagueSportTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.FantasyLeagues.LeagueSport
5 |
6 | @valid_attrs %{fantasy_league_id: 1, sports_league_id: 2}
7 | @invalid_attrs %{}
8 |
9 | test "changeset with valid attributes" do
10 | changeset = LeagueSport.changeset(%LeagueSport{}, @valid_attrs)
11 | assert changeset.valid?
12 | end
13 |
14 | test "changeset with invalid attributes" do
15 | changeset = LeagueSport.changeset(%LeagueSport{}, @invalid_attrs)
16 | refute changeset.valid?
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/owner_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.OwnerController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues.FantasyLeague
5 | alias Ex338.FantasyTeams.Owner
6 |
7 | def index(conn, %{"fantasy_league_id" => league_id}) do
8 | fantasy_league = Repo.get(FantasyLeague, league_id)
9 |
10 | owners =
11 | Owner
12 | |> Owner.by_league(league_id)
13 | |> preload([:fantasy_team, :user])
14 | |> Repo.all()
15 |
16 | render(
17 | conn,
18 | :index,
19 | fantasy_league: fantasy_league,
20 | owners: owners
21 | )
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710024225_create_roster_position.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateRosterPosition do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:roster_positions) do
6 | add :position, :string
7 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :delete_all)
8 | add :fantasy_player_id, references(:fantasy_players,
9 | on_delete: :delete_all)
10 |
11 | timestamps()
12 | end
13 | create index(:roster_positions, [:fantasy_team_id])
14 | create index(:roster_positions, [:fantasy_player_id])
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20161114231216_create_championship_result.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateChampionshipResult do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:championship_results) do
6 | add :rank, :integer
7 | add :points, :integer
8 | add :championship_id, references(:championships, on_delete: :nothing)
9 | add :fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
10 |
11 | timestamps()
12 | end
13 | create index(:championship_results, [:championship_id])
14 | create index(:championship_results, [:fantasy_player_id])
15 |
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/assets/js/filter_players_list.js:
--------------------------------------------------------------------------------
1 | const handleSportChange = (e) => {
2 | const allTables = document.querySelectorAll(".fantasy-player-collection")
3 | allTables.forEach((table) => (table.style.display = "none"))
4 |
5 | const sportAbbrev = e.target.value
6 | if (sportAbbrev) {
7 | const sportTable = document.getElementById(sportAbbrev)
8 | sportTable.style.display = ""
9 | } else {
10 | allTables.forEach((table) => (table.style.display = ""))
11 | }
12 | }
13 |
14 | const sportSelect = document.getElementById("sport-filter")
15 |
16 | if (sportSelect) {
17 | sportSelect.onchange = (event) => handleSportChange(event)
18 | }
19 |
--------------------------------------------------------------------------------
/test/ex338_web/views/error_view_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ErrorHTMLTest do
2 | use Ex338Web.ConnCase, async: true
3 |
4 | # Bring render/3 and render_to_string/3 for testing custom views
5 | import Phoenix.View
6 |
7 | test "renders 404.html" do
8 | assert render_to_string(Ex338Web.ErrorHTML, "404.html", []) == "Not Found"
9 | end
10 |
11 | test "render 500.html" do
12 | assert render_to_string(Ex338Web.ErrorHTML, "500.html", []) == "Internal Server Error"
13 | end
14 |
15 | test "render any other" do
16 | assert render_to_string(Ex338Web.ErrorHTML, "505.html", []) == "HTTP Version Not Supported"
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170525153855_remove_unused_trade_line_item_columns.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.RemoveUnusedTradeLineItemColumns do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop index(:trade_line_items, [:fantasy_team_id])
6 |
7 | alter table(:trade_line_items) do
8 | remove :action
9 | remove :fantasy_team_id
10 | end
11 | end
12 |
13 | def down do
14 | alter table(:trade_line_items) do
15 | add :action, :string
16 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
17 | end
18 |
19 | create index(:trade_line_items, [:fantasy_team_id])
20 | end
21 | end
22 |
--------------------------------------------------------------------------------
/config/prod.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # Note we also include the path to a cache manifest
4 | # containing the digested version of static files. This
5 | # manifest is generated by the `mix assets.deploy` task,
6 | # which you should run after static files are built and
7 | # before starting your production server.
8 | config :ex338, Ex338Web.Endpoint, cache_static_manifest: "priv/static/cache_manifest.json"
9 | config :ex338, plausible_analytics: true
10 |
11 | # Do not print debug messages in production
12 | config :logger, level: :info
13 |
14 | # Runtime production configuration, including reading
15 | # of environment variables, is done on config/runtime.exs.
16 |
--------------------------------------------------------------------------------
/assets/js/hooks/vega-lite-hook.js:
--------------------------------------------------------------------------------
1 | import vegaEmbed from "vega-embed"
2 | const VegaLiteHook = {
3 | mounted() {
4 | // This element is important so we can uniquely identify which element will be loaded
5 | this.props = { id: this.el.getAttribute("data-id") }
6 | // Handles the event of creating a graph and loads vegaEmbed targetting our main hook element
7 | this.handleEvent(`vega_lite:${this.props.id}:init`, ({ spec }) => {
8 | vegaEmbed(this.el, spec, { renderer: "svg", actions: false })
9 | .then((result) => result.view)
10 | .catch((error) => console.error(error))
11 | })
12 | },
13 | }
14 |
15 | export default VegaLiteHook
16 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170213213035_create_champ_with_events_result.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateChampWithEventsResult do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:champ_with_events_results) do
6 | add :rank, :integer
7 | add :points, :decimal
8 | add :winnings, :decimal
9 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
10 | add :championship_id, references(:championships, on_delete: :nothing)
11 |
12 | timestamps()
13 | end
14 | create index(:champ_with_events_results, [:fantasy_team_id])
15 | create index(:champ_with_events_results, [:championship_id])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20171203155647_create_trade_votes.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateTradeVotes do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:trade_votes) do
6 | add :approve, :boolean, default: true, null: false
7 | add :trade_id, references(:trades, on_delete: :delete_all)
8 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nilify_all)
9 | add :user_id, references(:users, on_delete: :nilify_all)
10 |
11 | timestamps()
12 | end
13 |
14 | create index(:trade_votes, [:trade_id])
15 | create index(:trade_votes, [:fantasy_team_id])
16 | create index(:trade_votes, [:user_id])
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/ex338/jobs.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Jobs do
2 | @moduledoc false
3 | import Ecto.Query, warn: false
4 |
5 | alias Ex338.Repo
6 |
7 | def get_autodraft_job_by(params) do
8 | %{championship_id: championship_id, fantasy_league_id: fantasy_league_id} = params
9 |
10 | query =
11 | Oban.Job
12 | |> where([j], j.worker == "Ex338.Workers.InSeasonAutodraftWorker")
13 | |> where(
14 | [j],
15 | fragment("?->>'championship_id' = ?", j.args, ^"#{championship_id}")
16 | )
17 | |> where(
18 | [j],
19 | fragment("?->>'fantasy_league_id' = ?", j.args, ^"#{fantasy_league_id}")
20 | )
21 |
22 | Repo.one(query)
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160730191329_create_coherence_rememberable.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateCoherenceRememberable do
2 | use Ecto.Migration
3 | def change do
4 | create table(:rememberables) do
5 | add :series_hash, :string
6 | add :token_hash, :string
7 | add :token_created_at, :utc_datetime
8 | add :user_id, references(:users, on_delete: :delete_all)
9 |
10 | timestamps()
11 | end
12 | create index(:rememberables, [:user_id])
13 | create index(:rememberables, [:series_hash])
14 | create index(:rememberables, [:token_hash])
15 | create unique_index(:rememberables, [:user_id, :series_hash, :token_hash])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160803104408_create_waiver.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateWaiver do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:waivers) do
6 | add :status, :string
7 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
8 | add :add_fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
9 | add :drop_fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
10 |
11 | timestamps()
12 | end
13 | create index(:waivers, [:fantasy_team_id])
14 | create index(:waivers, [:add_fantasy_player_id])
15 | create index(:waivers, [:drop_fantasy_player_id])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160806114105_create_trade_line_item.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateTradeLineItem do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:trade_line_items) do
6 | add :action, :string
7 | add :trade_id, references(:trades, on_delete: :delete_all)
8 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
9 | add :fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
10 |
11 | timestamps()
12 | end
13 | create index(:trade_line_items, [:trade_id])
14 | create index(:trade_line_items, [:fantasy_team_id])
15 | create index(:trade_line_items, [:fantasy_player_id])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/assets/js/hooks/sortable-hooks.js:
--------------------------------------------------------------------------------
1 | import Sortable from "../../vendor/sortable"
2 |
3 | const SortableInputsFor = {
4 | mounted() {
5 | let group = this.el.dataset.group
6 | new Sortable(this.el, {
7 | group: group ? { name: group, pull: true, put: true } : undefined,
8 | animation: 150,
9 | dragClass: "drag-item",
10 | ghostClass: "drag-ghost",
11 | handle: "[data-handle]",
12 | forceFallback: true,
13 | onEnd: (_e) => {
14 | this.el
15 | .closest("form")
16 | .querySelector("input")
17 | .dispatchEvent(new Event("input", { bubbles: true }))
18 | },
19 | })
20 | },
21 | }
22 |
23 | export default SortableInputsFor
24 |
--------------------------------------------------------------------------------
/lib/ex338/chats/chat.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Chats.Chat do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 |
7 | alias Ex338.Chats.Message
8 |
9 | schema "chats" do
10 | field :room_name, :string
11 |
12 | has_many :messages, Message, preload_order: [asc: :inserted_at]
13 | has_one :fantasy_league_draft, Ex338.FantasyLeagues.FantasyLeagueDraft
14 | timestamps()
15 | end
16 |
17 | @doc """
18 | Builds a changeset based on the `struct` and `params`.
19 | """
20 | def changeset(struct, params \\ %{}) do
21 | struct
22 | |> cast(params, [:room_name])
23 | |> validate_required([:room_name])
24 | |> unique_constraint(:room_name)
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/error_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ErrorHTML do
2 | use Ex338Web, :html
3 |
4 | # If you want to customize your error pages,
5 | # uncomment the embed_templates/1 call below
6 | # and add pages to the error directory:
7 | #
8 | # * lib/phx_template_1_7_11_web/controllers/error_html/404.html.heex
9 | # * lib/phx_template_1_7_11_web/controllers/error_html/500.html.heex
10 | #
11 | # embed_templates "error_html/*"
12 |
13 | # The default is to render a plain text page based on
14 | # the template name. For example, "404.html" becomes
15 | # "Not Found".
16 | def render(template, _assigns) do
17 | Phoenix.Controller.status_message_from_template(template)
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_leagues/historical_winning.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.HistoricalWinning do
2 | @moduledoc false
3 |
4 | use Ecto.Schema
5 |
6 | import Ecto.Changeset
7 | import Ecto.Query, warn: false
8 |
9 | schema "historical_winnings" do
10 | field(:team, :string)
11 | field(:amount, :integer)
12 |
13 | timestamps()
14 | end
15 |
16 | @doc """
17 | Builds a changeset based on the `struct` and `params`.
18 | """
19 | def changeset(record, params \\ %{}) do
20 | record
21 | |> cast(params, [:team, :amount])
22 | |> validate_required([:team, :amount])
23 | end
24 |
25 | def order_by_amount(query) do
26 | from(t in query, order_by: [desc: t.amount])
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_leagues/league_sport.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.LeagueSport do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 | import Ecto.Query, warn: false
7 |
8 | schema "league_sports" do
9 | belongs_to(:fantasy_league, Ex338.FantasyLeagues.FantasyLeague)
10 | belongs_to(:sports_league, Ex338.FantasyPlayers.SportsLeague)
11 |
12 | timestamps()
13 | end
14 |
15 | @doc """
16 | Builds a changeset based on the `struct` and `params`.
17 | """
18 | def changeset(league_sport, params \\ %{}) do
19 | league_sport
20 | |> cast(params, [:fantasy_league_id, :sports_league_id])
21 | |> validate_required([:fantasy_league_id, :sports_league_id])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/championship_slot_admin_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ChampionshipSlotAdminController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.Championships
5 |
6 | def create(conn, %{"fantasy_league_id" => league_id, "championship_id" => id}) do
7 | case Championships.create_slots_for_league(id, league_id) do
8 | {:ok, _} ->
9 | conn
10 | |> put_flash(:info, "Slots successfully created.")
11 | |> redirect(to: ~p"/fantasy_leagues/#{league_id}/championships/#{id}")
12 |
13 | {:error, _} ->
14 | conn
15 | |> put_flash(:info, "Error when creating slots.")
16 | |> redirect(to: ~p"/fantasy_leagues/#{league_id}/championships/#{id}")
17 | end
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/fantasy_team_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyTeamHTML do
2 | use Ex338Web, :html
3 |
4 | import Ex338Web.FantasyTeamComponents
5 |
6 | def index(assigns) do
7 | ~H"""
8 | <.page_header>
9 | Fantasy Teams
10 |
11 |
12 |
13 |
14 | <%= for team <- Enum.filter(@fantasy_teams, &owner?(@current_user, &1)) do %>
15 | <.team_card fantasy_team={team} />
16 | <% end %>
17 | <%= for team <- Enum.reject(@fantasy_teams, &owner?(@current_user, &1)) do %>
18 | <.team_card fantasy_team={team} />
19 | <% end %>
20 |
21 |
22 | """
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/assets/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "repository": {},
3 | "license": "MIT",
4 | "engines": {
5 | "node": "22.x"
6 | },
7 | "dependencies": {
8 | "alpinejs": "^2.4.0",
9 | "formdata-polyfill": "^3.0.18",
10 | "mdn-polyfills": "^5.17.0",
11 | "nprogress": "^0.2.0",
12 | "trix": "^2.1.15",
13 | "url-search-params-polyfill": "^8.1.0",
14 | "vega": "^5.33.0",
15 | "vega-embed": "^6.26.0",
16 | "vega-lite": "^5.19.0"
17 | },
18 | "devDependencies": {
19 | "@tailwindcss/forms": "^0.5.10",
20 | "@tailwindcss/typography": "^0.5.16",
21 | "prettier": "2.0.5",
22 | "tailwindcss-animate": "^1.0.7"
23 | },
24 | "scripts": {
25 | "deploy": "cd .. && mix assets.deploy && rm -f _build/esbuild*"
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/championship_results.csv:
--------------------------------------------------------------------------------
1 | championship_id,fantasy_player_id,rank,points
2 | 1,1,1,10
3 | 1,2,2,5
4 | 1,3,3,3
5 | 1,4,4,3
6 | 1,5,5,1
7 | 1,6,6,1
8 | 1,7,7,1
9 | 1,8,8,1
10 | 10,333,1,50
11 | 10,334,2,40
12 | 10,335,3,35
13 | 10,336,4,34
14 | 10,337,5,33
15 | 10,338,6,32
16 | 10,339,7,31
17 | 10,340,8,30
18 | 10,341,9,29
19 | 10,342,10,28
20 | 10,343,11,27
21 | 10,344,12,26
22 | 10,345,13,25
23 | 10,346,14,24
24 | 10,347,15,23
25 | 10,348,16,22
26 | 10,349,17,21
27 | 10,350,18,20
28 | 10,351,19,19
29 | 10,352,20,18
30 | 10,353,21,17
31 | 10,354,22,16
32 | 10,355,23,15
33 | 10,356,24,14
34 | 10,357,25,13
35 | 10,358,26,12
36 | 10,359,27,11
37 | 10,360,28,10
38 | 10,361,29,9
39 | 10,362,30,8
40 | 17,429,1,8
41 | 17,398,2,5
42 | 18,429,1,8
43 | 18,398,2,5
44 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160721220202_create_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateDraftPick do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:draft_picks) do
6 | add :draft_position, :decimal, precision: 5, scale: 2
7 | add :round, :integer
8 | add :fantasy_league_id, references(:fantasy_leagues, on_delete: :delete_all)
9 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
10 | add :fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
11 |
12 | timestamps()
13 | end
14 | create index(:draft_picks, [:fantasy_league_id])
15 | create index(:draft_picks, [:fantasy_team_id])
16 | create index(:draft_picks, [:fantasy_player_id])
17 |
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170408222357_create_in_season_draft_pick.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateInSeasonDraftPick do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:in_season_draft_picks) do
6 | add :position, :integer
7 | add :draft_pick_asset_id, references(:roster_positions, on_delete: :nothing)
8 | add :drafted_player_id, references(:fantasy_players, on_delete: :nothing)
9 | add :championship_id, references(:championships, on_delete: :nothing)
10 |
11 | timestamps()
12 | end
13 | create index(:in_season_draft_picks, [:draft_pick_asset_id])
14 | create index(:in_season_draft_picks, [:drafted_player_id])
15 | create index(:in_season_draft_picks, [:championship_id])
16 |
17 | end
18 | end
19 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/authorization.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Authorization do
2 | @moduledoc false
3 |
4 | import Phoenix.Controller
5 | import Plug.Conn
6 |
7 | alias Ex338.Accounts.User
8 |
9 | def authorize_admin(conn, _opts) do
10 | user = conn.assigns.current_user
11 | check_authorized(conn, user)
12 | end
13 |
14 | defp check_authorized(conn, %User{admin: true}) do
15 | conn
16 | end
17 |
18 | defp check_authorized(conn, _) do
19 | conn
20 | |> put_flash(:error, "You are not authorized")
21 | |> redirect(to: "/")
22 | |> halt()
23 | end
24 |
25 | def handle_unauthorized(conn) do
26 | conn
27 | |> put_flash(:error, "You can't access that page!")
28 | |> redirect(to: "/")
29 | |> halt()
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/test/ex338/chats_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.ChatsTest do
2 | use Ex338.DataCase
3 |
4 | alias Ex338.Chats
5 | alias Ex338.Chats.Message
6 |
7 | describe "messages" do
8 | @invalid_attrs %{content: nil}
9 |
10 | test "create_message/1 with valid data creates a message" do
11 | chat = insert(:chat)
12 | user = insert(:user)
13 | valid_attrs = %{content: "some content", user_id: user.id, chat_id: chat.id}
14 |
15 | assert {:ok, %Message{} = message} = Chats.create_message(valid_attrs)
16 | assert message.content == "some content"
17 | end
18 |
19 | test "create_message/1 with invalid data returns error changeset" do
20 | assert {:error, %Ecto.Changeset{}} = Chats.create_message(@invalid_attrs)
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200518235114_drop_coherence_rememberables_table.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DropCoherenceRememberablesTable do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop(table(:rememberables))
6 | end
7 |
8 | def down do
9 | create table(:rememberables) do
10 | add(:series_hash, :string)
11 | add(:token_hash, :string)
12 | add(:token_created_at, :utc_datetime)
13 | add(:user_id, references(:users, on_delete: :delete_all))
14 |
15 | timestamps()
16 | end
17 |
18 | create(index(:rememberables, [:user_id]))
19 | create(index(:rememberables, [:series_hash]))
20 | create(index(:rememberables, [:token_hash]))
21 | create(unique_index(:rememberables, [:user_id, :series_hash, :token_hash]))
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/ex338/rulebooks.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Rulebooks do
2 | @moduledoc false
3 | use NimblePublisher,
4 | build: Ex338.Rulebooks.Rulebook,
5 | from: Application.app_dir(:ex338, "priv/rules/**/*.md"),
6 | as: :rulebooks
7 |
8 | alias Ex338.FantasyLeagues.FantasyLeague
9 |
10 | # And finally export them
11 | def all_rulebooks, do: @rulebooks
12 |
13 | defmodule NotFoundError, do: defexception([:message, plug_status: 404])
14 |
15 | def get_rulebook_for_fantasy_league!(%FantasyLeague{year: year, draft_method: draft_method}) do
16 | Enum.find(
17 | all_rulebooks(),
18 | &(&1.year == year and &1.draft_method == Atom.to_string(draft_method))
19 | ) ||
20 | raise NotFoundError, "rule with year=#{year} and draft_method=#{draft_method} not found"
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_leagues/fantasy_league_draft.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.FantasyLeagueDraft do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 |
7 | schema "fantasy_league_drafts" do
8 | belongs_to(:fantasy_league, Ex338.FantasyLeagues.FantasyLeague)
9 | belongs_to(:championship, Ex338.Championships.Championship)
10 | belongs_to(:chat, Ex338.Chats.Chat)
11 |
12 | timestamps()
13 | end
14 |
15 | @doc false
16 | def changeset(fantasy_league_draft, attrs) do
17 | fantasy_league_draft
18 | |> cast(attrs, [:chat_id, :championship_id, :fantasy_league_id])
19 | |> validate_required([:fantasy_league_id, :chat_id])
20 | |> unique_constraint([:championship_id, :fantasy_league_id])
21 | |> unique_constraint([:chat_id])
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/ex338/repo.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo do
2 | use Ecto.Repo,
3 | otp_app: :ex338,
4 | adapter: Ecto.Adapters.Postgres
5 |
6 | @doc """
7 | A small wrapper around `Repo.transaction/2'.
8 |
9 | Commits the transaction if the lambda returns `:ok` or `{:ok, result}`,
10 | rolling it back if the lambda returns `:error` or `{:error, reason}`. In both
11 | cases, the function returns the result of the lambda.
12 | """
13 | def transact(fun, opts \\ []) do
14 | transaction(
15 | fn ->
16 | case fun.() do
17 | {:ok, value} -> value
18 | :ok -> :transaction_commited
19 | {:error, reason} -> rollback(reason)
20 | :error -> rollback(:transaction_rollback_error)
21 | end
22 | end,
23 | opts
24 | )
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160710122632_create_transaction_line_item.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateTransactionLineItem do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:transaction_line_items) do
6 | add :action, :string
7 | add :roster_transaction_id, references(:roster_transactions,
8 | on_delete: :delete_all)
9 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
10 | add :fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
11 |
12 | timestamps()
13 | end
14 | create index(:transaction_line_items, [:roster_transaction_id])
15 | create index(:transaction_line_items, [:fantasy_team_id])
16 | create index(:transaction_line_items, [:fantasy_player_id])
17 |
18 | end
19 | end
20 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240403232051_create_fantasy_league_drafts.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateFantasyLeagueDrafts do
2 | @moduledoc false
3 | use Ecto.Migration
4 |
5 | def change do
6 | create table(:fantasy_league_drafts) do
7 | add :fantasy_league_id, references(:fantasy_leagues, on_delete: :delete_all)
8 | add :chat_id, references(:chats, on_delete: :nothing)
9 | add :championship_id, references(:championships, on_delete: :delete_all)
10 |
11 | timestamps()
12 | end
13 |
14 | create index(:fantasy_league_drafts, [:fantasy_league_id])
15 | create index(:fantasy_league_drafts, [:championship_id])
16 |
17 | create unique_index(:fantasy_league_drafts, [:championship_id, :fantasy_league_id])
18 | create unique_index(:fantasy_league_drafts, [:chat_id])
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/test/ex338/rulebooks_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.RulebooksTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.Rulebooks
5 |
6 | describe "get_rulebook_for_fantasy_league!/2" do
7 | test "gets rulebook by year and draft method" do
8 | fantasy_league = insert(:fantasy_league, year: 2021, draft_method: "keeper")
9 | result = Rulebooks.get_rulebook_for_fantasy_league!(fantasy_league)
10 |
11 | assert result.year == 2021
12 | assert result.draft_method == "keeper"
13 | end
14 |
15 | test "crashes if it doesn't exist" do
16 | fantasy_league = insert(:fantasy_league, year: 0000, draft_method: "keeper")
17 |
18 | assert_raise Ex338.Rulebooks.NotFoundError, fn ->
19 | Rulebooks.get_rulebook_for_fantasy_league!(fantasy_league)
20 | end
21 | end
22 | end
23 | end
24 |
--------------------------------------------------------------------------------
/lib/ex338_web/gettext.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Gettext do
2 | @moduledoc """
3 | A module providing Internationalization with a gettext-based API.
4 |
5 | By using [Gettext](https://hexdocs.pm/gettext),
6 | your module gains a set of macros for translations, for example:
7 |
8 | use Gettext, backend: Ex338Web.Gettext
9 |
10 | # Simple translation
11 | gettext "Here is the string to translate"
12 |
13 | # Plural translation
14 | ngettext "Here is the string to translate",
15 | "Here are the strings to translate",
16 | 3
17 |
18 | # Domain-based translation
19 | dgettext "errors", "Here is the error message to translate"
20 |
21 | See the [Gettext Docs](https://hexdocs.pm/gettext) for detailed usage.
22 | """
23 | use Gettext.Backend, otp_app: :ex338
24 | end
25 |
--------------------------------------------------------------------------------
/test/ex338_web/plugs/load_user_teams_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.LoadUserTeamsTest do
2 | use Ex338Web.ConnCase
3 |
4 | alias Ex338Web.LoadUserTeams
5 |
6 | @opts LoadUserTeams.init([])
7 |
8 | test "preloads all current user teams into assigns" do
9 | user = insert(:user)
10 | league = insert(:fantasy_league)
11 | team = insert(:fantasy_team, fantasy_league: league)
12 | insert(:owner, fantasy_team: team, user: user)
13 |
14 | conn =
15 | assign(build_conn(), :current_user, user)
16 |
17 | conn = LoadUserTeams.call(conn, @opts)
18 |
19 | [result] = conn.assigns.current_user.fantasy_teams
20 |
21 | assert result.id == team.id
22 | end
23 |
24 | test "handles no current user" do
25 | conn = build_conn()
26 |
27 | conn = LoadUserTeams.call(conn, @opts)
28 |
29 | assert conn == conn
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20170115152858_create_injured_reserve.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateInjuredReserve do
2 | use Ecto.Migration
3 |
4 | def change do
5 | create table(:injured_reserves) do
6 | add :status, :string
7 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
8 | add :add_player_id, references(:fantasy_players, on_delete: :nothing)
9 | add :remove_player_id, references(:fantasy_players, on_delete: :nothing)
10 | add :replacement_player_id, references(:fantasy_players, on_delete: :nothing)
11 |
12 | timestamps()
13 | end
14 | create index(:injured_reserves, [:fantasy_team_id])
15 | create index(:injured_reserves, [:add_player_id])
16 | create index(:injured_reserves, [:remove_player_id])
17 | create index(:injured_reserves, [:replacement_player_id])
18 |
19 | end
20 | end
21 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160806130855_drop_transaction_line_item.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.DropTransactionLineItem do
2 | use Ecto.Migration
3 |
4 | def up do
5 | drop table(:transaction_line_items)
6 | end
7 |
8 | def down do
9 | create table(:transaction_line_items) do
10 | add :action, :string
11 | add :roster_transaction_id, references(:roster_transactions,
12 | on_delete: :delete_all)
13 | add :fantasy_team_id, references(:fantasy_teams, on_delete: :nothing)
14 | add :fantasy_player_id, references(:fantasy_players, on_delete: :nothing)
15 |
16 | timestamps()
17 | end
18 | create index(:transaction_line_items, [:roster_transaction_id])
19 | create index(:transaction_line_items, [:fantasy_team_id])
20 | create index(:transaction_line_items, [:fantasy_player_id])
21 | end
22 | end
23 |
--------------------------------------------------------------------------------
/test/ex338/calendar_assistant_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.CalendarAssistantTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.CalendarAssistant
5 |
6 | describe "days_from_now/1" do
7 | test "returns a date a specified number of days from now" do
8 | now = DateTime.utc_now()
9 | yesterday = CalendarAssistant.days_from_now(-1)
10 | tomorrow = CalendarAssistant.days_from_now(1)
11 |
12 | assert DateTime.after?(now, yesterday)
13 | assert DateTime.before?(now, tomorrow)
14 | end
15 | end
16 |
17 | describe "mins_from_now/1" do
18 | test "returns a date a specified number of mins from now" do
19 | now = DateTime.utc_now()
20 | before = CalendarAssistant.mins_from_now(-1)
21 | later = CalendarAssistant.mins_from_now(1)
22 |
23 | assert DateTime.after?(now, before)
24 | assert DateTime.before?(now, later)
25 | end
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/ex338/ecto_enums.ex:
--------------------------------------------------------------------------------
1 | import EctoEnum
2 |
3 | defenum(DraftQueueStatusEnum, :draft_queue_status, [
4 | :pending,
5 | :drafted,
6 | :unavailable,
7 | :archived,
8 | :cancelled
9 | ])
10 |
11 | defenum(FantasyLeagueNavbarDisplayEnum, :fantasy_league_navbar_display, [
12 | :primary,
13 | :archived,
14 | :hidden
15 | ])
16 |
17 | defenum(FantasyTeamAutodraftSettingEnum, :fantasy_team_autodraft_setting, [
18 | :on,
19 | :off,
20 | :single
21 | ])
22 |
23 | defenum(HistoricalRecordTypeEnum, :historical_record_type, [
24 | :season,
25 | :all_time
26 | ])
27 |
28 | defenum(FantasyLeagueDraftMethodEnum, :fantasy_league_draft_method, [
29 | :redraft,
30 | :keeper
31 | ])
32 |
33 | defenum(InjuredReserveStatusEnum, :injured_reserve_status, [
34 | :submitted,
35 | :approved,
36 | :rejected,
37 | :returned
38 | ])
39 |
40 | defenum(OwnerRulesEnum, :owner_rules, [
41 | :unaccepted,
42 | :accepted,
43 | :exempt
44 | ])
45 |
--------------------------------------------------------------------------------
/test/ex338_web/live/user_invitation_live_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserInvitationLiveTest do
2 | use Ex338Web.ConnCase, async: true
3 |
4 | import Phoenix.LiveViewTest
5 |
6 | describe "New Invitations as admin" do
7 | setup :register_and_log_in_admin
8 |
9 | test "sends a new invitation email", %{conn: conn} do
10 | {:ok, view, _html} = live(conn, ~p"/invitations/new")
11 |
12 | new_user_email = "brown@example.com"
13 |
14 | view
15 | |> form("#invitation_form", user: %{"email" => new_user_email})
16 | |> render_submit() =~ "Invitation sent to #{new_user_email}"
17 | end
18 | end
19 |
20 | describe "New Invitations as user" do
21 | setup :register_and_log_in_user
22 |
23 | test "redirects a regular user", %{conn: conn} do
24 | {:error, {:redirect, %{to: "/", flash: %{"error" => "You are not authorized"}}}} =
25 | live(conn, ~p"/invitations/new")
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/ex338_web/plugs/canonical_domain.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.CanonicalDomain do
2 | @moduledoc """
3 | Redirects to root domain when host is heroku domain.
4 | """
5 |
6 | import Plug.Conn
7 |
8 | def init(options) do
9 | # initialize options
10 |
11 | options
12 | end
13 |
14 | def call(conn, _opts) do
15 | uri =
16 | conn
17 | |> request_url()
18 | |> URI.parse()
19 |
20 | if redirect?(uri) do
21 | uri = %{uri | host: canonical_host()}
22 | canonical_url = URI.to_string(uri)
23 |
24 | conn
25 | |> put_status(:moved_permanently)
26 | |> Phoenix.Controller.redirect(external: canonical_url)
27 | |> halt()
28 | else
29 | conn
30 | end
31 | end
32 |
33 | defp redirect?(%{host: "the338challenge.herokuapp.com"}), do: true
34 |
35 | defp redirect?(_), do: false
36 |
37 | defp canonical_host do
38 | Ex338Web.Endpoint.config(:url)[:host]
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/ex338/validate_helpers.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.ValidateHelpers do
2 | @moduledoc false
3 |
4 | def slot_available?(roster_positions, max_flex_spots) do
5 | total_slot_count = count_total_slots(roster_positions)
6 |
7 | roster_positions
8 | |> count_regular_slots()
9 | |> calculate_flex_slots_used(total_slot_count)
10 | |> compare_flex_slots(max_flex_spots)
11 | end
12 |
13 | ## Helpers
14 |
15 | ## slot_available?
16 |
17 | defp count_total_slots(slots) do
18 | Enum.count(slots)
19 | end
20 |
21 | defp count_regular_slots(slots) do
22 | slots
23 | |> Enum.map(& &1.fantasy_player.sports_league_id)
24 | |> Enum.uniq()
25 | |> Enum.count()
26 | end
27 |
28 | defp calculate_flex_slots_used(regular_slots_filled, total_filled) do
29 | total_filled - regular_slots_filled
30 | end
31 |
32 | defp compare_flex_slots(num_filled, max_flex_spots) do
33 | num_filled <= max_flex_spots
34 | end
35 | end
36 |
--------------------------------------------------------------------------------
/test/support/fixtures/accounts_fixtures.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.AccountsFixtures do
2 | @moduledoc """
3 | This module defines test helpers for creating
4 | entities via the `Ex338.Accounts` context.
5 | """
6 |
7 | def unique_user_email, do: "user#{System.unique_integer()}@example.com"
8 | def valid_user_password, do: "hello_world!"
9 |
10 | def valid_user_attributes(attrs \\ %{}) do
11 | Enum.into(attrs, %{
12 | name: "Brown",
13 | email: unique_user_email(),
14 | password: valid_user_password()
15 | })
16 | end
17 |
18 | def user_fixture(attrs \\ %{}) do
19 | {:ok, user} =
20 | attrs
21 | |> valid_user_attributes()
22 | |> Ex338.Accounts.register_user_for_fixture()
23 |
24 | user
25 | end
26 |
27 | def extract_user_token(fun) do
28 | {:ok, captured_email} = fun.(&"[TOKEN]#{&1}[TOKEN]")
29 | [_, token | _] = String.split(captured_email.text_body, "[TOKEN]")
30 | token
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/page_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.PageController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues
5 | alias Ex338.Rulebooks
6 |
7 | def index(conn, _params) do
8 | leagues = FantasyLeagues.get_leagues_by_status("primary")
9 | season_records = FantasyLeagues.list_current_season_records()
10 | all_time_records = FantasyLeagues.list_current_all_time_records()
11 | winnings = FantasyLeagues.list_all_winnings()
12 |
13 | render(conn, :index,
14 | all_time_records: all_time_records,
15 | fantasy_leagues: leagues,
16 | page_title: "338 Challenge",
17 | season_records: season_records,
18 | winnings: winnings
19 | )
20 | end
21 |
22 | def rules(conn, %{"fantasy_league_id" => id}) do
23 | fantasy_league = FantasyLeagues.get(id)
24 | rulebook = Rulebooks.get_rulebook_for_fantasy_league!(fantasy_league)
25 | render(conn, :rules, fantasy_league: fantasy_league, rulebook: rulebook)
26 | end
27 | end
28 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/table_upload_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.TableUploadController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.Uploader
5 |
6 | def new(conn, _params) do
7 | render(conn, :new,
8 | table_options: Uploader.table_options(),
9 | page_title: "Upload Spreadsheet"
10 | )
11 | end
12 |
13 | def create(conn, %{"table_upload" => table_params}) do
14 | file = table_params["spreadsheet"]
15 | module = table_params["table"]
16 |
17 | case Uploader.insert_from_csv(file.path, module) do
18 | {:ok, results} ->
19 | conn
20 | |> put_flash(:info, "Uploaded #{Enum.count(results)} records to #{module} successfully")
21 | |> redirect(to: ~p"/table_upload/new")
22 |
23 | {:error, _, changeset, _} ->
24 | conn
25 | |> put_flash(:error, "There was an error during the upload: #{inspect(changeset.errors)}")
26 | |> render(:new, table_options: Uploader.table_options())
27 | end
28 | end
29 | end
30 |
--------------------------------------------------------------------------------
/assets/js/filter_players.js:
--------------------------------------------------------------------------------
1 | const sportSelect = document.querySelector(".sports-select-filter")
2 |
3 | if (sportSelect) {
4 | sportSelect.onchange = filterPlayers
5 | }
6 | const playerOptions = document.querySelectorAll(".players-to-filter option")
7 |
8 | const players = Array.from(playerOptions)
9 |
10 | function filterPlayers(event) {
11 | const sportAbbrev = event.target.value
12 |
13 | const playersSelect = document.querySelector(".players-to-filter")
14 |
15 | removePlayerOptions(playersSelect)
16 |
17 | const filteredPlayers = players.filter((player) => {
18 | return player.className === sportAbbrev
19 | })
20 |
21 | filteredPlayers.forEach(function (player) {
22 | const newPlayer = player.cloneNode(true)
23 | playersSelect.appendChild(newPlayer)
24 | })
25 |
26 | playersSelect.options[0].selected = true
27 | }
28 |
29 | function removePlayerOptions(selectElement) {
30 | while (selectElement.firstChild) {
31 | selectElement.removeChild(selectElement.firstChild)
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/test/ex338_web/live/championship_live/index_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ChampionshipLive.IndexTest do
2 | use Ex338Web.ConnCase
3 |
4 | import Phoenix.LiveViewTest
5 |
6 | describe "index/2" do
7 | test "lists all championships", %{conn: conn} do
8 | f_league = insert(:fantasy_league, year: 2017)
9 | s_league_a = insert(:sports_league)
10 | s_league_b = insert(:sports_league)
11 | insert(:league_sport, fantasy_league: f_league, sports_league: s_league_a)
12 | insert(:league_sport, fantasy_league: f_league, sports_league: s_league_b)
13 | championship_a = insert(:championship, sports_league: s_league_a)
14 | championship_b = insert(:championship, sports_league: s_league_b)
15 |
16 | {:ok, _view, html} = live(conn, ~p"/fantasy_leagues/#{f_league.id}/championships")
17 |
18 | assert html =~ "Championships"
19 | assert html =~ championship_a.title
20 | assert html =~ championship_b.title
21 | assert html =~ championship_b.sports_league.abbrev
22 | end
23 | end
24 | end
25 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20160730191327_create_coherence_user.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateCoherenceUser do
2 | use Ecto.Migration
3 | def change do
4 | create table(:users) do
5 | add :name, :string
6 | add :email, :string
7 | # rememberable
8 | add :remember_created_at, :utc_datetime
9 | # authenticatable
10 | add :password_hash, :string
11 | # recoverable
12 | add :reset_password_token, :string
13 | add :reset_password_sent_at, :utc_datetime
14 | # lockable
15 | add :failed_attempts, :integer, default: 0
16 | add :locked_at, :utc_datetime
17 | # trackable
18 | add :sign_in_count, :integer, default: 0
19 | add :current_sign_in_at, :utc_datetime
20 | add :last_sign_in_at, :utc_datetime
21 | add :current_sign_in_ip, :string
22 | add :last_sign_in_ip, :string
23 | # unlockable_with_token
24 | add :unlock_token, :string
25 |
26 | timestamps()
27 | end
28 | create unique_index(:users, [:email])
29 |
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/assets/css/app.css:
--------------------------------------------------------------------------------
1 | /* This file is for your main application css. */
2 |
3 | @import "tailwindcss";
4 |
5 | @import "nprogress/nprogress.css" layer(utilities);
6 | @import "trix/dist/trix.css";
7 |
8 | @import "./live_view.css" layer(utilities);
9 |
10 | @config '../tailwind.config.js';
11 |
12 | /*
13 | The default border color has changed to `currentColor` in Tailwind CSS v4,
14 | so we've added these compatibility styles to make sure everything still
15 | looks the same as it did with Tailwind CSS v3.
16 |
17 | If we ever want to remove these styles, we need to add an explicit border
18 | color utility to any element that depends on these defaults.
19 | */
20 | @layer base {
21 | *,
22 | ::after,
23 | ::before,
24 | ::backdrop,
25 | ::file-selector-button {
26 | border-color: var(--color-gray-200, currentColor);
27 | }
28 | }
29 |
30 | [x-cloak] {
31 | display: none;
32 | }
33 |
34 | .trix-button-group--file-tools {
35 | display: none !important;
36 | }
37 |
38 | .trix-button--icon-attach {
39 | display: none !important;
40 | }
41 |
--------------------------------------------------------------------------------
/config/test.exs:
--------------------------------------------------------------------------------
1 | import Config
2 |
3 | # Only in tests, remove the complexity from the password hashing algorithm
4 | config :bcrypt_elixir, :log_rounds, 1
5 |
6 | # Configure your database
7 | config :ex338, Ex338.Repo,
8 | adapter: Ecto.Adapters.Postgres,
9 | username: "postgres",
10 | password: "postgres",
11 | database: "ex338_test",
12 | hostname: "localhost",
13 | # We don't run a server during test. If one is required,
14 | # you can enable the server option below.
15 | pool: Ecto.Adapters.SQL.Sandbox
16 |
17 | config :ex338, Ex338Web.Endpoint,
18 | http: [port: 4001],
19 | server: false,
20 | pubsub_server: Ex338.PubSub
21 |
22 | config :ex338, Ex338Web.Mailer, adapter: Swoosh.Adapters.Test
23 | config :ex338, Oban, testing: :inline
24 |
25 | config :floki, :encode_raw_html, false
26 |
27 | config :honeybadger, :environment_name, :test
28 |
29 | # Print only warnings and errors during test
30 | config :logger, level: :warning
31 |
32 | # Initialize plugs at runtime for faster development compilation
33 | config :phoenix, :plug_init_mode, :runtime
34 |
--------------------------------------------------------------------------------
/lib/ex338/roster_positions/ir_position.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.RosterPositions.IRPosition do
2 | @moduledoc false
3 |
4 | def separate_from_active_for_teams(fantasy_teams) do
5 | Enum.map(fantasy_teams, &separate_from_active_for_team(&1))
6 | end
7 |
8 | def separate_from_active_for_team(%{roster_positions: roster_positions} = fantasy_team) do
9 | {ir_positions, active_positions} = split_ir_and_active_positions(roster_positions)
10 |
11 | ir_positions = set_position_to_ir(ir_positions)
12 |
13 | fantasy_team
14 | |> Map.delete(:roster_positions)
15 | |> Map.put(:ir_positions, ir_positions)
16 | |> Map.put(:roster_positions, active_positions)
17 | end
18 |
19 | def separate_from_active_for_team(fantasy_team) do
20 | fantasy_team
21 | end
22 |
23 | defp split_ir_and_active_positions(roster_positions) do
24 | Enum.split_with(roster_positions, &(&1.status == "injured_reserve"))
25 | end
26 |
27 | defp set_position_to_ir(ir_positions) do
28 | Enum.map(ir_positions, &Map.put(&1, :position, "Injured Reserve"))
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/table_upload_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.TableUploadHTML do
2 | use Ex338Web, :html
3 |
4 | def new(assigns) do
5 | ~H"""
6 | <.two_col_form
7 | :let={f}
8 | for={%{}}
9 | as={:table_upload}
10 | multipart={true}
11 | action={~p"/table_upload"}
12 | show_form_error={false}
13 | >
14 | <:title>
15 | Upload Data from CSV Spreadsheet
16 |
17 | <:description>
18 | Select a database table and corresponding CSV file from your computer to upload data
19 |
20 |
21 | <.input
22 | field={f[:table]}
23 | label="Table to Upload"
24 | type="select"
25 | required
26 | options={@table_options}
27 | prompt="Select a table to upload"
28 | />
29 |
30 | <.input field={f[:spreadsheet]} label="Upload Spreadsheet" type="file" />
31 |
32 | <:actions>
33 | <.submit_buttons back_route={~p"/"} submit_text="Upload" />
34 |
35 |
36 | """
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/ex338_web/controllers/fantasy_league/calendar_download_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyLeague.CalendarDownloadTest do
2 | use Ex338Web.ConnCase
3 |
4 | setup :register_and_log_in_user
5 |
6 | describe "get/2" do
7 | test "allows a user to download the calendar of events for a fantasy league", %{conn: conn} do
8 | fantasy_league = insert(:fantasy_league, year: 2017)
9 | sports_league = insert(:sports_league)
10 | insert(:league_sport, fantasy_league: fantasy_league, sports_league: sports_league)
11 | insert(:league_sport, fantasy_league: fantasy_league, sports_league: sports_league)
12 | championship = insert(:championship, sports_league: sports_league)
13 |
14 | _championship_event =
15 | insert(:championship,
16 | sports_league: sports_league,
17 | overall: championship,
18 | category: "event"
19 | )
20 |
21 | conn = get(conn, ~p"/fantasy_leagues/#{fantasy_league.id}/calendar_download")
22 |
23 | assert response_content_type(conn, :ics)
24 | end
25 | end
26 | end
27 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_leagues/fantasy_league_admin.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.FantasyLeagueAdmin do
2 | @moduledoc false
3 | def form_fields(_) do
4 | [
5 | fantasy_league_name: nil,
6 | year: nil,
7 | division: nil,
8 | navbar_display: %{choices: navbar_display_options()},
9 | championships_start_at: nil,
10 | championships_end_at: nil,
11 | max_flex_spots: nil,
12 | only_flex?: nil,
13 | must_draft_each_sport?: nil,
14 | draft_method: %{choices: draft_method_options()},
15 | max_draft_hours: nil,
16 | draft_picks_locked?: nil,
17 | sport_draft_id: %{
18 | label: "Select Sport With Active InSeason Draft",
19 | choices: [{"None", nil}] ++ Ex338.FantasyPlayers.list_sport_options()
20 | }
21 | ]
22 | end
23 |
24 | defp navbar_display_options do
25 | Enum.filter(FantasyLeagueNavbarDisplayEnum.__valid_values__(), &is_binary/1)
26 | end
27 |
28 | defp draft_method_options do
29 | Enum.filter(FantasyLeagueDraftMethodEnum.__valid_values__(), &is_binary/1)
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/priv/repo/annual_league_setup/draft_data.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.AnnualLeagueSetup.DraftData do
2 | @moduledoc """
3 | Script for populating the database. You can run it as:
4 |
5 | mix run priv/repo/annual_league_setup/draft_data.exs
6 |
7 | Inside the script, you can read and write to any of your
8 | repositories directly:
9 |
10 | Ex338.Repo.insert!(%Ex338.SomeModel{})
11 |
12 | We recommend using the bang functions (`insert!`, `update!`
13 | and so on) as they will fail if something goes wrong.
14 |
15 | To substitue hidden characters in VIM %s/vm/\r/g
16 | """
17 |
18 | alias Ex338.{Repo, DraftPicks.DraftPick}
19 |
20 | def store_draft_picks(row) do
21 | changeset = DraftPick.changeset(%DraftPick{}, row)
22 | Repo.insert!(changeset)
23 | end
24 | end
25 |
26 | File.stream!("priv/repo/annual_league_setup/data/draft_picks.csv")
27 | |> Stream.drop(1)
28 | |> CSV.decode!(headers: [:draft_position, :fantasy_league_id, :fantasy_team_id,
29 | :fantasy_team_name])
30 | |> Enum.each(&Ex338.AnnualLeagueSetup.DraftData.store_draft_picks/1)
31 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20240308035310_create_users_auth_tables.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.CreateUsersAuthTables do
2 | @moduledoc false
3 | use Ecto.Migration
4 |
5 | def up do
6 | execute "CREATE EXTENSION IF NOT EXISTS citext", ""
7 |
8 | alter table(:users) do
9 | modify :email, :citext, null: false
10 | add :confirmed_at, :naive_datetime
11 | end
12 |
13 | create table(:users_tokens) do
14 | add :user_id, references(:users, on_delete: :delete_all), null: false
15 | add :token, :binary, null: false
16 | add :context, :string, null: false
17 | add :sent_to, :string
18 | timestamps(updated_at: false)
19 | end
20 |
21 | create index(:users_tokens, [:user_id])
22 | create unique_index(:users_tokens, [:context, :token])
23 | end
24 |
25 | def down do
26 | drop index(:users_tokens, [:context, :token])
27 | drop index(:users_tokens, [:user_id])
28 |
29 | drop table(:users_tokens)
30 |
31 | alter table(:users) do
32 | remove :confirmed_at
33 | modify :email, :string, null: false
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/test/ex338/jobs_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.JobsTest do
2 | use Ex338.DataCase, async: true
3 | use Oban.Testing, repo: Ex338.Repo
4 |
5 | alias Ex338.CalendarAssistant
6 | alias Ex338.Jobs
7 |
8 | describe "get_autodraft_job_by/1" do
9 | test "starts autodraft for a championship and a fantasy_league" do
10 | fantasy_league = insert(:fantasy_league)
11 | championship = insert(:championship)
12 |
13 | five_mins = CalendarAssistant.mins_from_now(5)
14 |
15 | Oban.Testing.with_testing_mode(:manual, fn ->
16 | {:ok, job} =
17 | %{fantasy_league_id: fantasy_league.id, championship_id: championship.id}
18 | |> Ex338.Workers.InSeasonAutodraftWorker.new(scheduled_at: five_mins)
19 | |> Oban.insert()
20 |
21 | result =
22 | Jobs.get_autodraft_job_by(%{
23 | fantasy_league_id: fantasy_league.id,
24 | championship_id: championship.id
25 | })
26 |
27 | assert result.id == job.id
28 | assert DateTime.truncate(result.scheduled_at, :second) == five_mins
29 | end)
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Axel Clark
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/lib/ex338/workers/in_season_autodraft_worker.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Workers.InSeasonAutodraftWorker do
2 | @moduledoc false
3 | use Oban.Worker,
4 | max_attempts: 1
5 |
6 | alias Ex338.AutoDraft
7 | alias Ex338.Championships
8 |
9 | @thirty_seconds 30
10 |
11 | @impl Oban.Worker
12 | def perform(%Oban.Job{} = job) do
13 | %{args: %{"fantasy_league_id" => fantasy_league_id, "championship_id" => championship_id}} =
14 | job
15 |
16 | case make_in_season_draft_pick_from_queues(fantasy_league_id, championship_id) do
17 | {:ok, :in_season_draft_picks_complete} ->
18 | {:ok, :in_season_draft_picks_complete}
19 |
20 | result ->
21 | schedule_next_job(job)
22 | result
23 | end
24 | end
25 |
26 | defp make_in_season_draft_pick_from_queues(fantasy_league_id, championship_id) do
27 | championship = Championships.get_championship_by_league(championship_id, fantasy_league_id)
28 | AutoDraft.in_season_draft_pick_from_queues(fantasy_league_id, championship)
29 | end
30 |
31 | defp schedule_next_job(job) do
32 | job.args
33 | |> new(schedule_in: @thirty_seconds)
34 | |> Oban.insert()
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/ex338_web/channels/user_socket.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserSocket do
2 | use Phoenix.Socket
3 |
4 | ## Channels
5 | # channel "room:*", Ex338.RoomChannel
6 |
7 | # Socket params are passed from the client and can
8 | # be used to verify and authenticate a user. After
9 | # verification, you can put default assigns into
10 | # the socket that will be set for all channels, ie
11 | #
12 | # {:ok, assign(socket, :user_id, verified_user_id)}
13 | #
14 | # To deny connection, return `:error`.
15 | #
16 | # See `Phoenix.Token` documentation for examples in
17 | # performing token verification on connect.
18 | def connect(_params, socket) do
19 | {:ok, socket}
20 | end
21 |
22 | # Socket id's are topics that allow you to identify sockets for a given user:
23 | #
24 | # def id(socket), do: "users_socket:#{socket.assigns.user_id}"
25 | #
26 | # Would allow you to broadcast a "disconnect" event and terminate
27 | # all active sockets and channels for a given user:
28 | #
29 | # Ex338.Endpoint.broadcast("users_socket:#{user.id}", "disconnect", %{})
30 | #
31 | # Returning `nil` makes this socket anonymous.
32 | def id(_socket), do: nil
33 | end
34 |
--------------------------------------------------------------------------------
/assets/css/live_view.css:
--------------------------------------------------------------------------------
1 | /* LiveView specific classes for your customizations */
2 | .invalid-feedback {
3 | color: #a94442;
4 | display: block;
5 | margin: -1rem 0 2rem;
6 | }
7 |
8 | .phx-no-feedback.invalid-feedback, .phx-no-feedback .invalid-feedback {
9 | display: none;
10 | }
11 |
12 | .phx-click-loading {
13 | opacity: 0.5;
14 | transition: opacity 1s ease-out;
15 | }
16 |
17 | .phx-disconnected{
18 | cursor: wait;
19 | }
20 | .phx-disconnected *{
21 | pointer-events: none;
22 | }
23 |
24 | .phx-modal {
25 | opacity: 1!important;
26 | position: fixed;
27 | z-index: 1;
28 | left: 0;
29 | top: 0;
30 | width: 100%;
31 | height: 100%;
32 | overflow: auto;
33 | background-color: rgb(0,0,0);
34 | background-color: rgba(0,0,0,0.4);
35 | }
36 |
37 | .phx-modal-content {
38 | background-color: #fefefe;
39 | margin: 15% auto;
40 | padding: 20px;
41 | border: 1px solid #888;
42 | width: 80%;
43 | }
44 |
45 | .phx-modal-close {
46 | color: #aaa;
47 | float: right;
48 | font-size: 28px;
49 | font-weight: bold;
50 | }
51 |
52 | .phx-modal-close:hover,
53 | .phx-modal-close:focus {
54 | color: black;
55 | text-decoration: none;
56 | cursor: pointer;
57 | }
58 |
--------------------------------------------------------------------------------
/test/support/channel_case.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.ChannelCase do
2 | @moduledoc """
3 | This module defines the test case to be used by
4 | channel tests.
5 |
6 | Such tests rely on `Phoenix.ChannelTest` and also
7 | import other functionality to make it easier
8 | to build and query models.
9 |
10 | Finally, if the test case interacts with the database,
11 | it cannot be async. For this reason, every test runs
12 | inside a transaction which is reset at the beginning
13 | of the test unless the test case is marked as async.
14 | """
15 |
16 | use ExUnit.CaseTemplate
17 |
18 | alias Ecto.Adapters.SQL.Sandbox
19 |
20 | using do
21 | quote do
22 | # Import conveniences for testing with channels
23 | use Phoenix.ChannelTest
24 |
25 | import Ecto
26 | import Ecto.Changeset
27 | import Ecto.Query
28 |
29 | alias Ex338.Repo
30 |
31 | # The default endpoint for testing
32 | @endpoint Ex338Web.Endpoint
33 | end
34 | end
35 |
36 | setup tags do
37 | :ok = Sandbox.checkout(Ex338.Repo)
38 |
39 | if !tags[:async] do
40 | Sandbox.mode(Ex338.Repo, {:shared, self()})
41 | end
42 |
43 | :ok
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/test/ex338_web/controllers/owner_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.OwnerControllerTest do
2 | use Ex338Web.ConnCase
3 |
4 | describe "index/2" do
5 | test "lists all owners in a league", %{conn: conn} do
6 | league = insert(:fantasy_league)
7 | other_league = insert(:fantasy_league)
8 | team = insert(:fantasy_team, team_name: "Brown", fantasy_league: league)
9 |
10 | other_team =
11 | insert(
12 | :fantasy_team,
13 | team_name: "Another Team",
14 | fantasy_league: other_league
15 | )
16 |
17 | user = insert_user(%{name: "Brown", email: "brown@example.com"})
18 | other_user = insert_user(%{name: "Axel", email: "axel@example.com"})
19 | insert(:owner, fantasy_team: team, user: user)
20 | insert(:owner, fantasy_team: other_team, user: other_user)
21 |
22 | conn = get(conn, ~p"/fantasy_leagues/#{league.id}/owners")
23 |
24 | assert html_response(conn, 200) =~ ~r/Owners/
25 | assert String.contains?(conn.resp_body, team.team_name)
26 | assert String.contains?(conn.resp_body, user.name)
27 | refute String.contains?(conn.resp_body, other_team.team_name)
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/.iex.exs:
--------------------------------------------------------------------------------
1 | import Ecto.Query
2 |
3 | alias Ex338.Accounts.User
4 | alias Ex338.CalendarAssistant
5 | alias Ex338.Championships
6 | alias Ex338.Championships.Championship
7 | alias Ex338.Championships.ChampionshipResult
8 | alias Ex338.Championships.ChampionshipSlot
9 | alias Ex338.Championships.ChampWithEventsResult
10 | alias Ex338.DraftPicks
11 | alias Ex338.DraftPicks.Admin
12 | alias Ex338.DraftPicks.DraftPick
13 | alias Ex338.DraftQueues
14 | alias Ex338.DraftQueues.DraftQueue
15 | alias Ex338.FantasyLeagues
16 | alias Ex338.FantasyLeagues.FantasyLeague
17 | alias Ex338.FantasyPlayers
18 | alias Ex338.FantasyPlayers.FantasyPlayer
19 | alias Ex338.FantasyPlayers.SportsLeague
20 | alias Ex338.FantasyTeams
21 | alias Ex338.FantasyTeams.FantasyTeam
22 | alias Ex338.FantasyTeams.Owner
23 | alias Ex338.InjuredReserves
24 | alias Ex338.InSeasonDraftPicks
25 | alias Ex338.Mailer
26 | alias Ex338.NotificationEmail
27 | alias Ex338.Repo
28 | alias Ex338.RosterPositions
29 | alias Ex338.RosterPositions.Admin
30 | alias Ex338.RosterPositions.RosterPosition
31 | alias Ex338.Trades
32 | alias Ex338.Trades.Trade
33 | alias Ex338.Trades.TradeLineItem
34 | alias Ex338.Waivers
35 | alias Ex338.Waivers.Admin
36 | alias Ex338.Waivers.Waiver
37 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/commish_email_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.CommishEmailController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.FantasyLeagues.FantasyLeague
5 | alias Ex338.Repo
6 | alias Ex338Web.CommishNotifier
7 |
8 | require Logger
9 |
10 | def new(conn, _params) do
11 | render(
12 | conn,
13 | :new,
14 | fantasy_leagues: Repo.all(FantasyLeague),
15 | page_title: "Commish Email"
16 | )
17 | end
18 |
19 | def create(conn, %{
20 | "commish_email" => %{"leagues" => leagues, "subject" => subject, "message" => message}
21 | }) do
22 | result = CommishNotifier.send_email_to_leagues(leagues, subject, message)
23 |
24 | case result do
25 | {:ok, result} ->
26 | Logger.info("Commish email sent successfully: #{inspect(result)}")
27 |
28 | conn
29 | |> put_flash(:info, "Email sent successfully")
30 | |> redirect(to: ~p"/commish_email/new")
31 |
32 | {:error, reason} ->
33 | Logger.error("Commish email failed: #{inspect(reason)}")
34 |
35 | conn
36 | |> put_flash(:error, "Email failed to send: #{reason}")
37 | |> redirect(to: ~p"/commish_email/new")
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_teams/deadlines.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyTeams.Deadlines do
2 | @moduledoc """
3 | Adds deadline booleans to roster positions
4 | """
5 |
6 | def add_for_league(teams) do
7 | Enum.map(teams, &add_for_team(&1))
8 | end
9 |
10 | def add_for_team(%{roster_positions: positions} = team) do
11 | positions = update_positions(positions)
12 |
13 | %{team | roster_positions: positions}
14 | end
15 |
16 | ## Implementations
17 |
18 | ## add_for_team
19 |
20 | defp update_positions(positions) do
21 | Enum.map(positions, &update_position/1)
22 | end
23 |
24 | defp update_position(
25 | %{fantasy_player: %{sports_league: %{championships: [overall]}}} = position
26 | ) do
27 | overall = Ex338.Championships.Championship.add_deadline_statuses(overall)
28 |
29 | update_championship(position, overall)
30 | end
31 |
32 | defp update_position(position), do: position
33 |
34 | defp update_championship(position, championship) do
35 | put_in(
36 | position,
37 | [
38 | Access.key(:fantasy_player),
39 | Access.key(:sports_league),
40 | Access.key(:championships),
41 | Access.at(0)
42 | ],
43 | championship
44 | )
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/test/ex338/fantasy_leagues/historical_winning_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.HistoricalWinningTest do
2 | use Ex338.DataCase, aysnc: true
3 |
4 | alias Ex338.FantasyLeagues.HistoricalWinning
5 |
6 | describe "changeset/2" do
7 | @valid_attrs %{
8 | team: "Brown",
9 | amount: 213
10 | }
11 | test "changeset with valid attributes" do
12 | changeset = HistoricalWinning.changeset(%HistoricalWinning{}, @valid_attrs)
13 | assert changeset.valid?
14 | end
15 |
16 | @invalid_attrs %{
17 | team: "",
18 | amount: 0
19 | }
20 | test "changeset with invalid attributes" do
21 | changeset = HistoricalWinning.changeset(%HistoricalWinning{}, @invalid_attrs)
22 | refute changeset.valid?
23 | end
24 | end
25 |
26 | describe "order_by_amount/1" do
27 | test "orders winnings by amount" do
28 | a = insert(:historical_winning, amount: 100)
29 | b = insert(:historical_winning, amount: 300)
30 | c = insert(:historical_winning, amount: 200)
31 |
32 | result =
33 | HistoricalWinning
34 | |> HistoricalWinning.order_by_amount()
35 | |> Repo.all()
36 |
37 | assert Enum.map(result, & &1.id) == [b.id, c.id, a.id]
38 | end
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/test/ex338_web/live/fantasy_league_live/show_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyLeagueLive.ShowTest do
2 | use Ex338Web.ConnCase
3 |
4 | import Phoenix.LiveViewTest
5 |
6 | describe "show/2" do
7 | test "shows league and lists all fantasy teams", %{conn: conn} do
8 | league = insert(:fantasy_league)
9 |
10 | team_1 =
11 | insert(:fantasy_team, team_name: "Brown", fantasy_league: league, winnings_adj: 20.00)
12 |
13 | team_2 = insert(:fantasy_team, team_name: "Axel", fantasy_league: league, dues_paid: 100.00)
14 | player = insert(:fantasy_player)
15 | insert(:roster_position, fantasy_team: team_1, fantasy_player: player, status: "active")
16 | insert(:championship_result, points: 8, rank: 1, fantasy_player: player)
17 |
18 | insert(
19 | :champ_with_events_result,
20 | points: 8.0,
21 | rank: 1,
22 | winnings: 25.00,
23 | fantasy_team: team_1
24 | )
25 |
26 | {:ok, _view, html} = live(conn, ~p"/fantasy_leagues/#{league.id}")
27 |
28 | assert html =~ "Standings"
29 | assert html =~ team_1.team_name
30 | assert html =~ team_2.team_name
31 | assert html =~ "100"
32 | assert html =~ "16.0"
33 | assert html =~ "$70"
34 | end
35 | end
36 | end
37 |
--------------------------------------------------------------------------------
/lib/ex338/trades/trade_vote.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Trades.TradeVote do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 |
7 | alias Ex338.Trades.TradeVote
8 |
9 | schema "trade_votes" do
10 | field(:approve, :boolean, default: true)
11 | belongs_to(:trade, Ex338.Trades.Trade)
12 | belongs_to(:fantasy_team, Ex338.FantasyTeams.FantasyTeam)
13 | belongs_to(:user, Ex338.Accounts.User)
14 |
15 | timestamps()
16 | end
17 |
18 | def assoc_changeset(%TradeVote{} = trade_vote, attrs) do
19 | trade_vote
20 | |> cast(attrs, [:approve, :fantasy_team_id, :user_id])
21 | |> validate_required([:approve, :fantasy_team_id, :user_id])
22 | |> unique_constraint(
23 | :trade,
24 | name: :trade_votes_trade_id_fantasy_team_id_index,
25 | message: "Team has already voted"
26 | )
27 | end
28 |
29 | @doc false
30 | def changeset(%TradeVote{} = trade_vote, attrs) do
31 | trade_vote
32 | |> cast(attrs, [:approve, :trade_id, :fantasy_team_id, :user_id])
33 | |> validate_required([:approve, :trade_id, :fantasy_team_id, :user_id])
34 | |> unique_constraint(
35 | :trade,
36 | name: :trade_votes_trade_id_fantasy_team_id_index,
37 | message: "Team has already voted"
38 | )
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/commish_email_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.CommishEmailHTML do
2 | use Ex338Web, :html
3 |
4 | alias Ex338.FantasyLeagues
5 |
6 | def new(assigns) do
7 | ~H"""
8 | <.two_col_form
9 | :let={f}
10 | for={@conn}
11 | as={:commish_email}
12 | action={~p"/commish_email"}
13 | show_form_error={false}
14 | >
15 | <:title>
16 | Send an email to fantasy leagues
17 |
18 | <:description>
19 | Select one or more leagues to send an email.
20 |
21 |
22 | <.input
23 | field={f[:leagues]}
24 | label="Select leagues to email"
25 | type="select"
26 | multiple
27 | options={FantasyLeagues.format_leagues_for_select(@fantasy_leagues)}
28 | />
29 |
30 | Hold CTRL (Windows) or Command (Mac) to select multiple leagues
31 |
32 | <.input field={f[:subject]} label="Subject" type="text" required />
33 | <.input field={f[:message]} label="Message" type="textarea" required rows={6} />
34 | <:actions>
35 | <.submit_buttons back_route={~p"/"} submit_text="Send" />
36 |
37 |
38 | """
39 | end
40 | end
41 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ex338
2 |
3 | A web application to manage the 338 Challenge Fantasy League built using [Elixir](https://elixir-lang.org/)
4 | and [Phoenix](http://www.phoenixframework.org/). The 338 Challenge is a
5 | fantasy sports league where you pick teams instead of players and get points
6 | when your team wins its league championship.
7 |
8 | The draft, waivers, and trades are accomplished through the website. The league
9 | is set up to have multiple divisions (with relegation).
10 |
11 | ### Home Page
12 |
13 | 
14 |
15 |
16 |
17 |
18 | ### List of Players
19 |
20 | 
21 |
22 | ## Setup
23 |
24 | To start your Phoenix server:
25 |
26 | - Run `mix setup` to install and setup dependencies
27 | - Start Phoenix endpoint with `mix phx.server` or inside IEx with `iex -S mix phx.server`
28 |
29 | Now you can visit [`localhost:4000`](http://localhost:4000) from your browser.
30 |
31 | ## Contributing
32 |
33 | 1. Fork it!
34 | 2. Create your feature branch: `git checkout -b my-new-feature`
35 | 3. Commit your changes: `git commit -am 'Add some feature'`
36 | 4. Push to the branch: `git push origin my-new-feature`
37 | 5. Submit a pull request :D
38 |
--------------------------------------------------------------------------------
/lib/ex338/draft_picks/future_pick.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.DraftPicks.FuturePick do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 | import Ecto.Query, warn: false
7 |
8 | alias Ex338.FantasyTeams.FantasyTeam
9 |
10 | schema "future_picks" do
11 | field(:round, :integer)
12 | belongs_to(:original_team, FantasyTeam, foreign_key: :original_team_id)
13 | belongs_to(:current_team, FantasyTeam, foreign_key: :current_team_id)
14 |
15 | timestamps()
16 | end
17 |
18 | def by_league(query, fantasy_league_id) do
19 | from(
20 | p in query,
21 | join: t in assoc(p, :current_team),
22 | where: t.fantasy_league_id == ^fantasy_league_id
23 | )
24 | end
25 |
26 | @doc false
27 | def changeset(future_pick, attrs) do
28 | future_pick
29 | |> cast(attrs, [:round, :original_team_id, :current_team_id])
30 | |> validate_required([:round, :original_team_id, :current_team_id])
31 | end
32 |
33 | def preload_assocs(query) do
34 | from(
35 | p in query,
36 | preload: [:current_team, :original_team]
37 | )
38 | end
39 |
40 | def sort_by_round_and_team(query) do
41 | from(
42 | p in query,
43 | join: t in assoc(p, :current_team),
44 | order_by: [t.team_name, p.round]
45 | )
46 | end
47 | end
48 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_leagues/historical_record.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyLeagues.HistoricalRecord do
2 | @moduledoc false
3 |
4 | use Ecto.Schema
5 |
6 | import Ecto.Changeset
7 | import Ecto.Query, warn: false
8 |
9 | schema "historical_records" do
10 | field(:description, :string)
11 | field(:record, :string)
12 | field(:team, :string)
13 | field(:year, :string)
14 | field(:order, :float)
15 | field(:type, HistoricalRecordTypeEnum, default: "season")
16 | field(:archived, :boolean)
17 |
18 | timestamps()
19 | end
20 |
21 | def all_time_records(query) do
22 | from(r in query, where: r.type == "all_time")
23 | end
24 |
25 | @doc """
26 | Builds a changeset based on the `struct` and `params`.
27 | """
28 | def changeset(record, params \\ %{}) do
29 | record
30 | |> cast(params, [:archived, :description, :order, :record, :team, :type, :year])
31 | |> validate_required([:archived, :description, :record, :team, :type])
32 | end
33 |
34 | def current_records(query) do
35 | from(r in query, where: r.archived == false)
36 | end
37 |
38 | def season_records(query) do
39 | from(r in query, where: r.type == "season")
40 | end
41 |
42 | def sorted_by_order(query) do
43 | from(r in query, order_by: [asc: r.order])
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/commish/fantasy_league_live/edit.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Commish.FantasyLeagueLive.Edit do
2 | @moduledoc false
3 | use Ex338Web, :live_view
4 |
5 | alias Ex338.FantasyLeagues
6 | alias Ex338Web.Components.Commish
7 |
8 | @impl true
9 | def render(assigns) do
10 | ~H"""
11 |
12 |
13 | <.live_component
14 | module={Ex338Web.Commish.FantasyLeagueLive.FormComponent}
15 | id={@fantasy_league.id}
16 | title={@page_title}
17 | action={@live_action}
18 | fantasy_league={@fantasy_league}
19 | return_to={~p"/commish/fantasy_leagues/#{@fantasy_league}/edit"}
20 | >
21 |
22 | """
23 | end
24 |
25 | @impl true
26 | def mount(_params, _session, socket) do
27 | {:ok, socket}
28 | end
29 |
30 | @impl true
31 | def handle_params(%{"id" => id}, _, socket) do
32 | fantasy_league = FantasyLeagues.get_fantasy_league_with_teams!(id)
33 |
34 | socket =
35 | socket
36 | |> assign(:page_title, "Edit Fantasy League")
37 | |> assign(:fantasy_league, fantasy_league)
38 | |> assign(
39 | :current_route,
40 | ~p"/commish/fantasy_leagues/#{fantasy_league}/edit"
41 | )
42 |
43 | {:noreply, socket}
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/ex338_web/mailer.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Mailer do
2 | @moduledoc false
3 | use Swoosh.Mailer, otp_app: :ex338
4 |
5 | require Logger
6 |
7 | def handle_delivery({:ok, result}) do
8 | Logger.info("Sent email notification")
9 | {:ok, result}
10 | end
11 |
12 | def handle_delivery({:error, {_, reason}}) do
13 | Logger.error("Email failed to send: #{inspect(reason)}")
14 | {:error, reason}
15 | end
16 |
17 | def build_and_deliver(recipient, subject, body) do
18 | email =
19 | Swoosh.Email.new(
20 | to: recipient,
21 | from: default_from_name_and_email(),
22 | subject: subject,
23 | html_body:
24 | body
25 | |> Phoenix.HTML.html_escape()
26 | |> Phoenix.HTML.safe_to_string()
27 | )
28 |
29 | case deliver(email) do
30 | {:ok, _} ->
31 | Logger.info("Sent email notification")
32 | {:ok, email}
33 |
34 | {:error, {_, reason}} ->
35 | Logger.warning("Sending email failed: #{inspect(reason)}")
36 | {:error, reason}
37 | end
38 | end
39 |
40 | def default_from_name_and_email do
41 | email_address = Application.fetch_env!(:ex338, :mailer_default_from_email)
42 | name = Application.fetch_env!(:ex338, :mailer_default_from_name)
43 |
44 | {name, email_address}
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/priv/repo/annual_league_setup/data/owners.csv:
--------------------------------------------------------------------------------
1 | Fantasy Team Id,Team Name,2017 Fantasy Team Id,User Id
2 | 68,Brown,31,4
3 | 69,The Consortium,32,45
4 | 69,,32,10
5 | 69,,32,7
6 | 70,Kintz/Mori,33,44
7 | 70,,33,21
8 | 71,Axel,34,3
9 | 72,BD,35,5
10 | 73,Bartch,36,19
11 | 74,Wilson,37,35
12 | 74,,37,20
13 | 75,Miller/Wernz,38,24
14 | 75,,38,11
15 | 76,FloPow,39,42
16 | 76,,39,37
17 | 77,G-Brew,40,30
18 | 77,,40,25
19 | 78,Davie/Paul,41,36
20 | 78,,41,27
21 | 79,Bro/Heli,42,34
22 | 79,,42,6
23 | 80,Jake,43,41
24 | 81,Zach,44,40
25 | 82,Vondy/Clark,45,22
26 | 82,,45,15
27 | 83,Adam/Mike,46,33
28 | 83,,46,56
29 | 84,Evan,47,31
30 | 85,Hughes,48,43
31 | 86,Sister-Sister,49,14
32 | 86,,49,8
33 | 87,Murphy's Law,50,13
34 | 88,Tim,51,38
35 | 89,Mike,52,12
36 | 89,,52,58
37 | 90,Riss/Ehrman,53,17
38 | 90,,53,9
39 | 91,Bailey,54,29
40 | 92,Nelson,55,23
41 | 93,Teeter,56,16
42 | 94,Mick/Jason,57,26
43 | 94,,57,18
44 | 95,Tom,58,32
45 | 98,Tom/Josh,61,52
46 | 98,,61,51
47 | 96,Danger 338,62,57
48 | 97,THE LIFE OF JASO,63,54
49 | 103,Mick/Jackson,64,18
50 | 99,Christian/Zach,65,55
51 | 100,Scotchy-Scotch,66,59
52 | 101,Cody,NA,69
53 | 102,Ryan/Aaron,60,53
54 | 102,,60,63
55 | 103,Mick/Jackson,NA,62
56 | 103,,NA,18
57 | 104,Ross,NA,64
58 | 105,Gavin/Sahil,NA,65
59 | 105,,NA,14
60 | 106,Zach Taylor,NA,66
61 | 107,Jim,NA,67
62 | 108,Marcus,NA,68
63 | 109,Matthius/Casey,NA,54
64 | 109,,NA,10
65 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/fantasy_league_live/standings_chart_component.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyLeagueLive.StandingsChartComponent do
2 | @moduledoc false
3 | use Ex338Web, :live_component
4 |
5 | @impl true
6 | def update(%{standings_chart_data: data}, socket) do
7 | spec =
8 | [title: "Standings History", width: :container, height: :container]
9 | |> VegaLite.new()
10 | |> VegaLite.data_from_values(data, only: ["date", "points", "team_name"])
11 | |> VegaLite.mark(:line)
12 | |> VegaLite.encode_field(:x, "date", type: :temporal)
13 | |> VegaLite.encode_field(:y, "points", type: :quantitative)
14 | |> VegaLite.encode_field(:color, "team_name", type: :nominal, scale: [scheme: "category20"])
15 | # Output the specifcation
16 | |> VegaLite.to_spec()
17 |
18 | socket = assign(socket, id: socket.id)
19 | {:ok, push_event(socket, "vega_lite:#{socket.id}:init", %{"spec" => spec})}
20 | end
21 |
22 | @impl true
23 | def render(assigns) do
24 | ~H"""
25 |
36 | """
37 | end
38 | end
39 |
--------------------------------------------------------------------------------
/test/ex338_web/controllers/waiver_index_controller_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.WaiverIndexControllerTest do
2 | use Ex338Web.ConnCase
3 |
4 | describe "index/2" do
5 | test "lists all waivers in a league", %{conn: conn} do
6 | league = insert(:fantasy_league)
7 | other_league = insert(:fantasy_league)
8 | team = insert(:fantasy_team, team_name: "Brown", fantasy_league: league)
9 | player = insert(:fantasy_player)
10 | player2 = insert(:fantasy_player)
11 |
12 | other_team =
13 | insert(
14 | :fantasy_team,
15 | team_name: "Another Team",
16 | fantasy_league: other_league
17 | )
18 |
19 | insert(:waiver, fantasy_team: team, add_fantasy_player: player, status: "successful")
20 | insert(:waiver, fantasy_team: other_team, add_fantasy_player: player, status: "successful")
21 | insert(:waiver, fantasy_team: team, add_fantasy_player: player2, status: "pending")
22 |
23 | conn = get(conn, ~p"/fantasy_leagues/#{league.id}/waivers")
24 |
25 | assert html_response(conn, 200) =~ ~r/Waivers/
26 | assert String.contains?(conn.resp_body, team.team_name)
27 | assert String.contains?(conn.resp_body, player.player_name)
28 | assert String.contains?(conn.resp_body, player2.player_name)
29 | refute String.contains?(conn.resp_body, other_team.team_name)
30 | end
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/owner_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.OwnerHTML do
2 | use Ex338Web, :html
3 |
4 | def index(assigns) do
5 | ~H"""
6 | <.page_header class="sm:mb-6">
7 | League Owners
8 |
9 |
10 | <.legacy_table>
11 |
12 |
13 | <.legacy_th>
14 | Fantasy Team
15 |
16 | <.legacy_th>
17 | Owner
18 |
19 | <.legacy_th>
20 | Slack Name
21 |
22 |
23 |
24 |
25 | <%= for owner <- @owners do %>
26 |
27 | <.legacy_td class="text-indigo-700">
28 | <.fantasy_team_name_link fantasy_team={owner.fantasy_team} />
29 |
30 | <.legacy_td class="text-indigo-700">
31 | <.link href={~p"/users/#{owner.user.id}"}>
32 | {owner.user.name}
33 |
34 |
35 | <.legacy_td>
36 | <%= if owner.user.slack_name == "" do %>
37 | --
38 | <% else %>
39 | {owner.user.slack_name}
40 | <% end %>
41 |
42 |
43 | <% end %>
44 |
45 |
46 | """
47 | end
48 | end
49 |
--------------------------------------------------------------------------------
/lib/ex338/roster_positions.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.RosterPositions do
2 | @moduledoc false
3 |
4 | alias Ex338.FantasyLeagues.FantasyLeague
5 | alias Ex338.FantasyPlayers
6 | alias Ex338.Repo
7 | alias Ex338.RosterPositions.RosterPosition
8 |
9 | def get_by(clauses) do
10 | Repo.get_by(RosterPosition, clauses)
11 | end
12 |
13 | def list_all do
14 | RosterPosition
15 | |> RosterPosition.order_by_id()
16 | |> Repo.all()
17 | end
18 |
19 | def list_all_active do
20 | RosterPosition
21 | |> RosterPosition.all_active()
22 | |> RosterPosition.order_by_id()
23 | |> Repo.all()
24 | end
25 |
26 | def positions(%FantasyLeague{only_flex?: true, max_flex_spots: max_flex_spots}) do
27 | RosterPosition.flex_positions(max_flex_spots)
28 | end
29 |
30 | def positions(%FantasyLeague{id: fantasy_league_id, max_flex_spots: max_flex_spots}) do
31 | primary_positions = FantasyPlayers.list_sports_abbrevs(fantasy_league_id)
32 | primary_positions ++ RosterPosition.flex_positions(max_flex_spots)
33 | end
34 |
35 | def positions_for_draft(fantasy_league_id, championship_id) do
36 | RosterPosition
37 | |> RosterPosition.all_active()
38 | |> RosterPosition.all_draft_picks()
39 | |> RosterPosition.from_league(fantasy_league_id)
40 | |> RosterPosition.sport_from_champ(championship_id)
41 | |> RosterPosition.preload_assocs()
42 | |> Repo.all()
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/waiver_admin_html.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.WaiverAdminHTML do
2 | use Ex338Web, :html
3 |
4 | def edit(assigns) do
5 | ~H"""
6 | <.two_col_form :let={f} for={@changeset} action={~p"/waiver_admin/#{@waiver}"}>
7 | <:title>
8 | Process Waiver
9 |
10 | <:description>
11 | {@waiver.fantasy_team.team_name}'s current waiver position is {@waiver.fantasy_team.waiver_position}.
12 |
13 |
14 |
15 | Add Fantasy Player:
16 | <%= if @waiver.add_fantasy_player do %>
17 | {@waiver.add_fantasy_player.player_name}
18 | <% else %>
19 | None
20 | <% end %>
21 |
22 |
23 |
24 | Drop Fantasy Player:
25 | <%= if @waiver.drop_fantasy_player do %>
26 | {@waiver.drop_fantasy_player.player_name}
27 | <% else %>
28 | None
29 | <% end %>
30 |
31 |
32 | <.input
33 | field={f[:status]}
34 | label="Select status for the waiver"
35 | type="select"
36 | options={Ex338.Waivers.Waiver.status_options()}
37 | />
38 | <:actions>
39 | <.submit_buttons back_route={~p"/fantasy_leagues/#{@fantasy_league}/waivers"} />
40 |
41 |
42 | """
43 | end
44 | end
45 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/user_session_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserSessionController do
2 | use Ex338Web, :controller
3 |
4 | alias Ex338.Accounts
5 | alias Ex338Web.UserAuth
6 |
7 | def create(conn, %{"_action" => "registered"} = params) do
8 | create(conn, params, "Account created successfully!")
9 | end
10 |
11 | def create(conn, %{"_action" => "password_updated"} = params) do
12 | conn
13 | |> put_session(:user_return_to, ~p"/users/settings")
14 | |> create(params, "Password updated successfully!")
15 | end
16 |
17 | def create(conn, params) do
18 | create(conn, params, "Welcome back!")
19 | end
20 |
21 | defp create(conn, %{"user" => user_params}, info) do
22 | %{"email" => email, "password" => password} = user_params
23 |
24 | if user = Accounts.get_user_by_email_and_password(email, password) do
25 | conn
26 | |> put_flash(:info, info)
27 | |> UserAuth.log_in_user(user, user_params)
28 | else
29 | # In order to prevent user enumeration attacks, don't disclose whether the email is registered.
30 | conn
31 | |> put_flash(:error, "Invalid email or password")
32 | |> put_flash(:email, String.slice(email, 0, 160))
33 | |> redirect(to: ~p"/users/log_in")
34 | end
35 | end
36 |
37 | def delete(conn, _params) do
38 | conn
39 | |> put_flash(:info, "Logged out successfully.")
40 | |> UserAuth.log_out_user()
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/test/ex338_web/controllers/draft_pick_html_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.DraftPickHTMLTest do
2 | use Ex338Web.ConnCase, async: true
3 |
4 | alias Ex338Web.DraftPickHTML
5 |
6 | describe "current_picks/2" do
7 | test "returns last picks if no picks remaining" do
8 | amount = 10
9 | draft_picks = for n <- 1..16, do: %{draft_position: n, fantasy_player_id: n}
10 |
11 | results = DraftPickHTML.current_picks(draft_picks, amount)
12 |
13 | assert Enum.map(results, & &1.draft_position) == Enum.to_list(12..16)
14 | end
15 |
16 | test "returns first picks if no picks completed" do
17 | amount = 5
18 | draft_picks = for n <- 1..16, do: %{draft_position: n, fantasy_player_id: nil}
19 |
20 | results = DraftPickHTML.current_picks(draft_picks, amount)
21 |
22 | assert Enum.map(results, & &1.draft_position) == Enum.to_list(1..5)
23 | end
24 |
25 | test "returns last 5 and next 5 picks when amount 10 is provided" do
26 | amount = 10
27 | completed_draft_picks = for n <- 1..8, do: %{draft_position: n, fantasy_player_id: n}
28 | remaining_draft_picks = for n <- 9..16, do: %{draft_position: n, fantasy_player_id: nil}
29 |
30 | draft_picks = completed_draft_picks ++ remaining_draft_picks
31 |
32 | results = DraftPickHTML.current_picks(draft_picks, amount)
33 |
34 | assert Enum.map(results, & &1.draft_position) == Enum.to_list(4..13)
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_team_authorizer.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyTeamAuthorizer do
2 | @moduledoc """
3 | A module to authorize access to actions for a Fantasy Team
4 | """
5 |
6 | alias Ex338.Accounts.User
7 | alias Ex338.FantasyTeams.FantasyTeam
8 | alias Ex338.InSeasonDraftPicks.InSeasonDraftPick
9 | alias Ex338.Repo
10 |
11 | def authorize(_action, %User{} = %{admin: true}, _schema) do
12 | :ok
13 | end
14 |
15 | def authorize(:edit_team, %User{} = user, %FantasyTeam{} = fantasy_team) do
16 | if owner?(user.id, fantasy_team) do
17 | :ok
18 | else
19 | {:error, :not_authorized}
20 | end
21 | end
22 |
23 | def authorize(
24 | :edit_in_season_draft_pick,
25 | %User{} = user,
26 | %InSeasonDraftPick{} = in_season_draft_pick
27 | ) do
28 | if owner?(user.id, in_season_draft_pick) do
29 | :ok
30 | else
31 | {:error, :not_authorized}
32 | end
33 | end
34 |
35 | defp owner?(user_id, %FantasyTeam{} = fantasy_team) do
36 | fantasy_team = Repo.preload(fantasy_team, :owners)
37 | owners = fantasy_team.owners
38 | Enum.any?(owners, &(&1.user_id == user_id))
39 | end
40 |
41 | defp owner?(user_id, %InSeasonDraftPick{} = draft_pick) do
42 | draft_pick = Repo.preload(draft_pick, draft_pick_asset: [fantasy_team: :owners])
43 | owners = draft_pick.draft_pick_asset.fantasy_team.owners
44 | Enum.any?(owners, &(&1.user_id == user_id))
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/fantasy_teams.csv:
--------------------------------------------------------------------------------
1 | Team Name,Waiver Position,Dues Paid,Winnings Received,Fantasy League Id
2 | Cougars,14,0,0,1
3 | Madd Dogs,10,0,0,1
4 | Sharks,13,0,0,1
5 | Jay Hawks,6,0,0,1
6 | Wild Cats,6,0,0,2
7 | Black Mambas,1,0,0,1
8 | Bengal Tigers,2,0,0,1
9 | Huskies,12,0,0,1
10 | Riveters,3,0,0,1
11 | Junior Mints,10,0,0,2
12 | She-Unit,5,0,0,1
13 | Black Widows,9,0,0,1
14 | Blitz Babes,7,0,0,2
15 | Bare Exposure,9,0,0,2
16 | Pink Cougars,7,0,0,1
17 | End Zone Divas,11,0,0,1
18 | Girl Power,11,0,0,2
19 | Victorious S,4,0,0,1
20 | Air Farce,1,0,0,2
21 | Jagermeister,13,0,0,2
22 | Hoosier Daddys,8,0,0,2
23 | Ocho Stinko,5,0,0,2
24 | No Punt Intended,8,0,0,1
25 | Loose Ends,2,0,0,2
26 | Jerry Seinfelds,4,0,0,2
27 | Deep Threat,3,0,0,2
28 | Pass Up,12,0,0,2
29 | Fumble This,14,0,0,2
30 | Cougars,1,0,0,3
31 | Madd Dogs,2,0,0,3
32 | Sharks,3,0,0,3
33 | Jay Hawks,4,0,0,3
34 | Wild Cats,5,0,0,3
35 | Black Mambas,6,0,0,3
36 | Bengal Tigers,7,0,0,3
37 | Huskies,8,0,0,3
38 | Riveters,9,0,0,3
39 | Junior Mints,10,0,0,3
40 | She-Unit,11,0,0,3
41 | Black Widows,12,0,0,3
42 | Blitz Babes,13,0,0,3
43 | Bare Exposure,14,0,0,3
44 | Pink Cougars,1,0,0,4
45 | End Zone Divas,2,0,0,4
46 | Girl Power,3,0,0,4
47 | Victorious S,4,0,0,4
48 | Air Farce,5,0,0,4
49 | Jagermeister,6,0,0,4
50 | Hoosier Daddys,7,0,0,4
51 | Ocho Stinko,8,0,0,4
52 | No Punt Intended,9,0,0,4
53 | Loose Ends,10,0,0,4
54 | Jerry Seinfelds,11,0,0,4
55 | Deep Threat,12,0,0,4
56 | Pass Up,13,0,0,4
57 | Fumble This,14,0,0,4
58 |
--------------------------------------------------------------------------------
/priv/repo/migrations/20200518235225_remove_coherence_user_columns.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Repo.Migrations.RemoveCoherenceUserColumns do
2 | use Ecto.Migration
3 |
4 | def up do
5 | alter table(:users) do
6 | # rememberable
7 | remove(:remember_created_at)
8 | # recoverable
9 | remove(:reset_password_token)
10 | remove(:reset_password_sent_at)
11 | # lockable
12 | remove(:failed_attempts)
13 | remove(:locked_at)
14 | # trackable
15 | remove(:sign_in_count)
16 | remove(:current_sign_in_at)
17 | remove(:last_sign_in_at)
18 | remove(:current_sign_in_ip)
19 | remove(:last_sign_in_ip)
20 | # unlockable_with_token
21 | remove(:unlock_token)
22 | end
23 | end
24 |
25 | def down do
26 | alter table(:users) do
27 | # rememberable
28 | add(:remember_created_at, :utc_datetime)
29 | # recoverable
30 | add(:reset_password_token, :string)
31 | add(:reset_password_sent_at, :utc_datetime)
32 | # lockable
33 | add(:failed_attempts, :integer, default: 0)
34 | add(:locked_at, :utc_datetime)
35 | # trackable
36 | add(:sign_in_count, :integer, default: 0)
37 | add(:current_sign_in_at, :utc_datetime)
38 | add(:last_sign_in_at, :utc_datetime)
39 | add(:current_sign_in_ip, :string)
40 | add(:last_sign_in_ip, :string)
41 | # unlockable_with_token
42 | add(:unlock_token, :string)
43 | end
44 | end
45 | end
46 |
--------------------------------------------------------------------------------
/lib/ex338/abilities.ex:
--------------------------------------------------------------------------------
1 | defimpl Canada.Can, for: Ex338.Accounts.User do
2 | alias Ex338.Accounts.User
3 | alias Ex338.DraftPicks.DraftPick
4 | alias Ex338.FantasyTeams.FantasyTeam
5 | alias Ex338.InSeasonDraftPicks.InSeasonDraftPick
6 | alias Ex338.Waivers.Waiver
7 |
8 | def can?(%User{admin: true}, _, _), do: true
9 |
10 | def can?(%User{id: user_id}, action, %User{id: user_id}) when action in [:edit, :update] do
11 | true
12 | end
13 |
14 | def can?(%User{}, action, %User{}) when action in [:edit, :update] do
15 | false
16 | end
17 |
18 | def can?(%User{}, action, nil) when action in [:edit, :update] do
19 | false
20 | end
21 |
22 | def can?(%User{id: user_id}, action, model) when action in [:edit, :update, :create, :new] do
23 | owner?(user_id, model)
24 | end
25 |
26 | def can?(%User{id: _}, _, _), do: false
27 |
28 | defp owner?(user_id, %DraftPick{} = draft_pick) do
29 | owners = draft_pick.fantasy_team.owners
30 | Enum.any?(owners, &(&1.user_id == user_id))
31 | end
32 |
33 | defp owner?(user_id, %FantasyTeam{owners: owners}) do
34 | Enum.any?(owners, &(&1.user_id == user_id))
35 | end
36 |
37 | defp owner?(user_id, %Waiver{} = waiver) do
38 | owners = waiver.fantasy_team.owners
39 | Enum.any?(owners, &(&1.user_id == user_id))
40 | end
41 |
42 | defp owner?(user_id, %InSeasonDraftPick{} = draft_pick) do
43 | owners = draft_pick.draft_pick_asset.fantasy_team.owners
44 | Enum.any?(owners, &(&1.user_id == user_id))
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/user_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserController do
2 | use Ex338Web, :controller
3 |
4 | import Canary.Plugs
5 |
6 | alias Ex338.Accounts
7 | alias Ex338.Accounts.User
8 | alias Ex338Web.Authorization
9 |
10 | plug(
11 | :load_and_authorize_resource,
12 | model: User,
13 | only: [:edit, :update],
14 | preload: [owners: :fantasy_team],
15 | unauthorized_handler: {Authorization, :handle_unauthorized}
16 | )
17 |
18 | def edit(conn, %{"id" => _id}) do
19 | user = conn.assigns.user
20 |
21 | render(
22 | conn,
23 | :edit,
24 | changeset: User.user_changeset(user),
25 | page_title: "338 Challenge",
26 | user: user
27 | )
28 | end
29 |
30 | def show(conn, %{"id" => id}) do
31 | user = Accounts.get_user!(id)
32 |
33 | render(
34 | conn,
35 | :show,
36 | user: user,
37 | page_title: "338 Challenge"
38 | )
39 | end
40 |
41 | def update(conn, %{"id" => id, "user" => params}) do
42 | user = Accounts.get_user!(id)
43 |
44 | case Accounts.update_user(user, params) do
45 | {:ok, user} ->
46 | conn
47 | |> put_flash(:info, "User info updated successfully.")
48 | |> redirect(to: ~p"/users/#{user}")
49 |
50 | {:error, changeset} ->
51 | render(
52 | conn,
53 | :edit,
54 | user: user,
55 | page_title: "338 Challenge",
56 | changeset: changeset
57 | )
58 | end
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/lib/ex338_web/notifiers/commish_notifier.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.CommishNotifier do
2 | @moduledoc false
3 |
4 | alias Ex338.Accounts.User
5 | alias Ex338.FantasyTeams
6 | alias Ex338.Repo
7 | alias Ex338Web.Mailer
8 | alias Ex338Web.NotifierTemplate
9 |
10 | def send_email_to_leagues(leagues, subject, message) do
11 | owners = FantasyTeams.get_leagues_email_addresses(leagues)
12 | admins = Repo.all(User.admin_emails())
13 | recipients = unique_recipients(owners, admins)
14 | default_admin = Mailer.default_from_name_and_email()
15 |
16 | email_info = %{
17 | cc: default_admin,
18 | from: default_admin,
19 | subject: subject,
20 | message: message
21 | }
22 |
23 | recipients
24 | |> batch_by_aws_max_recipients()
25 | |> Enum.map(fn recipients ->
26 | email_info
27 | |> Map.put(:bcc, recipients)
28 | |> NotifierTemplate.plain_text()
29 | |> Mailer.deliver()
30 | |> Mailer.handle_delivery()
31 | end)
32 | |> parse_results()
33 | end
34 |
35 | def unique_recipients(owners, admins) do
36 | Enum.uniq(owners ++ admins)
37 | end
38 |
39 | defp batch_by_aws_max_recipients(recipients) do
40 | Enum.chunk_every(recipients, 40)
41 | end
42 |
43 | defp parse_results(results) do
44 | Enum.reduce_while(results, {:ok, "commish emails sent"}, &parse_result/2)
45 | end
46 |
47 | defp parse_result({:ok, _result}, success_message) do
48 | {:cont, success_message}
49 | end
50 |
51 | defp parse_result({:error, _message} = error, _success_message) do
52 | {:halt, error}
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_teams/owner.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyTeams.Owner do
2 | @moduledoc false
3 | use Ecto.Schema
4 |
5 | import Ecto.Changeset
6 | import Ecto.Query, warn: false
7 |
8 | schema "owners" do
9 | belongs_to(:fantasy_team, Ex338.FantasyTeams.FantasyTeam)
10 | belongs_to(:user, Ex338.Accounts.User)
11 | field(:rules, OwnerRulesEnum, default: "unaccepted")
12 |
13 | timestamps()
14 | end
15 |
16 | @doc """
17 | Builds a changeset based on the `struct` and `params`.
18 | """
19 | def changeset(owner, params \\ %{}) do
20 | owner
21 | |> cast(params, [:fantasy_team_id, :user_id, :rules])
22 | |> validate_required([:fantasy_team_id, :user_id])
23 | |> foreign_key_constraint(:user_id)
24 | |> foreign_key_constraint(:fantasy_team_id)
25 | end
26 |
27 | def by_league(query, league_id) do
28 | from(
29 | o in query,
30 | join: f in assoc(o, :fantasy_team),
31 | where: f.fantasy_league_id == ^league_id,
32 | order_by: [asc: f.team_name]
33 | )
34 | end
35 |
36 | def by_team(query, team_id) do
37 | from(
38 | o in query,
39 | where: o.fantasy_team_id == ^team_id
40 | )
41 | end
42 |
43 | def email_recipients_for_league(query, league_id) do
44 | query
45 | |> by_league(league_id)
46 | |> join(:inner, [o], u in assoc(o, :user))
47 | |> select([o, f, u], {u.name, u.email})
48 | end
49 |
50 | def email_recipients_for_team(query, team_id) do
51 | query
52 | |> by_team(team_id)
53 | |> join(:inner, [o], u in assoc(o, :user))
54 | |> select([o, u], {u.name, u.email})
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/priv/repo/annual_league_setup/data/fantasy_teams.csv:
--------------------------------------------------------------------------------
1 | Projected ID,Fantasy Team Name,Waiver Position,Winnings Adj,Dues Paid,Winnings Received,Commish Notes,Fantasy League Id
2 | 68,Brown,1,0,0,0,,8
3 | 69,The Consortium,1,0,0,0,"PayPal Mara, swin cash",8
4 | 70,Kintz/Mori,1,0,0,0,"split 50/50, paypal",9
5 | 71,Axel,1,0,0,0,"split payments 75% to Nana, 25% to Ax, paypal",8
6 | 72,BD,1,0,0,0,,9
7 | 73,Bartch,1,0,0,0,venmo,8
8 | 74,Wilson,1,0,0,0,venmo to Tim,8
9 | 75,Miller/Wernz,1,0,0,0,paypal,8
10 | 76,FloPow,1,0,0,0,paypal,8
11 | 77,G-Brew,1,0,0,0,paypal,9
12 | 78,Davie/Paul,1,0,0,0,,8
13 | 79,Bro/Heli,1,0,0,0,,10
14 | 80,Jake,1,0,0,0,,9
15 | 81,Zach,1,0,0,0,Venmo,9
16 | 82,Vondy/Clark,1,0,0,0,paypal to Vondy,8
17 | 83,Adam/Mike,1,0,0,0,venmo adam,9
18 | 84,Evan,1,0,0,0,venmo,8
19 | 85,Hughes,1,0,0,0,pay via Venmo,9
20 | 86,Sister-Sister,1,0,0,0,paypal,9
21 | 87,Murphy's Law,1,0,0,0,Paypal,9
22 | 88,Tim,1,0,0,0,PayPal,8
23 | 89,Mike,1,0,0,0,paypal BSH,9
24 | 90,Riss/Ehrman,1,0,0,0,,9
25 | 91,Bailey,1,0,0,0,paypal,8
26 | 92,Nelson,1,0,0,0,Venmo,10
27 | 93,Teeter,1,0,0,0,paypal,10
28 | 94,Mick/Jason,1,0,0,0,paypal to Mick,9
29 | 95,Tom,1,0,0,0,PayPal,8
30 | 96,Danger 338,1,0,0,0,venmo,9
31 | 97,THE LIFE OF JASO,1,0,0,0,paypal to thecaseymclain@gmail.com,9
32 | 98,Tom/Josh,1,0,0,0,PayPal to Tom,9
33 | 99,Christian/Zach,1,0,0,0,PayPal Zach,10
34 | 100,Scotchy-Scotch,1,0,0,0,paypal,9
35 | 101,Cody,1,0,0,0,,10
36 | 102,Ryan/Aaron,1,0,0,0,,10
37 | 103,Mick/Jackson,1,0,0,0,,10
38 | 104,Ross,1,0,0,0,,10
39 | 105,Gavin/Sahil,1,0,0,0,,10
40 | 106,Zach Taylor,1,0,0,0,,10
41 | 107,Jim,1,0,0,0,,10
42 | 108,Marcus,1,0,0,0,,10
43 | 109,Matthius/Casey,1,0,0,0,,10
44 |
--------------------------------------------------------------------------------
/test/ex338/validate_helpers_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.ValidateHelpersTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.ValidateHelpers
5 |
6 | describe "slot_available/2" do
7 | test "returns false if too many flex spots in use" do
8 | max_flex_slots = 3
9 | tm = insert(:fantasy_team)
10 | regular_slots = insert_list(4, :roster_position, fantasy_team: tm)
11 |
12 | flex_sport = List.first(regular_slots).fantasy_player.sports_league
13 |
14 | plyrs = insert_list(4, :fantasy_player, sports_league: flex_sport)
15 |
16 | flex_slots =
17 | for plyr <- plyrs do
18 | build(:roster_position, fantasy_team: tm, fantasy_player: plyr)
19 | end
20 |
21 | all_slots = regular_slots ++ flex_slots
22 |
23 | result = ValidateHelpers.slot_available?(all_slots, max_flex_slots)
24 |
25 | assert result == false
26 | end
27 |
28 | test "returns true if flex slot is available" do
29 | max_flex_slots = 3
30 | tm = insert(:fantasy_team)
31 | regular_slots = insert_list(4, :roster_position, fantasy_team: tm)
32 |
33 | flex_sport = List.first(regular_slots).fantasy_player.sports_league
34 |
35 | plyrs = insert_list(3, :fantasy_player, sports_league: flex_sport)
36 |
37 | flex_slots =
38 | for plyr <- plyrs do
39 | build(:roster_position, fantasy_team: tm, fantasy_player: plyr)
40 | end
41 |
42 | all_slots = regular_slots ++ flex_slots
43 |
44 | result = ValidateHelpers.slot_available?(all_slots, max_flex_slots)
45 |
46 | assert result == true
47 | end
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/ex338/trades/trade_vote_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Trades.TradeVoteTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.Trades.TradeVote
5 |
6 | describe "changeset/2" do
7 | @valid_attrs %{
8 | trade_id: 1,
9 | fantasy_team_id: 2,
10 | user_id: 3,
11 | approve: false
12 | }
13 | test "valid with required attributes" do
14 | changeset = TradeVote.changeset(%TradeVote{}, @valid_attrs)
15 | assert changeset.valid?
16 | end
17 |
18 | @invalid_attrs %{}
19 | test "invalid without required attributes" do
20 | changeset = TradeVote.changeset(%TradeVote{}, @invalid_attrs)
21 | refute changeset.valid?
22 | end
23 |
24 | test "invalid if team already voted" do
25 | trade = insert(:trade)
26 | team = insert(:fantasy_team)
27 | user = insert(:user)
28 | other_user = insert(:user)
29 | insert(:trade_vote, user: user, trade: trade, fantasy_team: team)
30 |
31 | attrs = %{
32 | trade_id: trade.id,
33 | fantasy_team_id: team.id,
34 | user_id: other_user.id,
35 | approve: false
36 | }
37 |
38 | changeset = TradeVote.changeset(%TradeVote{}, attrs)
39 | {:error, changeset} = Repo.insert(changeset)
40 |
41 | refute changeset.valid?
42 |
43 | assert changeset.errors == [
44 | trade:
45 | {"Team has already voted",
46 | [
47 | constraint: :unique,
48 | constraint_name: "trade_votes_trade_id_fantasy_team_id_index"
49 | ]}
50 | ]
51 | end
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/ex338/championships/championship_slot.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Championships.ChampionshipSlot do
2 | @moduledoc false
3 |
4 | use Ecto.Schema
5 |
6 | import Ecto.Changeset
7 | import Ecto.Query, warn: false
8 |
9 | alias Ex338.Championships.ChampionshipResult
10 |
11 | schema "championship_slots" do
12 | field(:slot, :integer)
13 | belongs_to(:roster_position, Ex338.RosterPositions.RosterPosition)
14 | belongs_to(:championship, Ex338.Championships.Championship)
15 |
16 | timestamps()
17 | end
18 |
19 | @doc """
20 | Builds a changeset based on the `struct` and `params`.
21 | """
22 | def changeset(slot_struct, params \\ %{}) do
23 | slot_struct
24 | |> cast(params, [:slot, :roster_position_id, :championship_id])
25 | |> validate_required([:slot])
26 | end
27 |
28 | def preload_assocs_by_league(query, league_id) do
29 | from(
30 | s in query,
31 | join: r in assoc(s, :roster_position),
32 | join: f in assoc(r, :fantasy_team),
33 | join: p in assoc(r, :fantasy_player),
34 | left_join: cr in ChampionshipResult,
35 | on: cr.fantasy_player_id == p.id and s.championship_id == cr.championship_id,
36 | join: c in assoc(s, :championship),
37 | where: f.fantasy_league_id == ^league_id,
38 | where: r.active_at < c.championship_at,
39 | where: r.released_at > c.championship_at or is_nil(r.released_at),
40 | order_by: [f.team_name, s.slot],
41 | preload: [roster_position: :fantasy_team],
42 | preload: [roster_position: {r, fantasy_player: {p, championship_results: cr}}],
43 | preload: [:championship]
44 | )
45 | end
46 | end
47 |
--------------------------------------------------------------------------------
/lib/ex338/chats.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Chats do
2 | @moduledoc """
3 | The Chats context.
4 | """
5 |
6 | import Ecto.Query
7 |
8 | alias Ex338.Accounts.User
9 | alias Ex338.Chats.Chat
10 | alias Ex338.Chats.Message
11 | alias Ex338.Repo
12 |
13 | def subscribe(%Chat{} = chat, %User{}) do
14 | Phoenix.PubSub.subscribe(Ex338.PubSub, topic(chat))
15 | end
16 |
17 | def subscribe(_chat, nil), do: nil
18 |
19 | def create_chat(chat_params) do
20 | %Chat{}
21 | |> Chat.changeset(chat_params)
22 | |> Repo.insert()
23 | end
24 |
25 | def create_message(message_params) do
26 | %Message{}
27 | |> Message.changeset(message_params)
28 | |> Repo.insert()
29 | |> case do
30 | {:ok, message} ->
31 | message = Repo.preload(message, user: [owners: :fantasy_team])
32 | broadcast(message, %Ex338.Events.MessageCreated{message: message})
33 | {:ok, message}
34 |
35 | other ->
36 | other
37 | end
38 | end
39 |
40 | def change_message(%Message{} = message, attrs \\ %{}) do
41 | Message.changeset(message, attrs)
42 | end
43 |
44 | def list_chats do
45 | Repo.all(Chat)
46 | end
47 |
48 | def get_chat(chat_id) do
49 | query =
50 | from c in Chat,
51 | where: c.id == ^chat_id,
52 | preload: [messages: :user]
53 |
54 | Repo.one(query)
55 | end
56 |
57 | defp broadcast(%Message{} = message, event) do
58 | Phoenix.PubSub.broadcast(Ex338.PubSub, topic(message), {__MODULE__, event})
59 | end
60 |
61 | def topic(%Message{} = message), do: "chat:#{message.chat_id}"
62 | def topic(%Chat{} = chat), do: "chat:#{chat.id}"
63 | end
64 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # The directory Mix will write compiled artifacts to.
2 | /_build/
3 |
4 | # If you run "mix test --cover", coverage assets end up here.
5 | /cover/
6 |
7 | # The directory Mix downloads your dependencies sources to.
8 | /deps/
9 |
10 | # Where 3rd-party dependencies like ExDoc output generated docs.
11 | /doc/
12 |
13 | # Ignore .fetch files in case you like to edit your project deps locally.
14 | /.fetch
15 |
16 | # If the VM crashes, it generates a dump, let's ignore it too.
17 | erl_crash.dump
18 |
19 | # Also ignore archive artifacts (built via "mix archive.build").
20 | *.ez
21 |
22 | # Temporary files, for example, from tests.
23 | /tmp/
24 |
25 | # Ignore package tarball (built via "mix hex.build").
26 | ex338-*.tar
27 |
28 | # Ignore assets that are produced by build tools.
29 | /priv/static/assets/
30 |
31 | # Ignore digested assets cache.
32 | /priv/static/cache_manifest.json
33 |
34 | # In case you use Node.js/npm, you want to ignore these.
35 | npm-debug.log
36 | /assets/node_modules/
37 |
38 | # App artifacts
39 | /db
40 |
41 | # Since we are building assets from assets/,
42 | # we ignore priv/static. You may want to comment
43 | # this depending on your deployment strategy.
44 | # /priv/static/
45 |
46 | # The config/prod.secret.exs file by default contains sensitive
47 | # data and you should not commit it into version control.
48 | #
49 | # Alternatively, you may comment the line below and commit the
50 | # secrets file as long as you replace its contents by environment
51 | # variables.
52 | /config/prod.secret.exs
53 |
54 | .DS_Store
55 |
56 | tags
57 |
58 | dump.rdb
59 |
60 | .projections.json
61 |
62 | logfile
63 |
64 | .lexical
65 |
--------------------------------------------------------------------------------
/lib/ex338/uploader.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Uploader do
2 | @moduledoc false
3 |
4 | alias Ecto.Multi
5 | alias Ex338.Repo
6 |
7 | @table_options [
8 | "Championships.Championship",
9 | "Championships.ChampionshipResult",
10 | "DraftPicks.DraftPick",
11 | "FantasyLeagues.FantasyLeague",
12 | "FantasyPlayers.FantasyPlayer",
13 | "FantasyTeams.FantasyTeam",
14 | "InSeasonDraftPicks.InSeasonDraftPick",
15 | "FantasyLeagues.LeagueSport",
16 | "FantasyTeams.Owner",
17 | "RosterPositions.RosterPosition",
18 | "FantasyPlayers.SportsLeague"
19 | ]
20 |
21 | def build_inserts_from_rows(rows, table) do
22 | module = Module.safe_concat([Ex338, table])
23 | {multi, _num_rows} = Enum.reduce(rows, {Multi.new(), 1}, &build_insert(&1, &2, module))
24 | multi
25 | end
26 |
27 | def insert_from_csv(file_path, table) do
28 | file_path
29 | |> File.stream!()
30 | |> format_text()
31 | |> CSV.decode!(headers: true)
32 | |> build_inserts_from_rows(table)
33 | |> Repo.transaction()
34 | end
35 |
36 | def table_options, do: @table_options
37 |
38 | ## Helpers
39 |
40 | ## build_inserts_from_rows
41 |
42 | defp build_insert(row, {multi, num}, module) do
43 | changeset =
44 | module
45 | |> struct()
46 | |> module.changeset(row)
47 |
48 | multi = Multi.insert(multi, {module, num}, changeset)
49 |
50 | {multi, num + 1}
51 | end
52 |
53 | ## insert_from_csv
54 |
55 | defp format_text(file_stream) do
56 | file_stream
57 | |> Stream.map(&String.replace(&1, "FALSE", "false"))
58 | |> Stream.map(&String.replace(&1, "TRUE", "true"))
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/lib/ex338/application.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Application do
2 | @moduledoc false
3 |
4 | use Application
5 |
6 | # See http://elixir-lang.org/docs/stable/elixir/Application.html
7 | # for more information on OTP Applications
8 | def start(_type, _args) do
9 | pubsub_options =
10 | case Mix.env() do
11 | :test ->
12 | [name: Ex338.PubSub]
13 |
14 | _ ->
15 | [
16 | name: Ex338.PubSub,
17 | adapter: Phoenix.PubSub.Redis,
18 | url: System.get_env("REDIS_URL") || "redis://localhost:6379",
19 | node_name: System.get_env("NODE") || "name"
20 | ]
21 | end
22 |
23 | # Define workers and child supervisors to be supervised
24 | children = [
25 | # Start the Ecto repository
26 | Ex338.Repo,
27 | {Phoenix.PubSub, pubsub_options},
28 | Ex338Web.Telemetry,
29 | Ex338Web.Presence,
30 | # Start the endpoint when the application starts
31 | Ex338Web.Endpoint,
32 | {Oban, Application.fetch_env!(:ex338, Oban)}
33 | # Start a worker by calling: HelloFly.Worker.start_link(arg)
34 | # {HelloFly.Worker, arg}
35 | ]
36 |
37 | # See http://elixir-lang.org/docs/stable/elixir/Supervisor.html
38 | # for other strategies and supported options
39 | opts = [strategy: :one_for_one, name: Ex338.Supervisor]
40 | Supervisor.start_link(children, opts)
41 | end
42 |
43 | # Tell Phoenix to update the endpoint configuration
44 | # whenever the application is updated.
45 | def config_change(changed, _new, removed) do
46 | Ex338Web.Endpoint.config_change(changed, removed)
47 | :ok
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/ex338/fantasy_players/sports_league.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.FantasyPlayers.SportsLeague do
2 | @moduledoc false
3 |
4 | use Ecto.Schema
5 |
6 | import Ecto.Changeset
7 | import Ecto.Query, warn: false
8 |
9 | alias Ex338.Championships.Championship
10 |
11 | schema "sports_leagues" do
12 | field(:league_name, :string)
13 | field(:abbrev, :string)
14 | field(:hide_waivers, :boolean)
15 | has_many(:fantasy_players, Ex338.FantasyPlayers.FantasyPlayer)
16 | has_many(:championships, Ex338.Championships.Championship)
17 | has_many(:league_sports, Ex338.FantasyLeagues.LeagueSport)
18 |
19 | timestamps()
20 | end
21 |
22 | def abbrev_a_to_z(query) do
23 | from(s in query, order_by: s.abbrev)
24 | end
25 |
26 | def alphabetical(query) do
27 | from(s in query, order_by: s.league_name)
28 | end
29 |
30 | def changeset(struct, params \\ %{}) do
31 | struct
32 | |> cast(params, [:league_name, :abbrev, :hide_waivers])
33 | |> validate_required([:league_name, :abbrev])
34 | end
35 |
36 | def for_league(query, fantasy_league_id) do
37 | from(
38 | s in query,
39 | inner_join: ls in assoc(s, :league_sports),
40 | where: ls.fantasy_league_id == ^fantasy_league_id
41 | )
42 | end
43 |
44 | def preload_league_overall_championships(query, fantasy_league_id) do
45 | championships =
46 | Championship
47 | |> Championship.overall_championships()
48 | |> Championship.all_for_league(fantasy_league_id)
49 |
50 | from(s in query, preload: [championships: ^championships])
51 | end
52 |
53 | def select_abbrev(query) do
54 | from(s in query, select: s.abbrev)
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/ex338/icalendar.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.ICalendar do
2 | @moduledoc false
3 |
4 | defmodule Event do
5 | @moduledoc false
6 | defstruct summary: nil,
7 | dtstart: nil,
8 | dtend: nil,
9 | dtstamp: nil,
10 | description: nil,
11 | uid: nil
12 | end
13 |
14 | def to_ics(events) when is_list(events) do
15 | events = Enum.map(events, &to_ics/1)
16 |
17 | calendar =
18 | """
19 | BEGIN:VCALENDAR
20 | CALSCALE:GREGORIAN
21 | VERSION:2.0
22 | PRODID:-//Ex338 ICalendar//EN
23 | #{events}END:VCALENDAR
24 | """
25 |
26 | String.replace(calendar, "\n", "\r\n")
27 | end
28 |
29 | def to_ics(event) do
30 | contents = to_kvs(event)
31 |
32 | """
33 | BEGIN:VEVENT
34 | #{contents}END:VEVENT
35 | """
36 | end
37 |
38 | defp to_kvs(event) do
39 | event
40 | |> Map.from_struct()
41 | |> Enum.map(&to_kv/1)
42 | |> List.flatten()
43 | |> Enum.sort()
44 | |> Enum.join()
45 | end
46 |
47 | defp to_kv({key, value}) do
48 | name =
49 | key
50 | |> to_string()
51 | |> String.upcase()
52 |
53 | build(name, value)
54 | end
55 |
56 | def build(_key, nil) do
57 | ""
58 | end
59 |
60 | def build(key, %Date{} = date) do
61 | "#{key}:#{Date.to_iso8601(date, :basic)}\n"
62 | end
63 |
64 | def build(key, %DateTime{} = datetime) do
65 | datetime =
66 | datetime
67 | |> DateTime.truncate(:second)
68 | |> DateTime.to_iso8601(:basic)
69 |
70 | "#{key}:#{datetime}\n"
71 | end
72 |
73 | def build(key, value) do
74 | "#{key}:#{value}\n"
75 | end
76 | end
77 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/user_invitation_live.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserInvitationLive do
2 | @moduledoc false
3 | use Ex338Web, :live_view
4 |
5 | alias Ex338.Accounts
6 |
7 | def render(assigns) do
8 | ~H"""
9 | <.padded_container>
10 |
11 | <.header class="text-center">
12 | Invite a new user
13 | <:subtitle>Email a registration link to a new user.
14 |
15 |
16 | <.simple_form for={@form} id="invitation_form" phx-submit="send_email" class="bg-gray-200!">
17 | <.input field={@form[:email]} type="email" placeholder="Email" required />
18 | <:actions>
19 | <.button phx-disable-with="Sending..." class="w-full">
20 | Send registration link
21 |
22 |
23 |
24 |
25 | <.link
26 | href={~p"/"}
27 | class="text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-hidden focus:underline transition ease-in-out duration-150"
28 | >
29 | Back
30 |
31 |
32 |
33 |
34 | """
35 | end
36 |
37 | def mount(_params, _session, socket) do
38 | {:ok, assign(socket, form: to_form(%{}, as: "user"))}
39 | end
40 |
41 | def handle_event("send_email", %{"user" => %{"email" => email}}, socket) do
42 | Accounts.deliver_registration_link(email, url(~p"/users/register"))
43 |
44 | info =
45 | "Invitation sent to #{email}"
46 |
47 | {:noreply, put_flash(socket, :info, info)}
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/ex338/roster_positions/open_position_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.RosterPositions.OpenPositionTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.RosterPositions.OpenPosition
5 |
6 | @league_positions [
7 | "CL",
8 | "CBB",
9 | "CFB",
10 | "CHK",
11 | "EPL",
12 | "KD",
13 | "LLWS",
14 | "MTn",
15 | "MLB",
16 | "NBA",
17 | "NFL",
18 | "NHL",
19 | "PGA",
20 | "WTn",
21 | "Flex1",
22 | "Flex2",
23 | "Flex3",
24 | "Flex4",
25 | "Flex5",
26 | "Flex6"
27 | ]
28 |
29 | describe "add_open_positions_to_teams/1" do
30 | test "adds position for any position without a player in a collection" do
31 | team_a = %{roster_positions: [%{position: "Unassigned", fantasy_player: %{}}]}
32 | team_b = %{roster_positions: [%{position: "CFB", fantasy_player: %{}}]}
33 | teams = [team_a, team_b]
34 |
35 | [a, b] = OpenPosition.add_open_positions_to_teams(teams, @league_positions)
36 |
37 | assert Enum.count(a.roster_positions) == 21
38 | assert Enum.count(b.roster_positions) == 20
39 | end
40 | end
41 |
42 | describe "add_open_positions_to_team/1" do
43 | test "adds position for any position without a player for a team" do
44 | team_a = %{
45 | roster_positions: [
46 | %{position: "Unassigned", fantasy_player: %{}},
47 | %{position: "Unassigned", fantasy_player: %{}},
48 | %{position: "CFB", fantasy_player: %{}}
49 | ]
50 | }
51 |
52 | result = OpenPosition.add_open_positions_to_team(team_a, @league_positions)
53 |
54 | assert Enum.count(result.roster_positions) == 22
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/ex338_web/controllers/waiver_admin_controller.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.WaiverAdminController do
2 | use Ex338Web, :controller
3 |
4 | import Canary.Plugs
5 |
6 | alias Ex338.Authorization
7 | alias Ex338.FantasyLeagues
8 | alias Ex338.Waivers
9 | alias Ex338.Waivers.Waiver
10 |
11 | plug(
12 | :load_and_authorize_resource,
13 | model: Waiver,
14 | only: [:edit, :update],
15 | preload: [:fantasy_team, :add_fantasy_player, :drop_fantasy_player],
16 | unauthorized_handler: {Authorization, :handle_unauthorized}
17 | )
18 |
19 | def edit(conn, _) do
20 | waiver = conn.assigns.waiver
21 | league_id = conn.assigns.waiver.fantasy_team.fantasy_league_id
22 | changeset = Waiver.changeset(waiver)
23 |
24 | render(
25 | conn,
26 | :edit,
27 | waiver: waiver,
28 | changeset: changeset,
29 | fantasy_league: FantasyLeagues.get(league_id)
30 | )
31 | end
32 |
33 | def update(conn, %{"id" => _, "waiver" => params}) do
34 | waiver = conn.assigns.waiver
35 |
36 | result = Waivers.process_waiver(waiver, params)
37 |
38 | case result do
39 | {:ok, %{waiver: _waiver}} ->
40 | conn
41 | |> put_flash(:info, "Waiver successfully processed")
42 | |> redirect(to: ~p"/fantasy_leagues/#{waiver.fantasy_team.fantasy_league_id}/waivers")
43 |
44 | {:error, _, changeset, _} ->
45 | league_id = conn.assigns.waiver.fantasy_team.fantasy_league_id
46 |
47 | render(
48 | conn,
49 | :edit,
50 | waiver: waiver,
51 | changeset: changeset,
52 | fantasy_league: FantasyLeagues.get(league_id)
53 | )
54 | end
55 | end
56 | end
57 |
--------------------------------------------------------------------------------
/lib/ex338/roster_positions/admin.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.RosterPositions.Admin do
2 | @moduledoc false
3 |
4 | def primary_positions(roster_positions) do
5 | Enum.reject(roster_positions, fn roster_position ->
6 | flex_position?(roster_position.position) || unassigned_position?(roster_position.position)
7 | end)
8 | end
9 |
10 | def flex_and_unassigned_positions(roster_positions) do
11 | unassigned = unassigned_positions(roster_positions)
12 | flex = flex_positions(roster_positions)
13 | flex ++ unassigned
14 | end
15 |
16 | defp flex_positions(roster_positions) do
17 | Enum.filter(roster_positions, &flex_position?(&1.position))
18 | end
19 |
20 | defp flex_position?(position) do
21 | Regex.match?(~r/Flex/, position)
22 | end
23 |
24 | defp unassigned_positions(roster_positions) do
25 | Enum.filter(roster_positions, &unassigned_position?(&1.position))
26 | end
27 |
28 | def unassigned_position?(position) do
29 | Regex.match?(~r/Unassigned/, position)
30 | end
31 |
32 | def order_by_position(%{roster_positions: positions} = fantasy_teams) do
33 | positions
34 | |> sort_by_position()
35 | |> sort_primary_positions_first()
36 | |> update_fantasy_team(fantasy_teams)
37 | end
38 |
39 | defp sort_by_position(positions) do
40 | Enum.sort(positions, &(&1.position <= &2.position))
41 | end
42 |
43 | defp sort_primary_positions_first(positions) do
44 | primary = primary_positions(positions)
45 | flex_and_unassigned = flex_and_unassigned_positions(positions)
46 |
47 | primary ++ flex_and_unassigned
48 | end
49 |
50 | def update_fantasy_team(positions, fantasy_team) do
51 | Map.put(fantasy_team, :roster_positions, positions)
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/ex338/waivers/batch_process.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Waiver.Batch do
2 | @moduledoc false
3 |
4 | def group_and_sort(waivers) do
5 | waivers
6 | |> group_by_league()
7 | |> Enum.map(&group_by_add_player/1)
8 | |> flatten_league_waivers()
9 | |> sort_by_process_at()
10 | |> Enum.map(&sort_by_waiver_position/1)
11 | end
12 |
13 | def group_by_league(waivers) do
14 | waivers
15 | |> Enum.group_by(& &1.fantasy_team.fantasy_league_id)
16 | |> convert_group_to_list()
17 | end
18 |
19 | def group_by_add_player(waivers) do
20 | waivers
21 | |> Enum.group_by(& &1.add_fantasy_player_id)
22 | |> convert_group_to_list()
23 | end
24 |
25 | def sort_by_waiver_position(waivers) do
26 | Enum.sort(waivers, &compare_waiver_positions/2)
27 | end
28 |
29 | def sort_by_process_at(waivers) do
30 | Enum.sort(waivers, &compare_process_at/2)
31 | end
32 |
33 | ## Helpers
34 |
35 | ## Implementations
36 |
37 | def convert_group_to_list(map) do
38 | Enum.map(map, fn {_key, value} -> value end)
39 | end
40 |
41 | ## batch
42 |
43 | defp flatten_league_waivers(waivers) do
44 | Enum.reduce(waivers, [], fn waivers, acc -> acc ++ waivers end)
45 | end
46 |
47 | ## sort_by_waiver_position
48 | defp compare_waiver_positions(_waiver, []), do: true
49 |
50 | defp compare_waiver_positions(waiver1, waiver2) do
51 | waiver1.fantasy_team.waiver_position <= waiver2.fantasy_team.waiver_position
52 | end
53 |
54 | ## sort_by_process_at
55 |
56 | defp compare_process_at([waiver1 | _], [waiver2 | _]) do
57 | case DateTime.compare(waiver1.process_at, waiver2.process_at) do
58 | :gt -> false
59 | :eq -> false
60 | :lt -> true
61 | end
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/user_login_live.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserLoginLive do
2 | @moduledoc false
3 | use Ex338Web, :live_view
4 |
5 | def render(assigns) do
6 | ~H"""
7 | <.padded_container>
8 |
9 | <.header class="text-center">
10 | Sign in to The 338 Challenge
11 | <:subtitle>
12 |
13 |
14 | <.simple_form
15 | for={@form}
16 | id="login_form"
17 | action={~p"/users/log_in"}
18 | phx-update="ignore"
19 | class="bg-gray-200! max-w-md m-auto"
20 | >
21 | <.input field={@form[:email]} type="email" label="Email" required />
22 | <.input field={@form[:password]} type="password" label="Password" required />
23 |
24 | <:actions>
25 | <.input field={@form[:remember_me]} type="checkbox" label="Keep me logged in" />
26 | <.link
27 | href={~p"/users/reset_password"}
28 | class="text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-hidden focus:underline transition ease-in-out duration-150"
29 | >
30 | Forgot your password?
31 |
32 |
33 | <:actions>
34 | <.button phx-disable-with="Signing in..." class="w-full">
35 | Sign in →
36 |
37 |
38 |
39 |
40 |
41 | """
42 | end
43 |
44 | def mount(_params, _session, socket) do
45 | email = Phoenix.Flash.get(socket.assigns.flash, :email)
46 | form = to_form(%{"email" => email}, as: "user")
47 | {:ok, assign(socket, form: form), temporary_assigns: [form: form]}
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/test/ex338/trades/admin_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Trades.AdminTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ecto.Multi
5 | alias Ex338.DraftPicks
6 | alias Ex338.RosterPositions.RosterPosition
7 | alias Ex338.Trades.Admin
8 | alias Ex338.Trades.Trade
9 | alias Ex338.Trades.TradeLineItem
10 |
11 | @trade %Trade{
12 | status: "Pending",
13 | trade_line_items: [
14 | %TradeLineItem{
15 | losing_team_id: 1,
16 | fantasy_player_id: 2,
17 | gaining_team_id: 3
18 | },
19 | %TradeLineItem{
20 | losing_team_id: 1,
21 | future_pick_id: 4,
22 | gaining_team_id: 3,
23 | future_pick: %DraftPicks.FuturePick{
24 | id: 4,
25 | round: 1,
26 | current_team_id: 1,
27 | original_team_id: 1
28 | }
29 | }
30 | ]
31 | }
32 |
33 | @positions [
34 | %RosterPosition{
35 | id: 4,
36 | fantasy_team_id: 1,
37 | fantasy_player_id: 2,
38 | status: "active"
39 | }
40 | ]
41 |
42 | describe "process_approved_trade/3" do
43 | test "with Approved status, returns a multi with valid changeset" do
44 | params = %{"status" => "Approved"}
45 |
46 | multi = Admin.process_approved_trade(@trade, params, @positions)
47 |
48 | assert [
49 | {:trade, {:update, trade_changeset, []}},
50 | {:losing_position_4, {:update, los_pos_changeset, []}},
51 | {:gaining_position_2, {:insert, gain_pos_changeset, []}},
52 | {:future_pick_4, {:update, future_pick_changeset, []}}
53 | ] = Multi.to_list(multi)
54 |
55 | assert trade_changeset.valid?
56 | assert los_pos_changeset.valid?
57 | assert gain_pos_changeset.valid?
58 | assert future_pick_changeset.valid?
59 | end
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/ex338/injured_reserves/injured_reserve.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.InjuredReserves.InjuredReserve do
2 | @moduledoc false
3 |
4 | use Ecto.Schema
5 |
6 | import Ecto.Changeset
7 | import Ecto.Query, warn: false
8 |
9 | alias Ex338.FantasyPlayers.FantasyPlayer
10 | alias Ex338.InjuredReserves.InjuredReserve
11 |
12 | schema "injured_reserves" do
13 | field(:status, InjuredReserveStatusEnum, default: "submitted")
14 | belongs_to(:fantasy_team, Ex338.FantasyTeams.FantasyTeam)
15 | belongs_to(:injured_player, FantasyPlayer)
16 | belongs_to(:replacement_player, FantasyPlayer)
17 |
18 | timestamps()
19 | end
20 |
21 | @doc """
22 | Builds a changeset based on the `struct` and `params`.
23 | """
24 | def changeset(%InjuredReserve{} = injured_reserve, params \\ %{}) do
25 | injured_reserve
26 | |> cast(params, [
27 | :status,
28 | :fantasy_team_id,
29 | :injured_player_id,
30 | :replacement_player_id
31 | ])
32 | |> validate_required([:fantasy_team_id, :injured_player_id, :replacement_player_id, :status])
33 | |> validate_inclusion(:status, InjuredReserveStatusEnum.__valid_values__())
34 | end
35 |
36 | def by_league(query, league_id) do
37 | from(
38 | i in query,
39 | join: f in assoc(i, :fantasy_team),
40 | where: f.fantasy_league_id == ^league_id,
41 | order_by: [desc: i.inserted_at]
42 | )
43 | end
44 |
45 | def by_status(query, statuses) when is_list(statuses) do
46 | from(
47 | i in query,
48 | where: i.status in ^statuses
49 | )
50 | end
51 |
52 | def preload_assocs(query) do
53 | from(
54 | i in query,
55 | preload: [
56 | [fantasy_team: [:owners, :fantasy_league]],
57 | [injured_player: :sports_league],
58 | [replacement_player: :sports_league]
59 | ]
60 | )
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/lib/ex338_web/telemetry.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.Telemetry do
2 | @moduledoc false
3 | use Supervisor
4 |
5 | import Telemetry.Metrics
6 |
7 | def start_link(arg) do
8 | Supervisor.start_link(__MODULE__, arg, name: __MODULE__)
9 | end
10 |
11 | @impl true
12 | def init(_arg) do
13 | children = [
14 | # Telemetry poller will execute the given period measurements
15 | # every 10_000ms. Learn more here: https://hexdocs.pm/telemetry_metrics
16 | {:telemetry_poller, measurements: periodic_measurements(), period: 10_000}
17 | # Add reporters as children of your supervision tree.
18 | # {Telemetry.Metrics.ConsoleReporter, metrics: metrics()}
19 | ]
20 |
21 | Supervisor.init(children, strategy: :one_for_one)
22 | end
23 |
24 | def metrics do
25 | [
26 | # Phoenix Metrics
27 | summary("phoenix.endpoint.stop.duration",
28 | unit: {:native, :millisecond}
29 | ),
30 | summary("phoenix.router_dispatch.stop.duration",
31 | tags: [:route],
32 | unit: {:native, :millisecond}
33 | ),
34 |
35 | # Database Time Metrics
36 | summary("ex338.repo.query.total_time", unit: {:native, :millisecond}),
37 | summary("ex338.repo.query.decode_time", unit: {:native, :millisecond}),
38 | summary("ex338.repo.query.query_time", unit: {:native, :millisecond}),
39 | summary("ex338.repo.query.queue_time", unit: {:native, :millisecond}),
40 | summary("ex338.repo.query.idle_time", unit: {:native, :millisecond}),
41 |
42 | # VM Metrics
43 | summary("vm.memory.total", unit: {:byte, :kilobyte}),
44 | summary("vm.total_run_queue_lengths.total"),
45 | summary("vm.total_run_queue_lengths.cpu"),
46 | summary("vm.total_run_queue_lengths.io")
47 | ]
48 | end
49 |
50 | defp periodic_measurements do
51 | []
52 | end
53 | end
54 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/user_confirmation_instructions_live.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.UserConfirmationInstructionsLive do
2 | @moduledoc false
3 | use Ex338Web, :live_view
4 |
5 | alias Ex338.Accounts
6 |
7 | def render(assigns) do
8 | ~H"""
9 | <.padded_container>
10 |
11 | <.header class="text-center">
12 | No confirmation instructions received?
13 | <:subtitle>We'll send a new confirmation link to your inbox
14 |
15 |
16 | <.simple_form for={@form} id="resend_confirmation_form" phx-submit="send_instructions">
17 | <.input field={@form[:email]} type="email" placeholder="Email" required />
18 | <:actions>
19 | <.button phx-disable-with="Sending..." class="w-full">
20 | Resend confirmation instructions
21 |
22 |
23 |
24 |
25 |
26 | <.link href={~p"/users/register"}>Register
27 | | <.link href={~p"/users/log_in"}>Log in
28 |
29 |
30 |
31 | """
32 | end
33 |
34 | def mount(_params, _session, socket) do
35 | {:ok, assign(socket, form: to_form(%{}, as: "user"))}
36 | end
37 |
38 | def handle_event("send_instructions", %{"user" => %{"email" => email}}, socket) do
39 | if user = Accounts.get_user_by_email(email) do
40 | Accounts.deliver_user_confirmation_instructions(
41 | user,
42 | &url(~p"/users/confirm/#{&1}")
43 | )
44 | end
45 |
46 | info =
47 | "If your email is in our system and it has not been confirmed yet, you will receive an email with instructions shortly."
48 |
49 | {:noreply,
50 | socket
51 | |> put_flash(:info, info)
52 | |> redirect(to: ~p"/")}
53 | end
54 | end
55 |
--------------------------------------------------------------------------------
/priv/repo/csv_seed_data/championship_slots.csv:
--------------------------------------------------------------------------------
1 | Roster_Position_ID,Championship ID,Slot,Fantasy Team Id,Fantasy Player Id,Position
2 | 13,17,1,11,398,MTn
3 | 38,17,1,25,398,MTn
4 | 442,17,1,4,400,MTn
5 | 427,17,1,14,400,Unassigned
6 | 341,17,1,1,401,MTn
7 | 257,17,1,24,401,MTn
8 | 244,17,1,7,405,MTn
9 | 252,17,2,26,405,Flex5
10 | 511,17,3,5,407,Unassigned
11 | 313,17,2,5,410,Flex1
12 | 469,17,2,18,410,Flex3
13 | 170,17,1,5,412,MTn
14 | 297,17,2,11,412,Unassigned
15 | 159,17,1,9,413,MTn
16 | 142,17,1,10,413,MTn
17 | 187,17,1,18,418,MTn
18 | 225,17,1,21,418,MTn
19 | 150,17,1,15,420,MTn
20 | 123,17,1,17,420,MTn
21 | 458,17,2,21,421,Unassigned
22 | 386,17,1,23,421,MTn
23 | 1,17,1,12,422,MTn
24 | 11,17,1,27,422,MTn
25 | 224,17,2,13,424,Flex3
26 | 202,17,1,16,424,MTn
27 | 196,17,2,12,427,Flex3
28 | 195,17,1,13,427,MTn
29 | 35,17,1,3,428,MTn
30 | 74,17,1,26,428,MTn
31 | 246,17,1,6,429,MTn
32 | 235,17,1,28,429,MTn
33 | 13,18,1,11,398,MTn
34 | 38,18,1,25,398,MTn
35 | 246,18,1,11,429,MTn
36 | 235,18,1,25,429,MTn
37 | 192,22,1,15,432,WTn
38 | 179,22,1,27,432,WTn
39 | 22,22,1,9,437,WTn
40 | 45,22,1,19,437,WTn
41 | 446,22,3,2,438,Flex4
42 | 463,22,2,5,438,Flex4
43 | 348,22,1,16,439,WTn
44 | 325,22,1,20,439,WTn
45 | 507,22,4,2,440,Unassigned
46 | 400,22,2,22,440,Unassigned
47 | 423,22,1,18,442,WTn
48 | 406,22,1,24,442,WTn
49 | 99,22,1,4,445,WTn
50 | 144,22,1,14,445,WTn
51 | 131,22,1,11,449,WTn
52 | 172,22,2,13,449,Flex2
53 | 501,22,1,26,452,WTn
54 | 343,22,1,1,453,WTn
55 | 317,22,1,17,453,WTn
56 | 397,22,1,3,455,Unassigned
57 | 393,22,1,10,455,WTn
58 | 336,22,2,2,458,Flex2
59 | 321,22,1,28,458,WTn
60 | 3,22,1,8,463,WTn
61 | 31,22,1,13,463,WTn
62 | 148,22,1,2,464,WTn
63 | 169,22,1,22,464,WTn
64 | 351,22,1,25,466,WTn
65 | 496,22,1,6,468,Unassigned
66 | 314,22,1,21,468,WTn
67 | 421,22,1,5,469,WTn
68 | 437,22,2,18,469,Flex2
69 | 436,22,1,20,502,MTn
70 | 494,22,1,23,503,WTn
71 | 370,22,1,22,507,MTn
72 | 472,22,1,19,508,MTn
73 |
--------------------------------------------------------------------------------
/test/ex338/commish_notifier_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.CommishNotifierTest do
2 | use Ex338.DataCase, async: true
3 |
4 | import Swoosh.TestAssertions
5 |
6 | alias Ex338Web.CommishNotifier
7 | alias Ex338Web.NotifierTemplate
8 |
9 | describe "send_email_to_leagues/3" do
10 | test "sends an email to owners of a list of leagues" do
11 | admin_user = insert_admin()
12 | other_user = insert_user()
13 | league = insert(:fantasy_league)
14 | team = insert(:fantasy_team, fantasy_league: league)
15 | insert(:owner, fantasy_team: team, user: other_user)
16 | subject = "Announcement"
17 | message = "Here is the latest info!"
18 |
19 | email_info = %{
20 | bcc: [{other_user.name, other_user.email}, {admin_user.name, admin_user.email}],
21 | cc: {"338 Commish", "commish@the338challenge.com"},
22 | from: {"338 Commish", "commish@the338challenge.com"},
23 | subject: subject,
24 | message: message
25 | }
26 |
27 | CommishNotifier.send_email_to_leagues(
28 | [league.id],
29 | subject,
30 | message
31 | )
32 |
33 | assert_email_sent(NotifierTemplate.plain_text(email_info))
34 | end
35 | end
36 |
37 | describe "unique_recipients/2" do
38 | test "combines admins and owners" do
39 | admins = [{"Ryan", "ryan@example.com"}]
40 | owners = [{"owner", "owner@example.com"}]
41 |
42 | result = CommishNotifier.unique_recipients(owners, admins)
43 |
44 | assert result == owners ++ admins
45 | end
46 |
47 | test "combines admins and owners while removing duplicates" do
48 | brown = {"Ryan", "ryan@example.com"}
49 | owners = [brown, {"owner", "owner@example.com"}]
50 | admins = [brown]
51 |
52 | result = CommishNotifier.unique_recipients(owners, admins)
53 |
54 | assert result == owners
55 | end
56 | end
57 | end
58 |
--------------------------------------------------------------------------------
/lib/ex338/championships/create_slot.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.Championships.CreateSlot do
2 | @moduledoc false
3 |
4 | alias Ecto.Multi
5 | alias Ex338.Championships.ChampionshipSlot
6 | alias Ex338.Repo
7 | alias Ex338.RosterPositions.Admin
8 |
9 | def create_slots_from_positions(teams, championship_id) do
10 | teams
11 | |> Enum.map(&calculate_slots_for_team(&1))
12 | |> retrieve_positions_to_array()
13 | |> insert_slots(championship_id)
14 | |> Repo.transaction()
15 | end
16 |
17 | def calculate_slots_for_team(team) do
18 | team
19 | |> Admin.order_by_position()
20 | |> add_slot_to_position()
21 | end
22 |
23 | defp add_slot_to_position(team) do
24 | {positions_with_slots, _} =
25 | Enum.map_reduce(team.roster_positions, 1, fn pos, acc ->
26 | {Map.put(pos, :slot, acc), acc + 1}
27 | end)
28 |
29 | update_roster_positions(team, positions_with_slots)
30 | end
31 |
32 | defp update_roster_positions(team, new_positions) do
33 | Map.put(team, :roster_positions, new_positions)
34 | end
35 |
36 | defp retrieve_positions_to_array(teams) do
37 | Enum.flat_map(teams, & &1.roster_positions)
38 | end
39 |
40 | defp insert_slots(positions, championship_id) do
41 | Enum.reduce(positions, Multi.new(), fn position, multi ->
42 | insert_slot_from_position(multi, position, championship_id)
43 | end)
44 | end
45 |
46 | defp insert_slot_from_position(multi, position, championship_id) do
47 | attrs = %{
48 | roster_position_id: position.id,
49 | championship_id: championship_id,
50 | slot: position.slot
51 | }
52 |
53 | multi_name = create_multi_name(position.id)
54 | changeset = ChampionshipSlot.changeset(%ChampionshipSlot{}, attrs)
55 | Multi.insert(multi, multi_name, changeset)
56 | end
57 |
58 | defp create_multi_name(id) do
59 | String.to_atom("insert_slot_for_position#{id}")
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/test/support/data_case.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338.DataCase do
2 | @moduledoc """
3 | This module defines the setup for tests requiring
4 | access to the application's data layer.
5 |
6 | You may define functions here to be used as helpers in
7 | your tests.
8 |
9 | Finally, if the test case interacts with the database,
10 | we enable the SQL sandbox, so changes done to the database
11 | are reverted at the end of every test. If you are using
12 | PostgreSQL, you can even run database tests asynchronously
13 | by setting `use DockerPhx.DataCase, async: true`, although
14 | this option is not recommended for other databases.
15 | """
16 |
17 | use ExUnit.CaseTemplate
18 |
19 | alias Ecto.Adapters.SQL.Sandbox
20 |
21 | using do
22 | quote do
23 | import Ecto
24 | import Ecto.Changeset
25 | import Ecto.Query
26 | import Ex338.DataCase
27 | import Ex338.Factory
28 |
29 | alias Ex338.Repo
30 | end
31 | end
32 |
33 | setup tags do
34 | Ex338.DataCase.setup_sandbox(tags)
35 | :ok
36 | end
37 |
38 | @doc """
39 | Sets up the sandbox based on the test tags.
40 | """
41 | def setup_sandbox(tags) do
42 | pid = Sandbox.start_owner!(Ex338.Repo, shared: not tags[:async])
43 | on_exit(fn -> Sandbox.stop_owner(pid) end)
44 | end
45 |
46 | @doc """
47 | A helper that transforms changeset errors into a map of messages.
48 |
49 | assert {:error, changeset} = Accounts.create_user(%{password: "short"})
50 | assert "password is too short" in errors_on(changeset).password
51 | assert %{password: ["password is too short"]} = errors_on(changeset)
52 |
53 | """
54 | def errors_on(changeset) do
55 | Ecto.Changeset.traverse_errors(changeset, fn {message, opts} ->
56 | Regex.replace(~r"%{(\w+)}", message, fn _, key ->
57 | opts |> Keyword.get(String.to_existing_atom(key), key) |> to_string()
58 | end)
59 | end)
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/lib/ex338_web/live/fantasy_team_draft_queues/edit.ex:
--------------------------------------------------------------------------------
1 | defmodule Ex338Web.FantasyTeamDraftQueuesLive.Edit do
2 | @moduledoc false
3 | use Ex338Web, :live_view
4 |
5 | alias Ex338.DraftQueues.DraftQueue
6 | alias Ex338.FantasyTeamAuthorizer
7 | alias Ex338.FantasyTeams
8 | alias Ex338.FantasyTeams.FantasyTeam
9 |
10 | @impl true
11 | def mount(_params, _session, socket) do
12 | {:ok, socket}
13 | end
14 |
15 | @impl true
16 | def handle_params(%{"id" => id}, _, socket) do
17 | with %FantasyTeam{} = fantasy_team <- FantasyTeams.find_for_edit(id),
18 | :ok <-
19 | FantasyTeamAuthorizer.authorize(:edit_team, socket.assigns.current_user, fantasy_team) do
20 | {:noreply, assign_defaults(socket, fantasy_team)}
21 | else
22 | {:error, :not_authorized} ->
23 | {:noreply,
24 | socket
25 | |> put_flash(:error, "Not authorized to edit that Fantasy Team's draft queues")
26 | |> push_navigate(to: ~p"/fantasy_teams/#{id}")}
27 |
28 | nil ->
29 | {:noreply,
30 | socket
31 | |> put_flash(:error, "Fantasy Team not found")
32 | |> push_navigate(to: ~p"/")}
33 | end
34 | end
35 |
36 | defp assign_defaults(socket, fantasy_team) do
37 | socket
38 | |> assign(:fantasy_team, fantasy_team)
39 | |> assign(:fantasy_league, fantasy_team.fantasy_league)
40 | |> assign(:new_draft_queue, %DraftQueue{})
41 | end
42 |
43 | @impl true
44 | def render(assigns) do
45 | ~H"""
46 |
47 | <.live_component
48 | module={Ex338Web.FantasyTeamDraftQueuesLive.NewFormComponent}
49 | id={:new}
50 | fantasy_team={@fantasy_team}
51 | draft_queue={@new_draft_queue}
52 | />
53 | <.live_component
54 | module={Ex338Web.FantasyTeamDraftQueuesLive.EditFormComponent}
55 | id={@fantasy_team.id}
56 | fantasy_team={@fantasy_team}
57 | />
58 |
59 | """
60 | end
61 | end
62 |
--------------------------------------------------------------------------------
/test/ex338/accounts/user_test.exs:
--------------------------------------------------------------------------------
1 | defmodule Ex338.UserTest do
2 | use Ex338.DataCase, async: true
3 |
4 | alias Ex338.Accounts.User
5 |
6 | describe "admin_emails/0" do
7 | test "returns all admin emails" do
8 | admin_a = insert_admin()
9 |
10 | query = User.admin_emails()
11 |
12 | assert Repo.all(query) == [{admin_a.name, admin_a.email}]
13 | end
14 | end
15 |
16 | describe "alphabetical/1" do
17 | test "returns users in alphabetical order by name" do
18 | insert(:user, name: "B")
19 | insert(:user, name: "A")
20 | insert(:user, name: "C")
21 |
22 | result =
23 | User
24 | |> User.alphabetical()
25 | |> Repo.all()
26 | |> Enum.map(& &1.name)
27 |
28 | assert result == ["A", "B", "C"]
29 | end
30 | end
31 |
32 | describe "preload_assocs/1" do
33 | test "preloads assocs for a user" do
34 | user = insert(:user)
35 | team = insert(:fantasy_team)
36 | insert(:owner, user: user, fantasy_team: team)
37 |
38 | %{owners: [owner_result]} =
39 | User
40 | |> User.preload_assocs()
41 | |> Repo.one()
42 |
43 | assert owner_result.fantasy_team.id == team.id
44 | end
45 | end
46 |
47 | describe "user_changeset/2" do
48 | @valid_attrs %{email: "axel@example.com"}
49 | test "changeset with valid attributes" do
50 | changeset = User.user_changeset(%User{name: "A", email: "j@me.com"}, @valid_attrs)
51 | assert changeset.valid?
52 | end
53 |
54 | @invalid_attrs %{email: "j"}
55 | test "changeset with invalid attributes" do
56 | changeset = User.user_changeset(%User{name: "A", email: "j@me.com"}, @invalid_attrs)
57 | refute changeset.valid?
58 | end
59 |
60 | @extra_attrs %{admin: true}
61 | test "doesn't allow updates to unauthorized fields" do
62 | changeset = User.user_changeset(%User{name: "A", email: "j@me.com"}, @extra_attrs)
63 | assert changeset.changes == %{}
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------