diff --git a/README.md b/README.md
index 5b53a0b1..c64badf0 100644
--- a/README.md
+++ b/README.md
@@ -1,38 +1,34 @@
-# Harbor Take Home Project
-
-Welcome to the Harbor take home project. We hope this is a good opportunity for you to showcase your skills.
-
-## The Challenge
-
-Build us a REST API for calendly. Remember to support
-
-- Setting own availability
-- Showing own availability
-- Finding overlap in schedule between 2 users
-
-It is up to you what else to support.
-
-## Expectations
-
-We care about
-
-- Have you thought through what a good MVP looks like? Does your API support that?
-- What trade-offs are you making in your design?
-- Working code - we should be able to pull and hit the code locally. Bonus points if deployed somewhere.
-- Any good engineer will make hacks when necessary - what are your hacks and why?
-
-We don't care about
-
-- Authentication
-- UI
-- Perfection - good and working quickly is better
-
-It is up to you how much time you want to spend on this project. There are likely diminishing returns as the time spent goes up.
-
-## Submission
-
-Please fork this repository and reach out to Prakash when finished.
-
-## Next Steps
-
-After submission, we will conduct a 30 to 60 minute code review in person. We will ask you about your thinking and design choices.
+## This is my implementation of calendly with below-mentioned components, features, use cases, assumptions, future scopes, etc.
+
+## Components
+1. This is a spring boot application which is divided into controller(API layer), service(service layer), model(contains object/DTOs), datastore(in-memeroy DB).
+2. We have 2 entities i.e. User & Meeting, these entities in turn have 2 dedicated singleton datastores, 2 services and 2 controllers.
+3. UserController has user CRUD APIs.
+4. MeetingController has scheduleMeeting() API which is used to schedule a meeting between a requestor and a requestee.
+
+## Features
+1. Users can be created, deleted, read and modified(new available slots can be added).
+2. Meetings can be scheduled between 2 people.
+3. Exception handling is added at all the required places.
+
+## APIs
+1. Create User
+2. Delete User
+3. Display all users
+4. Display a specific user
+5. Add available slots to an user
+6. Schedule meeting between the requestor and the requestee.
+
+## Assumptions:
+1. A user will always call an API with only his userId as a requestor to schedule a meeting.
+2. An in-memory singleton database is used.
+3. Requestor will schedule a meet in his own available slot, if not then our pre-validation check throws an exception.
+4. By default, all slots are unavailable for a new user.
+5. Time format used is [yyyy-MM-dd HH:mm].
+
+## Use Case
+1. Create user1
+2. Add available slots to user1 e.g. [2024-09-20 10:00 to 2024-09-20 10:30], [2024-09-20 12:00 to 2024-09-20 13:00], [2024-09-20 16:00 to 2024-09-20 16:45]
+3. Create user2
+4. Add available slots to user1 e.g. [2024-09-20 10:00 to 2024-09-20 10:30], [2024-09-20 13:00 to 2024-09-20 14:00], [2024-09-20 16:00 to 2024-09-20 16:30]
+5. user1 requests to schedule a meeting with user2 at [2024-09-20 10:00 to 2024-09-20 10:30]
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 00000000..dad42944
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,56 @@
+
+
+ 4.0.0
+
+ org.example
+ basic-spring-boot
+ 1.0-SNAPSHOT
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.5.RELEASE
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+ com.h2database
+ h2
+
+
+
+ org.projectlombok
+ lombok
+ 1.18.12
+ provided
+
+
+
+ org.modelmapper
+ modelmapper
+ 2.3.0
+
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/kaps/Application.java b/src/main/java/com/kaps/Application.java
new file mode 100644
index 00000000..c244a5bd
--- /dev/null
+++ b/src/main/java/com/kaps/Application.java
@@ -0,0 +1,11 @@
+package com.kaps;
+
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+
+@SpringBootApplication
+public class Application {
+ public static void main(String[] args){
+ SpringApplication.run(Application.class,args);
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/kaps/Utility.java b/src/main/java/com/kaps/Utility.java
new file mode 100644
index 00000000..32ae74e3
--- /dev/null
+++ b/src/main/java/com/kaps/Utility.java
@@ -0,0 +1,10 @@
+package com.kaps;
+
+import java.util.UUID;
+
+public class Utility {
+
+ public static final String generateUUID() {
+ return UUID.randomUUID().toString().replace("-", "");
+ }
+}
diff --git a/src/main/java/com/kaps/controller/MeetingController.java b/src/main/java/com/kaps/controller/MeetingController.java
new file mode 100644
index 00000000..5e53e14b
--- /dev/null
+++ b/src/main/java/com/kaps/controller/MeetingController.java
@@ -0,0 +1,25 @@
+package com.kaps.controller;
+
+import com.kaps.model.Meeting;
+import com.kaps.service.MeetingService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+
+@Controller
+@RequestMapping("/meeting")
+public class MeetingController {
+ @Autowired MeetingService meetingService;
+
+ @ResponseBody
+ @PostMapping("schedule")
+ public Meeting scheduleMeeting(@RequestParam("requestor") String requestor,
+ @RequestParam("requestee") String requestee,
+ @RequestParam("startTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startTime,
+ @RequestParam("endTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime endTime) throws Exception {
+ return meetingService.scheduleMeeting(requestor, requestee, startTime, endTime);
+ }
+}
diff --git a/src/main/java/com/kaps/controller/UserController.java b/src/main/java/com/kaps/controller/UserController.java
new file mode 100644
index 00000000..f965615b
--- /dev/null
+++ b/src/main/java/com/kaps/controller/UserController.java
@@ -0,0 +1,49 @@
+package com.kaps.controller;
+
+import com.kaps.model.User;
+import com.kaps.service.UserService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.format.annotation.DateTimeFormat;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.*;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Controller
+@RequestMapping("/user")
+public class UserController {
+ @Autowired UserService userService;
+
+ @ResponseBody
+ @PostMapping
+ public User createUser(@RequestBody User user) throws Exception {
+ return userService.createUser(user);
+ }
+
+ @ResponseBody
+ @GetMapping("/allusers")
+ public List getUsers() throws Exception {
+ return userService.getUsers();
+ }
+
+ @ResponseBody
+ @GetMapping
+ public User getUser(@RequestParam("userName") String userName) throws Exception {
+ return userService.getUserByName(userName);
+ }
+
+ @ResponseBody
+ @DeleteMapping
+ public void deleteUser(@RequestParam("userName") String userName) throws Exception {
+ userService.deleteUser(userName);
+ }
+
+ @ResponseBody
+ @PutMapping
+ public User addAvailableSlot(@RequestParam("userName") String userName,
+ @RequestParam("startTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime startTime,
+ @RequestParam("endTime") @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") LocalDateTime endTime) throws Exception {
+ return userService.addAvailableSlot(userName, startTime, endTime);
+ }
+}
diff --git a/src/main/java/com/kaps/datastore/MeetingDataStore.java b/src/main/java/com/kaps/datastore/MeetingDataStore.java
new file mode 100644
index 00000000..12fdf58d
--- /dev/null
+++ b/src/main/java/com/kaps/datastore/MeetingDataStore.java
@@ -0,0 +1,26 @@
+package com.kaps.datastore;
+
+import com.kaps.Utility;
+import com.kaps.model.Meeting;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class MeetingDataStore {
+ Map meetingDataMap;
+
+ public static MeetingDataStore getInstance() {
+ return new MeetingDataStore();
+ }
+
+ private MeetingDataStore() {
+ meetingDataMap = new HashMap<>();
+ }
+
+ public Meeting scheduleMeeting(Meeting meeting) {
+ meeting.setId(Utility.generateUUID());
+ meetingDataMap.put(meeting.getId(), meeting);
+
+ return meetingDataMap.get(meeting.getId());
+ }
+}
diff --git a/src/main/java/com/kaps/datastore/UserDataStore.java b/src/main/java/com/kaps/datastore/UserDataStore.java
new file mode 100644
index 00000000..c8304fac
--- /dev/null
+++ b/src/main/java/com/kaps/datastore/UserDataStore.java
@@ -0,0 +1,99 @@
+package com.kaps.datastore;
+
+import com.kaps.Utility;
+import com.kaps.model.Slot;
+import com.kaps.model.User;
+
+import java.time.LocalDateTime;
+import java.util.*;
+
+public class UserDataStore {
+ Map userNameToUserMap;
+
+ public static UserDataStore getInstance() {
+ return new UserDataStore();
+ }
+
+ private UserDataStore() {
+ userNameToUserMap = new HashMap<>();
+ }
+
+ public User createUser(User user) throws Exception {
+ checkIfUserAlreadyExists(user.getName());
+ user.setId(Utility.generateUUID());
+ userNameToUserMap.put(user.getName(), user);
+
+ return userNameToUserMap.get(user.getName());
+ }
+
+ public void deleteUser(String userName) throws Exception {
+ checkIfUserExists(userName);
+ userNameToUserMap.remove(userName);
+ }
+
+ public User addAvailableSlot(String userName, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ checkIfUserExists(userName);
+ performSanity(startTime, endTime);
+
+ User user = userNameToUserMap.get(userName);
+ user.getAvailableSlots().add(
+ new Slot(UUID.randomUUID().toString().replace("-", ""), startTime, endTime));
+
+ return userNameToUserMap.get(userName);
+ }
+
+ public List getUsers() {
+ List users = new ArrayList<>();
+
+ for (String userName: userNameToUserMap.keySet()) {
+ users.add(userNameToUserMap.get(userName));
+ }
+
+ return users;
+ }
+
+ public User getUserByName(String userName) throws Exception {
+ checkIfUserExists(userName);
+
+ return userNameToUserMap.get(userName);
+ }
+
+ public List getAvailableSlots(String userName) throws Exception {
+ checkIfUserExists(userName);
+
+ return new ArrayList<>(userNameToUserMap.get(userName).getAvailableSlots());
+ }
+
+ public void removeSlot(String userName, String slotId) throws Exception {
+ Set slots = userNameToUserMap.get(userName).getAvailableSlots();
+ Set newSlots = new HashSet<>();
+
+ for (Slot slot: slots) {
+ if (!slot.getId().equals(slotId))
+ newSlots.add(slot);
+ }
+
+ userNameToUserMap.get(userName).setAvailableSlots(newSlots);
+ }
+
+ private void checkIfUserExists(String userName) throws Exception {
+ if (!userNameToUserMap.containsKey(userName)) {
+ throw new Exception("No user exists with userName: " + userName);
+ }
+ }
+
+ private void checkIfUserAlreadyExists(String userName) throws Exception {
+ if (userNameToUserMap.containsKey(userName)) {
+ throw new Exception("User already exists with userName: " + userName);
+ }
+ }
+
+ public void performSanity(LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ if (startTime.isAfter(endTime)) {
+ throw new Exception("Meeting start time cannot be after meeting end time!");
+ }
+ if (startTime.isBefore(LocalDateTime.now())) {
+ throw new Exception("Cannot schedule an expired meeting!");
+ }
+ }
+}
diff --git a/src/main/java/com/kaps/model/Meeting.java b/src/main/java/com/kaps/model/Meeting.java
new file mode 100644
index 00000000..91475488
--- /dev/null
+++ b/src/main/java/com/kaps/model/Meeting.java
@@ -0,0 +1,23 @@
+package com.kaps.model;
+
+import javafx.util.Pair;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+
+import java.time.LocalDateTime;
+import java.util.Set;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class Meeting {
+ @Id private String id;
+ private String requestor;
+ private String requestee;
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+}
diff --git a/src/main/java/com/kaps/model/Slot.java b/src/main/java/com/kaps/model/Slot.java
new file mode 100644
index 00000000..72d08402
--- /dev/null
+++ b/src/main/java/com/kaps/model/Slot.java
@@ -0,0 +1,19 @@
+package com.kaps.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+
+import java.time.LocalDateTime;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class Slot {
+ @Id private String id;
+ private LocalDateTime startTime;
+ private LocalDateTime endTime;
+}
diff --git a/src/main/java/com/kaps/model/User.java b/src/main/java/com/kaps/model/User.java
new file mode 100644
index 00000000..029db72b
--- /dev/null
+++ b/src/main/java/com/kaps/model/User.java
@@ -0,0 +1,20 @@
+package com.kaps.model;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+import org.springframework.data.annotation.Id;
+
+import java.util.Set;
+
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+@Setter
+public class User {
+ @Id private String id;
+ private String name;
+ private String email;
+ private Set availableSlots;
+}
diff --git a/src/main/java/com/kaps/service/MeetingService.java b/src/main/java/com/kaps/service/MeetingService.java
new file mode 100644
index 00000000..7e06e27b
--- /dev/null
+++ b/src/main/java/com/kaps/service/MeetingService.java
@@ -0,0 +1,50 @@
+package com.kaps.service;
+
+import com.kaps.datastore.MeetingDataStore;
+import com.kaps.model.Meeting;
+import com.kaps.model.Slot;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+
+@Service
+public class MeetingService {
+ MeetingDataStore meetingDataStore;
+ @Autowired UserService userService;
+
+ public MeetingService() {
+ meetingDataStore = MeetingDataStore.getInstance();
+ userService = new UserService();
+ }
+
+ /*
+ * Scheduling Algo:
+ * 1. the requestor should have exact slot present otherwise throw exception, capture this slot id
+ * 2. then find overlapping slot for requestee
+ * 3. schedule the meet
+ * 4. remove these slots
+ * */
+
+ public Meeting scheduleMeeting(String requestor, String requestee, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ performMeetingSanity(startTime, endTime);
+ if (!userService.isSlotAvailable(requestor, startTime, endTime)) {
+ throw new Exception("Requestor of the meeting does not have the slot himself. Kindly add a slot and then schedule!");
+ }
+ Slot requesteeSlot = userService.getOverlappingSlots(requestee, startTime, endTime);
+ Meeting meet = meetingDataStore.scheduleMeeting(new Meeting(null, requestor, requestee, startTime, endTime));
+ userService.removeSlot(requestee, requesteeSlot.getId());
+ userService.removeSlot(requestor, userService.getSlotId(requestor, startTime, endTime));
+
+ return meet;
+ }
+
+ public void performMeetingSanity(LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ if (startTime.isAfter(endTime)) {
+ throw new Exception("Meeting start time cannot be after meeting end time!");
+ }
+ if (startTime.isBefore(LocalDateTime.now())) {
+ throw new Exception("Cannot schedule an expired meeting!");
+ }
+ }
+}
diff --git a/src/main/java/com/kaps/service/UserService.java b/src/main/java/com/kaps/service/UserService.java
new file mode 100644
index 00000000..0aa01343
--- /dev/null
+++ b/src/main/java/com/kaps/service/UserService.java
@@ -0,0 +1,75 @@
+package com.kaps.service;
+
+import com.kaps.datastore.UserDataStore;
+import com.kaps.model.Slot;
+import com.kaps.model.User;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDateTime;
+import java.util.List;
+
+@Service
+public class UserService {
+ UserDataStore userDataStore;
+
+ public UserService() {
+ userDataStore = UserDataStore.getInstance();
+ }
+
+ public User createUser(User user) throws Exception {
+ return userDataStore.createUser(user);
+ }
+
+ public void deleteUser(String userName) throws Exception {
+ userDataStore.deleteUser(userName);
+ }
+
+ public User addAvailableSlot(String userName, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ return userDataStore.addAvailableSlot(userName, startTime, endTime);
+ }
+
+ public List getUsers() {
+ return userDataStore.getUsers();
+ }
+
+ public User getUserByName(String userName) throws Exception {
+ return userDataStore.getUserByName(userName);
+ }
+
+ public boolean isSlotAvailable(String userName, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ for (Slot slot: userDataStore.getAvailableSlots(userName)) {
+ if (slot.getStartTime().equals(startTime) && slot.getEndTime().equals(endTime))
+ return true;
+ }
+
+ return false;
+ }
+
+ public String getSlotId(String userName, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ for (Slot slot: userDataStore.getAvailableSlots(userName)) {
+ if (slot.getStartTime().equals(startTime) && slot.getEndTime().equals(endTime))
+ return slot.getId();
+ }
+
+ return "";
+ }
+
+ public void removeSlot(String userName, String slotId) throws Exception {
+ userDataStore.removeSlot(userName, slotId);
+ }
+
+ public Slot getOverlappingSlots(String userName, LocalDateTime startTime, LocalDateTime endTime) throws Exception {
+ List slots = userDataStore.getAvailableSlots(userName);
+
+ for (Slot slot: slots) {
+
+
+ if (slot.getStartTime().equals(startTime) && slot.getEndTime().equals(endTime))
+ return slot;
+ else if (slot.getStartTime().isBefore(startTime) && (slot.getEndTime().equals(endTime) || slot.getEndTime().isAfter(endTime)))
+ return slot;
+ }
+
+ return new Slot();
+ }
+}