pipeline issues fix
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-09-25 11:59:54 +09:00
parent dc50a9858e
commit 4e3768a6ee
39 changed files with 1297 additions and 739 deletions

View File

@@ -1,17 +1,19 @@
from fastapi import FastAPI, HTTPException, Depends, Query
import math
from datetime import datetime, timedelta
from typing import List, Optional
from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.ext.asyncio import AsyncSession
from pydantic import BaseModel, Field
from sqlalchemy import select, text
from shared.config import settings
from shared.database import get_db
from shared.cache import CacheService
from services.location_service.models import UserLocation, LocationHistory
from sqlalchemy.ext.asyncio import AsyncSession
from services.location_service.models import LocationHistory, UserLocation
from services.user_service.main import get_current_user
from services.user_service.models import User
from pydantic import BaseModel, Field
from typing import List, Optional
from datetime import datetime, timedelta
import math
from shared.cache import CacheService
from shared.config import settings
from shared.database import get_db
app = FastAPI(title="Location Service", version="1.0.0")
@@ -40,7 +42,7 @@ class LocationResponse(BaseModel):
longitude: float
accuracy: Optional[float]
updated_at: datetime
class Config:
from_attributes = True
@@ -56,17 +58,17 @@ class NearbyUserResponse(BaseModel):
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate distance between two points using Haversine formula (in meters)"""
R = 6371000 # Earth's radius in meters
lat1_rad = math.radians(lat1)
lat2_rad = math.radians(lat2)
delta_lat = math.radians(lat2 - lat1)
delta_lon = math.radians(lon2 - lon1)
a = (math.sin(delta_lat / 2) * math.sin(delta_lat / 2) +
math.cos(lat1_rad) * math.cos(lat2_rad) *
math.sin(delta_lon / 2) * math.sin(delta_lon / 2))
a = math.sin(delta_lat / 2) * math.sin(delta_lat / 2) + math.cos(
lat1_rad
) * math.cos(lat2_rad) * math.sin(delta_lon / 2) * math.sin(delta_lon / 2)
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
distance = R * c
return distance
@@ -75,19 +77,19 @@ def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> fl
async def update_user_location(
location_data: LocationUpdate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Update user's current location"""
if not current_user.location_sharing_enabled:
raise HTTPException(status_code=403, detail="Location sharing is disabled")
# Update or create current location
result = await db.execute(
select(UserLocation).filter(UserLocation.user_id == current_user.id)
)
user_location = result.scalars().first()
if user_location:
user_location.latitude = location_data.latitude
user_location.longitude = location_data.longitude
@@ -106,7 +108,7 @@ async def update_user_location(
heading=location_data.heading,
)
db.add(user_location)
# Save to history
location_history = LocationHistory(
user_id=current_user.id,
@@ -116,17 +118,17 @@ async def update_user_location(
recorded_at=datetime.utcnow(),
)
db.add(location_history)
await db.commit()
# Cache location for fast access
await CacheService.set_location(
current_user.id,
location_data.latitude,
current_user.id,
location_data.latitude,
location_data.longitude,
expire=300 # 5 minutes
expire=300, # 5 minutes
)
return {"message": "Location updated successfully"}
@@ -134,20 +136,22 @@ async def update_user_location(
async def get_user_location(
user_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Get specific user's location (if sharing is enabled)"""
# Check if requested user exists and has location sharing enabled
result = await db.execute(select(User).filter(User.id == user_id))
target_user = result.scalars().first()
if not target_user:
raise HTTPException(status_code=404, detail="User not found")
if not target_user.location_sharing_enabled and target_user.id != current_user.id:
raise HTTPException(status_code=403, detail="User has disabled location sharing")
raise HTTPException(
status_code=403, detail="User has disabled location sharing"
)
# Try cache first
cached_location = await CacheService.get_location(user_id)
if cached_location:
@@ -157,18 +161,18 @@ async def get_user_location(
latitude=lat,
longitude=lng,
accuracy=None,
updated_at=datetime.utcnow()
updated_at=datetime.utcnow(),
)
# Get from database
result = await db.execute(
select(UserLocation).filter(UserLocation.user_id == user_id)
)
user_location = result.scalars().first()
if not user_location:
raise HTTPException(status_code=404, detail="Location not found")
return LocationResponse.model_validate(user_location)
@@ -179,17 +183,18 @@ async def get_nearby_users(
radius_km: float = Query(1.0, ge=0.1, le=10.0),
limit: int = Query(50, ge=1, le=200),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
db: AsyncSession = Depends(get_db),
):
"""Find users within specified radius"""
# Convert radius to degrees (approximate)
# 1 degree ≈ 111 km
radius_deg = radius_km / 111.0
# Query for nearby users with location sharing enabled
# Using bounding box for initial filtering (more efficient than distance calculation)
query = text("""
query = text(
"""
SELECT
ul.user_id,
ul.latitude,
@@ -205,42 +210,45 @@ async def get_nearby_users(
AND ul.longitude BETWEEN :lng_min AND :lng_max
AND ul.updated_at > :time_threshold
LIMIT :limit_val
""")
"""
)
time_threshold = datetime.utcnow() - timedelta(minutes=15) # Only recent locations
result = await db.execute(query, {
"current_user_id": current_user.id,
"lat_min": latitude - radius_deg,
"lat_max": latitude + radius_deg,
"lng_min": longitude - radius_deg,
"lng_max": longitude + radius_deg,
"time_threshold": time_threshold,
"limit_val": limit
})
result = await db.execute(
query,
{
"current_user_id": current_user.id,
"lat_min": latitude - radius_deg,
"lat_max": latitude + radius_deg,
"lng_min": longitude - radius_deg,
"lng_max": longitude + radius_deg,
"time_threshold": time_threshold,
"limit_val": limit,
},
)
nearby_users = []
for row in result:
# Calculate exact distance
distance = calculate_distance(
latitude, longitude,
row.latitude, row.longitude
)
distance = calculate_distance(latitude, longitude, row.latitude, row.longitude)
# Filter by exact radius
if distance <= radius_km * 1000: # Convert km to meters
nearby_users.append(NearbyUserResponse(
user_id=row.user_id,
latitude=row.latitude,
longitude=row.longitude,
distance_meters=distance,
last_seen=row.updated_at
))
nearby_users.append(
NearbyUserResponse(
user_id=row.user_id,
latitude=row.latitude,
longitude=row.longitude,
distance_meters=distance,
last_seen=row.updated_at,
)
)
# Sort by distance
nearby_users.sort(key=lambda x: x.distance_meters)
return nearby_users
@@ -249,30 +257,30 @@ async def get_location_history(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
hours: int = Query(24, ge=1, le=168), # Max 1 week
limit: int = Query(100, ge=1, le=1000)
limit: int = Query(100, ge=1, le=1000),
):
"""Get user's location history"""
time_threshold = datetime.utcnow() - timedelta(hours=hours)
result = await db.execute(
select(LocationHistory)
.filter(
LocationHistory.user_id == current_user.id,
LocationHistory.recorded_at >= time_threshold
LocationHistory.recorded_at >= time_threshold,
)
.order_by(LocationHistory.recorded_at.desc())
.limit(limit)
)
history = result.scalars().all()
return [
{
"latitude": entry.latitude,
"longitude": entry.longitude,
"accuracy": entry.accuracy,
"recorded_at": entry.recorded_at
"recorded_at": entry.recorded_at,
}
for entry in history
]
@@ -280,24 +288,23 @@ async def get_location_history(
@app.delete("/api/v1/location")
async def delete_user_location(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)
):
"""Delete user's current location"""
# Delete current location
result = await db.execute(
select(UserLocation).filter(UserLocation.user_id == current_user.id)
)
user_location = result.scalars().first()
if user_location:
await db.delete(user_location)
await db.commit()
# Clear cache
await CacheService.delete(f"location:{current_user.id}")
return {"message": "Location deleted successfully"}
@@ -309,4 +316,5 @@ async def health_check():
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8003)
uvicorn.run(app, host="0.0.0.0", port=8003)