{"id":24333374,"url":"https://github.com/ericgitangu/bets","last_synced_at":"2026-04-15T11:31:08.143Z","repository":{"id":225737878,"uuid":"766016116","full_name":"ericgitangu/bets","owner":"ericgitangu","description":null,"archived":false,"fork":false,"pushed_at":"2024-03-13T01:02:19.000Z","size":17108,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-18T03:15:14.135Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ericgitangu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2024-03-02T05:28:04.000Z","updated_at":"2024-03-02T05:28:41.000Z","dependencies_parsed_at":"2024-03-13T01:47:24.412Z","dependency_job_id":"add7543f-fef6-48c0-8e04-3b2d83d2986e","html_url":"https://github.com/ericgitangu/bets","commit_stats":null,"previous_names":["ericgitangu/bets"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgitangu%2Fbets","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgitangu%2Fbets/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgitangu%2Fbets/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ericgitangu%2Fbets/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ericgitangu","download_url":"https://codeload.github.com/ericgitangu/bets/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":243048019,"owners_count":20227592,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-18T03:15:17.745Z","updated_at":"2025-12-24T11:52:06.015Z","avatar_url":"https://github.com/ericgitangu.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Betting System\n\n## Folder Structure\n\n- `assets`: Contains asset files for the app - JS, CSS \u0026 Images.\n- `lib`: Contains source code files for the application logic.\n- `priv`: Contains private application files (e.g., assets, templates).\n- `test`: Contains test files for the application.\n\n## Installation\n\nTo set up and run this application, you need to follow these steps:\n\n1. Clone the repository: `git clone https:/github.com/ericgitangu/elixir-phoenix-betting-system`\n2. Install dependencies: `mix deps.get`\n3. Create and migrate the database: `mix ecto.setup`\n4. Start the Phoenix server: `mix phx.server`\n\n## Usage\n\nTo use the application, follow these steps:\n\n1. Access the application at `http:/localhost:4000`\n2. Sign up or log in with Github to start viewing and participating in wagering Bets\n\n## Overview\n\nCreating an architectural document and detailed implementation plan for a betting system using Elixir and Phoenix involves several steps. This document outlines the architecture, components, and implementation details of the system.\n\n### System Overview\n\nThe betting system will be a dynamic, scalable web application built using the Phoenix framework, which leverages the Elixir programming language. The system will support multiple sports, with an initial focus on football, and provide functionalities for user management, bet management, game management, and administrative tasks.\n\n### System Components\n\n1. **Web Interface**: A responsive and user-friendly interface for both user users and administrators.\n2. **Elixir/Phoenix Backend**: The core logic of the application, handling requests, business logic, data management, and notifications.\n3. **Database**: PostgreSQL for data storage, including user data, Bets, games, and transaction histories.\n4. **Authentication and Authorization**: For secure access control using Phoenix's built-in libraries or external libraries like Guardian.\n5. **Email Service**: For sending notifications to users about bet outcomes, using an email library compatible with Elixir, such as Bamboo.\n\n### Functional Requirements\n\n1. **User Management**: Registration, authentication, profile management, and account management.\n2. **Bet Management**: Placing, viewing, and canceling Bets.\n3. **Game Management**: Adding, updating, and managing sports games, initially limited to football but expandable to other types.\n4. **Transaction History**: Viewing a history of Bets, including winnings and losses.\n5. **Administrative Functions**: User management, bet oversight, soft deletion of users and data, and profit tracking.\n6. **Superuser Privileges**: Configuring games, managing user roles, and sending email notifications.\n\n### Implementation Details\n\n#### Database Schema\n\n- `Users`: Stores user information, including `first_name`, `last_name`, `email`, `msisdn`, and role.\n- `Games`: Contains game details, with a dynamic structure to accommodate different sports types.\n- `Bets`: Records details of Bets placed by users, including the associated game, bet amount, and status.\n- `Transactions`: Logs all Wagers transactions, including wins and losses.\n\n#### Elixir/Phoenix Modules\n\n1. **User Module**: Handles user registration, authentication, profile updates, and role management.\n2. **Game Module**: Manages game records, including creation, updates, and listing available games.\n3. **Bet Module**: Manages bet lifecycle, including placing, updating, and canceling Bets, as well as calculating outcomes.\n4. **Admin Module**: Provides administrative functionalities, including user management, bet oversight, and financial reporting.\n5. **Notification Module**: Manages sending email notifications to users regarding bet outcomes.\n\n#### Security\n\n- Implement secure authentication and session management.\n- Ensure proper role-based access control for different user types.\n\n#### Testing\n\n- **Unit Tests**: Write Elixir doctests and ExUnit tests for individual functions to ensure reliability and correctness. Also to test out edge cases.\n\n### Development and Deployment\n\n- Use Mix for project management and dependencies.\n- Employ continuous integration (CI) practices with tool -GitHub.\n- Deploy the application to a production environment - Fly.io.\n\n### Maintenance and Scalability\n\n- Monitor system performance and optimize queries and processes for efficiency - dev/platform.\n- Use Phoenix's built-in support for WebSockets for real-time features, if necessary.\n- Normalize DBs effectively to support effecient querying.\n\n## Database Schema Migrations\n\nBelow are the migration files needed to create the necessary tables in your PostgreSQL database:\n\n## Users Table\n\n```sh\ndefmodule Bets.Repo.Migrations.CreateUsers do\n  use Ecto.Migration\n\n  def change do\n    create table(:users) do\n      add :first_name, :string\n      add :last_name, :string\n      add :email, :string, unique: true\n      add :msisdn, :string, unique: true\n      add :role, :string, default: \"user\"\n\n      timestamps()\n    end\n\n    create index(:users, [:email])\n  end\nend\n```\n\n## Games Table\n\n```sh\ndefmodule Bets.Repo.Migrations.CreateGames do\n  use Ecto.Migration\n\n  def change do\n    create table(:games) do\n      add :name, :string\n      add :sport_type, :string\n      add :status, :string, default: \"upcoming\" # Possible values: upcoming, live, completed\n      add :result, :string\n\n      timestamps()\n    end\n  end\nend\n```\n\n## Bets Table\n\n```sh\ndefmodule Bets.Repo.Migrations.CreateBets do\n  use Ecto.Migration\n\n  def change do\n    create table(:Bets) do\n      add :amount, :decimal\n      add :status, :string, default: \"pending\" # Possible values: pending, won, lost\n      add :user_id, references(:users)\n      add :game_id, references(:games)\n\n      timestamps()\n    end\n\n    create index(:Bets, [:user_id])\n    create index(:Bets, [:game_id])\n  end\nend\n```\n\n## User Schema\n\n```sh\ndefmodule Bets.Users.User do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"users\" do\n    field :first_name, :string\n    field :last_name, :string\n    field :email, :string\n    field :msisdn, :string\n    field :role, :string\n\n    timestamps()\n  end\n\n  @doc false\n  def changeset(user, attrs) do\n    user\n    |\u003e cast(attrs, [:first_name, :last_name, :email, :msisdn, :role])\n    |\u003e validate_required([:first_name, :last_name, :email, :msisdn])\n  end\nend\n```\n\n## User Creation\n\n```sh\ndefmodule Bets.Users do\n  alias Bets.Users.User\n  alias Bets.Repo\n\n  def create_user(attrs) do\n    %User{}\n    |\u003e User.changeset(attrs)\n    |\u003e Repo.insert()\n  end\nend\n```\n\n## Authentication\n\nUsing Ueberauth for authentication with Gmail and GitHub, first add Ueberauth and the specific strategies to your mix.exs dependencies\n\n```sh\ndefp deps do\n  [\n    {:ueberauth, \"~\u003e 0.6\"},\n    {:ueberauth_github, \"~\u003e 0.8\"},\n    {:ueberauth_google, \"~\u003e 0.10\"}\n  ]\nend\n```\n\n## Authentication configs\n\n```sh\nconfig :ueberauth, Ueberauth,\n  providers: [\n    github: {Ueberauth.Strategy.Github, []},\n    google: {Ueberauth.Strategy.Google, []}\n  ]\n\nconfig :ueberauth, Ueberauth.Strategy.Github.OAuth,\n  client_id: System.get_env(\"GITHUB_CLIENT_ID\"),\n  client_secret: System.get_env(\"GITHUB_CLIENT_SECRET\")\n\nconfig :ueberauth, Ueberauth.Strategy.Google.OAuth,\n  client_id: System.get_env(\"GOOGLE_CLIENT_ID\"),\n  client_secret: System.get_env(\"GOOGLE_CLIENT_SECRET\"),\n  redirect_uri: System.get_env(\"GOOGLE_REDIRECT_URI\")\n```\n\n## Unit-Test Strategy (Red/Green)\n\nNext, we'll write ExUnit tests for the create_user function in the Bets.Users module. These tests will verify that the function behaves as expected when given valid and invalid input.\n\n## Example - Test for Successfull User Creation\n\n```sh\ndefmodule Bets.UsersTest do\n  use Bets.DataCase\n\n  alias Bets.Users\n  alias Bets.Users.User\n\n  describe \"create_user/1\" do\n    test \"creates a user with valid data\" do\n      attrs = %{first_name: \"Jane\", last_name: \"Doe\", email: \"jane@example.com\", msisdn: \"1234567890\"}\n      {:ok, user} = Accounts.create_user(attrs)\n\n      assert user.first_name == \"Jane\"\n      assert user.last_name == \"Doe\"\n      assert user.email == \"jane@example.com\"\n      assert user.msisdn == \"1234567890\"\n    end\n  end\nend\n```\n\n## Example - Test for User Creation with Invalid Data\n\n```sh\ndescribe \"create_user/1 with invalid data\" do\n  test \"fails to create a user with missing data\" do\n    attrs = %{first_name: \"Jane\"} # Missing required fields\n    assert {:error, changeset} = Accounts.create_user(attrs)\n\n    assert changeset.valid? == false\n    assert Enum.any?(changeset.errors, fn {field, _} -\u003e field == :last_name end)\n    assert Enum.any?(changeset.errors, fn {field, _} -\u003e field == :email end)\n  end\nend\n```\n\n## Entity Relational Diagram\n\n![Entity Relational Diagram](assets/BetsERD.png)\n\n## User Schema specific roles\n\nFirst, ensure your user schema includes a field to store the user's role. You might have a role field that can store values such as \"user\", \"admin\", or \"superuser\".\n\n```sh\ndefmodule Bets.Users.User do\n  use Ecto.Schema\n  import Ecto.Changeset\n\n  schema \"users\" do\n    field :first_name, :string\n    field :last_name, :string\n    field :email, :string\n    field :msisdn, :string\n    field :role, :string, default: \"user\" # Assuming 'user', 'admin', 'superuser'\n\n    timestamps()\n  end\n\n  def changeset(user, attrs) do\n    user\n    |\u003e cast(attrs, [:first_name, :last_name, :email, :msisdn, :role])\n    |\u003e validate_required([:first_name, :last_name, :email, :msisdn, :role])\n  end\nend\n```\n\n## Role Management\n\nIn your accounts context (or wherever you manage user-related logic), implement functions to grant or revoke admin roles. Ensure that only superusers can execute these functions.\n\n```sh\ndefmodule Bets.Users do\n  alias Bets.Users.User\n  alias Bets.Repo\n\n  \u003c!-- Function to grant admin role --\u003e\n  def grant_admin_role(superuser_id, target_user_id) do\n    with {:ok, superuser} \u003c- get_superuser(superuser_id),\n         {:ok, target_user} \u003c- get_user(target_user_id) do\n      target_user\n      |\u003e User.changeset(%{role: \"admin\"})\n      |\u003e Repo.update()\n    else\n      _error -\u003e {:error, :unauthorized}\n    end\n  end\n\n  \u003c!--  Function to revoke admin role --\u003e\n  def revoke_admin_role(superuser_id, target_user_id) do\n    with {:ok, superuser} \u003c- get_superuser(superuser_id),\n         {:ok, target_user} \u003c- get_user(target_user_id) do\n      target_user\n      |\u003e User.changeset(%{role: \"user\"})\n      |\u003e Repo.update()\n    else\n      _error -\u003e {:error, :unauthorized}\n    end\n  end\n\n   \u003c!-- Helper function to fetch a superuser --\u003e\n  defp get_superuser(id) do\n    case Repo.get(User, id) do\n      %User{role: \"superuser\"} = user -\u003e {:ok, user}\n      _ -\u003e {:error, :not_found}\n    end\n  end\n\n  \u003c!-- # Helper function to fetch any user --\u003e\n  defp get_user(id) do\n    case Repo.get(User, id) do\n      nil -\u003e {:error, :not_found}\n      user -\u003e {:ok, user}\n    end\n  end\nend\n```\n\n## Protecting Role Mangement Functions\n\nEnsure that role management functions can only be executed by superusers. This can be done at the controller level by checking the current user's role before allowing them to call grant_admin_role/2 or revoke_admin_role/2. You might use a plug or a controller callback to perform this check.\n\n```sh\ndefmodule BetsWeb.UserController do\n  use BetsWeb, :controller\n\n  plug :authorize_superuser when action in [:grant_admin, :revoke_admin]\n\n  def grant_admin(conn, %{\"target_user_id\" =\u003e target_user_id}) do\n    current_user = conn.assigns.current_user\n    case Bets.Users.grant_admin_role(current_user.id, target_user_id) do\n      {:ok, _user} -\u003e \n        conn\n        |\u003e put_flash(:info, \"Admin role granted successfully.\")\n        |\u003e redirect(to: Routes.user_path(conn, :index))\n      {:error, _reason} -\u003e \n        conn\n        |\u003e put_flash(:error, \"Unauthorized.\")\n        |\u003e redirect(to: Routes.page_path(conn, :index))\n    end\n  end\n\n  def revoke_admin(conn, %{\"target_user_id\" =\u003e target_user_id}) do\n    current_user = conn.assigns.current_user\n    case Bets.Users.revoke_admin_role(current_user.id, target_user_id) do\n      {:ok, _user} -\u003e \n        conn\n        |\u003e put_flash(:info, \"Admin role revoked successfully.\")\n        |\u003e redirect(to: Routes.user_path(conn, :index))\n      {:error, _reason} -\u003e \n        conn\n        |\u003e put_flash(:error, \"Unauthorized.\")\n        |\u003e redirect(to: Routes.page_path(conn, :index))\n    end\n  end\n  ```\n\n## Plug to check if the current user is a super\n\n  ```sh\n  plug :authorize_superuser when action in [:grant_admin, :revoke_admin]\n\n  defp authorize_superuser(conn, _opts) do\n    current_user = conn.assigns[:current_user]\n\n    if current_user \u0026\u0026 current_user.role == \"superuser\" do\n      conn\n    else\n      conn\n      |\u003e put_flash(:error, \"You must be a superuser to perform this action.\")\n      |\u003e redirect(to: Routes.page_path(conn, :index))\n      |\u003e halt()\n    end\n  end\n  ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgitangu%2Fbets","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fericgitangu%2Fbets","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fericgitangu%2Fbets/lists"}