Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Backend/bookmyvenue/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Binary file not shown.
102 changes: 102 additions & 0 deletions Backend/bookmyvenue/src/bookmyvenue/BackgroundWorker/Owner/tasks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import base64
import random

import structlog
from typing import List

from celery import shared_task
from sqlalchemy import select
from src.bookmyvenue.models.common import Venue
from src.bookmyvenue.core.database import session
from src.bookmyvenue.utils.image_kit import imagekit
from src.bookmyvenue.worker import app
logger = structlog.get_logger()


@app.task
def upload_media_to_imagekit(venue_id:int,cover_image,gallery:List,venue_name:str):
db = session()

#we need to get the venue record first
#update its status to uploading
#start the image uploads to imagekit
#on success update the status to uploaded

try:
current_venue_fetching_statement = select(Venue).where(Venue.id == venue_id)
venue = db.execute(current_venue_fetching_statement).scalar_one_or_none() #scalar is uses=d to return as python object

if not venue:
logger.error("venue not found for the given id" , venueid = venue_id)
return {
"status_code":400,
"message": "venue not found"
}

venue.task_status = 'Uploading'

cover_image_url = ''
gallery_urls = []
#First we upload the images to the imagekit storage and then return the urls to the route control

logger.info("trying to upload the cover image through imagekit" , venueid = venue_id, task_id = venue.celery_task_ID)


try:
base64_str = cover_image

if "base64," in base64_str:
base64_str = base64_str.split("base64,")[1]

# Convert the string to raw image bytes
cover_image_bytes = base64.b64decode(base64_str.encode("utf-8"))
uploaded_media = imagekit.files.upload(
file=cover_image_bytes,
file_name=f'{venue_name}-cover-{random.randint(0,10000)}',
folder='/covers'
)

if uploaded_media.url:
logger.info("successfully uploaded the cover image" , url = uploaded_media.url, venueid = venue_id, task_id = venue.celery_task_ID)
cover_image_url = uploaded_media.url

idx=0
for img in gallery:

base64_str = img
if "base64," in base64_str:
base64_str = base64_str.split("base64,")[1]

gallery_image_bytes = base64.b64decode(base64_str.encode("utf-8"))
uploaded_media_gallery = imagekit.files.upload(
file=gallery_image_bytes,
file_name= f'{venue_name}-gallery-idx',
folder='/gallery'
)
if uploaded_media_gallery.url:
logger.info("successfully uploaded the gallery image" , url = uploaded_media_gallery.url, venueid = venue_id, task_id = venue.celery_task_ID)
gallery_urls.append(uploaded_media_gallery.url) ## appending the gallery urls back.

idx += 1

venue.cover_image = cover_image_url
venue.gallery = gallery_urls
venue.task_status = 'Completed'
db.commit()

except Exception as e:
logger.info("Can't upload the media", venueid = venue_id, task_id = venue.celery_task_ID)
venue.task_status = "Failed"
raise e



except Exception as e:
db.rollback()
logger.info("owner media upload task error", venueid = venue_id)
raise e
finally:
db.close() #closing the opened connection



Empty file.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
76 changes: 76 additions & 0 deletions Backend/bookmyvenue/src/bookmyvenue/api/deps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import os
import structlog
import redis.asyncio as aioredis
from dotenv import load_dotenv
from fastapi import Depends, HTTPException, Request, status
from sqlalchemy.orm import Session
from clerk_backend_api import Clerk
from clerk_backend_api.security import authenticate_request
from clerk_backend_api.security.types import AuthenticateRequestOptions


from src.bookmyvenue.core.database import session
from src.bookmyvenue.models.admin import Admin
from src.bookmyvenue.repositories.admin.repository import adminRepository
from src.bookmyvenue.repositories.owner.repository import ownerRepository
load_dotenv()

clerk_SDK = Clerk(bearer_auth=os.getenv("CLERK_API_KEY"))
logger = structlog.get_logger()

def get_the_db_Session():
db = session() #generating the new sectio
try:
yield db #yield is used to get the current session pause the function pass the session to the respective function calling the db session and once it completes the job the contol comes back here and closes the connection
finally:
db.close()


def get_the_redis_client(request: Request) -> aioredis.Redis:
return request.app.state.redis #accessing the redis client stored in the app.state of the request.




# this function is used to check the incoming request with siginied in or not
def get_the_current_user(request: Request):

request_state = clerk_SDK.authenticate_request(
request,
AuthenticateRequestOptions(
authorized_parties=['http://localhost:3000']
)
)

if not request_state.is_signed_in:
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED,detail="user is not signed in")
else:
request_payload = request_state.payload
user_id = request_payload['sub'] # user ID
return user_id


# Role based checking of the Admin
def admin_only_route(request: Request,db:Session = Depends(get_the_db_Session)):
user_id = get_the_current_user(request=request) #passing the request to the function
admin_user = adminRepository.get_the_admin_by_user(db,user_id)

if not admin_user:
logger.error("Forbidden request, only allowed for the admin to access" ,clerk_id=user_id)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="Forbidden, only admin can access this endpoint")

return admin_user


#Role based check for owner
def owner_only_route(request: Request,db:Session = Depends(get_the_db_Session)):
user_id = get_the_current_user(request=request) #passing the request to the function
owner_user = ownerRepository.get_owner_record_by_ID(db,user_id)

if not owner_user:
logger.error("Forbidden request, only allowed for the owners to access" ,clerk_id=user_id)
raise HTTPException(status_code=status.HTTP_403_FORBIDDEN,detail="Forbidden, only owners can access this endpoint")

return owner_user


Empty file.
Binary file not shown.
Empty file.
Binary file not shown.
Binary file not shown.
61 changes: 61 additions & 0 deletions Backend/bookmyvenue/src/bookmyvenue/api/v1/admin/admin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
from typing import List

import structlog
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.orm import Session
import redis.asyncio as aioredis
from src.bookmyvenue.schema.admin.admin import AmenitesSchema, CategoriesSchema, CategorySchema
from src.bookmyvenue.models.admin import Admin
from src.bookmyvenue.api.deps import admin_only_route, get_the_db_Session, get_the_redis_client
from src.bookmyvenue.services.adminService import adminservice

logger = structlog.get_logger()
router = APIRouter(
prefix='/admin'
)

@router.post('/category')
async def add_category(category:CategoriesSchema,db:Session = Depends(get_the_db_Session), admin_user:Admin = Depends(admin_only_route), redis:aioredis.Redis = Depends(get_the_redis_client)):
"""
this endpoint is protected with the role based access such that only admins can access this route.
"""

category_addition_acknowledment = adminservice.add_category(db=db, categories=category.category)

if category_addition_acknowledment:
logger.info("successfully created the categories...." , admin_user = admin_user.user_id)
await redis.delete('bmv:categories:all')
raise HTTPException(
status_code=status.HTTP_201_CREATED,
detail=f"successfully created the categories"
)
else:
logger.error("cant complete the categories addition...." , admin_user = admin_user.user_id)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"some errors occured and cant complete the creation of categories."
)



@router.post('/amenity')
async def add_amenity(amenity:AmenitesSchema,db:Session = Depends(get_the_db_Session), admin_user:Admin = Depends(admin_only_route), redis:aioredis.Redis = Depends(get_the_redis_client)):
"""
this endpoint is protected with the role based access such that only admins can access this route.
"""

amenity_addition_acknowledment = adminservice.add_amenity(db=db, amenities=amenity.amenity)

if amenity_addition_acknowledment:
logger.info("successfully created the amenities...." , admin_user = admin_user.user_id)
await redis.delete('bmv:amenities:all')
raise HTTPException(
status_code=status.HTTP_201_CREATED,
detail=f"successfully created the amenities"
)
else:
logger.error("cant complete the amenitiy addition...." , admin_user = admin_user.user_id)
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=f"some errors occured and cant complete the creation of amenities."
)
Binary file not shown.
98 changes: 98 additions & 0 deletions Backend/bookmyvenue/src/bookmyvenue/api/v1/common/common.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import json
from typing import List

from pydantic import TypeAdapter
import structlog
import redis.asyncio as aioredis
from fastapi import APIRouter, Depends
from sqlalchemy.orm import Session
from src.bookmyvenue.schema.admin.admin import AmenitySchema, CategorySchema
from src.bookmyvenue.schema.responce.responces import AmenityFetchedResponce, CategoryFetchedResponce, ResponceAmenitySchema, ResponceCategorySchema
from src.bookmyvenue.api.deps import get_the_db_Session, get_the_redis_client
from src.bookmyvenue.services.commonService import commonService

logger = structlog.get_logger()
router = APIRouter()

@router.get("/categories", response_model=CategoryFetchedResponce)
async def get_the_categories(
db: Session = Depends(get_the_db_Session),
redis: aioredis.Redis = Depends(get_the_redis_client)
):
categories_adapter = TypeAdapter(List[ResponceCategorySchema])
#key used for accessing the categories
CACHE_KEY = 'bmv:categories:all'
cached_categories = await redis.get(CACHE_KEY)
if cached_categories:
logger.info("successfully fetched the categories from cache")
decoded_redis_data = json.loads(cached_categories)
return {
"status_code": 200,
"message": "fetched the categories successfully",
"data": categories_adapter.validate_json(decoded_redis_data)
}

#get the categories listed in the categories table when it is not found in the redis
categories = commonService.get_the_categories(db)
logger.info("successfully fetched the categories")


pydantic_categories = categories_adapter.validate_python(
categories,
from_attributes=True
)

# 5. Dump directly to a JSON string using Pydantic's high-performance encoder
# (This avoids using standard json.dumps which crashes on custom objects)
json_string = categories_adapter.dump_json(pydantic_categories).decode("utf-8")

logger.info("successfully fetched the categories from cache")
await redis.set(CACHE_KEY, json.dumps(json_string)) #No TTL is given since this has to be invalidated based on the event if category creation or deletion

return {
"status_code": 200,
"message": "fetched the categories successfully",
"data": categories
}


@router.get("/amenities", response_model=AmenityFetchedResponce)
async def get_the_amenities(
db: Session = Depends(get_the_db_Session),
redis: aioredis.Redis = Depends(get_the_redis_client)
):
amenities_adapter = TypeAdapter(List[ResponceAmenitySchema]) # type adapter is used to validate a list of objects
#get the categories listed in the categories table
CACHE_KEY = 'bmv:amenities:all'
await redis.delete(CACHE_KEY)
logger.info("successfully fetched the amenities")
cached_amenities = await redis.get(CACHE_KEY)
if cached_amenities:
logger.info("successfully fetched the categories from cache")
decoded_redis_data = json.loads(cached_amenities)
return {
"status_code": 200,
"message": "fetched the categories successfully",
"data": amenities_adapter.validate_json(decoded_redis_data)
}
amenities = commonService.get_the_amenities(db)

pydantic_amenities = amenities_adapter.validate_python(
amenities,
from_attributes=True
)

# 5. Dump directly to a JSON string using Pydantic's high-performance encoder
# (This avoids using standard json.dumps which crashes on custom objects)
json_string = amenities_adapter.dump_json(pydantic_amenities).decode("utf-8")

logger.info("successfully fetched the categories from cache")
await redis.set(CACHE_KEY, json.dumps(json_string)) #No TTL is given since this has to be invalidated based on the event if category creation or deletion

return {
"status_code": 200,
"message": "fetched the amenities successfully",
"data": amenities
}


Empty file.
Binary file not shown.
Binary file not shown.
Loading