jebin2 commited on
Commit
c4fd9c8
·
1 Parent(s): 2e708ff
app.py CHANGED
@@ -12,6 +12,7 @@ from fastapi.responses import JSONResponse
12
 
13
  from database import init_db
14
  from routers import auth, blink, general
 
15
 
16
  # Configure logging
17
  logging.basicConfig(
@@ -20,6 +21,8 @@ logging.basicConfig(
20
  )
21
  logger = logging.getLogger(__name__)
22
 
 
 
23
 
24
  @asynccontextmanager
25
  async def lifespan(app: FastAPI):
@@ -28,9 +31,18 @@ async def lifespan(app: FastAPI):
28
  Initializes database on startup.
29
  """
30
  logger.info("Starting up - initializing database...")
 
 
 
 
 
31
  await init_db()
32
  logger.info("Database initialized successfully")
33
  yield
 
 
 
 
34
  logger.info("Shutting down...")
35
 
36
 
 
12
 
13
  from database import init_db
14
  from routers import auth, blink, general
15
+ from drive_service import DriveService
16
 
17
  # Configure logging
18
  logging.basicConfig(
 
21
  )
22
  logger = logging.getLogger(__name__)
23
 
24
+ # Initialize Drive Service
25
+ drive_service = DriveService()
26
 
27
  @asynccontextmanager
28
  async def lifespan(app: FastAPI):
 
31
  Initializes database on startup.
32
  """
33
  logger.info("Starting up - initializing database...")
34
+
35
+ # Startup: Download DB from Drive
36
+ logger.info("Startup: Attempting to download database from Google Drive...")
37
+ drive_service.download_db()
38
+
39
  await init_db()
40
  logger.info("Database initialized successfully")
41
  yield
42
+
43
+ # Shutdown: Upload DB to Drive
44
+ logger.info("Shutdown: Uploading database to Google Drive...")
45
+ drive_service.upload_db()
46
  logger.info("Shutting down...")
47
 
48
 
drive_service.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import logging
3
+ import io
4
+ from dotenv import load_dotenv
5
+
6
+ load_dotenv()
7
+
8
+ from google.auth.transport.requests import Request
9
+ from google.oauth2.credentials import Credentials
10
+ from googleapiclient.discovery import build
11
+ from googleapiclient.http import MediaFileUpload, MediaIoBaseDownload
12
+ from googleapiclient.errors import HttpError
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ class DriveService:
17
+ SCOPES = [
18
+ 'https://www.googleapis.com/auth/gmail.send',
19
+ 'https://www.googleapis.com/auth/drive.file'
20
+ ]
21
+ FOLDER_NAME = "apigateway"
22
+ DB_FILENAME = "blink_data.db"
23
+
24
+ def __init__(self):
25
+ self.creds = None
26
+ self.service = None
27
+ self.client_id = os.getenv('GOOGLE_CLIENT_ID')
28
+ self.client_secret = os.getenv('GOOGLE_CLIENT_SECRET')
29
+ self.refresh_token = os.getenv('GOOGLE_REFRESH_TOKEN')
30
+
31
+ def authenticate(self):
32
+ """Authenticate using the refresh token."""
33
+ if not all([self.client_id, self.client_secret, self.refresh_token]):
34
+ logger.error("Missing Google API credentials for Drive Service")
35
+ return False
36
+
37
+ try:
38
+ self.creds = Credentials(
39
+ None,
40
+ refresh_token=self.refresh_token,
41
+ token_uri="https://oauth2.googleapis.com/token",
42
+ client_id=self.client_id,
43
+ client_secret=self.client_secret,
44
+ scopes=self.SCOPES
45
+ )
46
+
47
+ if self.creds and self.creds.expired and self.creds.refresh_token:
48
+ self.creds.refresh(Request())
49
+
50
+ self.service = build('drive', 'v3', credentials=self.creds)
51
+ return True
52
+ except Exception as e:
53
+ logger.error(f"Failed to authenticate with Drive API: {e}")
54
+ return False
55
+
56
+ def _find_folder(self):
57
+ """Find the 'apigateway' folder."""
58
+ try:
59
+ query = f"mimeType='application/vnd.google-apps.folder' and name='{self.FOLDER_NAME}' and trashed=false"
60
+ results = self.service.files().list(q=query, spaces='drive', fields='files(id, name)').execute()
61
+ files = results.get('files', [])
62
+ if not files:
63
+ return None
64
+ return files[0]['id']
65
+ except HttpError as error:
66
+ logger.error(f"An error occurred searching for folder: {error}")
67
+ return None
68
+
69
+ def _create_folder(self):
70
+ """Create the 'apigateway' folder."""
71
+ try:
72
+ file_metadata = {
73
+ 'name': self.FOLDER_NAME,
74
+ 'mimeType': 'application/vnd.google-apps.folder'
75
+ }
76
+ file = self.service.files().create(body=file_metadata, fields='id').execute()
77
+ logger.info(f"Created folder with ID: {file.get('id')}")
78
+ return file.get('id')
79
+ except HttpError as error:
80
+ logger.error(f"An error occurred creating folder: {error}")
81
+ return None
82
+
83
+ def _get_folder_id(self):
84
+ """Get folder ID, creating it if it doesn't exist."""
85
+ folder_id = self._find_folder()
86
+ if not folder_id:
87
+ folder_id = self._create_folder()
88
+ return folder_id
89
+
90
+ def upload_db(self):
91
+ """Upload the database file to Google Drive."""
92
+ if not self.service and not self.authenticate():
93
+ return False
94
+
95
+ if not os.path.exists(self.DB_FILENAME):
96
+ logger.warning(f"Database file {self.DB_FILENAME} not found for upload.")
97
+ return False
98
+
99
+ folder_id = self._get_folder_id()
100
+ if not folder_id:
101
+ return False
102
+
103
+ try:
104
+ # Check if file already exists in folder
105
+ query = f"name='{self.DB_FILENAME}' and '{folder_id}' in parents and trashed=false"
106
+ results = self.service.files().list(q=query, spaces='drive', fields='files(id)').execute()
107
+ files = results.get('files', [])
108
+
109
+ media = MediaFileUpload(self.DB_FILENAME, mimetype='application/x-sqlite3', resumable=True)
110
+
111
+ if files:
112
+ # Update existing file
113
+ file_id = files[0]['id']
114
+ self.service.files().update(fileId=file_id, media_body=media).execute()
115
+ logger.info(f"Updated database file {self.DB_FILENAME} in Drive (ID: {file_id})")
116
+ else:
117
+ # Create new file
118
+ file_metadata = {
119
+ 'name': self.DB_FILENAME,
120
+ 'parents': [folder_id]
121
+ }
122
+ self.service.files().create(body=file_metadata, media_body=media, fields='id').execute()
123
+ logger.info(f"Uploaded new database file {self.DB_FILENAME} to Drive")
124
+
125
+ return True
126
+ except HttpError as error:
127
+ logger.error(f"An error occurred uploading DB: {error}")
128
+ return False
129
+ except Exception as e:
130
+ logger.error(f"Unexpected error uploading DB: {e}")
131
+ return False
132
+
133
+ def download_db(self):
134
+ """Download the database file from Google Drive."""
135
+ if not self.service and not self.authenticate():
136
+ return False
137
+
138
+ folder_id = self._find_folder() # Don't create if not found, just return
139
+ if not folder_id:
140
+ logger.info("No 'apigateway' folder found in Drive. Starting with fresh DB.")
141
+ return False
142
+
143
+ try:
144
+ query = f"name='{self.DB_FILENAME}' and '{folder_id}' in parents and trashed=false"
145
+ results = self.service.files().list(q=query, spaces='drive', fields='files(id)').execute()
146
+ files = results.get('files', [])
147
+
148
+ if not files:
149
+ logger.info(f"No {self.DB_FILENAME} found in Drive folder. Starting with fresh DB.")
150
+ return False
151
+
152
+ file_id = files[0]['id']
153
+ request = self.service.files().get_media(fileId=file_id)
154
+ fh = io.FileIO(self.DB_FILENAME, 'wb')
155
+ downloader = MediaIoBaseDownload(fh, request)
156
+ done = False
157
+ while done is False:
158
+ status, done = downloader.next_chunk()
159
+ # logger.info(f"Download {int(status.progress() * 100)}%.")
160
+
161
+ logger.info(f"Successfully downloaded {self.DB_FILENAME} from Drive.")
162
+ return True
163
+ except HttpError as error:
164
+ logger.error(f"An error occurred downloading DB: {error}")
165
+ return False
166
+ except Exception as e:
167
+ logger.error(f"Unexpected error downloading DB: {e}")
168
+ return False
get_gmail_token.py → get_google_token.py RENAMED
@@ -3,7 +3,11 @@ import json
3
  from google_auth_oauthlib.flow import InstalledAppFlow
4
 
5
  # If modifying these scopes, delete the file token.json.
6
- SCOPES = ['https://www.googleapis.com/auth/gmail.send']
 
 
 
 
7
 
8
  def get_refresh_token():
9
  """Shows basic usage of the Gmail API.
 
3
  from google_auth_oauthlib.flow import InstalledAppFlow
4
 
5
  # If modifying these scopes, delete the file token.json.
6
+ # If modifying these scopes, delete the file token.json.
7
+ SCOPES = [
8
+ 'https://www.googleapis.com/auth/gmail.send',
9
+ 'https://www.googleapis.com/auth/drive.file'
10
+ ]
11
 
12
  def get_refresh_token():
13
  """Shows basic usage of the Gmail API.
routers/auth.py CHANGED
@@ -10,8 +10,10 @@ from models import User, AuditLog
10
  from schemas import CheckRegistrationRequest, RegisterRequest, ValidateRequest, ResetRequest
11
  from auth_utils import get_password_hash, verify_password, generate_secret_key, send_email
12
  from dependencies import check_rate_limit
 
13
 
14
  router = APIRouter(prefix="/auth", tags=["auth"])
 
15
 
16
  @router.post("/check-registration")
17
  async def check_registration(
@@ -109,6 +111,9 @@ Do not share this key with anyone."""
109
  "Your Secret Key - Credit System",
110
  email_body
111
  )
 
 
 
112
 
113
  return {"success": True, "message": "Registration successful. Check your email."}
114
 
@@ -117,6 +122,7 @@ Do not share this key with anyone."""
117
  async def validate_key(
118
  request: ValidateRequest,
119
  req: Request,
 
120
  db: AsyncSession = Depends(get_db)
121
  ):
122
  """
@@ -151,6 +157,9 @@ async def validate_key(
151
  db.add(audit_log)
152
  await db.commit()
153
 
 
 
 
154
  return {
155
  "valid": True,
156
  "user_id": valid_user.user_id,
@@ -233,6 +242,10 @@ Current Credits: {user.credits}"""
233
  "Your New Secret Key",
234
  email_body
235
  )
 
 
 
 
236
  else:
237
  # Log Audit (failed/not found)
238
  audit_log = AuditLog(
 
10
  from schemas import CheckRegistrationRequest, RegisterRequest, ValidateRequest, ResetRequest
11
  from auth_utils import get_password_hash, verify_password, generate_secret_key, send_email
12
  from dependencies import check_rate_limit
13
+ from drive_service import DriveService
14
 
15
  router = APIRouter(prefix="/auth", tags=["auth"])
16
+ drive_service = DriveService()
17
 
18
  @router.post("/check-registration")
19
  async def check_registration(
 
111
  "Your Secret Key - Credit System",
112
  email_body
113
  )
114
+
115
+ # Sync DB to Drive (Async)
116
+ background_tasks.add_task(drive_service.upload_db)
117
 
118
  return {"success": True, "message": "Registration successful. Check your email."}
119
 
 
122
  async def validate_key(
123
  request: ValidateRequest,
124
  req: Request,
125
+ background_tasks: BackgroundTasks,
126
  db: AsyncSession = Depends(get_db)
127
  ):
128
  """
 
157
  db.add(audit_log)
158
  await db.commit()
159
 
160
+ # Sync DB to Drive (Async) - Optional but good for audit logs
161
+ background_tasks.add_task(drive_service.upload_db)
162
+
163
  return {
164
  "valid": True,
165
  "user_id": valid_user.user_id,
 
242
  "Your New Secret Key",
243
  email_body
244
  )
245
+
246
+ # Sync DB to Drive (Async)
247
+ background_tasks.add_task(drive_service.upload_db)
248
+
249
  else:
250
  # Log Audit (failed/not found)
251
  audit_log = AuditLog(