0% found this document useful (0 votes)
28 views44 pages

Current Files With Codes

The document outlines the setup of a Node.js application using Express, MongoDB, and various middleware for handling authentication, rate limiting, and error management. It includes the connection to a MongoDB database, the use of Twilio for sending SMS OTPs, and the implementation of various API routes for user authentication, business management, and job postings. Additionally, it emphasizes the importance of environment variables and error handling throughout the application.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
28 views44 pages

Current Files With Codes

The document outlines the setup of a Node.js application using Express, MongoDB, and various middleware for handling authentication, rate limiting, and error management. It includes the connection to a MongoDB database, the use of Twilio for sending SMS OTPs, and the implementation of various API routes for user authentication, business management, and job postings. Additionally, it emphasizes the importance of environment variables and error handling throughout the application.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 44

==== File : server/db.

ts =====
// db.ts
import mongoose from "mongoose";
import dotenv from "dotenv";
dotenv.config();

const mongoUrl = process.env.DATABASE_URL;

if (!mongoUrl) {
throw new Error("DATABASE_URL must be set in .env");
}

export async function connectDB() {


try {


const connection = await mongoose.connect(mongoUrl);
console.log(" Connected to MongoDB Atlas");
return connection; // return the connection object


} catch (error) {
console.error(" Failed to connect MongoDB:", error);
process.exit(1);
}
}

====== File : env.ts =======


import dotenv from "dotenv";

const result = dotenv.config();

if (result.error) {
throw result.error;
}

export const env = process.env;

====== File : server/index.ts ========


import express, { Request, Response, NextFunction } from "express";
import "tsconfig-paths/register";
import cors from "cors";
import helmet from "helmet";
import rateLimit from "express-rate-limit";
import path from "path";
import { fileURLToPath } from "url";
import { dirname } from "path";
import otpRoutes from "./routes/auth/otp.routes";
import authRoutes from "./routes/auth.routes";
import { registerRoutes } from "./routes";
import { setupVite, serveStatic, log } from "./vite";
import { connectDB } from "./db";
import { setupSwagger } from "./swagger";
import { verifyToken as authMiddleware } from "./middlewares/auth.middleware";

// Resolve __dirname for ES Modules


const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

// Setup Express
const app = express();

// Connect to DB before server starts


await connectDB();

// Swagger setup
setupSwagger(app);

// Middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));

// Rate Limiting
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
message: { message: "Too many requests from this IP, please try again later." },
});
app.use("/api", apiLimiter);

// Routes
app.use("/api/auth", authRoutes);
app.use("/api/auth/otp", otpRoutes);
// app.use("/api", authMiddleware); // Enable if needed

// Request logger
app.use((req, res, next) => {
const start = Date.now();
const path = req.path;
let capturedJsonResponse: Record<string, any> | undefined;

const originalResJson = res.json;


res.json = function (bodyJson, ...args) {
capturedJsonResponse = bodyJson;
return originalResJson.apply(res, [bodyJson, ...args]);
};

res.on("finish", () => {
const duration = Date.now() - start;
if (path.startsWith("/api")) {
let logLine = `${req.method} ${path} ${res.statusCode} in ${duration}ms`;
if (capturedJsonResponse) {
logLine += ` :: ${JSON.stringify(capturedJsonResponse)}`;
}
if (logLine.length > 80) {
logLine = logLine.slice(0, 79) + "…";
}
log(logLine);
}
});

next();
});

// Global error handler


app.use((err: any, _req: Request, res: Response, _next: NextFunction) => {
const status = err.status || err.statusCode || 500;
const message = err.message || "Internal Server Error";


res.status(status).json({ message });
console.error(" Server Error:", err);
});

// Start server
const startServer = async () => {
await registerRoutes(app);

const port = parseInt(process.env.PORT || "5000", 10);

if (app.get("env") === "development") {


await setupVite(app); // Vite dev middleware
} else {
serveStatic(app);
app.get("*", (req, res) => {
res.sendFile(path.join(__dirname, "..", "client", "dist", "index.html"));
});
}


app.listen(port, "0.0.0.0", () => {
log(` Server is running on http://localhost:${port}`);
});
};


startServer().catch((err) => {
console.error(" Failed to start server:", err);
process.exit(1);
});

========== File : server/routes.ts ========


import type { Express } from "express";
import { createServer, type Server } from "http";
import { storage } from "./storage";
import {
insertBusinessSchema,
insertProductSchema,
insertJobSchema,
insertRideRequestSchema,
insertReviewSchema,
} from "@shared/schema";
import { z } from "zod";
import jwt from "jsonwebtoken";
import { verifyToken, getUserFromRequest } from "./middlewares/auth.middleware";
import { env } from "./env";
import { Redis } from "@upstash/redis";
import { nanoid } from "nanoid";
import twilio from "twilio";

// Import register/initiate and register/verify from auth.routes.ts


import authRoutes from "./routes/auth.routes"; // Assumes you default-exported your router in
auth.routes.ts

// Mobile normalization helper


function normalizeMobile(mobile: string) {
let cleaned = mobile.replace(/\D/g, "");
if (cleaned.length === 10) {
cleaned = "91" + cleaned;
}
if (!cleaned.startsWith("91")) cleaned = "91" + cleaned.slice(-10);
return "+" + cleaned;
}

// Initialize Upstash Redis


const redis = new Redis({
url: env.UPSTASH_REDIS_REST_URL,
token: env.UPSTASH_REDIS_REST_TOKEN,
});

// Initialize Twilio client


const twilioClient = twilio(env.TWILIO_ACCOUNT_SID, env.TWILIO_AUTH_TOKEN);

// Send SMS via Twilio (just SMS, no verify API)


async function sendSmsOtp(mobile: string, otp: string) {
return twilioClient.messages.create({
body: `Your OTP is ${otp}`,
to: mobile,
from: env.TWILIO_PHONE_NUMBER,
});
}

export async function registerRoutes(app: Express): Promise<Server> {


// Mount auth routes (includes /register/initiate and /register/verify)
app.use("/", authRoutes);

// Email/Password Login
app.post("/login/password", async (req, res) => {
const { email, password } = req.body;
const user = await storage.getUserByEmail(email);
if (!user) return res.status(404).json({ message: "User not found" });

const isValid = await storage.validateUserPassword(email, password);


if (!isValid) return res.status(401).json({ message: "Invalid password" });

const token = jwt.sign({ sub: user.id }, env.JWT_SECRET, { expiresIn: "7d" });


res.json({ token });
});

// Email OTP - Send


app.post("/send-email-otp", async (req, res) => {
const { email } = req.body;
if (!email) return res.status(400).json({ message: "Email is required" });
const emailOtp = Math.floor(100000 + Math.random() * 900000).toString();
await redis.set(`otp:email:${email}`, emailOtp, { ex: 300 });
console.log(`[EMAIL OTP] Sent OTP ${emailOtp} to ${email}`);
// TODO: Add email sending logic here if needed

res.json({ message: "Email OTP sent" });


});

// Email OTP - Verify (Do NOT delete OTP here)


app.post("/verify-email-otp", async (req, res) => {
const { email, emailOTP } = req.body;
const expectedOtp = await redis.get(`otp:email:${email}`);
console.log(`[EMAIL OTP VERIFY] Email: ${email} | Input: ${emailOTP} | Stored:
${expectedOtp}`);

if (expectedOtp !== emailOTP) {


return res.status(400).json({ message: "Invalid or expired OTP" });
}
// OTP deletion removed here to delete after user creation
res.json({ message: "OTP verification successful" });
});

// Mobile OTP - Send (generate OTP, save in Redis, send SMS via Twilio)
app.post("/send-mobile-otp", async (req, res) => {
const { mobile } = req.body;
if (!mobile) return res.status(400).json({ message: "Mobile number is required" });

const normalizedMobile = normalizeMobile(mobile);


const otp = Math.floor(100000 + Math.random() * 900000).toString();

try {
// Send SMS via Twilio
await sendSmsOtp(normalizedMobile, otp);

// Save OTP in Redis with 5 minutes expiry


await redis.set(`otp:mobile:${normalizedMobile}`, otp, { ex: 300 });

console.log(`[MOBILE OTP] Sent OTP ${otp} to ${normalizedMobile}`);

res.json({ message: "Mobile OTP sent" });


} catch (err) {
console.error("Error sending mobile OTP:", err);
res.status(500).json({ message: "Failed to send mobile OTP" });
}
});

// Mobile OTP - Verify (Do NOT delete OTP here)


app.post("/verify-mobile-otp", async (req, res) => {
const { mobile, mobileOTP } = req.body;
if (!mobile || !mobileOTP)
return res.status(400).json({ message: "Mobile number and OTP are required" });

const normalizedMobile = normalizeMobile(mobile);


const storedOtp = await redis.get(`otp:mobile:${normalizedMobile}`);

console.log(`[MOBILE OTP VERIFY] Mobile: ${normalizedMobile} | Input: ${mobileOTP} |


Stored: ${storedOtp}`);

if (storedOtp !== mobileOTP) {


return res.status(400).json({ message: "Invalid or expired OTP" });
}

// OTP deletion removed here to delete after user creation


res.json({ message: "Mobile verified successfully" });
});

// Google Social Login/Register


app.post("/login/social", async (req, res) => {
const { email, provider } = req.body;
let user = await storage.getUserByEmail(email);
if (!user) {
user = await storage.createUser({
email,
provider: provider || "google",
});
}
const token = jwt.sign({ sub: user.id }, env.JWT_SECRET, { expiresIn: "7d" });
res.json({ token });
});

// Phone Login/Register (mobile OTP via Redis)


app.post("/login/mobile", async (req, res) => {
const { mobile, mobileOTP } = req.body;
if (!mobile || !mobileOTP) return res.status(400).json({ message: "Mobile and OTP required"
});

const normalizedMobile = normalizeMobile(mobile);


const storedOtp = await redis.get(`otp:mobile:${normalizedMobile}`);
if (storedOtp !== mobileOTP) {
return res.status(401).json({ message: "Mobile verification failed" });
}

// Delete OTP after successful login


await redis.del(`otp:mobile:${normalizedMobile}`);

let user = await storage.getUserByPhone(normalizedMobile);


if (!user) {
user = await storage.createUser({
phone: normalizedMobile,
provider: "phone",
});
}

const token = jwt.sign({ sub: user.id }, env.JWT_SECRET, { expiresIn: "7d" });


res.json({ token });
});

// Fetch Logged In User


app.get("/api/auth/user", async (req: any, res) => {
try {
let user = null;
try {
user = await getUserFromRequest(req);
} catch {
user = null;
}
return res.json({ user });
} catch (error) {
console.error("Error fetching user:", error);
res.status(500).json({ message: "Failed to fetch user" });
}
});

// Business routes
app.get("/api/businesses", async (req, res) => {
try {
const { category, search } = req.query;
const businesses = await storage.getBusinesses(category as string, search as string);
res.json(businesses);
} catch (error) {
console.error("Error fetching businesses:", error);
res.status(500).json({ message: "Failed to fetch businesses" });
}
});

app.get("/api/businesses/:id", async (req, res) => {


try {
const business = await storage.getBusiness(req.params.id);
if (!business) return res.status(404).json({ message: "Business not found" });
res.json(business);
} catch (error) {
console.error("Error fetching business:", error);
res.status(500).json({ message: "Failed to fetch business" });
}
});

app.post("/api/businesses", verifyToken, async (req: any, res) => {


try {
const userId = req.user.sub;
const businessData = insertBusinessSchema.parse({ ...req.body, ownerId: userId });
const business = await storage.createBusiness(businessData);
res.json(business);
} catch (error) {
if (error instanceof z.ZodError)
return res.status(400).json({ message: "Invalid business data", errors: error.errors });
console.error("Error creating business:", error);
res.status(500).json({ message: "Failed to create business" });
}
});

app.get("/api/my-businesses", verifyToken, async (req: any, res) => {


try {
const userId = req.user.sub;
const businesses = await storage.getBusinessesByOwner(userId);
res.json(businesses);
} catch (error) {
console.error("Error fetching user businesses:", error);
res.status(500).json({ message: "Failed to fetch businesses" });
}
});

// Product routes
app.get("/api/products", async (req, res) => {
try {
const { businessId } = req.query;
const products = await storage.getProducts(businessId as string);
res.json(products);
} catch (error) {
console.error("Error fetching products:", error);
res.status(500).json({ message: "Failed to fetch products" });
}
});

app.post("/api/products", verifyToken, async (req: any, res) => {


try {
const productData = insertProductSchema.parse(req.body);
const product = await storage.createProduct(productData);
res.json(product);
} catch (error) {
if (error instanceof z.ZodError)
return res.status(400).json({ message: "Invalid product data", errors: error.errors });
console.error("Error creating product:", error);
res.status(500).json({ message: "Failed to create product" });
}
});

// Job routes
app.get("/api/jobs", async (req, res) => {
try {
const { category, location } = req.query;
const jobs = await storage.getJobs(category as string, location as string);
res.json(jobs);
} catch (error) {
console.error("Error fetching jobs:", error);
res.status(500).json({ message: "Failed to fetch jobs" });
}
});

app.get("/api/jobs/:id", async (req, res) => {


try {
const job = await storage.getJob(req.params.id);
if (!job) return res.status(404).json({ message: "Job not found" });
res.json(job);
} catch (error) {
console.error("Error fetching job:", error);
res.status(500).json({ message: "Failed to fetch job" });
}
});
app.post("/api/jobs", verifyToken, async (req: any, res) => {
try {
const jobData = insertJobSchema.parse(req.body);
const job = await storage.createJob(jobData);
res.json(job);
} catch (error) {
if (error instanceof z.ZodError)
return res.status(400).json({ message: "Invalid job data", errors: error.errors });
console.error("Error creating job:", error);
res.status(500).json({ message: "Failed to create job" });
}
});

// Ride routes
app.post("/api/ride-requests", verifyToken, async (req: any, res) => {
try {
const userId = req.user.sub;
const rideRequestData = insertRideRequestSchema.parse({ ...req.body, userId });
const rideRequest = await storage.createRideRequest(rideRequestData);
res.json(rideRequest);
} catch (error) {
if (error instanceof z.ZodError)
return res.status(400).json({ message: "Invalid ride request data", errors: error.errors });
console.error("Error creating ride request:", error);
res.status(500).json({ message: "Failed to create ride request" });
}
});

app.get("/api/my-ride-requests", verifyToken, async (req: any, res) => {


try {
const userId = req.user.sub;
const rideRequests = await storage.getRideRequests(userId);
res.json(rideRequests);
} catch (error) {
console.error("Error fetching ride requests:", error);
res.status(500).json({ message: "Failed to fetch ride requests" });
}
});

// Review routes
app.post("/api/reviews", verifyToken, async (req: any, res) => {
try {
const userId = req.user.sub;
const reviewData = insertReviewSchema.parse({ ...req.body, userId });
const review = await storage.createReview(reviewData);
res.json(review);
} catch (error) {
if (error instanceof z.ZodError)
return res.status(400).json({ message: "Invalid review data", errors: error.errors });
console.error("Error creating review:", error);
res.status(500).json({ message: "Failed to create review" });
}
});

app.get("/api/businesses/:id/reviews", async (req, res) => {


try {
const reviews = await storage.getBusinessReviews(req.params.id);
res.json(reviews);
} catch (error) {
console.error("Error fetching reviews:", error);
res.status(500).json({ message: "Failed to fetch reviews" });
}
});

const httpServer = createServer(app);


return httpServer;
}

========= File : server/storage.ts ==========


import {
type User,
type UpsertUser,
type Business,
type InsertBusiness,
type Product,
type InsertProduct,
type Job,
type InsertJob,
type RideRequest,
type InsertRideRequest,
type Review,
type InsertReview,
} from "@shared/schema";

import { BusinessModel } from "./models/business.model";


import { ProductModel } from "./models/product.model";
import { JobModel } from "./models/job.model";
import { RideRequestModel } from "./models/rideRequest.model";
import { ReviewModel } from "./models/review.model";
import { UserModel } from "./models/user.model";

// Add getUserByEmail to the interface


export interface IStorage {
getUser(id: string): Promise<User | null>;
getUserByEmail(email: string): Promise<User | null>;
createUser(user: UpsertUser): Promise<User>;
upsertUser(user: UpsertUser): Promise<User>;

getBusinesses(category?: string, search?: string): Promise<Business[]>;


getBusiness(id: string): Promise<Business | null>;
createBusiness(business: InsertBusiness): Promise<Business>;
updateBusiness(id: string, business: Partial<InsertBusiness>): Promise<Business | null>;
getBusinessesByOwner(ownerId: string): Promise<Business[]>;

getProducts(businessId?: string): Promise<Product[]>;


getProduct(id: string): Promise<Product | null>;
createProduct(product: InsertProduct): Promise<Product>;

getJobs(category?: string, location?: string): Promise<Job[]>;


getJob(id: string): Promise<Job | null>;
createJob(job: InsertJob): Promise<Job>;

createRideRequest(rideRequest: InsertRideRequest): Promise<RideRequest>;


getRideRequests(userId: string): Promise<RideRequest[]>;

createReview(review: InsertReview): Promise<Review>;


getBusinessReviews(businessId: string): Promise<Review[]>;
}

export class DatabaseStorage implements IStorage {


async getUser(id: string): Promise<User | null> {
return await UserModel.findById(id).lean();
}

// Implementation of getUserByEmail
async getUserByEmail(email: string): Promise<User | null> {
return await UserModel.findOne({ email }).lean();
}

// NEW createUser method to trigger pre-save hooks like password hashing


async createUser(user: UpsertUser): Promise<User> {
const newUser = new UserModel(user);
await newUser.save(); // triggers pre('save') hooks, including password hashing
return newUser.toObject();
}

async upsertUser(user: UpsertUser): Promise<User> {


return await UserModel.findByIdAndUpdate(user.id, user, { upsert: true, new: true,
setDefaultsOnInsert: true }).lean();
}

async getBusinesses(category?: string, search?: string): Promise<Business[]> {


const query: any = { isActive: true };
if (category) query.category = category;
if (search) query.name = { $regex: search, $options: "i" };
return await BusinessModel.find(query).sort({ rating: -1, createdAt: -1 }).lean();
}

async getBusiness(id: string): Promise<Business | null> {


return await BusinessModel.findById(id).lean();
}

async createBusiness(business: InsertBusiness): Promise<Business> {


const newBusiness = new BusinessModel(business);
await newBusiness.save();
return newBusiness.toObject();
}

async updateBusiness(id: string, business: Partial<InsertBusiness>): Promise<Business | null>


{
return await BusinessModel.findByIdAndUpdate(id, business, { new: true }).lean();
}

async getBusinessesByOwner(ownerId: string): Promise<Business[]> {


return await BusinessModel.find({ ownerId }).sort({ createdAt: -1 }).lean();
}

async getProducts(businessId?: string): Promise<Product[]> {


const query: any = { isAvailable: true };
if (businessId) query.businessId = businessId;
return await ProductModel.find(query).sort({ createdAt: -1 }).lean();
}

async getProduct(id: string): Promise<Product | null> {


return await ProductModel.findById(id).lean();
}
async createProduct(product: InsertProduct): Promise<Product> {
const newProduct = new ProductModel(product);
await newProduct.save();
return newProduct.toObject();
}

async getJobs(category?: string, location?: string): Promise<Job[]> {


const query: any = { isActive: true };
if (category) query.category = category;
if (location) query.location = { $regex: location, $options: "i" };
return await JobModel.find(query).sort({ createdAt: -1 }).lean();
}

async getJob(id: string): Promise<Job | null> {


return await JobModel.findById(id).lean();
}

async createJob(job: InsertJob): Promise<Job> {


const newJob = new JobModel(job);
await newJob.save();
return newJob.toObject();
}

async createRideRequest(rideRequest: InsertRideRequest): Promise<RideRequest> {


const newRideRequest = new RideRequestModel(rideRequest);
await newRideRequest.save();
return newRideRequest.toObject();
}

async getRideRequests(userId: string): Promise<RideRequest[]> {


return await RideRequestModel.find({ userId }).sort({ createdAt: -1 }).lean();
}

async createReview(review: InsertReview): Promise<Review> {


const newReview = await ReviewModel.create(review);

const reviews = await ReviewModel.find({ businessId: review.businessId });


const avgRating = reviews.reduce((sum, r) => sum + r.rating, 0) / (reviews.length || 1);

await BusinessModel.findByIdAndUpdate(review.businessId, {
rating: avgRating.toFixed(1),
reviewCount: reviews.length,
});
return newReview.toObject();
}

async getBusinessReviews(businessId: string): Promise<Review[]> {


return await ReviewModel.find({ businessId }).sort({ createdAt: -1 }).lean();
}
}

export const storage = new DatabaseStorage();

===== File : server/config/mailer.ts=======


import nodemailer from "nodemailer";
import dotenv from "dotenv";

dotenv.config();

const transporter = nodemailer.createTransport({


host: process.env.SMTP_HOST,
port: Number(process.env.SMTP_PORT) || 587,
secure: false, // true for 465, false for other ports
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
});

/**
* Send an email
* @param to Receiver's email
* @param subject Subject line
* @param html HTML content
*/
export const sendEmail = async (to: string, subject: string, html: string) => {
try {
const info = await transporter.sendMail({
from: process.env.FROM_EMAIL, // e.g., "YourApp <[email protected]>"
to,
subject,
html,
});
console.log("Email sent: %s", info.messageId);
return true;
} catch (err) {
console.error("Email send failed:", err);
return false;
}
};

===== File : server/config/redis.ts======


// server/config/redis.ts
import { Redis } from "@upstash/redis";
import dotenv from "dotenv";

dotenv.config();

if (!process.env.UPSTASH_REDIS_REST_URL ||


!process.env.UPSTASH_REDIS_REST_TOKEN) {
throw new Error(" UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN is
not defined in .env");
}

const redis = new Redis({


url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// Optional: test connection


(async () => {
try {


await redis.ping();
console.log(" Connected to Upstash Redis");


} catch (err: any) {
console.error(" Redis connection error:", err.message || err);
}
})();

export default redis;

===== File : server/controllers/auth/otp.controller.ts=======


// server/config/redis.ts
import { Redis } from "@upstash/redis";
import dotenv from "dotenv";

dotenv.config();
if (!process.env.UPSTASH_REDIS_REST_URL ||


!process.env.UPSTASH_REDIS_REST_TOKEN) {
throw new Error(" UPSTASH_REDIS_REST_URL or UPSTASH_REDIS_REST_TOKEN is
not defined in .env");
}

const redis = new Redis({


url: process.env.UPSTASH_REDIS_REST_URL!,
token: process.env.UPSTASH_REDIS_REST_TOKEN!,
});

// Optional: test connection


(async () => {
try {


await redis.ping();
console.log(" Connected to Upstash Redis");


} catch (err: any) {
console.error(" Redis connection error:", err.message || err);
}
})();

export default redis;

====== File : server/controllers/authController.ts =======


import { Request, Response } from "express";
import * as authService from "../services/auth.service"; // <-- Importing auth.service
import { createToken } from "../utils/jwt.util";
import User from "../models/user.model";

// -------------------- REGISTRATION --------------------

// Step 1: Initiate registration by sending OTPs (user not yet created)


export const registerInitiate = async (req: Request, res: Response) => {
try {
const { name, email, mobile, password } = req.body;

if (!name || !email || !mobile || !password) {


return res.status(400).json({ message: "All fields are required" });
}

const result = await authService.registerInitiate(


name,
email,
mobile,
password
);

return res.json({
message: "OTP sent to email and mobile for verification.",
...result,
});
} catch (error: any) {
console.error("registerInitiate error:", error);
return res.status(500).json({ message: error.message || "Server error" });
}
};

// Step 2: Verify OTPs and create user


export const registerVerify = async (req: Request, res: Response) => {
try {
const { userId, emailOTP, mobileOTP } = req.body;

if (!userId || !emailOTP || !mobileOTP) {


return res
.status(400)
.json({ message: "userId, emailOTP, and mobileOTP are required" });
}

const result = await authService.registerVerify(


userId,
emailOTP,
mobileOTP
);

return res.json(result);
} catch (error: any) {
console.error("registerVerify error:", error);
return res.status(500).json({ message: error.message || "Server error" });
}
};

// -------------------- LOGIN --------------------

// Login with email & password


export const loginWithPassword = async (req: Request, res: Response) => {
try {
const { email, password } = req.body;
if (!email || !password)
return res
.status(400)
.json({ message: "Email and password are required" });

const result = await authService.loginWithPassword(email, password);

return res.json(result);
} catch (error: any) {
console.error("loginWithPassword error:", error);
return res.status(500).json({ message: error.message || "Server error" });
}
};

// Login with mobile OTP


export const loginWithMobileOTP = async (req: Request, res: Response) => {
try {
const { mobile, mobileOTP } = req.body;
if (!mobile || !mobileOTP)
return res
.status(400)
.json({ message: "Mobile and OTP are required" });

const result = await authService.loginWithMobileOTP(mobile, mobileOTP);

return res.json(result);
} catch (error: any) {
console.error("loginWithMobileOTP error:", error);
return res.status(500).json({ message: error.message || "Server error" });
}
};

====== File : server/middlewares/auth.middleware.ts =======


// server/middlewares/auth.middleware.ts

import jwt from "jsonwebtoken";


import { Request, Response, NextFunction } from "express";

// Define what the decoded JWT will look like


export interface DecodedToken {
uid: string;
email?: string;
iat: number;
exp: number;
}

// Extend Express Request interface to include `user`


declare module "express-serve-static-core" {
interface Request {
user?: DecodedToken;
}
}

// Middleware for protecting routes


export const verifyToken = (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;

if (!authHeader || !authHeader.startsWith("Bearer ")) {


return res.status(401).json({ message: "Unauthorized: No token provided" });
}


if (!secret) {
console.error(" JWT_SECRET is not defined in environment variables.");
return res.status(500).json({ message: "Server configuration error" });
}

try {
const decoded = jwt.verify(token, secret) as DecodedToken;
req.user = decoded;
next();


} catch (error) {
console.error(" Token verification failed:", error);
return res.status(403).json({ message: "Forbidden: Invalid or expired token" });
}
};

// Helper for optionally extracting user from request, returns null if not present or invalid
export const getUserFromRequest = (req: Request): DecodedToken | null => {
const authHeader = req.headers.authorization;
const secret = process.env.JWT_SECRET;
if (!authHeader || !authHeader.startsWith("Bearer ") || !secret) {
return null;
}
const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, secret) as DecodedToken;
return decoded;
} catch {
return null;
}
};

====== File : rateLimit.middleware.ts ======


import rateLimit from "express-rate-limit";
import RedisStore from "rate-limit-redis";
import redis from "@server/config/redis"; // your Redis instance
import { Request, Response } from "express";

// Apply to OTP or login-related routes


export const otpRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 OTP requests per windowMs
message: {
status: 429,
message: "Too many OTP requests. Please try again after 15 minutes.",
},
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req: Request): string => {
return req.ip || "global";
},
store: new RedisStore({
sendCommand: (...args: string[]) => redis.call(...args),
}),
});

// Apply to login routes


export const loginRateLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 10,
message: {
status: 429,
message: "Too many login attempts. Please try again later.",
},
standardHeaders: true,
legacyHeaders: false,
keyGenerator: (req: Request): string => {
return req.ip || "global";
},
store: new RedisStore({
sendCommand: (...args: string[]) => redis.call(...args),
}),
});

====== File : server/models/user.models.ts =========


import mongoose from "mongoose";
import bcrypt from "bcryptjs";

export interface IUser extends mongoose.Document {


name?: string;
email?: string;
mobile?: string;
password?: string;
emailVerified?: boolean;
phoneVerified?: boolean;
comparePassword(password: string): Promise<boolean>;
}

const userSchema = new mongoose.Schema<IUser>(


{
name: {
type: String,
trim: true,
},
email: {
type: String,
unique: true,
sparse: true,
lowercase: true,
trim: true,
},
mobile: {
type: String,
unique: true,
sparse: true,
trim: true,
},
password: {
type: String,
minlength: 6,
},
emailVerified: {
type: Boolean,
default: false,
},
mobileVerified: {
type: Boolean,
default: false,
},
},
{
timestamps: true,
}
);

// Hash password before saving


userSchema.pre("save", async function (next) {
if (!this.isModified("password") || !this.password) return next();
const salt = await bcrypt.genSalt(10);
this.password = await bcrypt.hash(this.password, salt);
next();
});

// Compare password method


userSchema.methods.comparePassword = async function (password: string):
Promise<boolean> {
if (!this.password) return false;
return await bcrypt.compare(password, this.password);
};

export const UserModel = mongoose.model<IUser>("User", userSchema);

export default UserModel;

======= File : server/models/business.model.ts ======


// server/models/business.model.ts

import mongoose from "mongoose";

const businessSchema = new mongoose.Schema(


{
name: {
type: String,
required: true,
trim: true,
},
category: {
type: String,
required: true,
enum: ["shop", "service", "ride", "delivery", "job", "others"],
},
description: {
type: String,
},
address: {
street: String,
city: String,
state: String,
pincode: String,
},
phone: {
type: String,
required: true,
},
email: {
type: String,
},
location: {
latitude: { type: Number },
longitude: { type: Number },
},
owner: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
approved: {
type: Boolean,
default: false,
},
services: {
type: [String],
default: [],
},
createdAt: {
type: Date,
default: Date.now,
},
},
{
timestamps: true,
}
);

export const BusinessModel = mongoose.model("Business", businessSchema);

====== File : server/models/job.models.ts =======


// server/models/job.model.ts

import mongoose from "mongoose";

const jobSchema = new mongoose.Schema(


{
title: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
default: "",
},
jobType: {
type: String,
enum: ["Full-time", "Part-time", "Contract", "Internship", "Freelance"],
default: "Full-time",
},
location: {
type: String,
default: "Remote",
},
salary: {
type: Number,
},
skillsRequired: {
type: [String], // Example: ["JavaScript", "React", "MongoDB"]
default: [],
},
business: {
type: mongoose.Schema.Types.ObjectId,
ref: "Business",
required: true,
},
open: {
type: Boolean,
default: true,
},
},
{
timestamps: true,
}
);

export const JobModel = mongoose.model("Job", jobSchema);

====== File : server/models/product.model.ts ===========


// server/models/product.model.ts

import mongoose from "mongoose";

const productSchema = new mongoose.Schema(


{
name: {
type: String,
required: true,
trim: true,
},
description: {
type: String,
default: "",
},
price: {
type: Number,
required: true,
},
discountedPrice: {
type: Number,
},
stock: {
type: Number,
default: 0,
},
images: {
type: [String], // Array of image URLs or filenames
default: [],
},
category: {
type: String,
required: true,
},
business: {
type: mongoose.Schema.Types.ObjectId,
ref: "Business",
required: true,
},
available: {
type: Boolean,
default: true,
},
},
{
timestamps: true,
}
);

export const ProductModel = mongoose.model("Product", productSchema);

========= File : server/models/review.model.ts =======


// server/models/review.model.ts

import mongoose from "mongoose";

const reviewSchema = new mongoose.Schema(


{
reviewerName: {
type: String,
required: true,
},
reviewerId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User", // Optional if you add a user model
},
rating: {
type: Number,
required: true,
min: 1,
max: 5,
},
comment: {
type: String,
},
business: {
type: mongoose.Schema.Types.ObjectId,
ref: "Business",
},
product: {
type: mongoose.Schema.Types.ObjectId,
ref: "Product",
},
job: {
type: mongoose.Schema.Types.ObjectId,
ref: "Job",
},
rideRequest: {
type: mongoose.Schema.Types.ObjectId,
ref: "RideRequest",
},
photos: [
{
type: String, // URLs or file paths
},
],
},
{
timestamps: true,
}
);

export const ReviewModel = mongoose.model("Review", reviewSchema);

====== File : server/models/rideRequest.model.ts ========


// server/models/rideRequest.model.ts

import mongoose from "mongoose";

const rideRequestSchema = new mongoose.Schema(


{
pickupLocation: {
type: String,
required: true,
},
dropLocation: {
type: String,
required: true,
},
rideType: {
type: String,
enum: ["Bike", "Auto", "Car", "Delivery"],
default: "Bike",
},
business: {
type: mongoose.Schema.Types.ObjectId,
ref: "Business",
required: true,
},
customerName: {
type: String,
},
customerPhone: {
type: String,
},
status: {
type: String,
enum: ["Pending", "Accepted", "Completed", "Cancelled"],
default: "Pending",
},
fareEstimate: {
type: Number,
},
distanceKm: {
type: Number,
},
durationMinutes: {
type: Number,
},
},
{
timestamps: true,
}
);

export const RideRequestModel = mongoose.model("RideRequest", rideRequestSchema);

==== File : server/routes/auth/emailOtp.ts ======


import express from "express";
import nodemailer from "nodemailer";
import { Redis } from "@upstash/redis";

const router = express.Router();


const redis = new Redis({ /* config */ });

const transporter = nodemailer.createTransport({ /* SMTP config */ });

router.post("/send-email-otp", async (req, res) => {


const { email } = req.body;
if (!email) return res.status(400).json({ message: "Email is required" });

const emailOtp = Math.floor(100000 + Math.random() * 900000).toString();


await redis.set(`otp:email:${email}`, emailOtp, { ex: 300 });

// Actually send email


await transporter.sendMail({
to: email,
subject: "Your OTP",
text: `Your OTP is ${emailOtp}`,
});

res.json({ message: "Email OTP sent" });


});

export default router;

===== Files : server/routes/auth/mobileOtp.ts =======


import express from "express";
import redis from "../config/redis";
import { generateOTP } from "../utils/otp.util";

const router = express.Router();

function normalizeMobile(mobile: string) {


let cleaned = mobile.replace(/\D/g, "");
if (cleaned.length === 10) cleaned = "91" + cleaned;
if (!cleaned.startsWith("91")) cleaned = "91" + cleaned.slice(-10);
return "+" + cleaned;
}

router.post("/send-mobile-otp", async (req, res) => {


try {
const { mobile } = req.body;
if (!mobile) return res.status(400).json({ message: "Mobile number is required" });

const normalizedMobile = normalizeMobile(mobile);


const mobileOtp = generateOTP();

await redis.set(`otp:mobile:${normalizedMobile}`, mobileOtp, { ex: 300 });

// TODO: Send SMS with your SMS gateway here


console.log(`[SEND MOBILE OTP] Mobile: ${normalizedMobile} | OTP: ${mobileOtp}`);

res.json({
message: "Mobile OTP saved (and sent if SMS configured)",
mobile: normalizedMobile,
mobileOTP: mobileOtp // Useful for testing/dev, remove in production!
});
} catch (error) {
console.error(error);
res.status(500).json({ message: "Internal server error" });
}
});

export default router;

====== File : server/routes/auth/otp.routes.ts =======


import express from "express";
import {
sendEmailOTP,
verifyEmailOTPController,
} from "../../controllers/auth/otp.controller";

const router = express.Router();

/**
* @swagger
* /api/auth/otp/send-email:
* post:
* summary: Send OTP to email address
* tags: [OTP]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* required:
* - email
* responses:
* 200:
* description: Email OTP sent
* 400:
* description: Email required
*/
router.post("/send-email", sendEmailOTP);

/**
* @swagger
* /api/auth/otp/verify-email:
* post:
* summary: Verify email OTP
* tags: [OTP]
* requestBody:
* required: true
* content:
* application/json:
* schema:
* type: object
* properties:
* email:
* type: string
* emailOTP:
* type: string
* required:
* - email
* - emailOTP
* responses:
* 200:
* description: OTP verified
* 401:
* description: OTP invalid or expired
*/
router.post("/verify-email", verifyEmailOTPController);
export default router;

===== Files : server/routes/auth.routes.ts ======


import express from "express";
import {
registerInitiate,
registerVerify,
loginWithPassword,
loginWithMobileOTP,
} from "../controllers/authController"; // <-- Import main auth controller
import {
sendEmailOTP,
verifyEmailOTPController,
sendMobileOTPController,
verifyMobileOTPController,
} from "../controllers/auth/otp.controller"; // <-- Import OTP controller

const router = express.Router();

// -------------------- REDIRECTS --------------------


router.get("/login", (req, res) => {
res.redirect("/login");
});

// -------------------- REGISTRATION --------------------


router.post("/register/initiate", registerInitiate);
router.post("/register/verify", registerVerify);

// -------------------- LOGIN --------------------


router.post("/login/password", loginWithPassword);
router.post("/login/mobile-otp", loginWithMobileOTP);

// -------------------- EMAIL OTP --------------------


router.post("/send-email-otp", sendEmailOTP);
router.post("/verify-email-otp", verifyEmailOTPController);

// -------------------- MOBILE OTP --------------------


router.post("/send-mobile-otp", sendMobileOTPController);
router.post("/verify-mobile-otp", verifyMobileOTPController);

export default router;

===== File : server/routes/otp.ts ========


import express from "express";
import {
sendEmailOTP,
verifyEmailOTPController,
sendMobileOTPController,
verifyMobileOTPController,
} from "../controllers/auth/otp.controller";

// Mobile normalization helper (always returns +91xxxxxxxxxx for India)


function normalizeMobile(mobile: string) {
let cleaned = mobile.replace(/\D/g, "");
if (cleaned.length === 10) cleaned = "91" + cleaned;
if (!cleaned.startsWith("91")) cleaned = "91" + cleaned.slice(-10);
return "+" + cleaned;
}

const router = express.Router();

/**
* @route POST /api/auth/otp/send
* @desc Send OTP to mobile or email
* @body { target: string, type: "mobile" | "email", purpose: string }
*/
router.post("/send", async (req, res) => {
const { type, target } = req.body;

if (type === "email") {


req.body.email = target || req.body.email;
return sendEmailOTP(req, res);
} else if (type === "mobile") {
req.body.mobile = normalizeMobile(target || req.body.mobile);
return sendMobileOTPController(req, res);
} else {
return res.status(400).json({ message: "Invalid OTP type" });
}
});

/**
* @route POST /api/auth/otp/verify
* @desc Verify OTP sent to mobile or email
* @body { target: string, code: string, type: "mobile" | "email", purpose: string }
*/
router.post("/verify", async (req, res) => {
const { type, target, code } = req.body;
if (type === "email") {
req.body.email = target || req.body.email;
req.body.emailOTP = code || req.body.emailOTP;
return verifyEmailOTPController(req, res);
} else if (type === "mobile") {
req.body.mobile = normalizeMobile(target || req.body.mobile);
req.body.mobileOTP = code || req.body.mobileOTP; // Fix: always use 'mobileOTP' for
backend consistency
return verifyMobileOTPController(req, res);
} else {
return res.status(400).json({ message: "Invalid OTP type" });
}
});

export default router;

====== File : server/services/auth.service.ts ========


// server/services/auth.service.ts
import User from "../models/user.model";
import { hashPassword, comparePassword } from "../utils/hash.util";
import { generateOTP } from "../utils/otp.util";
import { sendEmail } from "../utils/email";
import { createToken } from "../utils/jwt.util";
import redis from "../config/redis";

// Mobile normalization helper (always returns +91xxxxxxxxxx for India)


function normalizeMobile(mobile: string) {
let cleaned = mobile.replace(/\D/g, "");
if (cleaned.length === 10) cleaned = "91" + cleaned;
if (!cleaned.startsWith("91")) cleaned = "91" + cleaned.slice(-10);
return "+" + cleaned;
}

// -------------------- REGISTRATION --------------------

// Step 1: Initiate registration


export const registerInitiate = async (
name: string,
email: string,
mobile: string,
password: string
) => {
const normalizedMobile = normalizeMobile(mobile);
const existingUser = await User.findOne({
$or: [{ email }, { mobile: normalizedMobile }],
});
if (existingUser) {
throw new Error("User already exists with this email or mobile.");
}

// Generate a temp user ID


const tempUserId = `temp_${Date.now()}_${Math.floor(Math.random() * 1000000)}`;

// Hash the password before storing in Redis


const hashedPassword = await hashPassword(password);

// Store pending user data in Redis for 5 minutes


await redis.hmset(`register:user:${tempUserId}`, {
name,
email,
mobile: normalizedMobile,
password: hashedPassword,
});
await redis.expire(`register:user:${tempUserId}`, 300); // 5 minutes

// Generate & store OTP for email


const emailOtp = generateOTP();
await redis.set(`otp:email:${email}`, emailOtp, { ex: 300 }); // Upstash syntax
await sendEmail(email, "Email OTP", `Your OTP is ${emailOtp}`);

// Generate & store OTP for mobile

✅ Upstash syntax
const mobileOtp = generateOTP();
await redis.set(`otp:mobile:${normalizedMobile}`, mobileOtp, { ex: 300 }); //
// TODO: Send mobileOtp via SMS provider here

return { userId: tempUserId, email, mobile: normalizedMobile };


};

// Step 2: Verify OTPs and create actual user


export const registerVerify = async (
tempUserId: string,
emailOtp: string,
mobileOtp: string
) => {
const pendingUser = await redis.hgetall(`register:user:${tempUserId}`);
if (!pendingUser || !pendingUser.email || !pendingUser.mobile) {
throw new Error("Invalid or expired registration session.");
}

// Check email OTP


const storedEmailOtp = await redis.get(`otp:email:${pendingUser.email}`);
if (!storedEmailOtp || storedEmailOtp !== emailOtp) {
throw new Error("Invalid or expired email OTP.");
}

// Check mobile OTP


const storedMobileOtp = await redis.get(`otp:mobile:${pendingUser.mobile}`);
if (!storedMobileOtp || storedMobileOtp !== mobileOtp) {
throw new Error("Invalid or expired mobile OTP.");
}

// Create actual user in DB


const user = await User.create({
name: pendingUser.name,
email: pendingUser.email,
mobile: pendingUser.mobile,
password: pendingUser.password,
emailVerified: true,
phoneVerified: true,
});

// Cleanup Redis
await redis.del(`register:user:${tempUserId}`);
await redis.del(`otp:email:${pendingUser.email}`);
await redis.del(`otp:mobile:${pendingUser.mobile}`);

const token = createToken({ uid: user._id, email: user.email });

return { token, user };


};

// -------------------- LOGIN --------------------

// Login with email & password


export const loginWithPassword = async (email: string, password: string) => {
const user = await User.findOne({ email });
if (!user) throw new Error("User not found.");

const isMatch = await comparePassword(password, user.password);


if (!isMatch) throw new Error("Invalid password.");
const token = createToken({ uid: user._id, email: user.email });
return { token, user };
};

// Login with mobile OTP


export const loginWithMobileOTP = async (mobile: string, otp: string) => {
const normalizedMobile = normalizeMobile(mobile);
const user = await User.findOne({ mobile: normalizedMobile });
if (!user) throw new Error("User not found.");

const storedOtp = await redis.get(`otp:mobile:${normalizedMobile}`);


if (!storedOtp || storedOtp !== otp) {
throw new Error("Invalid or expired OTP.");
}

// OTP matched, delete it now


await redis.del(`otp:mobile:${normalizedMobile}`);

const token = createToken({ uid: user._id, email: user.email });


return { token, user };
};

// -------------------- OTP SENDER --------------------


export const sendOtpService = async (
type: "email" | "mobile",
receiver: string
) => {
if (type === "email") {


const otp = generateOTP();
await redis.set(`otp:email:${receiver}`, otp, { ex: 300 }); // Upstash syntax
await sendEmail(receiver, "Email OTP", `Your OTP is ${otp}`);
return { message: "Email OTP sent" };
} else if (type === "mobile") {
const normalizedReceiver = normalizeMobile(receiver);


const otp = generateOTP();
await redis.set(`otp:mobile:${normalizedReceiver}`, otp, { ex: 300 }); // Upstash syntax
// TODO: send SMS here
return { message: "Mobile OTP saved and ready to send" };
} else {
throw new Error("Invalid OTP type");
}
};
======= File : server/utils/email.ts ========
import nodemailer from "nodemailer";

/**
* Configure the transporter with Gmail SMTP or your preferred service.
* Use env vars: EMAIL_USER, EMAIL_PASS
*/
const transporter = nodemailer.createTransport({
service: "gmail",
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASS,
},
});

/**
* Send an OTP email to the specified recipient.
*/
export async function sendEmail(email: string, subject: string, body: string): Promise<void> {
const mailOptions = {
from: process.env.EMAIL_USER,
to: email,
subject,
text: body,
};

await transporter.sendMail(mailOptions);
}

====== File : server/utils/hash.util.ts ======


import bcrypt from "bcryptjs";

/**
* Hash a password using bcrypt.
* @param password - The plain text password
* @returns The hashed password string
*/
export const hashPassword = async (password: string): Promise<string> => {
const saltRounds = 10;
const salt = await bcrypt.genSalt(saltRounds);
return bcrypt.hash(password, salt);
};
/**
* Compare a plain password with a hashed password.
* @param password - The plain text password
* @param hashedPassword - The hashed password from the DB
* @returns True if match, false otherwise
*/
export const comparePassword = async (
password: string,
hashedPassword: string
): Promise<boolean> => {
return bcrypt.compare(password, hashedPassword);
};

======== File : server/util/jwt.util.ts ======


// server/utils/jwt.util.ts
import jwt from "jsonwebtoken";

const JWT_SECRET = process.env.JWT_SECRET || "default_secret";


const JWT_EXPIRES_IN = "7d";

/**
* Alias generateToken so existing code using generateToken still works
*/
export function generateToken(payload: object): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
}

/**
* Keep your original createToken name for flexibility
*/
export function createToken(payload: object): string {
return jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRES_IN });
}

export function verifyToken(token: string): any {


try {
return jwt.verify(token, JWT_SECRET);
} catch (err) {
return null;
}
}
===== File : server/utils/otp.util.ts ======
import redis from "../config/redis";

const OTP_EXPIRATION_SECONDS = 300; // 5 minutes

// Normalize email for consistent keying


function normalizeEmail(email: string): string {
return email.trim().toLowerCase();
}

// Normalize mobile for consistent keying (+91xxxxxxxxxx)


function normalizeMobile(mobile: string): string {
let cleaned = mobile.replace(/\D/g, "");
if (cleaned.length === 10) cleaned = "91" + cleaned;
if (!cleaned.startsWith("91")) cleaned = "91" + cleaned.slice(-10);
return "+" + cleaned;
}

// Generate random 6-digit OTP


export function generateOTP(): string {
return Math.floor(100000 + Math.random() * 900000).toString();
}

// ------------------- EMAIL OTP -------------------

// Store OTP in Redis for email


export async function setEmailOTP(email: string, otp: string): Promise<void> {
const key = `otp:email:${normalizeEmail(email)}`;
await redis.set(key, otp, { ex: OTP_EXPIRATION_SECONDS });
}

// Retrieve OTP from Redis for email


export async function getEmailOTP(email: string): Promise<string | null> {
const key = `otp:email:${normalizeEmail(email)}`;
return await redis.get(key);
}

// Verify OTP for email (DO NOT delete OTP here)


export async function verifyEmailOTP(email: string, inputOtp: string | number):
Promise<boolean> {
const storedOtp = await getEmailOTP(email);
console.log(`[OTP VERIFY] Email: ${email} | Input OTP: ${inputOtp} | Stored OTP:
${storedOtp}`);
return String(storedOtp || "").trim() === String(inputOtp || "").trim();
}

// ------------------- MOBILE OTP -------------------

// Store OTP in Redis for mobile


export async function setMobileOTP(mobile: string, otp: string): Promise<void> {
const key = `otp:mobile:${normalizeMobile(mobile)}`;
await redis.set(key, otp, { ex: OTP_EXPIRATION_SECONDS });
}

// Retrieve OTP from Redis for mobile


export async function getMobileOTP(mobile: string): Promise<string | null> {
const key = `otp:mobile:${normalizeMobile(mobile)}`;
return await redis.get(key);
}

// Verify OTP for mobile (DO NOT delete OTP here)


export async function verifyMobileOTP(mobile: string, inputOtp: string | number):
Promise<boolean> {
const storedOtp = await getMobileOTP(mobile);
console.log(`[OTP VERIFY] Mobile: ${mobile} | Input OTP: ${inputOtp} | Stored OTP:
${storedOtp}`);
return String(storedOtp || "").trim() === String(inputOtp || "").trim();
}

// ------------------- ALIAS EXPORTS -------------------


// For backward compatibility
export const setOTP = setEmailOTP;
export const getOTP = getEmailOTP;
export const verifyOTP = verifyEmailOTP;
// Note: deleteOTP alias is NOT exported as per new plan to delete OTP only after user creation
success

====== File : server/utils/redis.ts =======


import { Redis } from "@upstash/redis";

const redis = new Redis({


url: process.env.UPSTASH_REDIS_REST_URL,
token: process.env.UPSTASH_REDIS_REST_TOKEN,
});
export default redis;

======= File : server/utils/sms.util.ts ========


// server/utils/sms.util.ts

export const sendSMS = async (mobile: string, message: string): Promise<boolean> => {

📱
try {
console.log(` Sending SMS to ${mobile}: ${message}`);
// TODO: Integrate real SMS API here (e.g., Twilio or Fast2SMS)
return true;


} catch (error) {
console.error(" SMS sending failed:", error);
return false;
}
};

You might also like