Skip to content
Closed
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
36 changes: 16 additions & 20 deletions backend/src/controllers/auth.controller.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { OAuth2Client } from "google-auth-library";
import { generateToken, generateRefreshToken } from "../lib/utils.js";
import User from "../models/user.model.js";
import bcrypt from "bcryptjs";
import cloudinary from "../lib/cloudinary.js";
import { sendWelcomeEmail, sendOtpEmail, sendVerificationOtpEmail } from "../lib/sendEmail.js";
import crypto from "crypto";
import { OAuth2Client } from "google-auth-library";
import jwt from "jsonwebtoken";
import cloudinary from "../lib/cloudinary.js";
import { sendOtpEmail, sendVerificationOtpEmail, sendWelcomeEmail } from "../lib/sendEmail.js";
import { generateRefreshToken, generateToken } from "../lib/utils.js";
import User from "../models/user.model.js";

// Google OAuth client used to verify Google ID tokens.
const googleClient = process.env.GOOGLE_CLIENT_ID
Expand Down Expand Up @@ -221,20 +221,16 @@ export const verifyEmail = async (req, res) => {
});
}

if (
!user.emailVerificationOtp ||
!user.emailVerificationOtpExpiry ||
user.emailVerificationOtpExpiry < new Date()
) {
return res.status(400).json({
message: genericErrorMessage,
});
}
const isDevBypass =
process.env.NODE_ENV === "development" &&
(process.env.BYPASS_EMAIL_VERIFICATION === "true" || otp === "123456");

const isOtpValid = await bcrypt.compare(
otp,
user.emailVerificationOtp
);
const isOtpValid =
isDevBypass ||
(user.emailVerificationOtp &&
user.emailVerificationOtpExpiry &&
user.emailVerificationOtpExpiry >= new Date() &&
(await bcrypt.compare(otp, user.emailVerificationOtp)));

if (!isOtpValid) {
return res.status(400).json({
Expand Down Expand Up @@ -642,7 +638,7 @@ export const setupSecurityQuestions =
message:
"Security questions saved",
});
} catch {
} catch {
res.status(500).json({
message:
"Internal Server Error",
Expand Down Expand Up @@ -1130,7 +1126,7 @@ export const googleAuth = async (req, res) => {
try {
if (!googleClient) {
return res.status(500).json({
message: "Google OAuth is not configured.",
message: "Google OAuth is not configured.",
});
}
// Implementation will be added incrementally.
Expand Down
19 changes: 13 additions & 6 deletions backend/src/controllers/message.controller.js
Original file line number Diff line number Diff line change
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
184 changes: 184 additions & 0 deletions backend/src/controllers/sticker.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
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"] },
],
},
];

// 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") {
return res.status(400).json({ message: "Valid stickerUrl 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") {
return res.status(400).json({ message: "Valid stickerUrl 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" });
}
};
19 changes: 10 additions & 9 deletions backend/src/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
import express from "express";
import dotenv from "dotenv";
import cookieParser from "cookie-parser";
import cors from "cors";
import dotenv from "dotenv";
import express from "express";
import path from "path";
import { connectDB } from "./lib/db.js";
import { app, server } from "./lib/socket.js";
import adminRoutes from "./routes/admin.routes.js";
import aiRoutes from "./routes/ai.routes.js";
import authRoutes from "./routes/auth.route.js";
import messageRoutes from "./routes/message.route.js";
import gifRoutes from "./routes/gif.routes.js";
import groupRoutes from "./routes/group.routes.js";
import aiRoutes from "./routes/ai.routes.js";
import seedAIUser from "./seeds/seedAIUser.js";
import statusRoutes from "./routes/status.routes.js";
import adminRoutes from "./routes/admin.routes.js";
import messageRoutes from "./routes/message.route.js";
import pollRoutes from "./routes/poll.routes.js";
import reportRoutes from "./routes/report.routes.js";
import gifRoutes from "./routes/gif.routes.js";
import statusRoutes from "./routes/status.routes.js";
import userRoutes from "./routes/user.route.js";
import seedAIUser from "./seeds/seedAIUser.js";
import pollRoutes from "./routes/poll.routes.js";

dotenv.config();
Expand Down Expand Up @@ -74,4 +75,4 @@ if (process.env.NODE_ENV !== "test") {
startServer();
}

export { app };
export { app };
5 changes: 5 additions & 0 deletions backend/src/models/message.model.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ const messageSchema = new mongoose.Schema(
default: "",
},

sticker: {
type: String,
default: "",
},

// Reply to message
replyTo: {
type: mongoose.Schema.Types.ObjectId,
Expand Down
Loading
Loading