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
72 changes: 34 additions & 38 deletions README.md
Original file line number Diff line number Diff line change
@@ -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]
56 changes: 56 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>org.example</groupId>
<artifactId>basic-spring-boot</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.12</version>
<scope>provided</scope>
</dependency>

<dependency>
<groupId>org.modelmapper</groupId>
<artifactId>modelmapper</artifactId>
<version>2.3.0</version>
</dependency>

</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
11 changes: 11 additions & 0 deletions src/main/java/com/kaps/Application.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
10 changes: 10 additions & 0 deletions src/main/java/com/kaps/Utility.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.kaps;

import java.util.UUID;

public class Utility {

public static final String generateUUID() {
return UUID.randomUUID().toString().replace("-", "");
}
}
25 changes: 25 additions & 0 deletions src/main/java/com/kaps/controller/MeetingController.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
49 changes: 49 additions & 0 deletions src/main/java/com/kaps/controller/UserController.java
Original file line number Diff line number Diff line change
@@ -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<User> 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);
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/kaps/datastore/MeetingDataStore.java
Original file line number Diff line number Diff line change
@@ -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<String, Meeting> 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());
}
}
99 changes: 99 additions & 0 deletions src/main/java/com/kaps/datastore/UserDataStore.java
Original file line number Diff line number Diff line change
@@ -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<String, User> 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<User> getUsers() {
List<User> 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<Slot> getAvailableSlots(String userName) throws Exception {
checkIfUserExists(userName);

return new ArrayList<>(userNameToUserMap.get(userName).getAvailableSlots());
}

public void removeSlot(String userName, String slotId) throws Exception {
Set<Slot> slots = userNameToUserMap.get(userName).getAvailableSlots();
Set<Slot> 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!");
}
}
}
23 changes: 23 additions & 0 deletions src/main/java/com/kaps/model/Meeting.java
Original file line number Diff line number Diff line change
@@ -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;
}
Loading