private shares and revokation works

This commit is contained in:
2025-12-13 00:34:38 +05:00
parent f5370aa7f9
commit 4007445396
13 changed files with 307 additions and 96 deletions

View File

@@ -8,11 +8,53 @@ from shapely.geometry import shape, Point, LineString
import json
from app.models.map_item import MapItem
from app.models.map import Map
from app.models.map_share import MapShare, SharePermission
from app.models.user import User
from app.schemas.map_item import MapItemCreate, MapItemUpdate
from app.services.map_service import get_map_by_id
def check_edit_permission(db: Session, map_id: UUID, user: User) -> None:
"""Check if user has edit permission on a map. Raises exception if not."""
map_obj = db.query(Map).filter(Map.id == map_id).first()
if not map_obj:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Map not found"
)
# Owner always has edit permission
if map_obj.owner_id == user.id:
return
# Admin always has edit permission
if user.is_admin:
return
# Check if user has share access
share = db.query(MapShare).filter(
MapShare.map_id == map_id,
MapShare.user_id == user.id
).first()
if not share:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have access to this map"
)
# Check if share permission is EDIT
if share.permission != SharePermission.EDIT:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You only have read-only access to this map"
)
return
def get_map_items(db: Session, map_id: UUID, user: Optional[User] = None) -> List[MapItem]:
"""Get all items for a map."""
# Verify user has access to the map
@@ -60,8 +102,8 @@ def geography_to_geojson(geography) -> dict:
def create_map_item(db: Session, map_id: UUID, item_data: MapItemCreate, user: User) -> MapItem:
"""Create a new map item."""
# Verify user has access to the map
get_map_by_id(db, map_id, user)
# Verify user has edit permission on the map
check_edit_permission(db, map_id, user)
# Convert GeoJSON to PostGIS geography
geometry_wkt = geojson_to_geography(item_data.geometry)
@@ -142,6 +184,9 @@ def update_map_item(db: Session, item_id: UUID, item_data: MapItemUpdate, user:
"""Update a map item."""
item = get_map_item_by_id(db, item_id, user)
# Verify user has edit permission on the map
check_edit_permission(db, item.map_id, user)
# Update fields if provided
if item_data.type is not None:
item.type = item_data.type
@@ -162,6 +207,9 @@ def delete_map_item(db: Session, item_id: UUID, user: User) -> None:
"""Delete a map item."""
item = get_map_item_by_id(db, item_id, user)
# Verify user has edit permission on the map
check_edit_permission(db, item.map_id, user)
# Capture map_id and item_id before deletion for WebSocket broadcast
map_id = item.map_id
deleted_item_id = str(item.id)

View File

@@ -6,12 +6,28 @@ from fastapi import HTTPException, status
from app.models.map import Map
from app.models.user import User
from app.models.map_share import MapShare
from app.schemas.map import MapCreate, MapUpdate
def get_user_maps(db: Session, user_id: UUID) -> List[Map]:
"""Get all maps owned by a user."""
return db.query(Map).filter(Map.owner_id == user_id).order_by(Map.updated_at.desc()).all()
"""Get all maps owned by or shared with a user."""
# Get owned maps
owned_maps = db.query(Map).filter(Map.owner_id == user_id).all()
# Get shared maps
shared_map_ids = db.query(MapShare.map_id).filter(MapShare.user_id == user_id).all()
shared_map_ids = [share.map_id for share in shared_map_ids]
shared_maps = []
if shared_map_ids:
shared_maps = db.query(Map).filter(Map.id.in_(shared_map_ids)).all()
# Combine and sort by updated_at
all_maps = owned_maps + shared_maps
all_maps.sort(key=lambda m: m.updated_at, reverse=True)
return all_maps
def get_map_by_id(db: Session, map_id: UUID, user: Optional[User] = None) -> Map:
@@ -26,7 +42,15 @@ def get_map_by_id(db: Session, map_id: UUID, user: Optional[User] = None) -> Map
# If user is provided, check authorization
if user:
if map_obj.owner_id != user.id and not user.is_admin:
# Check if user is owner, admin, or has been granted access via share
is_owner = map_obj.owner_id == user.id
is_admin = user.is_admin
has_share_access = db.query(MapShare).filter(
MapShare.map_id == map_id,
MapShare.user_id == user.id
).first() is not None
if not (is_owner or is_admin or has_share_access):
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="You don't have permission to access this map"

View File

@@ -33,18 +33,37 @@ def create_map_share(
detail="Only the map owner can share it"
)
# Check if user exists
target_user = db.query(User).filter(User.id == share_data.user_id).first()
# Look up user by username, email, or UUID
target_user = None
user_identifier = share_data.user_identifier.strip()
# Try UUID first
try:
user_uuid = UUID(user_identifier)
target_user = db.query(User).filter(User.id == user_uuid).first()
except ValueError:
# Not a valid UUID, try username or email
target_user = db.query(User).filter(
(User.username == user_identifier) | (User.email == user_identifier)
).first()
if not target_user:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User not found"
detail=f"User not found with identifier: {user_identifier}"
)
# Prevent sharing with self
if target_user.id == current_user.id:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Cannot share map with yourself"
)
# Check if already shared
existing_share = db.query(MapShare).filter(
MapShare.map_id == map_id,
MapShare.user_id == share_data.user_id
MapShare.user_id == target_user.id
).first()
if existing_share:
@@ -58,7 +77,7 @@ def create_map_share(
# Create new share
share = MapShare(
map_id=map_id,
user_id=share_data.user_id,
user_id=target_user.id,
permission=share_data.permission,
shared_by=current_user.id
)
@@ -120,7 +139,7 @@ def update_map_share(
return share
def delete_map_share(
async def delete_map_share(
db: Session,
map_id: UUID,
share_id: UUID,
@@ -146,9 +165,16 @@ def delete_map_share(
detail="Share not found"
)
# Get user_id before deleting the share
revoked_user_id = share.user_id
db.delete(share)
db.commit()
# Disconnect the user's WebSocket connections
from app.websocket.connection_manager import manager
await manager.disconnect_user(map_id, revoked_user_id)
def create_share_link(
db: Session,