MukeshKapoor25 commited on
Commit
b407a42
·
1 Parent(s): ef7a502

Add initial implementation of User Management Service

Browse files

- Create folder structure and necessary files for the service
- Implement FastAPI application with user authentication and OTP functionality
- Set up MongoDB and Redis configurations
- Add user and OTP models, along with services for user registration and login
- Include utility functions for sending SMS and email OTPs
- Define Pydantic schemas for request validation
- Update requirements.txt with necessary dependencies

.env ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ MONGO_URI=mongodb+srv://insightfy:k0KXafAbV8A8NmQK@cluster0.2shrc.mongodb.net/test??ssl=true&ssl_cert_reqs=CERT_NONE
3
+
4
+ DB_NAME=book-my-service
5
+
6
+ DATABASE_URI=postgresql+asyncpg://trans_owner:BookMyService7@ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech/bookmyservice?options=-csearch_path%3Dtrans
7
+
8
+ CACHE_URI=redis-11382.c305.ap-south-1-1.ec2.redns.redis-cloud.com:11382
9
+
10
+ #CACHE_URI=redis-11521.crce182.ap-south-1-1.ec2.redns.redis-cloud.com:11521
11
+
12
+ CACHE_K=dLRZrhU1d5EP9N1CW6grUgsj7MyWIj2i
13
+
14
+
15
+ RAZORPAY_KEY_ID=rzp_test_2UTAol2AFSV5VN
16
+
17
+ RAZORPAY_KEY_SECRET=elb4JNjUw3eLqhVMiLFiRgki
Dockerfile ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Read the doc: https://huggingface.co/docs/hub/spaces-sdks-docker
2
+ # you will also find guides on how best to write your Dockerfile
3
+
4
+ FROM python:3.11-slim-buster AS base
5
+
6
+ RUN useradd -m -u 1000 user
7
+ USER user
8
+ ENV PATH="/home/user/.local/bin:$PATH"
9
+
10
+ WORKDIR /app
11
+
12
+ COPY --chown=user ./requirements.txt requirements.txt
13
+ RUN pip install --no-cache-dir --upgrade -r requirements.txt
14
+
15
+ COPY --chown=user . /app
16
+ CMD ["uvicorn", "app.app:app", "--host", "0.0.0.0", "--port", "7860"]
app/app.py ADDED
@@ -0,0 +1,24 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## bookmyservice-ums/app/app.py
2
+
3
+ from fastapi import FastAPI
4
+ from fastapi.middleware.cors import CORSMiddleware
5
+ from app.routers import user_router, profile_router
6
+
7
+ app = FastAPI(title="BookMyService User Management Service")
8
+
9
+ app.add_middleware(
10
+ CORSMiddleware,
11
+ allow_origins=["*"],
12
+ allow_credentials=True,
13
+ allow_methods=["*"],
14
+ allow_headers=["*"],
15
+ )
16
+
17
+ app.include_router(user_router.router, prefix="/auth", tags=["user_auth"])
18
+ app.include_router(profile_router.router, prefix="/profile", tags=["profile"])
19
+
20
+ @app.get("/")
21
+ def root():
22
+ return {"message": "BookMyService UMS is running"}
23
+
24
+
app/constants/__init__.py ADDED
File without changes
app/core/cache_client.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from redis.asyncio import Redis
3
+ from redis.exceptions import RedisError
4
+ from app.core.config import settings
5
+
6
+ logger = logging.getLogger(__name__)
7
+
8
+ # Parse host and port
9
+ CACHE_HOST, CACHE_PORT = settings.CACHE_URI.split(":")
10
+ CACHE_PORT = int(CACHE_PORT)
11
+
12
+ try:
13
+ redis_client = Redis(
14
+ host=CACHE_HOST,
15
+ port=CACHE_PORT,
16
+ username="default",
17
+ password=settings.CACHE_K,
18
+ decode_responses=True
19
+ )
20
+ logger.info("Connected to Redis.")
21
+ except RedisError as e:
22
+ logger.error(f"Failed to connect to Redis: {e}")
23
+ raise
24
+
25
+ async def get_redis() -> Redis:
26
+ return redis_client
app/core/config.py ADDED
@@ -0,0 +1,34 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from dotenv import load_dotenv
2
+ import os
3
+
4
+ load_dotenv()
5
+
6
+ class Settings:
7
+ # MongoDB
8
+ MONGO_URI: str = os.getenv("MONGO_URI")
9
+ DB_NAME: str = os.getenv("DB_NAME")
10
+
11
+ # Redis
12
+ CACHE_URI: str = os.getenv("CACHE_URI")
13
+ CACHE_K: str = os.getenv("CACHE_K")
14
+
15
+ SECRET_KEY: str = os.getenv("SECRET_KEY", "B00Kmyservice@7")
16
+ ALGORITHM: str = os.getenv("ALGORITHM", "HS256")
17
+
18
+ TWILIO_ACCOUNT_SID: str = os.getenv("TWILIO_ACCOUNT_SID")
19
+ TWILIO_AUTH_TOKEN: str = os.getenv("TWILIO_AUTH_TOKEN")
20
+ TWILIO_SMS_FROM: str = os.getenv("TWILIO_SMS_FROM")
21
+
22
+ SMTP_HOST: str = os.getenv("SMTP_HOST")
23
+ SMTP_PORT: int = int(os.getenv("SMTP_PORT", "587"))
24
+ SMTP_USER: str = os.getenv("SMTP_USER")
25
+ SMTP_PASS: str = os.getenv("SMTP_PASS")
26
+ SMTP_FROM: str = os.getenv("SMTP_FROM")
27
+
28
+ def __post_init__(self):
29
+ if not self.MONGO_URI or not self.DB_NAME:
30
+ raise ValueError("MongoDB URI or DB_NAME not configured.")
31
+ if not self.CACHE_URI or not self.CACHE_K:
32
+ raise ValueError("Redis URI or password (CACHE_K) not configured.")
33
+
34
+ settings = Settings()
app/core/nosql_client.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from motor.motor_asyncio import AsyncIOMotorClient
3
+ from app.core.config import settings
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ mongo_client = AsyncIOMotorClient(settings.MONGO_URI)
8
+ db = mongo_client[settings.DB_NAME]
9
+
10
+ async def get_mongo_client() -> AsyncIOMotorClient:
11
+ return mongo_client
app/dependencies/__init__.py ADDED
File without changes
app/models/__init__.py ADDED
File without changes
app/models/otp_model.py ADDED
@@ -0,0 +1,59 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException
2
+ from app.core.cache_client import get_redis
3
+ from app.utils.sms_utils import send_sms_otp
4
+ from app.utils.email_utils import send_email_otp
5
+ from app.utils.common_utils import is_email
6
+
7
+ class BookMyServiceOTPModel:
8
+ OTP_TTL = 300 # 5 minutes
9
+ RATE_LIMIT_MAX = 3
10
+ RATE_LIMIT_WINDOW = 600 # 10 minutes
11
+
12
+ @staticmethod
13
+ async def store_otp(identifier: str, phone: str, otp: str, ttl: int = OTP_TTL):
14
+ redis = await get_redis()
15
+
16
+ # Rate limit: max 3 OTPs per 10 minutes
17
+ rate_key = f"otp_rate_limit:{identifier}"
18
+ attempts = await redis.incr(rate_key)
19
+ if attempts == 1:
20
+ await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
21
+ elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
22
+ raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
23
+
24
+ # Store OTP
25
+ await redis.setex(f"bms_otp:{identifier}", ttl, otp)
26
+
27
+ # Send OTP via SMS, fallback to Email if identifier is email
28
+ try:
29
+ sid = send_sms_otp(phone, otp)
30
+ print(f"OTP {otp} sent to {phone} via SMS. SID: {sid}")
31
+ except Exception as sms_error:
32
+ print(f"⚠️ SMS failed: {sms_error}")
33
+ if is_email(identifier):
34
+ try:
35
+ send_email_otp(identifier, otp)
36
+ print(f"✅ OTP {otp} sent to {identifier} via email fallback.")
37
+ except Exception as email_error:
38
+ raise HTTPException(status_code=500, detail=f"SMS and email both failed: {email_error}")
39
+ else:
40
+ raise HTTPException(status_code=500, detail="SMS failed and no email fallback available.")
41
+
42
+ @staticmethod
43
+ async def verify_otp(identifier: str, otp: str):
44
+ redis = await get_redis()
45
+ key = f"bms_otp:{identifier}"
46
+ stored = await redis.get(key)
47
+ if stored and stored == otp:
48
+ await redis.delete(key)
49
+ return True
50
+ return False
51
+
52
+ @staticmethod
53
+ async def read_otp(identifier: str):
54
+ redis = await get_redis()
55
+ key = f"bms_otp:{identifier}"
56
+ otp = await redis.get(key)
57
+ if otp:
58
+ return otp
59
+ raise HTTPException(status_code=404, detail="OTP not found or expired")
app/models/user_model.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException
2
+ from app.core.nosql_client import db
3
+ from app.utils.common_utils import is_email # Assumes utility exists
4
+
5
+ class BookMyServiceUserModel:
6
+ collection = db["book_my_service_users"]
7
+
8
+ @staticmethod
9
+ async def find_by_email(email: str):
10
+ return await BookMyServiceUserModel.collection.find_one({"email": email})
11
+
12
+ @staticmethod
13
+ async def find_by_mobile(mobile: str):
14
+ return await BookMyServiceUserModel.collection.find_one({"mobile": mobile})
15
+
16
+ @staticmethod
17
+ async def find_by_identifier(identifier: str):
18
+ if is_email(identifier):
19
+ user = await BookMyServiceUserModel.find_by_email(identifier)
20
+ else:
21
+ user = await BookMyServiceUserModel.find_by_mobile(identifier)
22
+
23
+ if not user:
24
+ raise HTTPException(status_code=404, detail="User not found with this email or mobile")
25
+ return user
26
+
27
+ @staticmethod
28
+ async def exists_by_email_or_phone(email: str, phone: str) -> bool:
29
+ return await BookMyServiceUserModel.collection.find_one({
30
+ "$or": [{"email": email}, {"mobile": phone}]
31
+ }) is not None
32
+
33
+ @staticmethod
34
+ async def create(user_data: dict):
35
+ result = await BookMyServiceUserModel.collection.insert_one(user_data)
36
+ return result.inserted_id
37
+
38
+ @staticmethod
39
+ async def update_by_identifier(identifier: str, update_fields: dict):
40
+ query = {"email": identifier} if is_email(identifier) else {"mobile": identifier}
41
+ result = await BookMyServiceUserModel.collection.update_one(query, {"$set": update_fields})
42
+ if result.matched_count == 0:
43
+ raise HTTPException(status_code=404, detail="User not found")
44
+ return result.modified_count > 0
app/repositories/__init__.py ADDED
File without changes
app/routers/profile_router.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ## bookmyservice-ums/app/routers/profile_router.py
3
+
4
+ from fastapi import APIRouter
5
+
6
+ router = APIRouter()
7
+
8
+ @router.get("/me")
9
+ def get_profile():
10
+ return {"user": "Sample user profile - implement real logic here"}
11
+
app/routers/user_router.py ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ## bookmyservice-ums/app/routers/user_router.py
2
+
3
+ from fastapi import APIRouter, HTTPException
4
+ from app.schemas.user_schema import OTPRequest, OTPVerifyRequest, UserRegisterRequest, OAuthLoginRequest, TokenResponse
5
+ from app.services.user_service import UserService
6
+ from app.utils.jwt import create_access_token
7
+
8
+ router = APIRouter()
9
+
10
+ @router.post("/send-otp")
11
+ async def send_otp_handler(payload: OTPRequest):
12
+ identifier = payload.email or payload.phone
13
+ if not identifier:
14
+ raise HTTPException(status_code=400, detail="Email or phone required")
15
+ await UserService.send_otp(identifier, payload.phone or "N/A")
16
+ return {"message": "OTP sent"}
17
+
18
+ @router.post("/verify-otp", response_model=TokenResponse)
19
+ async def verify_otp_handler(payload: OTPVerifyRequest):
20
+ return await UserService.verify_otp_and_login(payload.login_input, payload.otp)
21
+
22
+ @router.post("/oauth-login", response_model=TokenResponse)
23
+ async def oauth_login_handler(payload: OAuthLoginRequest):
24
+ user_id = f"{payload.provider}_{payload.token}" # In production: validate token and extract user info
25
+ return await UserService.verify_otp_and_login(user_id, otp="") # simulate OTP match via token
26
+
27
+ @router.post("/register", response_model=TokenResponse)
28
+ async def register_user(payload: UserRegisterRequest):
29
+ return await UserService.register(payload)
app/schemas/__init__.py ADDED
File without changes
app/schemas/user_schema.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr
2
+ from typing import Optional, Literal
3
+
4
+ # Used for OTP-based or OAuth-based user registration
5
+ class UserRegisterRequest(BaseModel):
6
+ full_name: str
7
+ email: Optional[EmailStr] = None
8
+ phone: Optional[str] = None
9
+ otp: Optional[str] = None
10
+ oauth_token: Optional[str] = None
11
+ provider: Optional[Literal["google", "apple"]] = None
12
+ mode: Literal["otp", "oauth"]
13
+
14
+ # Used in login form (optional display name prefilled from local storage)
15
+ class UserLoginRequest(BaseModel):
16
+ name: Optional[str] = None
17
+ email: EmailStr
18
+
19
+ # OTP request via email or phone
20
+ class OTPRequest(BaseModel):
21
+ email: Optional[EmailStr] = None
22
+ phone: Optional[str] = None
23
+
24
+ # Generic OTP request using single login input
25
+ class OTPRequestWithLogin(BaseModel):
26
+ login_input: str # email or phone
27
+
28
+ # OTP verification input
29
+ class OTPVerifyRequest(BaseModel):
30
+ login_input: str
31
+ otp: str
32
+
33
+ # OAuth login using Google/Apple
34
+ class OAuthLoginRequest(BaseModel):
35
+ provider: Literal["google", "apple"]
36
+ token: str
37
+
38
+ # JWT Token response format
39
+ class TokenResponse(BaseModel):
40
+ access_token: str
41
+ token_type: str = "bearer"
42
+
43
+ # Optional: profile info response post-login
44
+ class UserProfileResponse(BaseModel):
45
+ user_id: str
46
+ full_name: str
47
+ email: Optional[EmailStr] = None
app/services/__init__.py ADDED
File without changes
app/services/otp_service.py ADDED
@@ -0,0 +1,47 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from fastapi import HTTPException
2
+ from app.core.cache_client import get_redis
3
+ from app.utils.sms_utils import send_sms_otp
4
+ from app.utils.email_utils import send_email_otp
5
+ from app.utils.common_utils import is_email
6
+
7
+ class BookMyServiceOTPModel:
8
+ OTP_TTL = 300 # 5 minutes
9
+ RATE_LIMIT_MAX = 3
10
+ RATE_LIMIT_WINDOW = 600 # 10 minutes
11
+
12
+ @staticmethod
13
+ async def store_otp(identifier: str, phone: str, otp: str, ttl: int = OTP_TTL):
14
+ redis = await get_redis()
15
+
16
+ rate_key = f"otp_rate_limit:{identifier}"
17
+ attempts = await redis.incr(rate_key)
18
+ if attempts == 1:
19
+ await redis.expire(rate_key, BookMyServiceOTPModel.RATE_LIMIT_WINDOW)
20
+ elif attempts > BookMyServiceOTPModel.RATE_LIMIT_MAX:
21
+ raise HTTPException(status_code=429, detail="Too many OTP requests. Try again later.")
22
+
23
+ await redis.setex(f"bms_otp:{identifier}", ttl, otp)
24
+
25
+ try:
26
+ sid = send_sms_otp(phone, otp)
27
+ print(f"✅ OTP {otp} sent to {phone}. SID: {sid}")
28
+ except Exception as sms_error:
29
+ print(f"⚠️ SMS failed: {sms_error}")
30
+ if is_email(identifier):
31
+ try:
32
+ send_email_otp(identifier, otp)
33
+ print(f"✅ OTP {otp} sent to {identifier} via email fallback.")
34
+ except Exception as email_error:
35
+ raise HTTPException(status_code=500, detail=f"SMS and email both failed: {email_error}")
36
+ else:
37
+ raise HTTPException(status_code=500, detail="SMS failed and no fallback available.")
38
+
39
+ @staticmethod
40
+ async def verify_otp(identifier: str, otp: str):
41
+ redis = await get_redis()
42
+ key = f"bms_otp:{identifier}"
43
+ stored = await redis.get(key)
44
+ if stored and stored == otp:
45
+ await redis.delete(key)
46
+ return True
47
+ return False
app/services/user_service.py ADDED
@@ -0,0 +1,80 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import random
2
+ from jose import jwt
3
+ from datetime import datetime, timedelta
4
+ from fastapi import HTTPException
5
+ from app.models.user_model import BookMyServiceUserModel
6
+ from app.models.otp_model import BookMyServiceOTPModel
7
+ from app.core.config import settings
8
+ from app.utils.common_utils import is_email
9
+ from app.schemas.user_schema import UserRegisterRequest
10
+ import logging
11
+
12
+ logger = logging.getLogger("user_service")
13
+
14
+ class UserService:
15
+ @staticmethod
16
+ async def send_otp(identifier: str, phone: str):
17
+ otp = str(random.randint(100000, 999999))
18
+ await BookMyServiceOTPModel.store_otp(identifier, phone, otp)
19
+ logger.debug(f"OTP sent to {identifier}: {otp}")
20
+
21
+ @staticmethod
22
+ async def verify_otp_and_login(identifier: str, otp: str):
23
+ if not await BookMyServiceOTPModel.verify_otp(identifier, otp):
24
+ logger.debug(f"Invalid or expired OTP for identifier: {identifier}")
25
+ raise HTTPException(status_code=400, detail="Invalid or expired OTP")
26
+
27
+ user = await BookMyServiceUserModel.find_by_identifier(identifier)
28
+ if not user:
29
+ logger.debug(f"No user found for identifier: {identifier}")
30
+ raise HTTPException(status_code=404, detail="User not found")
31
+
32
+ token_data = {
33
+ "sub": user.get("user_id"),
34
+ "user_id": user.get("user_id"),
35
+ "email": user.get("email"),
36
+ "role": "user",
37
+ "exp": datetime.utcnow() + timedelta(hours=8)
38
+ }
39
+ access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
40
+ return {"access_token": access_token}
41
+
42
+ @staticmethod
43
+ async def register(data: UserRegisterRequest):
44
+ if data.mode == "otp":
45
+ identifier = data.email or data.phone
46
+ if not data.otp or not identifier:
47
+ raise HTTPException(status_code=400, detail="OTP and email/phone required")
48
+ if not await BookMyServiceOTPModel.verify_otp(identifier, data.otp):
49
+ raise HTTPException(status_code=400, detail="Invalid or expired OTP")
50
+ user_id = f"otp_{identifier}"
51
+
52
+ elif data.mode == "oauth":
53
+ if not data.oauth_token or not data.provider:
54
+ raise HTTPException(status_code=400, detail="OAuth token and provider required")
55
+ user_id = f"{data.provider}_{data.oauth_token}"
56
+
57
+ else:
58
+ raise HTTPException(status_code=400, detail="Unsupported registration mode")
59
+
60
+ if await BookMyServiceUserModel.collection.find_one({"user_id": user_id}):
61
+ raise HTTPException(status_code=409, detail="User already registered")
62
+
63
+ user_doc = {
64
+ "user_id": user_id,
65
+ "full_name": data.full_name,
66
+ "email": data.email,
67
+ "phone": data.phone,
68
+ "created_at": datetime.utcnow()
69
+ }
70
+ await BookMyServiceUserModel.collection.insert_one(user_doc)
71
+
72
+ token_data = {
73
+ "sub": user_id,
74
+ "user_id": user_id,
75
+ "email": data.email,
76
+ "role": "user",
77
+ "exp": datetime.utcnow() + timedelta(hours=8)
78
+ }
79
+ access_token = jwt.encode(token_data, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
80
+ return {"access_token": access_token}
app/settings.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ## bookmyservice-ums/settings.py
3
+
4
+ from pydantic import BaseSettings
5
+
6
+ class Settings(BaseSettings):
7
+ SECRET_KEY: str = "secret-key-placeholder"
8
+ ALGORITHM: str = "HS256"
9
+
10
+ settings = Settings()
11
+
app/tests/__init__.py ADDED
File without changes
app/utils/__init__.py ADDED
File without changes
app/utils/common_utils.py ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ import re
2
+
3
+ def is_email(identifier: str) -> bool:
4
+ return re.match(r"[^@]+@[^@]+\.[^@]+", identifier) is not None
app/utils/email_utils.py ADDED
@@ -0,0 +1,15 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import smtplib
2
+ from email.mime.text import MIMEText
3
+ from app.core.config import settings
4
+
5
+ async def send_email_otp(to_email: str, otp: str):
6
+ msg = MIMEText(f"Your OTP is {otp}. It is valid for 5 minutes.")
7
+ msg["Subject"] = "Your One-Time Password"
8
+ msg["From"] = settings.SMTP_FROM
9
+ msg["To"] = to_email
10
+
11
+ server = smtplib.SMTP(settings.SMTP_HOST, settings.SMTP_PORT)
12
+ server.starttls()
13
+ server.login(settings.SMTP_USER, settings.SMTP_PASS)
14
+ server.send_message(msg)
15
+ server.quit()
app/utils/jwt.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ ## bookmyservice-ums/app/utils/jwt.py
3
+
4
+ from jose import jwt
5
+ from datetime import datetime, timedelta
6
+
7
+ SECRET_KEY = "secret-key-placeholder"
8
+ ALGORITHM = "HS256"
9
+
10
+ def create_access_token(data: dict, expires_minutes: int = 60):
11
+ to_encode = data.copy()
12
+ expire = datetime.utcnow() + timedelta(minutes=expires_minutes)
13
+ to_encode.update({"exp": expire})
14
+ return jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
app/utils/sms_utils.py ADDED
@@ -0,0 +1,14 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ from twilio.rest import Client
3
+ from app.core.config import settings
4
+
5
+ def send_sms_otp(phone: str, otp: str) -> str:
6
+ client = Client(settings.TWILIO_ACCOUNT_SID, settings.TWILIO_AUTH_TOKEN)
7
+
8
+ message = client.messages.create(
9
+ from_=settings.TWILIO_SMS_FROM,
10
+ body=f"Your OTP is {otp}",
11
+ to=phone
12
+ )
13
+
14
+ return message.sid
create_ums_structure.sh ADDED
@@ -0,0 +1,22 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/bin/bash
2
+
3
+ # Base directory (change this as needed)
4
+ BASE_DIR="bookmyservice-ums/app"
5
+
6
+ # Create folder structure
7
+ mkdir -p $BASE_DIR/models
8
+ mkdir -p $BASE_DIR/schemas
9
+ mkdir -p $BASE_DIR/routers
10
+ mkdir -p $BASE_DIR/services
11
+ mkdir -p $BASE_DIR/repositories
12
+ mkdir -p $BASE_DIR/utils
13
+ mkdir -p $BASE_DIR/constants
14
+ mkdir -p $BASE_DIR/dependencies
15
+ mkdir -p $BASE_DIR/tests
16
+
17
+ # Add __init__.py to make each a Python package
18
+ for dir in models schemas routers services repositories utils constants dependencies tests; do
19
+ touch "$BASE_DIR/$dir/__init__.py"
20
+ done
21
+
22
+ echo "📁 Folder structure for User Management Service created under $BASE_DIR"
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+
2
+ fastapi
3
+ uvicorn
4
+ python-jose
5
+ pydantic
6
+ twilio