diff --git a/.jpb/persistence-units.xml b/.jpb/persistence-units.xml new file mode 100644 index 0000000000000000000000000000000000000000..a4ab6eb6024aa6d41a9e6011ae8f8c3c9c8d30b7 --- /dev/null +++ b/.jpb/persistence-units.xml @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project version="4"> + <component name="PersistenceUnitSettings"> + <persistence-units> + <persistence-unit name="Default" /> + </persistence-units> + </component> +</project> \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/build.gradle b/BackendFolder/SwitchRoom/build.gradle index fb9f39c70a90b989f9c42db07ccde0e0fef2ce3a..836672e13e795c70b9e74f64ed9616578613e3ce 100644 --- a/BackendFolder/SwitchRoom/build.gradle +++ b/BackendFolder/SwitchRoom/build.gradle @@ -24,7 +24,9 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // implementation 'org.springframework.boot:spring-boot-starter-data-mongodb' implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'io.jsonwebtoken:jjwt:0.9.1' implementation 'org.springframework.session:spring-session-core' + annotationProcessor 'io.jsonwebtoken:jjwt:0.9.1' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/SwitchRoomApplication.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/SwitchRoomApplication.java index 6e5574b011af1ede2363e739ff80bd09ff151f60..3352310c2925b49733d143cd0c23d6b487dd0ecd 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/SwitchRoomApplication.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/SwitchRoomApplication.java @@ -2,8 +2,10 @@ package vt.CS5934.SwitchRoom; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.scheduling.annotation.EnableScheduling; @SpringBootApplication +@EnableScheduling public class SwitchRoomApplication { public static void main(String[] args) { @@ -11,3 +13,10 @@ public class SwitchRoomApplication { } } + +/* +Change logs: +Date | Author | Description +2022-10-31 | Fangzheng Zhang | Add EnableScheduling to build the scheduled task + + */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedOfferController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedOfferController.java new file mode 100644 index 0000000000000000000000000000000000000000..bede6a5b3582fe55bae9dc9970565ceda78f2872 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedOfferController.java @@ -0,0 +1,59 @@ +package vt.CS5934.SwitchRoom.controllers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import vt.CS5934.SwitchRoom.models.ResponseModel; +import vt.CS5934.SwitchRoom.models.UserOfferModel; +import vt.CS5934.SwitchRoom.services.OfferPageService; +import vt.CS5934.SwitchRoom.services.WishlistPageService; + +/** + * The "@MatchedController" made the class into rest handle class + * The "@RequestMapping("matched")" on the class level make it only react to url ".../example/..." + */ +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) +@RestController +@RequestMapping("matchedOffer") +public class MatchedOfferController { + + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + OfferPageService offerPageService; + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(UserController.class); + + /** + * This function will handle the post function and change the JSON body into data class + */ + @GetMapping("/{offerId}") + public ResponseModel getOffer(@PathVariable long offerId) { + logger.info("You reached the getOffer() function."); + System.out.println(offerId); + try{ + UserOfferModel offer = offerPageService.getUserOfferInfo(offerId); +// List<MatchedWishlistRecordModel> offerNotifications = matchedWishlistRecordService.getOfferListWithIDFromDB(userId); +// List<ExampleModel> exampleModels = exampleService.getAllExampleModelsFromDB(); + return new ResponseModel( + "This this a offer String response", + HttpStatus.OK, + offer); + }catch (Exception e){ + return new ResponseModel( + "Error occur on get All ExampleModels, Error info is: " + e, + HttpStatus.OK, + null); + } + } +} \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedWishListController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedWishListController.java new file mode 100644 index 0000000000000000000000000000000000000000..ed7cdec427db88bddd53d6e26e252d540f32546c --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/MatchedWishListController.java @@ -0,0 +1,57 @@ +package vt.CS5934.SwitchRoom.controllers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import vt.CS5934.SwitchRoom.models.ResponseModel; +import vt.CS5934.SwitchRoom.models.WishlistItemModel; +import vt.CS5934.SwitchRoom.services.WishlistPageService; + + +/** + * The "@MatchedController" made the class into rest handle class + * The "@RequestMapping("matched")" on the class level make it only react to url ".../example/..." + */ +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) +@RestController +@RequestMapping("matchedWishList") +public class MatchedWishListController { + + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + WishlistPageService wishlistPageService; + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(UserController.class); + + /** + * This function will handle the post function and change the JSON body into data class + */ + @GetMapping("/{wishlistId}") + public ResponseModel getWishList(@PathVariable long wishlistId) { + logger.info("You reached the getWishList() function."); + System.out.println(wishlistId); + try{ + WishlistItemModel wishlist = wishlistPageService.getWishlistItemInfo(wishlistId); + return new ResponseModel( + "This this a offer String response", + HttpStatus.OK, + wishlist); + }catch (Exception e){ + return new ResponseModel( + "Error occur on get All ExampleModels, Error info is: " + e, + HttpStatus.OK, + null); + } + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/NotificationController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/NotificationController.java new file mode 100644 index 0000000000000000000000000000000000000000..7080bc3e0e6f6aee73a805d845c6ddc34fbc3de8 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/NotificationController.java @@ -0,0 +1,105 @@ +package vt.CS5934.SwitchRoom.controllers; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; +import vt.CS5934.SwitchRoom.models.*; +import vt.CS5934.SwitchRoom.services.DealWithWishlist; +import vt.CS5934.SwitchRoom.services.MatchedWishlistRecordService; +import vt.CS5934.SwitchRoom.services.OfferWishlistLookUpService; +import vt.CS5934.SwitchRoom.services.UserService; + +import java.util.List; + + +/** + * The "@RestController" made the class into rest handle class + * The "@RequestMapping("example")" on the class level make it only react to url ".../example/..." + */ +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) +@RestController +@RequestMapping("notification") +public class NotificationController { + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + MatchedWishlistRecordService matchedWishlistRecordService; + + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + OfferWishlistLookUpService offerWishlistLookUpService; + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + DealWithWishlist dealWithWishlist; + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(UserController.class); + + /** + * This function will handle the post function and change the JSON body into data class + */ + @CrossOrigin + @GetMapping("/{userId}") + public ResponseModel getNotification(@PathVariable long userId) { + logger.info("You reached the getNotification() function."); +// System.out.println(userId); + try{ + List<MatchedWishlistRecordModel> offerNotifications = matchedWishlistRecordService.getOfferListWithIDFromDB(userId); +// System.out.println(offerNotifications); +// List<ExampleModel> exampleModels = exampleService.getAllExampleModelsFromDB(); + return new ResponseModel( + "This this a notification String response", + HttpStatus.OK, + offerNotifications); + }catch (Exception e){ + return new ResponseModel( + "Error occur on get All ExampleModels, Error info is: " + e, + HttpStatus.OK, + null); + } + } + + /** + * This function will handle the post function and change the JSON body into data class + */ + @CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} + ) + @GetMapping("/wish/{userId}") + public ResponseModel getNotificationwish(@PathVariable long userId) { + logger.info("You reached the getNotificationwish() function."); +// System.out.println(userId); + try{ + List<UserOfferWishlistLookUpModel> lookup = offerWishlistLookUpService.getOfferWishlistLookUpWithIDFromDB(userId); + System.out.println(lookup); + List<MatchedWishlistRecordModel> wishlists = dealWithWishlist.getMatchedWishlist(lookup); +// List<MatchedWishlistRecordModel> offerNotifications = matchedWishlistRecordService.getOfferListWithIDFromDB(userId); +// List<ExampleModel> exampleModels = exampleService.getAllExampleModelsFromDB(); + return new ResponseModel( + "This this a notification String response", + HttpStatus.OK, + wishlists); + }catch (Exception e){ + return new ResponseModel( + "Error occur on get All ExampleModels, Error info is: " + e, + HttpStatus.OK, + null); + } + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/OfferPageController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/OfferPageController.java index a170c45d6b17f1dd3574ca4b30c602e10b62a46d..1a9a84181bc4277f2325171f0c5c9be1d3289f1a 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/OfferPageController.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/OfferPageController.java @@ -10,9 +10,15 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import vt.CS5934.SwitchRoom.models.ResponseModel; import vt.CS5934.SwitchRoom.models.UserOfferModel; +import vt.CS5934.SwitchRoom.models.WishlistMatchRequestInfo; import vt.CS5934.SwitchRoom.services.OfferPageService; -@CrossOrigin +import java.util.ArrayList; + +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) @RestController @RequestMapping("offer") public class OfferPageController { @@ -25,8 +31,8 @@ public class OfferPageController { * This class will fetch user's offer from DB and send to front end * @return ResponseModel contains offer data */ - @GetMapping("/{userId}") - public ResponseModel getUserOfferInfo(@PathVariable Long userId){ + @GetMapping + public ResponseModel getUserOfferInfo(@CookieValue(value = "userId") Long userId){ ResponseModel responseModel = new ResponseModel(); try{ responseModel.setMessage("Success"); @@ -51,7 +57,7 @@ public class OfferPageController { responseModel.setData(offerPageService.saveNewUserOffer(offerModel)); return responseModel; }catch (Exception e){ - logger.error("Error in createNewUserOffer: "+e); + logger.error("Error in createNewUserOffer: "+e.fillInStackTrace()); responseModel.setMessage("Failed, Reason: " + e); responseModel.setStatus(HttpStatus.NOT_FOUND); responseModel.setData(null); @@ -60,11 +66,12 @@ public class OfferPageController { } @PostMapping("/updateOffer") - public ResponseModel updateUserOffer(UserOfferModel offerModel){ + public ResponseModel updateUserOffer(@RequestBody UserOfferModel offerModel){ + offerPageService.removeOfferFromMatchedTable(offerModel.getUserId()); return createNewUserOffer(offerModel); } - @DeleteMapping("/{userId}") + @DeleteMapping("/deleteOffer/{userId}") public ResponseModel deleteUserOffer(@PathVariable Long userId){ ResponseModel responseModel = new ResponseModel(); try{ @@ -82,4 +89,26 @@ public class OfferPageController { } } + @GetMapping("/wishlistMatchRequestInfoList") + public ResponseModel getWishlistMatchRequestInfoList(@CookieValue(value = "userId") Long userId){ + ResponseModel responseModel = new ResponseModel(); + try{ + responseModel.setMessage("Success"); + responseModel.setStatus(HttpStatus.OK); + responseModel.setData(offerPageService.getWishlistMatchRequestInfoList(userId)); + return responseModel; + }catch (Exception e){ + logger.error("Error in getWishlistMatchRequestInfoList: "+e.fillInStackTrace()); + responseModel.setMessage("Failed, Reason: " + e); + responseModel.setStatus(HttpStatus.NOT_FOUND); + responseModel.setData(new ArrayList<WishlistMatchRequestInfo>()); + return responseModel; + } + } + } +/* +Change logs: +Date | Author | Description +2022-11-05 | Fangzheng Zhang | add getWishlistMatchRequestInfoList function + */ diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/SearchFlightController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/SearchFlightController.java index a74b70cc64602b9693e5b905ba34dd5afd7b3c31..575f9adbad9663a7d89357df24a1d80fee6a698f 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/SearchFlightController.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/SearchFlightController.java @@ -7,27 +7,38 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; +import vt.CS5934.SwitchRoom.models.FlightResultModel; import vt.CS5934.SwitchRoom.models.ResponseModel; import vt.CS5934.SwitchRoom.models.SerachFlightModel; +import vt.CS5934.SwitchRoom.services.FlightApiService; +import org.springframework.beans.factory.annotation.Autowired; +import java.util.List; -@CrossOrigin + +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) @RestController @RequestMapping("searchflight") public class SearchFlightController { private final Logger logger = LoggerFactory.getLogger(SearchFlightController.class); -// @Autowired -// OfferPageService offerPageService; + @Autowired + FlightApiService flightApiService; @PostMapping("/newFlight") public ResponseModel createNewSearchFlight(@RequestBody SerachFlightModel newFlight){ ResponseModel responseModel = new ResponseModel(); try{ + List<FlightResultModel> flightList = flightApiService.performSearch(newFlight); responseModel.setMessage("Success"); responseModel.setStatus(HttpStatus.OK); - responseModel.setData(newFlight); + responseModel.setData(flightList); +// System.out.println(flightApiService.performSearch(newFlight)); +// responseModel.setData(newFlight); return responseModel; }catch (Exception e){ logger.error("Error in createSearchFlight: "+e); diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/UserController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/UserController.java index 89404aff781ece5441a10a98e52dd19a3b1ec78d..f6fb157f2391b6e0ecdaf7c7f02b5ea9b208bafc 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/UserController.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/UserController.java @@ -1,5 +1,6 @@ package vt.CS5934.SwitchRoom.controllers; +import com.fasterxml.jackson.core.JsonProcessingException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; @@ -7,15 +8,21 @@ import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; import vt.CS5934.SwitchRoom.models.ResponseModel; import vt.CS5934.SwitchRoom.models.UserModel; +import vt.CS5934.SwitchRoom.services.Token; import vt.CS5934.SwitchRoom.services.UserService; +import javax.servlet.http.Cookie; +import javax.servlet.http.HttpServletResponse; import java.security.NoSuchAlgorithmException; /** * The "@RestController" made the class into rest handle class * The "@RequestMapping("example")" on the class level make it only react to url ".../example/..." */ -@CrossOrigin +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) @RestController @RequestMapping("user") public class UserController { @@ -55,36 +62,92 @@ public class UserController { } @PostMapping("/loginUser") - public ResponseModel loginUser(@RequestBody UserModel user) throws NoSuchAlgorithmException { - logger.info("You reached the handlePost() functions."); + public ResponseModel loginUser(@RequestBody UserModel userInfo, HttpServletResponse servletResponse) + throws NoSuchAlgorithmException { ResponseModel response = new ResponseModel(); - String inputPassword = userService.hashPassword(user.getPassword()); -// hash.get_SHA_1_SecurePassword(user.getPassword()); - try{ - UserModel existUser = userService.loginUser(user.getUsername()); - if (existUser != null && existUser.getPassword().equals(inputPassword)) { - response.setMessage("Login in successfully"); - response.setStatus(HttpStatus.OK); - } else { - response.setMessage("Couldn't find an account matching the login info you entered"); - response.setStatus(HttpStatus.FORBIDDEN); - } - existUser.setPassword(null); - response.setData(existUser); - -// UserModel existUser = userService.loginUser(user.getUsername()); -// response.setMessage("Login in successfully"); -// response.setStatus(HttpStatus.OK); -// existUser.setPassword(null); -// response.setData(existUser); + String[] result = userService.loginUser(userInfo); + + if (result.length > 0) { + Cookie theCookie = new Cookie("userId", result[0]); + theCookie.setHttpOnly(false); + theCookie.setSecure(false); + theCookie.setPath("/"); + theCookie.setMaxAge(60*60); // 1 hour + servletResponse.addCookie(theCookie); + + + Cookie theCookie2 = new Cookie("token", result[1]); + theCookie2.setHttpOnly(false); + theCookie2.setSecure(false); + theCookie2.setPath("/"); + theCookie2.setMaxAge(60*60); + servletResponse.addCookie(theCookie2); + + response.setMessage("Login in successfully"); + response.setStatus(HttpStatus.OK); + } else { + response.setMessage("Couldn't find an account matching the login info you entered"); + response.setStatus(HttpStatus.FORBIDDEN); + } + + return response; + } + @GetMapping("/checkLoginSession") + public ResponseModel checkLoginSession( + @CookieValue(value = "userId", required = false) String userId, + @CookieValue(value = "token", required = false) String token) { + ResponseModel response = new ResponseModel(); + boolean result = userService.checkLoginSession(userId, token); + + if (result) { + response.setStatus(HttpStatus.OK); + } else { + response.setMessage("Login session expired or invalid"); + response.setStatus(HttpStatus.FORBIDDEN); + } + + return response; + } + @PostMapping("/resetPassword") + public ResponseModel resetPassword( + @CookieValue(value = "userId", required = false) String userId, + @CookieValue(value = "token", required = false) String token, + @RequestBody String payload) throws JsonProcessingException, NoSuchAlgorithmException { + ResponseModel response = new ResponseModel(); + + if (userId == null || token == null || !userService.checkLoginSession(userId, token)) { + response.setMessage("Login session expired or invalid"); + response.setStatus(HttpStatus.FORBIDDEN); return response; - }catch (Exception e){ - return new ResponseModel( - "INTERNAL_SERVER_ERROR", - HttpStatus.INTERNAL_SERVER_ERROR, - null); } + + boolean result = userService.resetPassword(userId, payload); + if (result) { + response.setMessage("Successfully reset your password"); + response.setStatus(HttpStatus.OK); + } else { + response.setMessage("Couldn't find an account matching the login info you entered"); + response.setStatus(HttpStatus.FORBIDDEN); + } + + return response; + } + + @GetMapping("/profile") + public ResponseModel getProfile( + @CookieValue(value = "userId", required = false) String userId, + @CookieValue(value = "token", required = false) String token) { + ResponseModel response = new ResponseModel(); + + if (userId == null || token == null || !userService.checkLoginSession(userId, token)) { + response.setMessage("Login session expired or invalid"); + response.setStatus(HttpStatus.FORBIDDEN); + return response; + } + + response = userService.getProfile(userId); + return response; } } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/WishlistPageController.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/WishlistPageController.java index 2f6a29ff46a65a9c0f486f5ea38f09c54bc5615f..869e2825b35b5451ddb386ce9b8d910ba7c967b9 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/WishlistPageController.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/controllers/WishlistPageController.java @@ -5,11 +5,17 @@ import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.*; +import vt.CS5934.SwitchRoom.models.OfferMatchRequestInfo; import vt.CS5934.SwitchRoom.models.ResponseModel; import vt.CS5934.SwitchRoom.models.WishlistItemModel; import vt.CS5934.SwitchRoom.services.WishlistPageService; -@CrossOrigin +import java.util.List; + +@CrossOrigin( + allowCredentials = "true", + origins = {"http://localhost:8080/"} +) @RestController @RequestMapping("wishlist") public class WishlistPageController { @@ -19,8 +25,8 @@ public class WishlistPageController { @Autowired WishlistPageService wishlistPageService; - @GetMapping("/{userId}") - public ResponseModel getWishlistList(@PathVariable Long userId){ + @GetMapping + public ResponseModel getWishlistList(@CookieValue(value = "userId") Long userId){ ResponseModel responseModel = new ResponseModel(); try{ responseModel.setMessage("Success"); @@ -36,8 +42,8 @@ public class WishlistPageController { } } - @PostMapping("/newWishlistItem/{userId}") - public ResponseModel saveNewWishlistItem(@PathVariable Long userId, + @PostMapping("/newWishlistItem") + public ResponseModel saveNewWishlistItem(@CookieValue(value = "userId") Long userId, @RequestBody WishlistItemModel wishlistItemModel){ ResponseModel responseModel = new ResponseModel(); try{ @@ -71,10 +77,11 @@ public class WishlistPageController { } } - @DeleteMapping("/deleteWishlistItem/{userId}") - public ResponseModel deleteWishlistItem(@PathVariable Long userId, @RequestParam Long wishlistItemId){ + @DeleteMapping("/deleteWishlistItem/{wishlistItemId}") + public ResponseModel deleteWishlistItem(@PathVariable Long wishlistItemId){ ResponseModel responseModel = new ResponseModel(); try{ + Long userId = wishlistPageService.getPairedUserId(wishlistItemId); wishlistPageService.deleteWishlistItem(wishlistItemId); responseModel.setMessage("Success"); responseModel.setStatus(HttpStatus.OK); @@ -88,11 +95,48 @@ public class WishlistPageController { return responseModel; } } + + @GetMapping("/loadOfferMatchList/{wishlistItemId}") + public ResponseModel getOfferMatchList(@PathVariable Long wishlistItemId){ + ResponseModel responseModel = new ResponseModel(); + try{ + List<OfferMatchRequestInfo> offerMatchRequestInfoList = + wishlistPageService.getOfferMatchList(wishlistItemId); + responseModel.setMessage("Success"); + responseModel.setStatus(HttpStatus.OK); + responseModel.setData(offerMatchRequestInfoList); + return responseModel; + }catch (Exception e){ + logger.error("Error in getOfferMatchList: "+e.fillInStackTrace()); + responseModel.setMessage("getOfferMatchList Failed, Reason: " + e); + responseModel.setStatus(HttpStatus.NOT_FOUND); + responseModel.setData(null); + return responseModel; + } + } + @PostMapping("/loadOfferMatchCount") + public ResponseModel getOfferMatchCount(@RequestBody List<Long> wishlistItemIdList){ + ResponseModel responseModel = new ResponseModel(); + try{ + List<Long> offerMatchCount = wishlistPageService.getOfferMatchCount(wishlistItemIdList); + responseModel.setMessage("Success"); + responseModel.setStatus(HttpStatus.OK); + responseModel.setData(offerMatchCount); + return responseModel; + }catch (Exception e){ + logger.error("Error in getOfferMatchCount: "+e.fillInStackTrace()); + responseModel.setMessage("getOfferMatchCount Failed, Reason: " + e); + responseModel.setStatus(HttpStatus.NOT_FOUND); + responseModel.setData(null); + return responseModel; + } + } } /* Change logs: Date | Author | Description 2022-10-21 | Fangzheng Zhang | create class and init - +2022-11-03 | Fangzheng Zhang | Remove user id in the parameter, since cookie is ready +2022-11-05 | Fangzheng Zhang | add getOfferMatchList function and getOfferMatchCount function */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/jobs/OfferWishlistMatchingJob.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/jobs/OfferWishlistMatchingJob.java new file mode 100644 index 0000000000000000000000000000000000000000..8abe78e63600f7a9c90f754961b4cfa261a021eb --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/jobs/OfferWishlistMatchingJob.java @@ -0,0 +1,302 @@ +/** + * This class contains the business logic about find matches between offers and wishlist items + */ + +package vt.CS5934.SwitchRoom.jobs; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; +import vt.CS5934.SwitchRoom.models.*; +import vt.CS5934.SwitchRoom.repositories.*; +import vt.CS5934.SwitchRoom.utility.LookupTables; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static vt.CS5934.SwitchRoom.utility.UsefulTools.DATE_FORMAT; + + +@Component +@EnableAsync +public class OfferWishlistMatchingJob { + + private final Logger logger = LoggerFactory.getLogger(OfferWishlistMatchingJob.class); + + @Autowired + MatchingJobInfoRepository matchingJobInfoRepository; + @Autowired + UserOfferRepository userOfferRepository; + @Autowired + OfferWaitingMatchRepository offerWaitingMatchRepository; + @Autowired + WishlistItemRepository wishlistItemRepository; + @Autowired + WishlistWaitingMatchRepository wishlistWaitingMatchRepository; + + @Autowired + MatchedWishlistRecordRepository matchedWishlistRecordRepository; + + + @Async + @Scheduled(fixedDelayString = "${offer.wishlist.job.gap.seconds:60}000", + initialDelayString = "${offer.wishlist.job.init.delay.seconds:60}000") + public void jobStarter(){ + logger.info("Offer Wishlist Job start ..."); + HashMap<Long, MatchingJobInfoModel> lastPullTimeMap = new HashMap<>(); + lastPullTimeMap.put(LookupTables.OFFER_JOB_DB_ID, null); + lastPullTimeMap.put(LookupTables.WISHLIST_JOB_DB_ID, null); + lastPullTimeMap.put(LookupTables.MATCHING_JOB_DB_ID, null); + List<OfferWaitingMatchModel> newOfferWaitingRecordList = null; + List<WishlistWaitingMatchModel> newWishlistWaitingRecordList = null; + + try{ + //check the job status table, get the last pull time + getLastPullTime(lastPullTimeMap); + logger.info("getLastPullTime() finished"); + + // Query and push the new offer records into its waiting list + newOfferWaitingRecordList = + pushNewOfferRecordsIntoWaitingTable(lastPullTimeMap.get(LookupTables.OFFER_JOB_DB_ID)); + logger.info("pushNewOfferRecordsIntoWaitingTable() finished"); + + // Query and push the new wishlist records into its waiting list + newWishlistWaitingRecordList = + pushNewWishlistItemRecordsIntoWaitingTable(lastPullTimeMap.get(LookupTables.WISHLIST_JOB_DB_ID)); + logger.info("pushNewWishlistItemRecordsIntoWaitingTable() finished"); + + }catch (Exception e){ + logger.error("Error occur when fetch the new records from Offer table / Wishlist table or" + + "push them into their waiting table: " +e.fillInStackTrace()); + // may need to stop the task if needed. + } + + try{ + //find the match records and push to the matched table + findMatchFromWaitingTable(lastPullTimeMap.get(LookupTables.MATCHING_JOB_DB_ID), newOfferWaitingRecordList, + newWishlistWaitingRecordList); + }catch (Exception e){ + logger.error("Error occur when finding matches and push them to matched table: " +e.fillInStackTrace()); + } + + } + + + + + /** + * This function is to get the last pull time into a map, so the job knows the starting time range. + * If DB do not have the job record info, it will build a default with starting of time as last pull time + * @param lastPullTimeMap a map with job ids + */ + private void getLastPullTime(HashMap<Long, MatchingJobInfoModel> lastPullTimeMap){ + logger.info("Starting getLastPullTime function"); + for(Long jobId : lastPullTimeMap.keySet()){ + MatchingJobInfoModel jobInfo = matchingJobInfoRepository.findByJobId(jobId); + logger.info("Got job with id: {} info, the record is: {}", jobId, jobInfo); + if(jobInfo == null){ + logger.info("Offer job info is null, building initial offer job info record"); + jobInfo = new MatchingJobInfoModel(jobId,LookupTables.JOB_STATUS.Running, new Date(0)); + } + logger.info("The last pull time for job with id: {} is: {}", + jobId, DATE_FORMAT.format(jobInfo.getLastPullTime())); + jobInfo.setJobStatus(LookupTables.JOB_STATUS.Running); + jobInfo = matchingJobInfoRepository.save(jobInfo); + lastPullTimeMap.put(jobId,jobInfo); + } + } + + /** + * This function will pull the new Offer records from Offer_table and push them into offer_waiting_table + * @param offerJobRecord offer job information to start the task + */ + private List<OfferWaitingMatchModel> pushNewOfferRecordsIntoWaitingTable(MatchingJobInfoModel offerJobRecord){ + logger.info("Starting pushNewOfferRecordsIntoWaitingTable function with lastPullTime:{}" + ,DATE_FORMAT.format(offerJobRecord.getLastPullTime())); + + // update the job info record time + Date lastPullOfferTime = offerJobRecord.getLastPullTime(); + offerJobRecord.setJobStatus(LookupTables.JOB_STATUS.Done); + offerJobRecord.setLastPullTime(new Date(System.currentTimeMillis())); + + // fetch the Offer records and push them to waiting table + List<UserOfferModel> newOfferList = userOfferRepository + .findAllByOfferingAndModifyDateAfter(true, lastPullOfferTime); + logger.info("Fetched {} new Offer records, and set the last pull time to {}", newOfferList.size(), + DATE_FORMAT.format(offerJobRecord.getLastPullTime())); + + List<OfferWaitingMatchModel> newOfferWaitingRecordList = newOfferList.parallelStream().map(offerRecord-> + new OfferWaitingMatchModel(offerRecord.getUserId(), offerRecord.getAvailableTimeStart(), + offerRecord.getAvailableTimeEnd(), offerRecord.getStateCityCode())) + .toList(); + offerWaitingMatchRepository.saveAll(newOfferWaitingRecordList); + + matchingJobInfoRepository.save(offerJobRecord); + return newOfferWaitingRecordList; + } + + /** + * This function will pull the new wishlist records from wishlist_table and push them into wishlist_waiting_table + * @param wishlistJobInfo wishlist job information to start the task + */ + private List<WishlistWaitingMatchModel> pushNewWishlistItemRecordsIntoWaitingTable(MatchingJobInfoModel + wishlistJobInfo){ + logger.info("Starting pushNewWishlistItemRecordsIntoWaitingTable function with lastPullTime:{}" + ,DATE_FORMAT.format(wishlistJobInfo.getLastPullTime())); + + // update the job info record time + Date lastPullOfferTime = wishlistJobInfo.getLastPullTime(); + wishlistJobInfo.setJobStatus(LookupTables.JOB_STATUS.Done); + wishlistJobInfo.setLastPullTime(new Date(System.currentTimeMillis())); + + // fetch the Wishlist records and push them to waiting table + List<WishlistItemModel> newWishlistItemList = wishlistItemRepository + .findAllByModifyDateAfter(lastPullOfferTime); + logger.info("Fetched {} new Offer records, and set the last pull time to {}", newWishlistItemList.size(), + DATE_FORMAT.format(wishlistJobInfo.getLastPullTime())); + + List<WishlistWaitingMatchModel> newWishlistWaitingRecordList = newWishlistItemList + .parallelStream().map( newWishlistItem -> new WishlistWaitingMatchModel( + newWishlistItem.getWishlistItemId(), newWishlistItem.getStartTime() + , newWishlistItem.getEndTime(), newWishlistItem.getStateCityCode())) + .toList(); + wishlistWaitingMatchRepository.saveAll(newWishlistWaitingRecordList); + + matchingJobInfoRepository.save(wishlistJobInfo); + return newWishlistWaitingRecordList; + } + + /** + * This function will check records in the waiting table and try to match them. if match find, it will push them + * into matched table. + * @param matchJobRecord job info + */ + private void findMatchFromWaitingTable(MatchingJobInfoModel matchJobRecord, + List<OfferWaitingMatchModel> newOfferRecords, + List<WishlistWaitingMatchModel> newWishlistRecords){ + logger.info("Starting findMatchFromWaitingTable function with lastPullTime:{}" + ,DATE_FORMAT.format(matchJobRecord.getLastPullTime())); + // update the job info record time + Date lastPullOfferTime = matchJobRecord.getLastPullTime(); + matchJobRecord.setJobStatus(LookupTables.JOB_STATUS.Done); + matchJobRecord.setLastPullTime(new Date(System.currentTimeMillis())); + + // if the list is null, fetch new records + if(newOfferRecords == null) newOfferRecords = + offerWaitingMatchRepository.findAllByModifyDateAfter(lastPullOfferTime); + if(newWishlistRecords == null) newWishlistRecords = + wishlistWaitingMatchRepository.findAllByModifyDateAfter(lastPullOfferTime); + + // if one of them have new records + if(newOfferRecords.size() > 0 || newWishlistRecords.size() > 0){ + // load new Offers into map + + ConcurrentMap<Long, List<OfferWaitingMatchModel>> newStateCityCodeOfferMap = + loadOfferListIntoMap(newOfferRecords); + ConcurrentMap<Long, List<WishlistWaitingMatchModel>> newStateCityCodeWishlistMap = + loadWishlistListIntoMap(newWishlistRecords); + + // fetch both old records + List<OfferWaitingMatchModel> oldOfferRecords = + offerWaitingMatchRepository.findAllByModifyDateLessThanEqualAndStateCityCodeIn(lastPullOfferTime, + newStateCityCodeWishlistMap.keySet()); + List<WishlistWaitingMatchModel> oldWishlistRecords = + wishlistWaitingMatchRepository.findAllByModifyDateLessThanEqualAndStateCityCodeIn(lastPullOfferTime, + newStateCityCodeOfferMap.keySet()); + + // build maps by use the StateCityCode as the key so make the next pair step easier. + + ConcurrentMap<Long, List<OfferWaitingMatchModel>> oldStateCityCodeOfferMap = + loadOfferListIntoMap(oldOfferRecords); + ConcurrentMap<Long, List<WishlistWaitingMatchModel>> oldStateCityCodeWishlistMap = + loadWishlistListIntoMap(oldWishlistRecords); + + // after fetch all new Wishlist item records by unique stateCityCode + // match them with all Offer records + newStateCityCodeWishlistMap.entrySet() + .parallelStream() + .forEach(entry -> { + List<OfferWaitingMatchModel> newStateCityCodeOfferList = + !newStateCityCodeOfferMap.containsKey(entry.getKey())? new ArrayList<>() + : newStateCityCodeOfferMap.get(entry.getKey()); + List<OfferWaitingMatchModel> oldStateCityCodeOfferList = + !oldStateCityCodeOfferMap.containsKey(entry.getKey())? new ArrayList<>() + :oldStateCityCodeOfferMap.get(entry.getKey()); + List<OfferWaitingMatchModel> offerList = + Stream.concat(newStateCityCodeOfferList.stream(), + oldStateCityCodeOfferList.stream()).toList(); + findMatchedOfferForNewWishlistItemToDB(offerList, entry.getValue()); + }); + + + // If new offers and old Wishlist item records' unique stateCityCode has intersection, + // fetch old Wishlist item by new Offer stateCityCode + if(newOfferRecords.size() > 0){ + oldStateCityCodeWishlistMap.entrySet() + .parallelStream() + .forEach(entry ->{ + List<OfferWaitingMatchModel> newStateCityCodeOfferList = + !newStateCityCodeOfferMap.containsKey(entry.getKey())? new ArrayList<>() + : newStateCityCodeOfferMap.get(entry.getKey()); + findMatchedOfferForNewWishlistItemToDB(newStateCityCodeOfferList, + entry.getValue()); + }); + } + + } + + matchingJobInfoRepository.save(matchJobRecord); + } + + + private void findMatchedOfferForNewWishlistItemToDB(List<OfferWaitingMatchModel> offerList, + List<WishlistWaitingMatchModel> wishlistItemList) { + wishlistItemList.parallelStream().forEach(wishlistItem -> + useWishlistItemAndOfferListBuildMatchedRecordDB(wishlistItem, offerList) + ); + + } + + private void useWishlistItemAndOfferListBuildMatchedRecordDB(WishlistWaitingMatchModel wishlistItem, + List<OfferWaitingMatchModel> offerList) { + List<MatchedWishlistRecordModel> matchedRecordList = offerList.parallelStream() + .filter(offer-> isOverlapping(wishlistItem.getStartTime(),wishlistItem.getEndTime(), + offer.getStartTime(),offer.getEndTime())) + .map(offer -> new MatchedWishlistRecordModel(wishlistItem.getWishlistItemId(), + offer.getOfferId(), LookupTables.MATCHING_RECORD_USER_RESULT.Waiting, + LookupTables.MATCHING_RECORD_USER_RESULT.Waiting)) + .toList(); + + matchedWishlistRecordRepository.saveAll(matchedRecordList); + } + + private ConcurrentMap<Long, List<OfferWaitingMatchModel>> loadOfferListIntoMap( + List<OfferWaitingMatchModel> offerRecords){ + return offerRecords.parallelStream() + .collect(Collectors.groupingByConcurrent(OfferWaitingMatchModel::getStateCityCode)); + } + + private ConcurrentMap<Long, List<WishlistWaitingMatchModel>> loadWishlistListIntoMap( + List<WishlistWaitingMatchModel> wishlistRecords){ + return wishlistRecords.parallelStream() + .collect(Collectors.groupingByConcurrent(WishlistWaitingMatchModel::getStateCityCode)); + } + + private boolean isOverlapping(Date start1, Date end1, Date start2, Date end2) { + return start1.before(end2) && start2.before(end1); + } + +} +/* +Change logs: +Date | Author | Description +2022-10-30 | Fangzheng Zhang | implemented match service +2022-11-03 | Fangzheng Zhang | Optimised the code with parallel stream + */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/FlightResultModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/FlightResultModel.java new file mode 100644 index 0000000000000000000000000000000000000000..4a126aa2b41497e2dfacb747b7b6b796f61fa22a --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/FlightResultModel.java @@ -0,0 +1,192 @@ +package vt.CS5934.SwitchRoom.models; + +public class FlightResultModel { + + private Integer id; + // Travel detail + private String departure0; + private String arrival0; + private String departureDateTime_0; + private String arrivalDateTime_0; + private String classOfService_0; + private String aircraftType_0; + private Double distance_0; + + + // Return detail + private String departure_1; + private String arrival_1; + private String departureDateTime_1; + private String arrivalDateTime_1; + private String classOfService_1; + private String aircraftType_1; + private Double distance_1; + private Double price; + private String airlines; + private String detail_url; + + + public FlightResultModel(Integer id, String departure0, String arrival0, String departureDateTime_0, String arrivalDateTime_0, String classOfService_0, String aircraftType_0, Double distance_0, String departure_1, String arrival_1, String departureDateTime_1, String arrivalDateTime_1, String classOfService_1, String aircraftType_1, Double distance_1, Double price, String airlines, String detail_url) { + this.id = id; + this.departure0 = departure0; + this.arrival0 = arrival0; + this.departureDateTime_0 = departureDateTime_0; + this.arrivalDateTime_0 = arrivalDateTime_0; + this.classOfService_0 = classOfService_0; + this.aircraftType_0 = aircraftType_0; + this.distance_0 = distance_0; + this.departure_1 = departure_1; + this.arrival_1 = arrival_1; + this.departureDateTime_1 = departureDateTime_1; + this.arrivalDateTime_1 = arrivalDateTime_1; + this.classOfService_1 = classOfService_1; + this.aircraftType_1 = aircraftType_1; + this.distance_1 = distance_1; + this.price = price; + this.airlines = airlines; + this.detail_url = detail_url; + } + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getDeparture0() { + return departure0; + } + + public void setDeparture0(String departure0) { + this.departure0 = departure0; + } + + public String getArrival0() { + return arrival0; + } + + public void setArrival0(String arrival0) { + this.arrival0 = arrival0; + } + + public String getDepartureDateTime_0() { + return departureDateTime_0; + } + + public void setDepartureDateTime_0(String departureDateTime_0) { + this.departureDateTime_0 = departureDateTime_0; + } + + public String getArrivalDateTime_0() { + return arrivalDateTime_0; + } + + public void setArrivalDateTime_0(String arrivalDateTime_0) { + this.arrivalDateTime_0 = arrivalDateTime_0; + } + + public String getClassOfService_0() { + return classOfService_0; + } + + public void setClassOfService_0(String classOfService_0) { + this.classOfService_0 = classOfService_0; + } + + public String getAircraftType_0() { + return aircraftType_0; + } + + public void setAircraftType_0(String aircraftType_0) { + this.aircraftType_0 = aircraftType_0; + } + + public Double getDistance_0() { + return distance_0; + } + + public void setDistance_0(Double distance_0) { + this.distance_0 = distance_0; + } + + public String getDeparture_1() { + return departure_1; + } + + public void setDeparture_1(String departure_1) { + this.departure_1 = departure_1; + } + + public String getArrival_1() { + return arrival_1; + } + + public void setArrival_1(String arrival_1) { + this.arrival_1 = arrival_1; + } + + public String getDepartureDateTime_1() { + return departureDateTime_1; + } + + public void setDepartureDateTime_1(String departureDateTime_1) { + this.departureDateTime_1 = departureDateTime_1; + } + + public String getArrivalDateTime_1() { + return arrivalDateTime_1; + } + + public void setArrivalDateTime_1(String arrivalDateTime_1) { + this.arrivalDateTime_1 = arrivalDateTime_1; + } + + public String getClassOfService_1() { + return classOfService_1; + } + + public void setClassOfService_1(String classOfService_1) { + this.classOfService_1 = classOfService_1; + } + + public String getAircraftType_1() { + return aircraftType_1; + } + + public void setAircraftType_1(String aircraftType_1) { + this.aircraftType_1 = aircraftType_1; + } + + public Double getDistance_1() { + return distance_1; + } + + public void setDistance_1(Double distance_1) { + this.distance_1 = distance_1; + } + + public Double getPrice() { + return price; + } + + public void setPrice(Double price) { + this.price = price; + } + + public String getAirlines() { + return airlines; + } + + public void setAirlines(String airlines) { + this.airlines = airlines; + } + + public String getDetail_url() { + return detail_url; + } + + public void setDetail_url(String detail_url) { + this.detail_url = detail_url; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedRecordIdModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedRecordIdModel.java new file mode 100644 index 0000000000000000000000000000000000000000..95000dcef8c1f438deb8ff60931e079663b44e91 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedRecordIdModel.java @@ -0,0 +1,45 @@ +package vt.CS5934.SwitchRoom.models; + +import java.io.Serializable; +import java.util.Objects; + +public class MatchedRecordIdModel implements Serializable { + + private Long offerId; + private Long wishlistItemId; + + public MatchedRecordIdModel(){} + public MatchedRecordIdModel(Long offerId, Long wishlistItemId) { + this.offerId = offerId; + this.wishlistItemId = wishlistItemId; + } + + public Long getOfferId() { + return offerId; + } + + public void setOfferId(Long offerId) { + this.offerId = offerId; + } + + public Long getWishlistItemId() { + return wishlistItemId; + } + + public void setWishlistItemId(Long wishlistItemId) { + this.wishlistItemId = wishlistItemId; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MatchedRecordIdModel that = (MatchedRecordIdModel) o; + return Objects.equals(offerId, that.offerId) && Objects.equals(wishlistItemId, that.wishlistItemId); + } + + @Override + public int hashCode() { + return Objects.hash(offerId, wishlistItemId); + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedWishlistRecordModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedWishlistRecordModel.java new file mode 100644 index 0000000000000000000000000000000000000000..8d47292fea38e33f44c5df68d74a35d1548134a5 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchedWishlistRecordModel.java @@ -0,0 +1,73 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; +import vt.CS5934.SwitchRoom.utility.LookupTables; + +import javax.persistence.*; +import java.util.Date; + +@Entity +@Data +@Table(name = "matched_wishlist_record_table") +@IdClass(MatchedRecordIdModel.class) +@NoArgsConstructor +public class MatchedWishlistRecordModel { + @Id + private Long offerId; + @Id + private Long wishlistItemId; + private LookupTables.MATCHING_RECORD_USER_RESULT offerResult; + + private LookupTables.MATCHING_RECORD_USER_RESULT wishlistResult; + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private Date modifyDate; + + public MatchedWishlistRecordModel(Long wishlistItemId, Long offerId, + LookupTables.MATCHING_RECORD_USER_RESULT offerResult, + LookupTables.MATCHING_RECORD_USER_RESULT wishlistResult) { + this.wishlistItemId = wishlistItemId; + this.offerId = offerId; + this.offerResult = offerResult; + this.wishlistResult = wishlistResult; + } + + public Long getWishlistItemId() { + return wishlistItemId; + } + + public void setWishlistItemId(Long wishlistItemId) { + this.wishlistItemId = wishlistItemId; + } + + public Long getOfferId() { + return offerId; + } + + public void setOfferId(Long offerId) { + this.offerId = offerId; + } + + public LookupTables.MATCHING_RECORD_USER_RESULT getOfferResult() { + return offerResult; + } + + public void setOfferResult(LookupTables.MATCHING_RECORD_USER_RESULT offerResult) { + this.offerResult = offerResult; + } + + public LookupTables.MATCHING_RECORD_USER_RESULT getWishlistResult() { + return wishlistResult; + } + + public void setWishlistResult(LookupTables.MATCHING_RECORD_USER_RESULT wishlistResult) { + this.wishlistResult = wishlistResult; + } + + public Date getModifyDate() { + return modifyDate; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchingJobInfoModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchingJobInfoModel.java new file mode 100644 index 0000000000000000000000000000000000000000..0da2ead69bd8a2d72868b06bf1bb72dd96984e2a --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/MatchingJobInfoModel.java @@ -0,0 +1,60 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; +import vt.CS5934.SwitchRoom.utility.LookupTables; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "matching_job_info_table") +@NoArgsConstructor +public class MatchingJobInfoModel { + + @Id + private Long jobId; + private LookupTables.JOB_STATUS jobStatus; + private java.util.Date lastPullTime; + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private java.util.Date modifyDate; + + public MatchingJobInfoModel(Long jobId, LookupTables.JOB_STATUS jobStatus, Date lastPullTime) { + this.jobId = jobId; + this.jobStatus = jobStatus; + this.lastPullTime = lastPullTime; + } + + public Long getJobId() { + return jobId; + } + + public void setJobId(Long jobId) { + this.jobId = jobId; + } + + public LookupTables.JOB_STATUS getJobStatus() { + return jobStatus; + } + + public void setJobStatus(LookupTables.JOB_STATUS jobStatus) { + this.jobStatus = jobStatus; + } + + public java.util.Date getLastPullTime() { + return lastPullTime; + } + + public void setLastPullTime(java.util.Date lastStartTime) { + this.lastPullTime = lastStartTime; + } + + public java.util.Date getModifyDate() { + return modifyDate; + } + +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferMatchRequestInfo.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferMatchRequestInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..0a2378da9c1bb75d0f58229b0ce81e5ff9f00777 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferMatchRequestInfo.java @@ -0,0 +1,73 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; + +import java.util.Date; + +@Data +public class OfferMatchRequestInfo { + private Long offerOwnerId; + private Long wishListId; + private Date startTime; + private Date endTime; + private Integer zipCode; + private String hostName; + + public OfferMatchRequestInfo(Long offerOwnerId, Long wishListId, Date startTime, Date endTime, + Integer zipCode, String hostName) { + this.offerOwnerId = offerOwnerId; + this.wishListId = wishListId; + this.startTime = startTime; + this.endTime = endTime; + this.zipCode = zipCode; + this.hostName = hostName; + } + + public Long getOfferOwnerId() { + return offerOwnerId; + } + + public void setOfferOwnerId(Long offerOwnerId) { + this.offerOwnerId = offerOwnerId; + } + + public Long getWishListId() { + return wishListId; + } + + public void setWishListId(Long wishListId) { + this.wishListId = wishListId; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public Integer getZipCode() { + return zipCode; + } + + public void setZipCode(Integer zipCode) { + this.zipCode = zipCode; + } + + public String getHostName() { + return hostName; + } + + public void setHostName(String hostName) { + this.hostName = hostName; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferWaitingMatchModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferWaitingMatchModel.java new file mode 100644 index 0000000000000000000000000000000000000000..9d943f2e7d451c55942200feddabc11c58f6fb82 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/OfferWaitingMatchModel.java @@ -0,0 +1,68 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "offer_waiting_match_table") +@NoArgsConstructor +public class OfferWaitingMatchModel { + @Id + private Long offerId; + private Date startTime; + private Date endTime; + private Long stateCityCode; + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private java.util.Date modifyDate; + + public OfferWaitingMatchModel(Long offerId, Date startTime, Date endTime, Long stateCityCode) { + this.offerId = offerId; + this.startTime = startTime; + this.endTime = endTime; + this.stateCityCode = stateCityCode; + } + + public Long getOfferId() { + return offerId; + } + + public void setOfferId(Long offerId) { + this.offerId = offerId; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public Long getStateCityCode() { + return stateCityCode; + } + + public void setStateCityCode(Long stateCityCode) { + this.stateCityCode = stateCityCode; + } + + public java.util.Date getModifyDate() { + return modifyDate; + } + +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/SerachFlightModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/SerachFlightModel.java index dc7c1c4438f6fc9b45257a69cf8a0fa38f31b260..61f6277b29437d8bc90a5624a227457267b3183b 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/SerachFlightModel.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/SerachFlightModel.java @@ -1,6 +1,6 @@ package vt.CS5934.SwitchRoom.models; -import java.sql.Date; +import java.util.Date; public class SerachFlightModel { diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/StateCityLookupModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/StateCityLookupModel.java new file mode 100644 index 0000000000000000000000000000000000000000..b5b47449d446c021092817065cf3b8ad83aeff8c --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/StateCityLookupModel.java @@ -0,0 +1,48 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import javax.persistence.*; + +@Entity +@Data +@Table(name = "state_city_lookup_table") +@NoArgsConstructor +public class StateCityLookupModel { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long stateCityCode; + private Integer stateCode; + private String cityName; + + public StateCityLookupModel(Long stateCityCode, Integer stateCode, String cityName) { + this.stateCityCode = stateCityCode; + this.stateCode = stateCode; + this.cityName = cityName; + } + + public Long getStateCityCode() { + return stateCityCode; + } + + public void setStateCityCode(Long stateCityCode) { + this.stateCityCode = stateCityCode; + } + + public Integer getStateCode() { + return stateCode; + } + + public void setStateCode(Integer stateCode) { + this.stateCode = stateCode; + } + + public String getCityName() { + return cityName; + } + + public void setCityName(String cityName) { + this.cityName = cityName; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserModel.java index e0d652ea5876ae56f8bf206621a429ff484a040d..1e95cfc1a8f165e633924379e09e09d4e01d0b5a 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserModel.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserModel.java @@ -39,14 +39,16 @@ public class UserModel { @Column(name="gender") private String gender; + private String token; - public UserModel(String name, String password, String email, String firstname, String lastname, String gender) { + public UserModel(String name, String password, String email, String firstname, String lastname, String gender, String token) { this.username = name; this.password = password; this.email = email; this.firstname = firstname; this.lastname = lastname; this.gender = gender; + this.token = token; } @Override @@ -59,6 +61,7 @@ public class UserModel { ", firstname='" + firstname + '\'' + ", lastname='" + lastname + '\'' + ", gender='" + gender + '\'' + + ", token='" + token + '\'' + '}'; } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferModel.java index 96f6f91aea6761d5277c2e2eddb2d8838a9e8a41..ddd98bd46f1be4dca460a4a9b0c3fbeb3f1c451c 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferModel.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferModel.java @@ -2,9 +2,10 @@ package vt.CS5934.SwitchRoom.models; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; import javax.persistence.*; -import java.sql.Date; +import java.util.Date; @Data @Entity @@ -14,27 +15,38 @@ public class UserOfferModel { @Id private Long userId; + private String state; + private Integer stateCode; private Integer zipCode; private String spaceType; private String otherSpaceType; private String spaceLocateCity; - private Long cityCode; + private Long stateCityCode; private Date availableTimeStart; private Date availableTimeEnd; private Integer maxNumberOfPeople; + @Column(length = 500) private String spaceDetails; private Boolean offering; - public UserOfferModel(Long userId, Integer zipCode, String spaceType, + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private java.util.Date modifyDate; + + + public UserOfferModel(Long userId, String state, Integer stateCode, Integer zipCode, String spaceType, String otherSpaceType, String spaceLocateCity, Long cityCode, Date availableTimeStart, Date availableTimeEnd, Integer maxNumberOfPeople, String spaceDetails, Boolean offering) { this.userId = userId; + this.state = state; + this.stateCode = stateCode; this.zipCode = zipCode; this.spaceType = spaceType; this.otherSpaceType = otherSpaceType; this.spaceLocateCity = spaceLocateCity; - this.cityCode = cityCode; + this.stateCityCode = cityCode; this.availableTimeStart = availableTimeStart; this.availableTimeEnd = availableTimeEnd; this.maxNumberOfPeople = maxNumberOfPeople; @@ -50,6 +62,22 @@ public class UserOfferModel { this.userId = userId; } + public String getState() { + return state; + } + + public void setState(String state) { + this.state = state; + } + + public Integer getStateCode() { + return stateCode; + } + + public void setStateCode(Integer stateCode) { + this.stateCode = stateCode; + } + public Integer getZipCode() { return zipCode; } @@ -82,12 +110,12 @@ public class UserOfferModel { this.spaceLocateCity = spaceLocateCity; } - public Long getCityCode() { - return cityCode; + public Long getStateCityCode() { + return stateCityCode; } - public void setCityCode(Long cityCode) { - this.cityCode = cityCode; + public void setStateCityCode(Long cityCode) { + this.stateCityCode = cityCode; } public Date getAvailableTimeStart() { @@ -135,4 +163,5 @@ public class UserOfferModel { Change logs: Date | Author | Description 2022-10-20 | Fangzheng Zhang | create class and init +2022-10-30 | Fangzheng Zhang | Add modify_date into DB */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferWishlistLookUpModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferWishlistLookUpModel.java index 91cd389ba5e134c9e863de939d564b2013ee2369..fe3ea6401ab5c8380a971cb2428aa522ff57bc80 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferWishlistLookUpModel.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/UserOfferWishlistLookUpModel.java @@ -5,11 +5,10 @@ package vt.CS5934.SwitchRoom.models; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.Id; -import javax.persistence.Table; +import javax.persistence.*; +import java.util.Date; @Entity @Data @@ -20,12 +19,15 @@ public class UserOfferWishlistLookUpModel { @Id private Long wishlistItemId; private Long userId; - private Long matchingOfferId; - public UserOfferWishlistLookUpModel(Long userId, Long wishlistItemId, Long matchingOfferId) { + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private Date modifyDate; + + public UserOfferWishlistLookUpModel(Long userId, Long wishlistItemId) { this.userId = userId; this.wishlistItemId = wishlistItemId; - this.matchingOfferId = matchingOfferId; } public Long getUserId() { @@ -44,18 +46,11 @@ public class UserOfferWishlistLookUpModel { this.wishlistItemId = wishlistId; } - public Long getMatchingOfferId() { - return matchingOfferId; - } - - public void setMatchingOfferId(Long matchingOfferId) { - this.matchingOfferId = matchingOfferId; - } } /* Change logs: Date | Author | Description 2022-10-21 | Fangzheng Zhang | create class and init - +2022-10-30 | Fangzheng Zhang | Add modify_date into DB. Remove matchingOfferId, move it to a match result class */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistItemModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistItemModel.java index feee5ded6e75893f608b0733e7b08f43e7918f8b..a361cb6046362e48436f761fe0cdb95dd0a42afd 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistItemModel.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistItemModel.java @@ -5,9 +5,10 @@ package vt.CS5934.SwitchRoom.models; import lombok.Data; import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; import javax.persistence.*; -import java.sql.Date; +import java.util.Date; @Entity @Data @@ -18,17 +19,25 @@ public class WishlistItemModel { @GeneratedValue(strategy = GenerationType.IDENTITY) private Long wishlistItemId; private String cityName; + private Long stateCityCode; private Integer zipCode; private String state; private Integer stateCode; private Date startTime; private Date endTime; + @Column(length = 500) private String details; - public WishlistItemModel(Long wishlistItemId, String cityName, String state, Integer stateCode, + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private java.util.Date modifyDate; + + public WishlistItemModel(Long wishlistItemId, String cityName, Long stateCityCode, String state, Integer stateCode, Date startTime, Date endTime, String details, Integer zipCode) { this.wishlistItemId = wishlistItemId; this.cityName = cityName; + this.stateCityCode = stateCityCode; this.zipCode = zipCode; this.state = state; this.stateCode = stateCode; @@ -37,9 +46,10 @@ public class WishlistItemModel { this.details = details; } - public WishlistItemModel(String cityName, String state, Integer stateCode, Date startTime, + public WishlistItemModel(String cityName, Long stateCityCode, String state, Integer stateCode, Date startTime, Date endTime, String details) { this.cityName = cityName; + this.stateCityCode = stateCityCode; this.state = state; this.stateCode = stateCode; this.startTime = startTime; @@ -50,6 +60,15 @@ public class WishlistItemModel { public Long getWishlistItemId() { return wishlistItemId; } + + public Long getStateCityCode() { + return stateCityCode; + } + + public void setStateCityCode(Long stateCityCode) { + this.stateCityCode = stateCityCode; + } + public Integer getZipCode() { return zipCode; } @@ -115,5 +134,6 @@ public class WishlistItemModel { Change logs: Date | Author | Description 2022-10-21 | Fangzheng Zhang | create class and init +2022-10-30 | Fangzheng Zhang | Add modify_date into DB */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistMatchRequestInfo.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistMatchRequestInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..3cacc490f40feac3566ad443d9f55b6b4cdfcfdb --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistMatchRequestInfo.java @@ -0,0 +1,83 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; + +import java.util.Date; + +@Data +public class WishlistMatchRequestInfo { + private Long wishlistOwnerId; + private Long wishlistId; + private Date startTime; + private Date endTime; + private String wishListOwnerName; + private Boolean hasOffer; + private Integer zipCode; + + public WishlistMatchRequestInfo(Long wishlistOwnerId, Long wishlistId, Date startTime, Date endTime, + String wishListOwnerName, Boolean hasOffer, Integer zipCode) { + this.wishlistOwnerId = wishlistOwnerId; + this.wishlistId = wishlistId; + this.startTime = startTime; + this.endTime = endTime; + this.wishListOwnerName = wishListOwnerName; + this.hasOffer = hasOffer; + this.zipCode = zipCode; + } + + public Long getWishlistOwnerId() { + return wishlistOwnerId; + } + + public void setWishlistOwnerId(Long wishlistOwnerId) { + this.wishlistOwnerId = wishlistOwnerId; + } + + public Long getWishlistId() { + return wishlistId; + } + + public void setWishlistId(Long wishlistId) { + this.wishlistId = wishlistId; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public String getWishListOwnerName() { + return wishListOwnerName; + } + + public void setWishListOwnerName(String wishListOwnerName) { + this.wishListOwnerName = wishListOwnerName; + } + + public Boolean getHasOffer() { + return hasOffer; + } + + public void setHasOffer(Boolean hasOffer) { + this.hasOffer = hasOffer; + } + + public Integer getZipCode() { + return zipCode; + } + + public void setZipCode(Integer zipCode) { + this.zipCode = zipCode; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistWaitingMatchModel.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistWaitingMatchModel.java new file mode 100644 index 0000000000000000000000000000000000000000..68a01271b0b73dcb5dc0eca50a739ab4dd16e6a4 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/models/WishlistWaitingMatchModel.java @@ -0,0 +1,68 @@ +package vt.CS5934.SwitchRoom.models; + +import lombok.Data; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.UpdateTimestamp; + +import javax.persistence.*; +import java.util.Date; + +@Data +@Entity +@Table(name = "wishlist_waiting_match_table") +@NoArgsConstructor +public class WishlistWaitingMatchModel { + + @Id + private Long wishlistItemId; + private Date startTime; + private Date endTime; + private Long stateCityCode; + @UpdateTimestamp + @Temporal(TemporalType.TIMESTAMP) + @Column(name = "modify_date") + private java.util.Date modifyDate; + + public WishlistWaitingMatchModel(Long wishlistItemId, Date startTime, Date endTime, Long stateCityCode) { + this.wishlistItemId = wishlistItemId; + this.startTime = startTime; + this.endTime = endTime; + this.stateCityCode = stateCityCode; + } + + public Long getWishlistItemId() { + return wishlistItemId; + } + + public void setWishlistItemId(Long wishlistItemId) { + this.wishlistItemId = wishlistItemId; + } + + public Date getStartTime() { + return startTime; + } + + public void setStartTime(Date startTime) { + this.startTime = startTime; + } + + public Date getEndTime() { + return endTime; + } + + public void setEndTime(Date endTime) { + this.endTime = endTime; + } + + public Long getStateCityCode() { + return stateCityCode; + } + + public void setStateCityCode(Long stateCityCode) { + this.stateCityCode = stateCityCode; + } + + public java.util.Date getModifyDate() { + return modifyDate; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchedWishlistRecordRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchedWishlistRecordRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e9ef530a764129d691d31f707e55f231040cd650 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchedWishlistRecordRepository.java @@ -0,0 +1,30 @@ +package vt.CS5934.SwitchRoom.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import vt.CS5934.SwitchRoom.models.MatchedRecordIdModel; +import vt.CS5934.SwitchRoom.models.MatchedWishlistRecordModel; + +import java.util.List; + +public interface MatchedWishlistRecordRepository extends JpaRepository<MatchedWishlistRecordModel, MatchedRecordIdModel> { + /** + * The function name is the SQL query: + * findByIdAndName(long inputId, String inputName) == "SELETE * FROM table WHERE Id==inputId" AND name == inputName; + * This is an example of how to declare a SQL command, those will be use in service class + * @param offerId the id in table you are looking for + * @return ExampleModel object + */ + List<MatchedWishlistRecordModel> findAllByOfferId(Long offerId); + /** + * The function name is the SQL query: + * findByIdAndName(long inputId, String inputName) == "SELETE * FROM table WHERE Id==inputId" AND name == inputName; + * This is an example of how to declare a SQL command, those will be use in service class + * @param wishlistItemId the id in table you are looking for + * @return ExampleModel object + */ + List<MatchedWishlistRecordModel> findAllByWishlistItemId(Long wishlistItemId); + + void deleteAllByOfferId(Long offerId); + void deleteAllByWishlistItemId(Long wishlistItemId); + Long countByWishlistItemId(Long wishlistItemId); +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchingJobInfoRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchingJobInfoRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e85b9e57e39b0572d4d481da5af1ba46bdc06178 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/MatchingJobInfoRepository.java @@ -0,0 +1,8 @@ +package vt.CS5934.SwitchRoom.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import vt.CS5934.SwitchRoom.models.MatchingJobInfoModel; + +public interface MatchingJobInfoRepository extends JpaRepository<MatchingJobInfoModel, Long> { + MatchingJobInfoModel findByJobId(Long jobId); +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/OfferWaitingMatchRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/OfferWaitingMatchRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..3672521aa5dcecbff18e9a8b48dc4de9c1e385b4 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/OfferWaitingMatchRepository.java @@ -0,0 +1,20 @@ +package vt.CS5934.SwitchRoom.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import vt.CS5934.SwitchRoom.models.OfferWaitingMatchModel; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +public interface OfferWaitingMatchRepository extends JpaRepository<OfferWaitingMatchModel, Long> { + List<OfferWaitingMatchModel> findAllByStateCityCodeAndModifyDateAfter(Long stateCityCode, Date lastPullTime); + @Query() + List<OfferWaitingMatchModel> findAllByStateCityCodeAndModifyDateBefore(Long stateCityCode, Date lastPullTime); + + List<OfferWaitingMatchModel> findAllByModifyDateAfter(Date lastPullTime); + List<OfferWaitingMatchModel> findAllByModifyDateLessThanEqualAndStateCityCodeIn(Date lastPullTime, Set<Long> StateCityCodeSet); + void deleteAllByOfferId(Long offerId); + +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/StateCityLookupRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/StateCityLookupRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..e909f2fcb69f182a88cdf933d63e4c795f3558a0 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/StateCityLookupRepository.java @@ -0,0 +1,9 @@ +package vt.CS5934.SwitchRoom.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import vt.CS5934.SwitchRoom.models.StateCityLookupModel; + +public interface StateCityLookupRepository extends JpaRepository<StateCityLookupModel, Long> { + StateCityLookupModel findByCityNameAndStateCode(String cityName, Integer stateCode); + StateCityLookupModel findByStateCityCode(Long stateCityCode); +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferRepository.java index 24fb209ea24cd0018f81f7edad6dde4e6ad08887..4550399df11ef2e44c6b12d8c297ca11b4c2d566 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferRepository.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferRepository.java @@ -3,8 +3,13 @@ package vt.CS5934.SwitchRoom.repositories; import org.springframework.data.jpa.repository.JpaRepository; import vt.CS5934.SwitchRoom.models.UserOfferModel; +import javax.xml.crypto.Data; +import java.util.Date; +import java.util.List; + public interface UserOfferRepository extends JpaRepository<UserOfferModel, Long> { UserOfferModel findByUserId(Long userId); void deleteByUserId(Long userId); + List<UserOfferModel> findAllByOfferingAndModifyDateAfter(Boolean offering, Date modifyDate); } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferWishlistLookUpRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferWishlistLookUpRepository.java index fd79c1f06b7ef6d65781d609f9d7104002356b7d..02be9157138c577264aaeca9fca8a2d1be1fd4c0 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferWishlistLookUpRepository.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserOfferWishlistLookUpRepository.java @@ -8,8 +8,7 @@ import java.util.List; public interface UserOfferWishlistLookUpRepository extends JpaRepository<UserOfferWishlistLookUpModel, Long> { List<UserOfferWishlistLookUpModel> findAllByUserId(Long userId); - UserOfferModel findByWishlistItemId(Long wishlistItemId); - List<UserOfferModel> findAllByMatchingOfferId(Long matchingOfferId); + UserOfferWishlistLookUpModel findByWishlistItemId(Long wishlistItemId); void deleteByWishlistItemId(Long wishlistItemId); } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserRepository.java index 665888482bf64f4bb2821ae937ce88f8b2876653..a57cb14b937ab5511d02dfa7c04891ea8206cdfd 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserRepository.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/UserRepository.java @@ -20,11 +20,11 @@ public interface UserRepository extends JpaRepository<UserModel, Integer> { * @param userId the id in table you are looking for * @return ExampleModel object */ - UserModel findByUserId(long userId); + UserModel findByUserId(int userId); UserModel findByUsername(String username); List<UserModel> findAll(); - void deleteByUserId(long userId); + void deleteByUserId(Long userId); } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistItemRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistItemRepository.java index 5c89d1bd302177f28d3ebf869820ad5ccc75850c..a1ff688424d6b92b2931cb87b6d7c208264f4b41 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistItemRepository.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistItemRepository.java @@ -3,8 +3,12 @@ package vt.CS5934.SwitchRoom.repositories; import org.springframework.data.jpa.repository.JpaRepository; import vt.CS5934.SwitchRoom.models.WishlistItemModel; +import java.util.Date; +import java.util.List; + public interface WishlistItemRepository extends JpaRepository<WishlistItemModel, Long> { WishlistItemModel findByWishlistItemId(Long wishlistItemId); void deleteByWishlistItemId(Long wishlistItemId); + List<WishlistItemModel> findAllByModifyDateAfter(Date modifyDate); } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistWaitingMatchRepository.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistWaitingMatchRepository.java new file mode 100644 index 0000000000000000000000000000000000000000..ec4027858c2e4e50c5514a09df143081ba03f6d1 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/repositories/WishlistWaitingMatchRepository.java @@ -0,0 +1,23 @@ +package vt.CS5934.SwitchRoom.repositories; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import vt.CS5934.SwitchRoom.models.WishlistWaitingMatchModel; + +import java.util.Date; +import java.util.List; +import java.util.Set; + +public interface WishlistWaitingMatchRepository extends JpaRepository<WishlistWaitingMatchModel, Long> { + @Query("SELECT DISTINCT item.stateCityCode FROM WishlistWaitingMatchModel item WHERE item.modifyDate > ?1") + List<Long> findAllUniqueStateCityCodeAfter(Date lastPullTime); + + @Query("SELECT DISTINCT item.stateCityCode FROM WishlistWaitingMatchModel item WHERE item.modifyDate <= ?1") + List<Long> findAllUniqueStateCityCodeBefore(Date lastPullTime); + + List<WishlistWaitingMatchModel> findAllByModifyDateAfter(Date lastPullTime); + List<WishlistWaitingMatchModel> findAllByModifyDateLessThanEqualAndStateCityCodeIn(Date lastPullTime, Set<Long> stateCityCodeSet); + + void deleteAllByWishlistItemId(Long wishlistItemId); + +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/DealWithWishlist.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/DealWithWishlist.java new file mode 100644 index 0000000000000000000000000000000000000000..247b50dfdce25a6f4e50ab124416eb22ade7813a --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/DealWithWishlist.java @@ -0,0 +1,37 @@ +package vt.CS5934.SwitchRoom.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import vt.CS5934.SwitchRoom.models.MatchedWishlistRecordModel; +import vt.CS5934.SwitchRoom.models.UserOfferWishlistLookUpModel; +import vt.CS5934.SwitchRoom.repositories.MatchedWishlistRecordRepository; +import vt.CS5934.SwitchRoom.repositories.UserOfferWishlistLookUpRepository; + +import java.util.ArrayList; +import java.util.List; + +@Service +public class DealWithWishlist { + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(ExampleService.class); + + @Autowired + MatchedWishlistRecordRepository matchedWishlistRecordRepository; + + public List<MatchedWishlistRecordModel> getMatchedWishlist(List<UserOfferWishlistLookUpModel> list) { + logger.info("Reached get finalAns"); + List<MatchedWishlistRecordModel> finalAns = new ArrayList<>(); + for(int i = 0; i < list.size(); i++) { + System.out.println(list.get(i).getWishlistItemId()); + List<MatchedWishlistRecordModel> ans = matchedWishlistRecordRepository.findAllByWishlistItemId(list.get(i).getWishlistItemId()); + System.out.println(ans); + finalAns.addAll(ans); + } + return finalAns; + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/FlightApiService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/FlightApiService.java new file mode 100644 index 0000000000000000000000000000000000000000..90d04b5c97d4d3bc4b1520ef8f2bc58806f9026f --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/FlightApiService.java @@ -0,0 +1,253 @@ +package vt.CS5934.SwitchRoom.services; + +import org.springframework.stereotype.Service; +import vt.CS5934.SwitchRoom.models.FlightResultModel; +import vt.CS5934.SwitchRoom.models.SerachFlightModel; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import java.io.Serializable; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +import org.springframework.boot.configurationprocessor.json.JSONArray; +import org.springframework.boot.configurationprocessor.json.JSONObject; +@Service +public class FlightApiService implements Serializable { + private List<FlightResultModel> listOfFlightResults; + + public List<FlightResultModel> getListOfSearchedFlights() { + return listOfFlightResults; + } + + public void setListOfSearchedFlights(List<FlightResultModel> listOfFlightResults) { + this.listOfFlightResults = listOfFlightResults; + } + private final Logger logger = LoggerFactory.getLogger(FlightApiService.class); + public List<FlightResultModel> performSearch(SerachFlightModel newFlight) { + + listOfFlightResults = new ArrayList<>(); + + try { +// String URI = "https://tripadvisor16.p.rapidapi.com/api/v1/flights/searchFlights?" + +// "sourceAirportCode=" + upperCase(newFlight.getDeparture()) + "&destinationAirportCode=" +// + upperCase(newFlight.getDestination()) + "&date=" + convertDate1(newFlight.getDate1()) + "&itineraryType=ROUND_TRIP&sortOrder=PRICE&numAdults=" + newFlight.getNumAdults() + "&numSeniors=" + newFlight.getNumSeniors() + "&classOfService=" + newFlight.getClassOfService() + "&returnDate=" + convertDate1(newFlight.getDate2()) + "¤cyCode=USD"; +// System.out.println(URI); +// HttpRequest request = HttpRequest.newBuilder() +// .uri(java.net.URI.create(URI)) +// .header("X-RapidAPI-Key", "ccf2ddc79amsh1e381193b5f63a9p179bfdjsn9ed1d6b269f7") +// .header("X-RapidAPI-Host", "tripadvisor16.p.rapidapi.com") +// .method("GET", HttpRequest.BodyPublishers.noBody()) +// .build(); +// HttpResponse<String> response = HttpClient.newHttpClient().send(request, HttpResponse.BodyHandlers.ofString()); +// JSONObject searchResultsJsonObject = new JSONObject(response.body()); + + String file = "flightapi.json"; + String json = readFileAsString(file); + JSONObject searchResultsJsonObject = new JSONObject(json); + + JSONObject dataJsonObject = searchResultsJsonObject.getJSONObject("data"); + JSONArray flightJsonArray = dataJsonObject.getJSONArray("flights"); + + + int index = 0; + if (flightJsonArray.length() > index) { + while (flightJsonArray.length() > index) { + JSONObject flightObject = flightJsonArray.getJSONObject(index); + + JSONArray segmentsJSONArray = flightObject.getJSONArray("segments"); + + // ************* travel ************** + JSONObject firstObject = segmentsJSONArray.getJSONObject(0); + JSONArray legsObject = firstObject.getJSONArray("legs"); + JSONObject TravelObject = legsObject.getJSONObject(0); + + JSONObject secondObject = segmentsJSONArray.getJSONObject(1); + JSONArray legsObj = secondObject.getJSONArray("legs"); + JSONObject ReturnObject = legsObj.getJSONObject(0); + + JSONArray purchaseLinksJSONArray = flightObject.getJSONArray("purchaseLinks"); + JSONObject purchaseObject = purchaseLinksJSONArray.getJSONObject(0); + + String departure_0 = TravelObject.optString("originStationCode", ""); + if (departure_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String arrival_0 = TravelObject.optString("destinationStationCode", ""); + if (arrival_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String departureDateTime_0 = TravelObject.optString("departureDateTime", ""); + if (departureDateTime_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String arrivalDateTime_0 = TravelObject.optString("arrivalDateTime", ""); + if (arrivalDateTime_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + + String classOfService_0 = TravelObject.optString("classOfService", ""); + if (classOfService_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String aircraftType_0 = TravelObject.optString("equipmentId", ""); + if (aircraftType_0.equals("")) { + index++; + continue; // Jump to the next iteration + } + + Double distance_0 = TravelObject.optDouble("distanceInKM", 0.0); + if (distance_0 == 0.0) { + index++; + continue; // Jump to the next iteration + }else { + /* Round the calories value to 2 decimal places */ + distance_0 = distance_0 * 100; + distance_0 = (double) Math.round(distance_0); + distance_0 = distance_0 / 100; + } + + // ************* return ************** +// JSONObject secondObject = segmentsJSONArray.getJSONObject(1); +// JSONArray legsObj = secondObject.getJSONArray("legs"); +// JSONObject ReturnObject = legsObj.getJSONObject(0); + + + String departure_1 = ReturnObject.optString("originStationCode", ""); + if (departure_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String arrival_1 = ReturnObject.optString("destinationStationCode", ""); + if (arrival_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String departureDateTime_1 = ReturnObject.optString("departureDateTime", ""); + if (departureDateTime_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String arrivalDateTime_1 = ReturnObject.optString("arrivalDateTime", ""); + if (arrivalDateTime_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + + String classOfService_1 = ReturnObject.optString("classOfService", ""); + if (classOfService_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + String aircraftType_1 = ReturnObject.optString("equipmentId", ""); + if (aircraftType_1.equals("")) { + index++; + continue; // Jump to the next iteration + } + + + Double distance_1 = ReturnObject.optDouble("distanceInKM", 0.0); + if (distance_1 == 0.0) { + index++; + continue; // Jump to the next iteration + }else { + /* Round the calories value to 2 decimal places */ + distance_1 = distance_1 * 100; + distance_1 = (double) Math.round(distance_1); + distance_1 = distance_1 / 100; + } + +// JSONArray purchaseLinksJSONArray = flightObject.getJSONArray("purchaseLinks"); +// JSONObject purchaseObject = purchaseLinksJSONArray.getJSONObject(0); + + String airline = purchaseObject.optString("providerId", ""); + if (airline.equals("")) { + index++; + continue; // Jump to the next iteration + } + + Double price = purchaseObject.optDouble("totalPrice", 0.0); + if (price == 0.0) { + index++; + continue; // Jump to the next iteration + + }else { + /* Round the calories value to 2 decimal places */ + price = price * 100; + price = (double) Math.round(price); + price = price / 100; + } + + String url = purchaseObject.optString("url", ""); + if (url.equals("")) { + index++; + continue; // Jump to the next iteration + } + + FlightResultModel flight = new FlightResultModel(index,departure_0,arrival_0,convertDate(departureDateTime_0),convertDate(arrivalDateTime_0),classOfService_0,aircraftType_0,distance_0,departure_1,arrival_1,convertDate(departureDateTime_1),convertDate(arrivalDateTime_1),classOfService_1,aircraftType_1,distance_1,price,airline,url); +// System.out.println(flight); + listOfFlightResults.add(flight); + index++; + + } + } + else { + logger.error("Error in getFlightApiService: "); + } + }catch (Exception ex){ + logger.error("Error in getFlightApiService: "+ex); + } + return listOfFlightResults; + } + public static String upperCase(String str) { + if (str == null) { + return null; + } + return str.toUpperCase(); + } + public static String convertDate(String d) { + try { + //2022-12-01T06:30:00+05:30 + SimpleDateFormat oldDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ssXXX"); + Date date = oldDateFormat.parse(d); + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mmaaa"); + return simpleDateFormat.format(date); + } + catch (ParseException exc) { + return "N/A"; + } + } + + public static String convertDate1(Date d) { + SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd"); + return simpleDateFormat.format(d); + } + public static String readFileAsString(String file)throws Exception + { + return new String(Files.readAllBytes(Paths.get(file))); + } +} + diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/MatchedWishlistRecordService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/MatchedWishlistRecordService.java new file mode 100644 index 0000000000000000000000000000000000000000..8840f63b35e7063bca9e7759f319825795d5bc08 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/MatchedWishlistRecordService.java @@ -0,0 +1,31 @@ +package vt.CS5934.SwitchRoom.services; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import vt.CS5934.SwitchRoom.models.MatchedWishlistRecordModel; +import vt.CS5934.SwitchRoom.repositories.MatchedWishlistRecordRepository; + +import java.util.List; + +@Service +public class MatchedWishlistRecordService { + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(MatchedWishlistRecordService.class); + + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + MatchedWishlistRecordRepository matchedWishlistRecordRepository; + + public List<MatchedWishlistRecordModel> getOfferListWithIDFromDB(long id){ + logger.info("Reached getOfferListIDFromDB()"); + return matchedWishlistRecordRepository.findAllByOfferId(id); + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferPageService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferPageService.java index f40cc76e4bc8d66b951ffe06c16b2160de621dbd..a1501db98129ccd5802277ba5967435dd0c4c8dd 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferPageService.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferPageService.java @@ -4,8 +4,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; -import vt.CS5934.SwitchRoom.models.UserOfferModel; -import vt.CS5934.SwitchRoom.repositories.UserOfferRepository; +import org.springframework.transaction.annotation.Transactional; +import vt.CS5934.SwitchRoom.models.*; +import vt.CS5934.SwitchRoom.repositories.*; +import vt.CS5934.SwitchRoom.utility.StateCodeMap; + +import java.util.ArrayList; +import java.util.List; @Service public class OfferPageService { @@ -13,6 +18,18 @@ public class OfferPageService { @Autowired UserOfferRepository userOfferRepository; + @Autowired + StateCityLookupRepository stateCityLookupRepository; + @Autowired + MatchedWishlistRecordRepository matchedWishlistRecordRepository; + @Autowired + OfferWaitingMatchRepository offerWaitingMatchRepository; + @Autowired + UserOfferWishlistLookUpRepository userOfferWishlistLookUpRepository; + @Autowired + UserRepository userRepository; + @Autowired + WishlistItemRepository wishlistItemRepository; public UserOfferModel getUserOfferInfo(Long userId) { UserOfferModel userOfferModel = userOfferRepository.findByUserId(userId); @@ -25,10 +42,82 @@ public class OfferPageService { public UserOfferModel saveNewUserOffer(UserOfferModel offerModel) { + fillStateCodeAndStateCityCode(offerModel); return userOfferRepository.save(offerModel); } + @Transactional + public void removeOfferFromMatchedTable (Long offerId){ + logger.info("Removing all records related to offerId: {} from matched table.", offerId); + matchedWishlistRecordRepository.deleteAllByOfferId(offerId); + } + @Transactional public void deleteUserOffer(Long userId) { + logger.info("Removing all records related to offerId: {} from offer waiting table.", userId); userOfferRepository.deleteByUserId(userId); + offerWaitingMatchRepository.deleteAllByOfferId(userId); + removeOfferFromMatchedTable(userId); + } + + + + + /** + * This function will take a UserOfferModel. it will fill the StateCode with it first, + * then check and update the StateCityCode. If DB do not have the StateCityCode, then build one and fill it in. + * @param offerModel the new or updated offerModel + */ + private void fillStateCodeAndStateCityCode(UserOfferModel offerModel){ + + // Fill state code in the record + offerModel.setStateCode(StateCodeMap.StringToCodeMap.get(offerModel.getState())); + + // Check if the DB have the state city code or not, if not build one + StateCityLookupModel stateCityLookupModel = stateCityLookupRepository + .findByCityNameAndStateCode(offerModel.getSpaceLocateCity(), offerModel.getStateCode()); + + if(stateCityLookupModel == null){ + // not in DB yet + stateCityLookupModel = stateCityLookupRepository.save( + new StateCityLookupModel(null, offerModel.getStateCode(), + offerModel.getSpaceLocateCity())); + } + offerModel.setStateCityCode(stateCityLookupModel.getStateCityCode()); + } + + /** + * This service will fetch information from many table to get the WishlistMatchRequestInfo, so we can display it in + * the offer page + * @param userId The id of user who opened the offer page + * @return a list of WishlistMatchRequestInfo. + */ + public List<WishlistMatchRequestInfo> getWishlistMatchRequestInfoList(Long userId) { + List<WishlistMatchRequestInfo> wishlistMatchRequestInfoList = new ArrayList<>(); + List<MatchedWishlistRecordModel> matchedRecordList = matchedWishlistRecordRepository.findAllByOfferId(userId); + for(MatchedWishlistRecordModel matchedRecord : matchedRecordList){ + Long requestedWishlistId = matchedRecord.getWishlistItemId(); + Long wishlistOwnerId = userOfferWishlistLookUpRepository + .findByWishlistItemId(requestedWishlistId).getUserId(); + // get user information from userRepository, get wishlistItem information from wishlistItem table, + // check if the wishlistItem owner has Offer or not, if does, find zipcode. + UserModel wishlistOwnerInfo = userRepository.findByUserId(wishlistOwnerId.intValue()); + WishlistItemModel wishlistItem = wishlistItemRepository.findByWishlistItemId(requestedWishlistId); + UserOfferModel wishlistOwnerOfferModel = userOfferRepository.findByUserId(wishlistOwnerId); + WishlistMatchRequestInfo wishlistMatchRequestInfo = new WishlistMatchRequestInfo(wishlistOwnerId, + requestedWishlistId, wishlistItem.getStartTime(), wishlistItem.getEndTime(), + wishlistOwnerInfo.getFirstname() + " " + wishlistOwnerInfo.getLastname(), + wishlistOwnerOfferModel != null && wishlistOwnerOfferModel.getOffering(), + wishlistOwnerOfferModel == null ? null : wishlistOwnerOfferModel.getZipCode()); + wishlistMatchRequestInfoList.add(wishlistMatchRequestInfo); + } + return wishlistMatchRequestInfoList; } } +/* +Change logs: +Date | Author | Description +2022-10-21 | Fangzheng Zhang | create class and init +2022-10-30 | Fangzheng Zhang | implement state city code feature +2022-11-03 | Fangzheng Zhang | Added update and remove handling for matching waiting table and matched table + + */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferWishlistLookUpService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferWishlistLookUpService.java new file mode 100644 index 0000000000000000000000000000000000000000..44bbe0768a9fe9e1cac07245b587e84c8ac6b1f4 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/OfferWishlistLookUpService.java @@ -0,0 +1,34 @@ +package vt.CS5934.SwitchRoom.services; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import vt.CS5934.SwitchRoom.models.ExampleModel; +import vt.CS5934.SwitchRoom.models.MatchedWishlistRecordModel; +import vt.CS5934.SwitchRoom.models.UserOfferWishlistLookUpModel; +import vt.CS5934.SwitchRoom.repositories.MatchedWishlistRecordRepository; +import vt.CS5934.SwitchRoom.repositories.UserOfferWishlistLookUpRepository; + +import java.util.*; + +@Service +public class OfferWishlistLookUpService { + + /** + * You can use logger.[trace,debug,info,warn,error]("messages") to log into file + */ + private final Logger logger = LoggerFactory.getLogger(ExampleService.class); + + /** + * Autowired is a Spring feature that it will create or looking for the existing object in memory. + * It usually uses on Repository class, Service class, or some globe object in the class. + */ + @Autowired + UserOfferWishlistLookUpRepository userOfferWishlistLookUpRepository; + + public List<UserOfferWishlistLookUpModel> getOfferWishlistLookUpWithIDFromDB(long id){ + logger.info("Reached getWishlistLookUpWithIDFromDB()"); + return userOfferWishlistLookUpRepository.findAllByUserId(id); + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/Token.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/Token.java new file mode 100644 index 0000000000000000000000000000000000000000..271828118ca2d1e3b4afd30a2bf9d7fe339b5765 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/Token.java @@ -0,0 +1,29 @@ +package vt.CS5934.SwitchRoom.services; + +import io.jsonwebtoken.SignatureAlgorithm; +import lombok.Getter; +import io.jsonwebtoken.Jwts; + +import java.sql.Date; +import java.time.Instant; +import java.time.temporal.ChronoUnit; + +public class Token { + @Getter + private final String token; + + private Token(String token) { + this.token = token; + } + + public static Token of(int userId, Long validityInMinutes, String secretKey) { + var issueDate = Instant.now(); + return new Token( + Jwts.builder() + .claim("user_id", userId) + .setIssuedAt(Date.from(issueDate)) + .setExpiration(Date.from(issueDate.plus(validityInMinutes, ChronoUnit.MINUTES))) + .signWith(SignatureAlgorithm.HS256, secretKey) + .compact()); + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/UserService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/UserService.java index aac7d5e2da2f1162a67f41df220f4b38fb6b5013..50032ddb4efc8eced728087bffa8caf98b7e658c 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/UserService.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/UserService.java @@ -1,17 +1,24 @@ package vt.CS5934.SwitchRoom.services; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import net.bytebuddy.implementation.bytecode.Throw; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; import vt.CS5934.SwitchRoom.hash.SHAModel; import vt.CS5934.SwitchRoom.models.ExampleModel; +import vt.CS5934.SwitchRoom.models.ResponseModel; import vt.CS5934.SwitchRoom.models.UserModel; import vt.CS5934.SwitchRoom.repositories.UserRepository; import javax.transaction.Transactional; import java.security.NoSuchAlgorithmException; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; @Service public class UserService { @@ -35,16 +42,45 @@ public class UserService { public UserModel addUserToDB(String username, String password, String email, String firstname, String lastname, String gender){ logger.info("Reached addNewExampleModelToDB()"); - UserModel newUser = new UserModel(username, password, email, firstname, lastname, gender); + UserModel newUser = new UserModel(username, password, email, firstname, lastname, gender, null); userRepository.save(newUser); return newUser; } - public UserModel loginUser(String username) { - logger.info("Reached loginUser() in UserService"); + public String[] loginUser(UserModel userInfo) throws NoSuchAlgorithmException { + String username = userInfo.getUsername(); + String password = hashPassword(userInfo.getPassword()); UserModel existUser = userRepository.findByUsername(username); - return existUser; + if (existUser == null) { + logger.error("loginUser: FORBIDDEN (user not found)"); + return new String[] {}; + } else if (existUser.getPassword().equals(password)) { + var token = Token.of(existUser.getUserId(), 10L, "secret"); + existUser.setToken(token.getToken()); + userRepository.save(existUser); + + String result[] = new String[2]; + result[0] = existUser.getUserId().toString(); + result[1] = token.getToken(); + return result; + } else { + logger.warn("loginUser: FORBIDDEN (wrong password)"); + return new String[] {}; + } + } + + public boolean checkLoginSession(String userId, String token) { + UserModel existUser = userRepository.findByUserId(Integer.parseInt(userId)); + if (existUser == null) { + logger.error("checkLoginSession: FORBIDDEN (user not found)"); + return false; + } else if (existUser.getToken().equals(token)) { + return true; + } else { + logger.error("checkLoginSession: FORBIDDEN (invalid token)"); + return false; + } } public String hashPassword(String password) throws NoSuchAlgorithmException { @@ -53,5 +89,53 @@ public class UserService { return result; } + public boolean resetPassword(String userId, String payload) throws NoSuchAlgorithmException, JsonProcessingException { + UserModel existUser = userRepository.findByUserId(Integer.parseInt(userId)); + + if (existUser == null) { + logger.warn("checkLoginSession: FORBIDDEN (user not found)"); + return false; + } + ObjectMapper mapper = new ObjectMapper(); + String oldPassword = mapper.readTree(payload) + .get("oldPassword") + .asText(); + String newPassword = mapper.readTree(payload) + .get("newPassword") + .asText(); + String inputOldPassword = hashPassword(oldPassword); + + if (existUser.getPassword().equals(inputOldPassword)) { + String inputNewPassword = hashPassword(newPassword); + existUser.setPassword(inputNewPassword); + userRepository.save(existUser); + return true; + } else { + logger.warn("checkLoginSession: FORBIDDEN (password not matched)"); + return false; + } + } + public ResponseModel getProfile(String userId) { + ResponseModel response = new ResponseModel(); + UserModel existUser = userRepository.findByUserId(Integer.parseInt(userId)); + + if (existUser == null) { + logger.warn("getProfile: FORBIDDEN (user not found)"); + response.setMessage("user not found"); + response.setStatus(HttpStatus.FORBIDDEN); + return response; + } + + Map<String, String> profile = new LinkedHashMap<>(); + profile.put("username", existUser.getUsername()); + profile.put("email", existUser.getEmail()); + profile.put("firstName", existUser.getFirstname()); + profile.put("lastName", existUser.getLastname()); + + response.setData(profile); + response.setStatus(HttpStatus.OK); + + return response; + } } diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/WishlistPageService.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/WishlistPageService.java index 64d9e39925937389fee6ea3004119211437a2865..b9b04765212f9fd4bd49f456cafa4cba8a8817ca 100644 --- a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/WishlistPageService.java +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/services/WishlistPageService.java @@ -6,10 +6,10 @@ package vt.CS5934.SwitchRoom.services; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import vt.CS5934.SwitchRoom.models.UserOfferWishlistLookUpModel; -import vt.CS5934.SwitchRoom.models.WishlistItemModel; -import vt.CS5934.SwitchRoom.repositories.UserOfferWishlistLookUpRepository; -import vt.CS5934.SwitchRoom.repositories.WishlistItemRepository; +import vt.CS5934.SwitchRoom.models.*; +import vt.CS5934.SwitchRoom.repositories.*; +import vt.CS5934.SwitchRoom.utility.StateCodeMap; + import java.util.ArrayList; import java.util.List; @@ -20,6 +20,16 @@ public class WishlistPageService { WishlistItemRepository wishlistItemRepository; @Autowired UserOfferWishlistLookUpRepository userOfferWishlistLookUpRepository; + @Autowired + StateCityLookupRepository stateCityLookupRepository; + @Autowired + MatchedWishlistRecordRepository matchedWishlistRecordRepository; + @Autowired + WishlistWaitingMatchRepository wishlistWaitingMatchRepository; + @Autowired + UserOfferRepository userOfferRepository; + @Autowired + UserRepository userRepository; /** * This function will take userId and look into UserOfferWishlistLookUpModel DB and find all the wishlist item id @@ -45,10 +55,10 @@ public class WishlistPageService { */ @Transactional public WishlistItemModel saveNewWishlistItem(Long userId, WishlistItemModel wishlistItemModel) { - wishlistItemModel.setWishlistItemId(null); + fillStateCodeAndStateCityCode(wishlistItemModel); WishlistItemModel wishlistItemModelWithId = wishlistItemRepository.save(wishlistItemModel); UserOfferWishlistLookUpModel lookUpModel = new UserOfferWishlistLookUpModel(userId, - wishlistItemModelWithId.getWishlistItemId(), null); + wishlistItemModelWithId.getWishlistItemId()); userOfferWishlistLookUpRepository.save(lookUpModel); return wishlistItemModelWithId; } @@ -62,6 +72,8 @@ public class WishlistPageService { public void deleteWishlistItem(Long wishlistItemId) { wishlistItemRepository.deleteByWishlistItemId(wishlistItemId); userOfferWishlistLookUpRepository.deleteByWishlistItemId(wishlistItemId); + wishlistWaitingMatchRepository.deleteAllByWishlistItemId(wishlistItemId); + matchedWishlistRecordRepository.deleteAllByWishlistItemId(wishlistItemId); } /** @@ -71,16 +83,76 @@ public class WishlistPageService { */ @Transactional public WishlistItemModel updateWishlistItem(WishlistItemModel wishlistItemModel) { + fillStateCodeAndStateCityCode(wishlistItemModel); + matchedWishlistRecordRepository.deleteAllByWishlistItemId(wishlistItemModel.getWishlistItemId()); return wishlistItemRepository.save(wishlistItemModel); } public WishlistItemModel getWishlistItemInfo(Long wishlistItemId) { return wishlistItemRepository.findByWishlistItemId(wishlistItemId); } + + public Long getPairedUserId( Long wishlistItemId ){ + return userOfferWishlistLookUpRepository.findByWishlistItemId(wishlistItemId).getUserId(); + } + + + /** + * This function will take a UserOfferModel. it will fill the StateCode with it first, + * then check and update the StateCityCode. If DB do not have the StateCityCode, then build one and fill it in. + * @param wishlistItemModel the new or updated offerModel + */ + private void fillStateCodeAndStateCityCode(WishlistItemModel wishlistItemModel){ + + // Fill state code in the record + wishlistItemModel.setStateCode(StateCodeMap.StringToCodeMap.get(wishlistItemModel.getState())); + + // Check if the DB have the state city code or not, if not build one + StateCityLookupModel stateCityLookupModel = stateCityLookupRepository + .findByCityNameAndStateCode(wishlistItemModel.getCityName(), wishlistItemModel.getStateCode()); + + if(stateCityLookupModel == null){ + // not in DB yet + stateCityLookupModel = stateCityLookupRepository.save( + new StateCityLookupModel(null, wishlistItemModel.getStateCode(), + wishlistItemModel.getCityName())); + } + wishlistItemModel.setStateCityCode(stateCityLookupModel.getStateCityCode()); + } + + /** + * This function is used to get the matched offers of the given wishlist item + * @param wishlistItemId the target id + * @return a list of matched offer info of the given wishlistItem + */ + public List<OfferMatchRequestInfo> getOfferMatchList(Long wishlistItemId) { + List<OfferMatchRequestInfo> offerMatchRequestInfoList = new ArrayList<>(); + List<MatchedWishlistRecordModel> matchedRecordList = + matchedWishlistRecordRepository.findAllByWishlistItemId(wishlistItemId); + matchedRecordList.forEach( matchedRecord ->{ + UserOfferModel userOfferModel = userOfferRepository.findByUserId(matchedRecord.getOfferId()); + UserModel userInfo = userRepository.findByUserId(userOfferModel.getUserId().intValue()); + offerMatchRequestInfoList.add(new OfferMatchRequestInfo(userOfferModel.getUserId(), + wishlistItemId, userOfferModel.getAvailableTimeStart(),userOfferModel.getAvailableTimeEnd(), + userOfferModel.getZipCode(), userInfo.getFirstname()+" "+ userInfo.getLastname())); + }); + return offerMatchRequestInfoList; + } + + public List<Long> getOfferMatchCount(List<Long> wishlistItemIdList) { + List<Long> countList = new ArrayList<>(); + wishlistItemIdList.forEach((wishListId ->{ + Long count = matchedWishlistRecordRepository.countByWishlistItemId(wishListId); + countList.add(count == null ? 0 : count); + })); + return countList; + } } /* Change logs: Date | Author | Description 2022-10-21 | Fangzheng Zhang | create class and init - +2022-10-30 | Fangzheng Zhang | implement state city code feature +2022-11-03 | Fangzheng Zhang | handle update and deletion to matched table and waiting table +2022-11-05 | Fangzheng Zhang | Add getOfferMatchList function */ \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/LookupTables.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/LookupTables.java new file mode 100644 index 0000000000000000000000000000000000000000..eac4ced1a48f075ea634c362945f702a3fd9eee7 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/LookupTables.java @@ -0,0 +1,13 @@ +package vt.CS5934.SwitchRoom.utility; + +public class LookupTables { + // Matching job service related fields + public static final Long OFFER_JOB_DB_ID = 1L; + public static final Long WISHLIST_JOB_DB_ID = 2L; + public static final Long MATCHING_JOB_DB_ID = 3L; + + // Matching related DB data fields + public enum JOB_STATUS {Running, Done, Stopped} + public enum MATCHING_RECORD_USER_RESULT {Waiting, Accepted, Declined} + +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/StateCodeMap.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/StateCodeMap.java new file mode 100644 index 0000000000000000000000000000000000000000..a088d31dfacb7c6eb666bfb40651736996e95639 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/StateCodeMap.java @@ -0,0 +1,27 @@ +package vt.CS5934.SwitchRoom.utility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +public class StateCodeMap { + public static final Map<String, Integer> StringToCodeMap; + static { + StringToCodeMap = Map.ofEntries(Map.entry("AK", 0), Map.entry("AL", 1), Map.entry("AR", 2), + Map.entry("AZ", 3), Map.entry("CA", 4), Map.entry("CO", 5), Map.entry("CT", 6), + Map.entry("DE", 7), Map.entry("FL", 8), Map.entry("GA", 9), Map.entry("HI", 10), + Map.entry("IA", 11), Map.entry("ID", 12), Map.entry("IL", 13), + Map.entry("IN", 14), Map.entry("KS", 15), Map.entry("KY", 16), + Map.entry("LA", 17), Map.entry("MA", 18), Map.entry("MD", 19), + Map.entry("ME", 20), Map.entry("MI", 21), Map.entry("MN", 22), + Map.entry("MO", 23), Map.entry("MS", 24), Map.entry("MT", 25), + Map.entry("NC", 26), Map.entry("ND", 27), Map.entry("NE", 28), + Map.entry("NH", 29), Map.entry("NJ", 30), Map.entry("NM", 31), + Map.entry("NV", 32), Map.entry("NY", 33), Map.entry("OH", 34), + Map.entry("OK", 35), Map.entry("OR", 36), Map.entry("PA", 37), + Map.entry("RI", 38), Map.entry("SC", 39), Map.entry("SD", 40), + Map.entry("TN", 41), Map.entry("TX", 42), Map.entry("UT", 43), + Map.entry("VA", 44), Map.entry("VT", 45), Map.entry("WA", 46), + Map.entry("WI", 47), Map.entry("WV", 48), Map.entry("WY", 49)); + } +} diff --git a/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/UsefulTools.java b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/UsefulTools.java new file mode 100644 index 0000000000000000000000000000000000000000..d1cba369c510c563e9b7d15fb95a3f2079a830d3 --- /dev/null +++ b/BackendFolder/SwitchRoom/src/main/java/vt/CS5934/SwitchRoom/utility/UsefulTools.java @@ -0,0 +1,7 @@ +package vt.CS5934.SwitchRoom.utility; + +import java.text.SimpleDateFormat; + +public class UsefulTools { + public static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm:ss"); +} diff --git a/BackendFolder/SwitchRoom/src/main/resources/application.properties b/BackendFolder/SwitchRoom/src/main/resources/application.properties index e8ebd7b1fed9166552622df9a3b58984e34c3ca7..45dc0326a2a696d0e43297d4b5fe74f10c457db9 100644 --- a/BackendFolder/SwitchRoom/src/main/resources/application.properties +++ b/BackendFolder/SwitchRoom/src/main/resources/application.properties @@ -7,6 +7,13 @@ spring.datasource.username=root spring.datasource.password=mypassword spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update -spring.jpa.show-sql=true +spring.jpa.show-sql=false spring.jpa.properties.hibernate.format_sql=true spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL57InnoDBDialect + +# SpringBoot scheduling thread pool +spring.task.scheduling.pool.size = 1 + +#SpringBoot Offer Wishlist matching job fields: +offer.wishlist.job.gap.seconds = 60 +offer.wishlist.job.init.delay.seconds = 10 \ No newline at end of file diff --git a/BackendFolder/SwitchRoom/src/main/resources/data.sql b/BackendFolder/SwitchRoom/src/main/resources/data.sql new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/FrontendFolder/switch-room/package-lock.json b/FrontendFolder/switch-room/package-lock.json index ab05d8655f44ace5b38641225e9ecaf098abc844..c77aed193834d70fbd25c8a01d6d183ecb67babb 100644 --- a/FrontendFolder/switch-room/package-lock.json +++ b/FrontendFolder/switch-room/package-lock.json @@ -8,16 +8,23 @@ "name": "switch-room", "version": "0.1.0", "dependencies": { + "@element-plus/icons-vue": "^2.0.10", "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-regular-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/vue-fontawesome": "^3.0.1", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", "element-plus": "^2.2.18", + "pinia": "^2.0.23", "vue": "^3.2.13", - "vue-router": "^4.0.3" + "vue-router": "^4.0.3", + "vuex": "^4.1.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.3", + "@types/cors": "^2.8.12", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "@vue/cli-plugin-router": "~5.0.0", @@ -680,11 +687,7 @@ "name": "@sxzz/popperjs-es", "version": "2.11.7", "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.7.tgz", - "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/popperjs" - } + "integrity": "sha512-Ccy0NlLkzr0Ex2FKvh2X+OyERHXJ88XJ1MXtsI9y9fGexlaXaVTPzBCRBwIxFkORuOb+uBqeu+RqnpgYTEZRUQ==" }, "node_modules/@sideway/address": { "version": "4.1.4", @@ -845,6 +848,21 @@ "@types/node": "*" } }, + "node_modules/@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "dependencies": { + "@types/express": "*" + } + }, + "node_modules/@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, "node_modules/@types/eslint": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", @@ -1412,6 +1430,26 @@ } } }, + "node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15": { + "name": "vue-loader", + "version": "15.10.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", + "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "dev": true, + "dependencies": { + "@vue/component-compiler-utils": "^3.1.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + } + }, + "node_modules/@vue/cli-service/node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + }, "node_modules/@vue/cli-shared-utils": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/@vue/cli-shared-utils/-/cli-shared-utils-5.0.8.tgz", @@ -1706,38 +1744,6 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==" }, - "node_modules/@vue/vue-loader-v15": { - "name": "vue-loader", - "version": "15.10.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", - "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", - "dev": true, - "dependencies": { - "@vue/component-compiler-utils": "^3.1.0", - "hash-sum": "^1.0.2", - "loader-utils": "^1.1.0", - "vue-hot-reload-api": "^2.3.0", - "vue-style-loader": "^4.1.0" - }, - "peerDependencies": { - "css-loader": "*", - "webpack": "^3.0.0 || ^4.1.0 || ^5.0.0-0" - }, - "peerDependenciesMeta": { - "cache-loader": { - "optional": true - }, - "vue-template-compiler": { - "optional": true - } - } - }, - "node_modules/@vue/vue-loader-v15/node_modules/hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true - }, "node_modules/@vue/web-component-wrapper": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", @@ -3006,11 +3012,30 @@ "node": ">= 0.6" } }, + "node_modules/cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "dependencies": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/cookie-parser/node_modules/cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "node_modules/copy-webpack-plugin": { "version": "9.1.0", @@ -3060,6 +3085,18 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -6693,7 +6730,6 @@ "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -7097,6 +7133,31 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pinia": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz", + "integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==", + "dependencies": { + "@vue/devtools-api": "^6.4.4", + "vue-demi": "*" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "@vue/composition-api": "^1.4.0", + "typescript": ">=4.4.4", + "vue": "^2.6.14 || ^3.2.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + }, + "typescript": { + "optional": true + } + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -9177,7 +9238,7 @@ "version": "4.8.4", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -9289,7 +9350,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true, "engines": { "node": ">= 0.8" } @@ -9306,6 +9366,31 @@ "@vue/shared": "3.2.41" } }, + "node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, "node_modules/vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", @@ -9524,6 +9609,17 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "node_modules/vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "dependencies": { + "@vue/devtools-api": "^6.0.0-beta.11" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, "node_modules/watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", @@ -10828,6 +10924,21 @@ "@types/node": "*" } }, + "@types/cookie-parser": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/@types/cookie-parser/-/cookie-parser-1.4.3.tgz", + "integrity": "sha512-CqSKwFwefj4PzZ5n/iwad/bow2hTCh0FlNAeWLtQM3JA/NX/iYagIpWG2cf1bQKQ2c9gU2log5VUCrn7LDOs0w==", + "dev": true, + "requires": { + "@types/express": "*" + } + }, + "@types/cors": { + "version": "2.8.12", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.12.tgz", + "integrity": "sha512-vt+kDhq/M2ayberEtJcIN/hxXy1Pk+59g2FV/ZQceeaTyCtCucjL2Q7FXlFjtWn4n15KCr1NE2lNNFhp0lEThw==", + "dev": true + }, "@types/eslint": { "version": "8.4.6", "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.4.6.tgz", @@ -11236,6 +11347,29 @@ "webpack-merge": "^5.7.3", "webpack-virtual-modules": "^0.4.2", "whatwg-fetch": "^3.6.2" + }, + "dependencies": { + "@vue/vue-loader-v15": { + "version": "npm:vue-loader@15.10.0", + "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", + "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", + "dev": true, + "requires": { + "@vue/component-compiler-utils": "^3.1.0", + "hash-sum": "^1.0.2", + "loader-utils": "^1.1.0", + "vue-hot-reload-api": "^2.3.0", + "vue-style-loader": "^4.1.0" + }, + "dependencies": { + "hash-sum": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", + "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", + "dev": true + } + } + } } }, "@vue/cli-shared-utils": { @@ -11488,27 +11622,6 @@ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.2.41.tgz", "integrity": "sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==" }, - "@vue/vue-loader-v15": { - "version": "npm:vue-loader@15.10.0", - "resolved": "https://registry.npmjs.org/vue-loader/-/vue-loader-15.10.0.tgz", - "integrity": "sha512-VU6tuO8eKajrFeBzMssFUP9SvakEeeSi1BxdTH5o3+1yUyrldp8IERkSdXlMI2t4kxF2sqYUDsQY+WJBxzBmZg==", - "dev": true, - "requires": { - "@vue/component-compiler-utils": "^3.1.0", - "hash-sum": "^1.0.2", - "loader-utils": "^1.1.0", - "vue-hot-reload-api": "^2.3.0", - "vue-style-loader": "^4.1.0" - }, - "dependencies": { - "hash-sum": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/hash-sum/-/hash-sum-1.0.2.tgz", - "integrity": "sha512-fUs4B4L+mlt8/XAtSOGMUO1TXmAelItBPtJG7CyHJfYTdDjwisntGO2JQz7oUsatOY9o68+57eziUVNw/mRHmA==", - "dev": true - } - } - }, "@vue/web-component-wrapper": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/@vue/web-component-wrapper/-/web-component-wrapper-1.3.0.tgz", @@ -12461,11 +12574,26 @@ "integrity": "sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==", "dev": true }, + "cookie-parser": { + "version": "1.4.6", + "resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.6.tgz", + "integrity": "sha512-z3IzaNjdwUC2olLIB5/ITd0/setiaFMLYiZJle7xg5Fe9KWAceil7xszYfHHBtDFYLSgJduS2Ty0P1uJdPDJeA==", + "requires": { + "cookie": "0.4.1", + "cookie-signature": "1.0.6" + }, + "dependencies": { + "cookie": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.1.tgz", + "integrity": "sha512-ZwrFkGJxUR3EIoXtO+yVE69Eb7KlixbaeAWfBQB9vVsNn/o+Yw69gBWSSDK825hQNdN+wF8zELf3dFNl/kxkUA==" + } + } + }, "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==", - "dev": true + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" }, "copy-webpack-plugin": { "version": "9.1.0", @@ -12500,6 +12628,15 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true }, + "cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "requires": { + "object-assign": "^4", + "vary": "^1" + } + }, "cosmiconfig": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz", @@ -15200,8 +15337,7 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", - "dev": true + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" }, "object-inspect": { "version": "1.12.2", @@ -15504,6 +15640,15 @@ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "dev": true }, + "pinia": { + "version": "2.0.23", + "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.0.23.tgz", + "integrity": "sha512-N15hFf4o5STrxpNrib1IEb1GOArvPYf1zPvQVRGOO1G1d74Ak0J0lVyalX/SmrzdT4Q0nlEFjbURsmBmIGUR5Q==", + "requires": { + "@vue/devtools-api": "^6.4.4", + "vue-demi": "*" + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -16993,10 +17138,10 @@ } }, "typescript": { - "version": "4.5.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.5.5.tgz", - "integrity": "sha512-TCTIul70LyWe6IJWT8QSYeA54WQe8EjQFU4wY52Fasj5UKx88LNYKCgBEHcOMOrFF1rKGbD8v/xcNWVUq9SymA==", - "dev": true + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.4.tgz", + "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", + "devOptional": true }, "universalify": { "version": "2.0.0", @@ -17072,8 +17217,7 @@ "vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", - "dev": true + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==" }, "vue": { "version": "3.2.41", @@ -17087,6 +17231,12 @@ "@vue/shared": "3.2.41" } }, + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + }, "vue-eslint-parser": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-8.3.0.tgz", @@ -17251,6 +17401,14 @@ "integrity": "sha512-4gDntzrifFnCEvyoO8PqyJDmguXgVPxKiIxrBKjIowvL9l+N66196+72XVYR8BBf1Uv1Fgt3bGevJ+sEmxfZzw==", "dev": true }, + "vuex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/vuex/-/vuex-4.1.0.tgz", + "integrity": "sha512-hmV6UerDrPcgbSy9ORAtNXDr9M4wlNP4pEFKye4ujJF8oqgFFuxDCdOLS3eNoRTtq5O3hoBDh9Doj1bQMYHRbQ==", + "requires": { + "@vue/devtools-api": "^6.0.0-beta.11" + } + }, "watchpack": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", diff --git a/FrontendFolder/switch-room/package.json b/FrontendFolder/switch-room/package.json index aae32ab897a79a08d3f6cb5440d998e59619c5f0..7f0c7dcbc9593f40d0fa902650979693a45643cd 100644 --- a/FrontendFolder/switch-room/package.json +++ b/FrontendFolder/switch-room/package.json @@ -8,16 +8,23 @@ "lint": "vue-cli-service lint" }, "dependencies": { + "@element-plus/icons-vue": "^2.0.10", "@fortawesome/fontawesome-svg-core": "^6.2.0", "@fortawesome/free-brands-svg-icons": "^6.2.0", "@fortawesome/free-regular-svg-icons": "^6.2.0", "@fortawesome/free-solid-svg-icons": "^6.2.0", "@fortawesome/vue-fontawesome": "^3.0.1", + "cookie-parser": "^1.4.6", + "cors": "^2.8.5", "element-plus": "^2.2.18", + "pinia": "^2.0.23", "vue": "^3.2.13", - "vue-router": "^4.0.3" + "vue-router": "^4.0.3", + "vuex": "^4.1.0" }, "devDependencies": { + "@types/cookie-parser": "^1.4.3", + "@types/cors": "^2.8.12", "@typescript-eslint/eslint-plugin": "^5.4.0", "@typescript-eslint/parser": "^5.4.0", "@vue/cli-plugin-router": "~5.0.0", diff --git a/FrontendFolder/switch-room/src/App.vue b/FrontendFolder/switch-room/src/App.vue index 4d3c04cb326c0b33bfcd6b86e4ebe0530f274106..9be9464bf1bc9af89fcec6aaff5fbb7b8d98df45 100644 --- a/FrontendFolder/switch-room/src/App.vue +++ b/FrontendFolder/switch-room/src/App.vue @@ -3,9 +3,21 @@ import AppFooter from "@/components/AppFooter.vue"; import AppHeader from "@/components/AppHeader.vue"; </script> <template> - <app-header></app-header> - <router-view></router-view> - <app-footer></app-footer> + <body> + <app-header v-if="!$route.meta.hideHeader"></app-header> + <router-view class="wrapper"></router-view> + <app-footer class="footer"></app-footer> + </body> </template> -<style></style> \ No newline at end of file +<style> +body { + text-align: center; + display: flex; + flex-direction: column; + min-height: 100vh; +} +.wrapper { + flex: 1; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/AppFooter.vue b/FrontendFolder/switch-room/src/components/AppFooter.vue index 0b99af330618583e0873ad49c7957175a1543e8d..764cb82d2e93d2448c959dc7fe5acd2a38f0f2ee 100644 --- a/FrontendFolder/switch-room/src/components/AppFooter.vue +++ b/FrontendFolder/switch-room/src/components/AppFooter.vue @@ -36,7 +36,7 @@ text-align: left; font: normal 16px sans-serif; padding: 45px 50px; - position: fixed; + /*position: fixed;*/ bottom: 0; left: 0; right: 0; diff --git a/FrontendFolder/switch-room/src/components/AppHeader.vue b/FrontendFolder/switch-room/src/components/AppHeader.vue index 71e43e1196a1a1545368c33d7d5959afcb7dac6c..97d1133ca1a01e4efd4d460b541d07a39da080c2 100644 --- a/FrontendFolder/switch-room/src/components/AppHeader.vue +++ b/FrontendFolder/switch-room/src/components/AppHeader.vue @@ -9,24 +9,71 @@ active-text-color="#ffd04b" @select="handleSelect" > - - <el-menu-item index="1" v-on:click="redirect('/login-main-page')">Main</el-menu-item> - <el-menu-item index="2" v-on:click="redirect('/offer-page')">Offer Page</el-menu-item> - <el-menu-item index="3" v-on:click="redirect('/wishlist-page')">Wish List</el-menu-item> - <el-menu-item index="4" v-on:click="redirect('/flight')">Flight Ticket</el-menu-item> + <el-menu-item index="1" v-on:click="redirect('/login-main-page')" + >Main</el-menu-item + > + <el-menu-item index="2" v-on:click="redirect('/offer-page')" + >Offer Page</el-menu-item + > + <el-menu-item index="3" v-on:click="redirect('/wishlist-page')" + >Wish List</el-menu-item + > + <el-menu-item index="4" v-on:click="redirect('/flight')" + >Flight Ticket</el-menu-item + > + <el-menu-item index="5" v-on:click="redirect('/profile')" + >Profile</el-menu-item + > + <notification class="position"></notification> + <el-menu-item index="6" class="dock-right" @click="hanldeLogOut()" + >Log Out</el-menu-item + > </el-menu> </template> <script lang="ts"> import { defineComponent } from "vue"; +import { mapActions, mapGetters } from "vuex"; +import Notification from "@/components/Notification.vue"; +import { OfferNotification } from "@/models/OfferNotification"; export default defineComponent({ + components: { Notification }, + props: ["offers"], + data() { + return { + hasToken: document.cookie.length > 0, + }; + }, name: "LoginMainPage", methods: { redirect(url: string) { - this.$router.push({path: url}); - } + this.$router.push(url); + }, + ...mapActions("auth", { + logOut: "logOutApi", + }), + hanldeLogOut() { + this.logOut(); + document.cookie = + "userId=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + document.cookie = + "token=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;"; + this.$router.push("/"); + }, }, }); </script> -<style scoped></style> +<style scoped> +.h-6 { + display: flex; +} +.el-menu-demo > .el-menu-item.dock-right { + margin-left: auto; +} + +.position { + margin-top: 12px; + margin-left: auto; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/LoginMainPage.vue b/FrontendFolder/switch-room/src/components/LoginMainPage.vue index 7d8e3d441dc0396e2a9ce728271395caa0068894..9a9e65050cbe3a06e27ba68b5e465c0a187fd947 100644 --- a/FrontendFolder/switch-room/src/components/LoginMainPage.vue +++ b/FrontendFolder/switch-room/src/components/LoginMainPage.vue @@ -1,93 +1,66 @@ <template> - <el-container class="container"> - <el-main> - <el-row :gutter="10" justify="center"> - <el-col - :span="10" - style="display: flex; justify-content: center;" - > - <el-card - class="card" - v-on:click="redirect('/offer-page')" - > - <img - src="../assets/mainPage/offer.png" - class="image" - /> - <div> - <p style="font-size: 50px;">Offer</p> - <p>Match results go here</p> - </div> - </el-card> - </el-col> - <el-col - :span="10" - style="display: flex; justify-content: center;" - > - <el-card - class="card" - v-on:click="redirect('/wishlist-page')" - > - <img - src="../assets/mainPage/wishlist.jpg" - class="image" - /> - <div> - <p style="font-size: 50px;">Wish List</p> - <p>User's wish list go here</p> - </div> - </el-card> - </el-col> - </el-row> - <el-row style="margin-top: 5%;"> - <el-col - style="display: flex; justify-content: center;" - > - <el-card - class="card" - v-on:click="redirect('/flight')" - > - <img - src="../assets/mainPage/flight.jpeg" - class="image" - /> - <div> - <p style="font-size: 50px;">Flight Ticket</p> - <p>Find your flight!</p> - </div> - </el-card> - </el-col> - </el-row> - </el-main> - </el-container> + <el-container class="container"> + <el-main> + <el-row :gutter="10" justify="center"> + <el-col :span="10" style="display: flex; justify-content: center"> + <el-card class="card" v-on:click="redirect('/offer-page')"> + <img src="../assets/mainPage/offer.png" class="image" /> + <div> + <p style="font-size: 50px">Offer</p> + <p>Match results go here</p> + </div> + </el-card> + </el-col> + <el-col :span="10" style="display: flex; justify-content: center"> + <el-card class="card" v-on:click="redirect('/wishlist-page')"> + <img src="../assets/mainPage/wishlist.jpg" class="image" /> + <div> + <p style="font-size: 50px">Wish List</p> + <p>User's wish list go here</p> + </div> + </el-card> + </el-col> + </el-row> + <el-row style="margin-top: 5%"> + <el-col style="display: flex; justify-content: center"> + <el-card class="card" v-on:click="redirect('/flight')"> + <img src="../assets/mainPage/flight.jpeg" class="image" /> + <div> + <p style="font-size: 50px">Flight Ticket</p> + <p>Find your flight!</p> + </div> + </el-card> + </el-col> + </el-row> + </el-main> + </el-container> </template> - + <script lang="ts"> import { defineComponent } from "vue"; export default defineComponent({ - name: "LoginMainPage", - methods: { - redirect(url: string) { - this.$router.push({path: url}); - } + name: "LoginMainPage", + methods: { + redirect(url: string) { + this.$router.push({ path: url }); }, + }, }); </script> - + <style> .image { - width: 25vw; - display: block; + width: 25vw; + display: block; } .card { - cursor: pointer + cursor: pointer; } .container { - width: 100%; - height: 100%; - color: #fff; - background-image: linear-gradient(135deg, #b3d1ff 10%, #fd5e086b 100%); - font-size: 24px; + width: 100%; + height: 100%; + color: #fff; + background-image: linear-gradient(135deg, #b3d1ff 10%, #fd5e086b 100%); + font-size: 24px; } </style> - \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/components/MainPage.vue b/FrontendFolder/switch-room/src/components/MainPage.vue index 2979082cf3139623c66585c7b319231f2c8cf584..a105bd70bdb1b20945010f443c43fd66b4a10020 100644 --- a/FrontendFolder/switch-room/src/components/MainPage.vue +++ b/FrontendFolder/switch-room/src/components/MainPage.vue @@ -23,12 +23,12 @@ <div class="underline-title"></div> </div> <form method="post" class="form"> - <label for="user-name" style="padding-top: 13px; text-align: left"> + <label for="user-name" style="padding-top: 13px"> Username </label> <input id="user-name" - v-model="userInformation.username" + v-model="username" class="form-content" type="text" name="username" @@ -36,12 +36,12 @@ required /> <div class="form-border"></div> - <label for="user-password" style="padding-top: 22px; text-align: left" + <label for="user-password" style="padding-top: 22px" > Password </label> <input id="user-password" - v-model="userInformation.password" + v-model="password" class="form-content" type="password" name="password" @@ -51,13 +51,9 @@ <a href="#"> <legend id="forgot-pass">Forgot password?</legend> </a> - <input - id="submit-btn" - type="submit" - name="submit" - value="LOGIN" - @click.prevent="onSubmit" - /> + <button type="button" class="btn btn-primary" @click="handleLogin()"> + LOGIN + </button> <router-link to="/register"> <div id="signup">Don't have account yet?</div> </router-link> @@ -67,33 +63,51 @@ </div> </template> -<script setup lang="ts"> -import { ref, reactive } from "vue"; -import * as UserService from "../services/UserService"; -import { useRouter } from "vue-router"; -import { UserModel } from "@/models/UserModel"; -import { result } from "lodash"; -const router = useRouter(); -const user = ref(new UserModel()); -const userInformation = reactive({ - username: "", - password: "", -}); +<script lang="ts"> +import { defineComponent } from "vue"; +import { mapActions, mapGetters } from "vuex"; +import { checkLoginSession } from "../services/UserService"; -const onSubmit = async () => { - await UserService.loginUser(JSON.stringify(userInformation)) - .then((result) => { - if (result.status == "OK") { - user.value = result.data; - router.push("/login-main-page"); - } else { - alert(result.message); +export default defineComponent({ + data() { + return { + username: "", + password: "", + }; + }, + computed: { + ...mapGetters("auth", { + getLoginStatus: "getLoginStatus", + }), + }, + methods: { + ...mapActions("auth", { + actionLoginApi: "loginApi", + }), + async handleCreated() { + await checkLoginSession().then((result) => { + if (result.status == "OK") { + this.$router.push({ name: "LoginMainPage" }); + } else { + this.$router.push({ name: "home" }); + } + }); + }, + async handleLogin() { + const payload = { + username: this.username, + password: this.password, + }; + await this.actionLoginApi(payload); + if (this.getLoginStatus) { + this.$router.push({ name: "LoginMainPage" }); } - }) - .catch((error) => { - alert(error); - }); -}; + }, + }, + created: function () { + this.handleCreated(); + }, +}); </script> <style scoped> @@ -112,13 +126,12 @@ body { background: url("@/assets/mainPage/headerPhoto.jpeg"); text-align: center; width: 70%; - height: 800px; + height: auto; background-size: cover; background-attachment: fixed; position: relative; overflow: hidden; border-radius: 0 0 85% 85% / 30%; - display: flex; } .leftPart .overlay { width: 100%; @@ -172,7 +185,7 @@ label { background: #fbfbfb; border-radius: 8px; box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.65); - height: 450px; + height: 410px; width: 30%; } #card-content { diff --git a/FrontendFolder/switch-room/src/components/MatchOffer.vue b/FrontendFolder/switch-room/src/components/MatchOffer.vue new file mode 100644 index 0000000000000000000000000000000000000000..e9bbdee7837e91396bd1e9239099e6cf65ed75c4 --- /dev/null +++ b/FrontendFolder/switch-room/src/components/MatchOffer.vue @@ -0,0 +1,46 @@ +<template> + <div class="offerSide"> + <div class="card"> + <div class="container"> + <h4><b>Matched Offer Information</b></h4> + <p>Space Located City: {{ offer.spaceLocateCity }}</p> + <p>Space Located State: {{ offer.state }}</p> + <p>Available Time start: {{ offer.availableTimeStart }}</p> + <p>Available Time end: {{ offer.availableTimeEnd }}</p> + <p>Max People: {{ offer.maxNumberOfPeople }}</p> + <p v-if="offer.offering">It is offering now</p> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { defineProps } from "vue"; +import { OfferFormModel } from "@/models/OfferFormModel"; + +const props = defineProps<{ + offer: OfferFormModel; +}>(); +</script> + +<style scoped> +.offerSide { + display: flex; + justify-content: center; + align-items: center; +} +.card { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); + transition: 0.3s; + width: 40%; + text-align: center; +} + +.card:hover { + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); +} + +.container { + padding: 2px 16px; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/MatchWishList.vue b/FrontendFolder/switch-room/src/components/MatchWishList.vue new file mode 100644 index 0000000000000000000000000000000000000000..2af628d7c9e746f9e2936e9a8bfe67723a3513ec --- /dev/null +++ b/FrontendFolder/switch-room/src/components/MatchWishList.vue @@ -0,0 +1,45 @@ +<template> + <div class="wishlistSide"> + <div class="card"> + <div class="container"> + <h4><b>Matched required space Information</b></h4> + <p>required city: {{ wishlist.cityName }}</p> + <p>required state: {{ wishlist.state }}</p> + <p>required start time: {{ wishlist.startTime }}</p> + <p>required end time: {{ wishlist.endTime }}</p> + <p>required details: {{ wishlist.details }}</p> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { defineProps } from "vue"; +import { WishlistItemModel } from "@/models/WishlistItemModel"; + +const props = defineProps<{ + wishlist: WishlistItemModel; +}>(); +</script> + +<style scoped> +.wishlistSide { + display: flex; + justify-content: center; + align-items: center; +} +.card { + box-shadow: 0 4px 8px 0 rgba(0, 0, 0, 0.2); + transition: 0.3s; + width: 40%; + text-align: center; +} + +.card:hover { + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.2); +} + +.container { + padding: 2px 16px; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/Notification.vue b/FrontendFolder/switch-room/src/components/Notification.vue new file mode 100644 index 0000000000000000000000000000000000000000..f6d35a6340720b4772fa2416918ff17cd77eff67 --- /dev/null +++ b/FrontendFolder/switch-room/src/components/Notification.vue @@ -0,0 +1,153 @@ +<template> + <el-dropdown class="notification"> + <el-row> + <el-button type="info" :icon="Message" circle> + <div class="text-length">{{ length1 + length2 }}</div> + <!-- notification--> + <!-- <font-awesome-icon icon="fa-solid fa-envelope" />--> + <!-- Notification<el-icon class="el-icon--right"><arrow-down /></el-icon>--> + </el-button> + </el-row> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item v-for="offer in offers" :key="offer.offerId"> + <a> + <router-link + :to=" + '/matched-result/' + offer.offerId + '/' + offer.wishlistItemId + " + style="color: blue" + > + <div class="offer-match"> + <div class="offer-text"></div> + <font-awesome-icon icon="fa-solid fa-circle-info" /> + Offer Matched + </div> + </router-link> + </a> + </el-dropdown-item> + <el-dropdown-item + type="primary" + v-for="offer in offers2" + :key="offer.offerId" + > + <a + ><router-link + :to=" + '/matched-result/' + offer.offerId + '/' + offer.wishlistItemId + " + style="color: blue" + > + <div class="offer-match"> + <font-awesome-icon icon="fa-solid fa-circle-info" /> + WishList Matched + </div> + </router-link> + </a> + </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> +</template> + +<script setup lang="ts"> +import { ref, watch } from "vue"; +import { OfferNotification } from "@/models/OfferNotification"; +import * as NotificationService from "@/services/NotificationService"; +import { useRoute } from "vue-router"; +import { onMounted } from "vue"; +import { Message } from "@element-plus/icons-vue"; + +const route = useRoute(); + +function getCookie(userId: string) { + const value = `; ${document.cookie}`; + const parts: string[] = value.split(`; ${userId}=`); + if (parts.length === 2) return parts?.pop()?.split(";").shift(); +} + +const offers = ref([] as OfferNotification[]); +const offers2 = ref([] as OfferNotification[]); + +const length1 = ref(-1); +const length2 = ref(-1); +async function fetchNotification(userId: number) { + const response = await NotificationService.getNotificationFromServerWithID( + userId + ); + // const response2 = + // await NotificationService.getNotificationwishFromServerWithID(userId); + // console.log(response2); + offers.value = response.data; + length1.value = response.data.length; + // offers2.value = response2.data; +} + +async function fetchNotificationwish(userId: number) { + const response2 = + await NotificationService.getNotificationwishFromServerWithID(userId); + offers2.value = response2.data; + length2.value = response2.data.length; +} + +watch( + () => route.name, + (values) => { + const userIdString: string = getCookie("userId"); + let userIdNumber = +userIdString; + fetchNotification(userIdNumber); + fetchNotificationwish(userIdNumber); + }, + { immediate: true } +); +</script> + +<style scoped> +.example-showcase .el-dropdown + .el-dropdown { + margin-left: 15px; +} +.example-showcase .el-dropdown-link { + cursor: pointer; + color: var(--el-color-primary); + display: flex; + align-items: center; +} + +.offer-match { + display: flex; + margin-right: 2px; +} + +.offer-text { + font-size: 25px; + font-family: "Apple Symbols"; + color: #1e5be6; + margin-left: 2px; +} + +.text-length { + font-size: 20px; + color: white; + height: 20px; + width: 20px; + background-color: #cc0000; + border-radius: 50%; + display: inline-block; +} + +a:link { + text-decoration: none; +} + +a:visited { + text-decoration: none; +} + +a:hover { + text-decoration: none; +} + +a:active { + text-decoration: none; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/OfferPage.vue b/FrontendFolder/switch-room/src/components/OfferPage.vue index e5d189b2beb79f8ba63a8a071c0c176b2dc26764..2508f792b08dc060baada06e5dd7cea8ee93bdd8 100644 --- a/FrontendFolder/switch-room/src/components/OfferPage.vue +++ b/FrontendFolder/switch-room/src/components/OfferPage.vue @@ -1,6 +1,6 @@ <template> <el-row :gutter="20"> - <el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col> + <el-col :span="3"><div class="grid-content ep-bg-purple" /></el-col> <el-col :span="12"> <el-card style="width: 100%"> <template #header> @@ -19,13 +19,25 @@ status-icon > <el-form-item label="Located City" prop="spaceLocateCity"> + <el-col :span="15"> + <el-input v-model="ruleForm.spaceLocateCity" :readonly="buttonState === 'edit'"/> + </el-col> + </el-form-item> + <el-form-item label="State" prop="state"> <el-col :span="8"> - <el-input v-model="ruleForm.spaceLocateCity" /> + <el-select + v-model="ruleForm.state" + placeholder="Select state" + :disabled="buttonState === 'edit'" + > + <el-option v-for="(state_item, index) in USA_STATE_INITAL_LIST" + :label="state_item" :value="state_item" /> + </el-select> </el-col> </el-form-item> <el-form-item label="Zip Code" prop="zipCode"> - <el-col :span="5"> - <el-input v-model="ruleForm.zipCode" placeholder="24061.." /> + <el-col :span="7"> + <el-input v-model="ruleForm.zipCode" placeholder="24061" :readonly="buttonState === 'edit'"/> </el-col> </el-form-item> <el-col :span="24"> @@ -33,6 +45,7 @@ <el-select v-model="ruleForm.spaceType" placeholder="Please select your Space Type" + :disabled="buttonState === 'edit'" > <el-option label="Apartment (No Roommates)" @@ -65,13 +78,13 @@ :required="ruleForm.spaceType === 'other'" > <el-col :span="8"> - <el-input v-model="ruleForm.otherSpaceType" /> + <el-input v-model="ruleForm.otherSpaceType" :readonly="buttonState === 'edit'"/> </el-col> </el-form-item> <el-form-item label="Space Available for Offering?"> <el-col :span="2"> <el-form-item prop="offering"> - <el-switch v-model="ruleForm.offering" /> + <el-switch v-model="ruleForm.offering" :disabled="buttonState === 'edit'"/> </el-form-item> </el-col> </el-form-item> @@ -89,6 +102,7 @@ label="Pick a date" placeholder="Pick a date" style="width: 100%" + :readonly="buttonState === 'edit'" /> </el-form-item> </el-col> @@ -102,6 +116,7 @@ label="Pick a time" placeholder="Pick a time" style="width: 100%" + :readonly="buttonState === 'edit'" /> </el-form-item> </el-col> @@ -113,35 +128,60 @@ prop="maxNumberOfPeople" > <el-col :span="4"> - <el-input v-model="ruleForm.maxNumberOfPeople" /> + <el-input v-model="ruleForm.maxNumberOfPeople" :readonly="buttonState === 'edit'"/> </el-col> </el-form-item> <el-form-item></el-form-item> <el-form-item label="Space Details:" prop="spaceDetails"> <el-col :span="16" - ><el-input v-model="ruleForm.spaceDetails" type="textarea" + ><el-input v-model="ruleForm.spaceDetails" type="textarea" :readonly="buttonState === 'edit'" /></el-col> </el-form-item> - <el-form-item> + <el-form-item v-if="buttonState === 'create'"> <el-button type="primary" @click="submitForm(ruleFormRef)" - >Create</el-button - > + >Create</el-button> <el-button @click="resetForm(ruleFormRef)">Reset</el-button> </el-form-item> + <el-form-item v-if="buttonState === 'edit'"> + <el-button type="primary" @click="editForm()" + >Edit</el-button> + </el-form-item> + <el-form-item v-if="buttonState === 'update'"> + <el-button type="success" @click="updateForm(ruleFormRef)" + >Save</el-button> + <el-button type="danger" @click="deleteForm(ruleFormRef)">Delete</el-button> + </el-form-item> + </el-form> </el-card> </el-col> - <el-col :span="4"> - <el-card style="width: 100%" - ><template #header> + <el-col :span="6"> + <el-card style="width: 100%"> + <template #header> <div class="card-header"> <span>Match Request</span> </div> </template> - </el-card></el-col - > - <el-col :span="4"></el-col> +<!-- TODO --> + <el-card + v-for="(item, index) in matchRequestList.value" + shadow="hover" + :fill="true" + @click="selectMatchRequestItem(index)" + > + <time > {{formatDate(item.startTime)}} - {{formatDate(item.endTime)}}</time> + <div class="w-bottom"> + <span class="w-owner"> Wishlist Owner Name: {{item.wishListOwnerName}}</span> + <br> + <span class="w-zipCode" v-if="!item.hasOffer"> No Available Offer </span> + <span class="w-zipCode">Zip Code: {{item.zipCode}}</span> + </div> + </el-card> + + </el-card> + </el-col> + <el-col :span="2"></el-col> </el-row> </template> @@ -150,6 +190,7 @@ import { reactive, ref, onMounted } from "vue"; import type { FormInstance, FormRules } from "element-plus"; import { OfferFormModel } from "@/models/OfferFormModel"; import * as OfferPageService from "@/services/OfferPageService"; +import { USA_STATE_INITAL_LIST } from "@/services/Constans"; // Form verification relate field const formSize = ref("default"); @@ -163,6 +204,14 @@ const rules = reactive<FormRules>({ }, { min: 0, max: 20, message: "Length should be 0 to 20", trigger: "blur" }, ], + state: [ + { + required: true, + message: "Please give your space located state", + trigger: "blur", + }, + { min: 0, max: 20, message: "Length should be 0 to 20", trigger: "blur" }, + ], zipCode: [ { required: true, @@ -179,61 +228,90 @@ const rules = reactive<FormRules>({ ], }); // TODO: uncomment it when API connection ready -const userId = 233; let ruleForm = ref(new OfferFormModel()); +let buttonState = ref("create"); +let matchRequestList = reactive({value:[]}); onMounted(() => { -OfferPageService.getUserOfferInfoFromServer(userId).then( (data) => { - console.log("Receive user offer data: ", data); - if(data["data"] === null){ - // TODO: Handle fetch error - }else{ - ruleForm.value = data["data"]; - } - }) + loadInitOfferInfoPage(); + loadOfferPageMatchRequest(); }) +const loadInitOfferInfoPage = async () =>{ + await OfferPageService.getUserOfferInfoFromServer().then( (data) => { + console.log("Receive user offer data: ", data); + if(data["data"] === null){ + // TODO: Handle fetch error + }else{ + ruleForm.value = data["data"]; + if(ruleForm.value.spaceLocateCity == null || ruleForm.value.spaceLocateCity == ""){ + buttonState.value = "create"; + }else{ + buttonState.value = "edit"; + } + } + }); +} + +const loadOfferPageMatchRequest = async() =>{ + await OfferPageService.getWishlistMatchRequestInfoList().then( (data) =>{ + console.log("Fetching Offer Page Match Request Info: ", data); + matchRequestList.value = data["data"]; + }) +} + + const submitForm = async (formEl: FormInstance | undefined) => { if (!formEl) return; await formEl.validate((valid, fields) => { if (valid) { - let result = OfferPageService.createNewUserOffer(ruleForm); // TODO: Check the return result console.log("Form Data before save in DB: ", ruleForm.value); OfferPageService.createNewUserOffer(ruleForm.value).then((data) => { console.log("Save user offer data: ", data); + ruleForm.value = data["data"]; + buttonState.value = "edit"; }) console.log("submit!"); } else { console.log("error submit!", fields); } - console.log(ruleForm); }); }; -const updateFrom = async (formEl: FormInstance | undefined) => { +const editForm = () => { + buttonState.value = "update"; +} + +const updateForm = async (formEl: FormInstance | undefined) => { if (!formEl) return; await formEl.validate((valid, fields) => { if (valid) { - let result = OfferPageService.updateOffer(ruleForm); // TODO: Check the return result - console.log("update submit!"); + OfferPageService.updateOffer(ruleForm.value).then((data) =>{ + console.log("Update user offer data: ", data); + ruleForm.value = data["data"]; + buttonState.value = "edit"; + }) } else { console.log("error submit!", fields); } - console.log(ruleForm); }); }; -const deleteFrom = () => { +const deleteForm = async(formEl: FormInstance | undefined) => { // TODO: Check the return result - let result = OfferPageService.deleteOffer(userId); - console.log("Record deleted"); + await OfferPageService.deleteOffer(ruleForm.value.userId).then((data) =>{ + console.log("Deleting Offer Record"); + console.log("Message: " + data["message"]); + resetForm(); + buttonState.value = "create"; + }); }; -const resetForm = (formEl: FormInstance | undefined) => { - if (!formEl) return; - formEl.resetFields(); +const resetForm = () => { + let userId = ruleForm.value.userId; + ruleForm.value = new OfferFormModel(); ruleForm.value.userId = userId; }; @@ -241,4 +319,21 @@ const options = Array.from({ length: 10000 }).map((_, idx) => ({ value: `${idx + 1}`, label: `${idx + 1}`, })); + +const formatDate = (dateString) => { + const date = new Date(dateString); + return new Intl.DateTimeFormat('en-US').format(date); +} </script> + +<style scoped> +.time { + font-size: 12px; +} +.w-zipCode { + font-size: 12px; +} +.w-owner { + font-size: 12px; +} +</style> \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/components/ProfilePage.vue b/FrontendFolder/switch-room/src/components/ProfilePage.vue new file mode 100644 index 0000000000000000000000000000000000000000..7bfbf2f5f45b6847637446a4a45a5e5ddbe8ce26 --- /dev/null +++ b/FrontendFolder/switch-room/src/components/ProfilePage.vue @@ -0,0 +1,157 @@ +<template> + <div class="form_wrapper"> + <div class="form_container"> + <div class="title_container"> + <h2>Profile</h2> + <div class="underline-title"></div> + </div> + <div class="row clearfix"> + <div class=""> + <div class="form"> + <div class="profile-label"> + <div> Username</div> + <div>{{profile.username}} </div> + </div> + <div class="form-border"></div> + <div class="profile-label"> + <div> Email</div> + <div>{{profile.email}} </div> + </div> + <div class="form-border"></div> + <div class="profile-label"> + <div> First Name</div> + <div>{{profile.firstName}} </div> + </div> + <div class="form-border"></div> + <div class="profile-label"> + <div> Last Name</div> + <div>{{profile.lastName}} </div> + </div> + <div class="form-border"></div> + <button id="submit-btn" @click.prevent="onClickResetPassword" type="submit" value="Submit"> + Reset Password + </button> + </div> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { ref, reactive, onMounted } from "vue"; +import { useRouter } from "vue-router"; +import {getProfile} from "../services/UserService"; +const router = useRouter(); +const profile = reactive({ + username: "", + email: "", + firstName: "", + lastName: "", +}); +onMounted(async () => { + await getProfile() + .then((response) => { + if (response.status == "OK") { + const data = response.data + profile.username = data["username"] + profile.email = data["email"] + profile.firstName = data["firstName"] + profile.lastName = data["lastName"] + } else { + alert(response.message) + } + }) +}) +const onClickResetPassword = () => { + router.push("/resetpassword"); +}; + +</script> + +<style scoped> +body { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + background: #f2f2f2; +} + +.form_wrapper { + justify-content: center; + align-items: center; + display: flex; +} + +.form_container { + background: #fbfbfb; + border-radius: 8px; + box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.65); + height: auto; + width: 30%; + padding: 12px 44px; +} + +.title_container { + font-family: "Raleway Thin", sans-serif; + letter-spacing: 4px; + padding-bottom: 23px; + padding-top: 13px; + text-align: center; +} + +.form { + align-items: left; + display: flex; + flex-direction: column; +} + +.form-content { + background: #fbfbfb; + border: none; + outline: none; + padding-top: 14px; +} + +.form-border { + background: #2ec06f; + height: 1px; + width: 100%; +} + +.profile-label { + padding-top: 13px; + display: flex; + flex-direction: row; + justify-content: space-between; +} + +#submit-btn { + background: #2ec06f; + border: none; + border-radius: 21px; + box-shadow: 0px 1px 8px #24c64f; + cursor: pointer; + color: white; + font-family: "Raleway SemiBold", sans-serif; + height: 42.3px; + margin: 0 auto; + margin-top: 50px; + transition: 0.25s; + width: 153px; +} + +#submit-btn:hover { + box-shadow: 0px 1px 18px #24c64f; +} + +.underline-title { + background: -webkit-linear-gradient(right, #a6f77b, #2ec06f); + height: 2px; + margin: -1.1rem auto 0 auto; + width: 200px; +} + +.input_field radio_option { + margin-top: 10px; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/ResetPasswordPage.vue b/FrontendFolder/switch-room/src/components/ResetPasswordPage.vue new file mode 100644 index 0000000000000000000000000000000000000000..1d9aa218d13e9ffa9badec9451e28af03582111e --- /dev/null +++ b/FrontendFolder/switch-room/src/components/ResetPasswordPage.vue @@ -0,0 +1,139 @@ +<template> + <div class="form_wrapper"> + <div class="form_container"> + <div class="title_container"> + <h2>Reset Password</h2> + <div class="underline-title"></div> + </div> + <div class="row clearfix"> + <div class=""> + <form class="form"> + <label for="user-oldPassword" style="padding-top: 13px"> + Old Password + </label> + <input v-model="userInformation.oldPassword" id="user-oldPassword" class="form-content" type="password" + name="oldPassword" required /> + <div class="form-border"></div> + + <label for="user-newPassword" style="padding-top: 13px"> + New Password + </label> + <input v-model="userInformation.newPassword" id="user-newPassword" class="form-content" type="password" + name="newPassword" required /> + <div class="form-border"></div> + + <button id="submit-btn" @click.prevent="onSubmit" type="submit" value="Submit"> + Submit + </button> + </form> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { ref, reactive } from "vue"; +import * as UserService from "../services/UserService"; +import { useRouter } from "vue-router"; +import { User } from "@/type/types"; +const router = useRouter(); +const userInformation = reactive({ + oldPassword: "", + newPassword: "" +}); + +const onSubmit = async () => { + const userInfo = { + oldPassword: userInformation.oldPassword, + newPassword: userInformation.newPassword, + }; + const response = await UserService.resetPassword( + JSON.stringify(userInfo) + ); + alert(response.message) + userInformation.oldPassword = "" + userInformation.newPassword = "" +}; + +</script> + +<style scoped> +body { + font-family: Verdana, Geneva, sans-serif; + font-size: 14px; + background: #f2f2f2; +} + +.form_wrapper { + justify-content: center; + align-items: center; + display: flex; +} + +.form_container { + background: #fbfbfb; + border-radius: 8px; + box-shadow: 1px 2px 8px rgba(0, 0, 0, 0.65); + height: auto; + width: 30%; + padding: 12px 44px; +} + +.title_container { + font-family: "Raleway Thin", sans-serif; + letter-spacing: 4px; + padding-bottom: 23px; + padding-top: 13px; + text-align: center; +} + +.form { + align-items: left; + display: flex; + flex-direction: column; +} + +.form-content { + background: #fbfbfb; + border: none; + outline: none; + padding-top: 14px; +} + +.form-border { + background: -webkit-linear-gradient(right, #a6f77b, #2ec06f); + height: 1px; + width: 100%; +} + +#submit-btn { + background: -webkit-linear-gradient(right, #a6f77b, #2dbd6e); + border: none; + border-radius: 21px; + box-shadow: 0px 1px 8px #24c64f; + cursor: pointer; + color: white; + font-family: "Raleway SemiBold", sans-serif; + height: 42.3px; + margin: 0 auto; + margin-top: 50px; + transition: 0.25s; + width: 153px; +} + +#submit-btn:hover { + box-shadow: 0px 1px 18px #24c64f; +} + +.underline-title { + background: -webkit-linear-gradient(right, #a6f77b, #2ec06f); + height: 2px; + margin: -1.1rem auto 0 auto; + width: 200px; +} + +.input_field radio_option { + margin-top: 10px; +} +</style> diff --git a/FrontendFolder/switch-room/src/components/SearchFlight.vue b/FrontendFolder/switch-room/src/components/SearchFlight.vue index 1a1aafd36bb8a4ca88e8b141d8779d25e5aac932..5bc26fc26d33d31a790fc1757c7f4f0a4df7967f 100644 --- a/FrontendFolder/switch-room/src/components/SearchFlight.vue +++ b/FrontendFolder/switch-room/src/components/SearchFlight.vue @@ -67,7 +67,6 @@ v-model="flightForm.numAdults" :min="1" :max="10" - @change="handleChange" /> </el-form-item> <el-col @@ -80,7 +79,6 @@ v-model="flightForm.numSeniors" :min="0" :max="10" - @change="handleChange" /> </el-form-item> </el-form-item> @@ -99,7 +97,7 @@ </el-form-item> <el-form-item div="Button"> - <el-button type="primary" @click.prevent="submitForm(ruleFormRef)" + <el-button type="primary" @click="submitForm(ruleFormRef)" >Search</el-button > <el-button @click="resetForm(ruleFormRef)">Cancel</el-button> @@ -111,14 +109,20 @@ </template> <script lang="ts" setup> +import { useRouter } from "vue-router"; import { reactive, ref } from "vue"; import type { FormInstance, FormRules } from "element-plus"; import { FlightFormModel } from "@/models/FlightFormModel"; +import { FlightResultModel } from "@/models/FlightResultModel"; import * as FlightTicketService from "@/services/FlightTicketService"; +import { useTicketStore } from "@/store/flightdataStore"; const size = ref("large"); const labelPosition = ref("left"); const ruleFormRef = ref<FormInstance>(); +const router = useRouter(); +const results = ref([] as FlightResultModel[]); +const ticketStore = useTicketStore(); let flightForm = ref(new FlightFormModel()); // const flightForm = reactive({ @@ -171,17 +175,25 @@ const rules = reactive<FormRules>({ ], }); + const submitForm = async (formEl: FormInstance | undefined) => { if (!formEl) return; await formEl.validate((valid, fields) => { if (valid) { - let result = FlightTicketService.createNewFlight(flightForm); + // let result = FlightTicketService.createNewFlight(flightForm); // TODO: Check the return result // console.log("Form Data before save in DB: ",flightForm.value); - FlightTicketService.createNewFlight(flightForm.value).then((data) => { - console.log("Save search flight data: ", data); - }); - console.log("submit!"); + + // FlightTicketService.createNewFlight(flightForm.value).then((response) => { + // + // console.log("Save search flight data: ", response.data); + // results.value = response.data; + // router.push("/flight-ticket"); + // }); + // const response = FlightTicketService.createNewFlight(flightForm.value); + // console.log("this is a "+response.data); + ticketStore.fetchResultTicket(flightForm.value); + router.push("/flight-ticket"); } else { console.log("error submit!", fields); } @@ -193,6 +205,7 @@ const resetForm = (formEl: FormInstance | undefined) => { if (!formEl) return; formEl.resetFields(); }; + </script> <style> diff --git a/FrontendFolder/switch-room/src/components/TicketPage.vue b/FrontendFolder/switch-room/src/components/TicketPage.vue new file mode 100644 index 0000000000000000000000000000000000000000..57c53c329258e235b6b5f08c01f5ffefc41da006 --- /dev/null +++ b/FrontendFolder/switch-room/src/components/TicketPage.vue @@ -0,0 +1,163 @@ +<template> + + <div class="container"> + <h1 class="upcomming">Flight Tickets</h1> + <div class="item" v-for="ticket in ticketStore.ticketList"> + <div class="item-right"> + <h2 class="sign"><font-awesome-icon icon="fa-solid fa-dollar-sign" /></h2> + <p class="price">{{ticket.price}}</p> + <span class="up-border"></span> + <span class="down-border"></span> + </div> <!-- end item-right --> + + <div class="item-left"> + <p class="airline">{{ticket.airlines}} Airline</p> + <h2 class="title">{{ticket.departure0}} <font-awesome-icon icon="fa-solid fa-right-left" /> {{ticket.arrival0}}</h2> + + <div class="sce"> + <div class="icon"> + <font-awesome-icon icon="fa-solid fa-calendar-days" /> + </div> + <p>To: {{ticket.departureDateTime_0}}-{{ticket.arrivalDateTime_0}}<br/> Return: {{ticket.departureDateTime_1}}-{{ticket.arrivalDateTime_1}}</p> + </div> + <div class="fix"></div> + <div class="loc"> + <div class="icon"> + <font-awesome-icon icon="fa-solid fa-plane-up" /> + </div> + <p>{{ticket.aircraftType_0}} <br/> Class: {{ticket.classOfService_0}}</p> + </div> + <div class="fix"></div> + <a v-bind:href="ticket.detail_url"> <button class="tickets">Buy it</button></a> + </div> <!-- end item-right --> + </div> <!-- end item --> + </div> +</template> + +<script setup lang="ts"> +import { useTicketStore } from "@/store/flightdataStore"; + +const ticketStore = useTicketStore(); + +</script> + +<style scoped> +* { + box-sizing: border-box; + margin:0; + padding:0; +} +body { + background:#DDD; + font-family: 'Cabin', sans-serif; +} +div.container { + max-width: 1350px; + margin: 0 auto; + overflow: hidden +} +.upcomming { + font-size: 45px; + text-transform: uppercase; + border-left: 14px solid rgba(255, 235, 59, 0.78); + padding-left: 12px; + margin: 18px 8px; +} +.container .item { + width: 48%; + float: left; + padding: 0 20px; + background: #fff; + overflow: hidden; + margin: 10px +} +.container .item-right, .container .item-left { + float: left; + padding: 20px +} +.container .item-right { + padding: 79px 50px; + margin-right: 20px; + width: 25%; + position: relative; + height: 286px +} +.container .item-right .up-border, .container .item-right .down-border { + padding: 14px 15px; + background-color: #ddd; + border-radius: 50%; + position: absolute +} +.container .item-right .up-border { + top: -8px; + right: -35px; +} +.container .item-right .down-border { + bottom: -13px; + right: -35px; +} +.container .item-right .sign { + font-size: 60px; + text-align: center; + color: #111 +} +.container .item-right .price, .container .item-left .airline { + color: #555; + font-size: 20px; + margin-bottom: 9px; +} +.container .item-right .price { + text-align: center; + font-size: 25px; +} +.container .item-left { + width: 71%; + padding: 34px 0px 19px 46px; + border-left: 3px dotted #999; +} +.container .item-left .title { + color: #111; + font-size: 34px; + margin-bottom: 12px +} +.container .item-left .sce { + margin-top: 5px; + display: block +} +.container .item-left .sce .icon, .container .item-left .sce p, +.container .item-left .loc .icon, .container .item-left .loc p{ + float: left; + word-spacing: 5px; + letter-spacing: 1px; + color: #555; + margin-bottom: 10px; + font-size: 15px; +} +.container .item-left .sce .icon, .container .item-left .loc .icon { + margin-right: 10px; + font-size: 20px; + color: #666 +} +.container .item-left .loc {display: block} +.fix {clear: both} +.container .item .tickets{ + color: #fff; + padding: 6px 14px; + float: right; + margin-top: 10px; + font-size: 18px; + border: none; + cursor: pointer +} +.container .item .tickets {background: #3D71E9} +@media only screen and (max-width: 1150px) { + .container .item { + width: 100%; + margin-right: 20px + } + div.container { + margin: 0 20px auto + } +} +</style> + diff --git a/FrontendFolder/switch-room/src/components/WishlistPage.vue b/FrontendFolder/switch-room/src/components/WishlistPage.vue index 24584eef3d3982fe73265b2fcfdfa734da73797e..335e338e814a914449651f6cfee4205925ee792c 100644 --- a/FrontendFolder/switch-room/src/components/WishlistPage.vue +++ b/FrontendFolder/switch-room/src/components/WishlistPage.vue @@ -1,7 +1,7 @@ <template> <el-row :gutter="20"> - <el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col> - <el-col :span="16"> + <el-col :span="2"><div class="grid-content ep-bg-purple" /></el-col> + <el-col :span="20"> <el-card style="width: 100%"> <template #header> <div class="card-header"> @@ -14,20 +14,28 @@ <!-- Aside Coding Area --> <el-aside width="300px"> <el-space direction="vertical" width="100%"> - <el-card - v-for="(item, index) in wishlistList.value" - shadow="hover" - style="width: 280px" - @click="selectItem(index)" + <el-badge class="item" + v-for="(item, index) in wishlistList.value" + :value="totalMatchCountList.value[index]" + :hidden = "totalMatchCountList.value[index] === 0" > - <span>{{ item.cityName }}</span> - <div class="bottom"> - <time class="time" - >"{{ item.startTime }} to {{ item.endTime }}"</time - > - </div> - </el-card> + <el-card + shadow="hover" + style="width: 280px" + @click="selectItem(index)" + > + <span>{{ item.cityName }}, {{ item.state }}</span> + <div class="bottom"> + <time class="time" + >"{{ formatDate(item.startTime) }} to {{ formatDate(item.endTime) }}"</time + > + </div> + </el-card> + + </el-badge> + <el-card + class="item" shadow="hover" style="width: 280px" @click="addNewWishlistItem()" @@ -54,15 +62,22 @@ <el-form-item></el-form-item> <el-form-item label="City Name:" prop="cityName"> - <el-input v-model="wishlistItem.cityName" /> + <el-input v-model="wishlistItem.cityName" :readonly="buttonState === 'edit'" /> </el-form-item> <el-form-item label="Zip Code:" prop="zipCode"> - <el-input v-model="wishlistItem.zipCode" /> + <el-input v-model="wishlistItem.zipCode" :readonly="buttonState === 'edit'"/> </el-form-item> - <el-form-item label="State:" prop="state"> - <el-input v-model="wishlistItem.state" /> + <el-form-item label="State:" prop="state"> + <el-select + v-model="wishlistItem.state" + placeholder="Please select state" + :disabled="buttonState === 'edit'" + > + <el-option v-for="(state_item, index) in USA_STATE_INITAL_LIST" + :label="state_item" :value="state_item" /> + </el-select> </el-form-item> <el-form-item label="Trip Start Time" required> @@ -73,6 +88,7 @@ label="Pick a date" placeholder="Pick a date" style="width: 100%" + :readonly="buttonState === 'edit'" /> </el-form-item> </el-form-item> @@ -85,6 +101,7 @@ label="Pick a date" placeholder="Pick a date" style="width: 100%" + :readonly="buttonState === 'edit'" /> </el-form-item> </el-form-item> @@ -94,21 +111,49 @@ v-model="wishlistItem.details" type="textarea" placeholder="Trip plan or want to visit places..." + :readonly="buttonState === 'edit'" /> </el-form-item> - <el-button type="primary" @click="submitForm(ruleFormRef)" - >Create</el-button - > - <el-button @click="resetForm(ruleFormRef)">Reset</el-button> + <el-button type="primary" v-if="buttonState === 'create'" @click="submitForm(ruleFormRef)" + >Create</el-button> + <el-button type="primary" v-if="buttonState === 'edit'" @click="onEdit(ruleFormRef)" + >Edit</el-button> + <el-button type="success" v-if="buttonState === 'update'" @click="updateForm(ruleFormRef)" + >Save</el-button> + <el-button type="danger" v-if="buttonState === 'update'" @click="deleteForm(ruleFormRef)"> + Delete</el-button> </el-form> </el-card> </el-main> + + <el-aside width="300px" > + <el-space direction="vertical" width="100%" :fill="true"> + <div class="card-header"> + <span v-if="wishlistItem.cityName.length>0">Matched Offer for: + {{wishlistItem.cityName}}, {{wishlistItem.state}} + </span> + </div> + <el-card + v-for="(item, index) in offerMatchList.value" + shadow="hover" + style="width: 280px" + @click="selectMatchOfferItem(index)" + > + <span> {{formatDate(item.startTime)}} - {{formatDate(item.endTime)}} </span> + <div> + <span class="o-zipCode"> Zip code: {{item.zipCode}} </span> + <br> + <span class="o-owner"> Host name: {{item.hostName}} </span> + </div> + </el-card> + </el-space> + </el-aside> </el-container> </div> </el-card> </el-col> - <el-col :span="4"><div class="grid-content ep-bg-purple" /></el-col> + <el-col :span="2"><div class="grid-content ep-bg-purple" /></el-col> </el-row> </template> @@ -117,6 +162,7 @@ import { reactive, ref, onMounted } from "vue"; import type { FormInstance, FormRules } from "element-plus"; import { WishlistItemModel } from "@/models/WishlistItemModel"; import * as WishlistService from "@/services/WishlistService"; +import { USA_STATE_INITAL_LIST } from "@/services/Constans"; const formSize = ref("default"); const ruleFormRef = ref<FormInstance>(); @@ -161,30 +207,73 @@ const rules = reactive<FormRules>({ ], }); -// TODO: uncomment it when API connection ready -const userId = 233; const wishlistList = reactive({value:[]}); const wishlistItem = ref(new WishlistItemModel()); wishlistItem.value.wishlistItemId = null; +let buttonState = ref("create"); +const selectedIdx = ref(0); +const offerMatchList = reactive({value:[]}); +const totalMatchCountList = reactive({value:[]}); onMounted(() => { - WishlistService.getUserWishlistInfo(userId).then((data)=>{ + selectedIdx.value = 0; + wishlistInitLoad(); +}) + +const onEdit = () =>{ + buttonState.value = "update"; +} + +const wishlistInitLoad = async() =>{ + await WishlistService.getUserWishlistInfo().then((data)=>{ console.log("Receiving wishlist list data", data); - if(data["data"] === null){ + if(data["data"] === null || data["data"].length === 0){ wishlistList.value = []; + buttonState.value = "create"; }else{ - wishlistList.value wishlistList.value = data["data"]; + selectItem(0); + buttonState.value = "edit"; + loadMatchNumbersForAllList(data["data"]); } }) +} -}) +const loadMatchNumbersForAllList = async (wishlistItemList: Array<WishlistItemModel>) =>{ + await WishlistService.loadMatchOfferNumbersFor(wishlistItemList).then((data) =>{ + console.log("Receiving offer match count list data: ", data); + if(data["data"] === null || data["data"].length === 0){ + for(let i=0; i<totalMatchCountList.value.length; ++i){ + totalMatchCountList.value.push(0); + } + }else{ + totalMatchCountList.value = data["data"]; + } + }) +} +const loadOfferMatchList = async () =>{ + await WishlistService.getOfferMatchList(wishlistItem.value.wishlistItemId).then((data) =>{ + console.log("Receiving offer match list data: ", data); + if(data["data"] === null || data["data"].length === 0){ + offerMatchList.value = []; + }else{ + offerMatchList.value = data["data"]; + } + }) +} +const formatDate = (dateString) => { + const date = new Date(dateString); + return new Intl.DateTimeFormat('en-US').format(date); +} function selectItem(idx: number) { console.log("Selected wishlist Item: " + idx); wishlistItem.value = reactive(wishlistList.value[idx]); + buttonState.value = "edit"; + selectedIdx.value = idx; + loadOfferMatchList(wishlistItem.value.wishlistItemId); } function addNewWishlistItem() { @@ -192,18 +281,21 @@ function addNewWishlistItem() { console.log("addNewWishlistItem"); wishlistItem.value = new WishlistItemModel(); wishlistItem.value.wishlistItemId = null; + buttonState.value = "create"; } + const submitForm = async (formEl: FormInstance | undefined) => { if (!formEl) return; await formEl.validate((valid, fields) => { if (valid) { - WishlistService.createNewWishlistItem( userId, wishlistItem.value).then((data)=>{ + WishlistService.createNewWishlistItem( wishlistItem.value).then((data)=>{ console.log("Receiving create wishlist item data", data); if(data["data"] === null){ // TODO: create failed, handle it later }else{ wishlistItem.value = data["data"]; wishlistList.value.push(data["data"]); + buttonState.value = "edit"; } }) console.log("submit!"); @@ -214,18 +306,38 @@ const submitForm = async (formEl: FormInstance | undefined) => { }); }; -const resetForm = (formEl: FormInstance | undefined) => { - // TODO: make it delete to testing, need to change it back - // if (!formEl) return; - // formEl.resetFields(); +const updateForm = async (formEl: FormInstance | undefined) =>{ + if (!formEl) return; + await formEl.validate((valid, fields) => { + if (valid) { + WishlistService.createNewWishlistItem( wishlistItem.value).then((data)=>{ + console.log("Receiving wishlist item data", data); + if(data["data"] === null){ + // TODO: create failed, handle it later + }else{ + wishlistItem.value = data["data"]; + wishlistList.value[selectedIdx] = data["data"]; + buttonState.value = "edit"; + } + console.log("submit!"); + }) + } else { + console.log("error submit!", fields); + } + console.log(wishlistItem.value); + }); +} + +const deleteForm = async (formEl: FormInstance | undefined) => { if(wishlistItem.value.wishlistItemId !== null){ - WishlistService.deleteWishlistItem(userId, wishlistItem.value.wishlistItemId).then((data)=>{ + await WishlistService.deleteWishlistItem( wishlistItem.value.wishlistItemId).then((data)=>{ console.log("Delete wishlist item", data); if(data["data"] === null){ // TODO: delete failed or something wrong }else{ wishlistList.value=data["data"]; addNewWishlistItem(); + loadMatchNumbersForAllList(data["data"]); } }) } @@ -258,4 +370,14 @@ const options = Array.from({ length: 10000 }).map((_, idx) => ({ .main-card { padding: 0 0 0 20px; } +.o-zipCode { + font-size: 12px; +} +.o-owner { + font-size: 12px; +} +.item { + margin-top: 10px; + margin-right: 40px; +} </style> diff --git a/FrontendFolder/switch-room/src/main.ts b/FrontendFolder/switch-room/src/main.ts index 68591da5af4259d026dea2f926d0cf00825238bb..d14914943134c533744c0aef973384a2b5f55c73 100644 --- a/FrontendFolder/switch-room/src/main.ts +++ b/FrontendFolder/switch-room/src/main.ts @@ -1,5 +1,7 @@ import { createApp } from "vue"; import ElementPlus from "element-plus"; +import store from "./store/index"; +import * as cookieParser from "cookie-parser"; import "element-plus/dist/index.css"; import App from "./App.vue"; import router from "./router"; @@ -23,6 +25,9 @@ import { faLinkedin } from "@fortawesome/free-brands-svg-icons"; import { faGithub } from "@fortawesome/free-brands-svg-icons"; + +import {createPinia} from "pinia"; + /* add icons to the library */ library.add(faUserSecret); library.add(fas); @@ -30,9 +35,13 @@ library.add(faFacebook); library.add(faTwitter); library.add(faLinkedin); library.add(faGithub); +const pinia = createPinia(); + createApp(App) .component("font-awesome-icon", FontAwesomeIcon) .use(router) .use(ElementPlus) + .use(store) + .use(pinia) .mount("#app"); diff --git a/FrontendFolder/switch-room/src/models/FlightResultModel.ts b/FrontendFolder/switch-room/src/models/FlightResultModel.ts new file mode 100644 index 0000000000000000000000000000000000000000..b1a610d9e7f1d34235da2f62893fef574958d4d8 --- /dev/null +++ b/FrontendFolder/switch-room/src/models/FlightResultModel.ts @@ -0,0 +1,44 @@ +export class FlightResultModel { + public id : number; + public departure0: string; + public arrival0: string; + public departureDateTime_0: string; + public arrivalDateTime_0: string; + public classOfService_0: string; + public aircraftType_0: string; + public distance_0: number; + + public departure1: string; + public arrival1: string; + public departureDateTime_1: string; + public arrivalDateTime_1: string; + public classOfService_1: string; + public aircraftType_1: string; + public distance_1: number; + + public price: number; + public airlines: string; + public detail_url: string; + + + constructor(id: number, departure0: string, arrival0: string, departureDateTime_0: string, arrivalDateTime_0: string, classOfService_0: string, aircraftType_0: string, distance_0: number, departure1: string, arrival1: string, departureDateTime_1: string, arrivalDateTime_1: string, classOfService_1: string, aircraftType_1: string, distance_1: number, price: number, airlines: string, detail_url: string) { + this.id = id; + this.departure0 = departure0; + this.arrival0 = arrival0; + this.departureDateTime_0 = departureDateTime_0; + this.arrivalDateTime_0 = arrivalDateTime_0; + this.classOfService_0 = classOfService_0; + this.aircraftType_0 = aircraftType_0; + this.distance_0 = distance_0; + this.departure1 = departure1; + this.arrival1 = arrival1; + this.departureDateTime_1 = departureDateTime_1; + this.arrivalDateTime_1 = arrivalDateTime_1; + this.classOfService_1 = classOfService_1; + this.aircraftType_1 = aircraftType_1; + this.distance_1 = distance_1; + this.price = price; + this.airlines = airlines; + this.detail_url = detail_url; + } +} diff --git a/FrontendFolder/switch-room/src/models/OfferFormModel.ts b/FrontendFolder/switch-room/src/models/OfferFormModel.ts index 891d7b5cea584ec2e1e3c17e7fa2d48f3f6b72b8..938328063bc60eeb45cc2851048a63f629a10568 100644 --- a/FrontendFolder/switch-room/src/models/OfferFormModel.ts +++ b/FrontendFolder/switch-room/src/models/OfferFormModel.ts @@ -1,10 +1,12 @@ export class OfferFormModel { public readonly userId: number; + public state: string; + public stateCode: number; public zipCode: number | string; public spaceType: string; public otherSpaceType: string; public spaceLocateCity: string; - public cityCode: number; + public stateCityCode: number; public availableTimeStart: Date | string; public availableTimeEnd: Date | string; public maxNumberOfPeople: number; @@ -13,11 +15,13 @@ export class OfferFormModel { constructor( userId = -1, + state = "", + stateCode = -1, zipCode = "", spaceType = "", otherSpaceType = "", spaceLocateCity = "", - cityCode = 0, + stateCityCode = -1, offering = false, availableTimeStart = new Date(), availableTimeEnd = new Date(), @@ -25,11 +29,13 @@ export class OfferFormModel { spaceDetails = "" ) { this.userId = userId; + this.state = state; + this.stateCode = stateCode; this.zipCode = zipCode; this.spaceType = spaceType; this.otherSpaceType = otherSpaceType; this.spaceLocateCity = spaceLocateCity; - this.cityCode = cityCode; + this.stateCityCode = stateCityCode; this.offering = offering; this.availableTimeStart = availableTimeStart; this.availableTimeEnd = availableTimeEnd; diff --git a/FrontendFolder/switch-room/src/models/OfferNotification.ts b/FrontendFolder/switch-room/src/models/OfferNotification.ts new file mode 100644 index 0000000000000000000000000000000000000000..a9af3a6f2485d4a6be6acdc012c479f77e8cf3df --- /dev/null +++ b/FrontendFolder/switch-room/src/models/OfferNotification.ts @@ -0,0 +1,21 @@ +export class OfferNotification { + public offerId: number; + public wishlistItemId: number; + public modifyDate: Date; + public offerResult: number; + public wishlistResult: number; + + constructor( + offerId = -1, + wishlistItemId = -1, + modifyDate = new Date(), + offerResult = -1, + wishlistResult = -1 + ) { + this.offerId = offerId; + this.wishlistItemId = wishlistItemId; + this.modifyDate = modifyDate; + this.offerResult = offerResult; + this.wishlistResult = wishlistResult; + } +} diff --git a/FrontendFolder/switch-room/src/models/OfferPageWishlistMatchRequestInfo.ts b/FrontendFolder/switch-room/src/models/OfferPageWishlistMatchRequestInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..763f787145abfa0443c08de8cca3c85a431bc2c9 --- /dev/null +++ b/FrontendFolder/switch-room/src/models/OfferPageWishlistMatchRequestInfo.ts @@ -0,0 +1,27 @@ +export class OfferPageWishlistMatchRequestInfo { + wishlistOwnerId: number; + wishlistId: number; + startTime: Date; + endTime: Date; + wishListOwnerName: string; + hasOffer: boolean; + zipCode: number; + + constructor(wishlistOwnerId = -1, + wishlistId = -1, + startTime = new Date(), + endTime = new Date(), + wishListOwnerName = "", + hasOffer = false, + zipCode = -1) { + this.wishlistOwnerId = wishlistOwnerId; + this.wishlistId = wishlistId; + this.startTime = startTime; + this.endTime = endTime; + this.wishListOwnerName = wishListOwnerName; + this.hasOffer = hasOffer; + this.zipCode = zipCode; + } + + +} \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/models/WishlistItemModel.ts b/FrontendFolder/switch-room/src/models/WishlistItemModel.ts index dca073a57f6151b661f992a68581f66abdf49a8c..a537cef3283d49ec13c42d6fb58616f44ebe0148 100644 --- a/FrontendFolder/switch-room/src/models/WishlistItemModel.ts +++ b/FrontendFolder/switch-room/src/models/WishlistItemModel.ts @@ -1,9 +1,10 @@ export class WishlistItemModel { public readonly wishlistItemId: number; public cityName: string; + public stateCityCode: number; public zipCode: number| string public state: string; - public stateCode: number | string; + public stateCode: number; public startTime: Date | string; public endTime: Date | string; public details: string; @@ -12,7 +13,8 @@ export class WishlistItemModel { wishlistItemId = -1, cityName = "", state = "", - stateCode = "", + stateCityCode = -1, + stateCode = -1, startTime = "", endTime = "", details = "", @@ -22,6 +24,7 @@ export class WishlistItemModel { this.cityName = cityName; this.state = state; this.stateCode = stateCode; + this.stateCityCode = stateCityCode; this.startTime = startTime; this.endTime = endTime; this.details = details; diff --git a/FrontendFolder/switch-room/src/models/WishlistPageOfferMatchRequestInfo.ts b/FrontendFolder/switch-room/src/models/WishlistPageOfferMatchRequestInfo.ts new file mode 100644 index 0000000000000000000000000000000000000000..471d70302d36a209df463cb0d3a2d31993ddb55f --- /dev/null +++ b/FrontendFolder/switch-room/src/models/WishlistPageOfferMatchRequestInfo.ts @@ -0,0 +1,25 @@ +export class WishlistPageOfferMatchRequestInfo { + offerOwnerId: number; + wishListId: number; + startTime: Date; + endTime: Date; + zipCode: number; + hostName: string; + + constructor(offerOwnerId =-1, + wishListId = -1, + startTime = new Date(), + endTime = new Date(), + zipCode = -1, + hostName = "" + ) { + this.offerOwnerId = offerOwnerId; + this.wishListId = wishListId; + this.startTime = startTime; + this.endTime = endTime; + this.zipCode = zipCode; + this.hostName = hostName; + } + + +} \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/router/index.ts b/FrontendFolder/switch-room/src/router/index.ts index 68e81a0fe93ee3d119eec884d3b916558759121a..4ac42f6213ea79c1a4d94c5ec29fe9e4d05ede12 100644 --- a/FrontendFolder/switch-room/src/router/index.ts +++ b/FrontendFolder/switch-room/src/router/index.ts @@ -1,27 +1,53 @@ import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router"; import HomeView from "../views/HomeView.vue"; import RegisterView from "../views/RegisterView.vue"; +import ResetPasswordView from "../views/ResetPasswordView.vue"; +import ProfileView from "../views/ProfileView.vue"; +import MatchedView from "../views/MatchedView.vue"; const routes: Array<RouteRecordRaw> = [ { path: "/", name: "home", component: HomeView, + meta: { + hideHeader: true, + }, }, { path: "/register", name: "register", component: RegisterView, + meta: { + hideHeader: true, + }, + }, + { + path: "/resetPassword", + name: "resetPassword", + meta: { + requiresAuth: true, + hideHeader: false, + }, + component: ResetPasswordView, }, { path: "/offer-page", name: "OfferPage", + meta: { + requiresAuth: true, + hideHeader: false, + }, component: () => import(/* webpackChunkName: "about" */ "../views/OfferView.vue"), }, { path: "/wishlist-page", name: "WishlistPage", + meta: { + requiresAuth: true, + hideHeader: false, + }, component: () => import(/* webpackChunkName: "about" */ "../views/WishlistView.vue"), }, @@ -31,15 +57,43 @@ const routes: Array<RouteRecordRaw> = [ // route level code-splitting // this generates a separate chunk (about.[hash].js) for this route // which is lazy-loaded when the route is visited. + meta: { + requiresAuth: true, + hideHeader: false, + }, component: () => import(/* webpackChunkName: "about" */ "../views/FlightView.vue"), }, + { + path: "/profile", + name: "profile", + meta: { + requiresAuth: true, + hideHeader: false, + }, + component: ProfileView, + }, { path: "/login-main-page", name: "LoginMainPage", + meta: { + requiresAuth: true, + hideHeader: false, + }, + component: () => import("../views/LoginMainPageView.vue"), + }, + + { + path: "/matched-result/:offerId/:wishlistId", + name: "MatchResultPage", + component: MatchedView, + }, + { + path: "/flight-ticket", + name: "TicketPage", component: () => - import("../views/LoginMainPageView.vue"), - } + import(/* webpackChunkName: "about" */ "../views/FlightTicket.vue"), + }, ]; const router = createRouter({ @@ -47,4 +101,16 @@ const router = createRouter({ routes, }); +router.beforeEach((to, from, next) => { + if (to.matched.some((record) => record.meta.requiresAuth)) { + if (!document.cookie) { + next({ name: "home" }); + } else { + next(); + } + } else { + next(); + } +}); + export default router; diff --git a/FrontendFolder/switch-room/src/services/Constans.ts b/FrontendFolder/switch-room/src/services/Constans.ts index 8c8936a3fbf2918b9ac8af231c4fc34dce9cf837..b032e5f6c56fa6f34c0ece6f867cf24e627fef56 100644 --- a/FrontendFolder/switch-room/src/services/Constans.ts +++ b/FrontendFolder/switch-room/src/services/Constans.ts @@ -3,11 +3,71 @@ */ const SEVER_IP = location.hostname; + const SERVER_PORT = 8081; const Server_URL = location.protocol + "//" + SEVER_IP + ":" + SERVER_PORT + "/"; -export { SEVER_IP, SERVER_PORT, Server_URL }; +const USA_STATE_INITAL_LIST = [ + "AL", + "AK", + "AS", + "AZ", + "AR", + "CA", + "CO", + "CT", + "DE", + "DC", + "FL", + "GA", + "GU", + "HI", + "ID", + "IL", + "IN", + "IA", + "KS", + "KY", + "LA", + "ME", + "MD", + "MA", + "MI", + "MN", + "MS", + "MO", + "MT", + "NE", + "NV", + "NH", + "NJ", + "NM", + "NY", + "NC", + "ND", + "MP", + "OH", + "OK", + "OR", + "PA", + "PR", + "RI", + "SC", + "SD", + "TN", + "TX", + "UT", + "VT", + "VA", + "VI", + "WA", + "WV", + "WI", + "WY", +]; + +export { SEVER_IP, SERVER_PORT, Server_URL, USA_STATE_INITAL_LIST }; /* Change logs: diff --git a/FrontendFolder/switch-room/src/services/FetchOfferService.ts b/FrontendFolder/switch-room/src/services/FetchOfferService.ts new file mode 100644 index 0000000000000000000000000000000000000000..428a2b0629103b8102e23538d58fa1511e1d6aed --- /dev/null +++ b/FrontendFolder/switch-room/src/services/FetchOfferService.ts @@ -0,0 +1,10 @@ +import * as serverHttpService from "./ServerHttpService"; + +const baseUrl = "matchedOffer"; + +function getOffersFromServerWithID(offerId: number) { + const urlPath = "/" + offerId; + return serverHttpService.Get(baseUrl + urlPath); +} + +export { getOffersFromServerWithID }; diff --git a/FrontendFolder/switch-room/src/services/FetchWishListService.ts b/FrontendFolder/switch-room/src/services/FetchWishListService.ts new file mode 100644 index 0000000000000000000000000000000000000000..db9eda30612cdafde08ce653b89a00d58fe8d8ec --- /dev/null +++ b/FrontendFolder/switch-room/src/services/FetchWishListService.ts @@ -0,0 +1,10 @@ +import * as serverHttpService from "./ServerHttpService"; + +const baseUrl = "matchedWishList"; + +function getWishListFromServerWithID(wishListId: number) { + const urlPath = "/" + wishListId; + return serverHttpService.Get(baseUrl + urlPath); +} + +export { getWishListFromServerWithID }; diff --git a/FrontendFolder/switch-room/src/services/NotificationService.ts b/FrontendFolder/switch-room/src/services/NotificationService.ts new file mode 100644 index 0000000000000000000000000000000000000000..fa8bbace6cac91779a61a09415bf183c9bf28f19 --- /dev/null +++ b/FrontendFolder/switch-room/src/services/NotificationService.ts @@ -0,0 +1,15 @@ +import * as serverHttpService from "./ServerHttpService"; + +const baseUrl = "notification"; + +function getNotificationFromServerWithID(userId: number) { + const urlPath = "/" + userId; + return serverHttpService.Get(baseUrl + urlPath); +} + +function getNotificationwishFromServerWithID(userId: number) { + const urlPath = "/wish/" + userId; + return serverHttpService.Get(baseUrl + urlPath); +} + +export { getNotificationFromServerWithID, getNotificationwishFromServerWithID }; diff --git a/FrontendFolder/switch-room/src/services/OfferPageService.ts b/FrontendFolder/switch-room/src/services/OfferPageService.ts index 618095a0a27b23b406e3e1d372c1f80cb3836a74..277f08368fd9ff2257608bf13e3625b966e3ee4f 100644 --- a/FrontendFolder/switch-room/src/services/OfferPageService.ts +++ b/FrontendFolder/switch-room/src/services/OfferPageService.ts @@ -11,8 +11,8 @@ const baseUrl = "offer"; * Get offer with userID * @param userId the current user's id */ -export async function getUserOfferInfoFromServer(userId: number) { - return serverHttpService.Get(baseUrl + "/" + userId); +export async function getUserOfferInfoFromServer() { + return serverHttpService.Get(baseUrl); } /** @@ -38,14 +38,19 @@ export function updateOffer(offerForm: OfferFormModel) { * @param userId user's id * @param offerId form id */ -export function deleteOffer(userId: number) { - const urlPath = baseUrl + "/deleteOffer/" + userId; +export function deleteOffer(userId : number) { + const urlPath = baseUrl + "/deleteOffer/"+userId; return serverHttpService.Delete(urlPath); } +export function getWishlistMatchRequestInfoList(){ + const urlPath = baseUrl + "/wishlistMatchRequestInfoList" + return serverHttpService.Get(urlPath); +} + /* Change logs: Date | Author | Description 2022-10-20 | Fangzheng Zhang | create class and init - +2022-11-03 | Fangzheng Zhang | Remove user Id in parameter because cookie is ready */ diff --git a/FrontendFolder/switch-room/src/services/ServerHttpService.ts b/FrontendFolder/switch-room/src/services/ServerHttpService.ts index ea93675e01d8d8767e377c938f54c0e8396075dc..14c4c3f247daf8efcbf8b4d295f94a967e75f428 100644 --- a/FrontendFolder/switch-room/src/services/ServerHttpService.ts +++ b/FrontendFolder/switch-room/src/services/ServerHttpService.ts @@ -11,6 +11,7 @@ function Get(path: string) { return fetch(url, { method: "GET", + credentials: "include", }) .then((response) => { if (response.ok) { @@ -31,6 +32,7 @@ function Post(path: string, bodyData: any) { console.log("With Data: ", JSON.stringify(bodyData)); return fetch(url, { method: "POST", + credentials: "include", headers: { "Content-Type": "application/json", }, diff --git a/FrontendFolder/switch-room/src/services/UserService.ts b/FrontendFolder/switch-room/src/services/UserService.ts index b3a43ab224a34cba0194e2da6efc3844f4a6b8c6..77fed4b449d1a2805195905150257e14c38d5664 100644 --- a/FrontendFolder/switch-room/src/services/UserService.ts +++ b/FrontendFolder/switch-room/src/services/UserService.ts @@ -11,5 +11,23 @@ function loginUser(userData: any) { // console.log(Server_URL + baseUrl + urlPath); return serverHttpService.Post(baseUrl + urlPath, JSON.parse(userData)); } +function checkLoginSession() { + const urlPath = "/checkLoginSession"; + return serverHttpService.Get(baseUrl + urlPath); +} +function resetPassword(userData: any) { + const urlPath = "/resetPassword"; + return serverHttpService.Post(baseUrl + urlPath, JSON.parse(userData)); +} +function getProfile() { + const urlPath = "/profile"; + return serverHttpService.Get(baseUrl + urlPath); +} -export { postUserDataToServer, loginUser }; +export { + postUserDataToServer, + loginUser, + checkLoginSession, + resetPassword, + getProfile +}; diff --git a/FrontendFolder/switch-room/src/services/WishlistService.ts b/FrontendFolder/switch-room/src/services/WishlistService.ts index 5d0451f0bb6e3779669be3567128d410e47822e0..9225343f09b617f86e87435d38ed315228368fc6 100644 --- a/FrontendFolder/switch-room/src/services/WishlistService.ts +++ b/FrontendFolder/switch-room/src/services/WishlistService.ts @@ -9,19 +9,17 @@ const baseUrl = "wishlist"; /** * This function will fetch users wishlist item list - * @param userId user's id */ -export function getUserWishlistInfo(userId: number) { - return serverHttpService.Get(baseUrl + "/" + userId); +export function getUserWishlistInfo() { + return serverHttpService.Get(baseUrl); } /** * Send new wishlistItem information to server to create new record - * @param userId user's id * @param wishlistItem new wishlistItem */ -export function createNewWishlistItem( userId: number, wishlistItem: WishlistItemModel ) { - return serverHttpService.Post(baseUrl + "/newWishlistItem/"+userId, wishlistItem); +export function createNewWishlistItem( wishlistItem: WishlistItemModel ) { + return serverHttpService.Post(baseUrl + "/newWishlistItem", wishlistItem); } /** @@ -38,20 +36,31 @@ export function updateWishlistItem(wishlistItem: WishlistItemModel) { * @param userId user's id * @param wishlistItemId wishlist Item's Id */ -export function deleteWishlistItem(userId: number, wishlistItemId: number) { +export function deleteWishlistItem(wishlistItemId: number) { const urlPath = baseUrl + "/deleteWishlistItem/" + - userId + - "?wishlistItemId=" + wishlistItemId; return serverHttpService.Delete(urlPath); } +export function getOfferMatchList(wishlistItemId: number){ + const urlPath = baseUrl + "/loadOfferMatchList/"+wishlistItemId; + return serverHttpService.Get(urlPath); +} + +export function loadMatchOfferNumbersFor(wishlistItemList: Array<WishlistItemModel>){ + let wishlistIdList : Array<number>= []; + wishlistItemList.forEach((wishlistItem) => { + wishlistIdList.push(wishlistItem.wishlistItemId); + }) + const urlPath = baseUrl + "/loadOfferMatchCount"; + return serverHttpService.Post(urlPath, wishlistIdList); +} /* Change logs: Date | Author | Description 2022-10-20 | Fangzheng Zhang | create class and init - +2022-11-05 | Fangzheng Zhang | Add loadMatchOfferNumbersFor and getOfferMatchList to fetch match data */ diff --git a/FrontendFolder/switch-room/src/store/flightdataStore.ts b/FrontendFolder/switch-room/src/store/flightdataStore.ts new file mode 100644 index 0000000000000000000000000000000000000000..42b770f3ac34ec316bd5bf5e835f76f3a6eed54c --- /dev/null +++ b/FrontendFolder/switch-room/src/store/flightdataStore.ts @@ -0,0 +1,23 @@ +import { defineStore } from "pinia"; +import { FlightResultModel } from "@/models/FlightResultModel"; +import { FlightFormModel } from "@/models/FlightFormModel"; +import * as FlightTicketService from "@/services/FlightTicketService"; + + +export const useTicketStore = defineStore("TicketStore", { + //state + //options + //getters + state: () => ({ + ticketList: [] as FlightResultModel[], + }), + actions: { + async fetchResultTicket(flightModel: FlightFormModel) { + await FlightTicketService.createNewFlight(flightModel).then((response) => { + + console.log("Save search flight data: ", response.data); + this.ticketList = response.data; + }); + }, + }, +}); \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/store/index.ts b/FrontendFolder/switch-room/src/store/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..e9519f47c946b10f43ad9c938d1b765983166baa --- /dev/null +++ b/FrontendFolder/switch-room/src/store/index.ts @@ -0,0 +1,10 @@ +import { createStore } from "vuex"; +import authModule from "./modules/auth"; + +const store = createStore({ + modules: { + auth: authModule, + }, +}); + +export default store; diff --git a/FrontendFolder/switch-room/src/store/modules/auth.ts b/FrontendFolder/switch-room/src/store/modules/auth.ts new file mode 100644 index 0000000000000000000000000000000000000000..62609e6dc883ecdb0078891be57cdaf14fca98f6 --- /dev/null +++ b/FrontendFolder/switch-room/src/store/modules/auth.ts @@ -0,0 +1,43 @@ +import * as UserService from "../../services/UserService"; + +const state = () => ({ + loginStatus: false, +}); + +const getters = { + getLoginStatus(state: any) { + return state.loginStatus; + }, +}; + +const actions = { + async loginApi({ commit }: any, payload: any) { + const response = await UserService.loginUser(JSON.stringify(payload)).catch( + (error) => alert(error) + ); + if (response.status == "OK") { + commit("setLoginStatus", true); + } else { + alert(response.message); + } + }, + logOutApi({ commit }: any) { + // todo: clear session in backend + console.log("logOut"); + commit("setLoginStatus", false); + }, +}; + +const mutations = { + setLoginStatus(state: any, data: any) { + state.loginStatus = data; + }, +}; + +export default { + namespaced: true, + state, + getters, + actions, + mutations, +}; diff --git a/FrontendFolder/switch-room/src/views/FlightTicket.vue b/FrontendFolder/switch-room/src/views/FlightTicket.vue new file mode 100644 index 0000000000000000000000000000000000000000..8b86303df4ca6d2ba3674f7fd98900d2ba2486f2 --- /dev/null +++ b/FrontendFolder/switch-room/src/views/FlightTicket.vue @@ -0,0 +1,11 @@ +<template> +<ticket-page></ticket-page> +</template> + +<script setup lang="ts"> +import TicketPage from "@/components/TicketPage.vue"; +</script> + +<style scoped> + +</style> \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/views/LoginMainPageView.vue b/FrontendFolder/switch-room/src/views/LoginMainPageView.vue index 548ebc671b1c0edc7ee8441cd36ad0d32d103a87..1502ffb97752765dfd2b86edb25bd0e423552964 100644 --- a/FrontendFolder/switch-room/src/views/LoginMainPageView.vue +++ b/FrontendFolder/switch-room/src/views/LoginMainPageView.vue @@ -1,8 +1,40 @@ <template> - <main-page></main-page> + <main-page></main-page> </template> <script setup lang="ts"> import MainPage from "@/components/LoginMainPage.vue"; +import AppHeader from "@/components/AppHeader.vue"; +import { watch } from "vue"; +import { useRoute } from "vue-router"; +const route = useRoute(); +import { ref } from "vue"; +import { OfferNotification } from "@/models/OfferNotification"; +import * as NotificationService from "@/services/NotificationService"; + +// function getCookie(userId: string) { +// const value = `; ${document.cookie}`; +// const parts: string[] = value.split(`; ${userId}=`); +// if (parts.length === 2) return parts?.pop()?.split(";").shift(); +// } +// +// const offers = ref([] as OfferNotification[]); +// async function fetchNotification(userId: number) { +// const response = await NotificationService.getNotificationFromServerWithID( +// userId +// ); +// console.log(response); +// offers.value = response.data; +// } +// +// watch( +// () => route.name, +// (values) => { +// const userIdString: string = getCookie("userId"); +// let userIdNumber = +userIdString; +// console.log(userIdNumber); +// fetchNotification(userIdNumber); +// }, +// { immediate: true } +// ); </script> - \ No newline at end of file diff --git a/FrontendFolder/switch-room/src/views/MatchedView.vue b/FrontendFolder/switch-room/src/views/MatchedView.vue new file mode 100644 index 0000000000000000000000000000000000000000..641b85322d138e981295efb15fd422ca156c2742 --- /dev/null +++ b/FrontendFolder/switch-room/src/views/MatchedView.vue @@ -0,0 +1,159 @@ +<template> + <div class="match-view"> + <p>This is the matched result page</p> + <match-offer :offer="offer"></match-offer> + <match-wish-list :wishlist="wishlist"></match-wish-list> + <button>Sign Agreement Documentation</button> + <button>It is not suitable</button> + </div> +</template> + +<script setup lang="ts"> +import { ref, watch } from "vue"; +import { useRoute } from "vue-router"; +import * as FetchOfferService from "@/services/FetchOfferService"; +import * as FetchWishListService from "@/services/FetchWishListService"; +import { OfferFormModel } from "@/models/OfferFormModel"; +import { WishlistItemModel } from "@/models/WishlistItemModel"; +import MatchOffer from "@/components/MatchOffer.vue"; +import MatchWishList from "@/components/MatchWishList.vue"; + +const route = useRoute(); +const offer = ref(OfferFormModel); +const wishlist = ref(WishlistItemModel); + +async function fetchOffer(offerId: number) { + const response = await FetchOfferService.getOffersFromServerWithID(offerId); + console.log(response.data); + offer.value = response.data; +} + +async function fetchWishList(wishlistId: number) { + const response = await FetchWishListService.getWishListFromServerWithID( + wishlistId + ); + console.log(response.data); + wishlist.value = response.data; +} + +watch( + () => route.name, + (values) => { + const offerIdName = route.params.offerId; + const offerIdNumber = +offerIdName; + const wishlistIdName = route.params.wishlistId; + const wishlistIdNumber = +wishlistIdName; + fetchOffer(offerIdNumber); + fetchWishList(wishlistIdNumber); + }, + { immediate: true } +); +</script> + +<style scoped> +.match-view { + display: block; + text-align: center; +} + +.button1 { + margin-top: 2%; + margin-left: 2%; +} + +button, +button::after { + margin-top: 2%; + margin-left: 2%; + width: 300px; + height: 86px; + font-size: 36px; + font-family: "Bebas Neue", cursive; + background: #1e5be6; + border: 0; + color: #fff; + letter-spacing: 3px; + line-height: 88px; + box-shadow: 6px 0px 0px #00e6f6; + outline: transparent; + position: relative; +} + +button::after { + --slice-0: inset(50% 50% 50% 50%); + --slice-1: inset(80% -6px 0 0); + --slice-2: inset(50% -6px 30% 0); + --slice-3: inset(10% -6px 85% 0); + --slice-4: inset(40% -6px 43% 0); + --slice-5: inset(80% -6px 5% 0); + + content: "AVAILABLE NOW"; + display: block; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: linear-gradient( + 45deg, + transparent 3%, + #00e6f6 3%, + #00e6f6 5%, + #ff013c 5% + ); + text-shadow: -3px -3px 0px #f8f005, 3px 3px 0px #00e6f6; + clip-path: var(--slice-0); +} + +button:hover::after { + animation: 1s glitch; + animation-timing-function: steps(2, end); +} + +@keyframes glitch { + 0% { + clip-path: var(--slice-1); + transform: translate(-20px, -10px); + } + 10% { + clip-path: var(--slice-3); + transform: translate(10px, 10px); + } + 20% { + clip-path: var(--slice-1); + transform: translate(-10px, 10px); + } + 30% { + clip-path: var(--slice-3); + transform: translate(0px, 5px); + } + 40% { + clip-path: var(--slice-2); + transform: translate(-5px, 0px); + } + 50% { + clip-path: var(--slice-3); + transform: translate(5px, 0px); + } + 60% { + clip-path: var(--slice-4); + transform: translate(5px, 10px); + } + 70% { + clip-path: var(--slice-2); + transform: translate(-10px, 10px); + } + 80% { + clip-path: var(--slice-5); + transform: translate(20px, -10px); + } + 90% { + clip-path: var(--slice-1); + transform: translate(-10px, 0px); + } + 100% { + clip-path: var(--slice-1); + transform: translate(0); + } +} +</style> diff --git a/FrontendFolder/switch-room/src/views/ProfileView.vue b/FrontendFolder/switch-room/src/views/ProfileView.vue new file mode 100644 index 0000000000000000000000000000000000000000..fa0ed4652331ea5833f876defa5a7650e0c9963e --- /dev/null +++ b/FrontendFolder/switch-room/src/views/ProfileView.vue @@ -0,0 +1,7 @@ +<template> + <Profile-Page></Profile-Page> +</template> + +<script setup lang="ts"> +import ProfilePage from "@/components/ProfilePage.vue"; +</script> diff --git a/FrontendFolder/switch-room/src/views/ResetPasswordView.vue b/FrontendFolder/switch-room/src/views/ResetPasswordView.vue new file mode 100644 index 0000000000000000000000000000000000000000..2c56067f177519c9de0ed9d42474b2d44caa5fbf --- /dev/null +++ b/FrontendFolder/switch-room/src/views/ResetPasswordView.vue @@ -0,0 +1,7 @@ +<template> + <ResetPassword-Page></ResetPassword-Page> +</template> + +<script setup lang="ts"> +import ResetPasswordPage from "@/components/ResetPasswordPage.vue"; +</script> diff --git a/FrontendFolder/switch-room/tsconfig.json b/FrontendFolder/switch-room/tsconfig.json index b9dc703cfd8386d7c8e1eb0eb5e55edef576b33e..ab6cca7ec0bcfc482ef06b7d6f68ac2a7754d3b7 100644 --- a/FrontendFolder/switch-room/tsconfig.json +++ b/FrontendFolder/switch-room/tsconfig.json @@ -33,8 +33,8 @@ "src/**/*.tsx", "src/**/*.vue", "tests/**/*.ts", - "tests/**/*.tsx" - ], + "tests/**/*.tsx", + "src/store/index.ts",], "exclude": [ "node_modules" ] diff --git a/flightapi.json b/flightapi.json new file mode 100644 index 0000000000000000000000000000000000000000..a9fd7bd6585bc73b73cbb927d9f6adf2bab49a3a --- /dev/null +++ b/flightapi.json @@ -0,0 +1,1047 @@ +{ + "status": true, + "message": "Success", + "timestamp": 1667401694167, + "data": { + "session": { + "searchHash": "9974755e28e834b916b632b996600560", + "pageLoadUid": "93a5b4b8-d5ed-45a4-bef7-7a06d068bc4d", + "searchId": "af0e2d98-b24c-43ca-932f-bf4400898499.394" + }, + "complete": false, + "numOfFilters": 10, + "totalNumResults": 10, + "flights": [ + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T06:00:00+05:30", + "arrivalDateTime": "2022-12-01T08:05:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 954, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T21:55:00+05:30", + "arrivalDateTime": "2022-12-09T23:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 981, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|11", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|11&area=FLTCenterColumn|0|1|ItinList|1|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T06:30:00+05:30", + "arrivalDateTime": "2022-12-01T08:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 928, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T21:55:00+05:30", + "arrivalDateTime": "2022-12-09T23:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 981, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|8", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|8&area=FLTCenterColumn|0|1|ItinList|2|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T06:00:00+05:30", + "arrivalDateTime": "2022-12-01T08:05:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 954, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T09:30:00+05:30", + "arrivalDateTime": "2022-12-09T11:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 927, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|5", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|5&area=FLTCenterColumn|0|1|ItinList|3|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T06:30:00+05:30", + "arrivalDateTime": "2022-12-01T08:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 928, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T09:30:00+05:30", + "arrivalDateTime": "2022-12-09T11:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 927, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|6", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|6&area=FLTCenterColumn|0|1|ItinList|4|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T07:30:00+05:30", + "arrivalDateTime": "2022-12-01T09:40:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 930, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T21:55:00+05:30", + "arrivalDateTime": "2022-12-09T23:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 981, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|9", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|9&area=FLTCenterColumn|0|1|ItinList|5|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T17:35:00+05:30", + "arrivalDateTime": "2022-12-01T19:45:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Boeing 737-800 (winglets)", + "amenities": [], + "flightNumber": 910, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T21:55:00+05:30", + "arrivalDateTime": "2022-12-09T23:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 981, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|1", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|1&area=FLTCenterColumn|0|1|ItinList|6|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T22:45:00+05:30", + "arrivalDateTime": "2022-12-02T00:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 986, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T21:55:00+05:30", + "arrivalDateTime": "2022-12-09T23:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 981, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|4", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|4&area=FLTCenterColumn|0|1|ItinList|7|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T07:30:00+05:30", + "arrivalDateTime": "2022-12-01T09:40:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 930, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T09:30:00+05:30", + "arrivalDateTime": "2022-12-09T11:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 927, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|7", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|7&area=FLTCenterColumn|0|1|ItinList|8|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T17:35:00+05:30", + "arrivalDateTime": "2022-12-01T19:45:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Boeing 737-800 (winglets)", + "amenities": [], + "flightNumber": 910, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T09:30:00+05:30", + "arrivalDateTime": "2022-12-09T11:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 927, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|2", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|2&area=FLTCenterColumn|0|1|ItinList|9|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + }, + { + "segments": [ + { + "legs": [ + { + "originStationCode": "BOM", + "isDifferentOriginStation": false, + "destinationStationCode": "DEL", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-01T14:40:00+05:30", + "arrivalDateTime": "2022-12-01T16:55:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 944, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + }, + { + "legs": [ + { + "originStationCode": "DEL", + "isDifferentOriginStation": false, + "destinationStationCode": "BOM", + "isDifferentDestinationStation": false, + "departureDateTime": "2022-12-09T09:30:00+05:30", + "arrivalDateTime": "2022-12-09T11:35:00+05:30", + "classOfService": "ECONOMY", + "marketingCarrierCode": "UK", + "operatingCarrierCode": "UK", + "equipmentId": "Airbus A320-100/200", + "amenities": [], + "flightNumber": 927, + "seatGuruEquipmentId": 0, + "seatGuruAirlineUrl": "", + "numStops": 0, + "distanceInKM": 1138.1091, + "isInternational": false, + "selfTransfer": false, + "operatingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + }, + "marketingCarrier": { + "locationId": 8729207, + "code": "UK", + "logoUrl": "https://static.tacdn.com/img2/flights/airlines/logos/100x100/Vistara.png", + "displayName": "Vistara" + } + } + ], + "layovers": [] + } + ], + "purchaseLinks": [ + { + "purchaseLinkId": "Kayak|1|3", + "providerId": "Vistara", + "partnerSuppliedProvider": { + "id": "UK", + "displayName": "Vistara", + "logoUrl": "https://content.r9cdn.net/rimg/provider-logos/airlines/h/UK.png?crop=false&width=166&height=62&fallback=default2.png&_v=70673476ce3854369868788c913f25cc" + }, + "commerceName": "KayakFlightsMeta", + "currency": "USD", + "originalCurrency": "USD", + "seatAvailability": 0, + "taxesAndFees": 0, + "taxesAndFeesPerPassenger": 0, + "totalPrice": 540.78, + "totalPricePerPassenger": 540.78, + "fareBasisCodes": [], + "containedPurchaseLinks": [], + "partnerData": {}, + "isPaid": false, + "fareAttributesList": [], + "url": "https://www.tripadvisor.com/CheapFlightsPartnerHandoff?searchHash=9974755e28e834b916b632b996600560&provider=Kayak|1|3&area=FLTCenterColumn|0|1|ItinList|10|Meta_ItineraryPrice&resultsServlet=CheapFlightsSearchResults&handoffPlatform=desktop&impressionId=&totalPricePerPassenger=540.78" + } + ] + } + ] + } +} \ No newline at end of file