From f6e22bb9756faeaa2ea936fc7b0b199fbd5a9390 Mon Sep 17 00:00:00 2001 From: Edwin Eames Date: Tue, 23 Sep 2025 22:40:16 -0400 Subject: [PATCH] working api compltely --- .env | 10 ++ .gitignore | 0 Dockerfile.dev | 4 +- __pycache__/config.cpython-39.pyc | Bin 0 -> 603 bytes __pycache__/settings_dev.cpython-313.pyc | Bin 0 -> 1987 bytes __pycache__/settings_dev.cpython-39.pyc | Bin 0 -> 1342 bytes app/__pycache__/__init__.cpython-313.pyc | Bin 0 -> 145 bytes app/__pycache__/__init__.cpython-39.pyc | Bin 0 -> 111 bytes app/__pycache__/database.cpython-39.pyc | Bin 0 -> 898 bytes app/__pycache__/main.cpython-313.pyc | Bin 0 -> 3902 bytes app/__pycache__/main.cpython-39.pyc | Bin 0 -> 3939 bytes app/__pycache__/models.cpython-39.pyc | Bin 0 -> 1470 bytes app/__pycache__/voipms_client.cpython-39.pyc | Bin 0 -> 3879 bytes app/config.py | 22 ---- app/main.py | 72 +++++++++--- app/voipms_client.py | 117 +++++++++++++++++-- config.py | 2 +- requirements.txt | 5 +- settings_dev.py | 23 ++++ settings_local.py | 23 +++- settings_prod.py | 24 +++- 21 files changed, 249 insertions(+), 53 deletions(-) create mode 100644 .env create mode 100644 .gitignore create mode 100644 __pycache__/config.cpython-39.pyc create mode 100644 __pycache__/settings_dev.cpython-313.pyc create mode 100644 __pycache__/settings_dev.cpython-39.pyc create mode 100644 app/__pycache__/__init__.cpython-313.pyc create mode 100644 app/__pycache__/__init__.cpython-39.pyc create mode 100644 app/__pycache__/database.cpython-39.pyc create mode 100644 app/__pycache__/main.cpython-313.pyc create mode 100644 app/__pycache__/main.cpython-39.pyc create mode 100644 app/__pycache__/models.cpython-39.pyc create mode 100644 app/__pycache__/voipms_client.cpython-39.pyc delete mode 100644 app/config.py diff --git a/.env b/.env new file mode 100644 index 0000000..2722a93 --- /dev/null +++ b/.env @@ -0,0 +1,10 @@ +# --- VoIP.ms API Credentials --- +# Find these in your VoIP.ms customer portal under Main Menu > SOAP and REST/JSON API +VOIPMS_API_USERNAME="eddwinn@gmail.com" +VOIPMS_API_PASSWORD="!Gofionago123catdog" + +# --- Target DID and Routing Destinations --- +TARGET_DID="5084268800" +TARGET_SIP_ACCOUNT="407323_auburnoil@washington2.voip.ms" # e.g., 123456_myhome@newyork1.voip.ms +TARGET_CELLPHONE_1="7743342638" # Use E.164 format (country code + number) +TARGET_CELLPHONE_2="9143306100" # Use E.164 format \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile.dev b/Dockerfile.dev index 508ae4b..34e3f1f 100644 --- a/Dockerfile.dev +++ b/Dockerfile.dev @@ -6,9 +6,11 @@ ENV PYTHONUNBUFFERED=1 ENV MODE="DEVELOPMENT" WORKDIR /app +ENV PYTHONPATH=/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"] \ No newline at end of file +CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/__pycache__/config.cpython-39.pyc b/__pycache__/config.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cf11b1ae3d134017b3c4f594864791bfd5647085 GIT binary patch literal 603 zcmZ8fy-ve05WaJg5@?IWz+NGiFccmDAyAv4QfUysB1;w3p#;*{azX_NCU^=E5{x_w zGn-c?UV({o_)+1kyZG~WcfLC(s@G?Lq~CcxeqjJ_226@YV4XB?km0~Fg%Dym!AHi~ z6CNSAxP6PE#c>Bb=bgNu7uuUa+aI6--$J>;C2f)Bk7O>-3(9r{_wc}O5a9|qJ7sFg zZ-YV%jBR{bMS`j49KU$?^YNM%a@P2aqk;P&gNN2xk5DsK6oJw?chV zFK+MB9SI&clDrYlY;6baz1D6g=qb~dhk9;9$}|~7MIy46$c~a>=yZeDMpswc{=VN0 zwl@9VZfHv(q*u|7R7qBZ^*k<$Bpc>^sRUPMEaB|$St@()z+h3ekW<20$YtFdzmP zkqI!!3{1!Z%*YBXXarc14UBLzm&5e*qPb`(T8p-5e>=zytVMf>Mvfo_Y_Di^r#Qy> zuJgE0u47;{O8!Xyknu#I~Ud z$c9G~>?WJei5?Kib_YYR~55C+#Ad278aP4ZELeQ#Q;;Xx4ftBS@DAbtNVf88= z4L`lfJsMuT8IIfxhi`zR;m`gf_7a}l%%$=f0Au+S+yE?>jb+#?U_J)GR*p+xTOr1+ zu?3h&C9t!vgH#^I;_=)DN#FpBv*~m`nai>;d;=E2cP_8Ur5p2uWbDbJ5~!&`UJ-+P z;UGB*EuicwKatk=U@L?YUlSn2HVA8Sxn9+EJB07*e6?>GgK$Sgs#X;xL6V8t0?B1j zBFcDtgX734L%}WZfvOPO@ z>N)Y8dQZH~htFHHpEsH5|BI1$)9&N#qwV82M{k;+E;g68TCT6Y&7!~{1AY_I-wSN+ zJ!i-`FktH-Yu!}$7(w0D`n*^8FHnR`^>;;mOvIUyrsDE$f^KNpa|b5ywDc&h;$<$k%~7Qk_c~cjob(DixXoo283N%Oa zRHi$(5-Jjz7F`~drX1P%n@453`}^Lz3Mt(3Xg|Y6z|{GRY<9p>}z4@xsDyXf!_`M*Y0%LF4Uo&*vxh6chn+U+3T-^sBYJ%)Ta^H zCX7a_si@e~FZ994IM;_`q=W&Brx9g|655RY5kzEJR$q3S>WiP6YO~WmK+@sC^A5^U zd+r3wXY2WIydOgL)(OIC=cv`Xx<0zPKEApJ(2$ppW^o(>>jjSO%^<+h%XYiHe4Ntk zA%Cbo-!}bFJ=jinw$ths`cVDxD{H?%wTn^TIJdB7^zp>f&7pR#KOhawvR;kM9x9AA z^Hd+>UcZN`8MgWc*1Fx%1U7KZb@jo(I2#RhtZv~tXm>NBIaoejZ^mWdhRr!^*`eFI zR9jGrxEapEVdL9G!PvLw6yv0T@jM_4FU2K{-z;n|v&a~Zwu3RMbSI_>CB>FL9`}c* z7TPyP*7(%atuHAS?F2-Uo;KD_G)u=5vyUnphLirVXC-nFxl`9?i4d?vhCAp+fuD#|8lzg8`;}kSN#md>V3=r literal 0 HcmV?d00001 diff --git a/app/__pycache__/__init__.cpython-313.pyc b/app/__pycache__/__init__.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b1ec948ffa6c64259666e05ac1416de5e7061b5b GIT binary patch literal 145 zcmey&%ge<81nXa3%mC4kK?DpiLK&Y~fQ+dO=?t2Tek&P@n1H;`AgNmh`nh=}`pNkz zsrvbuIr^!IxyktuGQKQ7vmm!vKe3=dKR!M)FS8^*Uaz3?7Kcr4eoARhs$CH)&;XDH Q#UREg`kg7q&iW`O9&AOaaM0yz#qT+9L_QW%06G#UL?G8BP?5yUSM{ltO-FpiJU c%*!l^kJl@xyv1RYo1apelWGT2{TYZE0Mp+UT>t<8 literal 0 HcmV?d00001 diff --git a/app/__pycache__/database.cpython-39.pyc b/app/__pycache__/database.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fab5932e0ce6a1e6fdf9892769a4446261975a85 GIT binary patch literal 898 zcmYjPOLNmO5SDC5ahyk+wBMqGZ2AcOcE;)n!FUN`et_bLV~G@| z7(47qPx;g@u`dHvp_LK`lBg=JmbfBARiibGL~Vv>B&z3iPDFIx0BM4>K-NIkdG(6W zw!or2!?YtRJmjJ1@YC2Lo{PcBFXWSQJA(dh8Oq%m@?g;n33?fcLu(X#~A9 z8Bc_|&>nE~(SKrFd?~h9MyuieAlh1S$h6XdBik$Fd@Z^Hye0EVYvPRdZl22|hBamB ztN8}Ve3iC+crZA6+wTpA$AeyffA@XQ*6*mpQ@eI&z~_@*edsEf(CnTf1wfubHk$1;zBVg%fXZrT~Qqu9X?kXpGxt594VKR>sfAt(6OZ7A{}26(@8$pi literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-313.pyc b/app/__pycache__/main.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..7ae76e4e72a5666c763b89929740995752f57db6 GIT binary patch literal 3902 zcmeHK&2QUA79WzLJ}AkSKN2aMv|$@(YsaZ%D~+?Z>tJm+X>6lOD%wc{XDhJM*daiX z!jMY0!eZf`*1-nY?xD9HdaQvSTA+s>dI?@?l{O_Qbz zv^{kIzImK^^X9!b@5eXXOC}Wp?VlGuzws?_|HhY~g*(h%K_KK^q7hA4B8vhO7DXm5 zN=#CTCTh}BXi;V|@F7iJ3NJ=j#OK3H(Zv{x`FvzaS&Xx|O0Emf5-qC5G)0R~N-S|b zQ8*lIx8G|CEtwOvo=H*b{T(^onP5F2eRN+c;I!0rsh|dL`@Pl&I{GIiZJ-(z$P zK0(KUE*<@k(Q)tzI?`P_2G%nphwh`XBNC6C*By8E@&Zp@SYEz-?$0G!bq(9%VaL_o znsZ-7F@Z-N>bi!t?%ap-z=8f+wXD0eST@Qw(E7@h5vkfyx z^-9TZ)8b9rs8*buUajUTx?yFjZ}AwTI-ZN|vJ9;5g~xds$OdVJN%Ej~Xy&sU&9kpI z&c5o+6uh+N#h1O%GCK$&btQ)fL355#t;fMEwa__Uk;mIfQA9IX9|-?XcoDOHc>mJI ztnlRETI6Z6D#JRzb;$?#52m1B0mM&=masw!v993`t4XIZGofiARA`B-asL%afMQEp zkqXIRbiZH4`1ox_)Z|Ga(4-QOM>ij8fj)nJ;Gs!)-JjROKkicx(IQ7bGeQ!EObZwWkEsxd|_w^+SlTl7Ij ztCdQSWWhr)-f?r)pacy*LtYCw_k?@xg*(#~_A> z&CqWyJ5|$g8CDH0>E$xR6Xs!nRmE_*TrzEkvJi}I$-JjychPYfAd!b*ubS!dL}v%z zb}p9R-~x3pVN`4^jT#4WEiJlsvD=OuOdWv7`4|WQNg?qQ(yKS`!L!Zo^A|J-+8^!x4aqNNgr>fM;qzU59U8lkA2kFNT1qFe3=~l`?XDR zC#w7-d@Fq0eg9;mH+MHWh6Ax%vDAm9Gzte=^+6pKIjLdE@83^H=U(EjF*R z##QEBalEv|A~^sp*An zO-tJ^h;tLt_Jj<4y|07$5;aXfGUJa%_FsQPHiQw9h5Q`I)}p6mz;^6Mv8_CW?Rb6q z|HAfsaHQ(7@vQP7wDx9siX8?Y7#0K{(+qDm>=_{SeuQlI5_OmhBkWn2`vFM5PnP2N zK1kVd)P)dbzej=)WzQiQMuO;MBS0RHxo)*DfaEAVd!GP#Eanow0_I-3drfa%a~s!O z@3op&yXmFhd?f4yIknSzwAL;n7B)aBJ-md6!Oz+L{A!CjtB!sdTO1Mn)tCV*TV8RU^Q_*c-Y2E$uk z8_HNYc_R2ZD3exB|CG(`grR32v`kf;+E& zU*(~y?YKOSza+){*jVw>62tP#@O$7J$FLIn_tkdEj=l)mY9>9)=3xSFg3cE}T9P0L zUy*!+_$QnaYJgQpkn?l{dRhD;GKncrZ>`_ zaCBD`g%x2>BBI*zS*ZCSlzBU|dGh`9&7q0L(8Q;ePm3EFPk3cl>=#lfdr@d1+d9#a o^^~VRm}rj9G)8Cc{OQgm6kgsH``W@QAPj74_@@c`>wLHW3tIjcDgXcg literal 0 HcmV?d00001 diff --git a/app/__pycache__/main.cpython-39.pyc b/app/__pycache__/main.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9d04cd2b89e5f5ec0d4e3c41932e2bd4fd6418f7 GIT binary patch literal 3939 zcmeHKOLH5?5#HGs-XH{#vYz%vPNl>+<|}d@ilbPetcaC_Qi)QO>}6|(8B&W{V3FB@ zWTF5atX%0s4!KyaqK^6tt{ig6Dc9Wgnv?$mD{b{G*@6w6BxA{A|?~?BGPV{|?U)FtZbGBnF zzxx$gZ+m2}tSqyvoC>9){$9FlWu;qTs&3rf${M%t-@p6uCmk_RQPR)cRE27oeg*p) z%VyRd4tS_Uz#|^WWJucC!k$pUZX%zClIz)6R@xD13Uw@EZ-#L^I{WdBUvJ*=ev@q7 zUFoIXX3|$OiDMy0@7?HoSl3gD*9-gMp74r!Jxu9^GnJcLH&;f^`pVkM8n)}GNW~(n zaglaply9~4L`Yh6bZS+JRISc!sYAz}J|;d0R-1U!Z4@I<844ib?0`)SZqoPX#uw%o z0C{0PG^_sB;35;xSzhZuU@Hpv$2Ts z@(d)SN^U`T`Gn;x^vx{yH#C&dWq4sZlomH%*b`%PUD;g+ZI|qKU{07SaeK_VPurP$yl}^33fC^*G^~1rq_} z?jV$5FCDe-!_wY8@%tgw5AOZynm1Y~WVv4U&dM_1Vwzbvv)`PbM8=sj zkWpV{rD9ZO4I#9U1^Y+ zQ0PD>ymN3rL6(3N5g%Hk4{msA)EmS^JBdPY@L7tYy@WY1HNg=VNI_2jVM2gR*O znr-y$ha&DIJwbzM(3}f(d;?45O6@3l0UG(CDN5&#Uy&jtGELNb=twEx4NESg9936| z3&m<04MsK0=H1IF7x1X%yc~2AF60#qK5=pvf?1%xpsD(F?n4AB-GAo{(3~~a!H4j) z(1!r!Hi8wTH?frS+?d#7`_NUTvBjMOXfXrr@=HV46{-=I(EHrrrNXB6*yQCIVOID5 zgiv%eVLAn2Rwm9f2ZX6>O_&vUp*s3`)x$%l6R9?3j#ZD&To`vC#rp`C-Dqzp5e7S3 zcfGLFNrrv3qLq)>N0Kh7QMnLbdu7VA5cs9yLIEo`IzuTzz=3Abqq951P6q_MJB;If zeeYmqT65H*W#`uLh0V1T4+#A(lzo>Ag*)NM-hIli$?riV-=|^) z#i&hCoL|SMkJT{NB1bK>7E>S_)k`9WjP1#&&}Dio39~MuF+s} ztR$?+4HU0R@`tGW#_=hDP5FK5CqT-pRD3|in+W|Fh%+b!>$fpFC89C_#5#bext&$N zc5MQsI|Jq76qMI7_CG=a9i z&EdZ0xV?axydNY)=7-M=&{KpRente>&Dtz zaOanjC|ObnNIKD+v9#HA;#B?+lMiEYo$kGq0vDvtJXS}!2#T*GuQ!oF;rB1sDYI$? RZ>!0w4l=1TcrUcszX7Ae+hhO$ literal 0 HcmV?d00001 diff --git a/app/__pycache__/models.cpython-39.pyc b/app/__pycache__/models.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..daaed7350ff7f111424ef85c265a4aebd5c7b701 GIT binary patch literal 1470 zcma)6y>HYo6nAp@x?Jx1MJbL6LWI6srT+w=Jb~4`0>KdT z1d<;1q>p_$zylfJK!!M!5sqYxW0~N@L(FFbF_bAz0V5WO5gt85EM(CK#3G)a3~&Z| z%o5NOr;l0i5>1C^5Cu&W6YW%@m8FTFmzp1OWuli_73Gl$cSS|DNuE;8_Y28Qyj@j- z)6%57mEy%w`GU_)yw^&uiu}y8?mQE0(}wTm)_MPd19yk}27n{%F@$~Q;QLGgywnGmYS5UJbm~?{5p5_Gf&Vy)y7}2Y&LmV3@|pQ0qo_(j{CQ=+6wMaRPT! z*%Y^q3Db|^k6=4vm>y-i8TCwt*=`of%wcYn@8(B^Zh>gL$Wl)Uvsn6x*)8!Ryl0;3 zEX#86US?3j@=#x3#V4t5g^?|-R{jg*qO}k8dc8)%jO4!GcOS}{9v0u+-rj7!*W+Wq z@42BReaD|jXk@TH>k)K|uN2&IBr{)nk?E1#QgtAmXnDL>0 zqzC#l>sTWi+|$R}kr5crOlHD(Q|}mJ&(HYTL5^8T>BkyN2L{+oKt9$!u~_y){V@rQ zx3n$I)F5_#P&oJl(T+@_9a808WVyhEwlIEd`$LCgdSc|@wAORVWlcPk4fTWpV1DTh>5E!}ZbOvup67DvPDXEYL2p2S8U}lJ z!o@-C-f{Yp&jdKPat+$rRoP$`J;;sjI(8|ibUmB?AQaS1MxRiA@qHYex4OzEf)HO3KIOD!OkzBaWh(~W@N6JT@ zV4*eQ{+`DKZm8!Zw8oBbMpBsY8m8l*C-bwj+QhUQ^l+|DHzqK&BsD0i)@g^sEJ>e6 zaT@fw9_FKMZ1+6ILyK`A=Hld)4X~WywDvsLLt$ywQ4GV1z*V(jtJqP_p*fJA zD-7r*nvw-vXM1I7$$n#P-R?Ah@u0c29p-p)W@T83XW;A@Ur|1*OUw3Fv$NB>+q56F z*LK!g_txIH*9@~~4Rd|jccEjq$HS|!(~{k4Z#O&bwR_6D*|D3Q&izg;rB+NJ140;8 zv^zqRIldDT{#+B^gPM~|KXS%n^@~T^8}_;D*T)B80VcqPZExez$UOdkr($i8gsx zzr9+s1a5DUMuUPQI-1wuDKy63Sm5b8EGA@p(76YwOfW$=6>y>Cr5X4EMxztmpo%*p zk6{a7PO9|Pi`*4OtgT?}Jk%x~B?xYdIjE{FC|HUrww*@aLV^|#PW87MB{>t!_GMso$t%V@5m`3@ME5&WJAoC1X?LGkIkXkI~saEi?L z;VG;}yG!9H-dHP8F7JTR$}y3UN+(tpsZ<3fDKhC1n-)&3l%+>(Dgm1+Q#O@O#j6kl zBve6t)9vV+eojlBW7Ez#Hi42dKuLW%rBbY8lbBQ-ltO69P9{xkAlMV+s@O>_0Y2=AVj$nuj_%uO{j6_a+9A1d5*|k)asVSs1X2ilfkss6E#+X zPwmFELe`9nyikC6`DdV2XPQmVaRiLZU#|WNM2H)(;}sLS`Lleas6+AL&9;Z%g(OLw z@dGHBxJ-R6&Iwd2yHxxn6!y~N5Dr&`U-`}=3?muX+zYr)f zOEB>Z80qHD2Ju3qlu_QCwUK)eL#~6-@`@*4SgD94)ftV094U|j(4`RT9Mn7u>N(;H zDj{IX62z&Ic5N;+' result = update_did_routing(did=settings.target_did, routing=routing_string) - target_phone = routing_string.split(':')[1] + target_phone = settings.target_cellphone_2 db = Session() db.add(Call(current_phone=target_phone)) db.commit() @@ -79,4 +123,4 @@ def route_to_cellphone_2(): "voipms_response": result } except HTTPException as e: - raise e + raise e \ No newline at end of file diff --git a/app/voipms_client.py b/app/voipms_client.py index 446cf4f..3b38f71 100644 --- a/app/voipms_client.py +++ b/app/voipms_client.py @@ -1,7 +1,53 @@ +import sys +import os +sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..')) +from settings_dev import settings + import requests from fastapi import HTTPException, status -from .config import settings +def get_did_info(did: str): + """ + Calls the VoIP.ms API to get DID information. + + Args: + did (str): The phone number (DID) to query. + + 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": "getDIDInfo", + "did": did, + } + + try: + response = requests.get(settings.voipms_api_url, params=params) + response.raise_for_status() + data = response.json() + if data.get("status") != "success": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"VoIP.ms API Error: {data.get('status')}. Full response: {data}. Request params: {params}" + ) + return data + except requests.exceptions.RequestException as e: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=f"Failed to connect to VoIP.ms API: {e}" + ) + except Exception as e: + if isinstance(e, HTTPException): + raise e + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e}" + ) def update_did_routing(did: str, routing: str): """ @@ -20,37 +66,84 @@ def update_did_routing(did: str, routing: str): params = { "api_username": settings.voipms_api_username, "api_password": settings.voipms_api_password, - "method": "setDIDInfo", + "method": "setDIDRouting", # Correct method for routing updates "did": did, - "routing": routing, + "routing": routing, # e.g., 'fwd:7743342638' + "pop": "75", # Match the DID's current POP (from getDIDsInfo) } try: response = requests.get(settings.voipms_api_url, params=params) - response.raise_for_status() # Raises an HTTPError for bad responses (4xx or 5xx) - + print(f"Request URL: {response.request.url}") # Debug + print(f"Request Params: {params}") # Debug + response.raise_for_status() data = response.json() + print(f"VoIP.ms API Response: {data}") # Debug - # 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')}" + detail=f"VoIP.ms API Error: {data.get('status')} - Full response: {data}" ) 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}" + detail=f"Failed to connect to VoIP.ms API: {e} - Request params: {params}" ) 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 e raise HTTPException( status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"An unexpected error occurred: {e}" + detail=f"An unexpected error occurred: {e} - Request params: {params}" + ) + +def get_forwardings(phone_number: str = None): + """ + Retrieves call forwarding entries from VoIP.ms. + + Args: + phone_number (str, optional): Filter by phone number (e.g., '7743342638'). + + Returns: + dict: The JSON response from the VoIP.ms API, including forwarding IDs. + """ + params = { + "api_username": settings.voipms_api_username, + "api_password": settings.voipms_api_password, + "method": "getForwardings", + } + try: + response = requests.get(settings.voipms_api_url, params=params) + print(f"Get Forwardings Request URL: {response.request.url}") + print(f"Get Forwardings Response: {response.json()}") + response.raise_for_status() + data = response.json() + if data.get("status") != "success": + raise HTTPException( + status_code=status.HTTP_400_BAD_REQUEST, + detail=f"VoIP.ms API Error: {data.get('status')} - Full response: {data}" + ) + # Filter by phone_number if provided + if phone_number and "forwardings" in data: + for forwarding in data["forwardings"]: + if forwarding.get("phone_number") == phone_number: + return forwarding + raise HTTPException( + status_code=status.HTTP_404_NOT_FOUND, + detail=f"No forwarding entry found for phone number {phone_number}" + ) + return data + except requests.exceptions.RequestException as e: + raise HTTPException( + status_code=status.HTTP_503_SERVICE_UNAVAILABLE, + detail=f"Failed to connect to VoIP.ms API: {e} - Request params: {params}" + ) + except Exception as e: + raise HTTPException( + status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, + detail=f"An unexpected error occurred: {e} - Request params: {params}" ) \ No newline at end of file diff --git a/config.py b/config.py index ad92505..4f22694 100644 --- a/config.py +++ b/config.py @@ -22,5 +22,5 @@ def load_config(mode=os.environ.get('MODE')): pass except ImportError: - from settings_local import ApplicationConfig + from settings_dev import ApplicationConfig return ApplicationConfig \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c58e901..96ffb4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,7 @@ fastapi uvicorn[standard] pydantic-settings -requests \ No newline at end of file +python-dotenv +requests +sqlalchemy +psycopg2-binary \ No newline at end of file diff --git a/settings_dev.py b/settings_dev.py index f9cbf65..9bab9fe 100644 --- a/settings_dev.py +++ b/settings_dev.py @@ -1,5 +1,11 @@ +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + class ApplicationConfig: """ Basic Configuration for a generic User @@ -33,3 +39,20 @@ class ApplicationConfig: # # Authorize.net credentials (Sandbox Test Credentials) # API_LOGIN_ID = '5KP3u95bQpv' # TRANSACTION_KEY = '346HZ32z3fP4hTG2' + + # VoIP.ms Credentials and Settings + voipms_api_username = os.environ.get('VOIPMS_API_USERNAME') + voipms_api_password = os.environ.get('VOIPMS_API_PASSWORD') + + # Target DID and Destinations + target_did = os.environ.get('TARGET_DID') + target_sip_account = os.environ.get('TARGET_SIP_ACCOUNT') + target_cellphone_1 = os.environ.get('TARGET_CELLPHONE_1') + target_cellphone_2 = os.environ.get('TARGET_CELLPHONE_2') + + # VoIP.ms API endpoint + voipms_api_url = os.environ.get('VOIPMS_API_URL', "https://voip.ms/api/v1/rest.php") + + +# Create a single instance of the settings to be used throughout the app +settings = ApplicationConfig() diff --git a/settings_local.py b/settings_local.py index e249762..a256824 100644 --- a/settings_local.py +++ b/settings_local.py @@ -1,5 +1,11 @@ +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + class ApplicationConfig: """ Basic Configuration for a generic User @@ -26,5 +32,20 @@ class ApplicationConfig: "http://192.168.1.204:9614", "http://192.168.1.204:9612", "http://192.168.1.204:9611", -] + ] + # VoIP.ms Credentials and Settings + voipms_api_username = os.environ.get('VOIPMS_API_USERNAME') + voipms_api_password = os.environ.get('VOIPMS_API_PASSWORD') + + # Target DID and Destinations + target_did = os.environ.get('TARGET_DID') + target_sip_account = os.environ.get('TARGET_SIP_ACCOUNT') + target_cellphone_1 = os.environ.get('TARGET_CELLPHONE_1') + target_cellphone_2 = os.environ.get('TARGET_CELLPHONE_2') + + # VoIP.ms API endpoint + voipms_api_url = os.environ.get('VOIPMS_API_URL', "https://voip.ms/api/v1/rest.php") + +# Create a single instance of the settings to be used throughout the app +settings = ApplicationConfig() diff --git a/settings_prod.py b/settings_prod.py index f2dbbc8..cec7fe6 100644 --- a/settings_prod.py +++ b/settings_prod.py @@ -1,3 +1,10 @@ + +import os +from dotenv import load_dotenv + +# Load environment variables from .env file +load_dotenv() + class ApplicationConfig: """ Basic Configuration for a generic User @@ -19,4 +26,19 @@ class ApplicationConfig: origins = [ "https://oil.edwineames.com", "https://apiauto.edwineames.com", -] + ] + # VoIP.ms Credentials and Settings + voipms_api_username = os.environ.get('VOIPMS_API_USERNAME') + voipms_api_password = os.environ.get('VOIPMS_API_PASSWORD') + + # Target DID and Destinations + target_did = os.environ.get('TARGET_DID') + target_sip_account = os.environ.get('TARGET_SIP_ACCOUNT') + target_cellphone_1 = os.environ.get('TARGET_CELLPHONE_1') + target_cellphone_2 = os.environ.get('TARGET_CELLPHONE_2') + + # VoIP.ms API endpoint + voipms_api_url = os.environ.get('VOIPMS_API_URL', "https://voip.ms/api/v1/rest.php") + +# Create a single instance of the settings to be used throughout the app +settings = ApplicationConfig()