Skip to content

Commit

Permalink
Merge pull request #97 from NGR-NP/feat-set-expiration-time
Browse files Browse the repository at this point in the history
Refactor File Deletion Scheduler, Implement Rescheduling, and Enhance TopBadge Component
  • Loading branch information
maheshbasnet089 authored Mar 17, 2024
2 parents 7e01f60 + 96c1e9a commit 3c4e806
Show file tree
Hide file tree
Showing 6 changed files with 236 additions and 27 deletions.
4 changes: 2 additions & 2 deletions tapShareBackend/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,8 @@ app.get("/whoami", (req, res) => {
app.get("/:userId", async (req, res) => {
try {
const files = await File.find({ userId: req.params.userId });

if (!files) {
// console.log(files.length)
if (!files.length) {
return res.json({
status: 404,
message: "No files found or link has been expired ",
Expand Down
99 changes: 81 additions & 18 deletions tapShareBackend/controller/email/fileController.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,33 +6,91 @@ const path = require("path");
const schedule = require("node-schedule");
const { BASE_URL } = require("../../config/secrets");
// Function to schedule file deletion after 24 hours
const scheduleDeletion = (fileId) => {
const deletionJob = schedule.scheduleJob(
var userJobs = {};

const scheduleDeletion = (userId) => {
userJobs[userId] = schedule.scheduleJob(
new Date(Date.now() + 24 * 60 * 60 * 1000),
// new Date(Date.now() + 60 * 1000),
async () => {
console.log("schedule called");
try {
const file = await File.findByIdAndDelete(fileId);
if (file) {
const filePath = path.join(
"uploads",
file.path.replace(BASE_URL + "u/", "")
);
fs.unlink(filePath, (err) => {
if (err) {
console.log("Error deleting file:", err);
} else {
console.log("File deleted successfully:", filePath);
}
const file = await File.find({ userId });
if (file.length) {
file.forEach((f) => {
const filePath = path.join(
"uploads",
f.path.replace(BASE_URL + "u/", "")
);
fs.unlink(filePath, (err) => {
if (err) {
console.error("Error deleting file:", err);
} else {
console.log("File deleted successfully:", filePath);
}
});
});
await File.deleteMany({ userId });
delete userJobs[userId];
console.log(userJobs);
}
} catch (error) {
console.log("Error deleting file:", error);
console.error("Error deleting file:", error);
}
}
);
console.log("---", userJobs);
};

const timeOptions = [
{ value: "day", label: "Days", max: 15, in: 24 * 60 * 60 * 1000 },
{ value: "hr", label: "Hours", max: 24, in: 60 * 60 * 1000 },
{ value: "min", label: "Minutes", max: 60, in: 60 * 1000 },
{ value: "sec", label: "Seconds", max: 60, in: 1000 },
];
const validateInput = (type, time) => {
const selectedOption = timeOptions.find((option) => option.value === type);
const max = selectedOption.max;
console.log(selectedOption);
if (time < 1) {
return {
success: false,
msg: `Minimum expires ${selectedOption.label} is 1`,
};
} else if (time > max) {
return {
success: false,
msg: `Maximum expires ${selectedOption.label} is ${max}`,
};
} else {
return { success: true, msg: time * selectedOption.in };
}
};
exports.rescheduleDeletion = async (req, res) => {
const id = req.params.id;
const { type, time } = req.body;
console.log(req.body);
if (!type)
return res.status(400).json({ message: "type is required", status: 400 });

if (!time)
return res.status(400).json({ message: "time is required", status: 400 });

const isInputValid = validateInput(type, time);
if (!isInputValid.success)
return res.status(400).json({ message: isInputValid.msg, status: 400 });

const newDate = isInputValid.msg + Date.now();
if (userJobs[id]) {
const resudule = userJobs[id]?.reschedule(new Date(newDate));
res.send({ resudule, newDate });
} else {
res.json({
message: "there aren't any schedule to change",
status: 404,
});
}
};
exports.sendFiles = async (req, res) => {
const files = req.files;
try {
Expand Down Expand Up @@ -68,7 +126,12 @@ exports.sendFiles = async (req, res) => {
const savedFile = await newFile.save();
if (savedFile) {
filePaths.push(newFile.path);
scheduleDeletion(savedFile._id);
console.log("job", userJobs[req.body.userId]);
if (!userJobs[req.body.userId]) {
scheduleDeletion(req.body.userId);
} else {
console.log("already shedule");
}
}
}
// send email here test
Expand Down Expand Up @@ -106,14 +169,14 @@ exports.sendFiles = async (req, res) => {
emailOptions.to = email.value;
try {
const incomingData = await sendEmail(emailOptions);
console.log(incomingData)
console.log(incomingData);
// not working here
} catch (error) {
console.error(error);
}
}
}

phones.length > 0 &&
phones.forEach((phone) => {
emailOptions.to = phone.value;
Expand Down
4 changes: 2 additions & 2 deletions tapShareBackend/model/fileModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ const fileSchema = new Schema(
type: String,
required: true,
index: true,
},
},
path: {
type: String,
required: true,
Expand All @@ -30,7 +30,7 @@ const fileSchema = new Schema(
timestamps: true,
}
);
fileSchema.index({ createdAt: 1 }, { expireAfterSeconds: 24 * 60 * 60 });
fileSchema.index({ createdAt: 1 }, { expireAfterSeconds: 15 * 24 * 60 * 60 });

const File = mongoose.model("File", fileSchema);

Expand Down
4 changes: 1 addition & 3 deletions tapShareBackend/model/textModel.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const mongoose = require("mongoose");
const Schema = mongoose.Schema;


const codeSchema = new Schema(
{
text: {
Expand All @@ -28,7 +27,6 @@ const codeSchema = new Schema(
timestamps: true,
}
);
codeSchema.index({ createdAt: 1 }, { expireAfterSeconds: 24 * 60 * 60 });

codeSchema.index({ createdAt: 1 }, { expireAfterSeconds: 15 * 24 * 60 * 60 });
const Code = mongoose.model("Code", codeSchema);
module.exports = Code;
6 changes: 5 additions & 1 deletion tapShareBackend/route/fileRoute.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
const { sendFiles } = require("../controller/email/fileController");
const {
sendFiles,
rescheduleDeletion,
} = require("../controller/email/fileController");

const router = require("express").Router();
const { multer, storage } = require("../services/multerConfig");
const upload = multer({ storage: storage });

router.route("/sendFile").post(upload.array("files"), sendFiles);
router.post("/update-expires-time/:id", rescheduleDeletion);

module.exports = router;
146 changes: 145 additions & 1 deletion tapShareFrontend/src/components/misc/TopBadge.jsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,153 @@
import React, { useState, useEffect } from "react";
import { useParams } from "react-router-dom";
import {
CircularProgress,
FormControl,
FormHelperText,
MenuItem,
TextField,
} from "@mui/material";
import axios from "axios";
import { baseUrl } from "../../config";

const timeOptions = [
{ value: "day", label: "Days", max: 15 },
{ value: "hr", label: "Hours", max: 24 },
{ value: "min", label: "Minutes", max: 60 },
{ value: "sec", label: "Seconds", max: 60 },
];

export default function TopBadge() {
const { id } = useParams();
const [selectedIndex, setSelectedIndex] = useState(1);
const [value, setValue] = useState(24);
const [isChanged, setIsChanged] = useState(false);
const [loading, setLoading] = useState(false);
const [errorMsg, setErrorMsg] = useState("");

useEffect(() => {
validateInput();
}, [selectedIndex, value]);

useEffect(() => {
// get index and value form local storeage and setit on state
const index = Number(localStorage.getItem("exp-index"));
const val = localStorage.getItem("exp-value");
if (index) setSelectedIndex(index);
if (val) setValue(val);
}, []);

const handleMenuItemClick = (idx) => {
setSelectedIndex(idx);
setIsChanged(true);
};

const handleInputChange = (e) => {
const val = Number(e.target.value);
if (e.target.value.length > 2) {
setErrorMsg("Only 2 digits are allowed");
} else {
setValue(val);
setIsChanged(true);
}
};

const validateInput = () => {
const selectedOption = timeOptions[selectedIndex];
const max = selectedOption.max;

if (value < 1) {
setErrorMsg(`Minimum expires ${selectedOption.label} is 1`);
setLoading(false);
} else if (value > max) {
setErrorMsg(`Maximum expires ${selectedOption.label} is ${max}`);
setLoading(false);
} else {
setErrorMsg("");

if (isChanged) {
setLoading(true);
const handler = setTimeout(() => {
callAPi();
}, 550);
return () => {
clearTimeout(handler);
};
}
}
};

const callAPi = async () => {
const response = await axios.get("https://api64.ipify.org?format=json");
const ipAddress = response.data.ip;
const data = {
time: value,
type: timeOptions[selectedIndex].value,
ip: ipAddress,
};
const userId = localStorage.getItem("userId");
try {
const res = await axios.post(
`${baseUrl}api/v1/update-expires-time/${userId}`,
data
);
console.log(res, data);
} catch (error) {
setErrorMsg(error?.response?.data?.message);
} finally {
setLoading(false);
}

localStorage.setItem("exp-type", data.type);
localStorage.setItem("exp-index", selectedIndex);
localStorage.setItem("exp-value", data.time);
};

return (
<div className="flex justify-between bg-gray-50 py-4 rounded-md px-3 border-l-green-500 border-l-8">
<b className="select-none">Expires After : 24 hrs</b>
<div className="flex gap-4 items-start">
<b className="select-none pt-1">Expires After:</b>
<FormControl variant="standard">
<div className="flex gap-4 items-center ">
<TextField
sx={{ minWidth: "20px", width: "40px", textAlign: "center" }}
variant="standard"
value={value}
type="number"
name="expires"
inputMode="numeric"
onChange={handleInputChange}
error={!!errorMsg}
className={`${errorMsg && "animate-shake"}`}
/>

<TextField
id="standard-select-currency"
select
defaultValue={localStorage.getItem("exp-type") || "hr"}
variant="standard"
>
{timeOptions.map((option, idx) => (
<MenuItem
key={option.value}
selected={idx === selectedIndex}
onClick={() => handleMenuItemClick(idx)}
value={option.value}
>
{option.label}
</MenuItem>
))}
</TextField>
{loading && <CircularProgress size={20} />}
</div>
{errorMsg && (
<FormHelperText id="my-helper-text" error={!!errorMsg}>
{errorMsg}
</FormHelperText>
)}
</FormControl>
</div>

<div className="flex">
<b className="font-semibold select-none">Code</b> {" : "}{" "}
<h2 className="text-blue-500 font-medium select-text">{id}</h2>
Expand Down

0 comments on commit 3c4e806

Please sign in to comment.