MukeshKapoor25 commited on
Commit
f36ca66
·
1 Parent(s): 4ddb8b3

feat(favorites): add favorites feature for merchants

Browse files

implement full CRUD operations for user favorites including:
- add/remove merchants to favorites
- list favorites with pagination
- check favorite status
- update favorite notes
- get favorite details

README.md DELETED
@@ -1,11 +0,0 @@
1
- ---
2
- title: Bookmyservice Ums
3
- emoji: 📉
4
- colorFrom: blue
5
- colorTo: blue
6
- sdk: docker
7
- pinned: false
8
- short_description: User Management Services
9
- ---
10
-
11
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
app/app.py CHANGED
@@ -2,7 +2,7 @@
2
 
3
  from fastapi import FastAPI
4
  from fastapi.middleware.cors import CORSMiddleware
5
- from app.routers import user_router, profile_router, account_router, wallet_router, address_router, pet_router, guest_router
6
  from app.middleware.rate_limiter import RateLimitMiddleware
7
  from app.middleware.security_middleware import SecurityMiddleware
8
  import logging
@@ -52,6 +52,7 @@ app.include_router(wallet_router.router, prefix="/wallet", tags=["wallet_managem
52
  app.include_router(address_router.router, prefix="/addresses", tags=["address_management"])
53
  app.include_router(pet_router.router, prefix="/api/v1/users", tags=["pet_management"])
54
  app.include_router(guest_router.router, prefix="/api/v1/users", tags=["guest_management"])
 
55
 
56
  @app.get("/")
57
  def root():
 
2
 
3
  from fastapi import FastAPI
4
  from fastapi.middleware.cors import CORSMiddleware
5
+ from app.routers import user_router, profile_router, account_router, wallet_router, address_router, pet_router, guest_router, favorite_router
6
  from app.middleware.rate_limiter import RateLimitMiddleware
7
  from app.middleware.security_middleware import SecurityMiddleware
8
  import logging
 
52
  app.include_router(address_router.router, prefix="/addresses", tags=["address_management"])
53
  app.include_router(pet_router.router, prefix="/api/v1/users", tags=["pet_management"])
54
  app.include_router(guest_router.router, prefix="/api/v1/users", tags=["guest_management"])
55
+ app.include_router(favorite_router.router, prefix="/api/v1/users", tags=["favorites"])
56
 
57
  @app.get("/")
58
  def root():
app/models/favorite_model.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException
2
+ from app.core.nosql_client import db
3
+ import logging
4
+ from datetime import datetime
5
+ from typing import Optional
6
+
7
+ logger = logging.getLogger("favorite_model")
8
+
9
+ class BookMyServiceFavoriteModel:
10
+ collection = db["favourites"]
11
+
12
+ @staticmethod
13
+ async def create_favorite(
14
+ customer_id: str,
15
+ merchant_id: str,
16
+ merchant_category: str,
17
+ merchant_name: str,
18
+ notes: Optional[str] = None
19
+ ):
20
+ """Create a new favorite merchant entry"""
21
+ logger.info(f"Creating favorite for customer {customer_id}, merchant {merchant_id}")
22
+
23
+ try:
24
+ # Check if favorite already exists
25
+ existing_favorite = await BookMyServiceFavoriteModel.collection.find_one({
26
+ "customer_id": customer_id,
27
+ "merchant_id": merchant_id
28
+ })
29
+
30
+ if existing_favorite:
31
+ logger.warning(f"Favorite already exists for customer {customer_id}, merchant {merchant_id}")
32
+ raise HTTPException(status_code=409, detail="Merchant already in favorites")
33
+
34
+ # Create favorite document
35
+ favorite_doc = {
36
+ "customer_id": customer_id,
37
+ "merchant_id": merchant_id,
38
+ "merchant_category": merchant_category,
39
+ "merchant_name": merchant_name,
40
+ "added_at": datetime.utcnow(),
41
+ "notes": notes
42
+ }
43
+
44
+ result = await BookMyServiceFavoriteModel.collection.insert_one(favorite_doc)
45
+ logger.info(f"Favorite created successfully with ID: {result.inserted_id}")
46
+
47
+ return favorite_doc
48
+
49
+ except HTTPException:
50
+ raise
51
+ except Exception as e:
52
+ logger.error(f"Error creating favorite: {str(e)}", exc_info=True)
53
+ raise HTTPException(status_code=500, detail="Failed to create favorite")
54
+
55
+ @staticmethod
56
+ async def delete_favorite(customer_id: str, merchant_id: str):
57
+ """Remove a merchant from favorites"""
58
+ logger.info(f"Deleting favorite for customer {customer_id}, merchant {merchant_id}")
59
+
60
+ try:
61
+ result = await BookMyServiceFavoriteModel.collection.delete_one({
62
+ "customer_id": customer_id,
63
+ "merchant_id": merchant_id
64
+ })
65
+
66
+ if result.deleted_count == 0:
67
+ logger.warning(f"Favorite not found for customer {customer_id}, merchant {merchant_id}")
68
+ raise HTTPException(status_code=404, detail="Favorite not found")
69
+
70
+ logger.info(f"Favorite deleted successfully")
71
+ return True
72
+
73
+ except HTTPException:
74
+ raise
75
+ except Exception as e:
76
+ logger.error(f"Error deleting favorite: {str(e)}", exc_info=True)
77
+ raise HTTPException(status_code=500, detail="Failed to delete favorite")
78
+
79
+ @staticmethod
80
+ async def get_favorites(
81
+ customer_id: str,
82
+ category: Optional[str] = None,
83
+ skip: int = 0,
84
+ limit: int = 50
85
+ ):
86
+ """Get user's favorite merchants, optionally filtered by category"""
87
+ logger.info(f"Getting favorites for customer {customer_id}, category: {category}")
88
+
89
+ try:
90
+ # Build query
91
+ query = {"customer_id": customer_id}
92
+ if category:
93
+ query["merchant_category"] = category
94
+
95
+ # Get favorites with pagination
96
+ cursor = BookMyServiceFavoriteModel.collection.find(query).sort("added_at", -1).skip(skip).limit(limit)
97
+ favorites = await cursor.to_list(length=limit)
98
+
99
+ # Get total count for pagination
100
+ total_count = await BookMyServiceFavoriteModel.collection.count_documents(query)
101
+
102
+ logger.info(f"Found {len(favorites)} favorites for customer {customer_id}")
103
+
104
+ return {
105
+ "favorites": favorites,
106
+ "total_count": total_count,
107
+ "skip": skip,
108
+ "limit": limit
109
+ }
110
+
111
+ except Exception as e:
112
+ logger.error(f"Error getting favorites: {str(e)}", exc_info=True)
113
+ raise HTTPException(status_code=500, detail="Failed to get favorites")
114
+
115
+ @staticmethod
116
+ async def get_favorite(customer_id: str, merchant_id: str):
117
+ """Get a specific favorite entry"""
118
+ logger.info(f"Getting favorite for customer {customer_id}, merchant {merchant_id}")
119
+
120
+ try:
121
+ favorite = await BookMyServiceFavoriteModel.collection.find_one({
122
+ "customer_id": customer_id,
123
+ "merchant_id": merchant_id
124
+ })
125
+
126
+ if not favorite:
127
+ logger.warning(f"Favorite not found for customer {customer_id}, merchant {merchant_id}")
128
+ raise HTTPException(status_code=404, detail="Favorite not found")
129
+
130
+ return favorite
131
+
132
+ except HTTPException:
133
+ raise
134
+ except Exception as e:
135
+ logger.error(f"Error getting favorite: {str(e)}", exc_info=True)
136
+ raise HTTPException(status_code=500, detail="Failed to get favorite")
137
+
138
+ @staticmethod
139
+ async def update_favorite_notes(customer_id: str, merchant_id: str, notes: str):
140
+ """Update notes for a favorite merchant"""
141
+ logger.info(f"Updating notes for customer {customer_id}, merchant {merchant_id}")
142
+
143
+ try:
144
+ result = await BookMyServiceFavoriteModel.collection.update_one(
145
+ {
146
+ "customer_id": customer_id,
147
+ "merchant_id": merchant_id
148
+ },
149
+ {"$set": {"notes": notes, "updated_at": datetime.utcnow()}}
150
+ )
151
+
152
+ if result.matched_count == 0:
153
+ logger.warning(f"Favorite not found for customer {customer_id}, merchant {merchant_id}")
154
+ raise HTTPException(status_code=404, detail="Favorite not found")
155
+
156
+ logger.info(f"Favorite notes updated successfully")
157
+ return True
158
+
159
+ except HTTPException:
160
+ raise
161
+ except Exception as e:
162
+ logger.error(f"Error updating favorite notes: {str(e)}", exc_info=True)
163
+ raise HTTPException(status_code=500, detail="Failed to update favorite notes")
164
+
165
+ @staticmethod
166
+ async def is_favorite(customer_id: str, merchant_id: str) -> bool:
167
+ """Check if a merchant is in user's favorites"""
168
+ logger.debug(f"Checking if merchant {merchant_id} is favorite for customer {customer_id}")
169
+
170
+ try:
171
+ favorite = await BookMyServiceFavoriteModel.collection.find_one({
172
+ "customer_id": customer_id,
173
+ "merchant_id": merchant_id
174
+ })
175
+
176
+ return favorite is not None
177
+
178
+ except Exception as e:
179
+ logger.error(f"Error checking favorite status: {str(e)}", exc_info=True)
180
+ return False
app/routers/favorite_router.py ADDED
@@ -0,0 +1,196 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter, Depends, HTTPException, Query, status
2
+ from typing import Optional
3
+ from app.schemas.favorite_schema import (
4
+ FavoriteCreateRequest,
5
+ FavoriteUpdateRequest,
6
+ FavoriteResponse,
7
+ FavoritesListResponse,
8
+ FavoriteStatusResponse,
9
+ FavoriteSuccessResponse
10
+ )
11
+ from app.services.favorite_service import FavoriteService
12
+ from app.services.user_service import UserService
13
+ from app.utils.jwt import get_current_user
14
+ import logging
15
+
16
+ logger = logging.getLogger("favorite_router")
17
+
18
+ router = APIRouter(
19
+ prefix="/favorites",
20
+ tags=["favorites"],
21
+ responses={404: {"description": "Not found"}},
22
+ )
23
+
24
+ @router.post(
25
+ "",
26
+ response_model=FavoriteSuccessResponse,
27
+ status_code=status.HTTP_201_CREATED,
28
+ summary="Add merchant to favorites",
29
+ description="Add a merchant to the user's list of favorite merchants"
30
+ )
31
+ async def add_favorite(
32
+ favorite_data: FavoriteCreateRequest,
33
+ current_user: dict = Depends(get_current_user)
34
+ ):
35
+ """
36
+ Add a merchant to user's favorites.
37
+
38
+ - **merchant_id**: ID of the merchant to favorite
39
+ - **merchant_category**: Category of the merchant (salon, spa, pet_spa, etc.)
40
+ - **merchant_name**: Name of the merchant for quick display
41
+ - **notes**: Optional note about this favorite
42
+ """
43
+ try:
44
+ return await FavoriteService.add_favorite(
45
+ customer_id=current_user["user_id"],
46
+ favorite_data=favorite_data
47
+ )
48
+ except HTTPException as e:
49
+ raise e
50
+ except Exception as e:
51
+ logger.error(f"Error in add_favorite endpoint: {str(e)}", exc_info=True)
52
+ raise HTTPException(status_code=500, detail="Internal server error")
53
+
54
+ @router.delete(
55
+ "/{merchant_id}",
56
+ response_model=FavoriteSuccessResponse,
57
+ status_code=status.HTTP_200_OK,
58
+ summary="Remove merchant from favorites",
59
+ description="Remove a merchant from the user's list of favorite merchants"
60
+ )
61
+ async def remove_favorite(
62
+ merchant_id: str,
63
+ current_user: dict = Depends(get_current_user)
64
+ ):
65
+ """
66
+ Remove a merchant from user's favorites.
67
+
68
+ - **merchant_id**: ID of the merchant to remove from favorites
69
+ """
70
+ try:
71
+ return await FavoriteService.remove_favorite(
72
+ customer_id=current_user["user_id"],
73
+ merchant_id=merchant_id
74
+ )
75
+ except HTTPException as e:
76
+ raise e
77
+ except Exception as e:
78
+ logger.error(f"Error in remove_favorite endpoint: {str(e)}", exc_info=True)
79
+ raise HTTPException(status_code=500, detail="Internal server error")
80
+
81
+ @router.get(
82
+ "",
83
+ response_model=FavoritesListResponse,
84
+ status_code=status.HTTP_200_OK,
85
+ summary="List favorite merchants",
86
+ description="Get user's list of favorite merchants, optionally filtered by category"
87
+ )
88
+ async def list_favorites(
89
+ category: Optional[str] = Query(None, description="Filter by merchant category"),
90
+ skip: int = Query(0, ge=0, description="Number of items to skip"),
91
+ limit: int = Query(50, ge=1, le=100, description="Maximum number of items to return"),
92
+ current_user: dict = Depends(get_current_user)
93
+ ):
94
+ """
95
+ Get user's favorite merchants.
96
+
97
+ - **category**: Optional filter by merchant category
98
+ - **skip**: Number of items to skip (for pagination)
99
+ - **limit**: Maximum number of items to return (max 100)
100
+ """
101
+ try:
102
+ return await FavoriteService.get_favorites(
103
+ customer_id=current_user["user_id"],
104
+ category=category,
105
+ skip=skip,
106
+ limit=limit
107
+ )
108
+ except HTTPException as e:
109
+ raise e
110
+ except Exception as e:
111
+ logger.error(f"Error in list_favorites endpoint: {str(e)}", exc_info=True)
112
+ raise HTTPException(status_code=500, detail="Internal server error")
113
+
114
+ @router.get(
115
+ "/{merchant_id}/status",
116
+ response_model=FavoriteStatusResponse,
117
+ status_code=status.HTTP_200_OK,
118
+ summary="Check favorite status",
119
+ description="Check if a merchant is in user's favorites"
120
+ )
121
+ async def check_favorite_status(
122
+ merchant_id: str,
123
+ current_user: dict = Depends(get_current_user)
124
+ ):
125
+ """
126
+ Check if a merchant is in user's favorites.
127
+
128
+ - **merchant_id**: ID of the merchant to check
129
+ """
130
+ try:
131
+ return await FavoriteService.check_favorite_status(
132
+ customer_id=current_user["user_id"],
133
+ merchant_id=merchant_id
134
+ )
135
+ except HTTPException as e:
136
+ raise e
137
+ except Exception as e:
138
+ logger.error(f"Error in check_favorite_status endpoint: {str(e)}", exc_info=True)
139
+ raise HTTPException(status_code=500, detail="Internal server error")
140
+
141
+ @router.get(
142
+ "/{merchant_id}",
143
+ response_model=FavoriteResponse,
144
+ status_code=status.HTTP_200_OK,
145
+ summary="Get favorite details",
146
+ description="Get detailed information about a specific favorite merchant"
147
+ )
148
+ async def get_favorite_details(
149
+ merchant_id: str,
150
+ current_user: dict = Depends(get_current_user)
151
+ ):
152
+ """
153
+ Get detailed information about a specific favorite merchant.
154
+
155
+ - **merchant_id**: ID of the merchant
156
+ """
157
+ try:
158
+ return await FavoriteService.get_favorite_details(
159
+ customer_id=current_user["user_id"],
160
+ merchant_id=merchant_id
161
+ )
162
+ except HTTPException as e:
163
+ raise e
164
+ except Exception as e:
165
+ logger.error(f"Error in get_favorite_details endpoint: {str(e)}", exc_info=True)
166
+ raise HTTPException(status_code=500, detail="Internal server error")
167
+
168
+ @router.patch(
169
+ "/{merchant_id}/notes",
170
+ response_model=FavoriteSuccessResponse,
171
+ status_code=status.HTTP_200_OK,
172
+ summary="Update favorite notes",
173
+ description="Update the notes for a favorite merchant"
174
+ )
175
+ async def update_favorite_notes(
176
+ merchant_id: str,
177
+ notes_data: FavoriteUpdateRequest,
178
+ current_user: dict = Depends(get_current_user)
179
+ ):
180
+ """
181
+ Update the notes for a favorite merchant.
182
+
183
+ - **merchant_id**: ID of the merchant
184
+ - **notes**: Updated note about this favorite
185
+ """
186
+ try:
187
+ return await FavoriteService.update_favorite_notes(
188
+ customer_id=current_user["user_id"],
189
+ merchant_id=merchant_id,
190
+ notes_data=notes_data
191
+ )
192
+ except HTTPException as e:
193
+ raise e
194
+ except Exception as e:
195
+ logger.error(f"Error in update_favorite_notes endpoint: {str(e)}", exc_info=True)
196
+ raise HTTPException(status_code=500, detail="Internal server error")
app/schemas/favorite_schema.py ADDED
@@ -0,0 +1,42 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, Field
2
+ from typing import Optional, List
3
+ from datetime import datetime
4
+
5
+ class FavoriteCreateRequest(BaseModel):
6
+ """Request schema for creating a favorite"""
7
+ merchant_id: str = Field(..., description="ID of the merchant to favorite")
8
+ merchant_category: str = Field(..., description="Category of the merchant (salon, spa, pet_spa, etc.)")
9
+ merchant_name: str = Field(..., description="Name of the merchant for quick display")
10
+ notes: Optional[str] = Field(None, description="Optional note about this favorite")
11
+
12
+ class FavoriteUpdateRequest(BaseModel):
13
+ """Request schema for updating favorite notes"""
14
+ notes: Optional[str] = Field(None, description="Updated note about this favorite")
15
+
16
+ class FavoriteResponse(BaseModel):
17
+ """Response schema for a favorite merchant"""
18
+ id: str = Field(..., description="Favorite ID")
19
+ customer_id: str = Field(..., description="ID of the customer who favorited")
20
+ merchant_id: str = Field(..., description="ID of the merchant")
21
+ merchant_category: str = Field(..., description="Category of the merchant")
22
+ merchant_name: str = Field(..., description="Name of the merchant")
23
+ added_at: datetime = Field(..., description="When the merchant was favorited")
24
+ notes: Optional[str] = Field(None, description="Optional note about this favorite")
25
+
26
+ class FavoritesListResponse(BaseModel):
27
+ """Response schema for listing favorites"""
28
+ favorites: List[FavoriteResponse] = Field(..., description="List of favorite merchants")
29
+ total_count: int = Field(..., description="Total number of favorites")
30
+ skip: int = Field(..., description="Number of items skipped")
31
+ limit: int = Field(..., description="Maximum number of items returned")
32
+
33
+ class FavoriteStatusResponse(BaseModel):
34
+ """Response schema for checking favorite status"""
35
+ is_favorite: bool = Field(..., description="Whether the merchant is in favorites")
36
+ favorite_id: Optional[str] = Field(None, description="ID of the favorite if exists")
37
+
38
+ class FavoriteSuccessResponse(BaseModel):
39
+ """Response schema for successful favorite operations"""
40
+ success: bool = Field(..., description="Operation success status")
41
+ message: str = Field(..., description="Operation result message")
42
+ favorite_id: Optional[str] = Field(None, description="ID of the created/updated favorite")
app/services/favorite_service.py ADDED
@@ -0,0 +1,171 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException, Depends
2
+ from typing import Optional
3
+ from app.models.favorite_model import BookMyServiceFavoriteModel
4
+ from app.schemas.favorite_schema import (
5
+ FavoriteCreateRequest,
6
+ FavoriteUpdateRequest,
7
+ FavoriteResponse,
8
+ FavoritesListResponse,
9
+ FavoriteStatusResponse,
10
+ FavoriteSuccessResponse
11
+ )
12
+ from app.services.user_service import UserService
13
+ import logging
14
+
15
+ logger = logging.getLogger("favorite_service")
16
+
17
+ class FavoriteService:
18
+
19
+ @staticmethod
20
+ async def add_favorite(
21
+ customer_id: str,
22
+ favorite_data: FavoriteCreateRequest
23
+ ) -> FavoriteSuccessResponse:
24
+ """Add a merchant to user's favorites"""
25
+ try:
26
+ # Create favorite in database
27
+ favorite_doc = await BookMyServiceFavoriteModel.create_favorite(
28
+ customer_id=customer_id,
29
+ merchant_id=favorite_data.merchant_id,
30
+ merchant_category=favorite_data.merchant_category,
31
+ merchant_name=favorite_data.merchant_name,
32
+ notes=favorite_data.notes
33
+ )
34
+
35
+ return FavoriteSuccessResponse(
36
+ success=True,
37
+ message="Merchant added to favorites successfully",
38
+ favorite_id=str(favorite_doc.get("_id")) if favorite_doc.get("_id") else None
39
+ )
40
+
41
+ except HTTPException as e:
42
+ raise e
43
+ except Exception as e:
44
+ logger.error(f"Error adding favorite: {str(e)}", exc_info=True)
45
+ raise HTTPException(status_code=500, detail="Failed to add favorite")
46
+
47
+ @staticmethod
48
+ async def remove_favorite(customer_id: str, merchant_id: str) -> FavoriteSuccessResponse:
49
+ """Remove a merchant from user's favorites"""
50
+ try:
51
+ await BookMyServiceFavoriteModel.delete_favorite(customer_id, merchant_id)
52
+
53
+ return FavoriteSuccessResponse(
54
+ success=True,
55
+ message="Merchant removed from favorites successfully",
56
+ favorite_id=None
57
+ )
58
+
59
+ except HTTPException as e:
60
+ raise e
61
+ except Exception as e:
62
+ logger.error(f"Error removing favorite: {str(e)}", exc_info=True)
63
+ raise HTTPException(status_code=500, detail="Failed to remove favorite")
64
+
65
+ @staticmethod
66
+ async def get_favorites(
67
+ customer_id: str,
68
+ category: Optional[str] = None,
69
+ skip: int = 0,
70
+ limit: int = 50
71
+ ) -> FavoritesListResponse:
72
+ """Get user's favorite merchants"""
73
+ try:
74
+ result = await BookMyServiceFavoriteModel.get_favorites(
75
+ customer_id=customer_id,
76
+ category=category,
77
+ skip=skip,
78
+ limit=limit
79
+ )
80
+
81
+ # Convert MongoDB documents to response format
82
+ favorites = []
83
+ for fav in result["favorites"]:
84
+ favorites.append(FavoriteResponse(
85
+ id=str(fav["_id"]),
86
+ customer_id=fav["customer_id"],
87
+ merchant_id=fav["merchant_id"],
88
+ merchant_category=fav["merchant_category"],
89
+ merchant_name=fav["merchant_name"],
90
+ added_at=fav["added_at"],
91
+ notes=fav.get("notes")
92
+ ))
93
+
94
+ return FavoritesListResponse(
95
+ favorites=favorites,
96
+ total_count=result["total_count"],
97
+ skip=result["skip"],
98
+ limit=result["limit"]
99
+ )
100
+
101
+ except Exception as e:
102
+ logger.error(f"Error getting favorites: {str(e)}", exc_info=True)
103
+ raise HTTPException(status_code=500, detail="Failed to get favorites")
104
+
105
+ @staticmethod
106
+ async def check_favorite_status(customer_id: str, merchant_id: str) -> FavoriteStatusResponse:
107
+ """Check if a merchant is in user's favorites"""
108
+ try:
109
+ is_fav = await BookMyServiceFavoriteModel.is_favorite(customer_id, merchant_id)
110
+
111
+ favorite_id = None
112
+ if is_fav:
113
+ favorite = await BookMyServiceFavoriteModel.get_favorite(customer_id, merchant_id)
114
+ favorite_id = str(favorite["_id"]) if favorite and favorite.get("_id") else None
115
+
116
+ return FavoriteStatusResponse(
117
+ is_favorite=is_fav,
118
+ favorite_id=favorite_id
119
+ )
120
+
121
+ except Exception as e:
122
+ logger.error(f"Error checking favorite status: {str(e)}", exc_info=True)
123
+ raise HTTPException(status_code=500, detail="Failed to check favorite status")
124
+
125
+ @staticmethod
126
+ async def update_favorite_notes(
127
+ customer_id: str,
128
+ merchant_id: str,
129
+ notes_data: FavoriteUpdateRequest
130
+ ) -> FavoriteSuccessResponse:
131
+ """Update notes for a favorite merchant"""
132
+ try:
133
+ await BookMyServiceFavoriteModel.update_favorite_notes(
134
+ customer_id=customer_id,
135
+ merchant_id=merchant_id,
136
+ notes=notes_data.notes
137
+ )
138
+
139
+ return FavoriteSuccessResponse(
140
+ success=True,
141
+ message="Favorite notes updated successfully",
142
+ favorite_id=None
143
+ )
144
+
145
+ except HTTPException as e:
146
+ raise e
147
+ except Exception as e:
148
+ logger.error(f"Error updating favorite notes: {str(e)}", exc_info=True)
149
+ raise HTTPException(status_code=500, detail="Failed to update favorite notes")
150
+
151
+ @staticmethod
152
+ async def get_favorite_details(customer_id: str, merchant_id: str) -> FavoriteResponse:
153
+ """Get detailed information about a specific favorite"""
154
+ try:
155
+ favorite = await BookMyServiceFavoriteModel.get_favorite(customer_id, merchant_id)
156
+
157
+ return FavoriteResponse(
158
+ id=str(favorite["_id"]),
159
+ customer_id=favorite["customer_id"],
160
+ merchant_id=favorite["merchant_id"],
161
+ merchant_category=favorite["merchant_category"],
162
+ merchant_name=favorite["merchant_name"],
163
+ added_at=favorite["added_at"],
164
+ notes=favorite.get("notes")
165
+ )
166
+
167
+ except HTTPException as e:
168
+ raise e
169
+ except Exception as e:
170
+ logger.error(f"Error getting favorite details: {str(e)}", exc_info=True)
171
+ raise HTTPException(status_code=500, detail="Failed to get favorite details")
app/services/user_service.py CHANGED
@@ -44,7 +44,7 @@ class UserService:
44
  phone_number = None
45
 
46
  # Generate OTP - hardcoded for testing purposes
47
- otp = '77777'
48
  logger.debug(f"Generated hardcoded OTP for identifier: {identifier}")
49
 
50
  await BookMyServiceOTPModel.store_otp(identifier, phone_number, otp)
 
44
  phone_number = None
45
 
46
  # Generate OTP - hardcoded for testing purposes
47
+ otp = '777777'
48
  logger.debug(f"Generated hardcoded OTP for identifier: {identifier}")
49
 
50
  await BookMyServiceOTPModel.store_otp(identifier, phone_number, otp)
main.py DELETED
@@ -1,6 +0,0 @@
1
- def main():
2
- print("Hello from bookmyservice-ums!")
3
-
4
-
5
- if __name__ == "__main__":
6
- main()
 
 
 
 
 
 
 
pyproject.toml DELETED
@@ -1,7 +0,0 @@
1
- [project]
2
- name = "bookmyservice-ums"
3
- version = "0.1.0"
4
- description = "Add your description here"
5
- readme = "README.md"
6
- requires-python = ">=3.13"
7
- dependencies = []
 
 
 
 
 
 
 
 
test_phone_functionality.py DELETED
@@ -1,136 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- Test script to verify phone number functionality in UMS service
4
- """
5
-
6
- import requests
7
- import json
8
- import sys
9
-
10
- BASE_URL = "http://127.0.0.1:8001"
11
-
12
- def test_phone_validation():
13
- """Test phone number validation in various endpoints"""
14
- print("🧪 Testing phone number validation...")
15
-
16
- # Test 1: Valid phone number in send-otp endpoint
17
- print("\n1. Testing valid phone number in /auth/send-otp")
18
- response = requests.post(f"{BASE_URL}/auth/send-otp", json={
19
- "phone": "+1234567890"
20
- })
21
- print(f"Status: {response.status_code}")
22
- print(f"Response: {response.json()}")
23
-
24
- # Test 2: Valid email in send-otp endpoint
25
- print("\n2. Testing valid email in /auth/send-otp")
26
- response = requests.post(f"{BASE_URL}/auth/send-otp", json={
27
- "email": "test@example.com"
28
- })
29
- print(f"Status: {response.status_code}")
30
- print(f"Response: {response.json()}")
31
-
32
- # Test 3: Both email and phone provided (should fail)
33
- print("\n3. Testing both email and phone in /auth/send-otp (should fail)")
34
- response = requests.post(f"{BASE_URL}/auth/send-otp", json={
35
- "email": "test@example.com",
36
- "phone": "+1234567890"
37
- })
38
- print(f"Status: {response.status_code}")
39
- print(f"Response: {response.json()}")
40
-
41
- # Test 4: Invalid phone format
42
- print("\n4. Testing invalid phone format in /auth/send-otp")
43
- response = requests.post(f"{BASE_URL}/auth/send-otp", json={
44
- "phone": "invalid-phone"
45
- })
46
- print(f"Status: {response.status_code}")
47
- print(f"Response: {response.json()}")
48
-
49
- # Test 5: New unified endpoint with phone
50
- print("\n5. Testing phone number in /auth/send-otp-login")
51
- response = requests.post(f"{BASE_URL}/auth/send-otp-login", json={
52
- "login_input": "+1234567890"
53
- })
54
- print(f"Status: {response.status_code}")
55
- print(f"Response: {response.json()}")
56
-
57
- # Test 6: New unified endpoint with email
58
- print("\n6. Testing email in /auth/send-otp-login")
59
- response = requests.post(f"{BASE_URL}/auth/send-otp-login", json={
60
- "login_input": "test@example.com"
61
- })
62
- print(f"Status: {response.status_code}")
63
- print(f"Response: {response.json()}")
64
-
65
- # Test 7: Invalid identifier in unified endpoint
66
- print("\n7. Testing invalid identifier in /auth/send-otp-login")
67
- response = requests.post(f"{BASE_URL}/auth/send-otp-login", json={
68
- "login_input": "invalid-identifier"
69
- })
70
- print(f"Status: {response.status_code}")
71
- print(f"Response: {response.json()}")
72
-
73
- def test_registration_validation():
74
- """Test registration endpoint validation"""
75
- print("\n🧪 Testing registration validation...")
76
-
77
- # Test 1: Registration with phone only
78
- print("\n1. Testing registration with phone only")
79
- response = requests.post(f"{BASE_URL}/auth/register", json={
80
- "phone": "+1234567890",
81
- "name": "Test User",
82
- "mode": "otp",
83
- "otp": "123456"
84
- }, headers={"Authorization": "dummy-token"})
85
- print(f"Status: {response.status_code}")
86
- print(f"Response: {response.json()}")
87
-
88
- # Test 2: Registration with email only
89
- print("\n2. Testing registration with email only")
90
- response = requests.post(f"{BASE_URL}/auth/register", json={
91
- "email": "test@example.com",
92
- "name": "Test User",
93
- "mode": "otp",
94
- "otp": "123456"
95
- }, headers={"Authorization": "dummy-token"})
96
- print(f"Status: {response.status_code}")
97
- print(f"Response: {response.json()}")
98
-
99
- # Test 3: Registration with both email and phone (should fail)
100
- print("\n3. Testing registration with both email and phone (should fail)")
101
- response = requests.post(f"{BASE_URL}/auth/register", json={
102
- "email": "test@example.com",
103
- "phone": "+1234567890",
104
- "name": "Test User",
105
- "mode": "otp",
106
- "otp": "123456"
107
- }, headers={"Authorization": "dummy-token"})
108
- print(f"Status: {response.status_code}")
109
- print(f"Response: {response.json()}")
110
-
111
- def main():
112
- """Main test function"""
113
- print("🚀 Starting phone number functionality tests...")
114
- print(f"Testing against: {BASE_URL}")
115
-
116
- try:
117
- # Check if service is running
118
- response = requests.get(f"{BASE_URL}/")
119
- print(f"✅ Service is running: {response.json()}")
120
-
121
- # Run tests
122
- test_phone_validation()
123
- test_registration_validation()
124
-
125
- print("\n✅ All tests completed!")
126
-
127
- except requests.exceptions.ConnectionError:
128
- print(f"❌ Could not connect to {BASE_URL}")
129
- print("Make sure the UMS service is running on port 8001")
130
- sys.exit(1)
131
- except Exception as e:
132
- print(f"❌ Error during testing: {str(e)}")
133
- sys.exit(1)
134
-
135
- if __name__ == "__main__":
136
- main()