updates
This commit is contained in:
Binary file not shown.
@@ -562,3 +562,122 @@ async def upload_blog_image(
|
||||
except Exception as e:
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=f'Error uploading image: {str(e)}')
|
||||
|
||||
@router.get('/admin/tags', response_model=dict)
|
||||
async def get_all_tags(
|
||||
current_user: User = Depends(authorize_roles('admin')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Get all unique tags from all blog posts (admin only)"""
|
||||
try:
|
||||
all_posts = db.query(BlogPost).all()
|
||||
all_unique_tags = set()
|
||||
tag_usage = {} # Track how many posts use each tag
|
||||
|
||||
for post in all_posts:
|
||||
if post.tags:
|
||||
try:
|
||||
tags_list = json.loads(post.tags)
|
||||
for tag in tags_list:
|
||||
all_unique_tags.add(tag)
|
||||
tag_usage[tag] = tag_usage.get(tag, 0) + 1
|
||||
except:
|
||||
pass
|
||||
|
||||
tags_with_usage = [
|
||||
{'name': tag, 'usage_count': tag_usage.get(tag, 0)}
|
||||
for tag in sorted(all_unique_tags)
|
||||
]
|
||||
|
||||
return success_response({
|
||||
'tags': tags_with_usage,
|
||||
'total': len(tags_with_usage)
|
||||
})
|
||||
except Exception as e:
|
||||
logger.error(f"Error in get_all_tags: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.put('/admin/tags/rename', response_model=dict)
|
||||
async def rename_tag(
|
||||
old_tag: str = Query(..., description='Old tag name'),
|
||||
new_tag: str = Query(..., description='New tag name'),
|
||||
current_user: User = Depends(authorize_roles('admin')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Rename a tag across all blog posts (admin only)"""
|
||||
try:
|
||||
if not old_tag or not new_tag:
|
||||
raise HTTPException(status_code=400, detail='Both old_tag and new_tag are required')
|
||||
|
||||
if old_tag == new_tag:
|
||||
raise HTTPException(status_code=400, detail='Old and new tag names must be different')
|
||||
|
||||
# Get all posts that contain the old tag
|
||||
all_posts = db.query(BlogPost).all()
|
||||
updated_count = 0
|
||||
|
||||
for post in all_posts:
|
||||
if post.tags:
|
||||
try:
|
||||
tags_list = json.loads(post.tags)
|
||||
if old_tag in tags_list:
|
||||
# Replace old tag with new tag
|
||||
tags_list = [new_tag if tag == old_tag else tag for tag in tags_list]
|
||||
post.tags = json.dumps(tags_list)
|
||||
updated_count += 1
|
||||
except:
|
||||
pass
|
||||
|
||||
db.commit()
|
||||
|
||||
return success_response({
|
||||
'old_tag': old_tag,
|
||||
'new_tag': new_tag,
|
||||
'updated_posts': updated_count
|
||||
}, message=f'Tag renamed successfully. Updated {updated_count} post(s).')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error in rename_tag: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete('/admin/tags', response_model=dict)
|
||||
async def delete_tag(
|
||||
tag: str = Query(..., description='Tag name to delete'),
|
||||
current_user: User = Depends(authorize_roles('admin')),
|
||||
db: Session = Depends(get_db)
|
||||
):
|
||||
"""Delete a tag from all blog posts (admin only)"""
|
||||
try:
|
||||
if not tag:
|
||||
raise HTTPException(status_code=400, detail='Tag name is required')
|
||||
|
||||
# Get all posts that contain the tag
|
||||
all_posts = db.query(BlogPost).all()
|
||||
updated_count = 0
|
||||
|
||||
for post in all_posts:
|
||||
if post.tags:
|
||||
try:
|
||||
tags_list = json.loads(post.tags)
|
||||
if tag in tags_list:
|
||||
# Remove the tag
|
||||
tags_list = [t for t in tags_list if t != tag]
|
||||
post.tags = json.dumps(tags_list) if tags_list else None
|
||||
updated_count += 1
|
||||
except:
|
||||
pass
|
||||
|
||||
db.commit()
|
||||
|
||||
return success_response({
|
||||
'deleted_tag': tag,
|
||||
'updated_posts': updated_count
|
||||
}, message=f'Tag deleted successfully. Updated {updated_count} post(s).')
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f"Error in delete_tag: {str(e)}", exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
|
||||
Binary file not shown.
@@ -10,7 +10,7 @@ from ...security.middleware.auth import get_current_user, authorize_roles
|
||||
from ...auth.models.user import User
|
||||
from ..models.room import Room, RoomStatus
|
||||
from ..models.room_type import RoomType
|
||||
from ..schemas.room import CreateRoomRequest, UpdateRoomRequest, BulkDeleteRoomsRequest
|
||||
from ..schemas.room import CreateRoomRequest, UpdateRoomRequest, BulkDeleteRoomsRequest, UpdateAmenityRequest
|
||||
from ...shared.utils.response_helpers import success_response
|
||||
from ...reviews.models.review import Review, ReviewStatus
|
||||
from ...bookings.models.booking import Booking, BookingStatus
|
||||
@@ -72,6 +72,112 @@ async def get_amenities(db: Session=Depends(get_db)):
|
||||
logger.error(f'Error fetching amenities: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.put('/amenities/{old_name}', dependencies=[Depends(authorize_roles('admin'))])
|
||||
async def update_amenity(old_name: str, request: UpdateAmenityRequest, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
"""Update/rename an amenity across all rooms and room types."""
|
||||
try:
|
||||
import json
|
||||
updated_count = 0
|
||||
|
||||
# Update in room types
|
||||
room_types = db.query(RoomType).all()
|
||||
for rt in room_types:
|
||||
if rt.amenities:
|
||||
amenities_list = []
|
||||
if isinstance(rt.amenities, list):
|
||||
amenities_list = rt.amenities
|
||||
elif isinstance(rt.amenities, str):
|
||||
try:
|
||||
amenities_list = json.loads(rt.amenities)
|
||||
except:
|
||||
amenities_list = [s.strip() for s in rt.amenities.split(',') if s.strip()]
|
||||
|
||||
if old_name in amenities_list:
|
||||
amenities_list = [request.new_name if a == old_name else a for a in amenities_list]
|
||||
rt.amenities = amenities_list
|
||||
updated_count += 1
|
||||
|
||||
# Update in rooms
|
||||
rooms = db.query(Room).all()
|
||||
for room in rooms:
|
||||
if room.amenities:
|
||||
amenities_list = []
|
||||
if isinstance(room.amenities, list):
|
||||
amenities_list = room.amenities
|
||||
elif isinstance(room.amenities, str):
|
||||
try:
|
||||
amenities_list = json.loads(room.amenities)
|
||||
except:
|
||||
amenities_list = [s.strip() for s in room.amenities.split(',') if s.strip()]
|
||||
|
||||
if old_name in amenities_list:
|
||||
amenities_list = [request.new_name if a == old_name else a for a in amenities_list]
|
||||
room.amenities = amenities_list
|
||||
updated_count += 1
|
||||
|
||||
db.commit()
|
||||
return success_response(
|
||||
data={'updated_count': updated_count, 'old_name': old_name, 'new_name': request.new_name},
|
||||
message=f'Amenity "{old_name}" updated to "{request.new_name}" in {updated_count} location(s)'
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f'Error updating amenity: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.delete('/amenities/{amenity_name}', dependencies=[Depends(authorize_roles('admin'))])
|
||||
async def delete_amenity(amenity_name: str, current_user: User=Depends(authorize_roles('admin')), db: Session=Depends(get_db)):
|
||||
"""Remove an amenity from all rooms and room types."""
|
||||
try:
|
||||
import json
|
||||
updated_count = 0
|
||||
|
||||
# Remove from room types
|
||||
room_types = db.query(RoomType).all()
|
||||
for rt in room_types:
|
||||
if rt.amenities:
|
||||
amenities_list = []
|
||||
if isinstance(rt.amenities, list):
|
||||
amenities_list = rt.amenities
|
||||
elif isinstance(rt.amenities, str):
|
||||
try:
|
||||
amenities_list = json.loads(rt.amenities)
|
||||
except:
|
||||
amenities_list = [s.strip() for s in rt.amenities.split(',') if s.strip()]
|
||||
|
||||
if amenity_name in amenities_list:
|
||||
amenities_list = [a for a in amenities_list if a != amenity_name]
|
||||
rt.amenities = amenities_list if amenities_list else []
|
||||
updated_count += 1
|
||||
|
||||
# Remove from rooms
|
||||
rooms = db.query(Room).all()
|
||||
for room in rooms:
|
||||
if room.amenities:
|
||||
amenities_list = []
|
||||
if isinstance(room.amenities, list):
|
||||
amenities_list = room.amenities
|
||||
elif isinstance(room.amenities, str):
|
||||
try:
|
||||
amenities_list = json.loads(room.amenities)
|
||||
except:
|
||||
amenities_list = [s.strip() for s in room.amenities.split(',') if s.strip()]
|
||||
|
||||
if amenity_name in amenities_list:
|
||||
amenities_list = [a for a in amenities_list if a != amenity_name]
|
||||
room.amenities = amenities_list if amenities_list else []
|
||||
updated_count += 1
|
||||
|
||||
db.commit()
|
||||
return success_response(
|
||||
data={'updated_count': updated_count, 'amenity_name': amenity_name},
|
||||
message=f'Amenity "{amenity_name}" removed from {updated_count} location(s)'
|
||||
)
|
||||
except Exception as e:
|
||||
db.rollback()
|
||||
logger.error(f'Error deleting amenity: {str(e)}', exc_info=True)
|
||||
raise HTTPException(status_code=500, detail=str(e))
|
||||
|
||||
@router.get('/room-types')
|
||||
async def get_room_types(db: Session=Depends(get_db)):
|
||||
"""Get all room types for dropdowns and forms."""
|
||||
@@ -457,7 +563,21 @@ async def upload_room_images(id: int, images: List[UploadFile]=File(...), curren
|
||||
continue
|
||||
await f.write(content)
|
||||
image_urls.append(f'/uploads/rooms/{filename}')
|
||||
|
||||
# Handle existing_images - it might be a list, a JSON string, or None
|
||||
existing_images = room.images or []
|
||||
if isinstance(existing_images, str):
|
||||
# If it's a string, try to parse it as JSON
|
||||
import json
|
||||
try:
|
||||
existing_images = json.loads(existing_images)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# If parsing fails, treat as empty list
|
||||
existing_images = []
|
||||
# Ensure it's a list
|
||||
if not isinstance(existing_images, list):
|
||||
existing_images = []
|
||||
|
||||
updated_images = existing_images + image_urls
|
||||
room.images = updated_images
|
||||
db.commit()
|
||||
@@ -483,7 +603,21 @@ async def delete_room_images(id: int, image_url: str=Query(..., description='Ima
|
||||
if not normalized_url.startswith('/'):
|
||||
normalized_url = f'/{normalized_url}'
|
||||
filename = Path(normalized_url).name
|
||||
|
||||
# Handle existing_images - it might be a list, a JSON string, or None
|
||||
existing_images = room.images or []
|
||||
if isinstance(existing_images, str):
|
||||
# If it's a string, try to parse it as JSON
|
||||
import json
|
||||
try:
|
||||
existing_images = json.loads(existing_images)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# If parsing fails, treat as empty list
|
||||
existing_images = []
|
||||
# Ensure it's a list
|
||||
if not isinstance(existing_images, list):
|
||||
existing_images = []
|
||||
|
||||
updated_images = []
|
||||
for img in existing_images:
|
||||
stored_path = img if img.startswith('/') else f'/{img}'
|
||||
|
||||
Binary file not shown.
@@ -105,3 +105,8 @@ class BulkDeleteRoomsRequest(BaseModel):
|
||||
raise ValueError('All room IDs must be positive integers')
|
||||
return v
|
||||
|
||||
|
||||
class UpdateAmenityRequest(BaseModel):
|
||||
"""Schema for updating/renaming an amenity."""
|
||||
new_name: str = Field(..., min_length=1, max_length=100, description="New name for the amenity")
|
||||
|
||||
|
||||
Binary file not shown.
@@ -54,11 +54,14 @@ async def get_rooms_with_ratings(db: Session, rooms: List[Room], base_url: str)
|
||||
result.append(room_dict)
|
||||
return result
|
||||
|
||||
def get_predefined_amenities() -> List[str]:
|
||||
return ['Free WiFi', 'WiFi', 'High-Speed Internet', 'WiFi in Room', 'Flat-Screen TV', 'TV', 'Cable TV', 'Satellite TV', 'Smart TV', 'Netflix', 'Streaming Services', 'DVD Player', 'Stereo System', 'Radio', 'iPod Dock', 'Air Conditioning', 'AC', 'Heating', 'Climate Control', 'Ceiling Fan', 'Air Purifier', 'Private Bathroom', 'Ensuite Bathroom', 'Bathtub', 'Jacuzzi Bathtub', 'Hot Tub', 'Shower', 'Rain Shower', 'Walk-in Shower', 'Bidet', 'Hair Dryer', 'Hairdryer', 'Bathrobes', 'Slippers', 'Toiletries', 'Premium Toiletries', 'Towels', 'Mini Bar', 'Minibar', 'Refrigerator', 'Fridge', 'Microwave', 'Coffee Maker', 'Electric Kettle', 'Tea Making Facilities', 'Coffee Machine', 'Nespresso Machine', 'Kitchenette', 'Dining Table', 'Room Service', 'Breakfast Included', 'Breakfast', 'Complimentary Water', 'Bottled Water', 'Desk', 'Writing Desk', 'Office Desk', 'Work Desk', 'Sofa', 'Sitting Area', 'Lounge Area', 'Dining Area', 'Separate Living Area', 'Wardrobe', 'Closet', 'Dresser', 'Mirror', 'Full-Length Mirror', 'Seating Area', 'King Size Bed', 'Queen Size Bed', 'Double Bed', 'Twin Beds', 'Single Bed', 'Extra Bedding', 'Pillow Menu', 'Premium Bedding', 'Blackout Curtains', 'Soundproofing', 'Safe', 'In-Room Safe', 'Safety Deposit Box', 'Smoke Detector', 'Fire Extinguisher', 'Security System', 'Key Card Access', 'Door Lock', 'Pepper Spray', 'USB Charging Ports', 'USB Ports', 'USB Outlets', 'Power Outlets', 'Charging Station', 'Laptop Safe', 'HDMI Port', 'Phone', 'Desk Phone', 'Wake-Up Service', 'Alarm Clock', 'Digital Clock', 'Balcony', 'Private Balcony', 'Terrace', 'Patio', 'City View', 'Ocean View', 'Sea View', 'Mountain View', 'Garden View', 'Pool View', 'Park View', 'Window', 'Large Windows', 'Floor-to-Ceiling Windows', '24-Hour Front Desk', '24 Hour Front Desk', '24/7 Front Desk', 'Concierge Service', 'Butler Service', 'Housekeeping', 'Daily Housekeeping', 'Turndown Service', 'Laundry Service', 'Dry Cleaning', 'Ironing Service', 'Luggage Storage', 'Bell Service', 'Valet Parking', 'Parking', 'Free Parking', 'Airport Shuttle', 'Shuttle Service', 'Car Rental', 'Taxi Service', 'Gym Access', 'Fitness Center', 'Fitness Room', 'Spa Access', 'Spa', 'Sauna', 'Steam Room', 'Hot Tub', 'Massage Service', 'Beauty Services', 'Swimming Pool', 'Pool', 'Indoor Pool', 'Outdoor Pool', 'Infinity Pool', 'Pool Access', 'Golf Course', 'Tennis Court', 'Beach Access', 'Water Sports', 'Business Center', 'Meeting Room', 'Conference Room', 'Fax Service', 'Photocopying', 'Printing Service', 'Secretarial Services', 'Wheelchair Accessible', 'Accessible Room', 'Elevator Access', 'Ramp Access', 'Accessible Bathroom', 'Lowered Sink', 'Grab Bars', 'Hearing Accessible', 'Visual Alarm', 'Family Room', 'Kids Welcome', 'Baby Crib', 'Extra Bed', 'Crib', 'Childcare Services', 'Pets Allowed', 'Pet Friendly', 'Smoking Room', 'Non-Smoking Room', 'No Smoking', 'Interconnecting Rooms', 'Adjoining Rooms', 'Suite', 'Separate Bedroom', 'Kitchen', 'Full Kitchen', 'Dishwasher', 'Oven', 'Stove', 'Washing Machine', 'Dryer', 'Iron', 'Ironing Board', 'Clothes Rack', 'Umbrella', 'Shoe Shine Service', 'Fireplace', 'Jacuzzi', 'Steam Shower', 'Spa Bath', 'Bidet Toilet', 'Smart Home System', 'Lighting Control', 'Curtain Control', 'Automated Systems', 'Personalized Service', 'VIP Treatment', 'Butler', 'Private Entrance', 'Private Elevator', 'Panic Button', 'Blu-ray Player', 'Gaming Console', 'PlayStation', 'Xbox', 'Sound System', 'Surround Sound', 'Music System', 'Library', 'Reading Room', 'Study Room', 'Private Pool', 'Private Garden', 'Yard', 'Courtyard', 'Outdoor Furniture', 'BBQ Facilities', 'Picnic Area']
|
||||
|
||||
async def get_amenities_list(db: Session) -> List[str]:
|
||||
all_amenities = set(get_predefined_amenities())
|
||||
"""
|
||||
Get all unique amenities from the database only.
|
||||
Aggregates amenities from room_types and rooms tables.
|
||||
"""
|
||||
all_amenities = set()
|
||||
|
||||
# Get amenities from room types
|
||||
room_types = db.query(RoomType.amenities).all()
|
||||
for rt in room_types:
|
||||
if rt.amenities:
|
||||
@@ -74,6 +77,8 @@ async def get_amenities_list(db: Session) -> List[str]:
|
||||
all_amenities.update([s.strip() for s in rt.amenities.split(',') if s.strip()])
|
||||
except:
|
||||
all_amenities.update([s.strip() for s in rt.amenities.split(',') if s.strip()])
|
||||
|
||||
# Get amenities from rooms
|
||||
rooms = db.query(Room.amenities).all()
|
||||
for r in rooms:
|
||||
if r.amenities:
|
||||
@@ -89,4 +94,5 @@ async def get_amenities_list(db: Session) -> List[str]:
|
||||
all_amenities.update([s.strip() for s in r.amenities.split(',') if s.strip()])
|
||||
except:
|
||||
all_amenities.update([s.strip() for s in r.amenities.split(',') if s.strip()])
|
||||
|
||||
return sorted(list(all_amenities))
|
||||
Reference in New Issue
Block a user