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
1 change: 1 addition & 0 deletions contributors/AkshaTGA/server/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"description": "",
"main": "index.js",
"scripts": {
"dev":"nodemon index.js",
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

Missing space after comma in the dev script. Should be "dev": "nodemon index.js" with a space after the colon for consistent JSON formatting.

Suggested change
"dev":"nodemon index.js",
"dev": "nodemon index.js",

Copilot uses AI. Check for mistakes.
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
Expand Down
8 changes: 5 additions & 3 deletions contributors/AkshaTGA/server/src/Models/SubscriptionSchema.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import mongoose from "mongoose";
const mongoose = require("mongoose");

const SubscriptionSchema = new mongoose.Schema(
{
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
// required: true,
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The userId field's required constraint has been commented out. This allows subscriptions to be created without a userId, which could lead to orphaned subscriptions that cannot be queried or managed. This appears to be a workaround for missing authentication rather than a proper solution.

Suggested change
// required: true,
required: true,

Copilot uses AI. Check for mistakes.
index: true,
},

Expand Down Expand Up @@ -105,4 +105,6 @@ SubscriptionSchema.index(
{ sparse: true }
);

export default mongoose.model("Subscription", SubscriptionSchema);
module.exports =
mongoose.models.Subscription ||
mongoose.model("Subscription", SubscriptionSchema);
10 changes: 10 additions & 0 deletions contributors/AkshaTGA/server/src/Routes/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const express = require('express');
const router = express.Router();
const { addSubscription, getSubscriptions } = require('../controllers/Api');


router.get('/subscriptions', getSubscriptions )
router.post('/subscriptions',addSubscription )
Comment on lines +5 to +7
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The routes are missing authentication middleware. Both controllers expect req.user to be populated (lines 80 and 94 in Api.js) but there's no middleware to authenticate users and attach user information to the request. Without authentication middleware, req.user will always be undefined, causing all requests to fail with a 401 Unauthorized error.

Suggested change
router.get('/subscriptions', getSubscriptions )
router.post('/subscriptions',addSubscription )
// Simple authentication middleware to ensure req.user is populated
function authenticate(req, res, next) {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
router.get('/subscriptions', authenticate, getSubscriptions )
router.post('/subscriptions', authenticate, addSubscription )

Copilot uses AI. Check for mistakes.


module.exports = router;
233 changes: 233 additions & 0 deletions contributors/AkshaTGA/server/src/controllers/Api.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,233 @@
const Subscription = require("../Models/SubscriptionSchema");

const toUtcDateOnly = (value) => {
const date = new Date(value);
if (Number.isNaN(date.getTime())) return null;
return new Date(
Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate())
);
};

const addUtcDays = (startUtc, days) => {
const date = new Date(startUtc);
date.setUTCDate(date.getUTCDate() + days);
return date;
};

const addUtcMonths = (startUtc, months) => {
const date = new Date(startUtc);
date.setUTCMonth(date.getUTCMonth() + months);
return date;
};

const addUtcYears = (startUtc, years) => {
const date = new Date(startUtc);
date.setUTCFullYear(date.getUTCFullYear() + years);
return date;
};

const computeNextRenewalDate = (startUtc, billingInterval) => {
switch (billingInterval) {
case "daily":
return addUtcDays(startUtc, 1);
case "weekly":
return addUtcDays(startUtc, 7);
case "monthly":
return addUtcMonths(startUtc, 1);
case "yearly":
return addUtcYears(startUtc, 1);
default:
return null;
}
};

const computeIntervalCount = (startUtc, endUtc, billingInterval) => {
const msPerDay = 24 * 60 * 60 * 1000;
const diffMs = endUtc.getTime() - startUtc.getTime();
if (diffMs <= 0) return null;

const diffDays = diffMs / msPerDay;
if (!Number.isInteger(diffDays)) return null;

switch (billingInterval) {
case "daily":
return diffDays + 1;
case "weekly":
return diffDays % 7 === 0 ? diffDays / 7 + 1 : null;
case "monthly": {
if (startUtc.getUTCDate() !== endUtc.getUTCDate()) return null;
const months =
(endUtc.getUTCFullYear() - startUtc.getUTCFullYear()) * 12 +
(endUtc.getUTCMonth() - startUtc.getUTCMonth());
return months > 0 ? months + 1 : null;
}
case "yearly": {
if (
startUtc.getUTCMonth() !== endUtc.getUTCMonth() ||
startUtc.getUTCDate() !== endUtc.getUTCDate()
) {
return null;
}
const years = endUtc.getUTCFullYear() - startUtc.getUTCFullYear();
return years > 0 ? years + 1 : null;
}
Comment on lines +44 to +73
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The validation logic has an off-by-one error in the billing interval count calculation. The functions computeIntervalCount returns count + 1 for daily/weekly/monthly/yearly intervals (lines 54, 56, 62, 72), which includes both the start and end dates. However, this means if a subscription starts on Jan 1 and ends on Jan 2 (daily), it would count as 2 days instead of 1. This could lead to incorrect billing calculations where users are charged for an extra period.

Copilot uses AI. Check for mistakes.
default:
return null;
}
};

const getSubscriptions = (req, res) => {
const user = req.user;
if (!user || !user._id) {
return res.status(401).json({ error: "Unauthorized" });
}
Subscription.find({ userId: user._id })
.then((subscriptions) => {
res.status(200).json({ subscriptions });
})
.catch((err) => {
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The error handler does not log the error details. When an error occurs during subscription fetch, the error is caught but not logged, making it difficult to debug issues in production. Consider logging the error before returning the response.

Suggested change
.catch((err) => {
.catch((err) => {
console.error("Error fetching subscriptions for user:", user._id, err);

Copilot uses AI. Check for mistakes.
res.status(500).json({ error: "Failed to fetch subscriptions" });
});
};
//in this function, I have made is so that the next renewal date and billing interval count are calculated based on the start and end date entered byt the user.
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

Spelling error in the comment: "byt" should be "by". The comment contains a typo where "byt the user" should read "by the user".

Suggested change
//in this function, I have made is so that the next renewal date and billing interval count are calculated based on the start and end date entered byt the user.
//in this function, I have made is so that the next renewal date and billing interval count are calculated based on the start and end date entered by the user.

Copilot uses AI. Check for mistakes.
const addSubscription = async (req, res) => {
const user = req.user;
const data = req.body || {};

if (!user || !user._id) {
return res.status(401).json({ error: "Unauthorized" });
}

const {
name,
status,
billingInterval,
startDate,
endDate,
billingIntervalCount,
} = data;

if (!name || typeof name !== "string" || !name.trim()) {
return res.status(400).json({ error: "name is required" });
}
if (!status) {
return res.status(400).json({ error: "status is required" });
}
if (!billingInterval) {
return res.status(400).json({ error: "billingInterval is required" });
}
Comment on lines +109 to +118
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The validation for status and billingInterval only checks for truthiness, not whether the values are valid according to the schema enums. For example, status must be one of "trial", "active", "paused", "cancelled", or "expired", but any truthy value would pass this check. Consider validating against the allowed enum values before attempting to create the subscription.

Suggested change
if (!name || typeof name !== "string" || !name.trim()) {
return res.status(400).json({ error: "name is required" });
}
if (!status) {
return res.status(400).json({ error: "status is required" });
}
if (!billingInterval) {
return res.status(400).json({ error: "billingInterval is required" });
}
// derive allowed enum values from the Subscription schema, if defined
const statusPath = Subscription.schema && Subscription.schema.path
? Subscription.schema.path("status")
: null;
const billingIntervalPath = Subscription.schema && Subscription.schema.path
? Subscription.schema.path("billingInterval")
: null;
const allowedStatuses =
statusPath && Array.isArray(statusPath.enumValues)
? statusPath.enumValues
: null;
const allowedBillingIntervals =
billingIntervalPath && Array.isArray(billingIntervalPath.enumValues)
? billingIntervalPath.enumValues
: null;
if (!name || typeof name !== "string" || !name.trim()) {
return res.status(400).json({ error: "name is required" });
}
if (!status) {
return res.status(400).json({ error: "status is required" });
}
if (
allowedStatuses &&
(typeof status !== "string" || !allowedStatuses.includes(status))
) {
return res.status(400).json({ error: "status is invalid" });
}
if (!billingInterval) {
return res.status(400).json({ error: "billingInterval is required" });
}
if (
allowedBillingIntervals &&
(typeof billingInterval !== "string" ||
!allowedBillingIntervals.includes(billingInterval))
) {
return res.status(400).json({ error: "billingInterval is invalid" });
}

Copilot uses AI. Check for mistakes.
if (!startDate) {
return res.status(400).json({ error: "startDate is required" });
}
if (billingInterval !== "custom" && !endDate) {
return res.status(400).json({ error: "endDate is required" });
}

const parsedStartDate = toUtcDateOnly(startDate);
if (!parsedStartDate) {
return res.status(400).json({ error: "startDate must be a valid date" });
Comment on lines +117 to +128
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

The error message is inconsistent with how other field names are formatted in error messages. Throughout the code, field names in error messages are lowercase (e.g., "name is required", "status is required"), maintaining consistency would improve user experience.

Suggested change
return res.status(400).json({ error: "billingInterval is required" });
}
if (!startDate) {
return res.status(400).json({ error: "startDate is required" });
}
if (billingInterval !== "custom" && !endDate) {
return res.status(400).json({ error: "endDate is required" });
}
const parsedStartDate = toUtcDateOnly(startDate);
if (!parsedStartDate) {
return res.status(400).json({ error: "startDate must be a valid date" });
return res.status(400).json({ error: "billing interval is required" });
}
if (!startDate) {
return res.status(400).json({ error: "start date is required" });
}
if (billingInterval !== "custom" && !endDate) {
return res.status(400).json({ error: "end date is required" });
}
const parsedStartDate = toUtcDateOnly(startDate);
if (!parsedStartDate) {
return res.status(400).json({ error: "start date must be a valid date" });

Copilot uses AI. Check for mistakes.
}

let parsedEndDate = endDate ? toUtcDateOnly(endDate) : null;
if (endDate && !parsedEndDate) {
return res.status(400).json({ error: "endDate must be a valid date" });
}

let computedBillingIntervalCount;
let computedNextRenewalDate;

if (billingInterval === "custom") {
const count = Number(billingIntervalCount);
if (!Number.isFinite(count) || !Number.isInteger(count) || count < 1) {
return res.status(400).json({
error:
"billingIntervalCount must be a positive integer for custom interval",
});
}

computedBillingIntervalCount = count;
computedNextRenewalDate = addUtcDays(parsedStartDate, count);

if (
parsedEndDate &&
parsedEndDate.getTime() !== computedNextRenewalDate.getTime()
) {
return res.status(400).json({
error:
"endDate must match startDate + billingIntervalCount days for custom interval",
});
}

parsedEndDate = computedNextRenewalDate;
} else {
const count = computeIntervalCount(
parsedStartDate,
parsedEndDate,
billingInterval
);
if (!count) {
return res.status(400).json({
error:
"Invalid date range for billingInterval (endDate must be after startDate and align with the interval).",
});
}
computedBillingIntervalCount = count;

computedNextRenewalDate = computeNextRenewalDate(
parsedStartDate,
billingInterval
);
if (!computedNextRenewalDate) {
return res.status(400).json({ error: "Unsupported billingInterval" });
}
if (computedNextRenewalDate.getTime() > parsedEndDate.getTime()) {
return res.status(400).json({
error: "endDate must be on/after the first renewal date",
});
}
}

try {
const created = await Subscription.create({
userId: user._id,
name: name.trim(),
category: data.category,
status: data.status,
amount: data.amount,
currency: data.currency,
billingInterval: data.billingInterval,
billingIntervalCount: computedBillingIntervalCount,
startDate: parsedStartDate,
nextRenewalDate: computedNextRenewalDate,
endDate: parsedEndDate,
autoRenew: data.autoRenew,
trialEndDate: data.trialEndDate,
source: data.source,
sourceReferenceId: data.sourceReferenceId,
confidenceScore: data.confidenceScore,
userNotes: data.userNotes,
isArchived: data.isArchived,
});

return res.status(201).json({ subscription: created });
} catch (err) {
const isBadRequest =
err && (err.name === "ValidationError" || err.name === "CastError");

if (isBadRequest) {
return res.status(400).json({
error: "Invalid subscription data",
details: err.message,
});
}

return res
.status(500)
.json({ error: "Failed to add subscription", details: err.message });
}
};

module.exports = {
addSubscription,
getSubscriptions,
};
10 changes: 3 additions & 7 deletions contributors/AkshaTGA/server/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,23 +3,20 @@ const path = require("path");
require("dotenv").config({ path: path.resolve(__dirname, "..", ".env") });
const express = require("express");
const mongoConnect = require("./utils/mongoConnect");
const router = require("./Routes/routes");

const app = express();
app.use(cors());
app.use(express.json());

app.get("/", (req, res) => {
res.send("Server is running...");
});
app.use("/api",router);
Copy link

Copilot AI Dec 30, 2025

Choose a reason for hiding this comment

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

Missing space after comma in the router middleware call. Should be '/api', router with a space after the comma for consistent formatting.

Suggested change
app.use("/api",router);
app.use("/api", router);

Copilot uses AI. Check for mistakes.


const MongoURI = process.env.MONGO_URI;
const PORT = process.env.PORT || 5000;

if (!MongoURI) {
throw new Error(
"MONGO_URI is not set."
);
throw new Error("MONGO_URI is not set.");
}

mongoConnect(MongoURI)
Expand All @@ -35,4 +32,3 @@ mongoConnect(MongoURI)
console.error("Failed to connect to MongoDB", err.message);
process.exit(1);
});