Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions backend/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
PORT=5000
MONGO_URI=mongodb://localhost:27017/uniloot

# JWT configuration
JWT_SECRET=supersecretkey123
JWT_REFRESH_SECRET=anothersecretkey456

# Optional
NODE_ENV=development
FIREBASE_STORAGE_BUCKET=get-from-firebaseconsole
3 changes: 3 additions & 0 deletions backend/firebase-service-account.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"get it from your firebase console to use store image feature"
}
39 changes: 39 additions & 0 deletions backend/src/controllers/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import jwt, { SignOptions, JwtPayload } from "jsonwebtoken";
import dotenv from "dotenv";
dotenv.config();

if (!process.env.JWT_SECRET || !process.env.JWT_REFRESH_SECRET) {
throw new Error("JWT secrets missing in environment variables");
}
const accessSecret = process.env.JWT_SECRET as string;
const refreshSecret = process.env.JWT_REFRESH_SECRET as string;

export interface TokenPayload extends JwtPayload {
sub: string;
username?: string;
role?: string;
}
export function signAccessToken(payload: TokenPayload) {
const options: SignOptions = { expiresIn: process.env.ACCESS_TOKEN_EXPIRY as any };

const cleanPayload = { ...payload };
delete cleanPayload.iat;
delete cleanPayload.exp;

return jwt.sign(cleanPayload, accessSecret, options);
}
export function signRefreshToken(payload: TokenPayload) {
const options: SignOptions = { expiresIn: process.env.REFRESH_TOKEN_EXPIRY as any };

const cleanPayload = { ...payload };
delete cleanPayload.iat;
delete cleanPayload.exp;

return jwt.sign(cleanPayload, refreshSecret, options);
}
export function verifyAccessToken(token: string): TokenPayload {
return jwt.verify(token, accessSecret) as TokenPayload;
}
export function verifyRefreshToken(token: string): TokenPayload {
return jwt.verify(token, refreshSecret) as TokenPayload;
}
19 changes: 11 additions & 8 deletions backend/src/controllers/product.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,26 @@ import Product from "../models/product.model";

export const createProduct = async (req: Request, res: Response) => {
try {
const { name, description, price, category, stock } = req.body;
const { name, description, price, category, stock, imageUrl, condition } = req.body;

if (!name || !description || !price) {
return res.status(400).json({ message: "Missing required fields" });
if (!name || !description || !price || !category || !stock) {
return res.status(400).json({ error: "Missing required field(s)" });
}

const product = await Product.create({
const product = new Product({
name,
description,
price,
category,
stock,
imageUrl,
condition,
});
return res.status(201).json(product);
} catch (error) {
console.error("Create Product Error:", error);
res.status(500).json({ message: "Server error" });

await product.save();
res.status(201).json(product);
} catch (err: any) {
res.status(500).json({ error: err.message || "Server error" });
}
};

Expand Down
42 changes: 26 additions & 16 deletions backend/src/middleware/authMiddleware.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,38 @@
// src/middlewares/authMiddleware.ts
import { Request, Response, NextFunction } from "express";
import jwt from "jsonwebtoken";
import { User } from "../models/user.model";
const SECRET_KEY = process.env.JWT_SECRET || "your_secret_key";

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

export interface AuthRequest extends Request {
export interface AuthenticatedRequest extends Request {
user?: any;
}

export const protect = async (req: AuthRequest, res: Response, next: NextFunction) => {
let token;

if (req.headers.authorization && req.headers.authorization.startsWith("Bearer")) {
token = req.headers.authorization.split(" ")[1];
export const authenticate = (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({ message: "Unauthorized" });
}

if (!token) return res.status(401).json({ message: "Not authorized, no token" });

const token = authHeader.split(" ")[1];
try {
const decoded = jwt.verify(token, JWT_SECRET) as { id: string };
req.user = await User.findById(decoded.id).select("-password");
const decoded = jwt.verify(token, SECRET_KEY);
req.user = decoded;
next();
} catch (error) {
res.status(401).json({ message: "Not authorized, token failed" });
return res.status(401).json({ message: "Invalid token" });
}
};

// For legacy support in /api/users route expecting 'protect'
export const protect = authenticate;

// Role-based guard
export const authorizeRole = (role: string) => {
return (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
if (!req.user) {
return res.status(401).json({ error: 'Not authenticated' });
}
if (req.user.role !== role) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
};
19 changes: 19 additions & 0 deletions backend/src/middleware/uploadMiddleware.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import multer from "multer";

const upload = multer({
storage: multer.memoryStorage(),
limits: { fileSize: 2 * 1024 * 1024 },
fileFilter: (_req: any, file: { mimetype: string; }, cb: (arg0: Error | null, arg1: boolean | undefined) => void) => {
if (
file.mimetype === "image/jpeg" ||
file.mimetype === "image/png" ||
file.mimetype === "image/webp"
) {
cb(null, true);
} else {
cb(new Error("Invalid file type. Only JPEG, PNG, WEBP allowed."));
}
},
});

export default upload;
4 changes: 3 additions & 1 deletion backend/src/models/product.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export interface IProduct extends Document {
price: number;
category?: string;
stock: number;
imageUrl?: string;
createdAt: Date;
updatedAt: Date;
}
Expand All @@ -17,8 +18,9 @@ const productSchema = new Schema<IProduct>(
price: { type: Number, required: true },
category: { type: String },
stock: { type: Number, default: 0 },
imageUrl: { type: String },
},
{ timestamps: true }
);

export default mongoose.model<IProduct>("Product", productSchema);
export default mongoose.model<IProduct>("Product", productSchema);
2 changes: 1 addition & 1 deletion frontend/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

.env
node_modules
dist
dist-ssr
Expand Down
Loading
Loading