In this article, we delve into the development process of a feature-rich calendar application akin to Google Calendar, augmented with robust notification services. Meet Suprsend-DoodleCalendar, a versatile tool designed to streamline event creation, sharing, and management, all while keeping users promptly informed which we would use as the demo applicattion. Leveraging a potent technology stack comprising React.js, Bootstrap, Node.js, and MongoDB, we'll guide you through the intricate web of frontend and backend intricacies. From user authentication to event manipulation and cross-user collaboration, every aspect of the application will be meticulously dissected.
Technology Stack
Frontend - React.js, Bootstrap Backend - Node.js Database - MongoDB Notification SDK - SuprSend Node SDK & APIs File structure:
Application Features: Authentication (Login and Register) 2 types of email notifications- event_created, event_shared, and event_ reminder User can create multiple events in the same calendar file just like Google Calendar Find deployed application: https://suprsend-doodlecal.netlify.app/ Github: SuprSend-NotificationAPI/Suprsend-DoodleCalender: Doodle Calender (github.com) Application UI Login Page
Register Page
HomePage aka Calendar with day, week and year view
The user can change the information by clicking on any date/ view, upon which a modal will open asking for necessary changes. User can add, edit, deleter or share the event. The sharing will work for users who are registered on the website similar to Google Calendar.
Frontend Codes: You can get the necessary front-end codes of the application from our Github. SuprSend-NotificationAPI/Suprsend-DoodleCalender: Doodle Calender
Modify the user interface design as per your choice. We'll look primarily into the backend and integrations for this tool.
Backend Codes
Copy Code
// Middleware/fetchuser.js
require("dotenv").config();
var jwt = require("jsonwebtoken");
const JWT_SECRET = process.env.JWT_SECRET;
const fetchuser = (req, res, next) => {
// Get the user from the JWT token and add ID to the request object
const token = req.header("auth-token");
if (!token) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
try {
const data = jwt.verify(token, JWT_SECRET);
req.user = data.user;
next();
} catch (error) {
res.status(401).send({ error: "Please authenticate using a valid token" });
}
};
module.exports = fetchuser;
// Models/user.js
const mongoose = require('mongoose');
const { Schema, model } = mongoose;
const UserSchema = new Schema({
email: { type: String },
name: String,
phone: { type: Number },
password: String
});
const UserModel = model('User', UserSchema);
module.exports = UserModel;
// Models/event.js
const mongoose = require("mongoose");
const { Schema } = mongoose;
const collaboratorSchema = new mongoose.Schema({
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'user',
required: true,
},
});
const EventSchema = new Schema({
author: {
type: mongoose.Schema.Types.ObjectId,
ref: "user",
},
id: {
type: String,
},
title: {
type: String,
},
start: {
type: Date,
},
end: {
type: Date,
},
allDay: {
type: Boolean
},
collaborators: [collaboratorSchema],
});
module.exports = mongoose.model("Event", EventSchema);
// Connection to database db.js
require("dotenv").config();
const mongoose = require("mongoose");
const connectToMongo = async () => {
const URI = process.env.MONGO_URI;
mongoose.connect(URI, { useNewUrlParser: true });
};
module.exports = connectToMongo;
Setting up the backend
Copy Code
require("dotenv").config()
const connectToMongo =require("./db");
connectToMongo();
const express = require("express")
var fetchuser = require("./middleware/fetchUser")
const app = express();
const port = 4000;
const User = require("./models/user")
const Events = require("./models/event")
var jwt = require("jsonwebtoken")
const JWT_SECRET = process.env.JWT_SECRET
const cors = require('cors');
const { Suprsend} = require("@suprsend/node-sdk");
const { Event } = require("@suprsend/node-sdk");
const supr_client = new Suprsend(process.env.WKEY, process.env.WSECRET);
//middleware if we want to read the json and req file
app.use(cors());
app.use(express.json());
Configuring Routes
Copy Code
// Register route
/******** Add to database and register on Suprsend ********/
app.post("/register", async (req, res) => {
let success = false;
try {
const user = await User.create({
email: req.body.email,
name: req.body.name,
phone: req.body.countryCode + req.body.phone,
password: req.body.password
});
const data = {
user: {
id: user.id
}
};
const authtoken = jwt.sign(data, JWT_SECRET);
success = true;
const distinct_id = user.email;
const user1 = supr_client.user.get_instance(distinct_id);
user1.add_email(user.email);
user1.add_sms("+" + user.phone);
user1.add_whatsapp("+" + user.phone);
const response = user1.save();
response.then((res) => console.log("response", res));
res.json({ success, authtoken });
} catch (error) {
console.error(error.message);
res.status(500).send("Some error occurred");
}
});
// Login route
/************ Login user **********/
app.post("/login", async (req, res) => {
try {
const { email, password } = req.body;
let success = false;
let user = await User.findOne({ email: email });
if (!user) {
return res.status(400).json({ success, message: "User does not exist" });
}
if (password !== user.password) return res.status(400).json({ success, message: "Password is wrong" });
const data = {
user: {
id: user.id
}
};
const authtoken = jwt.sign(data, JWT_SECRET);
success = true;
res.json({ success, authtoken });
} catch (error) {
console.error(error.message);
res.status(500).send("Some error occurred");
}
});
app.get("/getdata", (req, res) => {
res.send("Hello");
});
Fetching all events
Copy Code
app.get("/fetchallevents",fetchuser,async(req,res)=>{
try {
const events = await Events.find({ collaborators: { $elemMatch: { user: req.user.id } } })
.sort({ updatedAt: -1 });
res.json(events);
} catch (error) {
console.error(error.message);
res.status(500).send("some error occured");
}
})
Adding and deleting events
Copy Code
// Add events :-
app.post("/addevent",fetchuser,async(req,res)=>{
try {
const event = new Events({
author : req.user.id,
id : req.body.id,
title : req.body.title,
start : req.body.start,
end : req.body.end,
allDay : req.body.allDay,
collaborators: [{ user: req.user.id }],
})
const savedevent = await event.save();
const updatedEvent = await Events.findOneAndUpdate(
{ _id: savedevent._id },
{ $set: { id: savedevent._id } },
{ new: true }
);
res.json(savedevent);
} catch (error) {
console.error(error.message);
res.status(500).send("some error occured");
}
})
// Delete events
app.delete("/deleteevent/:id",async (req, res) => {
try {
let event = await Events.findById(req.params.id);
if(!event){return res.status(404).send("NOT Found")}
event = await Events.findByIdAndDelete(req.params.id);
res.json({event});
} catch (error) {
console.error(error.message);
res.status(500).send("some error occured");
}
})
Sharing Events
Copy Code
app.post("/shareevent", fetchuser, async (req, res) => {
try {
let success = false;
const { share, eventid } = req.body;
const user = await User.findOne({ email: share });
if (!user) {
return res.status(404).json({ success, message: "no such user exists" });
}
let user2 = await User.findById(req.user.id);
let event1 = await Events.findById(eventid);
if (!event1) {
return res.status(404).json({ success,error: 'event not found' });
}
event1.collaborators.push({ user: user._id });
await event1.save();
success = true;
const dateString = event1.start;
const dateObject = new Date(dateString);
const formattedDate = dateObject.toLocaleDateString('en-GB', {
year: 'numeric',
month: '2-digit',
day: '2-digit',
});
// Format time as 'hh:mm AM/PM'
const formattedTime = dateObject.toLocaleTimeString('en-US', {
hour: '2-digit',
minute: '2-digit',
hour12: true,
});
const distinct_id = user.email;
const event_name = "EVENTSHARED"
const properties = {
"recep":user.name,
"owner":user2.name,
"title":event1.title,
"date":formattedDate,
"time":formattedTime
}
const event = new Event(distinct_id, event_name, properties)
const response = supr_client.track_event(event)
response.then((res) => console.log("response", res));
return res.json({ success, event1});
} catch (error) {
console.error(error.message);
return res.status(500).send("some error occurred");
}
});
Edit Events
Copy Code
app.post("/editevent/:id",fetchuser,async(req,res)=>{
try {
let event1 = await Events.findById(req.params.id);
if (!event1) {
return res.status(404).json({ message: "Event not found" });
}
event1.title = req.body.title;
await event1.save();
res.json({event1});
} catch (error) {
console.error(error.message);
return res.status(500).send("some error occurred");
}
});
/**********************listening on port **************************************/
app.listen(port,()=>{
console.log("server started on port 4000");
})
For further information on workflows: https://docs.suprsend.com/docs/node-trigger-workflow-from-api
Creating Templates and Worflows using SuprSend API Once your backend is ready with SuprSend Node SDK, you can programatically create workflows from it. More details in here: Trigger Workflow from API
You can create templates from the dashboard itself.
Written by:
Sanjeev Kumar
Engineering, SuprSend