Skip to content
Open

new #20

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
1a0a97b
new
Sep 1, 2025
24649be
"committed changes"
Sep 1, 2025
d6b498e
ok
Sep 1, 2025
b79321c
SEP 3, 2025
Sep 2, 2025
b41828c
sep62025
Sep 6, 2025
98e1bd9
sep16
Sep 15, 2025
e50ab60
sep17
Sep 16, 2025
78502fe
sep19
Sep 18, 2025
87cf819
sep 21 2025
Sep 20, 2025
832bc0e
okay
Sep 22, 2025
825e970
mongoDB connected
Sep 22, 2025
c41f6a6
mongo done
Sep 23, 2025
2c059c7
Updates
Shahzad-Ali-official Sep 24, 2025
dd252e4
done authenticaton
Sep 24, 2025
792a430
Merge branch 'starter' of https://github.com/Shahzad-Ali-official/ful…
Sep 24, 2025
c12a69e
Update .env
Shahzad-Ali-official Sep 24, 2025
079764e
Delete .gitignore
Shahzad-Ali-official Sep 25, 2025
9beb73b
Update .env
Shahzad-Ali-official Sep 25, 2025
60fa2a8
api defined
Sep 27, 2025
e9d2456
Merge branch 'starter' of https://github.com/Shahzad-Ali-official/ful…
Sep 27, 2025
4472b1a
upload sections done
Oct 1, 2025
8381eb2
backend .git/COMMIT_EDITMSG
Oct 1, 2025
fdb29af
post rendering
Oct 6, 2025
5cca902
comment and actions done
Oct 6, 2025
b18b40b
okay
Oct 7, 2025
cecb6d2
DoneProj
Oct 9, 2025
69f11b4
done
Oct 9, 2025
6b8822a
ok
Oct 22, 2025
5a8ffa4
api push
Oct 22, 2025
6030428
ok2
Oct 22, 2025
c2cfdfd
ok3
Oct 22, 2025
3ef9d32
fix: Correct Netlify functions path and ignore .env file
Oct 22, 2025
f52d6bf
ok4
Oct 22, 2025
bbaa1da
ok5
Oct 22, 2025
bdd38b3
ok6
Oct 22, 2025
315beca
Remove sensitive data from backend/.env
Shahzad-Ali-official Oct 22, 2025
b452def
Delete backend/.env
Shahzad-Ali-official Oct 22, 2025
5bc31e9
env hide
Oct 22, 2025
2425ac4
fix: Update Netlify build config and untrack .env
Oct 22, 2025
3d6ee29
ok7
Oct 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
The diff you're trying to view is too large. We only load the first 3000 changed files.
25 changes: 3 additions & 22 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,24 +1,5 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
# Environment variables
.env

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
node_modules
2 changes: 2 additions & 0 deletions backend/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
.env
node_modules
63 changes: 63 additions & 0 deletions backend/controllers/comment.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@

import Comment from "../models/comment.model.js";
import User from "../models/user.model.js";

export const getPostComments = async (req, res) => {
const comments = await Comment.find({ postId: req.params.postId })
.populate("user", "username img")
.sort({ createdAt: -1 });

res.json(comments);
};

export const addComment = async (req, res) => {
const clerkUserId = req.auth().userId;
const postId = req.params.postId;

if (!clerkUserId) {
return res.status(401).json("Not authenticated!");
}

const user = await User.findOne({ clerkUserId });

const newComment = new Comment({
...req.body,
user: user._id, // The user who made the comment
postId: postId, // The post the comment belongs to
});

const savedComment = await newComment.save();
setTimeout(() => {}, 1000);
// Populate the user field before sending the response
const populatedComment = await Comment.findById(savedComment._id).populate("user", "username img");
res.status(201).json(populatedComment);
};

export const deleteComment = async (req, res) => {
const clerkUserId = req.auth().userId;
const id = req.params.id;

if (!clerkUserId) {
return res.status(401).json("Not authenticated!");
}

const role = req.auth().sessionClaims?.metadata?.role || "user";

if (role === "admin") {
await Comment.findByIdAndDelete(req.params.id);
return res.status(200).json("Comment has been deleted");
}

const user = await User.findOne({ clerkUserId });

const deletedComment = await Comment.findOneAndDelete({
_id: id,
user: user._id,
});

if (!deletedComment) {
return res.status(403).json("You can delete only your comment!");
}

res.status(200).json("Comment deleted");
};
111 changes: 111 additions & 0 deletions backend/controllers/post.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import ImageKit from "imagekit";
import User from "../models/user.model.js";
import Post from "../models/post.model.js"

import dotenv from "dotenv";
dotenv.config();

export const getPosts = async (req, res) => {
const page = parseInt(req.query.page) || 1;
const limit = parseInt(req.query.limit) || 5;
const skip = (page - 1) * limit;
//const sort = { createdAt: -1 };

const posts = await Post
.find()
.populate("user","username")
.limit(limit)
.skip(skip);
const totalPosts = await Post.countDocuments();
const hasMore = page * limit < totalPosts;


res.status(200).json({ posts, hasMore });

};
export const getPost = async (req, res) => {
const post = await Post.findOne({slug:req.params.slug})
.populate("user","username img");
res.status(200).json(post);

};
export const createPost = async (req, res) => {
try {
const { title, desc, category, content, img } = req.body;
const clerkUserId = req.auth().userId;
if (!clerkUserId) {
return res.status(401).json({ message: "Unauthorized" });
}
if (!title || !category || !content) {
return res.status(400).json({ message: "Title, category, and content are required." });
}
const user = await User.findOne({ clerkUserId });
if (!user) {
return res.status(404).json({ message: "User not found" });
}
let slug = title.replace(/ /g, "-").toLowerCase();
let existingPost = await Post.findOne({ slug });
let counter = 2;
while (existingPost) {
slug = `${slug}-${counter}`;
existingPost = await Post.findOne({ slug });
counter++;
}
const newPost = new Post({
user: user._id,
slug,
title,
desc,
category,
content,
img,
});
const post = await newPost.save();
res.status(201).json(post);
} catch (error) {
console.error("Error creating post:", error);
// Handle Mongoose validation errors
if (error.name === 'ValidationError') {
const errors = Object.values(error.errors).map(err => err.message);
return res.status(400).json({
message: "Validation failed",
errors: errors
});
}

res.status(500).json({ message: "Failed to create post", error: error.message });
}
};
export const deletePost = async (req, res) => {
const clerkUserId = req.auth().userId;
if(!clerkUserId){
return res.status(401).json({message: "Unauthorized"});
}
const user = await User.findOne({clerkUserId});
const post = await Post.findOneAndDelete({_id: req.params.id,user: user._id,});
res.status(200).json("post has been deleted");
};


//console.log("ImageKit ENV:", {
// publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
// privateKey: process.env.IMAGEKIT_PRIVATE_KEY ? "loaded" : "missing",
// urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
//});
//
const imagekit = new ImageKit({

publicKey: process.env.IMAGEKIT_PUBLIC_KEY,
privateKey: process.env.IMAGEKIT_PRIVATE_KEY,
urlEndpoint: process.env.IMAGEKIT_URL_ENDPOINT,
});

export const uploadAuth = async(req, res) => {
try {
const result = imagekit.getAuthenticationParameters();
res.status(200).json(result);
} catch (error) {
console.error("ImageKit Auth Generation Error:", error);
res.status(500).json({ message: "Failed to generate authentication parameters." });
}
};
46 changes: 46 additions & 0 deletions backend/controllers/user.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import User from "../models/user.model.js";

export const getUserSavedPosts = async (req, res) => {
const clerkUserId = req.auth().userId;

if (!clerkUserId) {
return res.status(401).json("Not authenticated!");
}

const user = await User.findOne({ clerkUserId });

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

res.status(200).json(user.savedPosts);
};

export const savePost = async (req, res) => {
const clerkUserId = req.auth().userId;
const postId = req.body.postId;

if (!clerkUserId) {
return res.status(401).json("Not authenticated!");
}

const user = await User.findOne({ clerkUserId });

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

const isSaved = user.savedPosts.some((p) => p === postId);

if (!isSaved) {
await User.findByIdAndUpdate(user._id, {
$push: { savedPosts: postId },
});
} else {
await User.findByIdAndUpdate(user._id, {
$pull: { savedPosts: postId },
});
}

res.status(200).json(isSaved ? "Post unsaved" : "Post saved");
};
40 changes: 40 additions & 0 deletions backend/controllers/webhook.controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import User from "../models/user.model.js";
import { Webhook } from "svix";


export const clerkWebHook = async (req, res) => {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;
if(!WEBHOOK_SECRET){
throw new Error("CLERK_WEBHOOK_SECRET is not defined")
}
const payload = req.body;
const headers = req.headers;

const wh = new Webhook(WEBHOOK_SECRET);
let evt;
try {
evt = wh.verify(payload, headers);
} catch (err) {
console.error("Webhook verification failed:", err.message);
return res.status(400).json({message: "Webhook verification failed"});
}

if (evt.type === "user.created") {
try {
const newUser = new User({
clerkUserId: evt.data.id,
username: evt.data.username || evt.data.email_addresses[0].email_address,
email: evt.data.email_addresses[0].email_address,
img: evt.data.image_url,
});

await newUser.save();
return res.status(201).json({message: "User created successfully."});
} catch (err) {
console.error("Error creating user:", err);
return res.status(500).json({ message: "Internal server error while creating user." });
}
}
// Acknowledge other webhook events with a success response
return res.status(200).json({ message: "Webhook received, but no action taken." });
}
53 changes: 53 additions & 0 deletions backend/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import dotenv from "dotenv";
dotenv.config();

import express from "express";
import cors from "cors";
import { clerkMiddleware } from "@clerk/express";

import connectDB from "./lib/connectDB.js";
import userRouter from "./routes/user.route.js";
import postRouter from "./routes/post.route.js";
import commentsRouter from "./routes/comment.route.js";
import webhookRouter from "./routes/webhook.route.js";

const app = express();

// ✅ Configure CORS properly
const allowedOrigins = ["http://localhost:5173"]; // change if needed
const corsOptions = {
origin: (origin, callback) => {
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error("Not allowed by CORS"));
}
},
optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));

app.use(clerkMiddleware());
app.use(express.json());

// ✅ Routes
app.use("/webhooks", webhookRouter);
app.use("/api/users", userRouter);
app.use("/api/posts", postRouter);
app.use("/api/comments", commentsRouter);

// ✅ Error handler
//app.use((err, req, res, next) => {
// res.status(err.status || 500).json({
// message: err.message || "Something went wrong",
// status: err.status,
// stack: process.env.NODE_ENV === "development" ? err.stack : undefined,
// });
//});

const port = process.env.PORT || 3000;
app.listen(port, () => {
connectDB();

console.log(`✅ Server is running on port ${port}`);
});
13 changes: 13 additions & 0 deletions backend/lib/connectDB.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import mongoose from "mongoose";

const connectDB = async () => {
try{
await mongoose.connect(process.env.MONGO);
console.log("DB is successfully connected");
}
catch(err)
{
console.log(err)
}
}
export default connectDB;
31 changes: 31 additions & 0 deletions backend/models/comment.model.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { Schema } from "mongoose";
import mongoose from "mongoose";



const commentSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: "User",
required: true,

},
postId: {
type: Schema.Types.ObjectId,
ref: "Post",
required: true,

},
desc: {
type: String,
required: true,

},


},
{
timestamps: true
});
export default mongoose.model("Comment", commentSchema);

Loading