first commit

This commit is contained in:
Alaguraj0361 2026-04-17 10:55:05 +05:30
commit ec29463fb6
19 changed files with 2972 additions and 0 deletions

132
.gitignore vendored Normal file
View File

@ -0,0 +1,132 @@
# ---> Node
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

2
README.md Normal file
View File

@ -0,0 +1,2 @@
# VG Products_app_backend

View File

@ -0,0 +1,124 @@
# 🤖 VG Products — Integration & Features Documentation
Welcome to the official documentation for **VG Products**. This document provides an in-depth explanation of all integration features, architectural components, and API capabilities currently implemented in the platform.
---
## 📑 Table of Contents
1. [Platform Overview](#-platform-overview)
2. [User Authentication & Security](#-user-authentication--security)
3. [Social Media Connectivity (Meta/Instagram)](#-social-media-connectivity-metainstagram)
4. [Content & Engagement Management](#-content--engagement-management)
5. [AI-Powered Automation Suite](#-ai-powered-automation-suite)
6. [Payment & Subscription Infrastructure](#-payment--subscription-infrastructure)
7. [API Reference List](#-api-reference-list)
---
## 🌟 Platform Overview
VG Products is an all-in-one social media management and automation platform designed to help businesses manage their Instagram interaction through AI-driven insights and automated engagement. It bridges the gap between manual management and pure automation by providing a suite of tools for content monitoring and automated response systems.
---
## 🔐 User Authentication & Security
### 🔹 Local Authentication
- **JWT Implementation**: Secure token-based authentication using `jsonwebtoken`.
- **Encryption**: Password hashing via `bcrypt` for secure user data storage.
- **Session Management**: Configurable JWT expiration (default 30 days).
### 🔹 Google OAuth Integration
- **One-Tap Login**: Seamless authentication via Google using `passport-google-oauth20`.
- **Auto-Provisioning**: Automatic user record creation upon successful third-party authentication.
---
## 📱 Social Media Connectivity (Meta/Instagram)
### 🔹 Facebook Login & Instagram Graph API
VG Products uses the **Instagram Graph API** to interact with professional accounts.
- **Short-Lived to Long-Lived Token Exchange**: Automatically upgrades user access tokens to 60-day "Long-Lived" tokens for persistent connectivity.
- **Granular Permissions**: Requests only necessary scopes (`instagram_basic`, `instagram_manage_comments`, `pages_show_list`, etc.).
### 🔹 Channel & Account Management
- **Page Listing**: Fetches all Facebook Pages owned by the user.
- **IG Account Linking**: Identifies which Instagram Business accounts are linked to specific Facebook Pages.
- **Connection Persistence**: Securely stores page-level tokens for background automation tasks.
---
## 💬 Content & Engagement Management
### 🔹 Media Monitoring
- **Post Retrieval**: Fetches recent Instagram media (images, videos, reels) with metadata like captions, timestamps, and permalinks.
- **Detailed View**: Access specific media metrics and full-resolution content.
### 🔹 Comprehensive Comment System
- **Real-time Interaction**: Fetch comments for any Instagram post.
- **Standard Actions**: Support for replying to comments, deleting unwanted content, and toggling visibility (Hide/Unhide).
- **Engagement Tracking**: View comment counts and commenter details.
---
## 🤖 AI-Powered Automation Suite
The core value proposition of VG Products is its intelligent automation engine.
### 🔹 Smart Auto-Reply Engine
- **Context-Aware Processing**: Analyzes the post caption, the user's account bio, and the comment text to generate a relevant reply.
- **AI Generation**: Leverages advanced LLM logic to craft human-like responses that align with the brand's voice.
- **Intelligent Filtering**:
- **Self-Exclusion**: Automatically skips comments made by the account itself.
- **Time Window**: Defaults to processing comments within the last 24 hours to ensure relevance.
- **Duplicate Prevention**: Tracks `repliedComments` to avoid double-responding to the same user.
### 🔹 Automation Governance
- **Rate Limiting**: Built-in delays between replies to comply with Meta's rate limits and prevent "bot-like" behavior flags.
- **History Management**: Users can view automation summaries (Processed vs. Replied) and clear their reply history.
---
## 💳 Payment & Subscription Infrastructure
Powered by **Stripe**, VG Products features a robust monetization layer.
### 🔹 Tiered Subscription Plans
- **Basic, Standard, & Premium**: Flexible pricing tiers available in both **Monthly** and **Yearly** billing cycles.
- **Flexible Upgrades**: Managed through Stripe Checkout sessions.
### 🔹 Free Trial Activation
- **7-Day Trial**: One-time activation for new users to explore premium features with zero upfront cost.
- **Automated Expiry**: Features are automatically restricted once the trial period ends.
### 🔹 Billing Management
- **Self-Service Portal**: Direct integration with the **Stripe Billing Portal**, allowing users to manage payment methods (last 4 digits overview), download invoices, and cancel subscriptions without contacting support.
- **Webhook Synchronization**: Real-time updates for subscription status through Stripe Webhooks (Payment Succeeded, Failed, Cancelled).
---
## 🚀 API Reference List
### Authentication
- `GET /api/auth/status`: Check connectivity status.
- `GET /api/auth/login`: Initiate Facebook OAuth.
- `POST /api/auth/disconnect`: Revoke social connections.
### Social Meta-Data
- `GET /api/social/channels`: List available Facebook Pages.
- `POST /api/social/connect`: Link a specific Instagram Business account.
- `GET /api/social/media`: Fetch Instagram posts.
### Comments & Automation
- `POST /api/social/comments/:id/reply`: Manual reply.
- `DELETE /api/social/comments/:id`: Remove comment.
- `POST /api/social/automation/auto-reply`: Start the AI automation cycle.
- `GET /api/social/automation/replied-comments`: View reply history.
### Payments
- `POST /api/payment/create-checkout-session`: Start a subscription buy.
- `POST /api/payment/activate-trial`: Start 7-day trial.
- `POST /api/payment/create-portal-session`: Open Stripe Billing Portal.
---
*Documentation Version: 1.0.0*
*Last Updated: 2026-02-20*

13
config/db.js Normal file
View File

@ -0,0 +1,13 @@
const mongoose = require("mongoose");
const connectDB = async () => {
try {
await mongoose.connect(process.env.MONGODB_URI, { dbName: "VG Products" });
console.log("✅ MongoDB connected");
} catch (err) {
console.error("❌ MongoDB connection error:", err);
process.exit(1);
}
};
module.exports = { connectDB };

51
config/passport.js Normal file
View File

@ -0,0 +1,51 @@
const passport = require("passport");
const GoogleStrategy = require("passport-google-oauth20").Strategy;
const { getPool } = require("./db.js");
// 🧠 Save user to DB or fetch if exists
async function findOrCreateUser(profile, provider) {
const email = profile.emails?.[0]?.value || `${profile.id}@facebook.com`;
const name = profile.displayName;
const pool = await getPool();
const [existing] = await pool.execute(
"SELECT * FROM tbl_userlogin WHERE email = ? LIMIT 1",
[email]
);
if (existing.length) return existing[0];
const userid = uuidv4();
await pool.execute(
"INSERT INTO tbl_userlogin (userid, name, email, password, provider) VALUES (?, ?, ?, ?, ?)",
[userid, name, email, null, provider]
);
return { userid, name, email, provider };
}
// 🔹 Google
passport.use(
new GoogleStrategy(
{
clientID: "657565817895-sqmh7bd4cef3j7c7cmuno1tpbhg7fjbo.apps.googleusercontent.com",
clientSecret: "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",
callbackURL: `https://api.VG Products.co/api/auth/google/callback`,
},
async (accessToken, refreshToken, profile, done) => {
const user = await findOrCreateUser(profile, "google");
done(null, user);
}
)
);
// 🔹 Serialize/deserialize
passport.serializeUser((user, done) => done(null, user));
passport.deserializeUser((user, done) => done(null, user));
module.exports = passport;

62
data/store.json Normal file
View File

@ -0,0 +1,62 @@
{
"info@metatroncubesolutions.com": {
"longUserToken": "EAAH1s4xuDg8BQDjRlZACAb7BIoBwHOKMYZCbL4agvmtcRWyAouYZCJjg1MSQJr7O0VsnL91VnvJOrB2ZC2N4ZBZBpguGYSHgJYhTzO59fJ0H6RwllZAapjReBnjSWZAoqOHBsh5zd2XUdHM3ANahOhrwCOaAooZAd3w2kLZAsOJe7YmuwHOyxZBvsgYzWu1gam5tvHadHCvji9sSnW1FpSI",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQFqgeSlB3I3A3f4hZBSZCbL6PtIUFWRhIeTZBxBZAM7unQtBlNt3orBaBYNIBzyw3wDAqqEV19WKsicDZCMb2dhmCGmPpgnCd2fha7GIzUWoBz9bGlq6EF3XO0WeChfjFRM04eErhceIVVdL20SZCRj9a08ML6faYZBeX4iVNX6nwry4tZCLmgAAc7lY8yX5qNcVVUX1Vqoe",
"igUserId": "17841474871998063"
},
"null": {
"longUserToken": "EAAH1s4xuDg8BQOioF1dZCz50GaZC2Rzw1ZCGLj1RQ06sNspbZBnh3O6xnpzgd4q9PuE41Wxw20hDSKKUsHrZBJ3EFoJDbMTNsQbR6vrsUylbLsb41ZBAZAXwVyMU5LfauZAxAm488Qe8v54sHZB7j0s8QroFE2PClmxqvZBEsWvmpTx73ZATbyTjnE6ZBpt8oi2IwkT5",
"longUserTokenExpiresAt": null,
"pageId": "356453210890355",
"pageToken": "EAAH1s4xuDg8BQZAOdlPaZAyTZC1HMwjFyamNVcfvw5JWNgnYgZBJBaSRVd7EZB6uyy8DqmQjTMLQglXJhoIyATRagVn0ynkZA1oLtio8hjW5DGPUPCsaMmyOcdtzpXyY72ARCASwD4gJNMjKKuBq3vZCG2TcWUpw2N2ZBOqZAFELUCrNQ2gZAGisVNipOR4I2BTW3o8Ljk",
"igUserId": "17841467507578923"
},
"manesh@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQMcvltO48gREI4t0wEKeej2ehL1Kel3ZAU8nwfAUZBakiZCzIILyMm9fHX4aejCVKHtV17s2VJqLawgjhw6W1zeVtWnOZCHSiXtWio7oZCxzf6iH8m09ogAF4PsjnM1cvOBGazpf3525XhEYELnoJekDVeRYpDvOZCC2yn8OecbxgzhFM4FH4R",
"longUserTokenExpiresAt": null
},
"vijay@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQHHioWnhkoLTcrUCsohfVnZC6jUrckykslwpoZAnOXRAA5TCPCNPR3aU7TNap4VRDQwGhriUx1CIY0aCEiExkyPxCHWRS6D2dbJyz6ltFnhkPlyPaW5v7BKPZACiVVg8A7tP3sAmaUZBpetRg6ACESD8FyWmwTVQHPfpYs2uasfPme1Y3eCj",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQM3K7uiQVWdktgIFO4hjoCJSdmExSb0eQnF0eOKgxzeIJkZCGIAGb3JjeuXu3oGHTb0tSRJT3K3JZAGAbpcliCJccR56oi6epz31ZCOBYDEZAq3ClqcSfobxNC0Fwond0qPZAQZABfglE9z5iHZAov22WvNLaOO07TG9hWG3WADHexEQtutraxYfR4b",
"igUserId": "17841474871998063"
},
"partner@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQM48InpqtHcgXpnfnssHYTFpZBhR8ZAgbt1BVSOPmzpN2LBjKmoskfkGZBMUkDPNDc0ctVwMWrNUoeCJuRDReeoWzakYzvRhPJuBeUxoaZCmDcupPZCwpFsXkeh9hFtaYFz2hbS3ZCVtFc84fPuwTTYH6AL7WV3grQyOxGUTZAXh6b597D6",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQDGwkCZAZAsq3hCxnYUhZClRYl8DqKf0qi44iWtUW6FaD0wZBDz36esp7fpQMVKu2CooK0miuD0TK6cmQzN4A9V4DOe2dmwBYL886YE5SYXMrm7ZBlt4KNXmFZBGSoHB22xz9cV2jPuy7bNCmHjKxM5YS6NlSLo1pq7ZBWDUgZA0Sqz2e94G0R634xsaWZBNc",
"igUserId": "17841474871998063"
},
"trailuser@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQB4IKZCSADGUxNM3bZAFbV3Vzv94OCi3rKV2ERaIzZB3vm9fY7iMdcbPuhSpTZBfX13WZA2llzowkBIK7mCP9UXCHc9lMglBa5LfANgqefAMcEX0juRMs9EVSiqtY0qIZB4XsO0SXzekVlqM79hZCO5YlX5OL3BSGkJZA70G9wXORxm4vtcYsxXy",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQLc4vQhrTq89cnZCUnnrCWaDqKMS3wN98yMqGzlWseLrV7lCWyDvJ9w1YA5774kbhPzawViEgy9rrjZCjKGsrV4dLcxYOvcfozcDPGIBHnvluHAWcvUeAJ8HAH108sljDqDGiIRAwgtGyvGlZBG3YpAHwLbglcDUxpwtZAUMeor0xxF74IM1dRKY",
"igUserId": "17841474871998063"
},
"test@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQasZCppDmYQCrQEcemXWZC2yNKK3TFVQMnxGIqzZCG2RQSoBlpkNOHTB9HlnZCdHSoqUhkQ0ZA2HDHe3smxZBQF1wMZBm8eqaoTEraTJVPHU1gEqqa6OcdXWO6jlUEMSNhXfZCwphsGuMdYBtar5ouXgy9tVzed6NRwbdBCEVlPxMSJmseZBbgwtG",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQRwtXTkzGqcg0RQXJzM1WH3e0DlUNnu3EcZBMxg85vWkIRiNHDxWmMvCDbMSK85c88M6ANjoBQuSH25tUfZCmFkbz60CK1BfZCxwf1MTE6WZB1AOqmCOLibfrZAlngBHLSx0JGXZBMvy0un5IIPhsDWucqcGY8PudBZAlJ9KpZAJh9ZAAR5fzJpFzVGyA",
"igUserId": "17841474871998063"
},
"alaguraj0361@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQUzWCVGt435Gh3x7Pc1WORbvgZAVzB3f22UPZCbflbOyTZCXZCyHrjWuCvP9GndHmmUJOMHYtfA3ygySlnytDKpQ6fEpCiDDoSZCbK3mqI7IB0d5C74G71SZBnTaOXVEdl8k8ZBI7TqP152Qtqgia2r7hLaCM4ZBOuecBWLkWpPvL8S3KsavLy8hQYNsicapCgIukfGv",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQYq4IILSB8ZAtErj5eBLV79hHkOasDBbBF5QPX694k6pbLcAyXVworBz56qMBvH8EierFBqGy914Ft9lffEwTCsKQz3T1DYMQubrOeQojjAubyrt2nqFc20bzILy3drL1I2Dut5sQZCDE5ShsICo59Ghf6ZAPMlaby7ULW3G5KuEa8BsneDOahNMNIe5frts6zTZBFjidsJ0",
"igUserId": "17841474871998063"
},
"admin@gmail.com": {
"longUserToken": "EAAH1s4xuDg8BQa4MSdHEN7YhNz28JDFCM49Y6bAQZAWZAA5AgnpPW8ZBHL6qGE6LAjhNnpy8rEWwLj7qAtxFQehMhNoUxYRSIHYS6Y6PSyAYSHZBJDRuSOxz2h6hoy9hZCSEg1mq9ja2YAkrv3ZC05ZC5XBs5FxVQtM5nH64bBYAmzCdzLovmNlAXo8MQwRjh7QfBwCfoGlFP9k5ZCrA",
"longUserTokenExpiresAt": null,
"pageId": "631970130002549",
"pageToken": "EAAH1s4xuDg8BQZAhJxpI73iuNCjyGjO6ZBf05pZA8jiF2lsBesFmZCMwzd6YFaaZBLZBM1QipNIa4KwZBtM4KkRuezTtQI54jLMuwF3D1bGX7N0NGZAEeZBksp8HixJfuMbQboBKpiHWIuktsHuy5w83ZCra50eASaY5t81EU3qvvSq9S50pO8gGv3BdnsPefU0Hc0SkcF9Bm6uxlZAvnYg6nHY",
"igUserId": "17841474871998063"
}
}

1925
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

34
package.json Normal file
View File

@ -0,0 +1,34 @@
{
"name": "VG Products_app_backend",
"version": "1.0.0",
"description": "",
"main": "server.js",
"type": "commonjs",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"axios": "^1.12.2",
"bcrypt": "^6.0.0",
"body-parser": "^2.2.0",
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.2.1",
"jsonwebtoken": "^9.0.3",
"mongoose": "^8.19.3",
"node-fetch": "^3.3.2",
"nodemailer": "^7.0.10",
"passport": "^0.7.0",
"passport-google-oauth20": "^2.0.0",
"passport-jwt": "^4.0.1",
"stripe": "^20.0.0"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}

52
server.js Normal file
View File

@ -0,0 +1,52 @@
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const path = require("path");
const { connectDB } = require("./config/db");
const passport = require("./config/passport");
const authRoutes = require("./src/routes/auth.routes");
const userRoutes = require("./src/routes/user.routes");
const app = express();
const PORT = process.env.PORT || 3050;
// Request Logger
app.use((req, res, next) => {
console.log(`[REQUEST] ${req.method} ${req.url}`);
next();
});
// ------------------ Connect DB ------------------
connectDB()
.then(() => console.log("✅ Database connected successfully"))
.catch((err) => console.error("❌ Database connection failed:", err));
// ------------------ CORS ------------------
app.use(
cors({
origin: ["http://localhost:3500", "https://app.VG Products.co"],
credentials: true,
})
);
// ------------------ Body Parser (After Webhook) ------------------
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// ------------------ Static Files ------------------
app.use(express.static(path.join(__dirname, "public")));
//passport
app.use(passport.initialize());
// ------------------ Routes ------------------
app.use("/api/auth", authRoutes);
app.use("/api/users", userRoutes);
app.get("/", (_req, res) => {
res.send("VG Products backend is running...");
});
// ------------------ Start Server ------------------
app.listen(PORT, () => console.log(`🚀 Server running on port ${PORT}`));

View File

@ -0,0 +1,293 @@
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const User = require("../models/user.model.js");
const { sendResetPasswordMail, sendSignupMail } = require("../utils/mailer.js");
const crypto = require("crypto");
const passport = require("passport");
// ======================= SIGNUP =======================
async function signup(req, res) {
try {
const { name, email, mobileNumber, password } = req.body;
// Validate fields
if (!name || !email || !mobileNumber || !password) {
return res.status(400).json({ error: "All fields are required" });
}
// Check if user exists
const exists = await User.findOne({ email });
if (exists) {
return res.status(400).json({ error: "User already exists" });
}
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create user
const user = await User.create({
name,
email,
mobileNumber,
passwordHash,
});
// Send confirmation email (non-blocking)
sendSignupMail(email, name)
.then(() => console.log("Signup email sent to", email))
.catch((err) => console.error("Email send failed:", err));
res.status(201).json({
message: "Signup success, email sent",
id: user._id,
});
} catch (err) {
console.error(err);
res.status(500).json({ error: "Signup failed" });
}
}
// ======================= LOGIN =======================
async function login(req, res) {
try {
console.log("LOGIN REQ BODY =>", req.body);
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: "Email & password required" });
}
const user = await User.findOne({ email });
if (!user) return res.status(401).json({ error: "Invalid credentials" });
const match = await bcrypt.compare(password, user.passwordHash);
if (!match) return res.status(401).json({ error: "Invalid credentials" });
const token = jwt.sign(
{ id: user._id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: "1h" }
);
return res.json({
message: "Login success",
token,
user: {
id: user._id,
name: user.name,
email: user.email,
mobileNumber: user.mobileNumber,
role: user.role,
}
});
} catch (err) {
console.error("LOGIN ERROR:", err);
return res.status(500).json({ error: "Login failed" });
}
}
// ======================= CHANGE PASSWORD =======================
async function changePassword(req, res) {
try {
const { currentPassword, newPassword } = req.body;
if (!currentPassword || !newPassword) {
return res.status(400).json({ error: "Current password and new password are required" });
}
const user = await User.findById(req.user.id);
if (!user) return res.status(404).json({ error: "User not found" });
const isMatch = await bcrypt.compare(currentPassword, user.passwordHash);
if (!isMatch)
return res.status(401).json({ error: "Current password is incorrect" });
user.passwordHash = await bcrypt.hash(newPassword, 10);
await user.save();
res.json({ message: "Password updated successfully" });
} catch (err) {
console.error("changePassword error:", err);
res.status(500).json({ error: "Failed to change password" });
}
}
// ======================= FORGOT PASSWORD =======================
async function forgotPassword(req, res) {
try {
const { email } = req.body;
if (!email) return res.status(400).json({ error: "Email is required" });
const user = await User.findOne({ email });
if (!user)
return res.json({
message: "If the email is registered, a reset link has been sent.",
verificationCode: null,
});
// 4-digit numeric code
const verificationCode = Math.floor(1000 + Math.random() * 9000).toString();
user.resetPasswordToken = verificationCode;
user.resetPasswordExpires = Date.now() + 60 * 60 * 1000; // 1 hour
await user.save();
// Send code via email
await sendResetPasswordMail(email, verificationCode);
res.json({
message: "If the email is registered, a reset link has been sent.",
verificationCode,
});
} catch (err) {
console.error("forgotPassword error:", err);
res.status(500).json({ error: "Failed to send reset link" });
}
}
// ======================= RESET PASSWORD =======================
async function resetPassword(req, res) {
try {
const { token, newPassword } = req.body;
if (!token || !newPassword)
return res.status(400).json({ error: "Token and new password are required" });
const user = await User.findOne({
resetPasswordToken: token,
resetPasswordExpires: { $gt: Date.now() },
});
if (!user) return res.status(400).json({ error: "Invalid or expired token" });
user.passwordHash = await bcrypt.hash(newPassword, 10);
user.resetPasswordToken = undefined;
user.resetPasswordExpires = undefined;
await user.save();
res.json({ message: "Password has been reset successfully" });
} catch (err) {
console.error("resetPassword error:", err);
res.status(500).json({ error: "Failed to reset password" });
}
}
async function createUser(req, res) {
try {
const { name, email, mobileNumber, password, role } = req.body;
if (!name || !email || !password)
return res.status(400).json({ error: "Name, email & password required" });
const exists = await User.findOne({ email });
if (exists) return res.status(400).json({ error: "Email already exists" });
const passwordHash = await bcrypt.hash(password, 10);
const user = await User.create({
name,
email,
mobileNumber,
passwordHash,
role
});
res.json({
message: "User created successfully",
user: {
id: user._id,
name: user.name,
email: user.email,
mobileNumber: user.mobileNumber,
role: user.role,
},
});
} catch (err) {
console.error("Create user error:", err);
res.status(500).json({ error: "Failed to create user" });
}
}
async function getUsers(req, res) {
try {
const users = await User.find()
.select("name email mobileNumber role createdAt updatedAt");
res.json(users);
} catch (err) {
console.error("Get users error:", err);
res.status(500).json({ error: "Failed to fetch users" });
}
}
async function getUserById(req, res) {
try {
const user = await User.findById(req.params.id)
.select("name email mobileNumber role createdAt updatedAt");
if (!user) {
return res.status(404).json({ error: "User not found" });
}
res.json(user);
} catch (err) {
console.error("Get user error:", err);
res.status(500).json({ error: "Failed to fetch user" });
}
}
async function updateUser(req, res) {
try {
const { name, email, mobileNumber, role } = req.body;
const updated = await User.findByIdAndUpdate(
req.params.id,
{ name, email, mobileNumber, role },
{ new: true }
).select("name email mobileNumber role");
if (!updated) return res.status(404).json({ error: "User not found" });
res.json({ message: "User updated", user: updated });
} catch (err) {
console.error("Update user error:", err);
res.status(500).json({ error: "Failed to update user" });
}
}
async function deleteUser(req, res) {
try {
const deleted = await User.findByIdAndDelete(req.params.id);
if (!deleted) return res.status(404).json({ error: "User not found" });
res.json({ message: "User deleted successfully" });
} catch (err) {
console.error("Delete user error:", err);
res.status(500).json({ error: "Failed to delete user" });
}
}
module.exports = {
signup,
login,
changePassword,
forgotPassword,
resetPassword,
// CRUD
createUser,
getUsers,
getUserById,
updateUser,
deleteUser,
};

65
src/lib/http.js Normal file
View File

@ -0,0 +1,65 @@
// const axios = require('axios');
// const http = axios.create({
// timeout: 15000,
// maxRedirects: 2,
// validateStatus: (s) => s >= 200 && s < 500,
// });
// http.interceptors.response.use(async (res) => {
// if (res.status === 429 || res.status >= 500) {
// const retryAfter = Number(res.headers['retry-after'] || 1);
// await new Promise(r => setTimeout(r, retryAfter * 1000));
// }
// return res;
// });
// module.exports = { http };
const axios = require('axios');
const http = axios.create({
timeout: 15000,
maxRedirects: 2,
validateStatus: (s) => s >= 200 && s < 500,
});
http.interceptors.request.use((config) => {
console.log(`📤 ${config.method.toUpperCase()} ${config.url}`);
return config;
});
http.interceptors.response.use(async (res) => {
console.log(`📥 ${res.status} ${res.config.url}`);
// Log errors for debugging
if (res.status >= 400) {
console.error('❌ HTTP Error Response:', {
status: res.status,
url: res.config.url,
data: res.data
});
}
// Retry logic for rate limits and server errors
if (res.status === 429 || res.status >= 500) {
const retryAfter = Number(res.headers['retry-after'] || 1);
console.log(`⏳ Retrying after ${retryAfter}s...`);
await new Promise(r => setTimeout(r, retryAfter * 1000));
}
return res;
}, (error) => {
// Handle network errors (DNS, timeout, connection refused)
if (error.code === 'ENOTFOUND') {
console.error('🌐 DNS Error: Cannot resolve hostname', error.config?.url);
} else if (error.code === 'ETIMEDOUT') {
console.error('⏱️ Timeout Error:', error.config?.url);
} else if (error.code === 'ECONNREFUSED') {
console.error('🚫 Connection Refused:', error.config?.url);
} else {
console.error('💥 Request Error:', error.message);
}
throw error;
});
module.exports = { http };

View File

@ -0,0 +1,8 @@
function adminOnly(req, res, next) {
if (req.user.role !== "admin") {
return res.status(403).json({ error: "Admin access required" });
}
next();
}
module.exports = { adminOnly };

View File

@ -0,0 +1,19 @@
const jwt = require("jsonwebtoken");
function authMiddleware(req, res, next) {
const header = req.headers.authorization;
if (!header || !header.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing token" });
}
const token = header.split(" ")[1];
try {
req.user = jwt.verify(token, process.env.JWT_SECRET);
next();
} catch (err) {
return res.status(401).json({ error: "Invalid or expired token" });
}
}
module.exports = { authMiddleware };

View File

@ -0,0 +1,6 @@
const errorHandler = (err, req, res, next) => {
console.error(err);
res.status(500).json({ message: err.message || "Internal Server Error" });
};
module.exports = { errorHandler };

27
src/models/user.model.js Normal file
View File

@ -0,0 +1,27 @@
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
name: { type: String },
email: { type: String, required: true, unique: true, lowercase: true },
mobileNumber: { type: String },
passwordHash: { type: String, required: true },
// ⭐ ADD ROLE
role: {
type: String,
enum: ["customer", "admin", "partner"],
default: "customer",
},
resetPasswordToken: { type: String },
resetPasswordExpires: { type: Date },
// ⭐ FREE TRIAL
trialEndsAt: { type: Date },
isTrialActive: { type: Boolean, default: false },
stripeCustomerId: { type: String },
},
{ timestamps: true }
);
module.exports = mongoose.model("User", userSchema);

31
src/routes/auth.routes.js Normal file
View File

@ -0,0 +1,31 @@
const express = require("express");
const passport = require("passport");
const {
signup,
login,
changePassword,
forgotPassword,
resetPassword,
} = require("../controllers/userauth.controller.js");
const { authMiddleware } = require("../middlewares/auth.middleware.js");
const router = express.Router();
router.post("/signup", signup);
router.post("/login", login);
router.post("/change-password", authMiddleware, changePassword);
router.post("/forgot-password", forgotPassword);
router.post("/reset-password", resetPassword);
// router.get("/protected", protectedRoute);
// example protected route
router.get("/profile", authMiddleware, (req, res) => {
res.json({ user: req.user });
});
module.exports = router;

19
src/routes/user.routes.js Normal file
View File

@ -0,0 +1,19 @@
const express = require("express");
const router = express.Router();
const controller = require("../controllers/userauth.controller.js");
const { authMiddleware } = require("../middlewares/auth.middleware.js");
const { adminOnly } = require("../middlewares/admin.middleware.js");
// 🔐 ADMIN ONLY USER MANAGEMENT
// router.post("/create", authMiddleware, adminOnly, controller.createUser);
// router.get("/", controller.getUsers);
// router.get("/:id", controller.getUserById);
// router.put("/:id", authMiddleware, adminOnly, controller.updateUser);
// router.delete("/:id", authMiddleware, adminOnly, controller.deleteUser);
router.post("/create", controller.createUser);
router.get("/", controller.getUsers);
router.get("/:id", controller.getUserById);
router.put("/:id", controller.updateUser);
router.delete("/:id", controller.deleteUser);
module.exports = router;

84
src/utils/mailer.js Normal file
View File

@ -0,0 +1,84 @@
const nodemailer = require("nodemailer");
//
// Create reusable transporter object
//
const mailer = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: 587, // STARTTLS
secure: false, // must be false for port 587
auth: {
user: process.env.SMTP_USER,
pass: process.env.SMTP_PASS,
},
name: "mail.VG Products.co",
tls: {
rejectUnauthorized: false,
},
logger: true,
debug: true,
});
//
// Send welcome / signup email
//
async function sendSignupMail(toEmail, name= "user") {
try {
await mailer.sendMail({
from: `"Social Buddy" <${process.env.SMTP_USER}>`,
to: toEmail,
subject: "Welcome to Social Buddy",
html: `
<div style="font-family:Arial;max-width:600px;margin:auto;padding:20px;border:1px solid #eee;">
<h2 style="color:#007bff;">Welcome to Social Buddy 🎉</h2>
<p>Hello <b>${name}</b>,</p>
<p>Your signup was successful! You can now log in and start using the app.</p>
<div style="margin-top:20px;">
<a href="https://app.VG Products.co/login"
style="background:#007bff;color:white;padding:10px 20px;text-decoration:none;border-radius:5px;">
Login Now
</a>
</div>
<p style="margin-top:20px;color:#666;font-size:12px;">
If you didnt create this account, please ignore this email.
</p>
</div>
`,
});
console.log(`✅ Signup email sent to ${toEmail}`);
} catch (err) {
console.error("❌ Error sending signup email:", err);
}
}
//
// Send reset-password email with code or link
//
async function sendResetPasswordMail(email, token) {
try {
const resetURL = `${process.env.FRONTEND_URL}/reset-password?email=${email}&token=${token}`;
await mailer.sendMail({
from: `"Social Buddy" <${process.env.SMTP_USER}>`,
to: email,
subject: "Reset your password",
html: `
<p>You requested a password reset.</p>
<p>Click here to reset: <a href="${resetURL}">${resetURL}</a></p>
<p>This link is valid for 1 hour.</p>
`,
});
console.log(`✅ Reset password email sent to ${email}`);
} catch (err) {
console.error("❌ Error sending reset password email:", err);
}
}
module.exports = {
mailer,
sendSignupMail,
sendResetPasswordMail,
};

View File

@ -0,0 +1,25 @@
// utils/templates/welcomeTemplate.js
export const welcomeTemplate = (name) => `
<div style="font-family:Arial,sans-serif;line-height:1.6;background:#f9f9f9;padding:20px;">
<div style="max-width:600px;margin:auto;background:#fff;border-radius:8px;overflow:hidden;box-shadow:0 2px 6px rgba(0,0,0,0.1);">
<div style="background:#00d1ff;color:#fff;padding:15px 25px;text-align:center;">
<h2>Welcome to Social Buddy 🎉</h2>
</div>
<div style="padding:25px;">
<h3>Hello ${name},</h3>
<p>Thank you for registering with <strong>Our App</strong>!</p>
<p>Your account has been successfully created. We're thrilled to have you on board.</p>
<div style="margin:30px 0;text-align:center;">
<a href="https://app.VG Products.co/login"
style="background:#00d1ff;color:#fff;padding:10px 20px;text-decoration:none;border-radius:5px;">
Go to Dashboard
</a>
</div>
<p style="font-size:13px;color:#777;">If you didnt create this account, please ignore this email.</p>
<p style="font-size:13px;color:#777;"> The Team</p>
</div>
</div>
</div>
`;