updates
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -13,8 +13,15 @@ router = APIRouter(prefix='/favorites', tags=['favorites'])
|
||||
|
||||
@router.get('/')
|
||||
async def get_favorites(current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
if role and role.name in ['admin', 'staff', 'accountant']:
|
||||
# PERFORMANCE: Use eager-loaded role relationship if available
|
||||
if hasattr(current_user, 'role') and current_user.role is not None:
|
||||
role_name = current_user.role.name
|
||||
else:
|
||||
# Fallback: query if relationship wasn't loaded
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot have favorites')
|
||||
try:
|
||||
favorites = db.query(Favorite).filter(Favorite.user_id == current_user.id).order_by(Favorite.created_at.desc()).all()
|
||||
@@ -35,8 +42,15 @@ async def get_favorites(current_user: User=Depends(get_current_user), db: Sessio
|
||||
|
||||
@router.post('/{room_id}')
|
||||
async def add_favorite(room_id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
if role and role.name in ['admin', 'staff', 'accountant']:
|
||||
# PERFORMANCE: Use eager-loaded role relationship if available
|
||||
if hasattr(current_user, 'role') and current_user.role is not None:
|
||||
role_name = current_user.role.name
|
||||
else:
|
||||
# Fallback: query if relationship wasn't loaded
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot add favorites')
|
||||
try:
|
||||
room = db.query(Room).filter(Room.id == room_id).first()
|
||||
@@ -58,8 +72,15 @@ async def add_favorite(room_id: int, current_user: User=Depends(get_current_user
|
||||
|
||||
@router.delete('/{room_id}')
|
||||
async def remove_favorite(room_id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
if role and role.name in ['admin', 'staff', 'accountant']:
|
||||
# PERFORMANCE: Use eager-loaded role relationship if available
|
||||
if hasattr(current_user, 'role') and current_user.role is not None:
|
||||
role_name = current_user.role.name
|
||||
else:
|
||||
# Fallback: query if relationship wasn't loaded
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
raise HTTPException(status_code=403, detail='Admin, staff, and accountant users cannot remove favorites')
|
||||
try:
|
||||
favorite = db.query(Favorite).filter(Favorite.user_id == current_user.id, Favorite.room_id == room_id).first()
|
||||
@@ -76,8 +97,15 @@ async def remove_favorite(room_id: int, current_user: User=Depends(get_current_u
|
||||
|
||||
@router.get('/check/{room_id}')
|
||||
async def check_favorite(room_id: int, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
if role and role.name in ['admin', 'staff', 'accountant']:
|
||||
# PERFORMANCE: Use eager-loaded role relationship if available
|
||||
if hasattr(current_user, 'role') and current_user.role is not None:
|
||||
role_name = current_user.role.name
|
||||
else:
|
||||
# Fallback: query if relationship wasn't loaded
|
||||
role = db.query(Role).filter(Role.id == current_user.role_id).first()
|
||||
role_name = role.name if role else 'customer'
|
||||
|
||||
if role_name in ['admin', 'staff', 'accountant']:
|
||||
return {'status': 'success', 'data': {'isFavorited': False}}
|
||||
try:
|
||||
favorite = db.query(Favorite).filter(Favorite.user_id == current_user.id, Favorite.room_id == room_id).first()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query
|
||||
from fastapi import APIRouter, Depends, HTTPException, status, Query, Request
|
||||
from ...shared.utils.response_helpers import success_response, error_response
|
||||
from sqlalchemy.orm import Session, joinedload
|
||||
from sqlalchemy import func
|
||||
from sqlalchemy import func, and_
|
||||
from typing import Optional
|
||||
from ...shared.config.database import get_db
|
||||
from ...shared.config.logging_config import get_logger
|
||||
@@ -9,7 +9,9 @@ from ...security.middleware.auth import get_current_user, authorize_roles
|
||||
from ...auth.models.user import User
|
||||
from ..models.review import Review, ReviewStatus
|
||||
from ...rooms.models.room import Room
|
||||
from ...bookings.models.booking import Booking, BookingStatus
|
||||
from ..schemas.review import CreateReviewRequest
|
||||
from ...analytics.services.audit_service import audit_service
|
||||
|
||||
logger = get_logger(__name__)
|
||||
router = APIRouter(prefix='/reviews', tags=['reviews'])
|
||||
@@ -124,7 +126,11 @@ async def get_all_reviews(status_filter: Optional[str]=Query(None, alias='status
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.post('/')
|
||||
async def create_review(review_data: CreateReviewRequest, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
async def create_review(review_data: CreateReviewRequest, request: Request, current_user: User=Depends(get_current_user), db: Session=Depends(get_db)):
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
room_id = review_data.room_id
|
||||
rating = review_data.rating
|
||||
@@ -145,6 +151,25 @@ async def create_review(review_data: CreateReviewRequest, current_user: User=Dep
|
||||
detail=error_response(message='You have already reviewed this room')
|
||||
)
|
||||
|
||||
# BUSINESS RULE: Verify user has actually stayed in this room
|
||||
# Users can only review rooms they've booked and checked out from
|
||||
from ...shared.utils.role_helpers import is_admin, is_staff
|
||||
if not (is_admin(current_user, db) or is_staff(current_user, db)):
|
||||
# Check if user has a checked-out booking for this room
|
||||
past_booking = db.query(Booking).filter(
|
||||
and_(
|
||||
Booking.user_id == current_user.id,
|
||||
Booking.room_id == room_id,
|
||||
Booking.status == BookingStatus.checked_out
|
||||
)
|
||||
).first()
|
||||
|
||||
if not past_booking:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=error_response(message='You can only review rooms you have stayed in. Please complete a booking and check out before leaving a review.')
|
||||
)
|
||||
|
||||
review = Review(
|
||||
user_id=current_user.id,
|
||||
room_id=room_id,
|
||||
@@ -156,6 +181,28 @@ async def create_review(review_data: CreateReviewRequest, current_user: User=Dep
|
||||
db.commit()
|
||||
db.refresh(review)
|
||||
|
||||
# SECURITY: Log review creation for audit trail
|
||||
try:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='review_created',
|
||||
resource_type='review',
|
||||
user_id=current_user.id,
|
||||
resource_id=review.id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'room_id': review.room_id,
|
||||
'rating': review.rating,
|
||||
'status': 'pending',
|
||||
'comment_length': len(review.comment) if review.comment else 0
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to log review creation audit: {e}')
|
||||
|
||||
review_dict = {
|
||||
'id': review.id,
|
||||
'user_id': review.user_id,
|
||||
@@ -181,18 +228,47 @@ async def create_review(review_data: CreateReviewRequest, current_user: User=Dep
|
||||
)
|
||||
|
||||
@router.patch('/{id}/approve', dependencies=[Depends(authorize_roles('admin'))])
|
||||
async def approve_review(id: int, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
async def approve_review(id: int, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
review = db.query(Review).filter(Review.id == id).first()
|
||||
review = db.query(Review).options(joinedload(Review.room)).filter(Review.id == id).first()
|
||||
if not review:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=error_response(message='Review not found')
|
||||
)
|
||||
old_status = review.status.value if hasattr(review.status, 'value') else str(review.status)
|
||||
review.status = ReviewStatus.approved
|
||||
db.commit()
|
||||
db.refresh(review)
|
||||
|
||||
# SECURITY: Log review approval for audit trail
|
||||
try:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='review_approved',
|
||||
resource_type='review',
|
||||
user_id=current_user.id,
|
||||
resource_id=review.id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'review_user_id': review.user_id,
|
||||
'room_id': review.room_id,
|
||||
'room_number': review.room.room_number if review.room else None,
|
||||
'rating': review.rating,
|
||||
'old_status': old_status,
|
||||
'new_status': 'approved'
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to log review approval audit: {e}')
|
||||
|
||||
review_dict = {
|
||||
'id': review.id,
|
||||
'user_id': review.user_id,
|
||||
@@ -218,18 +294,47 @@ async def approve_review(id: int, current_user: User=Depends(authorize_roles('ad
|
||||
)
|
||||
|
||||
@router.patch('/{id}/reject', dependencies=[Depends(authorize_roles('admin'))])
|
||||
async def reject_review(id: int, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
async def reject_review(id: int, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
review = db.query(Review).filter(Review.id == id).first()
|
||||
review = db.query(Review).options(joinedload(Review.room)).filter(Review.id == id).first()
|
||||
if not review:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=error_response(message='Review not found')
|
||||
)
|
||||
old_status = review.status.value if hasattr(review.status, 'value') else str(review.status)
|
||||
review.status = ReviewStatus.rejected
|
||||
db.commit()
|
||||
db.refresh(review)
|
||||
|
||||
# SECURITY: Log review rejection for audit trail
|
||||
try:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='review_rejected',
|
||||
resource_type='review',
|
||||
user_id=current_user.id,
|
||||
resource_id=review.id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details={
|
||||
'review_user_id': review.user_id,
|
||||
'room_id': review.room_id,
|
||||
'room_number': review.room.room_number if review.room else None,
|
||||
'rating': review.rating,
|
||||
'old_status': old_status,
|
||||
'new_status': 'rejected'
|
||||
},
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to log review rejection audit: {e}')
|
||||
|
||||
review_dict = {
|
||||
'id': review.id,
|
||||
'user_id': review.user_id,
|
||||
@@ -255,13 +360,47 @@ async def reject_review(id: int, current_user: User=Depends(authorize_roles('adm
|
||||
)
|
||||
|
||||
@router.delete('/{id}', dependencies=[Depends(authorize_roles('admin'))])
|
||||
async def delete_review(id: int, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
async def delete_review(id: int, request: Request, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
client_ip = request.client.host if request.client else None
|
||||
user_agent = request.headers.get('User-Agent')
|
||||
request_id = getattr(request.state, 'request_id', None)
|
||||
|
||||
try:
|
||||
review = db.query(Review).filter(Review.id == id).first()
|
||||
review = db.query(Review).options(joinedload(Review.room)).filter(Review.id == id).first()
|
||||
if not review:
|
||||
raise HTTPException(status_code=404, detail='Review not found')
|
||||
|
||||
# Capture review details before deletion for audit
|
||||
review_details = {
|
||||
'review_id': review.id,
|
||||
'review_user_id': review.user_id,
|
||||
'room_id': review.room_id,
|
||||
'room_number': review.room.room_number if review.room else None,
|
||||
'rating': review.rating,
|
||||
'status': review.status.value if hasattr(review.status, 'value') else str(review.status),
|
||||
'comment_length': len(review.comment) if review.comment else 0
|
||||
}
|
||||
|
||||
db.delete(review)
|
||||
db.commit()
|
||||
|
||||
# SECURITY: Log review deletion for audit trail
|
||||
try:
|
||||
await audit_service.log_action(
|
||||
db=db,
|
||||
action='review_deleted',
|
||||
resource_type='review',
|
||||
user_id=current_user.id,
|
||||
resource_id=id,
|
||||
ip_address=client_ip,
|
||||
user_agent=user_agent,
|
||||
request_id=request_id,
|
||||
details=review_details,
|
||||
status='success'
|
||||
)
|
||||
except Exception as e:
|
||||
logger.warning(f'Failed to log review deletion audit: {e}')
|
||||
|
||||
return {'status': 'success', 'message': 'Review deleted successfully'}
|
||||
except HTTPException:
|
||||
raise
|
||||
|
||||
Reference in New Issue
Block a user