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
11 changes: 10 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,13 @@ dist
.pnp.*

# Miscellaneous
.DS_Store
.DS_Store

# user uploads
backend/uploads/
backend/uploads/**

# (optional) env files
.env
frontend/.env
backend/.env
106 changes: 0 additions & 106 deletions backend/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions backend/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import "dotenv/config";
import express, { NextFunction, Request, Response } from "express";
import cors from "cors";
import { isHttpError } from "http-errors";
import path from "path";
import productRoutes from "src/routes/product";
import userRoutes from "src/routes/user";
import interestEmailRoute from "src/routes/interestEmail";
Expand All @@ -25,6 +26,7 @@ app.use(
}),
);

app.use("/uploads", express.static(path.join(__dirname, "../uploads")));
app.use("/api/products", productRoutes);
app.use("/api/users", userRoutes);
app.use("/api/interestEmail", interestEmailRoute);
Expand Down
172 changes: 165 additions & 7 deletions backend/src/controllers/users.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Request, Response } from "express";
import UserModel from "src/models/user";
import { getAuth } from "firebase-admin/auth";
import { AuthenticatedRequest } from "src/validators/authUserMiddleware";
import multer from "multer";
import path from "path";
import fs from "fs";


export const getUsers = async (req: Request, res: Response) => {
try {
Expand All @@ -15,13 +20,10 @@ export const getUsers = async (req: Request, res: Response) => {
export const getUserById = async (req: Request, res: Response) => {
try {
const firebaseUid = req.params.firebaseUid;

const user = await UserModel.findOne({ firebaseUid: firebaseUid });

if (!user) {
return res.status(404).json({ message: "User not found" });
}

return res.status(200).json(user);
} catch (error) {
return res.status(500).json({ message: "Error getting user", error });
Expand All @@ -33,7 +35,6 @@ export const addUser = async (req: Request, res: Response) => {
try {
const { firebaseUid } = req.body;
const firebaseUser = await getAuth().getUser(firebaseUid);

const userEmail = firebaseUser.email;
const displayName = firebaseUser.displayName || "";

Expand All @@ -60,7 +61,6 @@ export const addUser = async (req: Request, res: Response) => {
});

await newUser.save();

res.status(201).json({ message: "User added successfully", user: newUser });
} catch (error) {
res.status(500).json({ message: "Error adding user", error });
Expand All @@ -78,7 +78,6 @@ export const deleteUserById = async (req: Request, res: Response) => {

// Toggle activeUser status
user.activeUser = false;

await user.save();

// Update Firebase user
Expand All @@ -98,6 +97,7 @@ export const updateUserById = async (req: Request, res: Response) => {
try {
const { displayName, deactivateAccount } = req.body;
const firebaseUid = req.params.firebaseUid;

const updatedUser = await UserModel.findOne({ firebaseUid: firebaseUid });
if (!updatedUser) {
throw new Error("User not found");
Expand All @@ -107,7 +107,6 @@ export const updateUserById = async (req: Request, res: Response) => {
if (displayName != undefined) {
updatedUser.displayName = displayName;
}

if (deactivateAccount != undefined) {
updatedUser.activeUser = false;
}
Expand All @@ -128,3 +127,162 @@ export const updateUserById = async (req: Request, res: Response) => {
res.status(500).json({ message: "Error updating user", error });
}
};

// Configure multer for avatar uploads
const avatarStorage = multer.diskStorage({
destination: function (req, file, cb) {
const uploadDir = "uploads/avatars";
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
const userId = req.body.userId;
if (!userId) return cb(new Error("Missing userId"), "");
const ext = path.extname(file.originalname);
cb(null, `${userId}${ext}`);
},

});

const avatarFileFilter = (
req: Express.Request,
file: Express.Multer.File,
cb: multer.FileFilterCallback,
) => {
const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("Invalid file type. Only JPEG, PNG, and WebP are allowed."));
}
};

const avatarUpload = multer({
storage: avatarStorage,
fileFilter: avatarFileFilter,
limits: {
fileSize: 5 * 1024 * 1024,
},
});

export const uploadAvatar = [
avatarUpload.single("avatar"),
async (req: AuthenticatedRequest, res: Response) => {
try {
const { userId } = req.body;

if (!userId) {
return res.status(400).json({ message: "Missing userId" });
}

if (!req.file) {
return res.status(400).json({ message: "No avatar image provided" });
}

const protocol = req.protocol;
const host = req.get("host");
const avatarUrl = `${protocol}://${host}/uploads/avatars/${req.file.filename}`;

console.log(`Avatar uploaded for user ${userId}:`, avatarUrl);

res.status(200).json({
message: "Avatar uploaded successfully",
avatarUrl: avatarUrl,
});
} catch (error) {
console.error("Error uploading avatar:", error);

if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}

res.status(500).json({
message: "Error uploading avatar",
error: error instanceof Error ? error.message : String(error),
});
}
},
];

// ========================================
// NEW COVER UPLOAD FUNCTIONALITY
// ========================================

// Configure multer for cover uploads
const coverStorage = multer.diskStorage({
destination: function (req, file, cb) {
const uploadDir = "uploads/covers";
if (!fs.existsSync(uploadDir)) {
fs.mkdirSync(uploadDir, { recursive: true });
}
cb(null, uploadDir);
},
filename: function (req, file, cb) {
const userId = req.body.userId;
if (!userId) return cb(new Error("Missing userId"), "");
const ext = path.extname(file.originalname);
cb(null, `${userId}${ext}`);
},

});

const coverFileFilter = (
req: Express.Request,
file: Express.Multer.File,
cb: multer.FileFilterCallback,
) => {
const allowedTypes = ["image/jpeg", "image/jpg", "image/png", "image/webp"];
if (allowedTypes.includes(file.mimetype)) {
cb(null, true);
} else {
cb(new Error("Invalid file type. Only JPEG, PNG, and WebP are allowed."));
}
};

const coverUpload = multer({
storage: coverStorage,
fileFilter: coverFileFilter,
limits: { fileSize: 5 * 1024 * 1024 },
});

// POST /api/users/cover
export const uploadCover = [
coverUpload.single("cover"),
async (req: AuthenticatedRequest, res: Response) => {
try {
const { userId } = req.body;

if (!userId) {
return res.status(400).json({ message: "Missing userId" });
}

if (!req.file) {
return res.status(400).json({ message: "No cover image provided" });
}

const protocol = req.protocol;
const host = req.get("host");
const coverUrl = `${protocol}://${host}/uploads/covers/${req.file.filename}`;

console.log(`Cover uploaded for user ${userId}:`, coverUrl);

return res.status(200).json({
message: "Cover uploaded successfully",
coverUrl,
});
} catch (error) {
console.error("Error uploading cover:", error);

if (req.file && fs.existsSync(req.file.path)) {
fs.unlinkSync(req.file.path);
}

return res.status(500).json({
message: "Error uploading cover",
error: error instanceof Error ? error.message : String(error),
});
}
},
];
Loading