diff --git a/backend/controllers/botController.js b/backend/controllers/botController.js index ca53f6b1..916014f7 100644 --- a/backend/controllers/botController.js +++ b/backend/controllers/botController.js @@ -1,5 +1,4 @@ -/* eslint-disable no-undef */ -const { genRes } = require("../utils/botService"); +import { genRes } from "../utils/botService.js"; const returnResponse = async (req, res) => { try { @@ -13,4 +12,4 @@ const returnResponse = async (req, res) => { } }; -module.exports = returnResponse; +export default returnResponse; diff --git a/backend/controllers/userController.js b/backend/controllers/userController.js new file mode 100644 index 00000000..95c31c1d --- /dev/null +++ b/backend/controllers/userController.js @@ -0,0 +1,141 @@ +import bcrypt from 'bcryptjs'; +import { PrismaClient } from '@prisma/client'; +import { generateToken } from '../utils/jwt.js'; + + +const prisma = new PrismaClient(); + + +export const registerUser = async(req,res)=>{ + try { + const {email,password,firstName,lastName} = req.body + + if(!email || !password || !firstName || !lastName){ + return res.status(400).json({ + success:false, + message:"Please provide all the fields" + }) + } + const existingUser = await prisma.user.findUnique({ + where:{email} + }) + + if(existingUser){ + return res.status(400).json({ + success:false, + message:"User with this email already exists" + }) + } + const salt = await bcrypt.genSalt(10); + const hashedPassword = await bcrypt.hash(password,salt) + const user = await prisma.user.create({ + data:{ + firstName, + lastName, + email, + password:hashedPassword + } + }) + const token = generateToken(user.id) + const {password:_, ...userResponse} = user + res.status(201).json({ + success:true, + data:{ + user:userResponse, + token + }, + message:"Registration Successfull" + }) + } catch (error) { + console.error("Register error:",error) + res.status(500).json({ + success:false, + message:"Server error during registration", + error:error.message + }) + } +} + +export const login = async(req,res)=>{ + try { + const {email,password} = req.body + if(!email || !password){ + return res.status(400).json({ + success:false, + message:"Please provide email and password" + }) + } + const user = await prisma.user.findUnique({ + where:{ + email + } + }) + if(!user){ + return res.status(401).json({ + success: false, + message:"Invalid email or password" + }) + } + const isPasswordValid = await bcrypt.compare(password,user.password) + if(!isPasswordValid){ + return res.status(401).json({ + success:false, + message:"Invalid password" + }) + } + const token = generateToken(user.id) + const {password:_,...userResponse} = user + res.status(200).json({ + success:true, + data:{ + user:userResponse, + token + }, + message:"Login Successfull" + }) + } catch (error) { + console.error("Login Error",error) + res.status(500).json({ + success:false, + message:"Server error during login", + error:error.message + }) + } +} + +export const getCurrentUser = async(req,res)=>{ + try { + const user = await prisma.user.findUnique({ + where:{ + id:req.user.id + }, + select:{ + id:true, + firstName:true, + lastName:true, + email:true, + type:true, + createdAt:true, + updatedAt:true + } + }) + if(!user){ + return res.status(404).json({ + success:false, + message:"User not found" + }) + } + res.status(200).json({ + success:true, + data:{user}, + message:"User retrieved successfully" + }) + } catch (error) { + console.error("Get current user error",error) + res.status(500).json({ + success:false, + message:"Server error", + error:error.message + }) + } +} \ No newline at end of file diff --git a/backend/index.js b/backend/index.js index 84fc798c..a7259159 100644 --- a/backend/index.js +++ b/backend/index.js @@ -1,8 +1,9 @@ -/* eslint-disable no-undef */ -const express = require("express"); -require("dotenv").config(); -const botRouter = require("./routes/botRoutes"); -const cors = require("cors"); +import express from "express"; +import dotenv from "dotenv"; +import botRouter from "./routes/botRoutes.js"; +import cors from "cors"; +import userRoutes from "./routes/userRoutes.js" +dotenv.config(); const app = express(); app.use( @@ -17,8 +18,8 @@ const port = process.env.SERVER_PORT || 3000; app.use(express.json()); -app.use("/bot", botRouter); - +app.use("/api/bot", botRouter); +app.use("/api/auth",userRoutes) app.get("/", (req, res) => { res.send("Backend is Running!"); }); diff --git a/backend/middleware/authMiddleware.js b/backend/middleware/authMiddleware.js new file mode 100644 index 00000000..91fea2ef --- /dev/null +++ b/backend/middleware/authMiddleware.js @@ -0,0 +1,67 @@ +import jwt from 'jsonwebtoken' +import { PrismaClient } from '@prisma/client' + + +const prisma = new PrismaClient() + +export const protect = async(req,res,next)=>{ + try { + let token + if(req.headers.authorization && req.headers.authorization.startsWith('Bearer')){ + token = req.headers.authorization.split(' ')[1] + } + if(!token){ + return res.status(401).json({ + success:false, + message:"Not authorized, no token provided" + }) + } + const decoded = jwt.verify(token,process.env.JWT_SECRET) + + //Get user from token + const user = await prisma.user.findUnique({ + where:{ + id:decoded.id + }, + select:{ + id:true, + firstName:true, + lastName:true, + email:true, + type:true, + createdAt:true, + updatedAt:true + } + }) + if(!user){ + return res.status(401).json({ + success:false, + message:"User not found" + }) + } + req.user = user + next(); + } catch (error) { + console.error('Auth middleware error:', error); + + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ + success: false, + message: 'Invalid token', + }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ + success: false, + message: 'Token expired', + }); + } + + res.status(401).json({ + success: false, + message: 'Not authorized', + error: error.message, + }); + } +} \ No newline at end of file diff --git a/backend/package-lock.json b/backend/package-lock.json index 693e986d..55c55d27 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -11,6 +11,11 @@ "dependencies": { "@google/genai": "^1.27.0", "@prisma/client": "^6.18.0", + "bcrypt": "^6.0.0", + "bcryptjs": "^3.0.2", + "body-parser": "^2.2.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0" }, @@ -186,6 +191,29 @@ ], "license": "MIT" }, + "node_modules/bcrypt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bcrypt/-/bcrypt-6.0.0.tgz", + "integrity": "sha512-cU8v/EGSrnH+HnxV2z0J7/blxH8gq7Xh2JFT6Aroax7UohdmiJJlxApMxtKfuI7z68NvvVcmR78k2LbT6efhRg==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^8.3.0", + "node-gyp-build": "^4.8.4" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/bcryptjs": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.2.tgz", + "integrity": "sha512-k38b3XOZKv60C4E2hVsXTolJWfkGRMbILBIe2IBITXciy5bOsTKot5kDrf3ZfufQtQOUN5mXceUEpU1rTl9Uog==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, "node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", diff --git a/backend/package.json b/backend/package.json index d9c4b880..eb9abd1d 100644 --- a/backend/package.json +++ b/backend/package.json @@ -2,6 +2,7 @@ "name": "backend", "version": "1.0.0", "main": "index.js", + "type": "module", "scripts": { "test": "echo \"Error: no test specified\" && exit 1", "start": "nodemon index.js" @@ -13,6 +14,11 @@ "dependencies": { "@google/genai": "^1.27.0", "@prisma/client": "^6.18.0", + "bcrypt": "^6.0.0", + "bcryptjs": "^3.0.2", + "body-parser": "^2.2.0", + "cookie-parser": "^1.4.7", + "cors": "^2.8.5", "dotenv": "^17.2.3", "express": "^5.1.0" }, diff --git a/backend/prisma/migrations/20251020103625_init/migration.sql b/backend/prisma/migrations/20251020103625_init/migration.sql deleted file mode 100644 index 3da291de..00000000 --- a/backend/prisma/migrations/20251020103625_init/migration.sql +++ /dev/null @@ -1,10 +0,0 @@ --- CreateTable -CREATE TABLE "Product" ( - "id" SERIAL NOT NULL, - "name" TEXT NOT NULL, - "price" DOUBLE PRECISION NOT NULL, - "inStock" BOOLEAN NOT NULL DEFAULT true, - "createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP, - - CONSTRAINT "Product_pkey" PRIMARY KEY ("id") -); diff --git a/backend/prisma/migrations/20251020170213_normal_jwt_setup/migration.sql b/backend/prisma/migrations/20251101103249_init/migration.sql similarity index 80% rename from backend/prisma/migrations/20251020170213_normal_jwt_setup/migration.sql rename to backend/prisma/migrations/20251101103249_init/migration.sql index 585236c2..afa2ac3e 100644 --- a/backend/prisma/migrations/20251020170213_normal_jwt_setup/migration.sql +++ b/backend/prisma/migrations/20251101103249_init/migration.sql @@ -1,18 +1,9 @@ -/* - Warnings: - - - You are about to drop the `Product` table. If the table is not empty, all the data it contains will be lost. - -*/ -- CreateEnum CREATE TYPE "Gender" AS ENUM ('male', 'female', 'other'); -- CreateEnum CREATE TYPE "UserType" AS ENUM ('ADMIN', 'USER', 'SELLER'); --- DropTable -DROP TABLE "public"."Product"; - -- CreateTable CREATE TABLE "User" ( "id" SERIAL NOT NULL, diff --git a/backend/prisma/migrations/20251101104032_updated/migration.sql b/backend/prisma/migrations/20251101104032_updated/migration.sql new file mode 100644 index 00000000..40bbaa09 --- /dev/null +++ b/backend/prisma/migrations/20251101104032_updated/migration.sql @@ -0,0 +1,23 @@ +/* + Warnings: + + - You are about to drop the column `addresses` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `dob` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `gender` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `name` on the `User` table. All the data in the column will be lost. + - You are about to drop the column `phone` on the `User` table. All the data in the column will be lost. + - Added the required column `firstName` to the `User` table without a default value. This is not possible if the table is not empty. + - Added the required column `lastName` to the `User` table without a default value. This is not possible if the table is not empty. + +*/ +-- DropIndex +DROP INDEX "public"."User_phone_key"; + +-- AlterTable +ALTER TABLE "User" DROP COLUMN "addresses", +DROP COLUMN "dob", +DROP COLUMN "gender", +DROP COLUMN "name", +DROP COLUMN "phone", +ADD COLUMN "firstName" TEXT NOT NULL, +ADD COLUMN "lastName" TEXT NOT NULL; diff --git a/backend/prisma/schema.prisma b/backend/prisma/schema.prisma index ed6bc0a8..46295203 100644 --- a/backend/prisma/schema.prisma +++ b/backend/prisma/schema.prisma @@ -15,13 +15,10 @@ datasource db { model User { id Int @id @default(autoincrement()) - name String + firstName String + lastName String email String @unique password String - phone String? @unique - gender Gender? - dob DateTime? - addresses Json? type UserType @default(USER) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt diff --git a/backend/routes/botRoutes.js b/backend/routes/botRoutes.js index d970c4b1..0e5cb607 100644 --- a/backend/routes/botRoutes.js +++ b/backend/routes/botRoutes.js @@ -1,10 +1,8 @@ -/* eslint-disable no-undef */ -const express = require("express"); -const returnResponse = require("../controllers/botController"); +import express from "express"; +import returnResponse from "../controllers/botController.js"; + const botRouter = express.Router(); botRouter.post("/", returnResponse); -module.exports = { - botRouter -}; +export default botRouter; diff --git a/backend/routes/userRoutes.js b/backend/routes/userRoutes.js new file mode 100644 index 00000000..5754427e --- /dev/null +++ b/backend/routes/userRoutes.js @@ -0,0 +1,13 @@ +import express from 'express' +import { registerUser,login,getCurrentUser } from '../controllers/userController.js' +import { protect } from '../middleware/authMiddleware.js' + + +const router = express.Router() + +router.post('/register',registerUser) +router.post('/login',login) + +router.get('/me',protect,getCurrentUser) + +export default router \ No newline at end of file diff --git a/backend/utils/botService.js b/backend/utils/botService.js index d1a7daec..c9c44378 100644 --- a/backend/utils/botService.js +++ b/backend/utils/botService.js @@ -1,5 +1,4 @@ -/* eslint-disable no-undef */ -const { GoogleGenAI } = require("@google/genai"); +import { GoogleGenAI } from "@google/genai"; const systemPrompt = ` You are SAST BOT — the official website assistant for the Society for Astronomy and Space Technology (SAST). @@ -30,7 +29,7 @@ You may summarize or explain scientific concepts, but do not make assumptions or `; const ai = new GoogleGenAI({ apiKey: process.env.GEMINI_API_KEY }); -exports.genRes = async (msg, history) => { +export const genRes = async (msg, history) => { const chat = await ai.chats.create({ model: "gemini-2.5-flash", history: history, diff --git a/backend/utils/jwt.js b/backend/utils/jwt.js new file mode 100644 index 00000000..64ef4677 --- /dev/null +++ b/backend/utils/jwt.js @@ -0,0 +1,7 @@ +import jwt from 'jsonwebtoken' + +export const generateToken = (userId) =>{ + return jwt.sign({id:userId},process.env.JWT_SECRET,{ + expiresIn:'7d', + }); +} \ No newline at end of file