first commit

This commit is contained in:
2025-09-22 21:18:24 -04:00
commit f2faced238
14 changed files with 396 additions and 0 deletions

14
Dockerfile.dev Normal file
View File

@@ -0,0 +1,14 @@
FROM python:3.9
ENV PYTHONFAULTHANDLER=1
ENV PYTHONUNBUFFERED=1
ENV MODE="DEVELOPMENT"
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

14
Dockerfile.local Normal file
View File

@@ -0,0 +1,14 @@
FROM python:3.9
ENV PYTHONFAULTHANDLER=1
ENV PYTHONUNBUFFERED=1
ENV MODE="LOCAL"
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

16
Dockerfile.prod Normal file
View File

@@ -0,0 +1,16 @@
FROM python:3.9
ENV PYTHONFAULTHANDLER=1
ENV PYTHONUNBUFFERED=1
ENV MODE="PRODUCTION"
WORKDIR /app
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
COPY . .
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

0
app/__init__.py Normal file
View File

22
app/config.py Normal file
View File

@@ -0,0 +1,22 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
# Load settings from the .env file
model_config = SettingsConfigDict(env_file="../.env", env_file_encoding='utf-8')
# VoIP.ms Credentials
voipms_api_username: str
voipms_api_password: str
# Target DID and Destinations
target_did: str
target_sip_account: str
target_cellphone_1: str
target_cellphone_2: str
# VoIP.ms API endpoint
voipms_api_url: str = "https://voip.ms/api/v1/rest.php"
# Create a single instance of the settings to be used throughout the app
settings = Settings()

34
app/database.py Normal file
View File

@@ -0,0 +1,34 @@
from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
from sqlalchemy.engine import URL
from config import load_config
ApplicationConfig = load_config()
url = URL.create(
drivername="postgresql",
username=ApplicationConfig.POSTGRES_USERNAME,
password=ApplicationConfig.POSTGRES_PW,
host=ApplicationConfig.POSTGRES_SERVER,
database=ApplicationConfig.POSTGRES_DBNAME00,
port=ApplicationConfig.POSTGRES_PORT
)
engine = create_engine(url)
Session = sessionmaker(autocommit=False, autoflush=False, bind=engine)
session = Session()
Base = declarative_base()
Base.metadata.create_all(engine)
def get_db():
db = Session()
try:
yield db
finally:
db.close()

82
app/main.py Normal file
View File

@@ -0,0 +1,82 @@
from fastapi import FastAPI, HTTPException, status
from .config import settings
from .voipms_client import update_did_routing
from .database import Session
from .models import Call
app = FastAPI(
title="EAMCO VoIP.ms Controller",
description="An API to manage routing for a VoIP.ms DID.",
version="1.0.0",
)
@app.get("/", tags=["General"])
def read_root():
"""A simple root endpoint to confirm the API is running."""
return {"message": f"Welcome to the VoIP.ms API for DID: {settings.target_did}"}
@app.post("/route/sip", status_code=status.HTTP_200_OK, tags=["DID Routing"])
def route_to_sip_account():
"""
Routes the target DID to the pre-configured SIP account.
"""
routing_string = f"sip:{settings.target_sip_account}"
try:
result = update_did_routing(did=settings.target_did, routing=routing_string)
target_phone = routing_string.split(':')[1]
db = Session()
db.add(Call(current_phone=target_phone))
db.commit()
db.close()
return {
"message": f"Successfully routed DID {settings.target_did} to SIP account {settings.target_sip_account}",
"voipms_response": result
}
except HTTPException as e:
# Re-raise the exception to let FastAPI handle the response
raise e
@app.post("/route/cellphone1", status_code=status.HTTP_200_OK, tags=["DID Routing"])
def route_to_cellphone_1():
"""
Routes the target DID to the pre-configured Cellphone #1.
"""
routing_string = f"fwd:{settings.target_cellphone_1}"
try:
result = update_did_routing(did=settings.target_did, routing=routing_string)
target_phone = routing_string.split(':')[1]
db = Session()
db.add(Call(current_phone=target_phone))
db.commit()
db.close()
return {
"message": f"Successfully routed DID {settings.target_did} to Cellphone #1 ({settings.target_cellphone_1})",
"voipms_response": result
}
except HTTPException as e:
raise e
@app.post("/route/cellphone2", status_code=status.HTTP_200_OK, tags=["DID Routing"])
def route_to_cellphone_2():
"""
Routes the target DID to the pre-configured Cellphone #2.
"""
routing_string = f"fwd:{settings.target_cellphone_2}"
try:
result = update_did_routing(did=settings.target_did, routing=routing_string)
target_phone = routing_string.split(':')[1]
db = Session()
db.add(Call(current_phone=target_phone))
db.commit()
db.close()
return {
"message": f"Successfully routed DID {settings.target_did} to Cellphone #2 ({settings.target_cellphone_2})",
"voipms_response": result
}
except HTTPException as e:
raise e

41
app/models.py Normal file
View File

@@ -0,0 +1,41 @@
## File: your_app/models.py
from sqlalchemy import Column, Integer, String, Float, DateTime, Boolean, ForeignKey, Numeric
from .database import Base
import datetime
class Customer(Base):
__tablename__ = "customer_customer"
id = Column(Integer, primary_key=True, index=True)
# --- ADD THIS COLUMN ---
# This stores the master profile ID from Authorize.Net's CIM.
auth_net_profile_id = Column(String(100))
# --- YOUR EXISTING COLUMNS ---
account_number = Column(String(25))
customer_last_name = Column(String(250))
customer_first_name = Column(String(250))
customer_town = Column(String(140))
customer_state = Column(Integer)
customer_zip = Column(String(25))
customer_first_call = Column(DateTime)
customer_email = Column(String(500))
customer_automatic = Column(Integer)
customer_phone_number = Column(String(25))
customer_home_type = Column(Integer)
customer_apt = Column(String(140))
customer_address = Column(String(1000))
company_id = Column(Integer)
customer_latitude = Column(String(250))
customer_longitude = Column(String(250))
correct_address = Column(Boolean)
# --- ADD THIS ENTIRE NEW MODEL ---
class Call(Base):
__tablename__ = "call_call"
id = Column(Integer, primary_key=True, index=True)
current_phone = Column(String(500))
created_at = Column(DateTime, default=datetime.datetime.utcnow)

56
app/voipms_client.py Normal file
View File

@@ -0,0 +1,56 @@
import requests
from fastapi import HTTPException, status
from .config import settings
def update_did_routing(did: str, routing: str):
"""
Calls the VoIP.ms API to update the routing for a specific DID.
Args:
did (str): The phone number (DID) to update.
routing (str): The new routing string (e.g., 'sip:user@server' or 'fwd:15551234567').
Raises:
HTTPException: If the API call fails or returns an error.
Returns:
dict: The JSON response from the VoIP.ms API on success.
"""
params = {
"api_username": settings.voipms_api_username,
"api_password": settings.voipms_api_password,
"method": "setDIDInfo",
"did": did,
"routing": routing,
}
try:
response = requests.get(settings.voipms_api_url, params=params)
response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx)
data = response.json()
# VoIP.ms API has its own status field in the JSON response
if data.get("status") != "success":
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"VoIP.ms API Error: {data.get('status')}"
)
return data
except requests.exceptions.RequestException as e:
# Handle network errors, timeouts, etc.
raise HTTPException(
status_code=status.HTTP_503_SERVICE_UNAVAILABLE,
detail=f"Failed to connect to VoIP.ms API: {e}"
)
except Exception as e:
# Catch any other exceptions, including the ones we raised manually
if isinstance(e, HTTPException):
raise e # Re-raise if it's already an HTTPException
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail=f"An unexpected error occurred: {e}"
)

26
config.py Normal file
View File

@@ -0,0 +1,26 @@
import os
def load_config(mode=os.environ.get('MODE')):
try:
print(f"mode is {mode}")
if mode == 'PRODUCTION':
from settings_prod import ApplicationConfig
return ApplicationConfig
elif mode == 'LOCAL':
from settings_local import ApplicationConfig
return ApplicationConfig
elif mode == 'DEVELOPMENT':
print("poop")
print("poop")
print("poop")
from settings_dev import ApplicationConfig
return ApplicationConfig
else:
pass
except ImportError:
from settings_local import ApplicationConfig
return ApplicationConfig

4
requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
fastapi
uvicorn[standard]
pydantic-settings
requests

35
settings_dev.py Normal file
View File

@@ -0,0 +1,35 @@
class ApplicationConfig:
"""
Basic Configuration for a generic User
"""
CURRENT_SETTINGS = 'DEVELOPMENT'
# databases info
POSTGRES_USERNAME = 'postgres'
POSTGRES_PW = 'password'
POSTGRES_SERVER = '192.168.1.204'
POSTGRES_PORT = '5432'
POSTGRES_DBNAME00 = 'eamco'
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'eamco': SQLALCHEMY_DATABASE_URI}
origins = [
"http://localhost:9000",
"https://localhost:9513",
"http://localhost:9514",
"http://localhost:9512",
"http://localhost:9511",
"http://localhost:5173", # Frontend port
"http://localhost:9516", # Authorize service port
]
# # Authorize.net credentials (Sandbox Test Credentials)
# API_LOGIN_ID = '5KP3u95bQpv'
# TRANSACTION_KEY = '346HZ32z3fP4hTG2'

30
settings_local.py Normal file
View File

@@ -0,0 +1,30 @@
class ApplicationConfig:
"""
Basic Configuration for a generic User
"""
CURRENT_SETTINGS = 'LOCAL'
# databases info
POSTGRES_USERNAME = 'postgres'
POSTGRES_PW = 'password'
POSTGRES_SERVER = '192.168.1.204'
POSTGRES_PORT = '5432'
POSTGRES_DBNAME00 = 'auburnoil'
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
origins = [
"http://192.168.1.204:9000",
"http://192.168.1.204:9613",
"http://192.168.1.204:9614",
"http://192.168.1.204:9612",
"http://192.168.1.204:9611",
]

22
settings_prod.py Normal file
View File

@@ -0,0 +1,22 @@
class ApplicationConfig:
"""
Basic Configuration for a generic User
"""
CURRENT_SETTINGS = 'PRODUCTION'
# databases info
POSTGRES_USERNAME = 'postgres'
POSTGRES_PW = 'password'
POSTGRES_SERVER = '192.168.1.204'
POSTGRES_PORT = '5432'
POSTGRES_DBNAME00 = 'auburnoil'
SQLALCHEMY_DATABASE_URI = "postgresql+psycopg2://{}:{}@{}/{}".format(POSTGRES_USERNAME,
POSTGRES_PW,
POSTGRES_SERVER,
POSTGRES_DBNAME00
)
SQLALCHEMY_BINDS = {'auburnoil': SQLALCHEMY_DATABASE_URI}
origins = [
"https://oil.edwineames.com",
"https://apiauto.edwineames.com",
]