upload files

This commit is contained in:
sbinsalman
2025-11-25 11:11:42 -07:00
commit 36f9a5e69b
41 changed files with 1971 additions and 0 deletions

BIN
backend/.DS_Store vendored Normal file

Binary file not shown.

139
backend/.gitignore vendored Normal file
View File

@@ -0,0 +1,139 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-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
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.*
!.env.example
# 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
# Sveltekit cache directory
.svelte-kit/
# vitepress build output
**/.vitepress/dist
# vitepress cache directory
**/.vitepress/cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# Firebase cache directory
.firebase/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v3
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/sdks
!.yarn/versions
# Vite logs files
vite.config.js.timestamp-*
vite.config.ts.timestamp-*

View File

@@ -0,0 +1,78 @@
import Comment from "../models/comments.model.js";
import { StatusCodes } from "http-status-codes";
// POST /api/comments
export const createComment = async (req, res) =>
{
try
{
const { userStoryId, commentText, commentedBy } = req.body;
if (!userStoryId || !commentText)
{
return res.status(StatusCodes.BAD_REQUEST).json({ message: "userStoryId and text are required" });
}
const comment = new Comment({userStoryId,commentText,commentedBy});
const saved = await comment.save();
return res.status(StatusCodes.CREATED).json(saved);
}
catch (err)
{
console.error("Error creating comment", err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: "Error creating comment" });
}
};
// PUT /api/comments/:id
export const updateComment = async (req, res) =>
{
try
{
const { id } = req.params;
const { commentText } = req.body;
if (!commentText)
{
return res.status(StatusCodes.BAD_REQUEST).json({ message: "text is required" });
}
const updated = await Comment.findByIdAndUpdate(id,{ commentText },{ new: true });
if (!updated)
{
return res.status(StatusCodes.NOT_FOUND).json({ message: "Comment not found" });
}
return res.status(StatusCodes.OK).json(updated);
}
catch (err)
{
console.error("Error updating comment", err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: "Error updating comment" });
}
};
// DELETE /api/comments/:id
export const deleteComment = async (req, res) =>
{
try
{
const { id } = req.params;
const deleted = await Comment.findByIdAndDelete(id);
if (!deleted)
{
return res.status(StatusCodes.NOT_FOUND).json({ message: "Comment not found" });
}
return res.status(StatusCodes.OK).json({ message: "Comment deleted successfully" });
}
catch (err)
{
console.error("Error deleting comment", err);
return res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: "Error deleting comment" });
}
};

View File

@@ -0,0 +1,64 @@
import UserStory from "../models/userStories.model.js";
import { StatusCodes } from "http-status-codes";
import Comment from "../models/comments.model.js";
export async function createUserStories(req, res) {
try {
const userStory = new UserStory(req.body);
const savedStory = await userStory.save();
res.status(StatusCodes.CREATED).json(savedStory);
} catch (e) {
res.status(StatusCodes.NOT_FOUND).json({ error: e.message });
}
}
export async function getUserStory(req, res) {
try {
const userStory = await UserStory.findById(req.params.id).populate({
path: "comments",
options: { sort: { createdAt: -1 } },
});
if (!userStory) {
return res.status(StatusCodes.NOT_FOUND).json({ message: "User story not found" });
}
return res.status(StatusCodes.OK).json(userStory);
} catch (error) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ message: error.message });
}
}
export async function updateUserStory(req, res) {
try {
const updatedStory = await UserStory.findByIdAndUpdate(req.params.id, req.body, { new: true });
if (!updatedStory) {
return res.status(StatusCodes.NOT_FOUND).json({ message: "User story not found" });
}
res.status(StatusCodes.OK).json(updatedStory);
} catch (error) {
res.status(StatusCodes.BAD_REQUEST).json({ message: error.message });
}
}
export async function deleteUserStory(req, res) {
try {
const out = await UserStory.findByIdAndDelete(req.params.id);
if (!out) return res.status(StatusCodes.NOT_FOUND).json({ error: "User story not found" });
res.status(StatusCodes.OK).json({ message: "User story deleted successfully" });
} catch (e) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: e.message });
}
}
export async function listUserStories(req, res) {
try {
const stories = await UserStory.find();
res.status(StatusCodes.OK).json(stories);
} catch (e) {
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({ error: e.message });
}
}

View File

@@ -0,0 +1,25 @@
import mongoose, { model } from "mongoose";
const { Schema } = mongoose;
const CommentUserStorySchema = new Schema(
{
userStoryId: { type: Schema.Types.ObjectId, ref: "UserStory", required: true },
commentText: { type: String, required: true },
commentedBy: { type: String, required: true },
},
{ timestamps: true }
);
CommentUserStorySchema.post("save", async function (doc, next) {
try {
await mongoose.model("UserStory").findByIdAndUpdate(
doc.userStoryId,
{ $addToSet: { comments: doc._id } } // $addToSet avoids duplicates
);
next();
} catch (err) {
next(err);
}
});
export default model("Comment", CommentUserStorySchema);

View File

@@ -0,0 +1,20 @@
import mongoose, { model } from "mongoose";
const { Schema } = mongoose;
const UserStorySchema = new Schema(
{
title: { type: String, required: true },
description: { type: String, required: true },
status: { type: String, enum: ["Todo", "In-Review", "Sprint-Ready"], default: "Todo" },
businessValue: { type: Number, required: true, min: 1, max: 100 },
storyPoint: { type: Number, required: true, enum: [1, 2, 3, 5, 8, 13, 21, 34, 55] },
assignedTo: { type: String },
comments: [{ type: Schema.Types.ObjectId, ref: "Comment" }],
},
{ timestamps: true }
);
//Sorting by most recent
UserStorySchema.index({ createdAt: -1 });
export default model("UserStory", UserStorySchema);

24
backend/package.json Normal file
View File

@@ -0,0 +1,24 @@
{
"name": "backend",
"type": "module",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"dotenv": "^17.2.3",
"express": "^5.1.0",
"http-status-codes": "^2.3.0",
"mongoose": "^8.19.2"
},
"devDependencies": {
"nodemon": "^3.1.10"
}
}

View File

@@ -0,0 +1,19 @@
import { Router } from "express";
import {
createComment,
updateComment,
deleteComment,
} from "../controllers/comments.controller.js";
const router = Router();
// POST /api/comments
router.post("/", createComment);
// PUT /api/comments/:id
router.put("/:id", updateComment);
// DELETE /api/comments/:id
router.delete("/:id", deleteComment);
export default router;

View File

@@ -0,0 +1,18 @@
import { Router } from "express";
import {
createUserStories,
getUserStory,
updateUserStory,
deleteUserStory,
listUserStories,
} from "../controllers/userStories.controller.js";
const router = Router();
router.get("/", listUserStories);
router.post("/", createUserStories);
router.get("/:id", getUserStory);
router.put("/:id", updateUserStory);
router.delete("/:id", deleteUserStory);
export default router;

35
backend/server.js Normal file
View File

@@ -0,0 +1,35 @@
import express from "express";
import mongoose from "mongoose";
import dotenv from "dotenv";
import cors from "cors";
import userStoriesRoutes from "./routes/userStories.route.js";
import commentsRoutes from "./routes/comments.route.js";
dotenv.config();
const app = express();
app.use(cors());
app.use(express.json());
// MongoDB connection
mongoose.connect(process.env.MONGO_URI)
.then(() => console.log("MongoDB connected"))
.catch(err => console.log(err));
// Health check route
app.get("/health", (req, res) => {
res.status(200).type("text/plain").send("Server works");
console.log("Server works")
});
// User Stories routes
app.use("/api/v1/user-stories", userStoriesRoutes);
// User stories comments routes
app.use("/api/v1/comments", commentsRoutes);
// Start server
const PORT = process.env.PORT || 5050;
app.listen(PORT, () => console.log(`Server running on port ${PORT}`));