https://github.com/ericgitangu/bets
https://github.com/ericgitangu/bets
Last synced: 3 months ago
JSON representation
- Host: GitHub
- URL: https://github.com/ericgitangu/bets
- Owner: ericgitangu
- Created: 2024-03-02T05:28:04.000Z (about 2 years ago)
- Default Branch: main
- Last Pushed: 2024-03-13T01:02:19.000Z (about 2 years ago)
- Last Synced: 2025-01-18T03:15:14.135Z (about 1 year ago)
- Language: Elixir
- Size: 16.3 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# Betting System
## Folder Structure
- `assets`: Contains asset files for the app - JS, CSS & Images.
- `lib`: Contains source code files for the application logic.
- `priv`: Contains private application files (e.g., assets, templates).
- `test`: Contains test files for the application.
## Installation
To set up and run this application, you need to follow these steps:
1. Clone the repository: `git clone https:/github.com/ericgitangu/elixir-phoenix-betting-system`
2. Install dependencies: `mix deps.get`
3. Create and migrate the database: `mix ecto.setup`
4. Start the Phoenix server: `mix phx.server`
## Usage
To use the application, follow these steps:
1. Access the application at `http:/localhost:4000`
2. Sign up or log in with Github to start viewing and participating in wagering Bets
## Overview
Creating 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.
### System Overview
The 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.
### System Components
1. **Web Interface**: A responsive and user-friendly interface for both user users and administrators.
2. **Elixir/Phoenix Backend**: The core logic of the application, handling requests, business logic, data management, and notifications.
3. **Database**: PostgreSQL for data storage, including user data, Bets, games, and transaction histories.
4. **Authentication and Authorization**: For secure access control using Phoenix's built-in libraries or external libraries like Guardian.
5. **Email Service**: For sending notifications to users about bet outcomes, using an email library compatible with Elixir, such as Bamboo.
### Functional Requirements
1. **User Management**: Registration, authentication, profile management, and account management.
2. **Bet Management**: Placing, viewing, and canceling Bets.
3. **Game Management**: Adding, updating, and managing sports games, initially limited to football but expandable to other types.
4. **Transaction History**: Viewing a history of Bets, including winnings and losses.
5. **Administrative Functions**: User management, bet oversight, soft deletion of users and data, and profit tracking.
6. **Superuser Privileges**: Configuring games, managing user roles, and sending email notifications.
### Implementation Details
#### Database Schema
- `Users`: Stores user information, including `first_name`, `last_name`, `email`, `msisdn`, and role.
- `Games`: Contains game details, with a dynamic structure to accommodate different sports types.
- `Bets`: Records details of Bets placed by users, including the associated game, bet amount, and status.
- `Transactions`: Logs all Wagers transactions, including wins and losses.
#### Elixir/Phoenix Modules
1. **User Module**: Handles user registration, authentication, profile updates, and role management.
2. **Game Module**: Manages game records, including creation, updates, and listing available games.
3. **Bet Module**: Manages bet lifecycle, including placing, updating, and canceling Bets, as well as calculating outcomes.
4. **Admin Module**: Provides administrative functionalities, including user management, bet oversight, and financial reporting.
5. **Notification Module**: Manages sending email notifications to users regarding bet outcomes.
#### Security
- Implement secure authentication and session management.
- Ensure proper role-based access control for different user types.
#### Testing
- **Unit Tests**: Write Elixir doctests and ExUnit tests for individual functions to ensure reliability and correctness. Also to test out edge cases.
### Development and Deployment
- Use Mix for project management and dependencies.
- Employ continuous integration (CI) practices with tool -GitHub.
- Deploy the application to a production environment - Fly.io.
### Maintenance and Scalability
- Monitor system performance and optimize queries and processes for efficiency - dev/platform.
- Use Phoenix's built-in support for WebSockets for real-time features, if necessary.
- Normalize DBs effectively to support effecient querying.
## Database Schema Migrations
Below are the migration files needed to create the necessary tables in your PostgreSQL database:
## Users Table
```sh
defmodule Bets.Repo.Migrations.CreateUsers do
use Ecto.Migration
def change do
create table(:users) do
add :first_name, :string
add :last_name, :string
add :email, :string, unique: true
add :msisdn, :string, unique: true
add :role, :string, default: "user"
timestamps()
end
create index(:users, [:email])
end
end
```
## Games Table
```sh
defmodule Bets.Repo.Migrations.CreateGames do
use Ecto.Migration
def change do
create table(:games) do
add :name, :string
add :sport_type, :string
add :status, :string, default: "upcoming" # Possible values: upcoming, live, completed
add :result, :string
timestamps()
end
end
end
```
## Bets Table
```sh
defmodule Bets.Repo.Migrations.CreateBets do
use Ecto.Migration
def change do
create table(:Bets) do
add :amount, :decimal
add :status, :string, default: "pending" # Possible values: pending, won, lost
add :user_id, references(:users)
add :game_id, references(:games)
timestamps()
end
create index(:Bets, [:user_id])
create index(:Bets, [:game_id])
end
end
```
## User Schema
```sh
defmodule Bets.Users.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :first_name, :string
field :last_name, :string
field :email, :string
field :msisdn, :string
field :role, :string
timestamps()
end
@doc false
def changeset(user, attrs) do
user
|> cast(attrs, [:first_name, :last_name, :email, :msisdn, :role])
|> validate_required([:first_name, :last_name, :email, :msisdn])
end
end
```
## User Creation
```sh
defmodule Bets.Users do
alias Bets.Users.User
alias Bets.Repo
def create_user(attrs) do
%User{}
|> User.changeset(attrs)
|> Repo.insert()
end
end
```
## Authentication
Using Ueberauth for authentication with Gmail and GitHub, first add Ueberauth and the specific strategies to your mix.exs dependencies
```sh
defp deps do
[
{:ueberauth, "~> 0.6"},
{:ueberauth_github, "~> 0.8"},
{:ueberauth_google, "~> 0.10"}
]
end
```
## Authentication configs
```sh
config :ueberauth, Ueberauth,
providers: [
github: {Ueberauth.Strategy.Github, []},
google: {Ueberauth.Strategy.Google, []}
]
config :ueberauth, Ueberauth.Strategy.Github.OAuth,
client_id: System.get_env("GITHUB_CLIENT_ID"),
client_secret: System.get_env("GITHUB_CLIENT_SECRET")
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI")
```
## Unit-Test Strategy (Red/Green)
Next, 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.
## Example - Test for Successfull User Creation
```sh
defmodule Bets.UsersTest do
use Bets.DataCase
alias Bets.Users
alias Bets.Users.User
describe "create_user/1" do
test "creates a user with valid data" do
attrs = %{first_name: "Jane", last_name: "Doe", email: "jane@example.com", msisdn: "1234567890"}
{:ok, user} = Accounts.create_user(attrs)
assert user.first_name == "Jane"
assert user.last_name == "Doe"
assert user.email == "jane@example.com"
assert user.msisdn == "1234567890"
end
end
end
```
## Example - Test for User Creation with Invalid Data
```sh
describe "create_user/1 with invalid data" do
test "fails to create a user with missing data" do
attrs = %{first_name: "Jane"} # Missing required fields
assert {:error, changeset} = Accounts.create_user(attrs)
assert changeset.valid? == false
assert Enum.any?(changeset.errors, fn {field, _} -> field == :last_name end)
assert Enum.any?(changeset.errors, fn {field, _} -> field == :email end)
end
end
```
## Entity Relational Diagram

## User Schema specific roles
First, 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".
```sh
defmodule Bets.Users.User do
use Ecto.Schema
import Ecto.Changeset
schema "users" do
field :first_name, :string
field :last_name, :string
field :email, :string
field :msisdn, :string
field :role, :string, default: "user" # Assuming 'user', 'admin', 'superuser'
timestamps()
end
def changeset(user, attrs) do
user
|> cast(attrs, [:first_name, :last_name, :email, :msisdn, :role])
|> validate_required([:first_name, :last_name, :email, :msisdn, :role])
end
end
```
## Role Management
In 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.
```sh
defmodule Bets.Users do
alias Bets.Users.User
alias Bets.Repo
def grant_admin_role(superuser_id, target_user_id) do
with {:ok, superuser} <- get_superuser(superuser_id),
{:ok, target_user} <- get_user(target_user_id) do
target_user
|> User.changeset(%{role: "admin"})
|> Repo.update()
else
_error -> {:error, :unauthorized}
end
end
def revoke_admin_role(superuser_id, target_user_id) do
with {:ok, superuser} <- get_superuser(superuser_id),
{:ok, target_user} <- get_user(target_user_id) do
target_user
|> User.changeset(%{role: "user"})
|> Repo.update()
else
_error -> {:error, :unauthorized}
end
end
defp get_superuser(id) do
case Repo.get(User, id) do
%User{role: "superuser"} = user -> {:ok, user}
_ -> {:error, :not_found}
end
end
defp get_user(id) do
case Repo.get(User, id) do
nil -> {:error, :not_found}
user -> {:ok, user}
end
end
end
```
## Protecting Role Mangement Functions
Ensure 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.
```sh
defmodule BetsWeb.UserController do
use BetsWeb, :controller
plug :authorize_superuser when action in [:grant_admin, :revoke_admin]
def grant_admin(conn, %{"target_user_id" => target_user_id}) do
current_user = conn.assigns.current_user
case Bets.Users.grant_admin_role(current_user.id, target_user_id) do
{:ok, _user} ->
conn
|> put_flash(:info, "Admin role granted successfully.")
|> redirect(to: Routes.user_path(conn, :index))
{:error, _reason} ->
conn
|> put_flash(:error, "Unauthorized.")
|> redirect(to: Routes.page_path(conn, :index))
end
end
def revoke_admin(conn, %{"target_user_id" => target_user_id}) do
current_user = conn.assigns.current_user
case Bets.Users.revoke_admin_role(current_user.id, target_user_id) do
{:ok, _user} ->
conn
|> put_flash(:info, "Admin role revoked successfully.")
|> redirect(to: Routes.user_path(conn, :index))
{:error, _reason} ->
conn
|> put_flash(:error, "Unauthorized.")
|> redirect(to: Routes.page_path(conn, :index))
end
end
```
## Plug to check if the current user is a super
```sh
plug :authorize_superuser when action in [:grant_admin, :revoke_admin]
defp authorize_superuser(conn, _opts) do
current_user = conn.assigns[:current_user]
if current_user && current_user.role == "superuser" do
conn
else
conn
|> put_flash(:error, "You must be a superuser to perform this action.")
|> redirect(to: Routes.page_path(conn, :index))
|> halt()
end
end
```