Prabha-AIMLOPS commited on
Commit
2cd3d4e
·
verified ·
1 Parent(s): f011634

Upload 24 files

Browse files
.env ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ MONGO_URI=mongodb+srv://appliedcommerce:xaqtoj-gikdaw-6giQdy@cluster0.qpc5d.mongodb.net/test??ssl=true&ssl_cert_reqs=CERT_NONE
2
+ DB_NAME=ac-user-auth
3
+
4
+ DATABASE_URI=postgresql+asyncpg://trans_owner:BookMyService7@ep-sweet-surf-a1qeduoy.ap-southeast-1.aws.neon.tech/bookmyservice?options=-csearch_path%3Dtrans
5
+
6
+ CACHE_URI=redis-13036.c84.us-east-1-2.ec2.redns.redis-cloud.com:13036
7
+ CACHE_K=vLiMNdXeJZtvRUKUbk0Ck4HeGchzeHP6
8
+
9
+ #JWT related configurations
10
+ JWT_SECRET=book-my-service
11
+ JWT_EXPIRY_MINUTES=60
app/__init__.py ADDED
File without changes
app/config/__init__.py ADDED
File without changes
app/config/nosql.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Module for NoSQL database connections and configurations.
3
+ """
4
+
5
+ import logging
6
+ from motor.motor_asyncio import AsyncIOMotorClient
7
+ from redis.asyncio import Redis
8
+ from redis.exceptions import RedisError
9
+ from dotenv import load_dotenv
10
+ import os
11
+
12
+ load_dotenv()
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+ # MongoDB connection
17
+ MONGO_URI = os.getenv("MONGO_URI")
18
+ DB_NAME = os.getenv("DB_NAME")
19
+ mongo_client = AsyncIOMotorClient(MONGO_URI)
20
+ db = mongo_client[DB_NAME]
21
+
22
+ # Redis connection
23
+ CACHE_URI = os.getenv("CACHE_URI")
24
+ CACHE_K = os.getenv("CACHE_K")
25
+
26
+ if not MONGO_URI or not DB_NAME:
27
+ raise ValueError("MongoDB URI or Database Name is not set in the environment variables.")
28
+
29
+ if not CACHE_URI or not CACHE_K:
30
+ raise ValueError("Redis URI or Database Name is not set in the environment variables.")
31
+
32
+ # Parse Redis host and port
33
+ CACHE_HOST, CACHE_PORT = CACHE_URI.split(":")
34
+ CACHE_PORT = int(CACHE_PORT)
35
+
36
+ # Initialize Redis client
37
+ try:
38
+ redis_client = Redis(
39
+ host=CACHE_HOST,
40
+ port=CACHE_PORT,
41
+ username="default",
42
+ password=CACHE_K,
43
+ decode_responses=True
44
+ )
45
+ logger.info("Connected to Redis.")
46
+ except RedisError as e:
47
+ logger.error(f"Failed to connect to Redis: {e}")
48
+ raise
49
+
50
+ async def get_mongo_client() -> AsyncIOMotorClient:
51
+ return mongo_client
app/controllers/__init__.py ADDED
File without changes
app/controllers/customer_controller.py ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Optional
3
+ from datetime import datetime, timedelta
4
+ from fastapi import APIRouter, HTTPException, Query, Depends, status, Body
5
+ from fastapi.responses import JSONResponse
6
+ #from fastapi.security import OAuth2PasswordRequestForm, OAuth2PasswordBearer
7
+ #from jose import JWTError, jwt
8
+ from app.services.customer_services import get_otp_from_cache, validate_email_mobile, verify_otp_and_save_customer
9
+ from app.models.customer import CustomerRegister
10
+
11
+
12
+ # Initialize router and logger
13
+ router = APIRouter(prefix="/customer")
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+
18
+ @router.post("/register")
19
+ async def register_customer(user: CustomerRegister = Body(...)):
20
+ """
21
+ API endpoint to register a new customer.
22
+
23
+ Args:
24
+ user (CustomerRegister): The details of the customer to register.
25
+
26
+ Returns:
27
+ dict: Confirmation message indicating email and SMS verification pending.
28
+ """
29
+ try:
30
+ logger.info("Registering a new customer with email: %s and mobile: %s", user.email, user.mobile)
31
+
32
+ # Validate email and mobile format
33
+ validation_result = await validate_email_mobile(user.email, user.mobile)
34
+ if validation_result["status"] == "error":
35
+ logger.error("Validation failed: %s", validation_result["errors"])
36
+ raise HTTPException(status_code=400, detail=validation_result["errors"])
37
+
38
+ return user.dict()
39
+
40
+ except HTTPException as e:
41
+ logger.error("Failed to register customer: %s", e.detail)
42
+ raise e
43
+ except Exception as e:
44
+ logger.error("Unexpected error while registering customer: %s", e)
45
+ raise HTTPException(status_code=500, detail="Failed to register customer") from e
46
+
47
+ @router.post("/otp/verify")
48
+ async def customer_otp_verification(user: CustomerRegister = Body(...)):
49
+ """
50
+ API endpoint to validate OTPs and save the complete customer object.
51
+
52
+ Args:
53
+ user (CustomerRegister): The complete customer object including email, mobile, and OTPs.
54
+
55
+ Returns:
56
+ dict: Confirmation message indicating success or failure.
57
+ """
58
+ try:
59
+ logger.info("Verifying OTPs and registering customer with email: %s and mobile: %s", user.email, user.mobile)
60
+ # Verify OTPs and save customer
61
+ result = await verify_otp_and_save_customer(user)
62
+ if result["status"] == "error":
63
+ raise HTTPException(status_code=400, detail=result["errors"])
64
+
65
+ return {"message": "Customer successfully registered and verified."}
66
+
67
+ except HTTPException as e:
68
+ logger.error("Failed to verify and register customer: %s", e.detail)
69
+ raise e
70
+ except Exception as e:
71
+ logger.error("Unexpected error while verifying and registering customer: %s", e)
72
+ raise HTTPException(status_code=500, detail="Failed to verify and register customer") from e
73
+
74
+ @router.get("/otp/get")
75
+ async def get_otps(key: str = Query(..., description="The email or mobile number to retrieve the OTP for")):
76
+ """
77
+ API endpoint to retrieve the email or SMS OTP from Redis cache.
78
+
79
+ Args:
80
+ key (str): The email or mobile number to retrieve the OTP for.
81
+
82
+ Returns:
83
+ dict: The OTP data if found, or an error message if not found.
84
+ """
85
+ try:
86
+ logger.info("Retrieving OTP for key: %s", key)
87
+
88
+ # Retrieve OTP from Redis
89
+ otp_data = await get_otp_from_cache(key)
90
+ if not otp_data:
91
+ raise HTTPException(status_code=404, detail="OTP not found or expired")
92
+
93
+ return {"key": key, "otp_data": otp_data}
94
+
95
+ except HTTPException as e:
96
+ logger.error("Failed to retrieve OTP for key %s: %s", key, e.detail)
97
+ raise e
98
+ except Exception as e:
99
+ logger.error("Unexpected error while retrieving OTP for key %s: %s", key, e)
100
+ raise HTTPException(status_code=500, detail="Failed to retrieve OTP") from e
app/controllers/merchant_controller.py ADDED
@@ -0,0 +1,97 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import logging
2
+ from typing import Optional
3
+ from datetime import datetime, timedelta
4
+ from fastapi import APIRouter, HTTPException, Query, Depends, status, Body
5
+ from app.models.merchant import MerchantRegister
6
+ from app.services.merchant_services import get_otp_from_cache, verify_otp_and_save_merchant, validate_merchant
7
+
8
+
9
+ # Initialize router and logger
10
+ router = APIRouter(prefix="/merchant")
11
+ logger = logging.getLogger(__name__)
12
+
13
+
14
+
15
+ @router.post("/register")
16
+ async def register_customer(user: MerchantRegister = Body(...)):
17
+ """
18
+ API endpoint to register a new Merchant.
19
+
20
+ Args:
21
+ user (MerchantRegister): The details of the Merchant to register.
22
+
23
+ Returns:
24
+ dict: Confirmation message indicating email and SMS verification pending.
25
+ """
26
+ try:
27
+ logger.info("Registering a new Merchant with Merchant name : %s, email: %s and mobile: %s", user.merchantname, user.email, user.mobile)
28
+
29
+ # Validate email and mobile format
30
+ validation_result = await validate_merchant(user)
31
+ if validation_result["status"] == "error":
32
+ logger.error("Validation failed: %s", validation_result["errors"])
33
+ raise HTTPException(status_code=400, detail=validation_result["errors"])
34
+
35
+ return user.dict()
36
+
37
+ except HTTPException as e:
38
+ logger.error("Failed to register merchant: %s", e.detail)
39
+ raise e
40
+ except Exception as e:
41
+ logger.error("Unexpected error while registering merchant: %s", e)
42
+ raise HTTPException(status_code=500, detail="Failed to register Merchant") from e
43
+
44
+ @router.post("/otp/verify")
45
+ async def customer_otp_verification(user: MerchantRegister = Body(...)):
46
+ """
47
+ API endpoint to validate OTPs and save the complete Merchant object.
48
+
49
+ Args:
50
+ user (MerchantRegister): The complete Merchant object including email, mobile, and OTPs.
51
+
52
+ Returns:
53
+ dict: Confirmation message indicating success or failure.
54
+ """
55
+ try:
56
+ logger.info("Verifying OTPs and registering merchant with email: %s and mobile: %s", user.email, user.mobile)
57
+ # Verify OTPs and save customer
58
+ result = await verify_otp_and_save_merchant(user)
59
+ if result["status"] == "error":
60
+ raise HTTPException(status_code=400, detail=result["errors"])
61
+
62
+ return {"message": "Merchant successfully registered and verified."}
63
+
64
+ except HTTPException as e:
65
+ logger.error("Failed to verify and register merchant: %s", e.detail)
66
+ raise e
67
+ except Exception as e:
68
+ logger.error("Unexpected error while verifying and registering merchant: %s", e)
69
+ raise HTTPException(status_code=500, detail="Failed to verify and register merchant") from e
70
+
71
+ @router.get("/otp/get")
72
+ async def get_otps(key: str = Query(..., description="The email or mobile number to retrieve the OTP for")):
73
+ """
74
+ API endpoint to retrieve the email or SMS OTP from Redis cache.
75
+
76
+ Args:
77
+ key (str): The email or mobile number to retrieve the OTP for.
78
+
79
+ Returns:
80
+ dict: The OTP data if found, or an error message if not found.
81
+ """
82
+ try:
83
+ logger.info("Retrieving OTP for key: %s", key)
84
+
85
+ # Retrieve OTP from Redis
86
+ otp_data = await get_otp_from_cache(key)
87
+ if not otp_data:
88
+ raise HTTPException(status_code=404, detail="OTP not found or expired")
89
+
90
+ return {"key": key, "otp_data": otp_data}
91
+
92
+ except HTTPException as e:
93
+ logger.error("Failed to retrieve OTP for key %s: %s", key, e.detail)
94
+ raise e
95
+ except Exception as e:
96
+ logger.error("Unexpected error while retrieving OTP for key %s: %s", key, e)
97
+ raise HTTPException(status_code=500, detail="Failed to retrieve OTP") from e
app/main.py ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Main application module for Customer Hub Service.
3
+ """
4
+ import logging
5
+ from fastapi import FastAPI
6
+ from fastapi.middleware.cors import CORSMiddleware
7
+ from app.routers.router import router
8
+
9
+ # Configure logging
10
+ logging.basicConfig(level=logging.INFO)
11
+ #logger = logging.getLogger(__name__)
12
+
13
+ app = FastAPI(
14
+ title="Authentication API's",
15
+ description="API for managing registration and login related services",
16
+ version="1.0.0",
17
+ )
18
+
19
+ # CORS configuration
20
+ app.add_middleware(
21
+ CORSMiddleware,
22
+ allow_origins=["*"], # Restrict to specific domains in production
23
+ allow_credentials=True,
24
+ allow_methods=["*"],
25
+ allow_headers=["*"],
26
+ )
27
+
28
+ # Register routers
29
+ app.include_router(router, prefix="/api/v1", )
30
+
31
+ # Ensure there is no trailing newline
app/models/__init__.py ADDED
File without changes
app/models/customer.py ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr, Field
2
+ from datetime import datetime
3
+
4
+ class CustomerRegister(BaseModel):
5
+ """
6
+ Pydantic model for customer registration request payload.
7
+ """
8
+ email: EmailStr = Field(..., description="The email address of the customer")
9
+ mobile: str = Field(..., description="The 10-digit mobile number of the customer")
10
+ status: str = Field("pending-verification", description="The status of the customer account")
11
+ email_ver_code: str = Field(..., description="The email verification code")
12
+ mobile_ver_code: str = Field(..., description="The mobile verification code")
13
+ created_at: datetime = Field(default_factory=datetime.now, description="The timestamp when the customer was created")
app/models/merchant.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from pydantic import BaseModel, EmailStr, Field
2
+ from datetime import datetime
3
+
4
+ class MerchantRegister(BaseModel):
5
+ """
6
+ Pydantic model for Merchant registration request payload.
7
+ """
8
+ merchant_name: str = Field(..., description="The name of the Merchant")
9
+ merchant_id: str = Field(..., description="The unique identifier for the Merchant")
10
+ country: str = Field(..., description="The country where the Merchant is located")
11
+ city: str = Field(..., description="The city where the Merchant is located")
12
+ area: str = Field(..., description="The area where the Merchant is located")
13
+ email: EmailStr = Field(..., description="The email address of the customer")
14
+ mobile: str = Field(..., description="The 10-digit mobile number of the customer")
15
+ status: str = Field("pending-verification", description="The status of the customer account")
16
+ email_ver_code: str = Field(..., description="The email verification code")
17
+ mobile_ver_code: str = Field(..., description="The mobile verification code")
18
+ created_at: datetime = Field(default_factory=datetime.now, description="The timestamp when the customer was created")
app/repositories/__init__.py ADDED
File without changes
app/repositories/cache_repository.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import logging
3
+ from app.config.nosql import redis_client
4
+
5
+ logger = logging.getLogger(__name__)
6
+
7
+ async def get_otp(key: str) -> dict:
8
+ """
9
+ Retrieve OTP data from Redis.
10
+
11
+ Args:
12
+ key (str): The key to retrieve the OTP data (e.g., email or mobile).
13
+
14
+ Returns:
15
+ dict: The OTP data if found, None otherwise.
16
+ """
17
+ try:
18
+ # Construct the Redis key
19
+ redis_key = f"otp:{key}"
20
+
21
+ # Retrieve the value from Redis
22
+ value = await redis_client.get(redis_key)
23
+
24
+ # If value exists, parse it as JSON and return
25
+ if value:
26
+ return json.loads(value)
27
+
28
+ # Return None if no value is found
29
+ return None
30
+ except Exception as e:
31
+ # Log the error and re-raise it
32
+ logger.error(f"Failed to retrieve OTP for key {key}: {e}")
33
+ raise
34
+
35
+ async def set_otp(key: str, otp_data: dict):
36
+ """
37
+ Store OTP data in Redis with an expiry time.
38
+
39
+ Args:
40
+ key (str): The key to associate the OTP with (e.g., email or mobile).
41
+ otp_data (dict): The OTP data to store, including the OTP and expiry duration.
42
+ """
43
+ try:
44
+ # Construct the Redis key
45
+ redis_key = f"otp:{key}"
46
+
47
+ # Extract expiry duration in seconds
48
+ expiry_duration = otp_data.get("expiry_duration", 15 * 60) # Default to 15 minutes if not provided
49
+
50
+ # Store the OTP data in Redis with the specified expiry
51
+ await redis_client.setex(redis_key, expiry_duration, json.dumps(otp_data))
52
+ except Exception as e:
53
+ # Log the error and re-raise it
54
+ logger.error(f"Failed to store OTP for key {key}: {e}")
55
+ raise
app/repositories/db_repository.py ADDED
@@ -0,0 +1,101 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.config.nosql import db
2
+
3
+ async def save_customer(customer_data: dict):
4
+ """
5
+ Save customer details to MongoDB.
6
+
7
+ Args:
8
+ customer_data (dict): The customer data to save.
9
+ """
10
+ await db["customers"].insert_one(customer_data)
11
+
12
+ async def get_customer_by_email(email: str) -> dict:
13
+ """
14
+ Retrieve a customer by email from the database.
15
+
16
+ Args:
17
+ email (str): The email address to search for.
18
+
19
+ Returns:
20
+ dict: The customer data if found, None otherwise.
21
+ """
22
+ return await db["customers"].find_one({"email": email})
23
+
24
+
25
+ async def get_customer_by_mobile(mobile: str) -> dict:
26
+ """
27
+ Retrieve a customer by mobile number from the database.
28
+
29
+ Args:
30
+ mobile (str): The mobile number to search for.
31
+
32
+ Returns:
33
+ dict: The customer data if found, None otherwise.
34
+ """
35
+ return await db["customers"].find_one({"mobile": mobile})
36
+
37
+
38
+ async def get_merchant_by_name(name: str) -> dict:
39
+ """
40
+ Retrieve a merchant by name from the database.
41
+
42
+ Args:
43
+ name (str): The name of the merchant.
44
+
45
+ Returns:
46
+ dict: The merchant data if found, None otherwise.
47
+ """
48
+ return await db["merchants"].find_one({"merchantname": name})
49
+
50
+
51
+ async def get_merchant_by_id(merchant_id: str) -> dict:
52
+ """
53
+ Retrieve a merchant by ID from the database.
54
+
55
+ Args:
56
+ merchant_id (str): The unique ID of the merchant.
57
+
58
+ Returns:
59
+ dict: The merchant data if found, None otherwise.
60
+ """
61
+ return await db["merchants"].find_one({"merchantId": merchant_id})
62
+
63
+
64
+ async def get_merchant_by_email(email: str) -> dict:
65
+ """
66
+ Retrieve a merchant by email from the database.
67
+
68
+ Args:
69
+ email (str): The email address of the merchant.
70
+
71
+ Returns:
72
+ dict: The merchant data if found, None otherwise.
73
+ """
74
+ return await db["merchants"].find_one({"email": email})
75
+
76
+
77
+ async def get_merchant_by_mobile(mobile: str) -> dict:
78
+ """
79
+ Retrieve a merchant by mobile number from the database.
80
+
81
+ Args:
82
+ mobile (str): The mobile number of the merchant.
83
+
84
+ Returns:
85
+ dict: The merchant data if found, None otherwise.
86
+ """
87
+ return await db["merchants"].find_one({"mobile": mobile})
88
+
89
+
90
+ async def save_merchant(merchant_data: dict) -> dict:
91
+ """
92
+ Save a new merchant to the database.
93
+
94
+ Args:
95
+ merchant_data (dict): The merchant data to save.
96
+
97
+ Returns:
98
+ dict: The inserted merchant data.
99
+ """
100
+ result = await db["merchants"].insert_one(merchant_data)
101
+ return {"id": str(result.inserted_id), **merchant_data}
app/routers/__init__.py ADDED
File without changes
app/routers/router.py ADDED
@@ -0,0 +1,7 @@
 
 
 
 
 
 
 
 
1
+ from fastapi import APIRouter
2
+ from app.controllers.customer_controller import router as customer
3
+ from app.controllers.merchant_controller import router as merchant
4
+
5
+ router = APIRouter()
6
+ router.include_router(customer, prefix="", tags=["Customer"])
7
+ router.include_router(merchant, prefix="", tags=["Merchant"])
app/services/__init__.py ADDED
File without changes
app/services/customer_services.py ADDED
@@ -0,0 +1,165 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import logging
3
+ from app.repositories.cache_repository import get_otp, set_otp
4
+ from app.repositories.db_repository import get_customer_by_email, get_customer_by_mobile, save_customer
5
+ from app.models.customer import CustomerRegister
6
+ from app.utils.auth_utils import validate_email, validate_mobile
7
+ import random
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ async def validate_email_mobile(email: str, mobile: str) -> dict:
12
+ """
13
+ Validate email and mobile format and check if they already exist in the database.
14
+
15
+ Args:
16
+ email (str): The email address to validate.
17
+ mobile (str): The mobile number to validate.
18
+
19
+ Returns:
20
+ dict: Validation result with consolidated error messages or success message.
21
+ """
22
+ errors = []
23
+
24
+ # Validate email format
25
+ if not validate_email(email):
26
+ errors.append("Invalid email format")
27
+
28
+ # Validate mobile format
29
+ if not validate_mobile(mobile):
30
+ errors.append("Invalid mobile number format")
31
+
32
+ # Check if email already exists in the database
33
+ existing_email = await get_customer_by_email(email)
34
+ if existing_email:
35
+ errors.append("Email is already registered")
36
+
37
+ # Check if mobile already exists in the database
38
+ existing_mobile = await get_customer_by_mobile(mobile)
39
+ if existing_mobile:
40
+ errors.append("Mobile number is already registered")
41
+
42
+ # Return errors if any
43
+ if errors:
44
+ return {"status": "error", "errors": errors}
45
+ else:
46
+ # Send verification emails and SMS if no errors
47
+ await send_email_verification(email)
48
+ await send_sms_verification(mobile)
49
+
50
+ # If all validations pass
51
+ return {"status": "success", "message": "Email and mobile are valid"}
52
+
53
+ async def save_customer_to_db(user: CustomerRegister) -> dict:
54
+ """
55
+ Save customer details to MongoDB after checking for duplicates.
56
+
57
+ Args:
58
+ user (CustomerRegister): The customer details to save.
59
+
60
+ Returns:
61
+ dict: Success message or error message if email or mobile already exists.
62
+ """
63
+ # Check if email already exists in the database
64
+ existing_email = await get_customer_by_email(user.email)
65
+ if existing_email:
66
+ return {"status": "error", "message": "Email is already registered"}
67
+
68
+ # Check if mobile already exists in the database
69
+ existing_mobile = await get_customer_by_mobile(user.mobile)
70
+ if existing_mobile:
71
+ return {"status": "error", "message": "Mobile number is already registered"}
72
+
73
+ # Save customer details to the database
74
+ await save_customer(user.dict())
75
+ return {"status": "success", "message": "Customer registered successfully"}
76
+
77
+ async def send_email_verification(email: str):
78
+ """
79
+ Send an email verification link or code.
80
+
81
+ Args:
82
+ email (str): The email address to send the verification to.
83
+ """
84
+ # Implement your email sending logic here
85
+ # Generate a 6-digit OTP
86
+ otp = ''.join(random.choices("0123456789", k=6))
87
+ # Store OTP in cache with a 15-minute expiry
88
+ await set_otp(email, {"otp": otp, "expiry_duration": 15 * 60}) # 15 minutes in seconds
89
+ # Send the OTP to the email address
90
+ # (You would typically use an email service here)
91
+ # For demonstration, we'll just log the OTP
92
+ logger.info("Sending email verification to: %s with OTP: %s", email, otp)
93
+ logger.info("Sending email verification to: %s", email)
94
+
95
+ async def send_sms_verification(mobile: str):
96
+ """
97
+ Send an SMS verification code.
98
+
99
+ Args:
100
+ mobile (str): The mobile number to send the verification to.
101
+ """
102
+ # Implement your SMS sending logic here
103
+ # Generate a 6-digit OTP
104
+ otp = ''.join(random.choices("0123456789", k=6))
105
+ # Store OTP in cache with a 15-minute expiry
106
+ await set_otp(mobile, {"otp": otp, "expiry_duration": 15 * 60}) # 15 minutes in seconds
107
+ # Send the OTP to the mobile number
108
+ # (You would typically use an SMS service here)
109
+ # For demonstration, we'll just log the OTP
110
+ logger.info("Sending SMS verification to: %s with OTP: %s", mobile, otp)
111
+ logger.info("Sending SMS verification to: %s", mobile)
112
+
113
+ async def verify_otp_and_save_customer(user: CustomerRegister) -> dict:
114
+ """
115
+ Verify SMS OTP and email OTP, and save the complete customer object in the database.
116
+
117
+ Args:
118
+ user (CustomerRegister): The complete customer object including email, mobile, and OTPs.
119
+
120
+ Returns:
121
+ dict: Result indicating success or failure.
122
+ """
123
+ errors = []
124
+
125
+ # Retrieve OTPs from cache
126
+ email_otp_data = await get_otp(user.email)
127
+ mobile_otp_data = await get_otp(user.mobile)
128
+
129
+ # Verify email OTP
130
+ if not email_otp_data or email_otp_data["otp"] != user.email_ver_code:
131
+ errors.append("Invalid or expired email OTP")
132
+
133
+ # Verify mobile OTP
134
+ if not mobile_otp_data or mobile_otp_data["otp"] != user.mobile_ver_code:
135
+ errors.append("Invalid or expired mobile OTP")
136
+
137
+ # Return errors if any
138
+ if errors:
139
+ return {"status": "error", "errors": errors}
140
+
141
+ # Save the complete customer object to the database
142
+ customer_data = user.dict()
143
+ customer_data["status"] = "completed" # Update status to completed
144
+ await save_customer(customer_data)
145
+
146
+ return {"status": "success", "message": "Customer successfully registered and verified"}
147
+
148
+ async def get_otp_from_cache(key: str) -> dict:
149
+ """
150
+ Retrieve OTP data for the given key (email or mobile) from the cache.
151
+
152
+ Args:
153
+ key (str): The email or mobile number to retrieve the OTP for.
154
+
155
+ Returns:
156
+ dict: The OTP data if found, or None if not found.
157
+ """
158
+ try:
159
+ # Call the get_otp function from the cache repository
160
+ otp_data = await get_otp(key)
161
+ return otp_data
162
+ except Exception as e:
163
+ # Log the error and re-raise it
164
+ logger.error(f"Failed to retrieve OTP for key {key}: {e}")
165
+ raise
app/services/merchant_services.py ADDED
@@ -0,0 +1,144 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from app.models.merchant import MerchantRegister
2
+ from app.repositories.db_repository import save_merchant, get_merchant_by_name, get_merchant_by_email, get_merchant_by_mobile
3
+ from app.utils.merchant_utils import generate_tenant_id
4
+ from app.utils.auth_utils import validate_email, validate_mobile
5
+ from app.repositories.cache_repository import get_otp, set_otp
6
+ import logging
7
+ import random
8
+
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ async def validate_merchant(merchant: MerchantRegister) -> dict:
13
+ """
14
+ Validate if the merchant details are valid and not already existing.
15
+
16
+ Args:
17
+ name (str): The name of the merchant.
18
+ merchant_id (str): The unique ID of the merchant.
19
+ email (str): The email address of the merchant.
20
+ mobile (str): The mobile number of the merchant.
21
+
22
+ Returns:
23
+ dict: Validation result with success or error messages.
24
+ """
25
+ errors = []
26
+
27
+ # Check if merchant name already exists
28
+ existing_merchant_name = await get_merchant_by_name(merchant.merchant_name)
29
+ if existing_merchant_name:
30
+ errors.append("Merchant name is already registered")
31
+
32
+ # Check if merchant email already exists
33
+ existing_merchant_email = await get_merchant_by_email(merchant.email)
34
+ if existing_merchant_email:
35
+ errors.append("Merchant email is already registered")
36
+
37
+ # Check if merchant mobile already exists
38
+ existing_merchant_mobile = await get_merchant_by_mobile(merchant.mobile)
39
+ if existing_merchant_mobile:
40
+ errors.append("Merchant mobile number is already registered")
41
+
42
+ # Return errors if any
43
+ if errors:
44
+ return {"status": "error", "errors": errors}
45
+ else:
46
+ #merchant_id = generate_tenant_id(merchant.merchantname, merchant.country, merchant.city, merchant.area) # Send verification emails and SMS if no errors
47
+ await send_email_verification(merchant.email)
48
+ await send_sms_verification(merchant.mobile)
49
+ #merchant.merchantId=merchant_id
50
+
51
+ # If all validations pass
52
+ return merchant.dict()
53
+
54
+ async def send_email_verification(email: str):
55
+ """
56
+ Send an email verification link or code.
57
+
58
+ Args:
59
+ email (str): The email address to send the verification to.
60
+ """
61
+ # Implement your email sending logic here
62
+ # Generate a 6-digit OTP
63
+ otp = ''.join(random.choices("0123456789", k=6))
64
+ # Store OTP in cache with a 15-minute expiry
65
+ await set_otp(email, {"otp": otp, "expiry_duration": 15 * 60}) # 15 minutes in seconds
66
+ # Send the OTP to the email address
67
+ # (You would typically use an email service here)
68
+ # For demonstration, we'll just log the OTP
69
+ logger.info("Sending email verification to: %s with OTP: %s", email, otp)
70
+ logger.info("Sending email verification to: %s", email)
71
+
72
+ async def send_sms_verification(mobile: str):
73
+ """
74
+ Send an SMS verification code.
75
+
76
+ Args:
77
+ mobile (str): The mobile number to send the verification to.
78
+ """
79
+ # Implement your SMS sending logic here
80
+ # Generate a 6-digit OTP
81
+ otp = ''.join(random.choices("0123456789", k=6))
82
+ # Store OTP in cache with a 15-minute expiry
83
+ await set_otp(mobile, {"otp": otp, "expiry_duration": 15 * 60}) # 15 minutes in seconds
84
+ # Send the OTP to the mobile number
85
+ # (You would typically use an SMS service here)
86
+ # For demonstration, we'll just log the OTP
87
+ logger.info("Sending SMS verification to: %s with OTP: %s", mobile, otp)
88
+ logger.info("Sending SMS verification to: %s", mobile)
89
+
90
+ async def verify_otp_and_save_merchant(merchant: MerchantRegister) -> dict:
91
+ """
92
+ Verify SMS OTP and email OTP, and save the complete merchant object in the database.
93
+
94
+ Args:
95
+ merchant (MerchantRegister): The complete merchant object including email, mobile, and OTPs.
96
+
97
+ Returns:
98
+ dict: Result indicating success or failure.
99
+ """
100
+ errors = []
101
+
102
+ # Retrieve OTPs from cache
103
+ email_otp_data = await get_otp_from_cache(merchant.email)
104
+ mobile_otp_data = await get_otp_from_cache(merchant.mobile)
105
+
106
+ # Verify email OTP
107
+ if not email_otp_data or email_otp_data["otp"] != merchant.email_ver_code:
108
+ errors.append("Invalid or expired email OTP")
109
+
110
+ # Verify mobile OTP
111
+ if not mobile_otp_data or mobile_otp_data["otp"] != merchant.mobile_ver_code:
112
+ errors.append("Invalid or expired mobile OTP")
113
+
114
+ # Return errors if any
115
+ if errors:
116
+ return {"status": "error", "errors": errors}
117
+
118
+ # Save the complete merchant object to the database
119
+ merchant_data = merchant.dict()
120
+ merchant_data["merchant_id"] = generate_tenant_id(merchant.merchant_name, merchant.country, merchant.city, merchant.area)
121
+ merchant_data["status"] = "completed" # Update status to completed
122
+
123
+ await save_merchant(merchant_data)
124
+
125
+ return {"status": "success", "message": "Merchant successfully registered and verified"}
126
+
127
+ async def get_otp_from_cache(key: str) -> dict:
128
+ """
129
+ Retrieve OTP data for the given key (email or mobile) from the cache.
130
+
131
+ Args:
132
+ key (str): The email or mobile number to retrieve the OTP for.
133
+
134
+ Returns:
135
+ dict: The OTP data if found, or None if not found.
136
+ """
137
+ try:
138
+ # Call the get_otp function from the cache repository
139
+ otp_data = await get_otp(key)
140
+ return otp_data
141
+ except Exception as e:
142
+ # Log the error and re-raise it
143
+ logger.error(f"Failed to retrieve OTP for key {key}: {e}")
144
+ raise
app/utils/__init__.py ADDED
File without changes
app/utils/auth_utils.py ADDED
@@ -0,0 +1,27 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import re
2
+ import logging
3
+
4
+ def validate_email(email: str) -> bool:
5
+ """
6
+ Validate the email address format.
7
+
8
+ Args:
9
+ email (str): The email address to validate.
10
+
11
+ Returns:
12
+ bool: True if valid, False otherwise.
13
+ """
14
+ email_regex = r'^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$'
15
+ return re.match(email_regex, email) is not None
16
+
17
+ def validate_mobile(mobile: str) -> bool:
18
+ """
19
+ Validate the mobile number format.
20
+
21
+ Args:
22
+ mobile (str): The mobile number to validate.
23
+
24
+ Returns:
25
+ bool: True if valid, False otherwise.
26
+ """
27
+ return mobile.isdigit() and len(mobile) == 10
app/utils/merchant_utils.py ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import hashlib
2
+ from nanoid import generate
3
+ import re
4
+
5
+ def slugify_company_name(name, max_length=6):
6
+ slug = re.sub(r'[^A-Z0-9]', '', name.upper())
7
+ return slug[:max_length]
8
+
9
+ def short_hash(text, length=4):
10
+ return hashlib.sha1(text.encode()).hexdigest().upper()[:length]
11
+
12
+ def alphanumeric_to_digits(text):
13
+ result = ''
14
+ for c in text.upper():
15
+ if c.isdigit():
16
+ result += c
17
+ elif c.isalpha():
18
+ result += str(ord(c) - 55) # A=10, B=11, ..., Z=35
19
+ return result
20
+
21
+ def compute_check_digit(base_id):
22
+ numeric_string = alphanumeric_to_digits(base_id)
23
+ total = sum(int(digit) for digit in numeric_string)
24
+ return str(total % 10)
25
+
26
+ def generate_tenant_id(company_name,country, city, area):
27
+ short_name = country + "-" + slugify_company_name(company_name, 5) + "-" + slugify_company_name(city,3) + slugify_company_name(area,3)
28
+ hash_part = short_hash(short_name)
29
+ nanoid_part = generate('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 5)
30
+ base_id = f"{short_name}-{hash_part}-{nanoid_part}"
31
+ return f"{base_id}"
32
+
33
+
app/utils/security.py ADDED
File without changes
requirements.txt CHANGED
@@ -1,2 +1,12 @@
1
- fastapi
2
- uvicorn[standard]
 
 
 
 
 
 
 
 
 
 
 
1
+ fastapi>=0.95,<1.0
2
+ uvicorn[standard]>=0.22.0
3
+ motor>=3.0
4
+ pydantic
5
+ python-dotenv
6
+ pydantic[email]
7
+ redis
8
+ python-jose
9
+ passlib
10
+ python-multipart
11
+ bcrypt
12
+ nanoid