-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmain.py
More file actions
398 lines (320 loc) · 13.3 KB
/
Copy pathmain.py
File metadata and controls
398 lines (320 loc) · 13.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
#!/usr/bin/env python3
from __future__ import annotations
from dotenv import load_dotenv
import uvicorn
import logging
from fastapi import FastAPI, BackgroundTasks, Depends, Request
from typing import Annotated
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from app.models.requests import (
IncorrectIdentificationRequest,
PlantSpeciesRequest,
UserBadgeSetRequest,
UserRegistrationRequest,
UserLoginRequest,
UserLeaderboardRequest,
UserPointAddRequest,
GoogleUserRegisterRequest,
UserPasswordResetRequest,
UserOTPVerifyRequest,
FriendAddRequest,
UserEmailUpdateRequest,
UserPasswordUpdateRequest,
PlantSubmissionRequest,
UsernameSetRequest
)
from app.auth.login_signup import (
auth_google_account,
add_google_account,
user_login,
record_user_registration,
user_has_otp,
)
from app.auth.token import get_current_user
from app.core.db_connection import build_engine
from app.core.users import (
delete_user_account,
get_friends_leaderboard,
get_global_leaderboard,
get_count_user,
add_user_global_points,
get_regional_leaderboard,
get_user_badge,
password_reset_mail_request,
get_user_points,
get_username,
set_user_badge,
get_user_region,
update_user_email,
update_user_password,
change_username
)
from app.db.incorrect_identification import record_incorrect_identification
from app.db.plant_species import record_plant_species, get_plant_species_url, get_species_id, update_plant_species_url
from app.db.friends import (
get_friends,
get_pending_requests,
add_friend,
accept_friend_request,
reject_friend_request,
delete_friend,
search_usernames,
)
from app.models.requests import FriendAddRequest, UserEmailUpdateRequest, UserPasswordUpdateRequest
from app.models.requests import PlantSubmissionRequest
from app.db.submissions import record_plant_submission, get_submission_history
from app.db.submissions import record_plant_submission, get_submission_history
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from slowapi.middleware import SlowAPIMiddleware
logging.basicConfig(level=logging.INFO)
HOST = "localhost"
PORT = 8000
load_dotenv()
limiter = Limiter(key_func=get_remote_address, default_limits=["50/minute"])
app = FastAPI(
title="Identiflora Database API",
version="0.1.0",
description="Minimal API for interacting with the Identiflora MySQL database.",
)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
app.add_middleware(SlowAPIMiddleware)
engine = build_engine()
@app.post("/authenticate-token")
# @limiter.limit("5/minute")
async def authenticate_token_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
"""Authenticates user token and returns boolean"""
logging.info(f"User: {token_claims.get('sub')} authenticated")
return True
@app.post("/incorrect-identifications")
async def add_incorrect_identification(
payload: IncorrectIdentificationRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
"""Route handler that records an incorrect identification via helper logic."""
logging.info(
f"Incorrect identification recorded by user {token_claims.get('sub')}: {payload.identification_id}"
)
return record_incorrect_identification(payload, engine)
@app.get("/species-id/{scientific_name}")
async def species_id(scientific_name: str):
"""Route handler that returns a species id via helper logic."""
return get_species_id(scientific_name, engine)
@app.post("/plant-species")
@limiter.exempt
async def add_plant_species(payload: PlantSpeciesRequest):
"""Route handler that records a new plant species via helper logic."""
logging.info("HIT /plant-species: %s", payload.scientific_name)
return record_plant_species(payload, engine)
@app.post("/add-plant-species-url")
@limiter.exempt
async def add_plant_species_url(payload: PlantSpeciesRequest):
"""Route handler that records a new plant species via helper logic."""
return update_plant_species_url(payload, engine)
@app.get("/plant-species-url/{scientific_name}")
async def get_plant_species_url_router(scientific_name: str):
"""Route handler that returns a plant species img url using query parameters."""
return get_plant_species_url(scientific_name, engine)
@app.post("/user/register")
async def add_registered_user(payload: UserRegistrationRequest):
"""Route handler that records user registration data via helper logic."""
return record_user_registration(payload, engine)
@app.post("/user/login")
async def login_user(payload: UserLoginRequest):
"""Route handler that records user registration data via helper logic."""
return user_login(payload, engine)
@app.post("/global-leaderboard")
async def load_global_leaderboard(payload: UserLeaderboardRequest):
"""Route handler that returns users on the global leaderboard via helper logic."""
return get_global_leaderboard(payload, engine)
@app.post("/regional-leaderboard")
async def load_regional_leaderboard(
payload: UserLeaderboardRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
"""Route handler that returns users on the regional leaderboard via helper logic."""
user_id = token_claims.get("sub")
return get_regional_leaderboard(user_id, payload, engine)
@app.post("/friends-leaderboard")
async def load_friends_leaderboard(
payload: UserLeaderboardRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
"""Route handler that returns users on the friends leaderboard via helper logic."""
user_id = token_claims.get("sub")
return get_friends_leaderboard(user_id, payload, engine)
@app.post("/user-count")
async def get_user_count(token_claims: Annotated[dict, Depends(get_current_user)]):
"""Route handler that gets user count via helper logic."""
logging.info(f"User {token_claims.get('sub')} user count request")
return get_count_user(engine)
@app.post("/add-global-user-pts")
async def add_user_global_points_router(
payload: UserPointAddRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
"""Route handler that adds global points to user via helper logic."""
user_id = token_claims.get("sub")
logging.info(f"User {user_id} add global points request")
add_points = payload.add_points
return add_user_global_points(user_id, add_points, engine)
@app.post("/google/auth")
async def google_auth(auth: HTTPAuthorizationCredentials = Depends(HTTPBearer())):
"""Route handler that attempts to decode and authenticate Google user with their token."""
token = auth.credentials
return await auth_google_account(token, engine)
@app.post("/google/register")
async def google_register(payload: GoogleUserRegisterRequest, auth: HTTPAuthorizationCredentials = Depends(HTTPBearer())):
"""Route handler that attempts to record user Google data via helper logic."""
token = auth.credentials
return add_google_account(token, payload, engine)
@app.post("/pwd-reset/otp-request")
async def google_auth(
payload: UserPasswordResetRequest,
backgroundTasks: BackgroundTasks,
):
"""Route handler that sends the user a password reset one time password via helper logic."""
return password_reset_mail_request(payload, engine, backgroundTasks)
@app.post("/pwd-reset/otp-check")
async def google_auth(payload: UserOTPVerifyRequest):
"""Route handler that attempts to check and verify the user's one time password via helper logic."""
return user_has_otp(payload, engine)
@app.post("/user-points")
async def get_user_points_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
"""Route handler that returns a user's global points."""
user_id = token_claims.get("sub")
return get_user_points(user_id, engine)
@app.get("/friends")
async def get_friends_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
user_id = int(token_claims.get("sub"))
return {"friends": get_friends(user_id=user_id, engine=engine)}
@app.get("/friends/pending")
async def pending_friends(
token_claims: Annotated[dict, Depends(get_current_user)]
):
user_id = int(token_claims.get("sub"))
return {"pending_requests": get_pending_requests(user_id, engine)}
@app.get("/friends/search")
async def search_friends_router(
query: str,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return {"results": search_usernames(query, user_id, engine)}
@app.post("/friends/add")
async def add_friend_router(
payload: FriendAddRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return add_friend(payload=payload, user_id=user_id, engine=engine)
@app.post("/friends/accept")
async def accept_friend_request_endpoint(
requester_id: int,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return accept_friend_request(requester_id, user_id, engine)
@app.post("/friends/reject")
async def reject_friend_request_endpoint(
requester_id: int,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return reject_friend_request(requester_id, user_id, engine)
@app.delete("/friends/delete")
async def delete_friend_endpoint(
friend_id: int,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return delete_friend(user_id, friend_id, engine)
@app.delete("/friends")
async def delete_friend_endpoint(
friend_id: int,
token_claims: Annotated[dict, Depends(get_current_user)],
):
user_id = int(token_claims.get("sub"))
return delete_friend(user_id, friend_id, engine)
@app.post("/username")
async def get_username_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
"""Route handler that returns a user's username."""
user_id = token_claims.get("sub")
return get_username(user_id, engine)
@app.post("/user/update-email")
async def update_email_router(payload: UserEmailUpdateRequest, token_claims: Annotated[dict, Depends(get_current_user)]):
"""Route handler for updating an authenticated user's email."""
user_id = int(token_claims.get('sub'))
logging.info(f"User {user_id} requested email update")
return update_user_email(user_id, payload, engine)
@app.post("/user/update-password")
async def update_password_router(payload: UserPasswordUpdateRequest, token_claims: Annotated[dict, Depends(get_current_user)]):
"""Route handler for updating an authenticated user's password."""
user_id = int(token_claims.get('sub'))
logging.info(f"User {user_id} requested password update")
return update_user_password(user_id, payload, engine)
@app.delete("/user/account")
async def delete_account_router(token_claims: Annotated[dict, Depends(get_current_user)]):
"""Route to permanently delete the authenticated user's account."""
user_id = int(token_claims.get('sub'))
return delete_user_account(user_id, engine)
@app.post("/set-user-badge")
async def set_user_badge_router(
payload: UserBadgeSetRequest,
token_claims: Annotated[dict, Depends(get_current_user)],
):
"""Route handler that sets a user's selected badge."""
user_id = token_claims.get("sub")
badge_file_path = payload.badge_file_path
return set_user_badge(user_id, badge_file_path, engine)
@app.post("/get-user-badge")
async def get_user_badge_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
"""Route handler that returns a Flutter file path to a user's badge."""
user_id = token_claims.get("sub")
return get_user_badge(user_id, engine)
@app.post("/get-user-region")
async def get_user_region_router(
token_claims: Annotated[dict, Depends(get_current_user)]
):
"""Route handler that returns a user region."""
user_id = token_claims.get("sub")
return get_user_region(user_id, engine)
@app.post("/user/submissions")
async def add_plant_submission(payload: PlantSubmissionRequest, token_claims: Annotated[dict, Depends(get_current_user)]):
user_id = int(token_claims.get('sub'))
logging.info(f"User {user_id} saving plant submission: {payload.user_guess}")
return record_plant_submission(payload, user_id, engine)
@app.get("/user/history")
async def get_user_history(token_claims: Annotated[dict, Depends(get_current_user)]):
"""Route handler that returns a user's plant identification history."""
user_id = int(token_claims.get('sub'))
logging.info(f"User {user_id} requested submission history")
return get_submission_history(user_id, engine)
@app.post("/change-username")
async def change_username_router(payload: UsernameSetRequest, token_claims: Annotated[dict, Depends(get_current_user)],):
"""Route handler that sets a user's username."""
user_id = token_claims.get("sub")
new_username = payload.new_username
logging.info(f"User {user_id} change username request, new username: {payload.new_username}")
return change_username(user_id, new_username, engine)
if __name__ == "__main__":
uvicorn.run(
"main:app",
host=HOST,
port=PORT,
reload=False,
proxy_headers=True,
forwarded_allow_ips="*",
)