Ajit Panday commited on
Commit
1de8ce6
·
1 Parent(s): 4afad64

Update code to use API-based architecture

Browse files
Files changed (3) hide show
  1. app/auth.py +29 -6
  2. app/models.py +61 -6
  3. main.py +66 -5
app/auth.py CHANGED
@@ -1,5 +1,5 @@
1
- from fastapi import APIRouter, Depends, HTTPException, status
2
- from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
3
  from sqlalchemy.orm import Session
4
  from datetime import datetime, timedelta
5
  from jose import JWTError, jwt
@@ -8,7 +8,8 @@ from typing import Optional
8
  import secrets
9
  import os
10
  from dotenv import load_dotenv
11
- from . import models
 
12
 
13
  # Load environment variables
14
  load_dotenv()
@@ -23,6 +24,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
23
 
24
  # OAuth2 scheme
25
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
 
26
 
27
  router = APIRouter()
28
 
@@ -30,13 +32,32 @@ router = APIRouter()
30
  ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
31
  ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin")
32
 
33
- def verify_password(plain_password, hashed_password):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  return pwd_context.verify(plain_password, hashed_password)
35
 
36
- def get_password_hash(password):
 
37
  return pwd_context.hash(password)
38
 
39
  def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
 
40
  to_encode = data.copy()
41
  if expires_delta:
42
  expire = datetime.utcnow() + expires_delta
@@ -46,7 +67,8 @@ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
46
  encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
47
  return encoded_jwt
48
 
49
- async def get_current_admin(token: str = Depends(oauth2_scheme)):
 
50
  credentials_exception = HTTPException(
51
  status_code=status.HTTP_401_UNAUTHORIZED,
52
  detail="Could not validate credentials",
@@ -59,6 +81,7 @@ async def get_current_admin(token: str = Depends(oauth2_scheme)):
59
  raise credentials_exception
60
  except JWTError:
61
  raise credentials_exception
 
62
  if username != ADMIN_USERNAME:
63
  raise credentials_exception
64
  return username
 
1
+ from fastapi import APIRouter, Depends, HTTPException, status, Security
2
+ from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm, APIKeyHeader
3
  from sqlalchemy.orm import Session
4
  from datetime import datetime, timedelta
5
  from jose import JWTError, jwt
 
8
  import secrets
9
  import os
10
  from dotenv import load_dotenv
11
+ from . import models, settings
12
+ from .database import get_db
13
 
14
  # Load environment variables
15
  load_dotenv()
 
24
 
25
  # OAuth2 scheme
26
  oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
27
+ api_key_header = APIKeyHeader(name="api-key")
28
 
29
  router = APIRouter()
30
 
 
32
  ADMIN_USERNAME = os.getenv("ADMIN_USERNAME", "admin")
33
  ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "admin")
34
 
35
+ def verify_api_key(api_key: str = Security(api_key_header)) -> models.Customer:
36
+ """Verify API key and return customer"""
37
+ db = next(get_db())
38
+ customer = db.query(models.Customer).filter(
39
+ models.Customer.api_key == api_key,
40
+ models.Customer.is_active == True
41
+ ).first()
42
+
43
+ if not customer:
44
+ raise HTTPException(
45
+ status_code=status.HTTP_401_UNAUTHORIZED,
46
+ detail="Invalid API key",
47
+ headers={"WWW-Authenticate": "Bearer"},
48
+ )
49
+ return customer
50
+
51
+ def verify_password(plain_password: str, hashed_password: str):
52
+ """Verify password"""
53
  return pwd_context.verify(plain_password, hashed_password)
54
 
55
+ def get_password_hash(password: str):
56
+ """Hash password"""
57
  return pwd_context.hash(password)
58
 
59
  def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
60
+ """Create JWT token for admin access"""
61
  to_encode = data.copy()
62
  if expires_delta:
63
  expire = datetime.utcnow() + expires_delta
 
67
  encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
68
  return encoded_jwt
69
 
70
+ def get_current_admin(token: str = Depends(oauth2_scheme)):
71
+ """Get current admin user"""
72
  credentials_exception = HTTPException(
73
  status_code=status.HTTP_401_UNAUTHORIZED,
74
  detail="Could not validate credentials",
 
81
  raise credentials_exception
82
  except JWTError:
83
  raise credentials_exception
84
+
85
  if username != ADMIN_USERNAME:
86
  raise credentials_exception
87
  return username
app/models.py CHANGED
@@ -1,10 +1,12 @@
1
- from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, create_engine
2
  from sqlalchemy.ext.declarative import declarative_base
3
  from sqlalchemy.orm import sessionmaker, relationship
4
  from datetime import datetime
5
  import os
6
  from dotenv import load_dotenv
7
  import json
 
 
8
 
9
  # Load environment variables
10
  load_dotenv()
@@ -21,7 +23,7 @@ Base = declarative_base()
21
  class Customer(Base):
22
  __tablename__ = "customers"
23
 
24
- id = Column(Integer, primary_key=True)
25
  name = Column(String(100), nullable=False)
26
  company_name = Column(String(100), nullable=False)
27
  email = Column(String(100), unique=True, nullable=False)
@@ -56,6 +58,44 @@ class Customer(Base):
56
  if engine:
57
  CallRecord.metadata.create_all(engine)
58
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
59
  # Relationship with call records
60
  call_records = relationship("CallRecord", back_populates="customer")
61
 
@@ -63,20 +103,35 @@ class CallRecord(Base):
63
  __tablename__ = "call_records"
64
 
65
  id = Column(String(36), primary_key=True)
66
- customer_id = Column(Integer, ForeignKey("customers.id"), nullable=False)
67
  caller_number = Column(String(20), nullable=False)
68
  called_number = Column(String(20), nullable=False)
69
  file_path = Column(String(255))
70
- transcription = Column(String(10000))
71
- summary = Column(String(1000))
72
  sentiment = Column(String(20))
73
- keywords = Column(String(1000))
74
  created_at = Column(DateTime, default=datetime.utcnow)
75
  updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
76
 
77
  # Relationship with customer
78
  customer = relationship("Customer", back_populates="call_records")
79
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
80
  # Create all tables in the main database
81
  Base.metadata.create_all(engine)
82
 
 
1
+ from sqlalchemy import Column, Integer, String, DateTime, ForeignKey, Boolean, create_engine, Text
2
  from sqlalchemy.ext.declarative import declarative_base
3
  from sqlalchemy.orm import sessionmaker, relationship
4
  from datetime import datetime
5
  import os
6
  from dotenv import load_dotenv
7
  import json
8
+ import requests
9
+ from typing import Optional, List, Dict
10
 
11
  # Load environment variables
12
  load_dotenv()
 
23
  class Customer(Base):
24
  __tablename__ = "customers"
25
 
26
+ id = Column(Integer, primary_key=True, index=True)
27
  name = Column(String(100), nullable=False)
28
  company_name = Column(String(100), nullable=False)
29
  email = Column(String(100), unique=True, nullable=False)
 
58
  if engine:
59
  CallRecord.metadata.create_all(engine)
60
 
61
+ def get_call_records(self, start_date: Optional[str] = None, end_date: Optional[str] = None) -> List[Dict]:
62
+ """Get call records via API"""
63
+ url = f"{settings.API_BASE_URL}/api/v1/calls"
64
+ params = {}
65
+ if start_date:
66
+ params['start_date'] = start_date
67
+ if end_date:
68
+ params['end_date'] = end_date
69
+
70
+ response = requests.get(
71
+ url,
72
+ headers={"api-key": self.api_key},
73
+ params=params
74
+ )
75
+ response.raise_for_status()
76
+ return response.json()
77
+
78
+ def get_call_details(self, call_id: str) -> Dict:
79
+ """Get specific call details via API"""
80
+ url = f"{settings.API_BASE_URL}/api/v1/calls/{call_id}"
81
+ response = requests.get(
82
+ url,
83
+ headers={"api-key": self.api_key}
84
+ )
85
+ response.raise_for_status()
86
+ return response.json()
87
+
88
+ def search_calls(self, query: Dict) -> List[Dict]:
89
+ """Search calls via API"""
90
+ url = f"{settings.API_BASE_URL}/api/v1/calls/search"
91
+ response = requests.get(
92
+ url,
93
+ headers={"api-key": self.api_key},
94
+ json=query
95
+ )
96
+ response.raise_for_status()
97
+ return response.json()
98
+
99
  # Relationship with call records
100
  call_records = relationship("CallRecord", back_populates="customer")
101
 
 
103
  __tablename__ = "call_records"
104
 
105
  id = Column(String(36), primary_key=True)
106
+ customer_id = Column(Integer, ForeignKey("customers.id"))
107
  caller_number = Column(String(20), nullable=False)
108
  called_number = Column(String(20), nullable=False)
109
  file_path = Column(String(255))
110
+ transcription = Column(Text)
111
+ summary = Column(Text)
112
  sentiment = Column(String(20))
113
+ keywords = Column(Text)
114
  created_at = Column(DateTime, default=datetime.utcnow)
115
  updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
116
 
117
  # Relationship with customer
118
  customer = relationship("Customer", back_populates="call_records")
119
 
120
+ @classmethod
121
+ def create_from_api(cls, customer_id: int, data: Dict) -> 'CallRecord':
122
+ """Create a call record from API data"""
123
+ return cls(
124
+ id=data['id'],
125
+ customer_id=customer_id,
126
+ caller_number=data['caller_number'],
127
+ called_number=data['called_number'],
128
+ file_path=data.get('file_path'),
129
+ transcription=data.get('transcription'),
130
+ summary=data.get('summary'),
131
+ sentiment=data.get('sentiment'),
132
+ keywords=data.get('keywords')
133
+ )
134
+
135
  # Create all tables in the main database
136
  Base.metadata.create_all(engine)
137
 
main.py CHANGED
@@ -1,10 +1,10 @@
1
- from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Header
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import JSONResponse
4
  from datetime import datetime
5
  import os
6
  from dotenv import load_dotenv
7
- from typing import Optional
8
  import uuid
9
  import tempfile
10
  from transformers import pipeline
@@ -12,6 +12,7 @@ from sqlalchemy.orm import Session
12
  import asyncio
13
  from app import models, auth
14
  from sqlalchemy.orm import sessionmaker
 
15
 
16
  # Load environment variables
17
  load_dotenv()
@@ -55,9 +56,9 @@ async def get_customer_by_api_key(api_key: str = Header(...), db: Session = Depe
55
  @app.post("/api/v1/process-call")
56
  async def process_call(
57
  file: UploadFile = File(...),
58
- caller_number: str = Header(...),
59
- called_number: str = Header(...),
60
- customer: models.Customer = Depends(get_customer_by_api_key),
61
  db: Session = Depends(models.get_db)
62
  ):
63
  """
@@ -159,6 +160,66 @@ async def list_calls(
159
  finally:
160
  customer_db.close()
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  if __name__ == "__main__":
163
  import uvicorn
164
  host = os.getenv("HOST", "0.0.0.0")
 
1
+ from fastapi import FastAPI, File, UploadFile, HTTPException, Depends, Header, Form
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from fastapi.responses import JSONResponse
4
  from datetime import datetime
5
  import os
6
  from dotenv import load_dotenv
7
+ from typing import Optional, List
8
  import uuid
9
  import tempfile
10
  from transformers import pipeline
 
12
  import asyncio
13
  from app import models, auth
14
  from sqlalchemy.orm import sessionmaker
15
+ from fastapi.security import OAuth2PasswordRequestForm
16
 
17
  # Load environment variables
18
  load_dotenv()
 
56
  @app.post("/api/v1/process-call")
57
  async def process_call(
58
  file: UploadFile = File(...),
59
+ caller_number: str = Form(...),
60
+ called_number: str = Form(...),
61
+ customer: models.Customer = Depends(auth.verify_api_key),
62
  db: Session = Depends(models.get_db)
63
  ):
64
  """
 
160
  finally:
161
  customer_db.close()
162
 
163
+ @app.post("/api/v1/token")
164
+ async def login_for_access_token(form_data: OAuth2PasswordRequestForm = Depends()):
165
+ """Admin login endpoint"""
166
+ if form_data.username != settings.ADMIN_USERNAME or form_data.password != settings.ADMIN_PASSWORD:
167
+ raise HTTPException(
168
+ status_code=status.HTTP_401_UNAUTHORIZED,
169
+ detail="Incorrect username or password",
170
+ headers={"WWW-Authenticate": "Bearer"},
171
+ )
172
+ access_token = auth.create_access_token(data={"sub": form_data.username})
173
+ return {"access_token": access_token, "token_type": "bearer"}
174
+
175
+ @app.post("/api/v1/customers/", response_model=models.Customer)
176
+ async def create_customer(
177
+ customer: models.CustomerCreate,
178
+ db: Session = Depends(models.get_db),
179
+ current_admin: str = Depends(auth.get_current_admin)
180
+ ):
181
+ """Create new customer"""
182
+ db_customer = models.Customer(
183
+ name=customer.name,
184
+ company_name=customer.company_name,
185
+ email=customer.email,
186
+ api_key=str(uuid.uuid4())
187
+ )
188
+ db.add(db_customer)
189
+ db.commit()
190
+ db.refresh(db_customer)
191
+ return db_customer
192
+
193
+ @app.get("/api/v1/calls", response_model=List[models.CallRecord])
194
+ async def get_calls(
195
+ start_date: Optional[str] = None,
196
+ end_date: Optional[str] = None,
197
+ customer: models.Customer = Depends(auth.verify_api_key)
198
+ ):
199
+ """Get customer's call records"""
200
+ return customer.get_call_records(start_date, end_date)
201
+
202
+ @app.get("/api/v1/calls/{call_id}", response_model=models.CallRecord)
203
+ async def get_call(
204
+ call_id: str,
205
+ customer: models.Customer = Depends(auth.verify_api_key)
206
+ ):
207
+ """Get specific call details"""
208
+ return customer.get_call_details(call_id)
209
+
210
+ @app.get("/api/v1/calls/search")
211
+ async def search_calls(
212
+ query: dict,
213
+ customer: models.Customer = Depends(auth.verify_api_key)
214
+ ):
215
+ """Search calls"""
216
+ return customer.search_calls(query)
217
+
218
+ @app.get("/api/v1/health")
219
+ async def health_check():
220
+ """Health check endpoint"""
221
+ return {"status": "healthy"}
222
+
223
  if __name__ == "__main__":
224
  import uvicorn
225
  host = os.getenv("HOST", "0.0.0.0")