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
44 changes: 0 additions & 44 deletions backend/src/controllers/auth.controller.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the OTP/auth logic being changed in this PR? These changes don't seem related to #119. Please revert the unrelated auth changes so this PR stays scoped to sticker support.

Original file line number Diff line number Diff line change
Expand Up @@ -89,50 +89,6 @@ export const signup = async (req, res) => {
),
}))
);
// Test-only bypass. It requires an explicit opt-in and can never run in
// production, even if the environment flag is set accidentally.
const isEmailVerificationBypassed =
process.env.NODE_ENV === "development" &&
process.env.BYPASS_EMAIL_VERIFICATION === "true";

if (isEmailVerificationBypassed) {
// Auto-verify the user and log them in immediately
const newUser = await User.create({
fullName,
email: normalizedEmail,
password: hashedPassword,
securityQuestions: hashedQuestions,
role: "user",
isVerified: true, // skip email verification
});

const token = generateToken(newUser._id);
const refreshToken = generateRefreshToken(newUser._id);
const refreshTokenHash = crypto.createHash("sha256").update(refreshToken).digest("hex");
newUser.refreshTokenHash = refreshTokenHash;
await newUser.save();

res.cookie("refreshToken", refreshToken, {
httpOnly: true,
sameSite: "strict",
secure: false,
maxAge: 7 * 24 * 60 * 60 * 1000,
});

console.log("[DEV MODE] User auto-verified, no OTP email sent.");

return res.status(201).json({
_id: newUser._id,
fullName: newUser.fullName,
email: newUser.email,
profilePic: newUser.profilePic,
role: newUser.role,
token,
message: "[DEV] Account created and verified automatically (dev mode).",
});
}
// --- END DEV MODE BYPASS ---

const verificationOtp = crypto.randomInt(100000, 1000000).toString();

const hashedVerificationOtp = await bcrypt.hash(
Expand Down
27 changes: 17 additions & 10 deletions backend/src/controllers/message.controller.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import mongoose from "mongoose";
import User from "../models/user.model.js";
import Message from "../models/message.model.js";
import Group from "../models/group.model.js";
import cloudinary from "../lib/cloudinary.js";
import { emitToUser, io } from "../lib/socket.js";
import analyzeMessage from "../lib/mlService.js";
import { emitToUser, io } from "../lib/socket.js";
import Group from "../models/group.model.js";
import Message from "../models/message.model.js";
import User from "../models/user.model.js";

export const updateChatWallpaper = async (req, res) => {
try {
Expand Down Expand Up @@ -35,7 +35,7 @@
chatId,
wallpaper: upload.secure_url,
});
} catch (err) {

Check warning on line 38 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Failed to update wallpaper" });
}
};
Expand Down Expand Up @@ -90,7 +90,7 @@
deletedForEveryone: false,
deletedFor: { $ne: myId },
})
.populate("replyTo", "text image senderId")
.populate("replyTo", "text image sticker senderId")
.sort({ createdAt: 1 });

await Message.updateMany(
Expand All @@ -110,14 +110,14 @@
});

res.status(200).json(messages);
} catch (err) {

Check warning on line 113 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};

export const sendMessage = async (req, res) => {
try {
const { text, image, audio, file, replyTo } = req.body;
const { text, image, audio, file, sticker, replyTo } = req.body;
const { id: receiverId } = req.params;
const senderId = req.user._id;

Expand All @@ -137,7 +137,7 @@
});
}

if (!text && !image && !audio && !file) {
if (!text && !image && !audio && !file && !sticker) {
return res.status(400).json({ message: "Message cannot be empty" });
}

Expand Down Expand Up @@ -189,6 +189,7 @@
image: imageUrl,
audio: audioUrl,
file: fileData,
sticker: sticker || "",
replyTo: replyTo || null,
status: "sent",

Expand All @@ -201,7 +202,7 @@

message = await message.populate({
path: "replyTo",
select: "text image senderId",
select: "text image sticker senderId",
populate: {
path: "senderId",
select: "fullName profilePic",
Expand All @@ -221,7 +222,7 @@
message,
smartReplies: analysis.smart_replies,
});
} catch (err) {

Check warning on line 225 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};
Expand Down Expand Up @@ -249,11 +250,11 @@
deletedFor: { $ne: userId },
})
.populate("senderId", "fullName profilePic")
.populate("replyTo", "text image senderId")
.populate("replyTo", "text image sticker senderId")
.sort({ createdAt: 1 });

res.status(200).json(messages);
} catch (err) {

Check warning on line 257 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};
Expand All @@ -262,7 +263,7 @@
try {
const { groupId } = req.params;
const senderId = req.user._id;
const { text, image, audio, file, replyTo } = req.body;
const { text, image, audio, file, sticker, replyTo } = req.body;

if (!mongoose.Types.ObjectId.isValid(groupId)) {
return res.status(400).json({ message: "Invalid group id" });
Expand All @@ -281,6 +282,10 @@
if (!isMember)
return res.status(403).json({ message: "Not a group member" });

if (!text && !image && !audio && !file && !sticker) {
return res.status(400).json({ message: "Message cannot be empty" });
}

let imageUrl = "";
let audioUrl = "";
let fileData = null;
Expand Down Expand Up @@ -329,6 +334,7 @@
image: imageUrl,
audio: audioUrl,
file: fileData,
sticker: sticker || "",
replyTo: replyTo || null,
status: "sent",

Expand All @@ -353,7 +359,7 @@
message,
smartReplies: analysis.smart_replies,
});
} catch (err) {

Check warning on line 362 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};
Expand Down Expand Up @@ -449,6 +455,7 @@
image: originalMessage.image,
audio: originalMessage.audio,
file: safeFile,
sticker: originalMessage.sticker || "",
isForwarded: true,
originalMessageId: originalMessage._id,
});
Expand Down Expand Up @@ -488,7 +495,7 @@
});

res.status(200).json({ success: true });
} catch (err) {

Check warning on line 498 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};
Expand Down Expand Up @@ -518,7 +525,7 @@
}

res.status(200).json({ success: true });
} catch (err) {

Check warning on line 528 in backend/src/controllers/message.controller.js

View workflow job for this annotation

GitHub Actions / backend-build

'err' is defined but never used
res.status(500).json({ message: "Internal Server Error" });
}
};
Expand Down
188 changes: 188 additions & 0 deletions backend/src/controllers/sticker.controller.js

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we validate stickerUrl against the available sticker packs before saving/sending it? Right now it looks like an arbitrary URL could be stored as a favorite/recent sticker.

Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
import User from "../models/user.model.js";

// Built-in sticker packs definition (also served via API for dynamic pack extensions)
export const BUILTIN_STICKER_PACKS = [
{
id: "reactions",
name: "Reactions",
icon: "😂",
category: "Emotions",
stickers: [
{ id: "mindblown", name: "Mind Blown", url: "/stickers/reactions/mindblown.svg", tags: ["mindblown", "shock", "wow", "explode", "omg"] },
{ id: "laughing", name: "Laughing", url: "/stickers/reactions/laughing.svg", tags: ["laugh", "lol", "haha", "rofl", "joy"] },
{ id: "crying", name: "Crying", url: "/stickers/reactions/crying.svg", tags: ["cry", "sad", "tears", "sob", "upset"] },
{ id: "party", name: "Party Time", url: "/stickers/reactions/party.svg", tags: ["party", "celebrate", "confetti", "yay", "cheers"] },
{ id: "cool", name: "Cool Sunglasses", url: "/stickers/reactions/cool.svg", tags: ["cool", "sunglasses", "chill", "boss", "swag"] },
{ id: "heart_eyes", name: "Heart Eyes", url: "/stickers/reactions/heart_eyes.svg", tags: ["love", "heart", "eyes", "crush", "cute"] },
{ id: "fire", name: "On Fire", url: "/stickers/reactions/fire.svg", tags: ["fire", "lit", "hot", "flame", "awesome"] },
{ id: "facepalm", name: "Facepalm", url: "/stickers/reactions/facepalm.svg", tags: ["facepalm", "smh", "disappointed", "duh", "oops"] },
{ id: "thinking", name: "Thinking", url: "/stickers/reactions/thinking.svg", tags: ["think", "hmm", "ponder", "wonder", "curious"] },
{ id: "shocked", name: "Shocked", url: "/stickers/reactions/shocked.svg", tags: ["shock", "gasp", "scared", "fear", "omg"] },
{ id: "angry", name: "Angry", url: "/stickers/reactions/angry.svg", tags: ["angry", "mad", "rage", "furious", "annoyed"] },
{ id: "angel", name: "Innocent Angel", url: "/stickers/reactions/angel.svg", tags: ["angel", "innocent", "good", "halo", "pure"] },
],
},
{
id: "pepe_memes",
name: "Pepe & Memes",
icon: "🐸",
category: "Memes",
stickers: [
{ id: "pepe_happy", name: "Pepe Happy", url: "/stickers/memes/pepe_happy.svg", tags: ["pepe", "happy", "smile", "feelsgood", "frog"] },
{ id: "pepe_sad", name: "Pepe Sad", url: "/stickers/memes/pepe_sad.svg", tags: ["pepe", "sad", "cry", "feelsbadman", "rain"] },
{ id: "pepe_hype", name: "Pepe Hype", url: "/stickers/memes/pepe_hype.svg", tags: ["pepe", "hype", "party", "dance", "energy"] },
{ id: "pepe_smart", name: "Big Brain Pepe", url: "/stickers/memes/pepe_smart.svg", tags: ["pepe", "brain", "smart", "genius", "iq"] },
{ id: "doge_wow", name: "Doge Wow", url: "/stickers/memes/doge_wow.svg", tags: ["doge", "wow", "shiba", "dog", "meme"] },
{ id: "score_100", name: "100 Percent", url: "/stickers/memes/score_100.svg", tags: ["100", "score", "perfect", "facts", "real"] },
{ id: "gg_wp", name: "GG Well Played", url: "/stickers/memes/gg_wp.svg", tags: ["gg", "game", "win", "gamer", "wp"] },
{ id: "stonks", name: "Stonks Up", url: "/stickers/memes/stonks.svg", tags: ["stonks", "profit", "money", "up", "crypto"] },
{ id: "popcat", name: "Pop Cat", url: "/stickers/memes/popcat.svg", tags: ["popcat", "cat", "mouth", "pop", "meme"] },
{ id: "bruh", name: "Bruh Moment", url: "/stickers/memes/bruh.svg", tags: ["bruh", "moment", "what", "bro", "meme"] },
],
},
{
id: "cute_animals",
name: "Cute Animals",
icon: "🐱",
category: "Animals",
stickers: [
{ id: "cat_heart", name: "Cat Love", url: "/stickers/animals/cat_heart.svg", tags: ["cat", "love", "heart", "kitty", "purr"] },
{ id: "cat_laptop", name: "Coder Cat", url: "/stickers/animals/cat_laptop.svg", tags: ["cat", "laptop", "code", "work", "busy"] },
{ id: "dog_happy", name: "Happy Pup", url: "/stickers/animals/dog_happy.svg", tags: ["dog", "pup", "wag", "happy", "cute"] },
{ id: "fox_sleeping", name: "Sleepy Fox", url: "/stickers/animals/fox_sleeping.svg", tags: ["fox", "sleep", "rest", "night", "bed"] },
{ id: "bear_hug", name: "Bear Hug", url: "/stickers/animals/bear_hug.svg", tags: ["bear", "hug", "love", "cuddle", "friend"] },
{ id: "bunny_cheer", name: "Cheering Bunny", url: "/stickers/animals/bunny_cheer.svg", tags: ["bunny", "rabbit", "hop", "cheer", "jump"] },
{ id: "panda_bamboo", name: "Panda Munch", url: "/stickers/animals/panda_bamboo.svg", tags: ["panda", "eat", "food", "cute", "bamboo"] },
{ id: "penguin_waddle", name: "Waddling Penguin", url: "/stickers/animals/penguin_waddle.svg", tags: ["penguin", "walk", "cold", "cute", "bird"] },
],
},
{
id: "anime_chibi",
name: "Anime & Chibi",
icon: "✨",
category: "Anime",
stickers: [
{ id: "chibi_sparkle", name: "Sparkle Eyes", url: "/stickers/anime/chibi_sparkle.svg", tags: ["anime", "sparkle", "eyes", "star", "chibi"] },
{ id: "chibi_rage", name: "Chibi Rage", url: "/stickers/anime/chibi_rage.svg", tags: ["anime", "rage", "anger", "flame", "chibi"] },
{ id: "chibi_sweat", name: "Nervous Sweat", url: "/stickers/anime/chibi_sweat.svg", tags: ["anime", "nervous", "sweat", "awkward", "oops"] },
{ id: "chibi_blush", name: "Kawaii Blush", url: "/stickers/anime/chibi_blush.svg", tags: ["anime", "blush", "kawaii", "shy", "cute"] },
{ id: "chibi_peace", name: "Peace Sign", url: "/stickers/anime/chibi_peace.svg", tags: ["anime", "peace", "victory", "pose", "v"] },
{ id: "chibi_sleepy", name: "Sleepy Chibi", url: "/stickers/anime/chibi_sleepy.svg", tags: ["anime", "sleep", "zzz", "tired", "chibi"] },
{ id: "chibi_gaming", name: "Pro Gamer", url: "/stickers/anime/chibi_gaming.svg", tags: ["anime", "game", "controller", "gamer", "play"] },
{ id: "chibi_coffee", name: "Coffee Time", url: "/stickers/anime/chibi_coffee.svg", tags: ["anime", "coffee", "morning", "tea", "drink"] },
],
},
{
id: "vibes_gestures",
name: "Vibes & Gestures",
icon: "✌️",
category: "Gestures",
stickers: [
{ id: "thumbs_up", name: "Thumbs Up", url: "/stickers/vibes/thumbs_up.svg", tags: ["thumbsup", "ok", "yes", "like", "agree"] },
{ id: "heart_hands", name: "Heart Hands", url: "/stickers/vibes/heart_hands.svg", tags: ["heart", "hands", "love", "kpop", "care"] },
{ id: "high_five", name: "High Five", url: "/stickers/vibes/high_five.svg", tags: ["highfive", "team", "celebrate", "slap", "hands"] },
{ id: "peace_out", name: "Peace Sign", url: "/stickers/vibes/peace_out.svg", tags: ["peace", "vibe", "bye", "chill", "cool"] },
{ id: "clapping", name: "Clapping", url: "/stickers/vibes/clapping.svg", tags: ["clap", "applause", "bravo", "great", "cheer"] },
{ id: "rocket_blast", name: "Rocket Launch", url: "/stickers/vibes/rocket_blast.svg", tags: ["rocket", "launch", "moon", "speed", "fast"] },
{ id: "sparkle_star", name: "Super Star", url: "/stickers/vibes/sparkle_star.svg", tags: ["star", "sparkle", "gold", "shine", "winner"] },
{ id: "coffee_mug", name: "Fresh Coffee", url: "/stickers/vibes/coffee_mug.svg", tags: ["coffee", "cup", "morning", "fuel", "tea"] },
],
},
];

const ALL_VALID_STICKER_URLS = new Set(
BUILTIN_STICKER_PACKS.flatMap((pack) => pack.stickers.map((s) => s.url))
);

// GET /api/stickers/packs
export const getStickerPacks = async (req, res) => {
try {
res.status(200).json({ packs: BUILTIN_STICKER_PACKS });
} catch (error) {
console.error("getStickerPacks error:", error);
res.status(500).json({ message: "Failed to load sticker packs" });
}
};

// GET /api/stickers/user-data
export const getUserStickerData = async (req, res) => {
try {
const user = await User.findById(req.user._id).select("favoriteStickers recentStickers");
if (!user) {
return res.status(404).json({ message: "User not found" });
}

res.status(200).json({
favoriteStickers: user.favoriteStickers || [],
recentStickers: user.recentStickers || [],
});
} catch (error) {
console.error("getUserStickerData error:", error);
res.status(500).json({ message: "Failed to load user sticker data" });
}
};

// POST /api/stickers/favorites/toggle
export const toggleFavoriteSticker = async (req, res) => {
try {
const { stickerUrl } = req.body;
if (!stickerUrl || typeof stickerUrl !== "string" || !ALL_VALID_STICKER_URLS.has(stickerUrl)) {
return res.status(400).json({ message: "Valid stickerUrl from official sticker packs is required" });
}

const user = await User.findById(req.user._id);
if (!user) {
return res.status(404).json({ message: "User not found" });
}

const currentFavorites = user.favoriteStickers || [];
const exists = currentFavorites.includes(stickerUrl);

let updatedFavorites;
if (exists) {
updatedFavorites = currentFavorites.filter((url) => url !== stickerUrl);
} else {
updatedFavorites = [stickerUrl, ...currentFavorites];
}

user.favoriteStickers = updatedFavorites;
await user.save();

res.status(200).json({
favoriteStickers: user.favoriteStickers,
isFavorited: !exists,
});
} catch (error) {
console.error("toggleFavoriteSticker error:", error);
res.status(500).json({ message: "Failed to toggle favorite sticker" });
}
};

// POST /api/stickers/recents
export const addRecentSticker = async (req, res) => {
try {
const { stickerUrl } = req.body;
if (!stickerUrl || typeof stickerUrl !== "string" || !ALL_VALID_STICKER_URLS.has(stickerUrl)) {
return res.status(400).json({ message: "Valid stickerUrl from official sticker packs is required" });
}

const user = await User.findById(req.user._id);
if (!user) {
return res.status(404).json({ message: "User not found" });
}

const currentRecents = user.recentStickers || [];
// Place at front, deduplicate, and limit to max 30 items
const filtered = currentRecents.filter((url) => url !== stickerUrl);
user.recentStickers = [stickerUrl, ...filtered].slice(0, 30);

await user.save();

res.status(200).json({
recentStickers: user.recentStickers,
});
} catch (error) {
console.error("addRecentSticker error:", error);
res.status(500).json({ message: "Failed to add recent sticker" });
}
};
Loading
Loading